@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
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import defaultMigrations from './migrations.js';
|
|
2
3
|
|
|
3
4
|
describe('migrations json', () => {
|
|
4
5
|
// As migration scripts are imported dynamically when update is run we assert the path is valid
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// Vitest 4 reimplemented its workers, which caused the previous dynamic import tests to fail.
|
|
7
|
+
// This test now only asserts that the migration script source file exists.
|
|
8
|
+
defaultMigrations.forEach((migration) => {
|
|
9
|
+
it(`should have a valid migration script path for ${migration.name}`, () => {
|
|
10
|
+
// ensure migration script exists
|
|
11
|
+
const sourceFilePath = migration.scriptPath.replace('.js', '.ts');
|
|
12
|
+
expect(existsSync(sourceFilePath)).toBe(true);
|
|
10
13
|
});
|
|
11
14
|
});
|
|
12
15
|
});
|
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
import { LEGACY_UPDATE_CUTOFF_VERSION } from '../../constants.js';
|
|
2
|
+
import { Codemod } from '../types.js';
|
|
3
|
+
import { resolveScriptPath } from '../utils.js';
|
|
2
4
|
|
|
3
|
-
export
|
|
5
|
+
export interface Migration extends Codemod {
|
|
4
6
|
version: string;
|
|
5
|
-
|
|
6
|
-
migrationScript: string;
|
|
7
|
-
};
|
|
7
|
+
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
export default [
|
|
10
|
+
{
|
|
11
|
+
name: '001-update-grafana-compose-extend',
|
|
12
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
13
|
+
description: 'Update ./docker-compose.yaml to extend from ./.config/docker-compose-base.yaml.',
|
|
14
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/001-update-grafana-compose-extend.js'),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: '002-update-is-compatible-workflow',
|
|
18
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
19
|
+
description:
|
|
20
|
+
'Update ./.github/workflows/is-compatible.yml to use is-compatible github action instead of calling levitate directly',
|
|
21
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/002-update-is-compatible-workflow.js'),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: '003-update-eslint-deprecation-rule',
|
|
25
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
26
|
+
description: 'Replace deprecated eslint-plugin-deprecation with @typescript-eslint/no-deprecated rule.',
|
|
27
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/003-update-eslint-deprecation-rule.js'),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: '004-eslint9-flat-config',
|
|
31
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
32
|
+
description: 'Migrate eslint config to flat config format and update devDependencies to latest versions.',
|
|
33
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/004-eslint9-flat-config.js'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: '005-react-18-3',
|
|
37
|
+
version: '6.1.9',
|
|
38
|
+
description: 'Update React and ReactDOM 18.x versions to ^18.3.0 to surface React 19 compatibility issues.',
|
|
39
|
+
scriptPath: resolveScriptPath(import.meta.url, './scripts/005-react-18-3.js'),
|
|
38
40
|
},
|
|
39
|
-
|
|
41
|
+
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
|
|
42
|
+
// for those written before the switch to updates as migrations.
|
|
43
|
+
] satisfies Migration[];
|
|
@@ -7,7 +7,7 @@ import { dirname, relative, resolve } from 'node:path';
|
|
|
7
7
|
import * as recast from 'recast';
|
|
8
8
|
import type { Context } from '../../context.js';
|
|
9
9
|
import { addDependenciesToPackageJson } from '../../utils.js';
|
|
10
|
-
|
|
10
|
+
// migrationsDebug removed - was from deleted migrations/utils.js
|
|
11
11
|
|
|
12
12
|
type Imports = Map<string, { name?: string; bindings?: string[] }>;
|
|
13
13
|
|
|
@@ -407,7 +407,7 @@ function getIgnorePaths(context: Context): string[] {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
} catch (error) {
|
|
410
|
-
|
|
410
|
+
console.log('Error parsing package.json: %s', error);
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Context } from '../../context.js';
|
|
3
|
+
import migrate from './005-react-18-3.js';
|
|
4
|
+
|
|
5
|
+
describe('005-react-18-3', () => {
|
|
6
|
+
it('should not modify anything if package.json does not exist', async () => {
|
|
7
|
+
const context = new Context('/virtual');
|
|
8
|
+
await migrate(context);
|
|
9
|
+
expect(context.listChanges()).toEqual({});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should not modify anything if there are no React dependencies', async () => {
|
|
13
|
+
const context = new Context('/virtual');
|
|
14
|
+
context.addFile(
|
|
15
|
+
'./package.json',
|
|
16
|
+
JSON.stringify({
|
|
17
|
+
dependencies: {
|
|
18
|
+
lodash: '^4.17.21',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
const initialChanges = context.listChanges();
|
|
23
|
+
await migrate(context);
|
|
24
|
+
expect(context.listChanges()).toEqual(initialChanges);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should not update React if version is below 18.0.0', async () => {
|
|
28
|
+
const context = new Context('/virtual');
|
|
29
|
+
const packageJson = {
|
|
30
|
+
dependencies: {
|
|
31
|
+
react: '^17.0.2',
|
|
32
|
+
'react-dom': '^17.0.2',
|
|
33
|
+
},
|
|
34
|
+
devDependencies: {
|
|
35
|
+
'@types/react': '^17.0.0',
|
|
36
|
+
'@types/react-dom': '^17.0.0',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
context.addFile('./package.json', JSON.stringify(packageJson, null, 2));
|
|
40
|
+
const initialPackageJson = context.getFile('./package.json');
|
|
41
|
+
|
|
42
|
+
await migrate(context);
|
|
43
|
+
|
|
44
|
+
expect(context.getFile('./package.json')).toBe(initialPackageJson);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should update React 18.0.0 to ^18.3.0', async () => {
|
|
48
|
+
const context = new Context('/virtual');
|
|
49
|
+
context.addFile(
|
|
50
|
+
'./package.json',
|
|
51
|
+
JSON.stringify({
|
|
52
|
+
dependencies: {
|
|
53
|
+
react: '^18.0.0',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await migrate(context);
|
|
59
|
+
|
|
60
|
+
const updatedPackageJson = JSON.parse(context.getFile('./package.json') || '{}');
|
|
61
|
+
expect(updatedPackageJson.dependencies.react).toBe('^18.3.0');
|
|
62
|
+
expect(updatedPackageJson.devDependencies?.['@types/react']).toBe('^18.3.0');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should not update React if version is already 18.3.0 or higher', async () => {
|
|
66
|
+
const context = new Context('/virtual');
|
|
67
|
+
const packageJson = {
|
|
68
|
+
dependencies: {
|
|
69
|
+
react: '^18.3.0',
|
|
70
|
+
'react-dom': '^18.3.0',
|
|
71
|
+
},
|
|
72
|
+
devDependencies: {
|
|
73
|
+
'@types/react': '^18.3.0',
|
|
74
|
+
'@types/react-dom': '^18.3.0',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
context.addFile('./package.json', JSON.stringify(packageJson, null, 2));
|
|
78
|
+
|
|
79
|
+
await migrate(context);
|
|
80
|
+
|
|
81
|
+
const updatedPackageJson = JSON.parse(context.getFile('./package.json') || '{}');
|
|
82
|
+
expect(updatedPackageJson.dependencies.react).toBe('^18.3.0');
|
|
83
|
+
expect(updatedPackageJson.dependencies['react-dom']).toBe('^18.3.0');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should not downgrade React 19.0.0 to 18.3.0', async () => {
|
|
87
|
+
const context = new Context('/virtual');
|
|
88
|
+
const packageJson = {
|
|
89
|
+
dependencies: {
|
|
90
|
+
react: '^19.0.0',
|
|
91
|
+
'react-dom': '^19.0.0',
|
|
92
|
+
},
|
|
93
|
+
devDependencies: {
|
|
94
|
+
'@types/react': '^19.0.0',
|
|
95
|
+
'@types/react-dom': '^19.0.0',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
context.addFile('./package.json', JSON.stringify(packageJson, null, 2));
|
|
99
|
+
|
|
100
|
+
await migrate(context);
|
|
101
|
+
|
|
102
|
+
const updatedPackageJson = JSON.parse(context.getFile('./package.json') || '{}');
|
|
103
|
+
expect(updatedPackageJson.dependencies.react).toBe('^19.0.0');
|
|
104
|
+
expect(updatedPackageJson.dependencies['react-dom']).toBe('^19.0.0');
|
|
105
|
+
expect(updatedPackageJson.devDependencies['@types/react']).toBe('^19.0.0');
|
|
106
|
+
expect(updatedPackageJson.devDependencies['@types/react-dom']).toBe('^19.0.0');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle version ranges correctly', async () => {
|
|
110
|
+
const context = new Context('/virtual');
|
|
111
|
+
context.addFile(
|
|
112
|
+
'./package.json',
|
|
113
|
+
JSON.stringify({
|
|
114
|
+
dependencies: {
|
|
115
|
+
react: '~18.1.0',
|
|
116
|
+
'react-dom': '18.2.0',
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await migrate(context);
|
|
122
|
+
|
|
123
|
+
const updatedPackageJson = JSON.parse(context.getFile('./package.json') || '{}');
|
|
124
|
+
expect(updatedPackageJson.dependencies.react).toBe('^18.3.0');
|
|
125
|
+
expect(updatedPackageJson.dependencies['react-dom']).toBe('^18.3.0');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should be idempotent', async () => {
|
|
129
|
+
const context = new Context('/virtual');
|
|
130
|
+
context.addFile(
|
|
131
|
+
'./package.json',
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
dependencies: {
|
|
134
|
+
react: '^18.2.0',
|
|
135
|
+
'react-dom': '^18.2.0',
|
|
136
|
+
},
|
|
137
|
+
devDependencies: {
|
|
138
|
+
'@types/react': '^18.2.0',
|
|
139
|
+
'@types/react-dom': '^18.2.0',
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
await expect(migrate).toBeIdempotent(context);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Context } from '../../context.js';
|
|
2
|
+
import { addDependenciesToPackageJson, isVersionGreater } from '../../utils.js';
|
|
3
|
+
|
|
4
|
+
export default function migrate(context: Context) {
|
|
5
|
+
if (context.doesFileExist('package.json')) {
|
|
6
|
+
const packageJson = JSON.parse(context.getFile('package.json') || '{}');
|
|
7
|
+
if (packageJson.dependencies?.react) {
|
|
8
|
+
if (isVersionGreater(packageJson.dependencies.react, '18.0.0', true)) {
|
|
9
|
+
addDependenciesToPackageJson(context, { react: '^18.3.0' }, { '@types/react': '^18.3.0' });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (packageJson.dependencies?.['react-dom']) {
|
|
13
|
+
if (isVersionGreater(packageJson.dependencies['react-dom'], '18.0.0', true)) {
|
|
14
|
+
addDependenciesToPackageJson(context, { 'react-dom': '^18.3.0' }, { '@types/react-dom': '^18.3.0' });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
@@ -14,7 +14,7 @@ describe('Migration - append profile to webpack', () => {
|
|
|
14
14
|
})
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
const updatedContext = await migrate(context);
|
|
17
|
+
const updatedContext = await migrate(context, { profile: true, skipBackup: false, verbose: false });
|
|
18
18
|
|
|
19
19
|
expect(updatedContext.getFile('./package.json')).toMatch(
|
|
20
20
|
'webpack -c ./.config/webpack/webpack.config.ts --profile --env production'
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import type { Context } from '../../context.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Example migration that demonstrates basic context operations
|
|
5
|
+
* This example shows how to modify package.json, add/delete files, and rename files
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type MigrateOptions = {
|
|
9
|
+
profile?: boolean;
|
|
10
|
+
skipBackup?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function migrate(context: Context, options: MigrateOptions = {}): Context {
|
|
15
|
+
const { profile = false, skipBackup = false, verbose = false } = options;
|
|
16
|
+
|
|
17
|
+
if (verbose) {
|
|
18
|
+
console.log('Running migration with options:', options);
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
const rawPkgJson = context.getFile('./package.json') ?? '{}';
|
|
5
22
|
const packageJson = JSON.parse(rawPkgJson);
|
|
6
23
|
|
|
@@ -9,14 +26,14 @@ export default function migrate(context: Context): Context {
|
|
|
9
26
|
|
|
10
27
|
const pattern = /(webpack.+-c\s.+\.ts)\s(.+)/;
|
|
11
28
|
|
|
12
|
-
if (pattern.test(buildScript) && !buildScript.includes('--profile')) {
|
|
29
|
+
if (profile && pattern.test(buildScript) && !buildScript.includes('--profile')) {
|
|
13
30
|
packageJson.scripts.build = buildScript.replace(pattern, `$1 --profile $2`);
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
context.updateFile('./package.json', JSON.stringify(packageJson, null, 2));
|
|
17
34
|
}
|
|
18
35
|
|
|
19
|
-
if (context.doesFileExist('./src/README.md')) {
|
|
36
|
+
if (!skipBackup && context.doesFileExist('./src/README.md')) {
|
|
20
37
|
context.deleteFile('./src/README.md');
|
|
21
38
|
}
|
|
22
39
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Context } from './context.js';
|
|
2
|
+
import { formatFiles, flushChanges, installNPMDependencies, printChanges } from './utils.js';
|
|
3
|
+
import { parseAndValidateOptions } from './schema-parser.js';
|
|
4
|
+
import { output } from '../utils/utils.console.js';
|
|
5
|
+
import { Codemod } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run a single codemod
|
|
9
|
+
*
|
|
10
|
+
* Steps:
|
|
11
|
+
* 1. Load codemod module from scriptPath
|
|
12
|
+
* 2. Parse and validate options from schema
|
|
13
|
+
* 3. Execute codemod transformation
|
|
14
|
+
* 4. Format files
|
|
15
|
+
* 5. Flush changes to disk
|
|
16
|
+
* 6. Print summary
|
|
17
|
+
* 7. Install dependencies if needed
|
|
18
|
+
*/
|
|
19
|
+
export async function runCodemod(codemod: Codemod, options?: Record<string, any>): Promise<Context> {
|
|
20
|
+
const codemodModule = await import(codemod.scriptPath);
|
|
21
|
+
if (!codemodModule.default || typeof codemodModule.default !== 'function') {
|
|
22
|
+
throw new Error(`Codemod ${codemod.name} must export a default function`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let codemodOptions = {};
|
|
26
|
+
|
|
27
|
+
if (options && codemodModule.schema) {
|
|
28
|
+
codemodOptions = parseAndValidateOptions(options, codemodModule.schema);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const basePath = process.cwd();
|
|
32
|
+
const context = new Context(basePath);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
output.log({
|
|
36
|
+
title: `Running ${codemod.name}`,
|
|
37
|
+
body: [codemod.description],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const updatedContext = await codemodModule.default(context, codemodOptions);
|
|
41
|
+
|
|
42
|
+
// standard post-processing pipeline
|
|
43
|
+
await formatFiles(updatedContext);
|
|
44
|
+
flushChanges(updatedContext);
|
|
45
|
+
printChanges(updatedContext, codemod.name, codemod.description);
|
|
46
|
+
installNPMDependencies(updatedContext);
|
|
47
|
+
|
|
48
|
+
return updatedContext;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof Error) {
|
|
51
|
+
const newError = new Error(`Error running ${codemod.name}: ${error.message}`);
|
|
52
|
+
newError.cause = error;
|
|
53
|
+
throw newError;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse and validate options using Valibot schema
|
|
5
|
+
* Valibot handles parsing, validation, type coercion, and defaults automatically
|
|
6
|
+
*/
|
|
7
|
+
export function parseAndValidateOptions<T extends v.BaseSchema<any, any, any>>(
|
|
8
|
+
options: Record<string, any>,
|
|
9
|
+
schema: T
|
|
10
|
+
): v.InferOutput<T> {
|
|
11
|
+
try {
|
|
12
|
+
return v.parse(schema, options);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (v.isValiError(error)) {
|
|
15
|
+
// format Valibot validation errors
|
|
16
|
+
const formattedErrors = error.issues
|
|
17
|
+
.map((issue) => {
|
|
18
|
+
const path = issue.path?.map((p) => p.key).join('.') || '';
|
|
19
|
+
return ` --${path}: ${issue.message}`;
|
|
20
|
+
})
|
|
21
|
+
.join('\n');
|
|
22
|
+
|
|
23
|
+
throw new Error(`Invalid flag(s) provided:\n\n${formattedErrors}`);
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/codemods/types.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import type { Context } from './context.js';
|
|
2
|
+
import type * as v from 'valibot';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
// used as a generic constraint for codemod schemas. accepts any input, output and error types
|
|
5
|
+
type AnySchema = v.BaseSchema<any, any, any>;
|
|
6
|
+
|
|
7
|
+
export interface CodemodModule<TSchema extends AnySchema = AnySchema> {
|
|
8
|
+
default: (context: Context, options: v.InferOutput<TSchema>) => Context | Promise<Context>;
|
|
9
|
+
schema?: TSchema;
|
|
5
10
|
}
|
|
6
11
|
|
|
7
|
-
export interface
|
|
12
|
+
export interface Codemod {
|
|
8
13
|
name: string;
|
|
9
14
|
description: string;
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AdditionModule<TOptions = any> extends CodemodModule {
|
|
14
|
-
default: (context: Context, options?: TOptions) => Context | Promise<Context>;
|
|
15
|
-
flags?: FlagDefinition[];
|
|
16
|
-
parseFlags?: (argv: any) => TOptions;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface MigrationModule extends CodemodModule {
|
|
20
|
-
default: (context: Context) => Context | Promise<Context>;
|
|
15
|
+
scriptPath: string;
|
|
21
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirSync } from 'tmp';
|
|
2
|
-
import { Context } from '
|
|
2
|
+
import { Context } from './context.js';
|
|
3
3
|
import {
|
|
4
4
|
addDependenciesToPackageJson,
|
|
5
5
|
removeDependenciesFromPackageJson,
|
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
formatFiles,
|
|
8
8
|
readJsonFile,
|
|
9
9
|
isVersionGreater,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
printChanges,
|
|
11
|
+
} from './utils.js';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
14
14
|
import { readFileSync } from 'node:fs';
|
|
15
|
-
import { output } from '
|
|
15
|
+
import { output } from '../utils/utils.console.js';
|
|
16
|
+
import { vi } from 'vitest';
|
|
16
17
|
|
|
17
18
|
describe('utils', () => {
|
|
18
19
|
const tmpObj = dirSync({ unsafeCleanup: true });
|
|
@@ -80,7 +81,7 @@ describe('utils', () => {
|
|
|
80
81
|
context.updateFile('baz.ts', 'new content');
|
|
81
82
|
context.deleteFile('bar.ts');
|
|
82
83
|
|
|
83
|
-
printChanges(context, 'key',
|
|
84
|
+
printChanges(context, 'key', 'test');
|
|
84
85
|
|
|
85
86
|
expect(outputMock.addHorizontalLine).toHaveBeenCalledWith('gray');
|
|
86
87
|
expect(outputMock.logSingleLine).toHaveBeenCalledWith('key (test)');
|
|
@@ -102,7 +103,7 @@ describe('utils', () => {
|
|
|
102
103
|
it('should print no changes', async () => {
|
|
103
104
|
const context = new Context(tmpDir);
|
|
104
105
|
|
|
105
|
-
printChanges(context, 'key',
|
|
106
|
+
printChanges(context, 'key', 'test');
|
|
106
107
|
|
|
107
108
|
expect(outputMock.logSingleLine).toHaveBeenCalledWith('No changes were made');
|
|
108
109
|
});
|
|
@@ -271,7 +272,7 @@ describe('utils', () => {
|
|
|
271
272
|
});
|
|
272
273
|
});
|
|
273
274
|
|
|
274
|
-
describe('
|
|
275
|
+
describe('isVersionGreater', () => {
|
|
275
276
|
describe('dist tag comparison', () => {
|
|
276
277
|
it('should return false when incoming is "latest" and existing is "next"', () => {
|
|
277
278
|
expect(isVersionGreater('latest', 'next')).toBe(false);
|
package/src/codemods/utils.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Shared utilities for codemods (migrations and additions).
|
|
3
|
-
* These functions work with the Context class to modify plugin codebases.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { dirname, join } from 'node:path';
|
|
1
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
2
|
import { createRequire } from 'node:module';
|
|
8
3
|
import { Context } from './context.js';
|
|
9
4
|
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
@@ -11,20 +6,10 @@ import chalk from 'chalk';
|
|
|
11
6
|
import { output } from '../utils/utils.console.js';
|
|
12
7
|
import { getPackageManagerSilentInstallCmd, getPackageManagerWithFallback } from '../utils/utils.packageManager.js';
|
|
13
8
|
import { execSync } from 'node:child_process';
|
|
14
|
-
import { clean, coerce, gt } from 'semver';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Generic metadata type for printChanges
|
|
18
|
-
*/
|
|
19
|
-
type ChangeMetadata = {
|
|
20
|
-
description: string;
|
|
21
|
-
};
|
|
9
|
+
import { clean, coerce, gt, gte } from 'semver';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
* Prints changes made to the context in a formatted way.
|
|
25
|
-
* Works with both migrations and additions.
|
|
26
|
-
*/
|
|
27
|
-
export function printChanges(context: Context, key: string, meta: ChangeMetadata) {
|
|
12
|
+
export function printChanges(context: Context, key: string, description: string) {
|
|
28
13
|
const changes = context.listChanges();
|
|
29
14
|
const lines = [];
|
|
30
15
|
|
|
@@ -39,7 +24,7 @@ export function printChanges(context: Context, key: string, meta: ChangeMetadata
|
|
|
39
24
|
}
|
|
40
25
|
|
|
41
26
|
output.addHorizontalLine('gray');
|
|
42
|
-
output.logSingleLine(`${key} (${
|
|
27
|
+
output.logSingleLine(`${key} (${description})`);
|
|
43
28
|
|
|
44
29
|
if (lines.length === 0) {
|
|
45
30
|
output.logSingleLine('No changes were made');
|
|
@@ -48,9 +33,6 @@ export function printChanges(context: Context, key: string, meta: ChangeMetadata
|
|
|
48
33
|
}
|
|
49
34
|
}
|
|
50
35
|
|
|
51
|
-
/**
|
|
52
|
-
* Writes all changes from the context to the filesystem.
|
|
53
|
-
*/
|
|
54
36
|
export function flushChanges(context: Context) {
|
|
55
37
|
const basePath = context.basePath;
|
|
56
38
|
const changes = context.listChanges();
|
|
@@ -122,9 +104,6 @@ export async function formatFiles(context: Context) {
|
|
|
122
104
|
// (This runs for each codemod used in an update)
|
|
123
105
|
let packageJsonInstallCache: string;
|
|
124
106
|
|
|
125
|
-
/**
|
|
126
|
-
* Installs NPM dependencies if package.json has changed.
|
|
127
|
-
*/
|
|
128
107
|
export function installNPMDependencies(context: Context) {
|
|
129
108
|
const hasPackageJsonChanges = Object.entries(context.listChanges()).some(
|
|
130
109
|
([filePath, { changeType }]) => filePath === 'package.json' && changeType === 'update'
|
|
@@ -152,9 +131,6 @@ export function installNPMDependencies(context: Context) {
|
|
|
152
131
|
}
|
|
153
132
|
}
|
|
154
133
|
|
|
155
|
-
/**
|
|
156
|
-
* Reads and parses a JSON file from the context.
|
|
157
|
-
*/
|
|
158
134
|
export function readJsonFile<T extends object = any>(context: Context, path: string): T {
|
|
159
135
|
if (!context.doesFileExist(path)) {
|
|
160
136
|
throw new Error(`Cannot find ${path}`);
|
|
@@ -166,9 +142,6 @@ export function readJsonFile<T extends object = any>(context: Context, path: str
|
|
|
166
142
|
}
|
|
167
143
|
}
|
|
168
144
|
|
|
169
|
-
/**
|
|
170
|
-
* Adds or updates dependencies in package.json, preventing downgrades.
|
|
171
|
-
*/
|
|
172
145
|
export function addDependenciesToPackageJson(
|
|
173
146
|
context: Context,
|
|
174
147
|
dependencies: Record<string, string>,
|
|
@@ -233,9 +206,6 @@ export function addDependenciesToPackageJson(
|
|
|
233
206
|
context.updateFile(packageJsonPath, JSON.stringify(updatedPackageJson, null, 2));
|
|
234
207
|
}
|
|
235
208
|
|
|
236
|
-
/**
|
|
237
|
-
* Removes dependencies from package.json.
|
|
238
|
-
*/
|
|
239
209
|
export function removeDependenciesFromPackageJson(
|
|
240
210
|
context: Context,
|
|
241
211
|
dependencies: string[],
|
|
@@ -276,8 +246,13 @@ const DIST_TAGS = {
|
|
|
276
246
|
|
|
277
247
|
/**
|
|
278
248
|
* Compares two version strings to determine if the incoming version is greater
|
|
249
|
+
*
|
|
250
|
+
* @param incomingVersion - The incoming version to compare.
|
|
251
|
+
* @param existingVersion - The existing version to compare.
|
|
252
|
+
* @param orEqualTo - Whether to check for greater than or equal to (>=) instead of just greater than (>).
|
|
253
|
+
*
|
|
279
254
|
*/
|
|
280
|
-
export function isVersionGreater(incomingVersion: string, existingVersion: string)
|
|
255
|
+
export function isVersionGreater(incomingVersion: string, existingVersion: string, orEqualTo = false) {
|
|
281
256
|
const incomingIsDistTag = incomingVersion in DIST_TAGS;
|
|
282
257
|
const existingIsDistTag = existingVersion in DIST_TAGS;
|
|
283
258
|
|
|
@@ -301,6 +276,10 @@ export function isVersionGreater(incomingVersion: string, existingVersion: strin
|
|
|
301
276
|
return true;
|
|
302
277
|
}
|
|
303
278
|
|
|
279
|
+
if (orEqualTo) {
|
|
280
|
+
return gte(incomingSemver, existingSemver);
|
|
281
|
+
}
|
|
282
|
+
|
|
304
283
|
return gt(incomingSemver, existingSemver);
|
|
305
284
|
}
|
|
306
285
|
|
|
@@ -319,3 +298,16 @@ function sortObjectByKeys<T extends Record<string, any>>(obj: T): T {
|
|
|
319
298
|
.sort()
|
|
320
299
|
.reduce((acc, key) => ({ ...acc, [key]: obj[key] }), {} as T);
|
|
321
300
|
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Resolves a script path relative to the caller's file location.
|
|
304
|
+
*
|
|
305
|
+
* @param callerUrl - The import.meta.url from the calling file
|
|
306
|
+
* @param relativePath - The relative path to resolve (e.g., './scripts/example.js')
|
|
307
|
+
* @returns The absolute resolved path
|
|
308
|
+
*/
|
|
309
|
+
export function resolveScriptPath(callerUrl: string, relativePath: string): string {
|
|
310
|
+
const __filename = fileURLToPath(callerUrl);
|
|
311
|
+
const __dirname = dirname(__filename);
|
|
312
|
+
return resolve(__dirname, relativePath);
|
|
313
|
+
}
|