Skip to content

feat(install-dynamic-plugins): add @red-hat-developer-hub/install-dynamic-plugins#3246

Open
gustavolira wants to merge 5 commits into
redhat-developer:mainfrom
gustavolira:feat/install-dynamic-plugins
Open

feat(install-dynamic-plugins): add @red-hat-developer-hub/install-dynamic-plugins#3246
gustavolira wants to merge 5 commits into
redhat-developer:mainfrom
gustavolira:feat/install-dynamic-plugins

Conversation

@gustavolira
Copy link
Copy Markdown
Member

Summary

Imports scripts/install-dynamic-plugins/ from redhat-developer/rhdh#4574 into this repo as a new workspace, so the package can be published to npm as @red-hat-developer-hub/install-dynamic-plugins. Once on npm, RHDH's init-container will consume it via npm install instead of the current curl-by-SHA pattern; a follow-up PR in redhat-developer/rhdh will remove scripts/install-dynamic-plugins/ and update the Containerfile.

The package is the TypeScript/Node.js port of the original Python install-dynamic-plugins.py init-container CLI. It materializes plugins on disk from a dynamic-plugins.yaml config, supporting OCI (via skopeo) and NPM sources.

What's preserved verbatim

The runtime contract is unchanged — RHDH installs upgrade in place:

  • CLI surface: same positional arg <dynamic-plugins-root>, same env vars (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 — same plugin_hash values, so existing dynamic-plugins-root directories don't trigger a full reinstall.
  • Security guards: tar-extract.ts (path-traversal, zip-bomb, link-target containment, allowed-type whitelist) and catalog-index.ts (isSafeSubdirectoryName, layer scanning) untouched.

What changed during the move

  • Layout: nested workspace (workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/) to match every other workspace and the release pipeline.
  • Build: single bundled .cjs via esbuild is preserved (hard requirement of the init-container image). backstage-cli repo build --all invokes our custom build script via the customBuild path.
  • Tests: migrated from vitest to jest to align with backstage-cli repo test. Mostly mechanical (vi.spyOnjest.spyOn, drop vitest type import, co-locate tests into src/). One for/if/else block refactored into it.each to satisfy jest/no-conditional-expect. 14 suites / 166 tests pass (no test removed or skipped).
  • Prettier: dropped the package-local .prettierrc.js in favour of the repo-wide @spotify/prettier-config.
  • ESLint: added .eslintrc.js extending the repo root config; a few inline eslint-disable comments on intentional patterns (PullPolicy const+type, tar filter inside loop).

Test plan

  • CI Workspace install-dynamic-plugins, CI step (node 22 + node 24) passes
  • CI Workspace install-dynamic-plugins, Verify step passes (lockfile + changeset verification)
  • Local yarn install + yarn tsc + yarn test + yarn workspace @red-hat-developer-hub/install-dynamic-plugins build clean
  • Local smoke run: node dist/install-dynamic-plugins.cjs <empty-dir-with-empty-config> exits 0

Related

🤖 Generated with Claude Code

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>
@gustavolira gustavolira requested review from a team as code owners May 27, 2026 19:09
@github-actions
Copy link
Copy Markdown
Contributor

This pull request adds a new top-level directory under workspaces/. Please follow Submitting a Pull Request for a New Workspace in CONTRIBUTING.md.

@rhdh-gh-app
Copy link
Copy Markdown

rhdh-gh-app Bot commented May 27, 2026

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/install-dynamic-plugins workspaces/install-dynamic-plugins/packages/install-dynamic-plugins minor v0.1.0

@rhdh-qodo-merge
Copy link
Copy Markdown

rhdh-qodo-merge Bot commented May 27, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Wrapper runs wrong file ✓ Resolved 🐞 Bug ≡ Correctness
Description
install-dynamic-plugins.sh executes install-dynamic-plugins.cjs from the package root, but the
bundled CLI is built/published at dist/install-dynamic-plugins.cjs. The documented init-container
invocation will fail at runtime unless the image manually renames/copies the bundle.
Code

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/install-dynamic-plugins.sh[18]

Relevance

⭐⭐ Medium

No history for new workspace; team fixes path mismatches in wrappers/imports when found (#2526).

PR-#2526

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The wrapper and README expect a root-level install-dynamic-plugins.cjs, but the package metadata
and actual built artifact location are dist/install-dynamic-plugins.cjs, so the wrapper command
cannot resolve the file as published.

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/install-dynamic-plugins.sh[18-18]
workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/package.json[26-32]
workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/README.md[7-21]
workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/dist/install-dynamic-plugins.cjs[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`install-dynamic-plugins.sh` runs `node install-dynamic-plugins.cjs`, but the project outputs and publishes the bundle as `dist/install-dynamic-plugins.cjs`. This breaks the documented runtime contract (`./install-dynamic-plugins.sh <root>`).

### Issue Context
- The package `main`/`bin` points at `dist/install-dynamic-plugins.cjs`, and only that path is included in published `files`.
- The README and wrapper both reference a root-level `install-dynamic-plugins.cjs` that is not present.

### Fix Focus Areas
- workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/install-dynamic-plugins.sh[18-18]
- workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/package.json[26-32]
- workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/README.md[7-21]

### Recommended fix
- Change the wrapper to exec the actual bundle path, ideally independent of CWD, e.g.:
 - `exec node "$(dirname "$0")/dist/install-dynamic-plugins.cjs" "$@"`
- Update README snippet to match the fixed wrapper (and/or align packaging if you truly want a root-level `.cjs`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Main config type unchecked 🐞 Bug ☼ Reliability
Description
loadDynamicPluginsConfig returns parsed YAML without verifying it is a mapping, so a malformed
dynamic-plugins.yaml (e.g. a scalar string) can later crash with a TypeError (e.g. `rawIncludes.map
is not a function`) instead of a controlled InstallException. Include files already enforce the
mapping constraint, so the main config is inconsistently handled.
Code

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[R201-208]

Relevance

⭐⭐⭐ High

Team often adds input validation/controlled errors (YAML fix #2581; robust parsing errors #2647).

PR-#2581
PR-#2647

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The main config parser returns content without an isPlainObject check, but later code assumes
content.includes is an array. The include-file parsing path already performs the mapping
validation, demonstrating the intended contract and the inconsistency for the main file.

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[189-209]
workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[229-233]
workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[241-253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The main `dynamic-plugins.yaml` is parsed and cast to `DynamicPluginsConfig` without runtime shape validation. If the YAML is a scalar/sequence, later code assumes `includes` is an array and can throw a JS `TypeError` rather than an `InstallException` with a clear message.

### Issue Context
Example failure: if `dynamic-plugins.yaml` parses as a string, `content.includes` resolves to the string method `includes` (truthy), and `resolveIncludes` then attempts `rawIncludes.map(...)`, causing `rawIncludes.map is not a function`.

### Fix Focus Areas
- workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[189-209]
- workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts[229-255]

### Recommended fix
- After `parseYaml`, validate `content` is a plain object (similar to include parsing):
 - if `content && !isPlainObject(content)` throw `new InstallException(`${configFileAbs} must contain a mapping`)`.
- Optionally also validate that `includes` (if present) is an array of strings and `plugins` (if present) is an array, so errors are deterministic and user-friendly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@rhdh-qodo-merge
Copy link
Copy Markdown

Review Summary by Qodo

Add @red-hat-developer-hub/install-dynamic-plugins workspace with TypeScript/Node.js port

✨ Enhancement

Grey Divider

Walkthroughs

Description
• 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
Diagram
flowchart 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"]

Loading

Grey Divider

File Changes

1. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts ✨ Enhancement +637/-0

Core installer orchestration and CLI entry point

• Main entry point for the dynamic plugins installer CLI, orchestrating the full installation
 pipeline
• Handles config loading, plugin merging, categorization (OCI/NPM/local), and concurrent
 installation
• Implements pre-pass optimization to skip no-op plugins without worker overhead
• Manages error handling, global config generation, and cleanup of obsolete plugins

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/index.ts


2. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger.ts ✨ Enhancement +562/-0

Plugin configuration merging with security and inheritance

• Implements deep merge logic with prototype-pollution protection via forbidden key filtering
• Handles OCI and NPM plugin merging with version/path resolution and inheritance support
• Pre-merge pass (preMergeOciDisabledState) computes disabled registries before any remote work
• Validates ambiguous path-less OCI references and detects duplicate configurations

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger.ts


3. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/catalog-index.ts ✨ Enhancement +325/-0

Catalog index extraction with security guards

• Extracts primary and extra catalog index OCI images using skopeo with security filtering
• Applies per-entry size limits, path-traversal rejection, and link-target containment
• Parses EXTRA_CATALOG_INDEX_IMAGES env var with auto-derived or explicit subdirectory names
• Validates subdirectory names to prevent escape attempts

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/catalog-index.ts


View more (47)
4. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/tar-extract.ts ✨ Enhancement +194/-0

Secure tarball extraction with path-traversal protection

• Extracts OCI plugin layers and NPM packages from tarballs with streaming via node-tar
• Enforces security: rejects path traversal, enforces size limits, validates link targets
• Strips package/ prefix from NPM archives and validates all entries stay within bounds
• Prevents sibling directory extraction via boundary-safe path prefix matching

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/tar-extract.ts


5. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.ts ✨ Enhancement +155/-0

OCI package specification parsing and validation

• Parses OCI package specs with regex supporting tags, digests, and {{inherit}} placeholders
• Auto-detects single-plugin paths from OCI image manifests when !path suffix is omitted
• Provides synchronous registry/path extraction for the pre-merge disable pass
• Validates digest algorithms against RECOGNIZED_ALGORITHMS

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.ts


6. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer-npm.ts ✨ Enhancement +155/-0

NPM package installation with integrity verification

• Runs npm pack --json --ignore-scripts to produce tarballs with lifecycle hook blocking
• Verifies package integrity against provided hashes (unless SKIP_INTEGRITY_CHECK is set)
• Extracts NPM packages and writes plugin hash for change detection
• Validates archive filenames to prevent directory traversal

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer-npm.ts


7. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/errors.ts ✨ Enhancement +22/-0

Custom exception type for installer errors

• Defines InstallException custom error class for consistent error handling

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/errors.ts


8. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/.eslintrc.js ⚙️ Configuration changes +1/-0

ESLint configuration for workspace

• Extends repo-wide ESLint configuration via @backstage/cli/config/eslint-factory

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/.eslintrc.js


9. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/extra-catalog-index.test.ts 🧪 Tests +358/-0

Extra catalog index extraction tests

• Tests imageRefToSubdirectory character replacement (/, :, @) with underscores
• Tests parseExtraCatalogIndexImages parsing of plain refs and explicit name=ref entries
• Tests extractExtraCatalogIndex with fake skopeo binary, validating entity extraction and
 safety checks
• Validates rejection of unsafe subdirectory names (path traversal, separators)

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/extra-catalog-index.test.ts


10. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger-pre-merge.test.ts 🧪 Tests +265/-0

Pre-merge OCI disable state and filtering tests

• Tests preMergeOciDisabledState level overrides and cross-form inheritance scenarios
• Tests path-less + multiple explicit paths ambiguity detection and error messages
• Tests same-level duplicate detection (warns for disabled, throws for enabled)
• Tests filterDisabledOciPlugins registry filtering and invalid OCI entry handling

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger-pre-merge.test.ts


11. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/tar-extract.test.ts 🧪 Tests +200/-0

Tarball extraction security tests

• Tests extractOciPlugin with path traversal rejection, size limits, and symlink safety
• Tests extractNpmPackage with package/ prefix stripping and archive validation
• Tests zip-bomb detection and rejection of entries outside allowed boundaries
• Tests sibling directory prefix matching prevention

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/tar-extract.test.ts


12. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.test.ts 🧪 Tests +222/-0

OCI package key parsing tests

• Tests ociPluginKey parsing of tag-based and digest-based OCI specs with explicit paths
• Tests {{inherit}} placeholder handling and auto-detection from image cache
• Tests invalid OCI format rejection (missing tags, unsupported algorithms, empty paths)
• Tests host:port registry support and multiple algorithm validation

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.test.ts


13. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer-oci.ts ✨ Enhancement +152/-0

OCI plugin installer with pull policy support

• Implements OCI plugin installation with support for image caching and pull policies
• Splits OCI package specs into image and plugin path components
• Handles IfNotPresent and Always pull policies with digest comparison
• Writes image and config hash files for change detection

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer-oci.ts


14. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/plugin-hash.ts ✨ Enhancement +128/-0

Plugin hash computation with Python compatibility

• Computes SHA256 hashes for plugin change detection with Python byte-compatibility
• Excludes pluginConfig and version from hash calculation
• Inspects local package metadata (mtimes, lockfiles) for local package hashing
• Uses deterministic JSON stringification with sorted keys for cross-version compatibility

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/plugin-hash.ts


15. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-cache.ts ✨ Enhancement +125/-0

OCI image caching and manifest handling

• Caches OCI image tarballs to avoid redundant downloads across multiple plugins
• Extracts plugin paths from OCI annotations (io.backstage.dynamic-packages)
• Resolves image digests and manages concurrent downloads via promise-based caching
• Locates first layer in OCI manifest for extraction

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-cache.ts


16. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/concurrency.test.ts 🧪 Tests +131/-0

Concurrency control and worker configuration tests

• Tests Semaphore class for bounding concurrent async operations
• Validates mapConcurrent respects concurrency limits and captures both successes and failures
• Tests getWorkers and getNpmWorkers environment variable parsing with auto-detection
• Verifies worker count clamping and fallback behavior

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/concurrency.test.ts


17. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger.test.ts 🧪 Tests +103/-0

Plugin merging and deep merge security tests

• Tests deepMerge for nested object merging with conflict detection
• Validates prototype pollution protection against __proto__, constructor, prototype
• Tests mergePlugin for plugin deduplication and level-based override semantics
• Verifies package field type validation

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/merger.test.ts


18. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/integrity.ts ✨ Enhancement +120/-0

SRI integrity verification with streaming hash

• Implements streaming SRI integrity verification for NPM packages
• Supports sha256, sha384, sha512 algorithms with base64 encoding
• Validates base64 format without regex to avoid ReDoS vulnerabilities
• Streams file hashing to avoid loading large archives into memory

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/integrity.ts


19. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.ts ✨ Enhancement +111/-0

Core type definitions and configuration constants

• Defines PluginSpec, Plugin, and PluginMap types for plugin configuration
• Exports PullPolicy enum and helper effectivePullPolicy function
• Defines constants for file names, protocols, and registry URLs
• Implements parseMaxEntrySize for environment variable parsing with fallback

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.ts


20. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/finalize-install.test.ts 🧪 Tests +123/-0

Install finalization and cleanup tests

• Tests finalizeInstall for writing global config and cleaning obsolete plugin directories
• Validates error handling preserves previous config and skips cleanup on failures
• Verifies config file is not created when no previous run exists and errors occur
• Tests installed plugin tracking and removal

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/finalize-install.test.ts


21. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/concurrency.ts ✨ Enhancement +120/-0

Concurrency control with semaphore and worker management

• Implements Semaphore class for bounding concurrent async work
• Provides mapConcurrent for parallel execution with error capture
• Implements getWorkers and getNpmWorkers with CPU-aware auto-detection
• Respects DYNAMIC_PLUGINS_WORKERS and DYNAMIC_PLUGINS_NPM_WORKERS env vars

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/concurrency.ts


22. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/skopeo.ts ✨ Enhancement +117/-0

Skopeo CLI wrapper with inspect caching

• Wraps skopeo CLI with promise-based caching for inspect results
• Implements copy, inspect, inspectRaw, and exists methods
• Deduplicates concurrent requests for the same image via promise caching
• Handles skopeo path resolution and error reporting

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/skopeo.ts


23. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/skopeo.test.ts 🧪 Tests +108/-0

Skopeo caching behavior tests

• Tests Skopeo.exists() memoization to verify single fork per URL
• Validates negative result caching for non-existent images
• Uses fake skopeo binary to verify cache behavior and deduplication

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/skopeo.test.ts


24. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/lock-file.ts ✨ Enhancement +104/-0

Lock file management with signal cleanup

• Implements exclusive lock file acquisition with polling and timeout
• Registers signal handlers for cleanup on SIGTERM/SIGINT
• Respects DYNAMIC_PLUGINS_LOCK_TIMEOUT_MS environment variable
• Prevents concurrent installs and handles stale locks

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/lock-file.ts


25. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/npm-key.test.ts 🧪 Tests +81/-0

NPM package key parsing tests

• Tests NPM package key extraction with version stripping
• Validates alias parsing (alias@npm:package@version)
• Tests Git URL and GitHub shorthand reference stripping
• Verifies local paths and tarballs remain unchanged

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/npm-key.test.ts


26. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/plugin-hash.test.ts 🧪 Tests +73/-0

Plugin hash computation and Python compatibility tests

• Tests deterministic hash generation for plugin change detection
• Validates pluginConfig and version exclusion from hash
• Verifies Python compatibility with reference hashes
• Tests hash changes on package and pullPolicy modifications

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/plugin-hash.test.ts


27. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/npm-key.ts ✨ Enhancement +71/-0

NPM package key extraction and parsing

• Parses NPM package specs to extract stable keys for deduplication
• Handles standard packages, aliases, Git URLs, and GitHub shorthand
• Strips version specifiers and Git references
• Preserves local paths and tarballs unchanged

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/npm-key.ts


28. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/integrity.test.ts 🧪 Tests +81/-0

SRI integrity verification tests

• Tests SRI integrity verification for sha256, sha384, sha512
• Validates base64 format validation and algorithm support
• Tests hash mismatch detection and error reporting
• Verifies streaming hash computation

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/integrity.test.ts


29. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/lock-file.test.ts 🧪 Tests +66/-0

Lock file creation and timeout tests

• Tests atomic lock file creation and removal
• Validates waiting for existing locks with timeout
• Tests stale lock timeout behavior
• Verifies removeLock is idempotent

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/lock-file.test.ts


30. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/util.ts ✨ Enhancement +72/-0

Utility functions for file and object operations

• Provides utility functions for file existence checks and path containment
• Implements isPlainObject for object type validation
• Defines allowed tar entry types for security filtering
• Implements markAsFresh to prevent stale hash cleanup

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/util.ts


31. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-resolver.test.ts 🧪 Tests +51/-0

Image resolver fallback tests

• Tests image resolution with registry fallback logic
• Validates registry.access.redhat.com/rhdh to quay.io/rhdh fallback
• Tests protocol preservation on fallback
• Verifies non-RHDH images pass through unchanged

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-resolver.test.ts


32. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/run.ts ✨ Enhancement +59/-0

Subprocess execution wrapper with error context

• Wraps subprocess execution with structured error handling
• Captures stdout/stderr and reports exit codes with context
• Throws InstallException on non-zero exit with full diagnostic info
• Matches Python run() contract for error reporting

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/run.ts


33. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-resolver.ts ✨ Enhancement +51/-0

OCI image resolution with registry fallback

• Resolves OCI image references with registry fallback
• Falls back from registry.access.redhat.com/rhdh to quay.io/rhdh on 404
• Preserves protocol (oci:// or docker://) on fallback
• Mirrors Python resolve_image behavior

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/image-resolver.ts


34. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.test.ts 🧪 Tests +48/-0

MAX_ENTRY_SIZE parsing tests

• Tests parseMaxEntrySize environment variable parsing
• Validates fallback to default (40 MB) for invalid/missing values
• Tests positive integer parsing and boundary conditions

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.test.ts


35. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/which.ts ✨ Enhancement +43/-0

Minimal PATH lookup implementation

• Implements minimal which(1) for PATH lookup without external dependency
• Handles Windows and Unix path separators and executable extensions
• Returns absolute path of executable or null if not found
• Respects PATH and PATHEXT environment variables

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/which.ts


36. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/log.ts ✨ Enhancement +18/-0

Uniform logging utility

• Provides uniform logging function for stdout output
• Simple wrapper around process.stdout.write with newline

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/log.ts


37. workspaces/install-dynamic-plugins/.eslintrc.js ⚙️ Configuration changes +1/-0

ESLint configuration

• Extends root ESLint configuration for the workspace

workspaces/install-dynamic-plugins/.eslintrc.js


38. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/install-dynamic-plugins.sh ✨ Enhancement +18/-0

Shell wrapper for Node.js entry point

• Shell wrapper script that invokes the bundled Node.js CommonJS entry point
• Passes the dynamic-plugins-root directory as first argument

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/install-dynamic-plugins.sh


39. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/README.md 📝 Documentation +111/-0

Package documentation and architecture guide

• Documents the TypeScript/Node.js port of the Python init-container installer
• Describes architecture, concurrency strategy, memory budget, and security checks
• Lists environment variables and development/testing instructions
• Highlights compatibility with previous Python implementation

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/README.md


40. workspaces/install-dynamic-plugins/package.json ⚙️ Configuration changes +54/-0

Workspace root package configuration

• Workspace root configuration with monorepo scripts
• Defines Node 22/24 engine requirement
• Configures Prettier and ESLint for the workspace

workspaces/install-dynamic-plugins/package.json


41. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/package.json ⚙️ Configuration changes +61/-0

Package metadata and build configuration

• Package metadata for @red-hat-developer-hub/install-dynamic-plugins
• Defines build, test, and lint scripts
• Specifies dependencies (tar, yaml) and dev dependencies
• Configures npm publish settings and Jest module mapping

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/package.json


42. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/esbuild.config.mjs ⚙️ Configuration changes +34/-0

Esbuild bundling configuration

• Configures esbuild to bundle TypeScript into single CommonJS file
• Minifies output and generates external sourcemap for debugging
• Targets Node 22 with shebang banner for CLI execution

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/esbuild.config.mjs


43. workspaces/install-dynamic-plugins/.changeset/initial-release.md 📝 Documentation +5/-0

Initial release changeset

• Changeset for initial release of @red-hat-developer-hub/install-dynamic-plugins
• Documents TypeScript/Node.js port with byte-compatible behavior

workspaces/install-dynamic-plugins/.changeset/initial-release.md


44. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/tsconfig.json ⚙️ Configuration changes +16/-0

TypeScript compiler configuration

• TypeScript compiler configuration for the package
• Targets ES2022 with strict mode and Node.js module resolution

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/tsconfig.json


45. workspaces/install-dynamic-plugins/.changeset/README.md 📝 Documentation +8/-0

Changesets documentation

• Standard Changesets documentation template

workspaces/install-dynamic-plugins/.changeset/README.md


46. workspaces/install-dynamic-plugins/.changeset/config.json ⚙️ Configuration changes +10/-0

Changesets configuration

• Changesets configuration for the workspace
• Defines public access and main branch settings

workspaces/install-dynamic-plugins/.changeset/config.json


47. workspaces/install-dynamic-plugins/tsconfig.json ⚙️ Configuration changes +10/-0

Workspace TypeScript configuration

• Workspace-level TypeScript configuration extending Backstage CLI defaults
• Configures strict type checking and output directories

workspaces/install-dynamic-plugins/tsconfig.json


48. workspaces/install-dynamic-plugins/.prettierignore ⚙️ Configuration changes +6/-0

Prettier ignore configuration

• Prettier ignore patterns for the workspace
• Excludes build artifacts and bundled output

workspaces/install-dynamic-plugins/.prettierignore


49. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/.prettierignore ⚙️ Configuration changes +5/-0

Package Prettier ignore configuration

• Prettier ignore patterns for the package
• Excludes node_modules, coverage, and dist directories

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/.prettierignore


50. workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/dist/install-dynamic-plugins.cjs Additional files +178/-0

...

workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/dist/install-dynamic-plugins.cjs


Grey Divider

Qodo Logo

@rhdh-qodo-merge rhdh-qodo-merge Bot added enhancement New feature or request Tests labels May 27, 2026
gustavolira and others added 2 commits May 27, 2026 16:51
- 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
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 64.67757% with 367 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.37%. Comparing base (c901f52) to head (7e06bed).
⚠️ Report is 8 commits behind head on main.
✅ All tests successful. No failed tests found.

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              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.58% <ø> (ø) Carriedforward from 7707d09
ai-integrations 70.03% <ø> (ø) Carriedforward from 7707d09
app-defaults 69.60% <ø> (ø) Carriedforward from 7707d09
augment 46.39% <ø> (ø) Carriedforward from 7707d09
bulk-import 72.86% <ø> (ø) Carriedforward from 7707d09
cost-management 16.49% <ø> (ø) Carriedforward from 7707d09
dcm 32.85% <ø> (ø) Carriedforward from 7707d09
extensions 61.79% <ø> (ø) Carriedforward from 7707d09
global-floating-action-button 74.30% <ø> (ø) Carriedforward from 7707d09
global-header 61.68% <ø> (ø) Carriedforward from 7707d09
homepage 51.52% <ø> (ø) Carriedforward from 7707d09
install-dynamic-plugins 64.67% <64.67%> (?)
konflux 91.01% <ø> (ø) Carriedforward from 7707d09
lightspeed 68.33% <ø> (ø) Carriedforward from 7707d09
mcp-integrations 81.59% <ø> (ø) Carriedforward from 7707d09
orchestrator 36.36% <ø> (ø) Carriedforward from 7707d09
quickstart 62.88% <ø> (ø) Carriedforward from 7707d09
sandbox 79.56% <ø> (ø) Carriedforward from 7707d09
scorecard 83.84% <ø> (ø) Carriedforward from 7707d09
theme 64.54% <ø> (ø) Carriedforward from 7707d09
translations 8.49% <ø> (ø) Carriedforward from 7707d09
x2a 78.47% <ø> (ø) Carriedforward from 7707d09

*This pull request uses carry forward flags. Click here to find out more.


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 c901f52...7e06bed. 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.

gustavolira and others added 2 commits May 27, 2026 17:20
- 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>
@sonarqubecloud
Copy link
Copy Markdown

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

Labels

enhancement New feature or request Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant