Skip to content

feat(plugin-dev): add plugin dev command for local RHDH development - #215

Open
gashcrumb wants to merge 13 commits into
redhat-developer:mainfrom
gashcrumb:feat/plugin-dev
Open

gashcrumb wants to merge 13 commits into
redhat-developer:mainfrom
gashcrumb:feat/plugin-dev

Conversation

@gashcrumb

Copy link
Copy Markdown
Member

Summary

Implements rhdh-cli plugin dev (RHIDP-16672) — a command that exports a dynamic plugin and manages its lifecycle against an existing RHDH Local checkout.

Commands

Command Description
plugin dev start Export, stage, write generated config, and bring up the Compose runtime
plugin dev update Re-export, re-stage, restart installer + RHDH service without full teardown
plugin dev stop Stop Compose services; --clean also removes containers and networks
plugin dev logs Stream logs; --rhdh for RHDH output, --installer for installer output
plugin dev status Human-readable runtime state (installer exit code, RHDH service health)

--rhdh-local-dir <path> or RHDH_LOCAL_DIR points at an existing RHDH Local checkout. --configure non-invasively adds the CLI-managed config include to dynamic-plugins.override.yaml on first use.

Runtime contract

  • Validates the RHDH Local checkout before doing anything (names missing files in the error)
  • Never mutates the checkout's Git state or user-owned configuration
  • Stages plugins to local-plugins/<name>/ for the RHDH Local installer's npm pack path
  • Owns only configs/dynamic-plugins/rhdh-cli.generated.yaml
  • stop --clean uses compose down — volumes and config are always preserved

Also fixes

  • plugin new: inject "version": "0.1.0" in adaptStandaloneProject — upstream standalone templates omit version, but npm pack (used by the RHDH Local installer) requires it
  • plugin export: preflight check in both backend.ts and frontend.ts that fails fast with a clear message when package.json is missing a version field, rather than letting npm pack fail inside the installer container
  • plugin export: ensureDir before writing the config schema in command.ts
  • Staging: stagePlugin uses fs.remove + fs.copy({ dereference: false }) to preserve relative symlinks in node_modules/.bin/ and avoid self-copy errors on repeated update runs

Testing

  • Manually verified: backend plugin and frontend plugin scaffold → plugin dev startplugin dev updateplugin dev stop against a real RHDH Local instance running quay.io/rhdh-community/rhdh:next
  • Unit tests cover: Compose arg construction, runtime validation, engine selection, ensureGeneratedConfigIncluded, updateGeneratedConfig, stagePlugin symlink handling, validateProjectFiles (backend requires dist-types, frontend does not), and formatRuntimeStatus

Closes #RHIDP-16672

Implements `rhdh-cli plugin dev [start|stop|update|logs|status]` to
manage a plugin's lifecycle against an existing RHDH Local checkout.

- start: exports and stages the plugin into local-plugins/, writes
  rhdh-cli.generated.yaml, and brings up the Compose runtime
- update: re-exports, re-stages, restarts the installer and RHDH
  service without tearing down the full runtime
- stop: stops Compose services; --clean also removes containers and
  networks (volumes and config are preserved)
- logs: streams Compose logs; --installer shows installer output,
  --rhdh shows RHDH output, --all includes all services
- status: interprets installer exit code and RHDH service state into
  a human-readable status message
- --configure: non-invasively adds the CLI-managed config include to
  dynamic-plugins.override.yaml on first use

Staging uses fs.remove + dereference:false copy to handle relative
symlinks in node_modules/.bin without errors on repeated runs.

Also fixes two issues discovered during live testing:
- plugin new: inject version '0.1.0' in adaptStandaloneProject so
  upstream templates (which omit version) produce packages that
  npm pack can handle in the RHDH Local installer
- plugin export: add preflight version checks in both backend.ts and
  frontend.ts that fail fast with a clear message when package.json
  is missing a version field, rather than letting npm pack fail inside
  the installer container with a cryptic error
- plugin export: ensureDir before writing config schema in command.ts

Assisted-By: OpenCode
Signed-off-by: Stan Lewis <[email protected]>

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:05 PM UTC · Completed 7:31 PM UTC

Commit: 5a2b64d · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-4-6 · Effort: high · Cost: $6.42

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Sep 17, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 17, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Tier 1 and Tier 2 signals are unchanged from the prior review — the same large blast radius (12 files, 1008 lines) is offset by zero security-sensitive paths, no CI or dependency changes, and a non-first-time human author; the only delta since the prior review is bug-fix commits on the new dev/ files which still carry no git history, leaving the composite at 2/moderate.

Previous run

Risk Assessment: moderate (2/5)

Details

Tier 1 sub-score ~2.1 (weighted 62%): large blast radius and 989 lines pull upward, but no security-sensitive paths, no CI changes, no dependency files, and a normal human non-first-time author profile keep the average low. Tier 2 sub-score ~2 (weighted 38%): three new dev/ files have no prior history; export-dynamic-plugin files show modest fix activity but no regressions against the PRs changed lines. No Tier 3 (Jira issue not accessible). Composite rounds to 2, unchanged from prior review.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Score drops from prior 3 to 2 because Tier 2 history is clean — the three new dev/ files have no regression history, zero reverts across all changed files, and low average churn — partially offset by large blast radius and below-threshold test-file ratio.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Moderate risk: a well-structured new feature addition by an experienced contributor with unit tests and no dependency or CI changes, offset by a large blast radius classification and modifications to export-dynamic-plugin files that carry significant fix/revert history.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 17, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] AGENTS.md — AGENTS.md is a protected governance file. The PR adds 60 lines documenting the plugin dev command architecture (runtime contract, staging conventions, symlink handling, Commander subcommand patterns). The PR body and Jira reference (RHIDP-16672) provide sufficient context for the modification. Human approval is required for all protected-path changes regardless of justification.

  • [stale-doc] README.md:128 — The ## Commands → ### Plugin Development Commands bullet list (lines 128–130) still omits plugin dev. The PR added a prose section under ### Testing a Plugin in RHDH Local that documents usage, but the Commands index was not updated. A reader consulting the Commands section will find no entry for the new subcommand group (start, update, stop, logs, status).
    Remediation: Add a bullet to the Plugin Development Commands list: - `plugin dev`: Export a dynamic plugin and manage its lifecycle against an existing RHDH Local runtime (`start`, `update`, `stop`, `logs`, `status`)

Low

  • [test-adequacy] src/commands/dev/command.test.tsparseComposeStatus has two parsing branches: JSON Lines (Docker Compose v2) and JSON array (Podman Compose). The JSON Lines branch is covered by two tests; the JSON array branch (trimmed.startsWith('[')) has no test. The branch includes a try/catch that throws a compose-specific error — neither the happy path nor the error path is exercised.
    Remediation: Add two test cases: one for valid array input (e.g. '[{"Service":"rhdh","State":"running"}]') and one for malformed JSON (e.g. '[not json') to cover the error path.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

Medium

  • [protected-path] AGENTS.md — AGENTS.md is a protected governance file. The PR adds 60 lines documenting the plugin dev command architecture. The PR body and Jira reference (RHIDP-16672) provide sufficient context. Human approval is required for all protected-path changes regardless of justification.

  • [stale-doc] README.md:128 — The ## Commands → ### Plugin Development Commands bullet list does not include plugin dev, which this PR introduces as a first-class subcommand group (start, update, stop, logs, status). The list stops at plugin export, plugin package, and plugin check-versions. A reader consulting the command reference will find no entry for the new command.
    Remediation: Add a plugin dev entry to the Plugin Development Commands list: - \plugin dev`: Export a dynamic plugin and manage its lifecycle against an existing RHDH Local runtime`

Low

  • [path-traversal] src/commands/dev/command.ts:192 — The path traversal guard correctly rejects names that resolve outside local-plugins/ (e.g. ..), but does not catch identity-resolving names where pluginName is empty or .. If packageJson.name is @ (after .replace(/^@/, '') it becomes ''), destination equals localPluginsDir itself. path.relative returns '' which passes both the startsWith('..') and isAbsolute checks. fs.remove(destination) would then delete the entire local-plugins/ directory. npm/yarn prohibit these package names, so only a deliberately crafted package.json can trigger this.
    Remediation: Add a check for empty or identity-resolving relative paths: if (!relative || relative === '.') { throw new Error(Derived plugin directory ... is not inside ...); }. The existing test helper withStagingDirs could add a test case for name @.

  • [edge-case] src/commands/dev/command.ts:397formatRuntimeStatus treats a container with State including exited and no ExitCode field as a failure, because exitCode(installer) returns undefined and undefined !== '0' evaluates to true. In practice Docker Compose and Podman Compose always provide ExitCode for exited containers, so this is unlikely to trigger, but a successful exit with no explicit exit code would be misreported as a plugin installation failure.
    Remediation: Add a guard to distinguish unknown exit code from non-zero: exitCode(installer) !== undefined && exitCode(installer) !== '0'.

  • [test-adequacy] src/commands/dev/command.test.tsparseComposeStatus has two parsing branches: JSON array (for Podman Compose output) and JSON Lines (for Docker Compose v2 output). The JSON Lines branch is tested by two cases, but the JSON array branch (trimmed.startsWith('[')) has no dedicated test. Since the array branch was changed in this revision (try/catch with compose-specific error added), a test confirming both valid and malformed array inputs would verify the new error handling.
    Remediation: Add test cases for the JSON array branch: one for valid array input (Podman Compose format) and one for malformed JSON triggering the new try/catch error path.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

Medium

  • [protected-path] AGENTS.md — AGENTS.md is a protected governance file. The PR adds 34 lines documenting the plugin dev command architecture. The PR body and Jira reference (RHIDP-16672) provide sufficient context. Human approval is required for all protected-path changes regardless of justification.

  • [api-shape] src/commands/dev/command.ts:265composeArgs is exported and takes four positional boolean parameters (includeAll, showRhdh, showInstaller, follow), all defaulting to false. Call sites (composeArgs('logs', opts.all, opts.rhdh, opts.installer, opts.follow)) are opaque without reading the signature. Every other exported handler in this module uses opts: OptionValues. The established codebase pattern for functions with multiple related boolean flags is to accept an options object.
    Remediation: Replace the four boolean positional parameters with a single options object, e.g. opts: { includeAll?: boolean; showRhdh?: boolean; showInstaller?: boolean; follow?: boolean } = {}. Update all call sites, including in command.test.ts.

  • [stale-doc] README.md:128 — The ## Commands → ### Plugin Development Commands bullet list does not include plugin dev, which this PR introduces as a first-class subcommand group. The PR head README adds a full plugin dev prose section but leaves the command reference list incomplete.
    Remediation: Add a plugin dev entry to the Plugin Development Commands list: - `plugin dev`: Export a dynamic plugin and manage its lifecycle against an existing RHDH Local runtime

Low

  • [error-handling-gap] src/commands/dev/command.ts:325parseComposeStatus's array-format branch calls JSON.parse(trimmed) without a try/catch. The JSON Lines branch wraps each parse in try/catch and rethrows with a compose-specific message; the array branch is asymmetrically hardened.
    Remediation: Wrap JSON.parse(trimmed) in a try/catch and rethrow with a compose-specific message, consistent with what the JSON Lines branch does.

  • [status-reporting-correctness] src/commands/dev/command.ts:261composeStatusArgs appends --all only when containerTool === 'docker'. For Podman (the default), compose ps runs without --all. Podman Compose versions prior to 1.3 do not include exited containers by default; if the installer container exits after finishing, formatRuntimeStatus silently falls through to 'RHDH Local is not running.' even on a real installation failure.
    Remediation: Pass --all regardless of container tool (both Docker Compose and Podman Compose accept it), or add a comment documenting the minimum verified Podman Compose version that shows exited containers by default.

  • [path-traversal] src/commands/dev/command.ts:171 — If packageJson.name is "..", pluginName resolves to ".." after .replace(/^@/, '').replaceAll('/', '-'), and destination becomes path.join(runtimeDir, 'local-plugins', '..') — i.e., runtimeDir itself. fs.remove(destination) would delete the entire RHDH Local checkout. npm prohibits names starting with ., so only a locally crafted package.json can trigger this.
    Remediation: After the replaceAll call, verify that destination is a strict descendant of path.join(runtimeDir, 'local-plugins') using path.relative() before calling fs.remove.

  • [missing-doc] README.md:97 — The plugin dev prose section documents --configure, RHDH_LOCAL_DIR, --clean, and --installer but does not mention --container-tool (defaulting to podman). Docker users have no hint of this in the documentation.
    Remediation: Add a sentence such as: "The default container tool is podman; pass --container-tool docker if your environment uses Docker instead."

  • [code-organization] src/commands/dev/command.ts:17import path from 'node:path' is placed in the same blank-line group as third-party packages (commander, fs-extra, yaml) without a separator. Sibling files (export-dynamic-plugin/backend.ts, export-dynamic-plugin/command.ts) each give Node built-ins their own separate blank-line group.
    Remediation: Move import path from 'node:path' below import YAML from 'yaml', separated by a blank line.

  • [code-organization] src/commands/dev/command.ts:35 — Section separator comments using box-drawing characters (// ── Section name ─────) appear five times in this file but nowhere else in src/commands/. Introducing a new decoration convention in a single file creates an inconsistency across the command package.
    Remediation: Replace the five box-drawing separators with plain comments or remove them entirely.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

Medium

  • [protected-path] AGENTS.md — AGENTS.md is a protected governance file. The PR adds 34 lines documenting the plugin dev command architecture. The PR body and linked Jira issue (RHIDP-16672) provide sufficient context. Human approval is required for all protected-path changes regardless of justification.

  • [breaking-behavioral-change] src/commands/export-dynamic-plugin/backend.ts:71plugin export for backend plugins now throws an error if package.json lacks a version field. This surfaces a real defect (npm pack requires a version to produce a valid tarball), but it changes the existing command's error surface: users with versionless plugins in CI pipelines will observe failures without any code change on their side. plugin new now mitigates this for newly scaffolded plugins. See also: [breaking-behavioral-change] in frontend.ts.
    Remediation: Document under ### Fixed in the ## Unreleased section of CHANGELOG.md so users know to add a version field to existing plugins.

  • [breaking-behavioral-change] src/commands/export-dynamic-plugin/frontend.ts:71 — Same mandatory version check for frontend plugin export. Same rationale: catches a previously silent npm pack failure, but changes the error surface of an existing public command.
    Remediation: Document in CHANGELOG.md alongside the backend change.

  • [stale-doc] CHANGELOG.md:7 — The plugin export version-field requirement (a behavior change to an existing command) is not documented in the ## Unreleased section. The ### Fixed entry is currently empty.
    Remediation: Add a ### Fixed bullet describing the new version requirement and noting that plugin new now injects version: '0.1.0' automatically for scaffolded projects.

  • [missing-doc] CHANGELOG.md:7 — The new plugin dev command — including subcommands start, update, stop, logs, status; flags --rhdh-local-dir, RHDH_LOCAL_DIR, --container-tool, --configure, --clean, --all, --rhdh, --installer; and the generated configs/dynamic-plugins/rhdh-cli.generated.yaml — is absent from the ## Unreleased section.
    Remediation: Add an ### Added bullet in ## Unreleased describing the plugin dev command, its subcommands, and its configuration contract.

Low

  • [path-traversal] src/commands/dev/command.ts:100pluginName is derived with .replace('/', '-') where String.replace with a string argument replaces only the first /. A package.json name with multiple slashes could produce a path escaping local-plugins/. npm validates names to at most one slash and the user controls their own package.json, so practical risk is low — but the fix is a one-character change.
    Remediation: Replace .replace('/', '-') with .replaceAll('/', '-') or .replace(/\//g, '-').

  • [error-handling-idiom] src/commands/dev/command.ts:276parseComposeStatus calls JSON.parse per line with no try/catch. Some Docker/Podman Compose versions emit deprecation warnings or trailing blank lines on stdout; a non-JSON line causes a raw SyntaxError rather than a compose-specific error message.
    Remediation: Filter empty lines before parsing and wrap each JSON.parse call in try/catch to surface a meaningful error when parsing fails.

  • [design-limitation] src/commands/dev/command.ts:122updateGeneratedConfig unconditionally overwrites rhdh-cli.generated.yaml with a single plugin entry. Running plugin dev start from a second plugin project against the same RHDH Local checkout silently drops the first plugin's configuration entry, leaving it staged but unconfigured. Single-plugin-per-checkout is the documented design.
    Remediation: Log a warning when overwriting an entry that references a different plugin path, or document this constraint prominently in the plugin dev help text.

  • [naming-conventions] src/commands/dev/command.ts:1src/commands/dev/command.ts and src/commands/dev/index.ts are missing the Apache 2.0 license header present in most repository source files (e.g., src/commands/export-dynamic-plugin/command.ts).
    Remediation: Add the standard Apache 2.0 license header to both new files.

  • [code-organization] src/commands/dev/command.ts:9import YAML from 'yaml' (a third-party package) appears after the local imports on line 9, breaking the blank-line-separated import grouping convention (third-party above the blank line, local below) used in sibling files.
    Remediation: Move import YAML from 'yaml' to join the other third-party imports on lines 1–3.

  • [edge-case] src/commands/dev/command.ts:176actionsToRun does not validate the action name against the recognized set; unknown actions pass through to composeArgs which throws a clear error. The split validation is functional.

  • [scope-creep] src/commands/export-dynamic-plugin/backend.ts — The PR bundles three pre-existing command fixes (plugin export version checks for backend and frontend, ensureDir in command.ts) with the primary plugin dev feature under a feat(plugin-dev): commit. These fixes are causally connected and documented under "Also fixes" but will not appear as fix: entries in changelogs.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

- Add Apache 2.0 license headers to command.ts and index.ts
- Move YAML import to third-party import group
- Use replaceAll('/', '-') in stagePlugin for complete scope segment replacement
- Filter non-JSON lines and add try/catch in parseComposeStatus
- Log a warning in updateGeneratedConfig when overwriting a different plugin entry
- Add --follow flag to plugin dev logs for streaming output
- Add CHANGELOG entries for plugin dev command and breaking plugin export changes

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
Comment thread src/commands/dev/command.ts Fixed

@gashcrumb gashcrumb left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed all fullsend findings in the follow-up commit b9e8f9b. Summary below; inline comments cover each thread.

Fixed: license headers, YAML import order, replaceAll, parseComposeStatus hardening, overwrite warning, --follow flag for logs, CHANGELOG entries.
Dismissed: the actionsToRun edge-case note (functional by design — composeArgs throws a clear error for unknown actions).

Comment thread src/commands/export-dynamic-plugin/backend.ts
Comment thread src/commands/export-dynamic-plugin/frontend.ts
Comment thread src/commands/dev/command.ts
Comment thread src/commands/dev/command.ts
Comment thread src/commands/dev/command.ts
Comment thread src/commands/dev/command.ts Outdated
Comment thread src/commands/dev/command.ts
Comment thread src/commands/dev/command.ts
…tory

SonarCloud flagged fs.chmod(destination, 0o755) as setting world-accessible
permissions. The chmod was applied only to the top-level directory and was
speculative — fs.copy preserves source permissions from dist-dynamic, which
the export process already creates with appropriate modes. Remove it.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb
gashcrumb dismissed fullsend-ai-review[bot]’s stale review September 18, 2026 09:52

All findings addressed in commits b9e8f9b and e092d74: license headers, import order, replaceAll, parseComposeStatus hardening, overwrite warning, --follow flag, CHANGELOG entries, and the SonarQube chmod finding.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
- Use node:path import prefix (typescript:S7772)
- Extract nested template literal expressions into local variables
  in formatRuntimeStatus (typescript:S4624, lines 350/356/359)

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
Run lint, prettier, and tsc before pushing to a PR branch.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
Comment thread src/commands/dev/command.ts Outdated
…bcommands

Each plugin dev action (start, update, stop, logs, status) is now a proper
Commander subcommand with only the flags that apply to it:

- start: --rhdh-local-dir, --container-tool, --configure
- update: --rhdh-local-dir, --container-tool
- stop:   --rhdh-local-dir, --container-tool, --clean
- logs:   --rhdh-local-dir, --container-tool, --rhdh, --installer, --all, --follow
- status: --rhdh-local-dir, --container-tool

This makes plugin dev -h list all available subcommands and prevents
nonsensical option combinations like plugin dev status --configure or
plugin dev start --clean.

command.ts exports one handler per subcommand plus all shared helpers.
actionsToRun() is removed; each handler directly invokes the compose
actions it needs. AGENTS.md documents the subcommand convention so future
command groups follow the same pattern.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb
gashcrumb marked this pull request as ready for review September 18, 2026 13:35
@gashcrumb

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:46 PM UTC · Completed 2:05 PM UTC

Commit: dd17ab6 · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-4-6 · Effort: high · Cost: $4.22

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

In a Backstage monorepo workspace the tsconfig.json sits at the workspace
root (e.g. workspaces/todo/) with outDir: dist-types and rootDir: '.', so
tsc writes types to <workspaceRoot>/dist-types/<relative-plugin-path>/ rather
than inside the plugin directory itself. validateProjectFiles() was always
checking <pluginDir>/dist-types, which never exists in a monorepo.

Introduce resolveDistTypes() which discriminates on paths.targetDir vs
paths.targetRoot:
- targetDir === targetRoot (standalone): dist-types is inside the plugin dir
- targetDir !== targetRoot (monorepo workspace): dist-types is at
  <targetRoot>/dist-types/<relative-path-from-root-to-plugin>

Three new tests cover: standalone resolution, monorepo path calculation, and
the full validateProjectFiles() flow against a simulated monorepo layout.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
- composeArgs: replace four positional boolean params with a LogsOptions
  object for call-site clarity; update all callers and tests
- composeStatusArgs: pass --all unconditionally (both Docker Compose and
  Podman Compose >=1.3 accept it); fixes potential misreporting on Podman
  when the installer container exits before status is queried
- parseComposeStatus: wrap the array-format JSON.parse branch in try/catch,
  consistent with the JSON Lines branch
- stagePlugin: guard against a crafted package name (e.g. '..') resolving
  the destination outside local-plugins/ before calling fs.remove
- import order: move node:path below third-party imports, matching sibling
  files that give Node built-ins their own group
- Remove box-drawing section separator comments; use plain blank lines
  between logical groups to stay consistent with the rest of src/commands/
- README: document that the default container tool is podman and how to
  switch to docker

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
…setup

SonarCloud reported 3.4% duplicated lines on the PR (threshold: 3%) due to
near-identical setup/teardown in the two stagePlugin tests. Extract a shared
withStagingDirs() helper that creates srcDir/runtimeDir, writes package.json,
sets the test global, and handles cleanup in a finally block.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:50 PM UTC · Completed 3:08 PM UTC

Commit: bd2bae3 · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-4-6 · Effort: high · Cost: $5.14

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

- stagePlugin: extend path-traversal guard to also reject empty or
  identity-resolving relative paths (e.g. package name '@' strips to '',
  which path.relative returns as '' — the same issue as '.'). Guard now
  checks !relative || relative === '.' in addition to the existing
  startsWith('..') and isAbsolute checks. Test covers both '..' and '@'.

- formatRuntimeStatus: add undefined check before comparing exitCode to
  '0'. Previously an exited installer container with no ExitCode field
  (exitCode returns undefined) would satisfy undefined !== '0' and be
  misreported as a plugin installation failure. Guard is now:
  exitCode !== undefined && exitCode !== '0'. Test covers this case.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:49 PM UTC · Completed 5:07 PM UTC

Commit: 34d7a52 · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-4-6 · Effort: high · Cost: $4.72

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • README.md (file-level): Line 128 · [medium] stale-doc

The ## Commands → ### Plugin Development Commands bullet list still omits plugin dev. The PR added a prose section documenting usage but the command index was not updated. A reader consulting the Commands section will find no entry for the new subcommand group (start, update, stop, logs, status).

Suggested fix: Add a bullet to the Plugin Development Commands list: - \plugin dev`: Export a dynamic plugin and manage its lifecycle against an existing RHDH Local runtime (`start`, `update`, `stop`, `logs`, `status`)`

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

…uacy

restart:
- New 'plugin dev restart' subcommand stops and restarts the RHDH service
  without re-exporting or re-staging the plugin. Useful when changing RHDH
  Local configuration (app-config, override YAML, etc.) without touching
  plugin code. 'plugin dev update' remains the command for plugin code changes.
- Registered in src/commands/index.ts alongside the other dev subcommands.
- Documented in README, CHANGELOG, and AGENTS.md.

stale-doc (fullsend):
- Add 'plugin dev' bullet (including 'restart') to the Commands index in README.
- Update CHANGELOG and README prose to include 'restart' in the subcommand list.

test-adequacy (fullsend):
- Add test for parseComposeStatus JSON array branch (Podman Compose format).
- Add test for malformed JSON array input triggering the try/catch error path.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@gashcrumb

Copy link
Copy Markdown
Member Author

Addressed in ea7a53a:

[stale-doc] Added plugin dev (including restart) to the Plugin Development Commands index in the README. CHANGELOG and README prose updated.

[test-adequacy] Added two parseComposeStatus tests for the JSON array branch: one for valid Podman Compose array output and one for malformed JSON triggering the try/catch error path.

[protected-path] The AGENTS.md changes are intentional — they document the plugin dev command architecture and the Commander subcommand convention introduced in this PR so future contributors and automated reviewers have a concrete rule to check against. Happy to get human sign-off on that section if needed.

[feat] Also added plugin dev restart in this commit — stops and restarts the RHDH service without re-exporting or re-staging the plugin, for picking up RHDH Local configuration changes.

podman-compose ps does not support --all and exits with an error when it
is passed. The earlier change to pass --all unconditionally broke plugin
dev status and plugin dev restart for podman users.

Restore the original discriminator: --all is added only for docker, where
compose ps requires it to include exited containers. podman-compose includes
exited containers by default (verified with 1.6.0) so the flag is omitted.

composeStatusArgs() takes containerTool again. Update the call site in
getRuntimeStatus and add a test for the podman variant alongside the
existing docker test.

Assisted-By: claude-sonnet-4-6@default

rh-pre-commit.version: 2.4.0
rh-pre-commit.check-secrets: ENABLED
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants