Skip to content

Commit 2d59f34

Browse files
gustavoliraclaude
andauthored
feat(install-dynamic-plugins): add @red-hat-developer-hub/cli-module-install-dynamic-plugins (#3246)
* feat(install-dynamic-plugins): import package from redhat-developer/rhdh 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> * fix(install-dynamic-plugins): address self-review feedback - 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> * fix(install-dynamic-plugins): unblock CI api-reports step 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> * chore(install-dynamic-plugins): address SonarCloud findings - 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> * refactor(install-dynamic-plugins): parse argv with cleye 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> * refactor(install-dynamic-plugins): adopt cli-module convention while 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> * refactor(install-dynamic-plugins): drop custom esbuild bundle, ship unbundled cli-module Replaces the dual build (esbuild bundle + backstage-cli) with plain `backstage-cli package build` — the standard Backstage cli-module pattern. The bundle and the keytar gymnastics only existed to satisfy RHDH's init-container `COPY` of a single self-contained `.cjs`; that consumption model is moving to `npm install` (redhat-developer/rhdh#4908), so the single-file requirement is going away. What's left in the package: - `src/installer.ts`: install pipeline + `main(args, programName)`. - `src/index.ts`: `createCliModule(...)` default export, registers the `install` command. Discovered by `backstage-cli` when this package is a dependency of a host project. - `src/command.ts`: thin loader that calls `installer.main`. - `bin/install-dynamic-plugins`: fast-path shim that loads `dist/installer.cjs.js` directly and runs `main(process.argv.slice(2))`, bypassing `@backstage/cli-node`'s `runCliModule` dispatch — saves ~80 ms of cold start for direct/`npx`/init-container invocations. The cli-module discovery path still goes through `runCliModule` and pays the dispatch cost where it belongs. Removed: - `esbuild.config.mjs` (the custom bundle config) - `src/cli.ts` (the esbuild entry) - `dist/install-dynamic-plugins.cjs` from `files` (no longer produced) - `esbuild` devDependency 166/166 tests pass; tsc/lint/prettier/api-reports clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(install-dynamic-plugins): drop stale references to the esbuild bundle The README and the createCliModule docstring still referenced the self-contained bundle that the package no longer ships: - `npm run build` description and committed-bundle CI-check section. - `node install-dynamic-plugins.cjs "$1"` wrapper line in "How RHDH consumes it" — replaced with a pointer to redhat-developer/rhdh#4908. - `src/index.ts` describing the bin path as the bundle. - Source layout was missing `index.ts` / `command.ts` / `installer.ts`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(install-dynamic-plugins): address PR review — align with upstream cli-module contract Applies the four findings from @schultzp2020's review: - package.json reshaped to match the upstream `@backstage/cli-module-*` contract: `main`/`types` point at `src/index.ts` for local dev, `publishConfig` overrides with `dist/index.cjs.js`/`dist/index.d.ts`, `prepack` and `postpack` wire up `backstage-cli package prepack/postpack` so the published artifact has the correct entry points, and `files` is `["bin", "dist"]` (the old `dist/**/*.js` glob excluded `.d.ts`). - bin wrapper now uses `@backstage/cli-node/config/nodeTransform.cjs` (the cli-node variant) instead of `@backstage/cli/config/nodeTransform.cjs`. `@backstage/cli` is only a devDependency — the previous import would have broken `npx`/installed-package invocations. - bin wrapper now goes through `runCliModule(...)` like every other `@backstage/cli-module-*` package. The earlier fast path bypassed it to save ~80 ms of cold start, but per @schultzp2020 that cost is one-time per init-container run (not per-plugin), and going through the standard dispatch gives us `--version`/`--help` and future runCliModule improvements for free. - installer.ts: added an `isPlainObject` guard for the parsed main config, mirroring the existing guard on include files. A YAML scalar or array in `dynamic-plugins.yaml` now throws a clear `InstallException` instead of a confusing downstream `TypeError`. Also drops the stale `install-dynamic-plugins.sh` wrapper from the package — it pointed at the long-removed esbuild bundle and was no longer listed in `files`. 166/166 tests pass; tsc/lint/prettier/api-reports clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5c9083a commit 2d59f34

55 files changed

Lines changed: 24070 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "main",
9+
"updateInternalDependencies": "patch"
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/cli-module-install-dynamic-plugins': minor
3+
---
4+
5+
Initial release. TypeScript/Node.js port of the RHDH init-container installer (originally Python; see [redhat-developer/rhdh#4574](https://github.com/redhat-developer/rhdh/pull/4574)), packaged as a Backstage CLI module. The `install` command is registered through `createCliModule` so the package is auto-discovered by `backstage-cli` when listed as a dependency. The package also ships a self-contained esbuild bundle as its `bin`, so direct `npx install-dynamic-plugins <dir>` invocations (and RHDH's init-container `COPY` of the `.cjs`) stay fast and don't require `@backstage/cli-node` at runtime. Env vars, on-disk layout, `plugin-hash` format, and tar/OCI security guards are byte-compatible with the previous Python implementation.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../.eslintrc.cjs');
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# macOS
2+
.DS_Store
3+
4+
# Logs
5+
logs
6+
*.log
7+
npm-debug.log*
8+
yarn-debug.log*
9+
yarn-error.log*
10+
lerna-debug.log*
11+
12+
# Coverage directory generated when running tests with coverage
13+
coverage
14+
15+
# Dependencies
16+
node_modules/
17+
18+
# Yarn files
19+
.pnp.*
20+
.yarn/*
21+
!.yarn/patches
22+
!.yarn/plugins
23+
!.yarn/releases
24+
!.yarn/sdks
25+
!.yarn/versions
26+
27+
# Node version directives
28+
.nvmrc
29+
30+
# dotenv environment variables file
31+
.env
32+
.env.test
33+
34+
# Build output
35+
dist
36+
dist-types
37+
38+
# Temporary change files created by Vim
39+
*.swp
40+
41+
# MkDocs build output
42+
site
43+
44+
# Local configuration files
45+
*.local.yaml
46+
47+
# Sensitive credentials
48+
*-credentials.yaml
49+
50+
# vscode database functionality support files
51+
*.session.sql
52+
53+
# E2E test reports
54+
e2e-test-report/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist
2+
dist-types
3+
coverage
4+
.vscode
5+
.eslintrc.js
6+
**/dist/install-dynamic-plugins.cjs
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "@internal/install-dynamic-plugins",
3+
"version": "0.0.0",
4+
"private": true,
5+
"engines": {
6+
"node": "22 || 24"
7+
},
8+
"workspaces": {
9+
"packages": [
10+
"packages/*"
11+
]
12+
},
13+
"scripts": {
14+
"tsc": "tsc",
15+
"tsc:full": "tsc --skipLibCheck true --incremental false",
16+
"build:all": "backstage-cli repo build --all",
17+
"build:api-reports": "yarn build:api-reports:only --tsc",
18+
"build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags",
19+
"build:knip-reports": "backstage-repo-tools knip-reports",
20+
"clean": "backstage-cli repo clean",
21+
"test": "backstage-cli repo test",
22+
"test:all": "backstage-cli repo test --coverage",
23+
"fix": "backstage-cli repo fix",
24+
"lint": "backstage-cli repo lint --since origin/main",
25+
"lint:all": "backstage-cli repo lint",
26+
"prettier:check": "prettier --check .",
27+
"prettier:fix": "prettier --write .",
28+
"new": "backstage-cli new --scope @red-hat-developer-hub",
29+
"postinstall": "cd ../../ && yarn install"
30+
},
31+
"prettier": "@spotify/prettier-config",
32+
"lint-staged": {
33+
"*.{js,jsx,ts,tsx,mjs,cjs}": [
34+
"eslint --fix",
35+
"prettier --write"
36+
],
37+
"*.{json,md}": [
38+
"prettier --write"
39+
]
40+
},
41+
"dependencies": {
42+
"@backstage/cli": "^0.36.0",
43+
"@backstage/cli-defaults": "0.1.0",
44+
"@backstage/repo-tools": "^0.17.0",
45+
"@changesets/cli": "^2.27.1",
46+
"@jest/environment-jsdom-abstract": "^30.3.0",
47+
"@spotify/prettier-config": "^15.0.0",
48+
"jest": "^30.3.0",
49+
"jsdom": "^27.1.0",
50+
"prettier": "^3.4.2",
51+
"typescript": "~5.8.0"
52+
},
53+
"packageManager": "yarn@4.12.0"
54+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
build/
3+
coverage/
4+
*.log
5+
.yarn/
6+
dist/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
coverage/
3+
build/
4+
dist/
5+
package-lock.json

0 commit comments

Comments
 (0)