Skip to content

feat: remove scalprum frontend support - #182

Open
hopehadfield wants to merge 9 commits into
redhat-developer:mainfrom
hopehadfield:remove-scalprum
Open

hopehadfield wants to merge 9 commits into
redhat-developer:mainfrom
hopehadfield:remove-scalprum

Conversation

@hopehadfield

Copy link
Copy Markdown
Member

https://redhat.atlassian.net/browse/RHIDP-15353

Make frontend plugin export use Backstage standard module federation exclusively and remove the legacy Scalprum bundler, commands, options, and dependencies. Clean legacy artifacts from packaged output, preserve NFS feature metadata when available, and update end-to-end expectations for NFS remotes.

Assisted-by: gpt-5.6-luna

Make frontend plugin export use Backstage standard module federation exclusively and remove the legacy Scalprum bundler, commands, options, and dependencies. Clean legacy artifacts from packaged output, preserve NFS feature metadata when available, and update end-to-end expectations for NFS remotes.

Assisted-by: gpt-5.6-luna
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 2 iteration(s)) · Started 8:39 PM UTC · Completed 9:20 PM UTC

Commit: f40fdb4 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 9, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 1 iteration(s)) · Started 6:45 PM UTC · Completed 6:47 PM UTC

Commit: 45eb8a2 · View workflow run →

Runtime: claude · Model: opus → claude-opus-5 · Effort: high

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:02 PM UTC · Completed 2:22 PM UTC

Commit: e652c51 · View workflow run →

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

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

fullsend-ai-review Bot commented Sep 14, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Re-anchoring to prior score of 2: Tier 1 signals are unchanged (large blast radius, 0 protected paths, 2 dependency files changed), and Tier 2 reveals modest churn driven largely by housekeeping files with 6 fix commits concentrated on paths being deleted.

Previous run

Risk Assessment: moderate (2/5)

Details

Re-anchoring to prior score of 2: Tier 1 signals are unchanged (large blast radius, 0 protected paths, expected dependency-file changes), and while the new delta introduces a functional modification of the actively-maintained frontend.ts, the overall PR remains a large removal of confirmed-dormant bundler code with no protected paths or security-sensitive changes.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Large deletion PR removing stable, dormant bundler code keeps overall risk moderate despite the size and dependency-file changes; the only high-churn file is yarn.lock, which is expected for a dependency removal.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 14, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [breaking-cli] src/commands/index.ts:85 — Five options are removed from rhdh-cli plugin export: --scalprum-config, --generate-scalprum-assets, --no-generate-scalprum-assets, --generate-module-federation-assets, and --no-generate-module-federation-assets. Any external CI pipeline, Makefile, or shell script that passes any of these flags will receive an "unknown option" error and the export will abort.
    Remediation: Remove all five options from rhdh-cli plugin export invocations. Standard module-federation assets are now generated unconditionally — no flag is needed.

  • [artifact-protocol-breaking] src/commands/export-dynamic-plugin/frontend.ts — The published output artifact for frontend dynamic plugins changes format: dist-dynamic/dist-scalprum/plugin-manifest.json is no longer produced; the canonical artifact is now dist-dynamic/dist/remoteEntry.js. Any downstream RHDH platform loader, deployment pipeline, or consumer repository that reads dist-scalprum/plugin-manifest.json to discover or load a dynamic frontend plugin will fail silently (file absent) after upgrading to 3.0.0.
    Remediation: Update all consumers (RHDH platform loader, CI packaging scripts, Helm charts, operator configs) to locate dynamic frontend plugin assets under dist/remoteEntry.js. Use backstage.features in the plugin's package.json for NFS feature metadata in place of Scalprum manifest fields.

  • [breaking-contract] src/commands/export-dynamic-plugin/frontend.ts:48 — The scalprum field in a plugin's package.json was previously consumed as inline Scalprum configuration during plugin export. It now causes an immediate hard error: "Frontend plugin export no longer supports the scalprum package field; remove it before exporting". Any plugin package that includes "scalprum": {...} will encounter a hard export failure when the CLI is upgraded to 3.0.0.
    Remediation: Remove the scalprum key from each plugin's package.json before upgrading to rhdh-cli 3.0.0. Scalprum module exposure configuration is no longer applicable; module federation is handled automatically.

Medium

  • [breaking-contract] src/commands/export-dynamic-plugin/frontend.ts:54 — If a plugin's npm files array contains any glob that includes dist-scalprum (e.g. "dist-scalprum/**"), or if a dist-scalprum/ directory exists on disk in the plugin's target directory, plugin export now throws a hard error before any build step executes. Previously both were valid Scalprum outputs and caused no error. Plugins that have not removed Scalprum artifacts from their package manifest and working tree will be blocked from exporting.
    Remediation: Remove any dist-scalprum globs from plugin files fields and delete checked-in or stale dist-scalprum/ directories before running rhdh-cli plugin export with 3.0.0.

Low

  • [scope-authorization-unverifiable] N/A — Authorization for this non-trivial breaking change (3.0.0, 30+ files removed) relies solely on Jira ticket RHIDP-15353, which is behind an auth wall and cannot be verified. No GitHub issue is linked as a verifiable alternative.
    Remediation: Link a GitHub issue (or create one) that captures the removal decision, migration timeline, and stakeholder acknowledgment so that the authorization is verifiable outside the Jira auth boundary.

  • [error-handling-idiom] src/commands/export-dynamic-plugin/command.ts:52 — The guard if (fs.existsSync(path.join(targetPath, 'dist'))) that previously protected the config-schema write path was removed. The new code unconditionally assigns configSchemaPaths. If dist-dynamic/dist does not exist (e.g., a plugin whose files field omits dist), the subsequent fs.writeJson throws ENOENT because fs-extra's writeJson does not create parent directories.
    Remediation: Use fs.outputJson instead of fs.writeJson, or add await fs.ensureDir(path.dirname(configSchemaPath)) before the write loop.

  • [pr-conventions] N/A — The PR title feat: remove scalprum frontend support omits the breaking-change marker. With package.json bumped to 3.0.0 and CLI options removed, this meets the Conventional Commits breaking-change threshold. AGENTS.md requires Conventional Commits compliance enforced by CI.
    Remediation: Change the title to feat!: remove scalprum frontend support or add a BREAKING CHANGE: footer to the PR body.


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

  • [scope-authorization-unverifiable] N/A — Authorization for this non-trivial breaking change (3.0.0, 30+ files changed) relies solely on Jira ticket RHIDP-15353, which is behind an auth wall and cannot be verified. No GitHub issue is linked as a verifiable alternative. The change permanently removes the plugin build, plugin start, and Scalprum bundler commands — commands that consumers may depend on.
    Remediation: Link a GitHub issue (or create one) that captures the removal decision, migration timeline, and stakeholder acknowledgment so that the authorization is verifiable outside the Jira auth boundary.

Low

  • [error-handling] src/commands/export-dynamic-plugin/command.ts:52 — The guard if (fs.existsSync(path.join(targetPath, 'dist'))) that previously protected the config-schema write path was removed. The new code unconditionally assigns configSchemaPaths. If dist-dynamic/dist does not exist (e.g., a plugin whose files field omits dist), the subsequent fs.writeJson throws ENOENT because fs-extra's writeJson does not create parent directories — replacing a silent skip with an uncaught error.
    Remediation: Use fs.outputJson instead of fs.writeJson, or add await fs.ensureDir(path.dirname(configSchemaPath)) before the write loop.

  • [pr-conventions] N/A — The PR title feat: remove scalprum frontend support omits the breaking-change marker. With package.json bumped to 3.0.0 and three CLI commands removed, this meets the Conventional Commits breaking-change threshold. AGENTS.md requires Conventional Commits compliance enforced by CI.
    Remediation: Change the title to feat!: remove scalprum frontend support or add a BREAKING CHANGE: footer to the PR body.

  • [architectural-conflict] src/commands/export-dynamic-plugin/frontend.ts:18import { buildFrontend } from "@backstage/cli-module-build/dist/lib/buildFrontend.cjs.js" hard-codes an internal compiled-artifact path. With all Scalprum fallback paths removed, the entire frontend export path depends on this single unstable import. An upstream build-layout reorganization would break this at runtime with no TypeScript warning.
    Remediation: File an upstream issue requesting a stable public export-map entry in @backstage/cli-module-build, or wrap the import in a local adapter module with a version-guard comment.

  • [breaking-cli] src/commands/export-dynamic-plugin/frontend.ts:174 — An error is thrown if dist-scalprum files remain in the packed output after the fs.remove at line 129. Because line 129 handles the top-level case, this check only fires for edge-case parent-glob plugins. Migration documentation in CHANGELOG.md and README.md addresses the upgrade requirement.

  • [injection] src/commands/export-dynamic-plugin/frontend.ts:175 — The error message interpolates raw filesystem paths via legacyScalprumFiles.join(', ') without sanitizing GHA workflow command sequences (::set-env::, ::set-output::, %0A). If this CLI runs in a GHA job and a crafted filename is present in the packed output, the error log could be interpreted as a workflow command. Advisory: the precondition chain (write access to the plugin source tree + crafted filename + error fires) is narrow in practice.
    Remediation: Sanitize each path by replacing :: sequences and URL-encoded newlines before constructing the error message, or report only the file count.


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

High

  • [breaking-cli] src/commands/index.ts — This PR removes three CLI flags (--scalprum-config, --generate-scalprum-assets/--no-generate-scalprum-assets, --generate-module-federation-assets/--no-generate-module-federation-assets) and eliminates dist-scalprum/ from the plugin export output. Commander.js throws on unrecognized options, so any script or pipeline passing the removed flags will fail immediately. CI/CD pipelines that read dist-scalprum/plugin-manifest.json will break silently. The version bump (2.0.6 → 2.1.0) does not reflect semver expectations for removed public CLI surface and changed default output layout — a major bump (3.0.0) is warranted.
    Remediation: Bump to 3.0.0 (or document the project policy if minor bumps are permitted for CLI-only changes); add a CHANGELOG entry and migration guide; consider silently ignoring removed flags with a deprecation warning for one release cycle.

  • [missing-doc] CHANGELOG.md — No ## 2.1.0 entry exists despite the version bump from 2.0.6 to 2.1.0. The README Release Process section (line 121) explicitly requires a new CHANGELOG heading for every version bump. The removed flags, changed output defaults, and eliminated dist-scalprum/ output all warrant documentation.
    Remediation: Add a ## 2.1.0 - YYYY-MM-DD section documenting: removal of --scalprum-config, --generate-scalprum-assets, and --generate-module-federation-assets from plugin export; plugin export for frontend plugins now exclusively generates module-federation assets in dist/ (no longer producing dist-scalprum/).

Medium

  • [logic-error] src/commands/export-dynamic-plugin/frontend.ts:47isScalprumFilesEntry computes suffix = normalizedPath.slice(13) without first checking that normalizedPath starts with "dist-scalprum". Any string whose 14th character is /, *, ?, or [ is a false positive — e.g. "dist-dynamic/*" produces suffix = "*" which matches. False positives silently remove legitimate entries from the derived package files array, potentially causing files to be absent from the packaged dynamic plugin.
    Remediation: Add a guard: return normalizedPath === "dist-scalprum" || (normalizedPath.startsWith("dist-scalprum") && (suffix.startsWith("/") || suffix.startsWith("*") || suffix.startsWith("?") || suffix.startsWith("[")));

Low

  • [breaking-cli] src/commands/export-dynamic-plugin/frontend.ts:165 — New code throws if any dist-scalprum files remain in packed output after removal. fs.remove on line 123 handles the typical stale-directory case, so this is a safety net for edge cases. Plugin authors with dist-scalprum in their files array should be informed.
    Remediation: Document that plugin authors must remove dist-scalprum from their files array after upgrading.

  • [pattern-inconsistency] src/commands/export-dynamic-plugin/frontend.ts:27import recursive from "recursive-readdir" (third-party) is placed in the same import block as import path from "path" (Node.js built-in), mixing import groups against the established convention in this file and its siblings.
    Remediation: Move the recursive import into the third-party block alongside chalk, commander, and fs-extra.

  • [pr-conventions] N/A — The PR title omits the breaking-change marker. Conventional Commits specifies feat!: or a BREAKING CHANGE: footer for backwards-incompatible changes. AGENTS.md requires Conventional Commits compliance enforced by CI.
    Remediation: Change title to "feat!: remove scalprum frontend support" or add a BREAKING CHANGE: footer to the PR body.

  • [architectural-conflict] src/commands/export-dynamic-plugin/frontend.ts:18import { buildFrontend } from "@backstage/cli-module-build/dist/lib/buildFrontend.cjs.js" uses a hard-coded internal compiled-artifact path. If the upstream package reorganizes its build layout in any release, this import breaks at runtime with no build-time TypeScript warning.
    Remediation: File an upstream issue requesting a stable public export map entry in @backstage/cli-module-build, or centralize the unstable import in a local adapter module.


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.

fullsend-ai-review[bot]

This comment was marked as outdated.

Document the breaking frontend export migration to standard Backstage module federation and NFS metadata. Remove legacy Scalprum options and output references from the supported workflow, and publish the cutover as a major CLI release.

Assisted-by: gpt-5.6-luna
Only remove files entries that target dist-scalprum or one of its glob variants, avoiding false positives for unrelated package paths.

Assisted-by: gpt-5.6-luna
Keep the recursive-readdir third-party import with the other package imports and separate it from the Node.js path import.

Assisted-by: gpt-5.6-luna
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:38 PM UTC · Completed 4:58 PM UTC

Commit: 1b29437 · View workflow run →

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

getFullPluginPath(),
'dist-dynamic/dist-scalprum/plugin-manifest.json',
),
path.join(getFullPluginPath(), 'dist-dynamic/dist/remoteEntry.js'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of adding tests that verify the dist-scalprum directory no longer exists, we can just remove any expects on the directory entirely. If we no longer carry it as an implementation, I see no need to test its absence.

),
).toEqual(true);
// eslint-disable-next-line jest/no-conditional-expect
expect(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, lets just drop it.

// eslint-disable-next-line jest/no-conditional-expect
expect(distDynamicPkg.backstage?.features).toEqual(expectedFeatures);
// eslint-disable-next-line jest/no-conditional-expect
expect(distDynamicPkg).not.toHaveProperty('scalprum');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here, lets just drop it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's drop any 'scaprum' related assertions here as well.


// Remove stale Scalprum output that may have been included by npm-packlist
// before the derived package manifest is customized below.
await fs.remove(path.join(target, 'dist-scalprum'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where would this dist-scalprum directory come from? Can we not remove this line and ensure it is never emitted where it is sourced from instead of trying to clean it up here?

scripts: {},
files,
files: Array.isArray(files)
? files.filter((file: string) => !isScalprumFilesEntry(file))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather than trying to filter out the Scalprum entries, could we not error directly when such an entry is found? Plugins should no longer be carrying Scalprum-related content any longer, so it would be fine for them to fail so authors are forced to clean up. WDYT?

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.

Makes sense to me. I removed the cleanup and changed it to fail early when it detects anything scalprum-related.

}
: undefined,
after: pkg => {
delete (pkg as unknown as Record<string, unknown>).scalprum;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here; if any Scalprum-related content is found, this should immediately fail an export. We should not try to fix it up for plugin authors.

`Generating dynamic frontend plugin assets in ${chalk.cyan(
resolvedScalprumDistPath,
)}`,
const legacyScalprumFiles = (await recursive(target)).filter(file =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we fail early, I think we can forgo this check entirely (see comments above).

return normalized === 'true' || normalized === '1' || normalized === 'yes';
}

function isScalprumFilesEntry(file: string): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps a simple file.includes('dist-scalprum') would suffice? We don't really care about glob semantics, and we're only targeting a minimal set of packages.

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

Keep the e2e coverage focused on the standard module federation output and feature metadata rather than asserting the absence of removed implementation details.

Assisted-by: gpt-5.6-luna
Use the minimal dist-scalprum substring check for the small set of supported frontend packages, as suggested in review.
Fail frontend exports when plugin metadata, files entries, or output directories still reference Scalprum instead of silently filtering or cleaning them.
@sonarqubecloud

Copy link
Copy Markdown

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:29 PM UTC · Ended 9:51 PM UTC

Commit: f7f74b7 · View workflow run →

@hopehadfield

Copy link
Copy Markdown
Member Author

Updated with your comments @jonkoops, thanks for the review! Looks like we have some removals to do on the plugins side before this gets in...

@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.

Comment thread src/commands/index.ts
true,
)
.option('--no-generate-module-federation-assets', '', false)
.action(lazy(() => import('./export-dynamic-plugin').then(m => m.command)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] breaking-cli

Five options are removed from rhdh-cli plugin export: --scalprum-config, --generate-scalprum-assets, --no-generate-scalprum-assets, --generate-module-federation-assets, and --no-generate-module-federation-assets. Any external CI pipeline, Makefile, or shell script that passes any of these flags will receive an 'unknown option' error and the export will abort.

Suggested fix: Remove all five options from rhdh-cli plugin export invocations. Standard module-federation assets are now generated unconditionally — no flag is needed.

const { name, files } = originalPkg;

if (!opts.generateScalprumAssets && !opts.generateModuleFederationAssets) {
if ('scalprum' in originalPkg) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] breaking-contract

The scalprum field in a plugin's package.json was previously consumed as inline Scalprum configuration during plugin export. It now causes an immediate hard error. Any plugin package that includes scalprum: {...} will encounter a hard export failure when the CLI is upgraded to 3.0.0.

Suggested fix: Remove the scalprum key from each plugin's package.json before upgrading to rhdh-cli 3.0.0. Scalprum module exposure configuration is no longer applicable; module federation is handled automatically.

if (opts.clean) {
await fs.remove(path.join(paths.targetDir, 'dist'));
}
const legacyScalprumFile = Array.isArray(files)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-contract

If a plugin's npm files array contains any glob that includes dist-scalprum, or if a dist-scalprum/ directory exists on disk in the plugin's target directory, plugin export now throws a hard error before any build step executes. Plugins that have not removed Scalprum artifacts from their package manifest and working tree will be blocked from exporting.

Suggested fix: Remove any dist-scalprum globs from plugin files fields and delete checked-in or stale dist-scalprum/ directories before running rhdh-cli plugin export with 3.0.0.

if (fs.existsSync(path.join(targetPath, 'dist'))) {
configSchemaPaths.push(path.join(targetPath, 'dist/.config-schema.json'));
}
configSchemaPaths = [path.join(targetPath, 'dist/.config-schema.json')];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling-idiom

The guard that previously protected the config-schema write path was removed. The new code unconditionally assigns configSchemaPaths. If dist-dynamic/dist does not exist, the subsequent fs.writeJson throws ENOENT because fs-extra's writeJson does not create parent directories.

Suggested fix: Use fs.outputJson instead of fs.writeJson, or add await fs.ensureDir(path.dirname(configSchemaPath)) before the write loop.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:29 PM UTC · Completed 9:51 PM UTC

Commit: f7f74b7 · View workflow run →

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

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.

2 participants