Skip to content

chore(install-dynamic-plugins): consume installer from npm#4908

Open
gustavolira wants to merge 3 commits into
redhat-developer:mainfrom
gustavolira:chore/install-dynamic-plugins-npm-install
Open

chore(install-dynamic-plugins): consume installer from npm#4908
gustavolira wants to merge 3 commits into
redhat-developer:mainfrom
gustavolira:chore/install-dynamic-plugins-npm-install

Conversation

@gustavolira
Copy link
Copy Markdown
Member

@gustavolira gustavolira commented Jun 1, 2026

Summary

Swaps the COPY of scripts/install-dynamic-plugins/{install-dynamic-plugins.cjs,install-dynamic-plugins.sh} for an npm install of @red-hat-developer-hub/cli-module-install-dynamic-plugins@0.1.0 (built and published out of redhat-developer/rhdh-plugins#3254 / the unbundled successor feat/install-dynamic-plugins-cli-module-unbundled).

Lets the rhdh-plugins side use the standard backstage-cli package build (the cli-module convention) and drops a couple of workarounds on its side: no custom esbuild bundling step, no keytar native-binary stub.

Containerfile change

  • Install into /opt/dynamic-plugins-installer/ (its own dir, no package.json) so npm cannot reach into /opt/app-root/src — that path is the yarn workspace root and npm install there would honor workspaces and reshuffle the production tree built by yarn workspaces focus.
  • --no-save --omit=dev so npm doesn't write package-lock.json into the installer dir.
  • Exact version pin 0.1.0 so image builds stay reproducible.
  • Build-time install-dynamic-plugins --help smoke check so a missing or renamed CLI entrypoint fails the image build instead of the pod.
  • install-dynamic-plugins.sh shim at the original /opt/app-root/src/ path delegates to node_modules/.bin/install-dynamic-plugins (npm-managed symlink) so the Helm chart and Operator init-container spec keep working unchanged.

Cold-start benchmark (final)

On an empty dynamic-plugins.yaml, macOS, hyperfine, 50 runs each, warm cache:

Variant Bundle / install size Median σ
Bundled .cjs (current main) 231 KB single file 59 ms ±3 ms
npm install cli-module + dispatch 25 MB node_modules 176 ms ±21 ms
npm install cli-module + fast-path bin (this PR's target) 25 MB node_modules 101 ms ±15 ms

The fast-path bin (in the rhdh-plugins unbundled branch) bypasses @backstage/cli-node's runCliModule dispatch for direct invocation — saves ~75 ms of cold start. Net cost vs the current bundled approach is ~42 ms per pod start, dwarfed by the actual skopeo / npm pack work that follows.

Hermetic build (Konflux / hermeto)

scripts/local-hermeto-build.sh:213 calls hermeto fetch-deps with rpm, yarn, yarn ./dynamic-plugins, and pip — no npm. Downstream Konflux runs with enableNetwork: false, so as-is this PR's RUN npm install ... will fail in the hermetic build.

hermeto does support npm prefetch (needs package-lock.json v2+). To land this PR end-to-end, the gating work is:

  1. A package-lock.json somewhere in this repo describing the installer + transitive deps
  2. Adding {"type": "npm", "path": "<lockfile-dir>"} to the hermeto fetch-deps invocation in scripts/local-hermeto-build.sh and the equivalent midstream config (see sync-midstream.sh)
  3. The RUN npm install in this Containerfile needs to read from /cachi2/output/deps/npm/ (likely via injected env vars from generate-env)

Happy to do that work in a follow-up commit on this PR (or split into a separate prep PR) — flagging now so reviewers can weigh the cost. If the hermeto-npm wiring is too costly, the alternative is to keep the COPY pattern but pull the bundled .cjs from the published npm tarball during sync-midstream.sh — that keeps the hermetic build trivial and gives us the upstream-published source of truth.

Follow-ups

  • Delete scripts/install-dynamic-plugins/ from this repo once @red-hat-developer-hub/cli-module-install-dynamic-plugins is published and consumed.
  • Wire npm into hermeto fetch-deps (per "Hermetic build" above).

🤖 Generated with Claude Code

Replaces the COPY of scripts/install-dynamic-plugins/{install-dynamic-plugins.cjs,
install-dynamic-plugins.sh} with an `npm install` of
@red-hat-developer-hub/cli-module-install-dynamic-plugins (built and published
out of redhat-developer/rhdh-plugins).

This unblocks the cli-module structure on the rhdh-plugins side — it lets
that package use the standard `backstage-cli package build` (unbundled,
multi-file dist) instead of a custom esbuild bundle with a keytar stub. See
the conversation context: redhat-developer/rhdh-plugins#3254

Backward compatibility is preserved by writing a tiny
`/opt/app-root/src/install-dynamic-plugins.sh` shim that delegates to the
npm-installed bin, so the Helm chart and Operator init-container spec
continue to invoke `./install-dynamic-plugins.sh /dynamic-plugins-root`
unchanged.

DRAFT — DO NOT MERGE: blocked on
redhat-developer/rhdh-plugins#3254 (or the unbundled successor) being
merged and published to npm. Opened for review of the consumption pattern
and to back the cold-start benchmark posted in Slack.

Trade-off summary (cold-start benchmark on empty config):

- Current (bundled .cjs, 231 KB single file): ~89 ms warm cache (median)
- Proposed (npm install, 25 MB node_modules): ~180 ms warm cache (median)

The ~90 ms gap is the module-resolution overhead of unbundled Node — paid
once per pod start. Image build time also gets +`npm install` of ~25 MB
(one extra layer), offset by deleting ~7000 lines of vendored installer
script from this repo in a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Jun 1, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.25%. Comparing base (c1acb63) to head (15a52a7).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4908      +/-   ##
==========================================
- Coverage   55.82%   55.25%   -0.58%     
==========================================
  Files         121      109      -12     
  Lines        2350     2132     -218     
  Branches      562      536      -26     
==========================================
- Hits         1312     1178     -134     
+ Misses       1032      954      -78     
+ Partials        6        0       -6     
Flag Coverage Δ
rhdh 55.25% <ø> (-0.58%) ⬇️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c1acb63...15a52a7. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… step

- Install into /opt/dynamic-plugins-installer (its own dir, no package.json)
  instead of /opt/app-root/src so npm cannot honor the yarn workspace and
  perturb the production tree that `yarn workspaces focus` built at line 208.
- Delegate the shim to `node_modules/.bin/install-dynamic-plugins` (the
  symlink npm creates from the package's bin field) instead of reaching
  into the package's internal layout.
- Add `--no-save --omit=dev` so npm doesn't write a package-lock.json into
  the installer dir and doesn't fetch devDependencies.
- Pin the installer to an exact version (0.1.0) so image builds are
  reproducible.
- Add a build-time smoke check (`install-dynamic-plugins --help`) so a
  missing or renamed CLI entrypoint fails the image build instead of the
  init container at pod start.

The hermetic-build concern (npm reaching the public registry when this
Containerfile runs under Konflux with networking disabled) is acknowledged
separately in the PR description — it's the real gating work and is not
addressed by this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

The container image build workflow finished with status: cancelled.

The unbundled cli-module variant ships a fast-path bin that calls the
installer directly (bypassing @backstage/cli-node's runCliModule dispatch),
so the published binary takes the dynamic-plugins-root as a positional
without a subcommand prefix — matching the original CLI surface.

Verified locally:
  $ /opt/dynamic-plugins-installer/node_modules/.bin/install-dynamic-plugins /dynamic-plugins-root
exits 0 on an empty config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gustavolira gustavolira marked this pull request as ready for review June 1, 2026 15:35
@gustavolira gustavolira changed the title [DRAFT] chore(install-dynamic-plugins): consume installer from npm chore(install-dynamic-plugins): consume installer from npm Jun 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

The container image build workflow finished with status: cancelled.

@openshift-ci openshift-ci Bot requested review from JessicaJHee and PatAKnight June 1, 2026 15:35
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 1, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

The container image build workflow finished with status: failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant