diff --git a/.github/workflows/verify-plugin-export.yaml b/.github/workflows/verify-plugin-export.yaml index 4de74e0..791bd92 100644 --- a/.github/workflows/verify-plugin-export.yaml +++ b/.github/workflows/verify-plugin-export.yaml @@ -67,7 +67,6 @@ jobs: - extensions - global-header - homepage - - lightspeed - orchestrator - quickstart - scorecard diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d01505..9fa4e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to `@red-hat-developer-hub/cli` are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.11.4 - 2026-07-30 + +### Fixed + +- Propagate monorepo root yarn resolutions to dynamic plugin exports: + read resolutions from the monorepo root `package.json` and propagate + them to the generated `dist-dynamic/package.json`, filtering out `patch:` + resolutions. + ## 1.11.3 - 2026-07-20 ### Fixed diff --git a/e2e-tests/rhdh-plugins-build-package.test.ts b/e2e-tests/rhdh-plugins-build-package.test.ts index f0af0c5..2b95246 100644 --- a/e2e-tests/rhdh-plugins-build-package.test.ts +++ b/e2e-tests/rhdh-plugins-build-package.test.ts @@ -104,7 +104,7 @@ describe('export and package rhdh-plugins scorecard workspace plugin', () => { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); const role = packageJson.backstage?.role; const expectedFeatures = expect.objectContaining({ - './alpha': '@backstage/FrontendPlugin', + '.': '@backstage/FrontendPlugin', }); if (role === 'frontend-plugin') { // eslint-disable-next-line jest/no-conditional-expect diff --git a/package.json b/package.json index 17c0db5..008c65c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@red-hat-developer-hub/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "1.11.3", + "version": "1.11.4", "publishConfig": { "access": "public" }, diff --git a/src/commands/export-dynamic-plugin/backend.ts b/src/commands/export-dynamic-plugin/backend.ts index e9cbca4..16148c1 100644 --- a/src/commands/export-dynamic-plugin/backend.ts +++ b/src/commands/export-dynamic-plugin/backend.ts @@ -43,6 +43,13 @@ import { isValidPluginModule, } from './backend-utils'; +export async function getMonorepoRootResolutions(): Promise< + Record +> { + const rootPkgPath = path.join(paths.targetRoot, 'package.json'); + return (await fs.readJson(rootPkgPath).catch(() => ({}))).resolutions || {}; +} + export async function backend(opts: OptionValues): Promise { const targetRelativePath = 'dist-dynamic'; const target = path.join(paths.targetDir, targetRelativePath); @@ -68,6 +75,8 @@ export async function backend(opts: OptionValues): Promise { const suppressNative = (opts.suppressNativePackage || []) as string[]; const ignoreVersionCheck = (opts.ignoreVersionCheck || []) as string[]; const monoRepoPackages = await getPackages(paths.targetDir); + + const rootResolutions = await getMonorepoRootResolutions(); const embeddedResolvedPackages = await searchEmbedded( pkg, packagesToEmbed, @@ -291,6 +300,7 @@ throw new Error( // which are related to the packaging of the original static package. scripts: {}, }, + rootResolutions, additionalResolutions: { ...embeddedDependenciesResolutions, ...suppressNative @@ -772,6 +782,7 @@ export function customizeForDynamicUse(options: { }) | undefined; additionalOverrides?: { [key: string]: any } | undefined; + rootResolutions?: Record | undefined; additionalResolutions?: { [key: string]: any } | undefined; after?: ((pkg: BackstagePackageJson) => void) | undefined; }): (dynamicPkgPath: string) => Promise { @@ -945,6 +956,24 @@ export function customizeForDynamicUse(options: { // to prevent npm version drift during yarn install --no-immutable. // Existing resolutions and additionalResolutions (embedded file: refs) take precedence. const resolutions = (pkgToCustomize as any).resolutions || {}; + + // Skip patch: resolutions — the .yarn/patches/ files won't exist in the + // dist-dynamic directory so those entries can't be applied. Propagating + // only plain version resolutions avoids asymmetry when the workspace is + // scrubbed (--remove-patches deletes patch: entries between loops). + const filteredRootResolutions: Record = {}; + if (options.rootResolutions) { + for (const [key, value] of Object.entries(options.rootResolutions)) { + if (value.startsWith('patch:')) continue; + filteredRootResolutions[key] = value; + } + } + const rootResKeys = Object.keys(filteredRootResolutions); + if (rootResKeys.length > 0) { + Task.log( + ` Propagating ${rootResKeys.length} root resolution(s) to dist-dynamic: ${rootResKeys.join(', ')}`, + ); + } (pkgToCustomize as any).resolutions = { // The following lines are a workaround for the fact that the @aws-sdk/util-utf8-browser package // is not compatible with the NPM 9+, so that `npm pack` would not grab the Javascript files. @@ -952,6 +981,7 @@ export function customizeForDynamicUse(options: { // // See https://github.com/aws/aws-sdk-js-v3/issues/5305. '@aws-sdk/util-utf8-browser': 'npm:@smithy/util-utf8@~2', + ...filteredRootResolutions, ...pinnedResolutions, ...resolutions, ...(options.additionalResolutions || {}), diff --git a/src/commands/export-dynamic-plugin/frontend.ts b/src/commands/export-dynamic-plugin/frontend.ts index a1aa95b..f5c0960 100644 --- a/src/commands/export-dynamic-plugin/frontend.ts +++ b/src/commands/export-dynamic-plugin/frontend.ts @@ -28,7 +28,7 @@ import { buildScalprumPlugin } from '../../lib/builder/buildScalprumPlugin'; import { productionPack } from '../../lib/packager/productionPack'; import { paths } from '../../lib/paths'; import { Task } from '../../lib/tasks'; -import { customizeForDynamicUse } from './backend'; +import { customizeForDynamicUse, getMonorepoRootResolutions } from './backend'; import { detectBackstageFeatures } from './features'; function isTruthyCiEnv(value: string | undefined): boolean { @@ -128,6 +128,9 @@ export async function frontend( : undefined; const monoRepoPackages = await getPackages(paths.targetDir); + + const rootResolutions = await getMonorepoRootResolutions(); + await customizeForDynamicUse({ embedded: [], isYarnV1: false, @@ -142,6 +145,7 @@ export async function frontend( scripts: {}, files, }, + rootResolutions, after: detectedFeatures ? pkg => { pkg.backstage = pkg.backstage ?? {};