Merge pull request #24459 from backstage/backstage-cli-handle-yarn-plugin

versions:bump: handle `backstage:^` versions
This commit is contained in:
Patrik Oldsberg
2024-09-10 16:12:56 +03:00
committed by GitHub
2 changed files with 106 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Add support for `backstage:^` version ranges to versions:bump when using the experimental yarn plugin
+101 -6
View File
@@ -25,6 +25,8 @@ import chalk from 'chalk';
import ora from 'ora';
import semver from 'semver';
import { OptionValues } from 'commander';
import yaml from 'yaml';
import z from 'zod';
import { isError, NotFoundError } from '@backstage/errors';
import { resolve as resolvePath } from 'path';
import { run } from '../../lib/run';
@@ -76,6 +78,8 @@ type PkgVersionInfo = {
export default async (opts: OptionValues) => {
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
const lockfile = await Lockfile.load(lockfilePath);
const hasYarnPlugin = await getHasYarnPlugin();
let pattern = opts.pattern;
if (!pattern) {
@@ -177,12 +181,34 @@ export default async (opts: OptionValues) => {
for (const depType of DEP_TYPES) {
if (depType in pkgJson && dep.name in pkgJson[depType]) {
const oldRange = pkgJson[depType][dep.name];
pkgJson[depType][dep.name] = dep.range;
// backstage:^ are written to the lockfile as
// backstage:<backstage-version>, so that updates to
// backstage.json can be detected during yarn install. In order to
// locate the corresponding lockfile entry for "backstage:^"
// versions, we need to perform the same transformation.
const oldLockfileRange = await asLockfileVersion(oldRange);
const useBackstageRange =
hasYarnPlugin &&
// Only use backstage:^ versions if the package is present in
// the manifest for the release we're bumping to.
releaseManifest.packages.find(
({ name: manifestPackageName }) =>
dep.name === manifestPackageName,
) &&
// Don't use backstage:^ versions for peerDependencies; they only
// support npm and workspace: versions.
depType !== 'peerDependencies';
const newRange = useBackstageRange ? 'backstage:^' : dep.range;
pkgJson[depType][dep.name] = newRange;
// Check if the update was at least a pre-v1 minor or post-v1 major release
const lockfileEntry = lockfile
.get(dep.name)
?.find(entry => entry.range === oldRange);
?.find(entry => entry.range === oldLockfileRange);
if (lockfileEntry) {
const from = lockfileEntry.version;
const to = dep.target;
@@ -266,6 +292,21 @@ export default async (opts: OptionValues) => {
console.log();
}
if (hasYarnPlugin) {
console.log();
console.log(
chalk.blue(
`${chalk.bold(
'NOTE',
)}: this bump used backstage:^ versions in package.json files, since the Backstage ` +
`yarn plugin was detected in the repository. To migrate back to explicit npm versions, ` +
`remove the plugin by running "yarn plugin remove @yarnpkg/plugin-backstage", then ` +
`repeat this command.`,
),
);
console.log();
}
console.log(chalk.green('Version bump complete!'));
}
@@ -354,16 +395,23 @@ export function createVersionFinder(options: {
};
}
export async function bumpBackstageJsonVersion(version: string) {
const backstageJsonPath = paths.resolveTargetRoot(BACKSTAGE_JSON);
const backstageJson = await fs.readJSON(backstageJsonPath).catch(e => {
function getBackstageJsonPath() {
return paths.resolveTargetRoot(BACKSTAGE_JSON);
}
async function getBackstageJson() {
const backstageJsonPath = getBackstageJsonPath();
return fs.readJSON(backstageJsonPath).catch(e => {
if (e.code === 'ENOENT') {
// gracefully continue in case the file doesn't exist
return;
}
throw e;
});
}
export async function bumpBackstageJsonVersion(version: string) {
const backstageJson = await getBackstageJson();
const prevVersion = backstageJson?.version;
if (prevVersion === version) {
@@ -394,7 +442,7 @@ export async function bumpBackstageJsonVersion(version: string) {
}
await fs.writeJson(
backstageJsonPath,
getBackstageJsonPath(),
{ ...backstageJson, version },
{
spaces: 2,
@@ -403,6 +451,53 @@ export async function bumpBackstageJsonVersion(version: string) {
);
}
async function asLockfileVersion(version: string) {
if (version === 'backstage:^') {
return `backstage:${(await getBackstageJson())?.version}`;
}
return version;
}
const yarnRcSchema = z.object({
plugins: z
.array(
z.object({
path: z.string(),
}),
)
.optional(),
});
async function getHasYarnPlugin() {
const yarnRcPath = paths.resolveTargetRoot('.yarnrc.yml');
const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => {
if (e.code === 'ENOENT') {
// gracefully continue in case the file doesn't exist
return '';
}
throw e;
});
if (!yarnRcContent) {
return false;
}
const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent));
if (!parseResult.success) {
throw new Error(
`Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`,
);
}
const yarnRc = parseResult.data;
return yarnRc.plugins?.some(
plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs',
);
}
export async function runYarnInstall() {
const spinner = ora({
prefixText: `Running ${chalk.blue('yarn install')} to install new versions`,