@grafana/create-plugin 6.2.0-canary.2233.19097561440.0 → 6.2.0-canary.2233.19368311379.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/CONTRIBUTING.md +3 -0
- package/dist/codemods/additions/additions.js +8 -8
- package/dist/codemods/additions/scripts/example-addition.js +47 -0
- package/dist/codemods/migrations/manager.js +13 -40
- package/dist/codemods/migrations/migrations.js +34 -25
- package/dist/codemods/migrations/scripts/004-eslint9-flat-config.js +1 -2
- package/dist/codemods/migrations/scripts/005-react-18-3.js +20 -0
- package/dist/codemods/migrations/scripts/example-migration.js +7 -3
- package/dist/codemods/runner.js +38 -0
- package/dist/codemods/schema-parser.js +20 -0
- package/dist/codemods/utils.js +15 -6
- package/dist/commands/add.command.js +24 -55
- package/dist/commands/update.command.js +7 -41
- package/dist/utils/utils.checks.js +40 -0
- package/dist/utils/utils.config.js +1 -16
- package/package.json +3 -2
- package/src/codemods/additions/additions.test.ts +12 -0
- package/src/codemods/additions/additions.ts +9 -22
- package/src/codemods/additions/scripts/example-addition.test.ts +92 -0
- package/src/codemods/additions/scripts/example-addition.ts +62 -0
- package/src/codemods/migrations/fixtures/migrations.ts +19 -18
- package/src/codemods/migrations/manager.test.ts +67 -73
- package/src/codemods/migrations/manager.ts +17 -50
- package/src/codemods/migrations/migrations.test.ts +8 -5
- package/src/codemods/migrations/migrations.ts +38 -34
- package/src/codemods/migrations/scripts/004-eslint9-flat-config.ts +2 -2
- package/src/codemods/migrations/scripts/005-react-18-3.test.ts +145 -0
- package/src/codemods/migrations/scripts/005-react-18-3.ts +19 -0
- package/src/codemods/migrations/scripts/example-migration.test.ts +1 -1
- package/src/codemods/migrations/scripts/example-migration.ts +20 -3
- package/src/codemods/runner.ts +57 -0
- package/src/codemods/schema-parser.ts +27 -0
- package/src/codemods/types.ts +9 -14
- package/src/codemods/{migrations/utils.test.ts → utils.test.ts} +8 -7
- package/src/codemods/utils.ts +28 -36
- package/src/commands/add.command.ts +26 -62
- package/src/commands/update.command.ts +8 -47
- package/src/migrations/migrations.ts +44 -0
- package/src/utils/utils.checks.ts +47 -0
- package/src/utils/utils.config.ts +1 -28
- package/templates/common/_package.json +7 -5
- package/templates/github/workflows/bundle-stats.yml +1 -1
- package/templates/github/workflows/ci.yml +11 -11
- package/templates/github/workflows/cp-update.yml +9 -14
- package/templates/github/workflows/is-compatible.yml +3 -3
- package/templates/github/workflows/release.yml +1 -1
- package/vitest.config.ts +12 -0
- package/dist/codemods/additions/manager.js +0 -115
- package/dist/codemods/additions/scripts/add-i18n.js +0 -445
- package/dist/codemods/additions/utils.js +0 -10
- package/dist/codemods/migrations/utils.js +0 -10
- package/src/codemods/additions/manager.ts +0 -145
- package/src/codemods/additions/scripts/add-i18n.test.ts +0 -347
- package/src/codemods/additions/scripts/add-i18n.ts +0 -584
- package/src/codemods/additions/utils.ts +0 -12
- package/src/codemods/migrations/utils.ts +0 -12
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isGitDirectory, isGitDirectoryClean } from './utils.git.js';
|
|
2
|
+
import { isPluginDirectory } from './utils.plugin.js';
|
|
3
|
+
import { output } from './utils.console.js';
|
|
4
|
+
|
|
5
|
+
async function performPreCodemodChecks(argv) {
|
|
6
|
+
if (!await isGitDirectory() && !argv.force) {
|
|
7
|
+
output.error({
|
|
8
|
+
title: "You are not inside a git directory",
|
|
9
|
+
body: [
|
|
10
|
+
`In order to proceed please run ${output.formatCode("git init")} in the root of your project and commit your changes.`,
|
|
11
|
+
`(This check is necessary to make sure that changes are easy to revert and don't interfere with any changes you currently have.`,
|
|
12
|
+
`In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
|
|
13
|
+
]
|
|
14
|
+
});
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!await isGitDirectoryClean() && !argv.force) {
|
|
18
|
+
output.error({
|
|
19
|
+
title: "Please clean your repository working tree before making changes.",
|
|
20
|
+
body: [
|
|
21
|
+
"Commit your changes or stash them.",
|
|
22
|
+
`(This check is necessary to make sure that changes are easy to revert and don't mess with any changes you currently have.`,
|
|
23
|
+
`In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (!isPluginDirectory() && !argv.force) {
|
|
29
|
+
output.error({
|
|
30
|
+
title: "Are you inside a plugin directory?",
|
|
31
|
+
body: [
|
|
32
|
+
`We couldn't find a "src/plugin.json" file under your current directory.`,
|
|
33
|
+
`(Please make sure to run this command from the root of your plugin folder. In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { performPreCodemodChecks };
|
|
@@ -8,9 +8,6 @@ import path from 'node:path';
|
|
|
8
8
|
import { writeFile } from 'node:fs/promises';
|
|
9
9
|
import { EOL } from 'node:os';
|
|
10
10
|
|
|
11
|
-
function isFeatureEnabled(features, featureName) {
|
|
12
|
-
return features[featureName] === true;
|
|
13
|
-
}
|
|
14
11
|
let hasShownConfigWarnings = false;
|
|
15
12
|
function getConfig(workDir = process.cwd()) {
|
|
16
13
|
const rootConfig = getRootConfig(workDir);
|
|
@@ -95,17 +92,5 @@ async function setRootConfig(configOverride = {}) {
|
|
|
95
92
|
await writeFile(rootConfigPath, JSON.stringify(updatedConfig, null, 2) + EOL);
|
|
96
93
|
return updatedConfig;
|
|
97
94
|
}
|
|
98
|
-
async function setFeatureFlag(featureName, enabled = true) {
|
|
99
|
-
const userConfig = getUserConfig() || { features: {} };
|
|
100
|
-
const userConfigPath = path.resolve(process.cwd(), ".cprc.json");
|
|
101
|
-
const updatedConfig = {
|
|
102
|
-
...userConfig,
|
|
103
|
-
features: {
|
|
104
|
-
...userConfig.features,
|
|
105
|
-
[featureName]: enabled
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
await writeFile(userConfigPath, JSON.stringify(updatedConfig, null, 2) + EOL);
|
|
109
|
-
}
|
|
110
95
|
|
|
111
|
-
export { getConfig,
|
|
96
|
+
export { getConfig, setRootConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "6.2.0-canary.2233.
|
|
3
|
+
"version": "6.2.0-canary.2233.19368311379.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"directory": "packages/create-plugin",
|
|
6
6
|
"url": "https://github.com/grafana/plugin-tools"
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"recast": "^0.23.11",
|
|
39
39
|
"semver": "^7.3.5",
|
|
40
40
|
"title-case": "^4.3.0",
|
|
41
|
+
"valibot": "^1.1.0",
|
|
41
42
|
"which": "^5.0.0",
|
|
42
43
|
"yaml": "^2.7.0"
|
|
43
44
|
},
|
|
@@ -59,5 +60,5 @@
|
|
|
59
60
|
"engines": {
|
|
60
61
|
"node": ">=20"
|
|
61
62
|
},
|
|
62
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "265f74e1e4a843bdeaad5300a13b94391ffd5fff"
|
|
63
64
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import defaultAdditions from './additions.js';
|
|
2
|
+
|
|
3
|
+
describe('additions json', () => {
|
|
4
|
+
// as addition scripts are imported dynamically when add is run we assert the path is valid
|
|
5
|
+
defaultAdditions.forEach((addition) => {
|
|
6
|
+
it(`should have a valid addition script path for ${addition.name}`, () => {
|
|
7
|
+
expect(async () => {
|
|
8
|
+
await import(addition.scriptPath);
|
|
9
|
+
}).not.toThrow();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
i18n: {
|
|
10
|
-
name: 'i18n',
|
|
11
|
-
description: 'Add internationalization (i18n) support to your plugin',
|
|
12
|
-
scriptPath: './scripts/add-i18n.js',
|
|
13
|
-
featureName: 'i18nEnabled',
|
|
1
|
+
import { Codemod } from '../types.js';
|
|
2
|
+
import { resolveScriptPath } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
name: 'example-addition',
|
|
7
|
+
description: 'Example addition demonstrating Valibot schema with type inference',
|
|
8
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/example-addition.js'),
|
|
14
9
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export default { additions };
|
|
18
|
-
|
|
19
|
-
// extract feature names for type-safe FeatureFlags in utils.config.ts
|
|
20
|
-
type AdditionValues = (typeof additions)[keyof typeof additions];
|
|
21
|
-
export type AdditionFeatureName = AdditionValues['featureName'];
|
|
22
|
-
|
|
23
|
-
export type TypedAdditionMeta = AdditionMeta<AdditionFeatureName>;
|
|
10
|
+
] satisfies Codemod[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { Context } from '../../context.js';
|
|
4
|
+
import migrate from './example-addition.js';
|
|
5
|
+
|
|
6
|
+
describe('example-addition', () => {
|
|
7
|
+
it('should add example script to package.json', () => {
|
|
8
|
+
const context = new Context('/virtual');
|
|
9
|
+
|
|
10
|
+
context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
|
|
11
|
+
|
|
12
|
+
const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
|
|
13
|
+
|
|
14
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
15
|
+
expect(packageJson.scripts['example-script']).toBe('echo "Running testFeature"');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should add dev dependency', () => {
|
|
19
|
+
const context = new Context('/virtual');
|
|
20
|
+
|
|
21
|
+
context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
|
|
22
|
+
|
|
23
|
+
const result = migrate(context, { featureName: 'myFeature', enabled: false, frameworks: ['react'] });
|
|
24
|
+
|
|
25
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
26
|
+
expect(packageJson.devDependencies['example-dev-dep']).toBe('^1.0.0');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should create feature TypeScript file with options', () => {
|
|
30
|
+
const context = new Context('/virtual');
|
|
31
|
+
|
|
32
|
+
context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
|
|
33
|
+
|
|
34
|
+
const result = migrate(context, {
|
|
35
|
+
featureName: 'myFeature',
|
|
36
|
+
enabled: false,
|
|
37
|
+
port: 4000,
|
|
38
|
+
frameworks: ['react', 'vue'],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(result.doesFileExist('src/features/myFeature.ts')).toBe(true);
|
|
42
|
+
const featureCode = result.getFile('src/features/myFeature.ts');
|
|
43
|
+
expect(featureCode).toContain('export const myFeature');
|
|
44
|
+
expect(featureCode).toContain('enabled: false');
|
|
45
|
+
expect(featureCode).toContain('port: 4000');
|
|
46
|
+
expect(featureCode).toContain('frameworks: ["react","vue"]');
|
|
47
|
+
expect(featureCode).toContain('myFeature initialized on port 4000');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should delete deprecated file if it exists', () => {
|
|
51
|
+
const context = new Context('/virtual');
|
|
52
|
+
|
|
53
|
+
context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
|
|
54
|
+
context.addFile('src/deprecated.ts', 'export const old = true;');
|
|
55
|
+
|
|
56
|
+
const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
|
|
57
|
+
|
|
58
|
+
expect(result.doesFileExist('src/deprecated.ts')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should rename old-config.json if it exists', () => {
|
|
62
|
+
const context = new Context('/virtual');
|
|
63
|
+
|
|
64
|
+
context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
|
|
65
|
+
context.addFile('src/old-config.json', JSON.stringify({ old: true }));
|
|
66
|
+
|
|
67
|
+
const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
|
|
68
|
+
|
|
69
|
+
expect(result.doesFileExist('src/old-config.json')).toBe(false);
|
|
70
|
+
expect(result.doesFileExist('src/new-config.json')).toBe(true);
|
|
71
|
+
const newConfig = JSON.parse(result.getFile('src/new-config.json') || '{}');
|
|
72
|
+
expect(newConfig.old).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should not add script if it already exists', () => {
|
|
76
|
+
const context = new Context('/virtual');
|
|
77
|
+
|
|
78
|
+
context.addFile(
|
|
79
|
+
'package.json',
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
scripts: { 'example-script': 'existing command' },
|
|
82
|
+
dependencies: {},
|
|
83
|
+
devDependencies: {},
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
|
|
88
|
+
|
|
89
|
+
const packageJson = JSON.parse(result.getFile('package.json') || '{}');
|
|
90
|
+
expect(packageJson.scripts['example-script']).toBe('existing command');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
import type { Context } from '../../context.js';
|
|
3
|
+
import { addDependenciesToPackageJson } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Example addition demonstrating Valibot schema with type inference
|
|
7
|
+
* Schema defines validation rules, defaults and types are automatically inferred
|
|
8
|
+
*/
|
|
9
|
+
export const schema = v.object({
|
|
10
|
+
featureName: v.pipe(
|
|
11
|
+
v.string(),
|
|
12
|
+
v.minLength(3, 'Feature name must be at least 3 characters'),
|
|
13
|
+
v.maxLength(50, 'Feature name must be at most 50 characters')
|
|
14
|
+
),
|
|
15
|
+
enabled: v.optional(v.boolean(), true),
|
|
16
|
+
port: v.optional(
|
|
17
|
+
v.pipe(v.number(), v.minValue(1000, 'Port must be at least 1000'), v.maxValue(65535, 'Port must be at most 65535'))
|
|
18
|
+
),
|
|
19
|
+
frameworks: v.optional(v.array(v.string()), ['react']),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Type is automatically inferred from the schema
|
|
23
|
+
type ExampleOptions = v.InferOutput<typeof schema>;
|
|
24
|
+
|
|
25
|
+
export default function exampleAddition(context: Context, options: ExampleOptions): Context {
|
|
26
|
+
// These options have been validated by the framework
|
|
27
|
+
const { featureName, enabled, port, frameworks } = options;
|
|
28
|
+
|
|
29
|
+
const rawPkgJson = context.getFile('./package.json') ?? '{}';
|
|
30
|
+
const packageJson = JSON.parse(rawPkgJson);
|
|
31
|
+
|
|
32
|
+
if (packageJson.scripts && !packageJson.scripts['example-script']) {
|
|
33
|
+
packageJson.scripts['example-script'] = `echo "Running ${featureName}"`;
|
|
34
|
+
context.updateFile('./package.json', JSON.stringify(packageJson, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addDependenciesToPackageJson(context, {}, { 'example-dev-dep': '^1.0.0' });
|
|
38
|
+
|
|
39
|
+
if (!context.doesFileExist(`./src/features/${featureName}.ts`)) {
|
|
40
|
+
const featureCode = `export const ${featureName} = {
|
|
41
|
+
name: '${featureName}',
|
|
42
|
+
enabled: ${enabled},
|
|
43
|
+
port: ${port ?? 3000},
|
|
44
|
+
frameworks: ${JSON.stringify(frameworks)},
|
|
45
|
+
init() {
|
|
46
|
+
console.log('${featureName} initialized on port ${port ?? 3000}');
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
`;
|
|
50
|
+
context.addFile(`./src/features/${featureName}.ts`, featureCode);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (context.doesFileExist('./src/deprecated.ts')) {
|
|
54
|
+
context.deleteFile('./src/deprecated.ts');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (context.doesFileExist('./src/old-config.json')) {
|
|
58
|
+
context.renameFile('./src/old-config.json', './src/new-config.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return context;
|
|
62
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
export default
|
|
2
|
-
|
|
3
|
-
'migration-key1'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
},
|
|
8
|
-
'migration-key2': {
|
|
9
|
-
version: '5.4.0',
|
|
10
|
-
description: 'Update project to use new cache directory',
|
|
11
|
-
migrationScript: './5-4-0-cache-directory.js',
|
|
12
|
-
},
|
|
13
|
-
'migration-key3': {
|
|
14
|
-
version: '6.0.0',
|
|
15
|
-
description: 'Update project to use new cache directory',
|
|
16
|
-
migrationScript: './5-4-0-cache-directory.js',
|
|
17
|
-
},
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
name: 'migration-key1',
|
|
4
|
+
version: '5.0.0',
|
|
5
|
+
description: 'Update project to use new cache directory',
|
|
6
|
+
scriptPath: './5-0-0-cache-directory.js',
|
|
18
7
|
},
|
|
19
|
-
|
|
8
|
+
{
|
|
9
|
+
name: 'migration-key2',
|
|
10
|
+
version: '5.4.0',
|
|
11
|
+
description: 'Update project to use new cache directory',
|
|
12
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'migration-key3',
|
|
16
|
+
version: '6.0.0',
|
|
17
|
+
description: 'Update project to use new cache directory',
|
|
18
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { flushChanges, formatFiles } from '../utils.js';
|
|
2
|
-
import { getMigrationsToRun,
|
|
1
|
+
import { flushChanges, formatFiles, printChanges } from '../utils.js';
|
|
2
|
+
import { getMigrationsToRun, runMigrations } from './manager.js';
|
|
3
3
|
|
|
4
4
|
import { Context } from '../context.js';
|
|
5
|
-
import {
|
|
5
|
+
import { Migration } from './migrations.js';
|
|
6
6
|
import { gitCommitNoVerify } from '../../utils/utils.git.js';
|
|
7
7
|
import migrationFixtures from './fixtures/migrations.js';
|
|
8
|
-
import { printChanges } from './utils.js';
|
|
9
8
|
import { setRootConfig } from '../../utils/utils.config.js';
|
|
10
9
|
import { vi } from 'vitest';
|
|
11
10
|
|
|
12
|
-
vi.mock('
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
vi.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
})
|
|
11
|
+
vi.mock('../utils.js', async (importOriginal) => {
|
|
12
|
+
const actual = (await importOriginal()) as any;
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
flushChanges: vi.fn(),
|
|
16
|
+
formatFiles: vi.fn(),
|
|
17
|
+
installNPMDependencies: vi.fn(),
|
|
18
|
+
printChanges: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
22
21
|
|
|
23
22
|
// Silence terminal output during tests.
|
|
24
23
|
vi.mock('../../utils/utils.console.js', () => ({
|
|
@@ -50,92 +49,83 @@ describe('Migrations', () => {
|
|
|
50
49
|
it('should return the migrations that need to be run', () => {
|
|
51
50
|
const fromVersion = '3.0.0';
|
|
52
51
|
const toVersion = '5.0.0';
|
|
53
|
-
const migrations = getMigrationsToRun(fromVersion, toVersion, migrationFixtures
|
|
54
|
-
expect(migrations).toEqual(
|
|
55
|
-
|
|
52
|
+
const migrations = getMigrationsToRun(fromVersion, toVersion, migrationFixtures);
|
|
53
|
+
expect(migrations).toEqual([
|
|
54
|
+
{
|
|
55
|
+
name: 'migration-key1',
|
|
56
56
|
version: '5.0.0',
|
|
57
57
|
description: 'Update project to use new cache directory',
|
|
58
|
-
|
|
58
|
+
scriptPath: './5-0-0-cache-directory.js',
|
|
59
59
|
},
|
|
60
|
-
|
|
60
|
+
]);
|
|
61
61
|
|
|
62
62
|
const fromVersion2 = '5.0.0';
|
|
63
63
|
const toVersion2 = '5.5.0';
|
|
64
|
-
const migrations2 = getMigrationsToRun(fromVersion2, toVersion2, migrationFixtures
|
|
65
|
-
expect(migrations2).toEqual(
|
|
66
|
-
|
|
64
|
+
const migrations2 = getMigrationsToRun(fromVersion2, toVersion2, migrationFixtures);
|
|
65
|
+
expect(migrations2).toEqual([
|
|
66
|
+
{
|
|
67
|
+
name: 'migration-key1',
|
|
67
68
|
version: '5.0.0',
|
|
68
69
|
description: 'Update project to use new cache directory',
|
|
69
|
-
|
|
70
|
+
scriptPath: './5-0-0-cache-directory.js',
|
|
70
71
|
},
|
|
71
|
-
|
|
72
|
+
{
|
|
73
|
+
name: 'migration-key2',
|
|
72
74
|
version: '5.4.0',
|
|
73
75
|
description: 'Update project to use new cache directory',
|
|
74
|
-
|
|
76
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
75
77
|
},
|
|
76
|
-
|
|
78
|
+
]);
|
|
77
79
|
|
|
78
80
|
const fromVersion3 = '5.5.0';
|
|
79
81
|
const toVersion3 = '6.0.0';
|
|
80
|
-
const migrations3 = getMigrationsToRun(fromVersion3, toVersion3, migrationFixtures
|
|
81
|
-
expect(migrations3).toEqual(
|
|
82
|
-
|
|
82
|
+
const migrations3 = getMigrationsToRun(fromVersion3, toVersion3, migrationFixtures);
|
|
83
|
+
expect(migrations3).toEqual([
|
|
84
|
+
{
|
|
85
|
+
name: 'migration-key3',
|
|
83
86
|
version: '6.0.0',
|
|
84
87
|
description: 'Update project to use new cache directory',
|
|
85
|
-
|
|
88
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
86
89
|
},
|
|
87
|
-
|
|
90
|
+
]);
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
it('should sort migrations by version', () => {
|
|
91
94
|
const fromVersion = '2.0.0';
|
|
92
95
|
const toVersion = '6.0.0';
|
|
93
|
-
const migrations = getMigrationsToRun(fromVersion, toVersion,
|
|
94
|
-
|
|
96
|
+
const migrations = getMigrationsToRun(fromVersion, toVersion, [
|
|
97
|
+
{
|
|
98
|
+
name: 'migration-key1',
|
|
95
99
|
version: '5.3.0',
|
|
96
100
|
description: 'Update project to use new cache directory',
|
|
97
|
-
|
|
101
|
+
scriptPath: './5.3.0-migration.js',
|
|
98
102
|
},
|
|
99
|
-
|
|
103
|
+
{
|
|
104
|
+
name: 'migration-key2',
|
|
100
105
|
version: '2.3.0',
|
|
101
106
|
description: 'Update project to use new cache directory',
|
|
102
|
-
|
|
107
|
+
scriptPath: './2.3.0-migration.js',
|
|
103
108
|
},
|
|
104
|
-
|
|
109
|
+
{
|
|
110
|
+
name: 'migration-key3',
|
|
105
111
|
version: '2.0.0',
|
|
106
112
|
description: 'Update project to use new cache directory',
|
|
107
|
-
|
|
113
|
+
scriptPath: './2.0.0-migration.js',
|
|
108
114
|
},
|
|
109
|
-
|
|
115
|
+
{
|
|
116
|
+
name: 'migration-key4',
|
|
110
117
|
version: '2.0.0',
|
|
111
118
|
description: 'Update project to use new cache directory',
|
|
112
|
-
|
|
119
|
+
scriptPath: './2.0.0-migration.js',
|
|
113
120
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
expect(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const mockContext = new Context('/virtual');
|
|
123
|
-
const migrationFn = vi.fn().mockResolvedValue(mockContext);
|
|
124
|
-
|
|
125
|
-
vi.doMock('./test-migration.js', () => ({
|
|
126
|
-
default: migrationFn,
|
|
127
|
-
}));
|
|
128
|
-
|
|
129
|
-
const migration: MigrationMeta = {
|
|
130
|
-
version: '1.0.0',
|
|
131
|
-
description: 'test migration',
|
|
132
|
-
migrationScript: './test-migration.js',
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const result = await runMigration(migration, mockContext);
|
|
136
|
-
|
|
137
|
-
expect(migrationFn).toHaveBeenCalledWith(mockContext);
|
|
138
|
-
expect(result).toBe(mockContext);
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
expect(migrations.map((m) => m.name)).toEqual([
|
|
124
|
+
'migration-key3',
|
|
125
|
+
'migration-key4',
|
|
126
|
+
'migration-key2',
|
|
127
|
+
'migration-key1',
|
|
128
|
+
]);
|
|
139
129
|
});
|
|
140
130
|
});
|
|
141
131
|
|
|
@@ -144,25 +134,27 @@ describe('Migrations', () => {
|
|
|
144
134
|
const migrationTwoFn = vi.fn();
|
|
145
135
|
const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
146
136
|
|
|
147
|
-
vi.doMock('
|
|
137
|
+
vi.doMock('virtual-test-migration.js', async () => ({
|
|
148
138
|
default: migrationOneFn,
|
|
149
139
|
}));
|
|
150
|
-
vi.doMock('
|
|
140
|
+
vi.doMock('virtual-test-migration2.js', async () => ({
|
|
151
141
|
default: migrationTwoFn,
|
|
152
142
|
}));
|
|
153
143
|
|
|
154
|
-
const migrations:
|
|
155
|
-
|
|
144
|
+
const migrations: Migration[] = [
|
|
145
|
+
{
|
|
146
|
+
name: 'migration-one',
|
|
156
147
|
version: '1.0.0',
|
|
157
148
|
description: '...',
|
|
158
|
-
|
|
149
|
+
scriptPath: 'virtual-test-migration.js',
|
|
159
150
|
},
|
|
160
|
-
|
|
151
|
+
{
|
|
152
|
+
name: 'migration-two',
|
|
161
153
|
version: '1.2.0',
|
|
162
154
|
description: '...',
|
|
163
|
-
|
|
155
|
+
scriptPath: 'virtual-test-migration2.js',
|
|
164
156
|
},
|
|
165
|
-
|
|
157
|
+
];
|
|
166
158
|
|
|
167
159
|
beforeEach(() => {
|
|
168
160
|
migrationOneFn.mockImplementation(async (context: Context) => {
|
|
@@ -208,6 +200,7 @@ describe('Migrations', () => {
|
|
|
208
200
|
it('should commit the changes for each migration if the CLI arg is present', async () => {
|
|
209
201
|
await runMigrations(migrations, { commitEachMigration: true });
|
|
210
202
|
|
|
203
|
+
// 2 migration commits + 1 version update commit = 3 total
|
|
211
204
|
expect(gitCommitNoVerify).toHaveBeenCalledTimes(3);
|
|
212
205
|
});
|
|
213
206
|
|
|
@@ -216,6 +209,7 @@ describe('Migrations', () => {
|
|
|
216
209
|
|
|
217
210
|
await runMigrations(migrations, { commitEachMigration: true });
|
|
218
211
|
|
|
212
|
+
// 1 migration commit (only migration-one has changes) + 1 version update commit = 2 total
|
|
219
213
|
expect(gitCommitNoVerify).toHaveBeenCalledTimes(2);
|
|
220
214
|
});
|
|
221
215
|
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import defaultMigrations, {
|
|
2
|
-
import {
|
|
1
|
+
import defaultMigrations, { Migration } from './migrations.js';
|
|
2
|
+
import { runCodemod } from '../runner.js';
|
|
3
3
|
import { gte, satisfies } from 'semver';
|
|
4
|
-
import { migrationsDebug, printChanges } from './utils.js';
|
|
5
|
-
|
|
6
4
|
import { CURRENT_APP_VERSION } from '../../utils/utils.version.js';
|
|
7
|
-
import { Context } from '../context.js';
|
|
8
|
-
import type { MigrationModule } from '../types.js';
|
|
9
5
|
import { gitCommitNoVerify } from '../../utils/utils.git.js';
|
|
10
6
|
import { output } from '../../utils/utils.console.js';
|
|
11
7
|
import { setRootConfig } from '../../utils/utils.config.js';
|
|
@@ -13,59 +9,36 @@ import { setRootConfig } from '../../utils/utils.config.js';
|
|
|
13
9
|
export function getMigrationsToRun(
|
|
14
10
|
fromVersion: string,
|
|
15
11
|
toVersion: string,
|
|
16
|
-
migrations:
|
|
17
|
-
):
|
|
12
|
+
migrations: Migration[] = defaultMigrations
|
|
13
|
+
): Migration[] {
|
|
18
14
|
const semverRange = `${fromVersion} - ${toVersion}`;
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
return migrations
|
|
17
|
+
.filter((meta) => satisfies(meta.version, semverRange))
|
|
21
18
|
.sort((a, b) => {
|
|
22
|
-
return gte(a
|
|
23
|
-
})
|
|
24
|
-
.reduce<Record<string, MigrationMeta>>((acc, [key, meta]) => {
|
|
25
|
-
if (satisfies(meta.version, semverRange)) {
|
|
26
|
-
acc[key] = meta;
|
|
27
|
-
}
|
|
28
|
-
return acc;
|
|
29
|
-
}, {});
|
|
30
|
-
|
|
31
|
-
return migrationsToRun;
|
|
19
|
+
return gte(a.version, b.version) ? 1 : -1;
|
|
20
|
+
});
|
|
32
21
|
}
|
|
33
22
|
|
|
34
23
|
type RunMigrationsOptions = {
|
|
35
24
|
commitEachMigration?: boolean;
|
|
25
|
+
codemodOptions?: Record<string, any>;
|
|
36
26
|
};
|
|
37
27
|
|
|
38
|
-
export async function runMigrations(migrations:
|
|
39
|
-
const
|
|
40
|
-
const migrationList = Object.entries(migrations).map(
|
|
41
|
-
([key, migrationMeta]) => `${key} (${migrationMeta.description})`
|
|
42
|
-
);
|
|
28
|
+
export async function runMigrations(migrations: Migration[], options: RunMigrationsOptions = {}) {
|
|
29
|
+
const migrationList = migrations.map((meta) => `${meta.name} (${meta.description})`);
|
|
43
30
|
|
|
44
31
|
const migrationListBody = migrationList.length > 0 ? output.bulletList(migrationList) : ['No migrations to run.'];
|
|
45
32
|
|
|
46
33
|
output.log({ title: 'Running the following migrations:', body: migrationListBody });
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
migrationsDebug(`context for "${key} (${migration.migrationScript})":`);
|
|
54
|
-
migrationsDebug('%O', context.listChanges());
|
|
55
|
-
|
|
56
|
-
await formatFiles(context);
|
|
57
|
-
flushChanges(context);
|
|
58
|
-
printChanges(context, key, migration);
|
|
35
|
+
// run migrations sequentially in version order where lowest version runs first
|
|
36
|
+
for (const migration of migrations) {
|
|
37
|
+
const context = await runCodemod(migration, options.codemodOptions);
|
|
38
|
+
const shouldCommit = options.commitEachMigration && context.hasChanges();
|
|
59
39
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (shouldCommit) {
|
|
63
|
-
await gitCommitNoVerify(`chore: run create-plugin migration - ${key} (${migration.migrationScript})`);
|
|
64
|
-
}
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error instanceof Error) {
|
|
67
|
-
throw new Error(`Error running migration "${key} (${migration.migrationScript})": ${error.message}`);
|
|
68
|
-
}
|
|
40
|
+
if (shouldCommit) {
|
|
41
|
+
await gitCommitNoVerify(`chore: run create-plugin migration - ${migration.name} (${migration.scriptPath})`);
|
|
69
42
|
}
|
|
70
43
|
}
|
|
71
44
|
|
|
@@ -75,9 +48,3 @@ export async function runMigrations(migrations: Record<string, MigrationMeta>, o
|
|
|
75
48
|
await gitCommitNoVerify(`chore: update .config/.cprc.json to version ${CURRENT_APP_VERSION}.`);
|
|
76
49
|
}
|
|
77
50
|
}
|
|
78
|
-
|
|
79
|
-
export async function runMigration(migration: MigrationMeta, context: Context): Promise<Context> {
|
|
80
|
-
const module = (await import(migration.migrationScript)) as MigrationModule;
|
|
81
|
-
|
|
82
|
-
return module.default(context);
|
|
83
|
-
}
|