@grafana/create-plugin 6.2.0-canary.2314.19505018775.0 → 6.2.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 +12 -0
- package/dist/bin/run.js +3 -1
- package/dist/codemods/additions/additions.js +9 -0
- package/dist/codemods/additions/scripts/example-addition.js +47 -0
- package/dist/{migrations → codemods}/context.js +3 -2
- package/dist/codemods/migrations/manager.js +32 -0
- package/dist/codemods/migrations/migrations.js +50 -0
- package/dist/{migrations → codemods/migrations}/scripts/003-update-eslint-deprecation-rule.js +1 -1
- package/dist/{migrations → codemods/migrations}/scripts/004-eslint9-flat-config.js +1 -1
- package/dist/{migrations → codemods/migrations}/scripts/005-react-18-3.js +1 -1
- package/dist/{migrations → codemods/migrations}/scripts/007-remove-testing-library-types.js +1 -1
- package/dist/codemods/runner.js +33 -0
- package/dist/codemods/schema-parser.js +20 -0
- package/dist/{migrations → codemods}/utils.js +5 -16
- package/dist/commands/add.command.js +51 -0
- package/dist/commands/update.command.js +8 -42
- package/dist/utils/utils.checks.js +40 -0
- package/package.json +3 -2
- package/src/bin/run.ts +2 -1
- package/src/codemods/additions/additions.test.ts +17 -0
- package/src/codemods/additions/additions.ts +9 -0
- package/src/codemods/additions/scripts/example-addition.test.ts +92 -0
- package/src/codemods/additions/scripts/example-addition.ts +62 -0
- package/src/{migrations → codemods}/context.test.ts +10 -10
- package/src/{migrations → codemods}/context.ts +4 -2
- package/src/codemods/migrations/fixtures/migrations.ts +20 -0
- package/src/{migrations → codemods/migrations}/manager.test.ts +76 -77
- package/src/codemods/migrations/manager.ts +50 -0
- package/src/codemods/migrations/migrations.test.ts +17 -0
- package/src/codemods/migrations/migrations.ts +55 -0
- package/src/{migrations → codemods/migrations}/scripts/001-update-grafana-compose-extend.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/001-update-grafana-compose-extend.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/002-update-is-compatible-workflow.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/002-update-is-compatible-workflow.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/003-update-eslint-deprecation-rule.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/003-update-eslint-deprecation-rule.ts +2 -2
- package/src/{migrations → codemods/migrations}/scripts/004-eslint9-flat-config.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/004-eslint9-flat-config.ts +2 -2
- package/src/{migrations → codemods/migrations}/scripts/005-react-18-3.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/005-react-18-3.ts +2 -2
- package/src/{migrations → codemods/migrations}/scripts/006-webpack-nested-fix.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/006-webpack-nested-fix.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/007-remove-testing-library-types.test.ts +1 -1
- package/src/{migrations → codemods/migrations}/scripts/007-remove-testing-library-types.ts +2 -2
- package/src/codemods/runner.ts +51 -0
- package/src/codemods/schema-parser.ts +27 -0
- package/src/codemods/types.ts +16 -0
- package/src/{migrations → codemods}/utils.test.ts +5 -4
- package/src/{migrations → codemods}/utils.ts +8 -22
- package/src/commands/add.command.ts +56 -0
- package/src/commands/index.ts +1 -0
- package/src/commands/update.command.ts +9 -48
- package/src/utils/utils.checks.ts +47 -0
- package/dist/migrations/manager.js +0 -58
- package/dist/migrations/migrations.js +0 -43
- package/dist/migrations/scripts/example-migration.js +0 -25
- package/src/migrations/fixtures/migrations.ts +0 -19
- package/src/migrations/manager.ts +0 -82
- package/src/migrations/migrations.test.ts +0 -16
- package/src/migrations/migrations.ts +0 -55
- package/src/migrations/scripts/example-migration.test.ts +0 -40
- package/src/migrations/scripts/example-migration.ts +0 -34
- package/templates/panel/.config/AGENTS/fundamentals.md +0 -81
- package/templates/panel/.config/AGENTS/howto/add-panel-options.md +0 -130
- package/templates/panel/AGENTS.md +0 -3
- /package/dist/{migrations → codemods/migrations}/scripts/001-update-grafana-compose-extend.js +0 -0
- /package/dist/{migrations → codemods/migrations}/scripts/002-update-is-compatible-workflow.js +0 -0
- /package/dist/{migrations → codemods/migrations}/scripts/006-webpack-nested-fix.js +0 -0
- /package/src/{migrations → codemods/migrations}/fixtures/foo/bar.ts +0 -0
- /package/src/{migrations → codemods/migrations}/fixtures/foo/baz.ts +0 -0
- /package/src/{migrations → codemods}/test-utils.ts +0 -0
|
@@ -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['@types/node']).toBe('^20.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, {}, { '@types/node': '^20.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
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Context } from './context.js';
|
|
|
3
3
|
describe('Context', () => {
|
|
4
4
|
describe('getFile', () => {
|
|
5
5
|
it('should read a file from the file system', () => {
|
|
6
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
6
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
7
7
|
const content = context.getFile('foo/bar.ts');
|
|
8
8
|
expect(content).toEqual("console.log('foo/bar.ts');\n");
|
|
9
9
|
});
|
|
@@ -16,14 +16,14 @@ describe('Context', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it('should get a file that was updated in the current context', () => {
|
|
19
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
19
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
20
20
|
context.updateFile('foo/bar.ts', 'content');
|
|
21
21
|
const content = context.getFile('foo/bar.ts');
|
|
22
22
|
expect(content).toEqual('content');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should not return a file that was marked for deletion', () => {
|
|
26
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
26
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
27
27
|
context.deleteFile('foo/bar.ts');
|
|
28
28
|
const content = context.getFile('foo/bar.ts');
|
|
29
29
|
expect(content).toEqual(undefined);
|
|
@@ -77,7 +77,7 @@ describe('Context', () => {
|
|
|
77
77
|
|
|
78
78
|
describe('renameFile', () => {
|
|
79
79
|
it('should rename a file', () => {
|
|
80
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
80
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
81
81
|
context.renameFile('foo/bar.ts', 'new-file.txt');
|
|
82
82
|
expect(context.listChanges()).toEqual({
|
|
83
83
|
'new-file.txt': { content: "console.log('foo/bar.ts');\n", changeType: 'add' },
|
|
@@ -102,20 +102,20 @@ describe('Context', () => {
|
|
|
102
102
|
|
|
103
103
|
describe('readDir', () => {
|
|
104
104
|
it('should read the directory', () => {
|
|
105
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
105
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
106
106
|
const files = context.readDir('foo');
|
|
107
107
|
expect(files).toEqual(['foo/bar.ts', 'foo/baz.ts']);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it('should filter out deleted files', () => {
|
|
111
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
111
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
112
112
|
context.deleteFile('foo/bar.ts');
|
|
113
113
|
const files = context.readDir('foo');
|
|
114
114
|
expect(files).toEqual(['foo/baz.ts']);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it('should include files that are only added to the context', () => {
|
|
118
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
118
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
119
119
|
context.addFile('foo/foo.txt', '');
|
|
120
120
|
const files = context.readDir('foo');
|
|
121
121
|
expect(files).toEqual(['foo/bar.ts', 'foo/baz.ts', 'foo/foo.txt']);
|
|
@@ -124,7 +124,7 @@ describe('Context', () => {
|
|
|
124
124
|
|
|
125
125
|
describe('normalisePath', () => {
|
|
126
126
|
it('should normalise the path', () => {
|
|
127
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
127
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
128
128
|
expect(context.normalisePath('foo/bar.ts')).toEqual('foo/bar.ts');
|
|
129
129
|
expect(context.normalisePath('./foo/bar.ts')).toEqual('foo/bar.ts');
|
|
130
130
|
expect(context.normalisePath('/foo/bar.ts')).toEqual('foo/bar.ts');
|
|
@@ -133,12 +133,12 @@ describe('Context', () => {
|
|
|
133
133
|
|
|
134
134
|
describe('hasChanges', () => {
|
|
135
135
|
it('should return FALSE if the context has no changes', () => {
|
|
136
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
136
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
137
137
|
expect(context.hasChanges()).toEqual(false);
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it('should return TRUE if the context has changes', () => {
|
|
141
|
-
const context = new Context(`${__dirname}/fixtures`);
|
|
141
|
+
const context = new Context(`${__dirname}/migrations/fixtures`);
|
|
142
142
|
|
|
143
143
|
context.addFile('foo.ts', '');
|
|
144
144
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { constants, accessSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { relative, normalize, join, dirname } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { debug } from '../utils/utils.cli.js';
|
|
4
|
+
|
|
5
|
+
const codemodsDebug = debug.extend('codemods');
|
|
4
6
|
|
|
5
7
|
export type ContextFile = Record<
|
|
6
8
|
string,
|
|
@@ -58,7 +60,7 @@ export class Context {
|
|
|
58
60
|
if (originalContent !== content) {
|
|
59
61
|
this.files[path] = { content, changeType: 'update' };
|
|
60
62
|
} else {
|
|
61
|
-
|
|
63
|
+
codemodsDebug(`Context.updateFile() - no updates for ${filePath}`);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -0,0 +1,20 @@
|
|
|
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',
|
|
7
|
+
},
|
|
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,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getMigrationsToRun,
|
|
1
|
+
import { flushChanges, formatFiles, printChanges } from '../utils.js';
|
|
2
|
+
import { getMigrationsToRun, runMigrations } from './manager.js';
|
|
3
|
+
|
|
4
|
+
import { Context } from '../context.js';
|
|
5
|
+
import { Migration } from './migrations.js';
|
|
6
|
+
import { gitCommitNoVerify } from '../../utils/utils.git.js';
|
|
3
7
|
import migrationFixtures from './fixtures/migrations.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
8
|
+
import { setRootConfig } from '../../utils/utils.config.js';
|
|
9
|
+
import { vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
vi.mock('../utils.js', async (importOriginal) => {
|
|
12
|
+
const actual: typeof import('../utils.js') = await importOriginal();
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
flushChanges: vi.fn(),
|
|
16
|
+
formatFiles: vi.fn(),
|
|
17
|
+
installNPMDependencies: vi.fn(),
|
|
18
|
+
printChanges: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
17
21
|
|
|
18
22
|
// Silence terminal output during tests.
|
|
19
|
-
vi.mock('
|
|
23
|
+
vi.mock('../../utils/utils.console.js', () => ({
|
|
20
24
|
output: {
|
|
21
25
|
log: vi.fn(),
|
|
22
26
|
addHorizontalLine: vi.fn(),
|
|
@@ -25,10 +29,10 @@ vi.mock('../utils/utils.console.js', () => ({
|
|
|
25
29
|
},
|
|
26
30
|
}));
|
|
27
31
|
|
|
28
|
-
vi.mock('
|
|
32
|
+
vi.mock('../../utils/utils.config.js', () => ({
|
|
29
33
|
setRootConfig: vi.fn(),
|
|
30
34
|
}));
|
|
31
|
-
vi.mock('
|
|
35
|
+
vi.mock('../../utils/utils.git.js', () => ({
|
|
32
36
|
gitCommitNoVerify: vi.fn(),
|
|
33
37
|
}));
|
|
34
38
|
|
|
@@ -45,92 +49,83 @@ describe('Migrations', () => {
|
|
|
45
49
|
it('should return the migrations that need to be run', () => {
|
|
46
50
|
const fromVersion = '3.0.0';
|
|
47
51
|
const toVersion = '5.0.0';
|
|
48
|
-
const migrations = getMigrationsToRun(fromVersion, toVersion, migrationFixtures
|
|
49
|
-
expect(migrations).toEqual(
|
|
50
|
-
|
|
52
|
+
const migrations = getMigrationsToRun(fromVersion, toVersion, migrationFixtures);
|
|
53
|
+
expect(migrations).toEqual([
|
|
54
|
+
{
|
|
55
|
+
name: 'migration-key1',
|
|
51
56
|
version: '5.0.0',
|
|
52
57
|
description: 'Update project to use new cache directory',
|
|
53
|
-
|
|
58
|
+
scriptPath: './5-0-0-cache-directory.js',
|
|
54
59
|
},
|
|
55
|
-
|
|
60
|
+
]);
|
|
56
61
|
|
|
57
62
|
const fromVersion2 = '5.0.0';
|
|
58
63
|
const toVersion2 = '5.5.0';
|
|
59
|
-
const migrations2 = getMigrationsToRun(fromVersion2, toVersion2, migrationFixtures
|
|
60
|
-
expect(migrations2).toEqual(
|
|
61
|
-
|
|
64
|
+
const migrations2 = getMigrationsToRun(fromVersion2, toVersion2, migrationFixtures);
|
|
65
|
+
expect(migrations2).toEqual([
|
|
66
|
+
{
|
|
67
|
+
name: 'migration-key1',
|
|
62
68
|
version: '5.0.0',
|
|
63
69
|
description: 'Update project to use new cache directory',
|
|
64
|
-
|
|
70
|
+
scriptPath: './5-0-0-cache-directory.js',
|
|
65
71
|
},
|
|
66
|
-
|
|
72
|
+
{
|
|
73
|
+
name: 'migration-key2',
|
|
67
74
|
version: '5.4.0',
|
|
68
75
|
description: 'Update project to use new cache directory',
|
|
69
|
-
|
|
76
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
70
77
|
},
|
|
71
|
-
|
|
78
|
+
]);
|
|
72
79
|
|
|
73
80
|
const fromVersion3 = '5.5.0';
|
|
74
81
|
const toVersion3 = '6.0.0';
|
|
75
|
-
const migrations3 = getMigrationsToRun(fromVersion3, toVersion3, migrationFixtures
|
|
76
|
-
expect(migrations3).toEqual(
|
|
77
|
-
|
|
82
|
+
const migrations3 = getMigrationsToRun(fromVersion3, toVersion3, migrationFixtures);
|
|
83
|
+
expect(migrations3).toEqual([
|
|
84
|
+
{
|
|
85
|
+
name: 'migration-key3',
|
|
78
86
|
version: '6.0.0',
|
|
79
87
|
description: 'Update project to use new cache directory',
|
|
80
|
-
|
|
88
|
+
scriptPath: './5-4-0-cache-directory.js',
|
|
81
89
|
},
|
|
82
|
-
|
|
90
|
+
]);
|
|
83
91
|
});
|
|
84
92
|
|
|
85
93
|
it('should sort migrations by version', () => {
|
|
86
94
|
const fromVersion = '2.0.0';
|
|
87
95
|
const toVersion = '6.0.0';
|
|
88
|
-
const migrations = getMigrationsToRun(fromVersion, toVersion,
|
|
89
|
-
|
|
96
|
+
const migrations = getMigrationsToRun(fromVersion, toVersion, [
|
|
97
|
+
{
|
|
98
|
+
name: 'migration-key1',
|
|
90
99
|
version: '5.3.0',
|
|
91
100
|
description: 'Update project to use new cache directory',
|
|
92
|
-
|
|
101
|
+
scriptPath: './5.3.0-migration.js',
|
|
93
102
|
},
|
|
94
|
-
|
|
103
|
+
{
|
|
104
|
+
name: 'migration-key2',
|
|
95
105
|
version: '2.3.0',
|
|
96
106
|
description: 'Update project to use new cache directory',
|
|
97
|
-
|
|
107
|
+
scriptPath: './2.3.0-migration.js',
|
|
98
108
|
},
|
|
99
|
-
|
|
109
|
+
{
|
|
110
|
+
name: 'migration-key3',
|
|
100
111
|
version: '2.0.0',
|
|
101
112
|
description: 'Update project to use new cache directory',
|
|
102
|
-
|
|
113
|
+
scriptPath: './2.0.0-migration.js',
|
|
103
114
|
},
|
|
104
|
-
|
|
115
|
+
{
|
|
116
|
+
name: 'migration-key4',
|
|
105
117
|
version: '2.0.0',
|
|
106
118
|
description: 'Update project to use new cache directory',
|
|
107
|
-
|
|
119
|
+
scriptPath: './2.0.0-migration.js',
|
|
108
120
|
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
expect(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const mockContext = new Context('/virtual');
|
|
118
|
-
const migrationFn = vi.fn().mockResolvedValue(mockContext);
|
|
119
|
-
|
|
120
|
-
vi.doMock('virtual-test-migration.js', () => ({
|
|
121
|
-
default: migrationFn,
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
const migration: MigrationMeta = {
|
|
125
|
-
version: '1.0.0',
|
|
126
|
-
description: 'test migration',
|
|
127
|
-
migrationScript: 'virtual-test-migration.js',
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const result = await runMigration(migration, mockContext);
|
|
131
|
-
|
|
132
|
-
expect(migrationFn).toHaveBeenCalledWith(mockContext);
|
|
133
|
-
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
|
+
]);
|
|
134
129
|
});
|
|
135
130
|
});
|
|
136
131
|
|
|
@@ -146,18 +141,20 @@ describe('Migrations', () => {
|
|
|
146
141
|
default: migrationTwoFn,
|
|
147
142
|
}));
|
|
148
143
|
|
|
149
|
-
const migrations:
|
|
150
|
-
|
|
144
|
+
const migrations: Migration[] = [
|
|
145
|
+
{
|
|
146
|
+
name: 'migration-one',
|
|
151
147
|
version: '1.0.0',
|
|
152
148
|
description: '...',
|
|
153
|
-
|
|
149
|
+
scriptPath: 'virtual-test-migration.js',
|
|
154
150
|
},
|
|
155
|
-
|
|
151
|
+
{
|
|
152
|
+
name: 'migration-two',
|
|
156
153
|
version: '1.2.0',
|
|
157
154
|
description: '...',
|
|
158
|
-
|
|
155
|
+
scriptPath: 'virtual-test-migration2.js',
|
|
159
156
|
},
|
|
160
|
-
|
|
157
|
+
];
|
|
161
158
|
|
|
162
159
|
beforeEach(() => {
|
|
163
160
|
migrationOneFn.mockImplementation(async (context: Context) => {
|
|
@@ -203,7 +200,8 @@ describe('Migrations', () => {
|
|
|
203
200
|
it('should commit the changes for each migration if the CLI arg is present', async () => {
|
|
204
201
|
await runMigrations(migrations, { commitEachMigration: true });
|
|
205
202
|
|
|
206
|
-
|
|
203
|
+
// 2 migration commits + 1 version update commit = 3 total
|
|
204
|
+
expect(gitCommitNoVerify).toHaveBeenCalledTimes(3);
|
|
207
205
|
});
|
|
208
206
|
|
|
209
207
|
it('should not create a commit for a migration that has no changes', async () => {
|
|
@@ -211,7 +209,8 @@ describe('Migrations', () => {
|
|
|
211
209
|
|
|
212
210
|
await runMigrations(migrations, { commitEachMigration: true });
|
|
213
211
|
|
|
214
|
-
|
|
212
|
+
// 1 migration commit (only migration-one has changes) + 1 version update commit = 2 total
|
|
213
|
+
expect(gitCommitNoVerify).toHaveBeenCalledTimes(2);
|
|
215
214
|
});
|
|
216
215
|
|
|
217
216
|
it('should update version in ".config/.cprc.json" on a successful update', async () => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import defaultMigrations, { Migration } from './migrations.js';
|
|
2
|
+
import { runCodemod } from '../runner.js';
|
|
3
|
+
import { gte, satisfies } from 'semver';
|
|
4
|
+
import { CURRENT_APP_VERSION } from '../../utils/utils.version.js';
|
|
5
|
+
import { gitCommitNoVerify } from '../../utils/utils.git.js';
|
|
6
|
+
import { output } from '../../utils/utils.console.js';
|
|
7
|
+
import { setRootConfig } from '../../utils/utils.config.js';
|
|
8
|
+
|
|
9
|
+
export function getMigrationsToRun(
|
|
10
|
+
fromVersion: string,
|
|
11
|
+
toVersion: string,
|
|
12
|
+
migrations: Migration[] = defaultMigrations
|
|
13
|
+
): Migration[] {
|
|
14
|
+
const semverRange = `${fromVersion} - ${toVersion}`;
|
|
15
|
+
|
|
16
|
+
return migrations
|
|
17
|
+
.filter((meta) => satisfies(meta.version, semverRange))
|
|
18
|
+
.sort((a, b) => {
|
|
19
|
+
return gte(a.version, b.version) ? 1 : -1;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type RunMigrationsOptions = {
|
|
24
|
+
commitEachMigration?: boolean;
|
|
25
|
+
codemodOptions?: Record<string, any>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function runMigrations(migrations: Migration[], options: RunMigrationsOptions = {}) {
|
|
29
|
+
const migrationList = migrations.map((meta) => `${meta.name} (${meta.description})`);
|
|
30
|
+
|
|
31
|
+
const migrationListBody = migrationList.length > 0 ? output.bulletList(migrationList) : ['No migrations to run.'];
|
|
32
|
+
|
|
33
|
+
output.log({ title: 'Running the following migrations:', body: migrationListBody });
|
|
34
|
+
|
|
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();
|
|
39
|
+
|
|
40
|
+
if (shouldCommit) {
|
|
41
|
+
await gitCommitNoVerify(`chore: run create-plugin migration - ${migration.name} (${migration.scriptPath})`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setRootConfig({ version: CURRENT_APP_VERSION });
|
|
46
|
+
|
|
47
|
+
if (options.commitEachMigration) {
|
|
48
|
+
await gitCommitNoVerify(`chore: update .config/.cprc.json to version ${CURRENT_APP_VERSION}.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import defaultMigrations from './migrations.js';
|
|
4
|
+
|
|
5
|
+
describe('migrations json', () => {
|
|
6
|
+
// As migration scripts are imported dynamically when update is run we assert the path is valid
|
|
7
|
+
// Vitest 4 reimplemented its workers, which caused the previous dynamic import tests to fail.
|
|
8
|
+
// This test now only asserts that the migration script source file exists.
|
|
9
|
+
defaultMigrations.forEach((migration) => {
|
|
10
|
+
it(`should have a valid migration script path for ${migration.name}`, () => {
|
|
11
|
+
// import.meta.resolve() returns a file:// URL, convert to path
|
|
12
|
+
const filePath = fileURLToPath(migration.scriptPath);
|
|
13
|
+
const sourceFilePath = filePath.replace('.js', '.ts');
|
|
14
|
+
expect(existsSync(sourceFilePath)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LEGACY_UPDATE_CUTOFF_VERSION } from '../../constants.js';
|
|
2
|
+
import { Codemod } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export interface Migration extends Codemod {
|
|
5
|
+
version: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
{
|
|
10
|
+
name: '001-update-grafana-compose-extend',
|
|
11
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
12
|
+
description: 'Update ./docker-compose.yaml to extend from ./.config/docker-compose-base.yaml.',
|
|
13
|
+
scriptPath: import.meta.resolve('./scripts/001-update-grafana-compose-extend.js'),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: '002-update-is-compatible-workflow',
|
|
17
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
18
|
+
description:
|
|
19
|
+
'Update ./.github/workflows/is-compatible.yml to use is-compatible github action instead of calling levitate directly',
|
|
20
|
+
scriptPath: import.meta.resolve('./scripts/002-update-is-compatible-workflow.js'),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: '003-update-eslint-deprecation-rule',
|
|
24
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
25
|
+
description: 'Replace deprecated eslint-plugin-deprecation with @typescript-eslint/no-deprecated rule.',
|
|
26
|
+
scriptPath: import.meta.resolve('./scripts/003-update-eslint-deprecation-rule.js'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: '004-eslint9-flat-config',
|
|
30
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
31
|
+
description: 'Migrate eslint config to flat config format and update devDependencies to latest versions.',
|
|
32
|
+
scriptPath: import.meta.resolve('./scripts/004-eslint9-flat-config.js'),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: '005-react-18-3',
|
|
36
|
+
version: '6.1.9',
|
|
37
|
+
description: 'Update React and ReactDOM 18.x versions to ^18.3.0 to surface React 19 compatibility issues.',
|
|
38
|
+
scriptPath: import.meta.resolve('./scripts/005-react-18-3.js'),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: '006-webpack-nested-fix',
|
|
42
|
+
version: '6.1.11',
|
|
43
|
+
description: 'Fix webpack variable replacement in nested plugins files.',
|
|
44
|
+
scriptPath: import.meta.resolve('./scripts/006-webpack-nested-fix.js'),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: '007-remove-testing-library-types',
|
|
48
|
+
version: '6.1.13',
|
|
49
|
+
description:
|
|
50
|
+
'Add setupTests.d.ts for @testing-library/jest-dom types and remove @types/testing-library__jest-dom npm package.',
|
|
51
|
+
scriptPath: import.meta.resolve('./scripts/007-remove-testing-library-types.js'),
|
|
52
|
+
},
|
|
53
|
+
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
54
|
+
// for those written before the switch to updates as migrations.
|
|
55
|
+
] satisfies Migration[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { type Context } from '
|
|
2
|
+
import { type Context } from '../../context.js';
|
|
3
3
|
import { Node, Pair, parseDocument, Scalar, stringify, visit, YAMLMap, Document, YAMLSeq, visitorFn } from 'yaml';
|
|
4
4
|
|
|
5
5
|
export default async function migrate(context: Context) {
|