Skip to content

Commit

Permalink
Merge pull request #24375 from backstage/blam/improve-migrate-tooling
Browse files Browse the repository at this point in the history
cli: Improve the migrate tooling
  • Loading branch information
benjdlambert committed Apr 19, 2024
2 parents 3b4eae3 + d50f5f3 commit 064ae68
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-waves-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---

Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes`
1 change: 1 addition & 0 deletions packages/cli/cli-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,5 +622,6 @@ Usage: backstage-cli versions:migrate [options]
Options:
--pattern <glob>
--skip-code-changes
-h, --help
```
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"react-dev-utils": "^12.0.0-next.60",
"react-refresh": "^0.14.0",
"recursive-readdir": "^2.2.2",
"replace-in-file": "^7.1.0",
"rollup": "^4.0.0",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,10 @@ export function registerCommands(program: Command) {
'--pattern <glob>',
'Override glob for matching packages to upgrade',
)
.option(
'--skip-code-changes',
'Skip code changes and only update package.json files',
)
.description(
'Migrate any plugins that have been moved to the @backstage-community namespace automatically',
)
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/commands/versions/bump.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ describe('bump', () => {
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
'Running yarn install to install new versions',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage/theme : 1.0.0 ~> 2.0.0',
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
Expand Down Expand Up @@ -323,6 +324,7 @@ describe('bump', () => {
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
'Skipping yarn install',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage/theme : 1.0.0 ~> 2.0.0',
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
Expand Down Expand Up @@ -437,6 +439,7 @@ describe('bump', () => {
'bumping @backstage/core in a to ^1.0.6',
'Your project is now at version 0.0.1, which has been written to backstage.json',
'Running yarn install to install new versions',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage/theme : 1.0.0 ~> 5.0.0',
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
Expand Down Expand Up @@ -640,6 +643,7 @@ describe('bump', () => {
'bumping @backstage/core in a to ^1.0.6',
'Your project is now at version 1.0.0, which has been written to backstage.json',
'Running yarn install to install new versions',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage/theme : 1.0.0 ~> 5.0.0',
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
Expand Down Expand Up @@ -745,6 +749,7 @@ describe('bump', () => {
'bumping @backstage/theme in b to ^2.0.0',
'Skipping backstage.json update as custom pattern is used',
'Running yarn install to install new versions',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage-extra/custom-two : 1.0.0 ~> 2.0.0',
' @backstage/theme : 1.0.0 ~> 2.0.0',
Expand Down Expand Up @@ -968,6 +973,7 @@ describe('bump', () => {
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
'Running yarn install to install new versions',
'Checking for moved packages to the @backstage-community namespace...',
'⚠️ The following packages may have breaking changes:',
' @backstage/theme : 1.0.0 ~> 2.0.0',
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/versions/bump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,12 @@ export default async (opts: OptionValues) => {
}

if (!opts.skipMigrate) {
console.log();

const changed = await migrateMovedPackages({
pattern: opts.pattern,
});

if (changed && !opts.skipInstall) {
await runYarnInstall();
}
Expand Down
178 changes: 178 additions & 0 deletions packages/cli/src/commands/versions/migrate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ describe('versions:migrate', () => {
});

expectLogsToMatch(logs, [
'Checking for moved packages to the @backstage-community namespace...',
'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)',
Expand Down Expand Up @@ -164,4 +165,181 @@ describe('versions:migrate', () => {
},
});
});

it('should replace the occurences of the moved package in files inside the correct package', async () => {
mockDir.setContent({
'package.json': JSON.stringify({
workspaces: {
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({
name: 'a',
dependencies: {
'@backstage/core': '^1.0.5',
'@backstage/custom': '^1.0.1',
'@backstage/custom-two': '^1.0.0',
},
}),
src: {
'index.ts': "import { myThing } from '@backstage/custom';",
'index.test.ts': "import { myThing } from '@backstage/custom-two';",
},
},
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);

await withLogCollector(async () => {
await migrate({});
});

expect(run.run).toHaveBeenCalledTimes(1);
expect(run.run).toHaveBeenCalledWith(
'yarn',
['install'],
expect.any(Object),
);

const indexA = await fs.readFile(
mockDir.resolve('packages/a/src/index.ts'),
'utf-8',
);

expect(indexA).toEqual(
"import { myThing } from '@backstage-community/custom';",
);

const indexTestA = await fs.readFile(
mockDir.resolve('packages/a/src/index.test.ts'),
'utf-8',
);

expect(indexTestA).toEqual(
"import { myThing } from '@backstage-community/custom-two';",
);
});

it('should replaces the occurences of changed packages, and is careful', async () => {
mockDir.setContent({
'package.json': JSON.stringify({
workspaces: {
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',
}),
},
},
},
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',
},
}),
src: {
'index.ts': "import { myThing } from '@backstage/custom';",
'index.test.ts': "import { myThing } from '@backstage/custom-two';",
},
},
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);

await withLogCollector(async () => {
await migrate({});
});

expect(run.run).toHaveBeenCalledTimes(1);
expect(run.run).toHaveBeenCalledWith(
'yarn',
['install'],
expect.any(Object),
);

const indexA = await fs.readFile(
mockDir.resolve('packages/a/src/index.ts'),
'utf-8',
);

expect(indexA).toEqual(
"import { myThing } from '@backstage-community/custom';",
);

const indexTestA = await fs.readFile(
mockDir.resolve('packages/a/src/index.test.ts'),
'utf-8',
);

expect(indexTestA).toEqual(
"import { myThing } from '@backstage/custom-two';",
);
});
});
73 changes: 55 additions & 18 deletions packages/cli/src/commands/versions/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,54 @@
*/
import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node';
import chalk from 'chalk';
import { resolve as resolvePath } from 'path';
import { resolve as resolvePath, join as joinPath } from 'path';
import { OptionValues } from 'commander';
import { readJson, writeJson } from 'fs-extra';
import { minimatch } from 'minimatch';
import { runYarnInstall } from './bump';
import replace from 'replace-in-file';

declare module 'replace-in-file' {
export default function (config: {
files: string | string[];
processor: (content: string, file: string) => string;
ignore?: string | string[];
allowEmptyPaths?: boolean;
}): Promise<
{
file: string;
hasChanged: boolean;
numMatches?: number;
numReplacements?: number;
}[]
>;
}

export default async (options: OptionValues) => {
const changed = await migrateMovedPackages({
pattern: options.pattern,
skipCodeChanges: options.skipCodeChanges,
});

if (changed) {
await runYarnInstall();
}
};

export async function migrateMovedPackages(options?: { pattern?: string }) {
export async function migrateMovedPackages(options?: {
pattern?: string;
skipCodeChanges?: boolean;
}) {
console.log(
'Checking for moved packages to the @backstage-community namespace...',
);
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;
const movedPackages = new Map<string, string>();

for (const depType of [
'dependencies',
Expand Down Expand Up @@ -87,6 +96,7 @@ export async function migrateMovedPackages(options?: { pattern?: string }) {
const movedPackageName = packageInfo.backstage?.moved;

if (movedPackageName) {
movedPackages.set(depName, movedPackageName);
console.log(
chalk.yellow(
`Found a moved package ${depName}@${depVersion} -> ${movedPackageName} in ${pkgName} (${depType})`,
Expand All @@ -106,6 +116,33 @@ export async function migrateMovedPackages(options?: { pattern?: string }) {
await writeJson(resolvePath(pkg.dir, 'package.json'), pkg.packageJson, {
spaces: 2,
});

if (!options?.skipCodeChanges) {
// Replace all occurrences of the old package names in the code.
const files = await replace({
files: joinPath(pkg.dir, 'src', '**'),
allowEmptyPaths: true,
processor: content => {
return Array.from(movedPackages.entries()).reduce(
(newContent, [oldName, newName]) => {
return newContent
.replace(new RegExp(`"${oldName}"`, 'g'), `"${newName}"`)
.replace(new RegExp(`'${oldName}'`, 'g'), `'${newName}'`)
.replace(new RegExp(`${oldName}/`, 'g'), `${newName}/`);
},
content,
);
},
});

if (files.length > 0) {
console.log(
chalk.green(
`Updated ${files.length} files in ${pkgName} to use the new package names`,
),
);
}
}
}
}

Expand Down
Loading

0 comments on commit 064ae68

Please sign in to comment.