feat(install-dynamic-plugins): add @red-hat-developer-hub/cli-module-install-dynamic-plugins#3246
Conversation
Migrates scripts/install-dynamic-plugins/ from redhat-developer/rhdh#4574 into this repo as @red-hat-developer-hub/install-dynamic-plugins so it can be published to npm and consumed by the RHDH init-container without curl-by-SHA. Runtime contract (CLI args, env vars, plugin-hash format, on-disk layout, tar/OCI security guards) preserved verbatim. Build remains a single self-contained .cjs via esbuild. Tests migrated from vitest to jest to align with the repo's backstage-cli pipeline (14 suites / 166 tests pass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This pull request adds a new top-level directory under |
|
Important This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior. Changed Packages
|
Code Review by Qodo
1.
|
Review Summary by QodoAdd @red-hat-developer-hub/install-dynamic-plugins workspace with TypeScript/Node.js port
WalkthroughsDescription• Imports scripts/install-dynamic-plugins/ from redhat-developer/rhdh as a new workspace package @red-hat-developer-hub/install-dynamic-plugins for npm publication • Implements TypeScript/Node.js port of the original Python init-container CLI with byte-compatible behavior • Core functionality: materializes plugins on disk from dynamic-plugins.yaml config, supporting OCI (via skopeo) and NPM sources • Preserves runtime contract unchanged — same CLI surface, environment variables, and plugin hash values for in-place upgrades • Implements comprehensive security guards: path-traversal rejection, zip-bomb detection, link-target containment, and allowed-type whitelisting in tar-extract.ts and catalog-index.ts • Migrated test suite from vitest to jest with 14 test suites and 166 passing tests • Configured single bundled .cjs via esbuild for init-container image compatibility • Nested workspace layout (workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/) matches monorepo structure and release pipeline • Implements concurrency control with semaphore-based worker management for OCI and NPM installations • Includes OCI image caching, manifest handling, pull policy support (IfNotPresent/Always), and registry fallback logic • Provides SRI integrity verification for NPM packages with streaming hash computation • Implements lock file management with signal cleanup to prevent concurrent installs Diagramflowchart LR
A["Python init-container<br/>install-dynamic-plugins.py"] -->|"Port to TypeScript/Node.js"| B["@red-hat-developer-hub/<br/>install-dynamic-plugins"]
B --> C["CLI orchestration<br/>index.ts"]
C --> D["Config merging<br/>merger.ts"]
C --> E["OCI installation<br/>installer-oci.ts"]
C --> F["NPM installation<br/>installer-npm.ts"]
E --> G["Image caching<br/>image-cache.ts"]
E --> H["Skopeo wrapper<br/>skopeo.ts"]
F --> I["Integrity verification<br/>integrity.ts"]
E --> J["Secure extraction<br/>tar-extract.ts"]
F --> J
C --> K["Catalog index<br/>catalog-index.ts"]
K --> H
C --> L["Lock management<br/>lock-file.ts"]
C --> M["Finalization<br/>finalize-install.ts"]
B -->|"Published to npm"| N["RHDH init-container<br/>consumes via npm install"]
File Changes1. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts
|
- Add bin/install-dynamic-plugins shim and have package.json bin point at it (matches the convention used by extensions-cli, translations-cli, and rhdh-repo-tools). Split src/cli.ts as the esbuild entry so the bundle no longer needs the require.main guard or a shebang banner. - Stop committing dist/install-dynamic-plugins.cjs; the release pipeline rebuilds via the customBuild path, and a new prepack script makes yarn npm publish self-healing for local runs. - Drop the .js suffix from relative imports across src/ so the package matches the rest of the repo and the jest moduleNameMapper workaround is no longer needed. - Consolidate the tsconfigs: the inner package extends the workspace tsconfig and only declares what differs. - Add why-it's-intentional comments to the two eslint-disable lines (PullPolicy const+type pair, tar.x filter inside a sequential loop). - README now leads with the npm/npx usage path; the RHDH init-container section is below. tar/yaml stay in dependencies (not devDependencies as the review suggested) — @backstage/no-undeclared-imports flagged the source imports, and the repo convention treats bundling as opaque. 166/166 tests pass, tsc/lint/prettier clean, bin shim and bundle both exit 0 on the empty-config smoke run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI step "check api reports and generate API reference" runs
`backstage-repo-tools api-reports --ci` before the build, and that tool
requires the bin file to introspect the CLI. The previous shim did a
plain `require('../dist/install-dynamic-plugins.cjs')`, which failed
under CI because dist/ is no longer committed and the build hasn't run
yet.
- Switch the bin shim to the local-vs-installed pattern used by every
other CLI in the repo (extensions-cli, translations-cli,
rhdh-repo-tools): when `src/` exists (monorepo), load TS directly via
`@backstage/cli/config/nodeTransform`; otherwise require the built
bundle (npm-installed scenario).
- Add `--help` / `-h` handling to main() so the api-reports tool can
introspect the CLI usage without creating a stray `--help/` directory.
- Commit the generated `cli-report.md`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3246 +/- ##
==========================================
+ Coverage 53.23% 53.37% +0.13%
==========================================
Files 2413 2434 +21
Lines 86358 87397 +1039
Branches 23897 24156 +259
==========================================
+ Hits 45975 46647 +672
- Misses 40049 40416 +367
Partials 334 334
*This pull request uses carry forward flags. Click here to find out more. Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
- Use String.raw for strings containing backslash literals so the source
reads with one '\' instead of '\\\\' (catalog-index.ts log message,
extra-catalog-index.test.ts subdirectory fixtures, skopeo.test.ts shell
escape).
- Switch the OCI regex builder to a joined string array — eliminates the
nested template literals SonarCloud was flagging on oci-key.ts (and
reads much better).
- Object.prototype.hasOwnProperty.call -> Object.hasOwn in
merger.test.ts (ES2022, available since Node 16.9).
- String#replace(/'/g, ...) -> String#replaceAll("'", ...) in
skopeo.test.ts (ES2021).
- Hoist test helpers (stageLayer, fakeImageCache) out of their describe
blocks so they aren't re-defined on every test.
- Drop the redundant parseMaxEntrySize(undefined) call in types.test.ts —
the parameter already defaults to process.env.MAX_ENTRY_SIZE.
166/166 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the hand-rolled `process.argv` + USAGE-string handling in main() to `cleye` — the same parser every `@backstage/cli-module-*` package uses (already in our transitive deps). Aligns with the Backstage CLI convention requested during PR review. Existing surface preserved: - positional `<dynamic-plugins-root>` (required, exit 1 if absent) - `--help` / `-h` prints usage and exits 0 - normal run still exits with the installer's status code Bundle grew from 226 kB -> 267 kB (cleye + type-flag minified). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…keeping the bundled bin Aligns with the @backstage/cli-module-* convention without losing the self-contained bundled artifact: - Rename to @red-hat-developer-hub/cli-module-install-dynamic-plugins, backstage.role: cli-module. - src/installer.ts holds the install pipeline and main() (formerly src/index.ts). - New src/index.ts default-exports `createCliModule(...)` registering an `install` command whose loader is src/command.ts. Exposes the package through backstage-cli discovery — `backstage-cli install <dir>` works when the package is a dependency. - src/cli.ts (the esbuild entry) keeps invoking installer.main() directly, so the bundled .cjs stays self-contained: no @backstage/cli-node and no keytar gymnastics in the bin path. - Build is dual now — `backstage-cli package build && node esbuild.config.mjs`. backstage-cli emits dist/index.cjs.js (the cli-module export) and the unbundled supporting modules; esbuild emits dist/install-dynamic-plugins.cjs (the standalone bin). Both are published. - bin shim's installed branch now requires the bundled .cjs explicitly rather than going through `main` — that keeps direct/npx/init-container invocations at ~60 ms cold start instead of paying the cli-module dispatch cost. - main() now takes optional `args` and `programName` so the cli-module loader can pass the command's argv slice and have `--help` print the real invocation (`install-dynamic-plugins install …`). - @backstage/cli-node added as a runtime dependency. It is only loaded by the cli-module discovery path; the bundled bin never imports it. 166/166 tests pass; tsc/lint/prettier/api-reports clean. Bundle size unchanged at ~267 KB. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|



Summary
Imports
scripts/install-dynamic-plugins/from redhat-developer/rhdh#4574 into this repo and packages it as a Backstage CLI module —@red-hat-developer-hub/cli-module-install-dynamic-plugins— so the overlay repo (and any other RHDH consumer) cannpm installit instead of vendoring the script.Originally opened as
@red-hat-developer-hub/install-dynamic-plugins(rolecli); restructured to follow the cli-module convention requested during review while keeping the self-contained bundle that the RHDH init-containerCOPYs today.Two faces, one install pipeline
bin/install-dynamic-plugins→ bundled.cjs.npx, RHDH's init-containerCOPY, and any other standalone invocation hit the esbuild-bundleddist/install-dynamic-plugins.cjs. ~60 ms cold start, nonode_modulesrequired at runtime, no@backstage/cli-nodein the bundle.main: dist/index.cjs.js→createCliModule(...). When a host project lists this package as a dependency,backstage-cliauto-discovers theinstallcommand.backstage-cli install <dynamic-plugins-root>works out of the box.Both share
src/installer.ts, so there is one install pipeline regardless of how the command is invoked. Thecli-modulerole +createCliModule+ the renamed package follow the@backstage/cli-module-*convention; the bundled bin keeps the standalone story intact.What's preserved verbatim
MAX_ENTRY_SIZE,SKIP_INTEGRITY_CHECK,CATALOG_INDEX_IMAGE,EXTRA_CATALOG_INDEX_IMAGES,DYNAMIC_PLUGINS_WORKERS,DYNAMIC_PLUGINS_NPM_WORKERS,DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS,CATALOG_ENTITIES_EXTRACT_DIR).plugin-hash.ts: byte-compatible with the previous Python implementation — existing RHDH installs upgrade in place.tar-extract.tsandcatalog-index.tsuntouched.What changed during the move
@backstage/cli-module-*uses internally).src/installer.ts(install pipeline +main()),src/index.ts(createCliModuledefault export),src/command.ts(loader for theinstallcommand),src/cli.ts(esbuild entry).backstage-cli package build && node esbuild.config.mjs.backstage-cliemits the cli-module dist; esbuild emits the standalone bundle. Both ship in the npm tarball (filesincludesdist/**/*.jsanddist/install-dynamic-plugins.cjs).backstage-cli package test. Mostly mechanical migration from the original vitest setup (dropvi.spyOn→jest.spyOn, co-locate tests intosrc/). 14 suites / 166 tests pass.Trade-offs vs the original
role: cliversioncli)cli-moduledual)@backstage/cli-module-*npm install)@backstage/cli-node+ transitives, ~25 MB)The extra install footprint is paid by consumers that resolve the package via
npm install— RHDH's init-containerCOPYpath doesn't touchnode_modules.Test plan
yarn tsc/lint/prettier:check/build:api-reports:only --cicleannode dist/install-dynamic-plugins.cjs <dir>exits 0 on an empty configrequire(dist/index.cjs.js).default.$$type === '@backstage/CliModule'🤖 Generated with Claude Code