feat: starting work on migration tooling

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-04-19 10:25:01 +02:00
parent 3b4eae38ad
commit 4182031a6b
7 changed files with 177 additions and 19 deletions
+1
View File
@@ -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",
+4
View File
@@ -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',
)
@@ -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',
@@ -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',
@@ -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',
@@ -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',
@@ -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',
@@ -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',
@@ -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();
}
@@ -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)',
@@ -164,4 +165,96 @@ 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);
const s = await withLogCollector(async () => {
await migrate({});
});
console.log(s);
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';",
);
});
});
+55 -18
View File
@@ -15,15 +15,33 @@
*/
import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node';
import chalk from 'chalk';
import { resolve as resolvePath } from 'path';
import { resolve as resolvePath, join } 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) {
@@ -31,29 +49,20 @@ export default async (options: OptionValues) => {
}
};
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',
@@ -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})`,
@@ -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: join(pkg.dir, '**', '*.{ts,tsx,js,jsx}'),
ignore: ['/node_modules/', '/yarn.lock/'],
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}"`);
},
content,
);
},
});
if (files.length > 0) {
console.log(
chalk.green(
`Updated ${files.length} files in ${pkgName} to use the new package names`,
),
);
}
}
}
}
+15 -1
View File
@@ -3729,6 +3729,7 @@ __metadata:
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
@@ -29082,7 +29083,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^8.0.0, glob@npm:^8.0.1, glob@npm:^8.0.3":
"glob@npm:^8.0.0, glob@npm:^8.0.1, glob@npm:^8.0.3, glob@npm:^8.1.0":
version: 8.1.0
resolution: "glob@npm:8.1.0"
dependencies:
@@ -40432,6 +40433,19 @@ __metadata:
languageName: node
linkType: hard
"replace-in-file@npm:^7.1.0":
version: 7.1.0
resolution: "replace-in-file@npm:7.1.0"
dependencies:
chalk: ^4.1.2
glob: ^8.1.0
yargs: ^17.7.2
bin:
replace-in-file: bin/cli.js
checksum: 2ed61bd0cf0752b18775b52342ad36f4ee6c806f7eca0b0d085c23bafe0cb4828e4ec8f59058bde6b67d2ed5ac51e1681284f586089b58b966e2489712830db0
languageName: node
linkType: hard
"request@npm:^2.88.0":
version: 2.88.2
resolution: "request@npm:2.88.2"