diff --git a/.changeset/flat-crabs-smash.md b/.changeset/flat-crabs-smash.md new file mode 100644 index 0000000000..b56b340605 --- /dev/null +++ b/.changeset/flat-crabs-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `versions:bump` command now filters out `npm_` environment configuration when running `yarn install`. This has the effect of allowing it to consider local configuration files within the repository, which is the behavior that one would expect. diff --git a/.changeset/little-ravens-kick.md b/.changeset/little-ravens-kick.md new file mode 100644 index 0000000000..1e14f2f37e --- /dev/null +++ b/.changeset/little-ravens-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `versions:bump` command now also considers the root `package.json` when searching for updates. It has also received updates to its output, including a link the [Backstage upgrade helper](https://backstage.github.io/upgrade-helper) and silenced `yarn install` output. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 73ecc0284e..e7b3c858aa 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -31,6 +31,7 @@ import { rest } from 'msw'; // 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, @@ -38,6 +39,18 @@ jest.mock('chalk', () => ({ yellow: (str: string) => str, })); +jest.mock('ora', () => ({ + __esModule: true, + default({ prefixText }: any) { + console.log(prefixText); + return { + start: () => ({ + succeed: () => {}, + }), + }; + }, +})); + const REGISTRY_VERSIONS: { [name: string]: string } = { '@backstage/core': '1.0.6', '@backstage/core-api': '1.0.7', @@ -177,7 +190,11 @@ describe('bump', () => { ); expect(runObj.run).toHaveBeenCalledTimes(1); - expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); + expect(runObj.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); expect(lockfileContents).toBe(lockfileMockResult); @@ -273,7 +290,7 @@ describe('bump', () => { 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/theme in b to ^5.0.0', - 'Creating backstage.json', + 'Your project is now at version 0.0.1, which has been written to backstage.json', 'Running yarn install to install new versions', '⚠️ The following packages may have breaking changes:', ' @backstage/theme : 1.0.0 ~> 5.0.0', @@ -296,7 +313,11 @@ describe('bump', () => { ); expect(runObj.run).toHaveBeenCalledTimes(1); - expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); + expect(runObj.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); expect(lockfileContents).toBe(lockfileMockResult); @@ -389,7 +410,7 @@ describe('bump', () => { 'Some packages are outdated, updating', 'bumping @backstage/core in a to ^5.0.0', 'bumping @backstage/core in b to ^5.0.0', - 'Creating backstage.json', + 'Your project is now at version 2.0.0, which has been written to backstage.json', 'Running yarn install to install new versions', '⚠️ The following packages may have breaking changes:', ' @backstage/core : 1.0.3 ~> 5.0.0', @@ -398,7 +419,11 @@ describe('bump', () => { ]); expect(runObj.run).toHaveBeenCalledTimes(1); - expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); + expect(runObj.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); const packageA = await fs.readJson('/packages/a/package.json'); expect(packageA).toEqual({ @@ -512,7 +537,7 @@ describe('bump', () => { 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/theme in b to ^5.0.0', - 'Creating backstage.json', + 'Your project is now at version 1.0.0, which has been written to backstage.json', 'Running yarn install to install new versions', '⚠️ The following packages may have breaking changes:', ' @backstage/theme : 1.0.0 ~> 5.0.0', @@ -645,7 +670,11 @@ describe('bump', () => { ); expect(runObj.run).toHaveBeenCalledTimes(1); - expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); + expect(runObj.run).toHaveBeenCalledWith( + 'yarn', + ['install'], + expect.any(Object), + ); const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); expect(lockfileContents).toEqual(customLockfileMockResult); @@ -765,8 +794,16 @@ describe('bumpBackstageJsonVersion', () => { .spyOn(paths, 'resolveTargetRoot') .mockImplementation((...path) => resolvePath('/', ...path)); - await bumpBackstageJsonVersion('1.4.1'); + const { log } = await withLogCollector(async () => { + await bumpBackstageJsonVersion('1.4.1'); + }); expect(await fs.readJson('/backstage.json')).toEqual({ version: '1.4.1' }); + expect(log).toEqual([ + 'Upgraded from release 0.0.1 to 1.4.1, please review these template changes:', + undefined, + ' https://backstage.github.io/upgrade-helper/?from=0.0.1&to=1.4.1', + undefined, + ]); }); it("should create backstage.json if doesn't exist", async () => { @@ -777,8 +814,13 @@ describe('bumpBackstageJsonVersion', () => { .spyOn(paths, 'resolveTargetRoot') .mockImplementation((...path) => resolvePath('/', ...path)); - await bumpBackstageJsonVersion(latest); + const { log } = await withLogCollector(async () => { + await bumpBackstageJsonVersion(latest); + }); expect(await fs.readJson('/backstage.json')).toEqual({ version: latest }); + expect(log).toEqual([ + 'Your project is now at version 1.4.1, which has been written to backstage.json', + ]); }); }); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 8c3c661996..b9d318bccc 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import chalk from 'chalk'; +import ora from 'ora'; import semver from 'semver'; import minimatch from 'minimatch'; import { Command } from 'commander'; @@ -259,12 +260,8 @@ export default async (cmd: Command) => { ), ); } - console.log(); - console.log( - `Running ${chalk.blue('yarn install')} to install new versions`, - ); - console.log(); - await run('yarn', ['install']); + + await runYarnInstall(); if (breakingUpdates.size > 0) { console.log(); @@ -420,17 +417,34 @@ export async function bumpBackstageJsonVersion(version: string) { throw e; }); - if (backstageJson?.version === version) { + const prevVersion = backstageJson?.version; + + if (prevVersion === version) { return; } - console.log( - chalk.yellow( - typeof backstageJson === 'undefined' - ? `Creating ${BACKSTAGE_JSON}` - : `Bumping version in ${BACKSTAGE_JSON}`, - ), - ); + const { yellow, cyan, green } = chalk; + if (prevVersion) { + const from = encodeURIComponent(prevVersion); + const to = encodeURIComponent(version); + const link = `https://backstage.github.io/upgrade-helper/?from=${from}&to=${to}`; + console.log( + yellow( + `Upgraded from release ${green(prevVersion)} to ${green( + version, + )}, please review these template changes:`, + ), + ); + console.log(); + console.log(` ${cyan(link)}`); + console.log(); + } else { + console.log( + yellow( + `Your project is now at version ${version}, which has been written to ${BACKSTAGE_JSON}`, + ), + ); + } await fs.writeJson( backstageJsonPath, @@ -441,3 +455,35 @@ export async function bumpBackstageJsonVersion(version: string) { }, ); } + +async function runYarnInstall() { + const spinner = ora({ + prefixText: `Running ${chalk.blue('yarn install')} to install new versions`, + spinner: 'arc', + color: 'green', + }).start(); + + const installOutput = new Array(); + try { + await run('yarn', ['install'], { + env: { + FORCE_COLOR: 'true', + // We filter out all of the npm_* environment variables that are added when + // executing through yarn. This works around an issue where these variables + // incorrectly override local yarn or npm config in the project directory. + ...Object.fromEntries( + Object.entries(process.env).map(([name, value]) => + name.startsWith('npm_') ? [name, undefined] : [name, value], + ), + ), + }, + stdoutLogFunc: data => installOutput.push(data), + stderrLogFunc: data => installOutput.push(data), + }); + spinner.succeed(); + } catch (error) { + spinner.fail(); + process.stdout.write(Buffer.concat(installOutput)); + throw error; + } +} diff --git a/packages/cli/src/lib/logging.ts b/packages/cli/src/lib/logging.ts index 8745585d1e..59473d7f64 100644 --- a/packages/cli/src/lib/logging.ts +++ b/packages/cli/src/lib/logging.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export type LogFunc = (data: Buffer | string) => void; +export type LogFunc = (data: Buffer) => void; export type LogPipe = (dst: NodeJS.WriteStream) => LogFunc; export type LogOptions = { diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 9f2f2bdc65..ed7266a692 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -67,7 +67,10 @@ export async function mapDependencies( targetDir: string, pattern: string, ): Promise> { - const { packages } = await getPackages(targetDir); + const { packages, root } = await getPackages(targetDir); + + // Include root package.json too + packages.push(root); const dependencyMap = new Map(); for (const pkg of packages) {