From ddca5d2f6680f37e67733d6c9f5a76aa0cd759a8 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 19 Mar 2024 16:36:51 +0100 Subject: [PATCH 01/14] Change package name if moved Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 33 +++++++++++++++------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index d64d5a9756..a995f07745 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -372,26 +372,39 @@ export function createVersionFinder(options: { releaseManifest?.packages.map(p => [p.name, p.version]), ); return async function findTargetVersion(name: string) { - const existing = found.get(name); + let _name = name; + const existing = found.get(_name); if (existing) { return existing; } - console.log(`Checking for updates of ${name}`); - const manifestVersion = releasePackages.get(name); + console.log(`Checking for updates of ${_name}`); + + const manifestVersion = releasePackages.get(_name); if (manifestVersion) { return manifestVersion; } - const info = await packageInfoFetcher(name); + // TODO: Properly fix inacurate typing here... + const info = (await packageInfoFetcher(_name)) as YarnInfoInspectData & { + backstage?: { moved?: string }; + }; + + if ( + info.backstage?.moved && + info.backstage.moved.startsWith('@backstage-community/') + ) { + _name = info.backstage.moved; + } + const latestVersion = info['dist-tags'].latest; if (!latestVersion) { - throw new Error(`No target 'latest' version found for ${name}`); + throw new Error(`No target 'latest' version found for ${_name}`); } const taggedVersion = info['dist-tags'][distTag]; if (distTag === 'latest' || !taggedVersion) { - found.set(name, latestVersion); + found.set(_name, latestVersion); return latestVersion; } @@ -399,12 +412,12 @@ export function createVersionFinder(options: { const taggedVersionDateStr = info.time[taggedVersion]; if (!latestVersionDateStr) { throw new Error( - `No time available for version '${latestVersion}' of ${name}`, + `No time available for version '${latestVersion}' of ${_name}`, ); } if (!taggedVersionDateStr) { throw new Error( - `No time available for version '${taggedVersion}' of ${name}`, + `No time available for version '${taggedVersion}' of ${_name}`, ); } @@ -412,11 +425,11 @@ export function createVersionFinder(options: { const taggedVersionRelease = new Date(taggedVersionDateStr).getTime(); if (latestVersionRelease > taggedVersionRelease) { // Prefer latest version if it's newer. - found.set(name, latestVersion); + found.set(_name, latestVersion); return latestVersion; } - found.set(name, taggedVersion); + found.set(_name, taggedVersion); return taggedVersion; }; } From 0f3f72a40151f29313c63a7a23ee5987781d83c7 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 19 Mar 2024 16:39:09 +0100 Subject: [PATCH 02/14] Add changeset Signed-off-by: Philipp Hugenroth --- .changeset/nervous-pans-push.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nervous-pans-push.md diff --git a/.changeset/nervous-pans-push.md b/.changeset/nervous-pans-push.md new file mode 100644 index 0000000000..594dc26785 --- /dev/null +++ b/.changeset/nervous-pans-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Update reference if a package is moved to `@backstage-community` scope From a1a36cc67a23fa8e40de47ce55fc03d40effec54 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 5 Apr 2024 12:05:05 +0200 Subject: [PATCH 03/14] Update bump script Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 114 +++++++++++++++------ 1 file changed, 80 insertions(+), 34 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index a995f07745..121472ee18 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -108,6 +108,11 @@ export default async (opts: OptionValues) => { const versionBumps = new Map(); // Track package versions that we want to remove from yarn.lock in order to trigger a bump const unlocked = Array<{ name: string; range: string; target: string }>(); + // Moved packages & their new package version info + const moved = new Map< + string, + { name: string; range: string; target: string } + >(); await runParallelWorkers({ parallelismFactor: 4, @@ -173,6 +178,33 @@ export default async (opts: OptionValues) => { }, }); + await runParallelWorkers({ + parallelismFactor: 4, + items: unlocked, + async worker({ name, range, target }) { + let info = (await fetchPackageInfo(name)) as YarnInfoInspectData & { + backstage?: { moved: string }; + }; + + // TODO: To be removed + if (name === '@backstage/plugin-catalog-react') { + info.backstage = { + ...info.backstage, + moved: '@pagerduty/backstage-plugin', + }; + } + + if (info.backstage?.moved) { + info = await fetchPackageInfo(info.backstage?.moved); + moved.set(name, { + name: info.name, + range: `^${info['dist-tags'].latest}`, + target: info['dist-tags'].latest, + }); + } + }, + }); + console.log(); // Write all discovered version bumps to package.json in this repo @@ -184,21 +216,35 @@ export default async (opts: OptionValues) => { if (unlocked.length > 0) { const removed = new Set(); - for (const { name, range, target } of unlocked) { + for (const unlockedPackage of unlocked) { + const movedPackage = moved.get(unlockedPackage.name); + const { name, range, target } = movedPackage + ? movedPackage + : unlockedPackage; + // Don't bother removing lockfile entries if they're already on the correct version const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === target) { + if (existingEntry?.version === target && !moved) { continue; } const key = JSON.stringify({ name, range }); if (!removed.has(key)) { removed.add(key); - console.log( - `${chalk.magenta('unlocking')} ${name}@${chalk.yellow( - range, - )} ~> ${chalk.yellow(target)}`, - ); - lockfile.remove(name, range); + if (movedPackage) { + console.log( + `${chalk.yellow('moving')} ${unlockedPackage.name}@${ + unlockedPackage.range + } ~> ${chalk.yellow(`${name}@${range}`)}`, + ); + lockfile.remove(unlockedPackage.name, unlockedPackage.range); + } else { + console.log( + `${chalk.magenta('unlocking')} ${name}@${chalk.yellow( + range, + )} ~> ${chalk.yellow(target)}`, + ); + lockfile.remove(name, range); + } } } await lockfile.save(lockfilePath); @@ -213,15 +259,26 @@ export default async (opts: OptionValues) => { const pkgJson = await fs.readJson(pkgPath); for (const dep of deps) { - console.log( - `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( - name, - )} to ${chalk.yellow(dep.range)}`, - ); + const movedDep = moved.get(dep.name); + if (movedDep) { + console.log( + `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( + name, + )} to ${chalk.yellow(`${movedDep.name}@${movedDep.range}`)}`, + ); + } else { + console.log( + `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( + name, + )} to ${chalk.yellow(dep.range)}`, + ); + } for (const depType of DEP_TYPES) { if (depType in pkgJson && dep.name in pkgJson[depType]) { const oldRange = pkgJson[depType][dep.name]; + + // TODO: Remove & replace when dependency moved pkgJson[depType][dep.name] = dep.range; // Check if the update was at least a pre-v1 minor or post-v1 major release @@ -372,39 +429,28 @@ export function createVersionFinder(options: { releaseManifest?.packages.map(p => [p.name, p.version]), ); return async function findTargetVersion(name: string) { - let _name = name; - const existing = found.get(_name); + const existing = found.get(name); if (existing) { return existing; } - console.log(`Checking for updates of ${_name}`); + console.log(`Checking for updates of ${name}`); - const manifestVersion = releasePackages.get(_name); + const manifestVersion = releasePackages.get(name); if (manifestVersion) { return manifestVersion; } - // TODO: Properly fix inacurate typing here... - const info = (await packageInfoFetcher(_name)) as YarnInfoInspectData & { - backstage?: { moved?: string }; - }; - - if ( - info.backstage?.moved && - info.backstage.moved.startsWith('@backstage-community/') - ) { - _name = info.backstage.moved; - } + const info = (await packageInfoFetcher(name)) as YarnInfoInspectData; const latestVersion = info['dist-tags'].latest; if (!latestVersion) { - throw new Error(`No target 'latest' version found for ${_name}`); + throw new Error(`No target 'latest' version found for ${name}`); } const taggedVersion = info['dist-tags'][distTag]; if (distTag === 'latest' || !taggedVersion) { - found.set(_name, latestVersion); + found.set(name, latestVersion); return latestVersion; } @@ -412,12 +458,12 @@ export function createVersionFinder(options: { const taggedVersionDateStr = info.time[taggedVersion]; if (!latestVersionDateStr) { throw new Error( - `No time available for version '${latestVersion}' of ${_name}`, + `No time available for version '${latestVersion}' of ${name}`, ); } if (!taggedVersionDateStr) { throw new Error( - `No time available for version '${taggedVersion}' of ${_name}`, + `No time available for version '${taggedVersion}' of ${name}`, ); } @@ -425,11 +471,11 @@ export function createVersionFinder(options: { const taggedVersionRelease = new Date(taggedVersionDateStr).getTime(); if (latestVersionRelease > taggedVersionRelease) { // Prefer latest version if it's newer. - found.set(_name, latestVersion); + found.set(name, latestVersion); return latestVersion; } - found.set(_name, taggedVersion); + found.set(name, taggedVersion); return taggedVersion; }; } From ce463c570f185b3037713f6ed5d7151b64c3e07a Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 5 Apr 2024 14:27:09 +0200 Subject: [PATCH 04/14] Replace dependency in package.json Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 121472ee18..49d79ba9c5 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -262,7 +262,7 @@ export default async (opts: OptionValues) => { const movedDep = moved.get(dep.name); if (movedDep) { console.log( - `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( + `${chalk.yellow('bumping')} ${dep.name} in ${chalk.cyan( name, )} to ${chalk.yellow(`${movedDep.name}@${movedDep.range}`)}`, ); @@ -276,9 +276,14 @@ export default async (opts: OptionValues) => { for (const depType of DEP_TYPES) { if (depType in pkgJson && dep.name in pkgJson[depType]) { + if (movedDep) { + delete pkgJson[depType][dep.name]; + pkgJson[depType][movedDep.name] = movedDep.range; + continue; + } + const oldRange = pkgJson[depType][dep.name]; - // TODO: Remove & replace when dependency moved pkgJson[depType][dep.name] = dep.range; // Check if the update was at least a pre-v1 minor or post-v1 major release From ae4b56031e30f8e3a0454ff3b184eb3c803ee7eb Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 5 Apr 2024 14:38:17 +0200 Subject: [PATCH 05/14] Clean up changes Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 49d79ba9c5..7d9d6859c3 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -181,7 +181,7 @@ export default async (opts: OptionValues) => { await runParallelWorkers({ parallelismFactor: 4, items: unlocked, - async worker({ name, range, target }) { + async worker({ name }) { let info = (await fetchPackageInfo(name)) as YarnInfoInspectData & { backstage?: { moved: string }; }; @@ -224,7 +224,7 @@ export default async (opts: OptionValues) => { // Don't bother removing lockfile entries if they're already on the correct version const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === target && !moved) { + if (existingEntry?.version === target && !movedPackage) { continue; } const key = JSON.stringify({ name, range }); @@ -283,7 +283,6 @@ export default async (opts: OptionValues) => { } const oldRange = pkgJson[depType][dep.name]; - pkgJson[depType][dep.name] = dep.range; // Check if the update was at least a pre-v1 minor or post-v1 major release @@ -440,14 +439,12 @@ export function createVersionFinder(options: { } console.log(`Checking for updates of ${name}`); - const manifestVersion = releasePackages.get(name); if (manifestVersion) { return manifestVersion; } - const info = (await packageInfoFetcher(name)) as YarnInfoInspectData; - + const info = await packageInfoFetcher(name); const latestVersion = info['dist-tags'].latest; if (!latestVersion) { throw new Error(`No target 'latest' version found for ${name}`); From 302f27edd2e284bb75de258707e99078a1edf98a Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 11 Apr 2024 22:41:03 +0200 Subject: [PATCH 06/14] Adjust types Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 6 ++---- packages/cli/src/lib/versioning/packages.ts | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 7d9d6859c3..d288ae376d 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -182,9 +182,7 @@ export default async (opts: OptionValues) => { parallelismFactor: 4, items: unlocked, async worker({ name }) { - let info = (await fetchPackageInfo(name)) as YarnInfoInspectData & { - backstage?: { moved: string }; - }; + let info = await fetchPackageInfo(name); // TODO: To be removed if (name === '@backstage/plugin-catalog-react') { @@ -195,7 +193,7 @@ export default async (opts: OptionValues) => { } if (info.backstage?.moved) { - info = await fetchPackageInfo(info.backstage?.moved); + info = await fetchPackageInfo(info.backstage.moved); moved.set(name, { name: info.name, range: `^${info['dist-tags'].latest}`, diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 0499775bf2..bad9dc98fa 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -27,12 +27,16 @@ const DEP_TYPES = [ 'optionalDependencies', ] as const; -// Package data as returned by `yarn info` +// Package data as returned by `yarn info`. This is an uncomplete type fullfilling the required needs in the CLI export type YarnInfoInspectData = { name: string; 'dist-tags': Record; versions: string[]; time: { [version: string]: string }; + backstage?: { + role?: string; + moved?: string; + }; }; // Possible `yarn info` output From f401739ec563f907e0a06b9f00b53c1a578a3663 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 12 Apr 2024 15:47:24 +0200 Subject: [PATCH 07/14] Ensure mismatching versions are not ignroed for moved plugins Signed-off-by: Philipp Hugenroth --- packages/cli/src/commands/versions/bump.ts | 99 +++++++++++++--------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index d288ae376d..a589e91fc7 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -14,32 +14,32 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import chalk from 'chalk'; -import ora from 'ora'; -import semver from 'semver'; -import { minimatch } from 'minimatch'; -import { OptionValues } from 'commander'; -import { isError, NotFoundError } from '@backstage/errors'; -import { resolve as resolvePath } from 'path'; -import { run } from '../../lib/run'; -import { paths } from '../../lib/paths'; -import { - mapDependencies, - fetchPackageInfo, - Lockfile, - YarnInfoInspectData, -} from '../../lib/versioning'; -import { forbiddenDuplicatesFilter } from './lint'; import { BACKSTAGE_JSON } from '@backstage/cli-common'; -import { runParallelWorkers } from '../../lib/parallel'; +import { PackageGraph } from '@backstage/cli-node'; +import { isError, NotFoundError } from '@backstage/errors'; import { getManifestByReleaseLine, getManifestByVersion, ReleaseManifest, } from '@backstage/release-manifests'; +import chalk from 'chalk'; +import { OptionValues } from 'commander'; +import fs from 'fs-extra'; import 'global-agent/bootstrap'; -import { PackageGraph } from '@backstage/cli-node'; +import { minimatch } from 'minimatch'; +import ora from 'ora'; +import { resolve as resolvePath } from 'path'; +import semver from 'semver'; +import { runParallelWorkers } from '../../lib/parallel'; +import { paths } from '../../lib/paths'; +import { run } from '../../lib/run'; +import { + fetchPackageInfo, + Lockfile, + mapDependencies, + YarnInfoInspectData, +} from '../../lib/versioning'; +import { forbiddenDuplicatesFilter } from './lint'; const DEP_TYPES = [ 'dependencies', @@ -108,6 +108,8 @@ export default async (opts: OptionValues) => { const versionBumps = new Map(); // Track package versions that we want to remove from yarn.lock in order to trigger a bump const unlocked = Array<{ name: string; range: string; target: string }>(); + // All backstage package versions in yarn.lock that are not in `unlocked` + const outsideRange = Array<{ name: string; range: string; target: string }>(); // Moved packages & their new package version info const moved = new Map< string, @@ -170,6 +172,7 @@ export default async (opts: OptionValues) => { // Ignore lockfile entries that don't satisfy the version range, since // these can't cause the package to be locked to an older version if (!semver.satisfies(target, entry.range)) { + outsideRange.push({ name, range: entry.range, target }); continue; } // Unlock all entries that are within range but on the old version @@ -180,31 +183,31 @@ export default async (opts: OptionValues) => { await runParallelWorkers({ parallelismFactor: 4, - items: unlocked, + items: [...unlocked, ...outsideRange], async worker({ name }) { - let info = await fetchPackageInfo(name); + if (!moved.get(name)) { + let info = await fetchPackageInfo(name); - // TODO: To be removed - if (name === '@backstage/plugin-catalog-react') { - info.backstage = { - ...info.backstage, - moved: '@pagerduty/backstage-plugin', - }; - } + // TODO: To be removed + if (name === '@backstage/plugin-catalog-react') { + info.backstage = { + ...info.backstage, + moved: '@pagerduty/backstage-plugin', + }; + } - if (info.backstage?.moved) { - info = await fetchPackageInfo(info.backstage.moved); - moved.set(name, { - name: info.name, - range: `^${info['dist-tags'].latest}`, - target: info['dist-tags'].latest, - }); + if (info.backstage?.moved) { + info = await fetchPackageInfo(info.backstage.moved); + moved.set(name, { + name: info.name, + range: `^${info['dist-tags'].latest}`, + target: info['dist-tags'].latest, + }); + } } }, }); - console.log(); - // Write all discovered version bumps to package.json in this repo if (versionBumps.size === 0 && unlocked.length === 0) { console.log(chalk.green('All Backstage packages are up to date!')); @@ -212,8 +215,9 @@ export default async (opts: OptionValues) => { console.log(chalk.yellow('Some packages are outdated, updating')); console.log(); - if (unlocked.length > 0) { + if (unlocked.length > 0 || outsideRange.length > 0) { const removed = new Set(); + for (const unlockedPackage of unlocked) { const movedPackage = moved.get(unlockedPackage.name); const { name, range, target } = movedPackage @@ -245,6 +249,25 @@ export default async (opts: OptionValues) => { } } } + + for (const outsideRangePackage of outsideRange) { + const movedPackage = moved.get(outsideRangePackage.name); + if (movedPackage) { + const { name, range } = movedPackage; + const key = JSON.stringify({ name, range }); + if (!removed.has(key)) { + removed.add(key); + // TODO: Does this need special instructions as there might be custom bumping needed? + console.log( + `${chalk.yellow('moving')} ${movedPackage.name}@${ + movedPackage.range + } ~> ${chalk.yellow(`${name}@${range}`)}`, + ); + lockfile.remove(movedPackage.name, movedPackage.range); + } + } + } + await lockfile.save(lockfilePath); } From ab14656446d78562990d362f92087e17c69ff5f5 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 15 Apr 2024 16:04:18 +0200 Subject: [PATCH 08/14] chore: include backstage-community deps in the bump Signed-off-by: blam --- .../cli/src/commands/versions/bump.test.ts | 161 +++++++++++++++++- packages/cli/src/commands/versions/bump.ts | 152 ++++------------- 2 files changed, 189 insertions(+), 124 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index d00b4ce8da..f3eafb760a 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -92,6 +92,8 @@ const REGISTRY_VERSIONS: { [name: string]: string } = { '@backstage-extra/custom': '1.1.0', '@backstage-extra/custom-two': '2.0.0', '@backstage/create-app': '1.0.0', + '@backstage-community/custom': '1.1.0', + '@backstage-community/custom-two': '2.0.0', }; const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. @@ -204,7 +206,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', @@ -261,6 +263,151 @@ describe('bump', () => { }); }); + it('should bump backstage-community dependencies', async () => { + const customLockfileMock = `${lockfileMock} +"@backstage-community/custom@^1.1.0": + version "1.1.0" + +"@backstage-community/custom@^1.0.1": + version "1.0.1" + +"@backstage-community/custom-two@^1.0.0": + version "1.0.0" +`; + const customLockfileMockResult = `${HEADER} +"@backstage-community/custom-two@^1.0.0": + version "1.0.0" + +"@backstage-community/custom@^1.1.0": + version "1.1.0" + +"@backstage/core@^1.0.5": + version "1.0.6" + dependencies: + "@backstage/core-api" "^1.0.6" + +"@backstage/theme@^1.0.0": + version "1.0.0" +`; + mockDir.setContent({ + 'yarn.lock': customLockfileMock, + 'package.json': JSON.stringify({ + workspaces: { + packages: ['packages/*'], + }, + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + dependencies: { + '@backstage/core': '^1.0.5', + '@backstage-community/custom': '^1.0.1', + '@backstage-community/custom-two': '^1.0.0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + dependencies: { + '@backstage/core': '^1.0.3', + '@backstage/theme': '^1.0.0', + '@backstage-community/custom': '^1.1.0', + '@backstage-community/custom-two': '^1.0.0', + }, + }), + }, + }, + }); + + jest.spyOn(runObj, 'run').mockResolvedValue(undefined); + worker.use( + rest.get( + 'https://versions.backstage.io/v1/tags/main/manifest.json', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + packages: [], + }), + ), + ), + ); + const { log: logs } = await withLogCollector(['log'], async () => { + await bump({ + release: 'main', + } as any); + }); + expectLogsToMatch(logs, [ + 'Checking for updates of @backstage/core', + 'Checking for updates of @backstage-community/custom', + 'Checking for updates of @backstage-community/custom-two', + 'Checking for updates of @backstage/theme', + 'Checking for updates of @backstage/core-api', + 'Some packages are outdated, updating', + 'Using default pattern glob @backstage?(-community)/*', + 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', + 'unlocking @backstage-community/custom@^1.0.1 ~> 1.1.0', + 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', + 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', + 'bumping @backstage/core in a to ^1.0.6', + 'bumping @backstage-community/custom in a to ^1.1.0', + 'bumping @backstage-community/custom-two in a to ^2.0.0', + 'bumping @backstage/core in b to ^1.0.6', + 'bumping @backstage-community/custom in b to ^1.1.0', + 'bumping @backstage-community/custom-two in b to ^2.0.0', + 'bumping @backstage/theme in b to ^2.0.0', + 'Running yarn install to install new versions', + '⚠️ The following packages may have breaking changes:', + ' @backstage-community/custom-two : 1.0.0 ~> 2.0.0', + ' @backstage/theme : 1.0.0 ~> 2.0.0', + ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', + 'Version bump complete!', + ]); + + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5); + expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); + expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); + + expect(runObj.run).toHaveBeenCalledTimes(1); + expect(runObj.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); + + const lockfileContents = await fs.readFile( + mockDir.resolve('yarn.lock'), + 'utf8', + ); + expect(lockfileContents).toEqual(customLockfileMockResult); + + const packageA = await fs.readJson( + mockDir.resolve('packages/a/package.json'), + ); + expect(packageA).toEqual({ + name: 'a', + dependencies: { + '@backstage-community/custom': '^1.1.0', + '@backstage-community/custom-two': '^2.0.0', + '@backstage/core': '^1.0.6', + }, + }); + const packageB = await fs.readJson( + mockDir.resolve('packages/b/package.json'), + ); + expect(packageB).toEqual({ + name: 'b', + dependencies: { + '@backstage-community/custom': '^1.1.0', + '@backstage-community/custom-two': '^2.0.0', + '@backstage/core': '^1.0.6', + '@backstage/theme': '^2.0.0', + }, + }); + }); + it('should bump backstage dependencies but not install them', async () => { mockDir.setContent({ 'yarn.lock': lockfileMock, @@ -311,7 +458,7 @@ describe('bump', () => { } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', @@ -423,7 +570,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/theme', @@ -523,7 +670,7 @@ describe('bump', () => { ).rejects.toThrow('No release found for 999.0.1 version'); }); expect(logs.filter(Boolean)).toEqual([ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', ]); expect(runObj.run).toHaveBeenCalledTimes(0); @@ -626,7 +773,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'next' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/theme', @@ -841,7 +988,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Package info not found, ignoring package @backstage/core', @@ -956,7 +1103,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', + 'Using default pattern glob @backstage?(-community)/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index a589e91fc7..c6a84208ab 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -14,32 +14,32 @@ * limitations under the License. */ -import { BACKSTAGE_JSON } from '@backstage/cli-common'; -import { PackageGraph } from '@backstage/cli-node'; +import fs from 'fs-extra'; +import chalk from 'chalk'; +import ora from 'ora'; +import semver from 'semver'; +import { minimatch } from 'minimatch'; +import { OptionValues } from 'commander'; import { isError, NotFoundError } from '@backstage/errors'; +import { resolve as resolvePath } from 'path'; +import { run } from '../../lib/run'; +import { paths } from '../../lib/paths'; +import { + mapDependencies, + fetchPackageInfo, + Lockfile, + YarnInfoInspectData, +} from '../../lib/versioning'; +import { forbiddenDuplicatesFilter } from './lint'; +import { BACKSTAGE_JSON } from '@backstage/cli-common'; +import { runParallelWorkers } from '../../lib/parallel'; import { getManifestByReleaseLine, getManifestByVersion, ReleaseManifest, } from '@backstage/release-manifests'; -import chalk from 'chalk'; -import { OptionValues } from 'commander'; -import fs from 'fs-extra'; import 'global-agent/bootstrap'; -import { minimatch } from 'minimatch'; -import ora from 'ora'; -import { resolve as resolvePath } from 'path'; -import semver from 'semver'; -import { runParallelWorkers } from '../../lib/parallel'; -import { paths } from '../../lib/paths'; -import { run } from '../../lib/run'; -import { - fetchPackageInfo, - Lockfile, - mapDependencies, - YarnInfoInspectData, -} from '../../lib/versioning'; -import { forbiddenDuplicatesFilter } from './lint'; +import { PackageGraph } from '@backstage/cli-node'; const DEP_TYPES = [ 'dependencies', @@ -48,7 +48,7 @@ const DEP_TYPES = [ 'optionalDependencies', ]; -const DEFAULT_PATTERN_GLOB = '@backstage/*'; +const DEFAULT_PATTERN_GLOB = '@backstage?(-community)/*'; type PkgVersionInfo = { range: string; @@ -108,13 +108,6 @@ export default async (opts: OptionValues) => { const versionBumps = new Map(); // Track package versions that we want to remove from yarn.lock in order to trigger a bump const unlocked = Array<{ name: string; range: string; target: string }>(); - // All backstage package versions in yarn.lock that are not in `unlocked` - const outsideRange = Array<{ name: string; range: string; target: string }>(); - // Moved packages & their new package version info - const moved = new Map< - string, - { name: string; range: string; target: string } - >(); await runParallelWorkers({ parallelismFactor: 4, @@ -172,7 +165,6 @@ export default async (opts: OptionValues) => { // Ignore lockfile entries that don't satisfy the version range, since // these can't cause the package to be locked to an older version if (!semver.satisfies(target, entry.range)) { - outsideRange.push({ name, range: entry.range, target }); continue; } // Unlock all entries that are within range but on the old version @@ -181,32 +173,7 @@ export default async (opts: OptionValues) => { }, }); - await runParallelWorkers({ - parallelismFactor: 4, - items: [...unlocked, ...outsideRange], - async worker({ name }) { - if (!moved.get(name)) { - let info = await fetchPackageInfo(name); - - // TODO: To be removed - if (name === '@backstage/plugin-catalog-react') { - info.backstage = { - ...info.backstage, - moved: '@pagerduty/backstage-plugin', - }; - } - - if (info.backstage?.moved) { - info = await fetchPackageInfo(info.backstage.moved); - moved.set(name, { - name: info.name, - range: `^${info['dist-tags'].latest}`, - target: info['dist-tags'].latest, - }); - } - } - }, - }); + console.log(); // Write all discovered version bumps to package.json in this repo if (versionBumps.size === 0 && unlocked.length === 0) { @@ -215,59 +182,25 @@ export default async (opts: OptionValues) => { console.log(chalk.yellow('Some packages are outdated, updating')); console.log(); - if (unlocked.length > 0 || outsideRange.length > 0) { + if (unlocked.length > 0) { const removed = new Set(); - - for (const unlockedPackage of unlocked) { - const movedPackage = moved.get(unlockedPackage.name); - const { name, range, target } = movedPackage - ? movedPackage - : unlockedPackage; - + for (const { name, range, target } of unlocked) { // Don't bother removing lockfile entries if they're already on the correct version const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === target && !movedPackage) { + if (existingEntry?.version === target) { continue; } const key = JSON.stringify({ name, range }); if (!removed.has(key)) { removed.add(key); - if (movedPackage) { - console.log( - `${chalk.yellow('moving')} ${unlockedPackage.name}@${ - unlockedPackage.range - } ~> ${chalk.yellow(`${name}@${range}`)}`, - ); - lockfile.remove(unlockedPackage.name, unlockedPackage.range); - } else { - console.log( - `${chalk.magenta('unlocking')} ${name}@${chalk.yellow( - range, - )} ~> ${chalk.yellow(target)}`, - ); - lockfile.remove(name, range); - } + console.log( + `${chalk.magenta('unlocking')} ${name}@${chalk.yellow( + range, + )} ~> ${chalk.yellow(target)}`, + ); + lockfile.remove(name, range); } } - - for (const outsideRangePackage of outsideRange) { - const movedPackage = moved.get(outsideRangePackage.name); - if (movedPackage) { - const { name, range } = movedPackage; - const key = JSON.stringify({ name, range }); - if (!removed.has(key)) { - removed.add(key); - // TODO: Does this need special instructions as there might be custom bumping needed? - console.log( - `${chalk.yellow('moving')} ${movedPackage.name}@${ - movedPackage.range - } ~> ${chalk.yellow(`${name}@${range}`)}`, - ); - lockfile.remove(movedPackage.name, movedPackage.range); - } - } - } - await lockfile.save(lockfilePath); } @@ -280,29 +213,14 @@ export default async (opts: OptionValues) => { const pkgJson = await fs.readJson(pkgPath); for (const dep of deps) { - const movedDep = moved.get(dep.name); - if (movedDep) { - console.log( - `${chalk.yellow('bumping')} ${dep.name} in ${chalk.cyan( - name, - )} to ${chalk.yellow(`${movedDep.name}@${movedDep.range}`)}`, - ); - } else { - console.log( - `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( - name, - )} to ${chalk.yellow(dep.range)}`, - ); - } + console.log( + `${chalk.cyan('bumping')} ${dep.name} in ${chalk.cyan( + name, + )} to ${chalk.yellow(dep.range)}`, + ); for (const depType of DEP_TYPES) { if (depType in pkgJson && dep.name in pkgJson[depType]) { - if (movedDep) { - delete pkgJson[depType][dep.name]; - pkgJson[depType][movedDep.name] = movedDep.range; - continue; - } - const oldRange = pkgJson[depType][dep.name]; pkgJson[depType][dep.name] = dep.range; From 7fe125abd651c21724c08df14093223d3fc9e844 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 15 Apr 2024 18:44:10 +0200 Subject: [PATCH 09/14] chore: moving out the migrate to a seperate command Signed-off-by: blam --- .../cli/src/commands/versions/bump.test.ts | 161 +----------------- packages/cli/src/commands/versions/bump.ts | 2 +- .../cli/src/commands/versions/migrate.test.ts | 156 +++++++++++++++++ packages/cli/src/commands/versions/migrate.ts | 23 +++ 4 files changed, 187 insertions(+), 155 deletions(-) create mode 100644 packages/cli/src/commands/versions/migrate.test.ts create mode 100644 packages/cli/src/commands/versions/migrate.ts diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index f3eafb760a..d00b4ce8da 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -92,8 +92,6 @@ const REGISTRY_VERSIONS: { [name: string]: string } = { '@backstage-extra/custom': '1.1.0', '@backstage-extra/custom-two': '2.0.0', '@backstage/create-app': '1.0.0', - '@backstage-community/custom': '1.1.0', - '@backstage-community/custom-two': '2.0.0', }; const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. @@ -206,7 +204,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', @@ -263,151 +261,6 @@ describe('bump', () => { }); }); - it('should bump backstage-community dependencies', async () => { - const customLockfileMock = `${lockfileMock} -"@backstage-community/custom@^1.1.0": - version "1.1.0" - -"@backstage-community/custom@^1.0.1": - version "1.0.1" - -"@backstage-community/custom-two@^1.0.0": - version "1.0.0" -`; - const customLockfileMockResult = `${HEADER} -"@backstage-community/custom-two@^1.0.0": - version "1.0.0" - -"@backstage-community/custom@^1.1.0": - version "1.1.0" - -"@backstage/core@^1.0.5": - version "1.0.6" - dependencies: - "@backstage/core-api" "^1.0.6" - -"@backstage/theme@^1.0.0": - version "1.0.0" -`; - mockDir.setContent({ - 'yarn.lock': customLockfileMock, - 'package.json': JSON.stringify({ - workspaces: { - packages: ['packages/*'], - }, - }), - packages: { - a: { - 'package.json': JSON.stringify({ - name: 'a', - dependencies: { - '@backstage/core': '^1.0.5', - '@backstage-community/custom': '^1.0.1', - '@backstage-community/custom-two': '^1.0.0', - }, - }), - }, - b: { - 'package.json': JSON.stringify({ - name: 'b', - dependencies: { - '@backstage/core': '^1.0.3', - '@backstage/theme': '^1.0.0', - '@backstage-community/custom': '^1.1.0', - '@backstage-community/custom-two': '^1.0.0', - }, - }), - }, - }, - }); - - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); - worker.use( - rest.get( - 'https://versions.backstage.io/v1/tags/main/manifest.json', - (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ - packages: [], - }), - ), - ), - ); - const { log: logs } = await withLogCollector(['log'], async () => { - await bump({ - release: 'main', - } as any); - }); - expectLogsToMatch(logs, [ - 'Checking for updates of @backstage/core', - 'Checking for updates of @backstage-community/custom', - 'Checking for updates of @backstage-community/custom-two', - 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', - 'Some packages are outdated, updating', - 'Using default pattern glob @backstage?(-community)/*', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage-community/custom@^1.0.1 ~> 1.1.0', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', - 'bumping @backstage/core in a to ^1.0.6', - 'bumping @backstage-community/custom in a to ^1.1.0', - 'bumping @backstage-community/custom-two in a to ^2.0.0', - 'bumping @backstage/core in b to ^1.0.6', - 'bumping @backstage-community/custom in b to ^1.1.0', - 'bumping @backstage-community/custom-two in b to ^2.0.0', - 'bumping @backstage/theme in b to ^2.0.0', - 'Running yarn install to install new versions', - '⚠️ The following packages may have breaking changes:', - ' @backstage-community/custom-two : 1.0.0 ~> 2.0.0', - ' @backstage/theme : 1.0.0 ~> 2.0.0', - ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', - 'Version bump complete!', - ]); - - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); - - expect(runObj.run).toHaveBeenCalledTimes(1); - expect(runObj.run).toHaveBeenCalledWith( - 'yarn', - ['install'], - expect.any(Object), - ); - - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toEqual(customLockfileMockResult); - - const packageA = await fs.readJson( - mockDir.resolve('packages/a/package.json'), - ); - expect(packageA).toEqual({ - name: 'a', - dependencies: { - '@backstage-community/custom': '^1.1.0', - '@backstage-community/custom-two': '^2.0.0', - '@backstage/core': '^1.0.6', - }, - }); - const packageB = await fs.readJson( - mockDir.resolve('packages/b/package.json'), - ); - expect(packageB).toEqual({ - name: 'b', - dependencies: { - '@backstage-community/custom': '^1.1.0', - '@backstage-community/custom-two': '^2.0.0', - '@backstage/core': '^1.0.6', - '@backstage/theme': '^2.0.0', - }, - }); - }); - it('should bump backstage dependencies but not install them', async () => { mockDir.setContent({ 'yarn.lock': lockfileMock, @@ -458,7 +311,7 @@ describe('bump', () => { } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', @@ -570,7 +423,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/theme', @@ -670,7 +523,7 @@ describe('bump', () => { ).rejects.toThrow('No release found for 999.0.1 version'); }); expect(logs.filter(Boolean)).toEqual([ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', ]); expect(runObj.run).toHaveBeenCalledTimes(0); @@ -773,7 +626,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'next' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/theme', @@ -988,7 +841,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Package info not found, ignoring package @backstage/core', @@ -1103,7 +956,7 @@ describe('bump', () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage?(-community)/*', + 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core-api', diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index c6a84208ab..d64d5a9756 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -48,7 +48,7 @@ const DEP_TYPES = [ 'optionalDependencies', ]; -const DEFAULT_PATTERN_GLOB = '@backstage?(-community)/*'; +const DEFAULT_PATTERN_GLOB = '@backstage/*'; type PkgVersionInfo = { range: string; diff --git a/packages/cli/src/commands/versions/migrate.test.ts b/packages/cli/src/commands/versions/migrate.test.ts new file mode 100644 index 0000000000..1aef6ff307 --- /dev/null +++ b/packages/cli/src/commands/versions/migrate.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + MockDirectory, + createMockDirectory, +} from '@backstage/backend-test-utils'; +import * as run from '../../lib/run'; +import migrate from './migrate'; +import { withLogCollector } from '@backstage/test-utils'; +import fs from 'fs-extra'; + +let mockDir: MockDirectory; +jest.mock('../../lib/paths', () => ({ + paths: { + resolveTargetRoot(filename: string) { + return mockDir.resolve(filename); + }, + get targetDir() { + return mockDir.path; + }, + }, +})); + +jest.mock('../../lib/run', () => { + return { + run: jest.fn(), + }; +}); + +const mockFetchPackageInfo = jest.fn(); +jest.mock('../../lib/versioning/packages', () => { + const actual = jest.requireActual('../../lib/versioning/packages'); + return { + ...actual, + fetchPackageInfo: (name: string) => mockFetchPackageInfo(name), + }; +}); + +describe('versions:migrate', () => { + mockDir = createMockDirectory(); + + beforeEach(() => {}); + + it('should bump to the moved version when the package is moved', async () => { + mockDir.setContent({ + 'package.json': JSON.stringify({ + workspaces: { + packages: ['packages/*'], + }, + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + dependencies: { + '@backstage/core': '^1.0.5', + '@backstage/custom': '^1.0.1', + '@backstage/custom-two': '^1.0.0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + dependencies: { + '@backstage/core': '^1.0.3', + '@backstage/theme': '^1.0.0', + '@backstage/custom': '^1.1.0', + '@backstage/custom-two': '^1.0.0', + }, + }), + }, + }, + }); + + jest.spyOn(run, 'run').mockResolvedValue(undefined); + + const { log: logs } = await withLogCollector(['log'], async () => { + await migrate({}); + }); + // expectLogsToMatch(logs, [ + // 'Checking for updates of @backstage/core', + // 'Checking for updates of @backstage/custom', + // 'Checking for updates of @backstage/custom-two', + // 'Checking for updates of @backstage/theme', + // 'Checking for updates of @backstage/core-api', + // 'Some packages are outdated, updating', + // 'Using default pattern glob @backstage?(-community)/*', + // 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', + // 'unlocking @backstage-community/custom@^1.0.1 ~> 1.1.0', + // 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', + // 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', + // 'bumping @backstage/core in a to ^1.0.6', + // 'bumping @backstage-community/custom in a to ^1.1.0', + // 'bumping @backstage-community/custom-two in a to ^2.0.0', + // 'bumping @backstage/core in b to ^1.0.6', + // 'bumping @backstage-community/custom in b to ^1.1.0', + // 'bumping @backstage-community/custom-two in b to ^2.0.0', + // 'bumping @backstage/theme in b to ^2.0.0', + // 'Running yarn install to install new versions', + // '⚠️ The following packages may have breaking changes:', + // ' @backstage-community/custom-two : 1.0.0 ~> 2.0.0', + // ' @backstage/theme : 1.0.0 ~> 2.0.0', + // ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', + // 'Version bump complete!', + // ]); + + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5); + expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); + expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); + + expect(run.run).toHaveBeenCalledTimes(1); + expect(run.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); + + const packageA = await fs.readJson( + mockDir.resolve('packages/a/package.json'), + ); + expect(packageA).toEqual({ + name: 'a', + dependencies: { + '@backstage-community/custom': '^1.1.0', + '@backstage-community/custom-two': '^2.0.0', + '@backstage/core': '^1.0.6', + }, + }); + const packageB = await fs.readJson( + mockDir.resolve('packages/b/package.json'), + ); + expect(packageB).toEqual({ + name: 'b', + dependencies: { + '@backstage-community/custom': '^1.1.0', + '@backstage-community/custom-two': '^2.0.0', + '@backstage/core': '^1.0.6', + '@backstage/theme': '^2.0.0', + }, + }); + }); +}); diff --git a/packages/cli/src/commands/versions/migrate.ts b/packages/cli/src/commands/versions/migrate.ts new file mode 100644 index 0000000000..2e18db456c --- /dev/null +++ b/packages/cli/src/commands/versions/migrate.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PackageGraph } from '@backstage/cli-node'; +import { OptionValues } from 'commander'; + +export default async (_: OptionValues) => { + console.log( + PackageGraph.fromPackages(await PackageGraph.listTargetPackages()), + ); +}; From 2ccd38916636664bba85bb0781f0001a258b4443 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Apr 2024 09:16:03 +0200 Subject: [PATCH 10/14] chore: starting some more on the script Signed-off-by: blam --- .../cli-node/src/monorepo/PackageGraph.ts | 1 + packages/cli/src/commands/index.ts | 7 ++++ .../cli/src/commands/versions/migrate.test.ts | 37 +++++++++++++++---- packages/cli/src/commands/versions/migrate.ts | 10 ++++- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index cd01ec833f..86d54bf328 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -136,6 +136,7 @@ export class PackageGraph extends Map { * Lists all local packages in a monorepo. */ static async listTargetPackages(): Promise { + console.log(paths.targetDir); const { packages } = await getPackages(paths.targetDir); return packages as BackstagePackage[]; } diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 431bd6cc68..a33394b414 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -395,6 +395,13 @@ export function registerCommands(program: Command) { .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); + program + .command('versions:migrate') + .description( + 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', + ) + .action(lazy(() => import('./versions/migrate').then(m => m.default))); + // TODO(Rugvip): Deprecate in favor of package variant program .command('clean') diff --git a/packages/cli/src/commands/versions/migrate.test.ts b/packages/cli/src/commands/versions/migrate.test.ts index 1aef6ff307..c822b98e9c 100644 --- a/packages/cli/src/commands/versions/migrate.test.ts +++ b/packages/cli/src/commands/versions/migrate.test.ts @@ -20,18 +20,21 @@ import { import * as run from '../../lib/run'; import migrate from './migrate'; import { withLogCollector } from '@backstage/test-utils'; +import { paths } from '../../lib/paths'; import fs from 'fs-extra'; let mockDir: MockDirectory; -jest.mock('../../lib/paths', () => ({ - paths: { +jest.mock('@backstage/cli-common', () => ({ + ...jest.requireActual('@backstage/cli-common'), + findPaths: () => ({ resolveTargetRoot(filename: string) { return mockDir.resolve(filename); }, get targetDir() { + console.log('calling!'); return mockDir.path; }, - }, + }), })); jest.mock('../../lib/run', () => { @@ -49,10 +52,30 @@ jest.mock('../../lib/versioning/packages', () => { }; }); +const REGISTRY_VERSIONS: { [name: string]: string } = { + '@backstage/core': '1.0.6', + '@backstage/core-api': '1.0.7', + '@backstage/theme': '2.0.0', + '@backstage-extra/custom': '1.1.0', + '@backstage-extra/custom-two': '2.0.0', + '@backstage/create-app': '1.0.0', +}; + describe('versions:migrate', () => { mockDir = createMockDirectory(); - beforeEach(() => {}); + beforeEach(() => { + mockFetchPackageInfo.mockImplementation(async name => ({ + name: name, + 'dist-tags': { + latest: REGISTRY_VERSIONS[name], + }, + })); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); it('should bump to the moved version when the package is moved', async () => { mockDir.setContent({ @@ -87,10 +110,10 @@ describe('versions:migrate', () => { }); jest.spyOn(run, 'run').mockResolvedValue(undefined); + console.log(paths); + await migrate({}); - const { log: logs } = await withLogCollector(['log'], async () => { - await migrate({}); - }); + const { log: logs } = await withLogCollector(['log'], async () => {}); // expectLogsToMatch(logs, [ // 'Checking for updates of @backstage/core', // 'Checking for updates of @backstage/custom', diff --git a/packages/cli/src/commands/versions/migrate.ts b/packages/cli/src/commands/versions/migrate.ts index 2e18db456c..557680d5e3 100644 --- a/packages/cli/src/commands/versions/migrate.ts +++ b/packages/cli/src/commands/versions/migrate.ts @@ -17,7 +17,13 @@ import { PackageGraph } from '@backstage/cli-node'; import { OptionValues } from 'commander'; export default async (_: OptionValues) => { - console.log( - PackageGraph.fromPackages(await PackageGraph.listTargetPackages()), + const packageMap = PackageGraph.fromPackages( + await PackageGraph.listTargetPackages(), ); + + const packagesThatHaveMoved = new Map(); + + for (const [name, pkg] of packageMap.entries()) { + console.log(name, pkg); + } }; From 03f3bb580879d1b840689e00810a935cca610484 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Apr 2024 11:01:28 +0200 Subject: [PATCH 11/14] chore: rework how we do the bumping as a seperate command Signed-off-by: blam --- .../cli-node/src/monorepo/PackageGraph.ts | 3 +- packages/cli/src/commands/index.ts | 4 + packages/cli/src/commands/versions/bump.ts | 2 +- .../cli/src/commands/versions/migrate.test.ts | 67 ++++++------- packages/cli/src/commands/versions/migrate.ts | 98 +++++++++++++++++-- 5 files changed, 125 insertions(+), 49 deletions(-) diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 86d54bf328..68ba614c94 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -45,6 +45,7 @@ export interface BackstagePackageJson { backstage?: { role?: PackageRole; + moved?: string; }; exports?: JsonValue; @@ -136,8 +137,8 @@ export class PackageGraph extends Map { * Lists all local packages in a monorepo. */ static async listTargetPackages(): Promise { - console.log(paths.targetDir); const { packages } = await getPackages(paths.targetDir); + return packages as BackstagePackage[]; } diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index a33394b414..98ade80472 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -397,6 +397,10 @@ export function registerCommands(program: Command) { program .command('versions:migrate') + .option( + '--pattern ', + 'Override glob for matching packages to upgrade', + ) .description( 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', ) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index d64d5a9756..3d3ab16427 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -470,7 +470,7 @@ export async function bumpBackstageJsonVersion(version: string) { ); } -async function runYarnInstall() { +export async function runYarnInstall() { const spinner = ora({ prefixText: `Running ${chalk.blue('yarn install')} to install new versions`, spinner: 'arc', diff --git a/packages/cli/src/commands/versions/migrate.test.ts b/packages/cli/src/commands/versions/migrate.test.ts index c822b98e9c..878906a1f0 100644 --- a/packages/cli/src/commands/versions/migrate.test.ts +++ b/packages/cli/src/commands/versions/migrate.test.ts @@ -43,36 +43,9 @@ jest.mock('../../lib/run', () => { }; }); -const mockFetchPackageInfo = jest.fn(); -jest.mock('../../lib/versioning/packages', () => { - const actual = jest.requireActual('../../lib/versioning/packages'); - return { - ...actual, - fetchPackageInfo: (name: string) => mockFetchPackageInfo(name), - }; -}); - -const REGISTRY_VERSIONS: { [name: string]: string } = { - '@backstage/core': '1.0.6', - '@backstage/core-api': '1.0.7', - '@backstage/theme': '2.0.0', - '@backstage-extra/custom': '1.1.0', - '@backstage-extra/custom-two': '2.0.0', - '@backstage/create-app': '1.0.0', -}; - describe('versions:migrate', () => { mockDir = createMockDirectory(); - beforeEach(() => { - mockFetchPackageInfo.mockImplementation(async name => ({ - name: name, - 'dist-tags': { - latest: REGISTRY_VERSIONS[name], - }, - })); - }); - afterEach(() => { jest.resetAllMocks(); }); @@ -84,6 +57,28 @@ describe('versions:migrate', () => { packages: ['packages/*'], }, }), + node_modules: { + '@backstage': { + custom: { + 'package.json': JSON.stringify({ + name: '@backstage-extra/custom', + version: '1.0.1', + backstage: { + moved: '@backstage-community/custom', + }, + }), + }, + 'custom-two': { + 'package.json': JSON.stringify({ + name: '@backstage-extra/custom-two', + version: '1.0.0', + backstage: { + moved: '@backstage-community/custom-two', + }, + }), + }, + }, + }, packages: { a: { 'package.json': JSON.stringify({ @@ -110,7 +105,6 @@ describe('versions:migrate', () => { }); jest.spyOn(run, 'run').mockResolvedValue(undefined); - console.log(paths); await migrate({}); const { log: logs } = await withLogCollector(['log'], async () => {}); @@ -141,10 +135,6 @@ describe('versions:migrate', () => { // 'Version bump complete!', // ]); - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); - expect(run.run).toHaveBeenCalledTimes(1); expect(run.run).toHaveBeenCalledWith( 'yarn', @@ -155,12 +145,13 @@ describe('versions:migrate', () => { const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); + expect(packageA).toEqual({ name: 'a', dependencies: { - '@backstage-community/custom': '^1.1.0', - '@backstage-community/custom-two': '^2.0.0', - '@backstage/core': '^1.0.6', + '@backstage-community/custom': '^1.0.1', + '@backstage-community/custom-two': '^1.0.0', + '@backstage/core': '^1.0.5', }, }); const packageB = await fs.readJson( @@ -170,9 +161,9 @@ describe('versions:migrate', () => { name: 'b', dependencies: { '@backstage-community/custom': '^1.1.0', - '@backstage-community/custom-two': '^2.0.0', - '@backstage/core': '^1.0.6', - '@backstage/theme': '^2.0.0', + '@backstage-community/custom-two': '^1.0.0', + '@backstage/core': '^1.0.3', + '@backstage/theme': '^1.0.0', }, }); }); diff --git a/packages/cli/src/commands/versions/migrate.ts b/packages/cli/src/commands/versions/migrate.ts index 557680d5e3..fa3492ca7c 100644 --- a/packages/cli/src/commands/versions/migrate.ts +++ b/packages/cli/src/commands/versions/migrate.ts @@ -13,17 +13,97 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PackageGraph } from '@backstage/cli-node'; +import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; +import chalk from 'chalk'; +import { resolve as resolvePath } from 'path'; import { OptionValues } from 'commander'; +import { readJson, writeJson } from 'fs-extra'; +import { minimatch } from 'minimatch'; +import { runYarnInstall } from './bump'; -export default async (_: OptionValues) => { - const packageMap = PackageGraph.fromPackages( - await PackageGraph.listTargetPackages(), - ); +export default async (options: OptionValues) => { + const changed = await migrateMovedPackages({ + pattern: options.pattern, + }); - const packagesThatHaveMoved = new Map(); - - for (const [name, pkg] of packageMap.entries()) { - console.log(name, pkg); + if (changed) { + await runYarnInstall(); } }; + +export async function migrateMovedPackages(options?: { pattern?: string }) { + const packages = await PackageGraph.listTargetPackages(); + + const thingsThatHaveMoved = new Map< + string, + { + dependencies: { [k: string]: string }; + devDependencies: { [k: string]: string }; + peerDependencies: { [k: string]: string }; + } + >(); + + let didAnythingChange = false; + + for (const pkg of packages) { + const pkgName = pkg.packageJson.name; + thingsThatHaveMoved.set(pkgName, { + dependencies: {}, + devDependencies: {}, + peerDependencies: {}, + }); + + let didPackageChange = false; + + for (const depType of [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + ] as const) { + const depsObj = pkg.packageJson[depType]; + if (!depsObj) { + continue; + } + for (const [depName, depVersion] of Object.entries(depsObj)) { + if (options?.pattern && !minimatch(depName, options.pattern)) { + continue; + } + let packageInfo: BackstagePackageJson; + try { + packageInfo = await readJson( + require.resolve(`${depName}/package.json`, { + paths: [pkg.dir], + }), + ); + } catch (ex) { + console.warn( + chalk.yellow`Could not find package.json for ${depName}@${depVersion} in ${pkgName} (${depType})`, + ); + continue; + } + + const movedPackageName = packageInfo.backstage?.moved; + + if (movedPackageName) { + console.log( + chalk.yellow`Found a moved package ${depName}@${depVersion} -> ${movedPackageName} in ${pkgName} (${depType})`, + ); + + didPackageChange = true; + didAnythingChange = true; + + depsObj[movedPackageName] = depsObj[depName]; + delete depsObj[depName]; + } + } + } + + if (didPackageChange) { + await writeJson(resolvePath(pkg.dir, 'package.json'), pkg.packageJson, { + spaces: 2, + }); + } + } + + return didAnythingChange; +} From ed9260f6505d6d88a8212177b2f6f6398509159d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Apr 2024 11:02:16 +0200 Subject: [PATCH 12/14] chore: add changeset Signed-off-by: blam --- .changeset/tender-zoos-rush.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tender-zoos-rush.md diff --git a/.changeset/tender-zoos-rush.md b/.changeset/tender-zoos-rush.md new file mode 100644 index 0000000000..0e09a75e44 --- /dev/null +++ b/.changeset/tender-zoos-rush.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli-node': patch +'@backstage/cli': patch +--- + +Added `versions:migrate` command to help move packages to the new `@backstage-community` namespace From a544108f93a6a4c17212c0192b2faa18793a99b4 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Apr 2024 11:12:37 +0200 Subject: [PATCH 13/14] chore: fix tests Signed-off-by: blam --- packages/cli/src/commands/index.ts | 1 + .../cli/src/commands/versions/bump.test.ts | 24 ++++---- packages/cli/src/commands/versions/bump.ts | 10 ++++ .../cli/src/commands/versions/migrate.test.ts | 57 +++++++++---------- packages/cli/src/commands/versions/migrate.ts | 8 ++- 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 98ade80472..609c7b5946 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -386,6 +386,7 @@ export function registerCommands(program: Command) { 'main', ) .option('--skip-install', 'Skips yarn install step') + .option('--skip-migrate', 'Skips migration of any moved packages') .description('Bump Backstage packages to the latest versions') .action(lazy(() => import('./versions/bump').then(m => m.default))); diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index d00b4ce8da..a6537423c8 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -58,16 +58,16 @@ jest.mock('ora', () => ({ })); let mockDir: MockDirectory; - -jest.mock('../../lib/paths', () => ({ - paths: { +jest.mock('@backstage/cli-common', () => ({ + ...jest.requireActual('@backstage/cli-common'), + findPaths: () => ({ resolveTargetRoot(filename: string) { return mockDir.resolve(filename); }, get targetDir() { return mockDir.path; }, - }, + }), })); jest.mock('../../lib/run', () => { @@ -200,7 +200,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ @@ -303,7 +303,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main', @@ -419,7 +419,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ @@ -517,7 +517,7 @@ describe('bump', () => { (_, res, ctx) => res(ctx.status(404), ctx.json({})), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await expect( bump({ pattern: null, release: '999.0.1' } as unknown as Command), ).rejects.toThrow('No release found for 999.0.1 version'); @@ -622,7 +622,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'next' } as unknown as Command); }); expectLogsToMatch(logs, [ @@ -718,7 +718,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: '@{backstage,backstage-extra}/*', release: 'main', @@ -837,7 +837,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ @@ -952,7 +952,7 @@ describe('bump', () => { ), ), ); - const { log: logs } = await withLogCollector(['log'], async () => { + const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ pattern: null, release: 'main' } as unknown as Command); }); expectLogsToMatch(logs, [ diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 3d3ab16427..dc0aa27530 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -40,6 +40,7 @@ import { } from '@backstage/release-manifests'; import 'global-agent/bootstrap'; import { PackageGraph } from '@backstage/cli-node'; +import { migrateMovedPackages } from './migrate'; const DEP_TYPES = [ 'dependencies', @@ -264,6 +265,15 @@ export default async (opts: OptionValues) => { console.log(chalk.yellow(`Skipping yarn install`)); } + if (!opts.skipMigrate) { + const changed = await migrateMovedPackages({ + pattern: opts.pattern, + }); + if (changed && !opts.skipInstall) { + await runYarnInstall(); + } + } + if (breakingUpdates.size > 0) { console.log(); console.log( diff --git a/packages/cli/src/commands/versions/migrate.test.ts b/packages/cli/src/commands/versions/migrate.test.ts index 878906a1f0..46c7f9bfb8 100644 --- a/packages/cli/src/commands/versions/migrate.test.ts +++ b/packages/cli/src/commands/versions/migrate.test.ts @@ -20,8 +20,18 @@ import { import * as run from '../../lib/run'; import migrate from './migrate'; import { withLogCollector } from '@backstage/test-utils'; -import { paths } from '../../lib/paths'; import fs from 'fs-extra'; +import { expectLogsToMatch } from '../../lib/new/factories/common/testUtils'; + +// Remove log coloring to simplify log matching +jest.mock('chalk', () => ({ + red: (str: string) => str, + blue: (str: string) => str, + cyan: (str: string) => str, + green: (str: string) => str, + magenta: (str: string) => str, + yellow: (str: string) => str, +})); let mockDir: MockDirectory; jest.mock('@backstage/cli-common', () => ({ @@ -31,7 +41,6 @@ jest.mock('@backstage/cli-common', () => ({ return mockDir.resolve(filename); }, get targetDir() { - console.log('calling!'); return mockDir.path; }, }), @@ -105,35 +114,23 @@ describe('versions:migrate', () => { }); jest.spyOn(run, 'run').mockResolvedValue(undefined); - await migrate({}); - const { log: logs } = await withLogCollector(['log'], async () => {}); - // expectLogsToMatch(logs, [ - // 'Checking for updates of @backstage/core', - // 'Checking for updates of @backstage/custom', - // 'Checking for updates of @backstage/custom-two', - // 'Checking for updates of @backstage/theme', - // 'Checking for updates of @backstage/core-api', - // 'Some packages are outdated, updating', - // 'Using default pattern glob @backstage?(-community)/*', - // 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - // 'unlocking @backstage-community/custom@^1.0.1 ~> 1.1.0', - // 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - // 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', - // 'bumping @backstage/core in a to ^1.0.6', - // 'bumping @backstage-community/custom in a to ^1.1.0', - // 'bumping @backstage-community/custom-two in a to ^2.0.0', - // 'bumping @backstage/core in b to ^1.0.6', - // 'bumping @backstage-community/custom in b to ^1.1.0', - // 'bumping @backstage-community/custom-two in b to ^2.0.0', - // 'bumping @backstage/theme in b to ^2.0.0', - // 'Running yarn install to install new versions', - // '⚠️ The following packages may have breaking changes:', - // ' @backstage-community/custom-two : 1.0.0 ~> 2.0.0', - // ' @backstage/theme : 1.0.0 ~> 2.0.0', - // ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', - // 'Version bump complete!', - // ]); + const { warn, log: logs } = await withLogCollector(async () => { + await migrate({}); + }); + + expectLogsToMatch(logs, [ + 'Found a moved package @backstage/custom@^1.0.1 -> @backstage-community/custom in a (dependencies)', + 'Found a moved package @backstage/custom-two@^1.0.0 -> @backstage-community/custom-two in a (dependencies)', + 'Found a moved package @backstage/custom@^1.1.0 -> @backstage-community/custom in b (dependencies)', + 'Found a moved package @backstage/custom-two@^1.0.0 -> @backstage-community/custom-two in b (dependencies)', + ]); + + expectLogsToMatch(warn, [ + 'Could not find package.json for @backstage/core@^1.0.5 in a (dependencies)', + 'Could not find package.json for @backstage/core@^1.0.3 in b (dependencies)', + 'Could not find package.json for @backstage/theme@^1.0.0 in b (dependencies)', + ]); expect(run.run).toHaveBeenCalledTimes(1); expect(run.run).toHaveBeenCalledWith( diff --git a/packages/cli/src/commands/versions/migrate.ts b/packages/cli/src/commands/versions/migrate.ts index fa3492ca7c..f824fa2b96 100644 --- a/packages/cli/src/commands/versions/migrate.ts +++ b/packages/cli/src/commands/versions/migrate.ts @@ -77,7 +77,9 @@ export async function migrateMovedPackages(options?: { pattern?: string }) { ); } catch (ex) { console.warn( - chalk.yellow`Could not find package.json for ${depName}@${depVersion} in ${pkgName} (${depType})`, + chalk.yellow( + `Could not find package.json for ${depName}@${depVersion} in ${pkgName} (${depType})`, + ), ); continue; } @@ -86,7 +88,9 @@ export async function migrateMovedPackages(options?: { pattern?: string }) { if (movedPackageName) { console.log( - chalk.yellow`Found a moved package ${depName}@${depVersion} -> ${movedPackageName} in ${pkgName} (${depType})`, + chalk.yellow( + `Found a moved package ${depName}@${depVersion} -> ${movedPackageName} in ${pkgName} (${depType})`, + ), ); didPackageChange = true; From 82ff03e16f0592a93d3725f5c8b1218eab176c77 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Apr 2024 11:41:34 +0200 Subject: [PATCH 14/14] chore: update types Signed-off-by: blam --- .changeset/nervous-pans-push.md | 5 ----- .changeset/rotten-queens-grow.md | 5 +++++ .../backend-dynamic-feature-service/api-report.md | 4 +++- .../src/scanner/types.ts | 6 ++++-- packages/cli-node/api-report.md | 1 + packages/cli/cli-report.md | 12 ++++++++++++ packages/cli/src/lib/versioning/packages.ts | 6 +----- 7 files changed, 26 insertions(+), 13 deletions(-) delete mode 100644 .changeset/nervous-pans-push.md create mode 100644 .changeset/rotten-queens-grow.md diff --git a/.changeset/nervous-pans-push.md b/.changeset/nervous-pans-push.md deleted file mode 100644 index 594dc26785..0000000000 --- a/.changeset/nervous-pans-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Update reference if a package is moved to `@backstage-community` scope diff --git a/.changeset/rotten-queens-grow.md b/.changeset/rotten-queens-grow.md new file mode 100644 index 0000000000..b636414036 --- /dev/null +++ b/.changeset/rotten-queens-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Use `PackageRole` type explicitly diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 8075acabfb..57b2228975 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -240,7 +240,9 @@ export interface NewBackendPluginInstaller { export type ScannedPluginManifest = BackstagePackageJson & Required> & Required> & { - backstage: Required; + backstage: { + role: PackageRole; + }; }; // @public (undocumented) diff --git a/packages/backend-dynamic-feature-service/src/scanner/types.ts b/packages/backend-dynamic-feature-service/src/scanner/types.ts index b456ffeea1..5bac92f064 100644 --- a/packages/backend-dynamic-feature-service/src/scanner/types.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BackstagePackageJson } from '@backstage/cli-node'; +import { BackstagePackageJson, PackageRole } from '@backstage/cli-node'; /** * @public @@ -30,5 +30,7 @@ export interface ScannedPluginPackage { export type ScannedPluginManifest = BackstagePackageJson & Required> & Required> & { - backstage: Required; + backstage: { + role: PackageRole; + }; }; diff --git a/packages/cli-node/api-report.md b/packages/cli-node/api-report.md index 4bd152dca8..5b9b8485b8 100644 --- a/packages/cli-node/api-report.md +++ b/packages/cli-node/api-report.md @@ -17,6 +17,7 @@ export interface BackstagePackageJson { // (undocumented) backstage?: { role?: PackageRole; + moved?: string; }; // (undocumented) bundled?: boolean; diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 9a471e2ca6..a913b63f60 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -23,6 +23,7 @@ Commands: migrate [command] versions:bump [options] versions:check [options] + versions:migrate [options] clean build-workspace [options] [packages...] create-github-app @@ -600,6 +601,7 @@ Options: --pattern --release --skip-install + --skip-migrate -h, --help ``` @@ -612,3 +614,13 @@ Options: --fix -h, --help ``` + +### `backstage-cli versions:migrate` + +``` +Usage: backstage-cli versions:migrate [options] + +Options: + --pattern + -h, --help +``` diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index bad9dc98fa..0499775bf2 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -27,16 +27,12 @@ const DEP_TYPES = [ 'optionalDependencies', ] as const; -// Package data as returned by `yarn info`. This is an uncomplete type fullfilling the required needs in the CLI +// Package data as returned by `yarn info` export type YarnInfoInspectData = { name: string; 'dist-tags': Record; versions: string[]; time: { [version: string]: string }; - backstage?: { - role?: string; - moved?: string; - }; }; // Possible `yarn info` output