@grafana/create-plugin 6.2.0-canary.2233.19133609453.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/CONTRIBUTING.md +3 -0
  3. package/dist/codemods/additions/additions.js +8 -8
  4. package/dist/codemods/additions/scripts/example-addition.js +19 -33
  5. package/dist/codemods/migrations/manager.js +13 -40
  6. package/dist/codemods/migrations/migrations.js +34 -25
  7. package/dist/codemods/migrations/scripts/004-eslint9-flat-config.js +1 -2
  8. package/dist/codemods/migrations/scripts/005-react-18-3.js +20 -0
  9. package/dist/codemods/migrations/scripts/example-migration.js +7 -3
  10. package/dist/codemods/runner.js +38 -0
  11. package/dist/codemods/schema-parser.js +20 -0
  12. package/dist/codemods/utils.js +15 -6
  13. package/dist/commands/add.command.js +24 -55
  14. package/dist/commands/update.command.js +7 -41
  15. package/dist/utils/utils.checks.js +40 -0
  16. package/dist/utils/utils.config.js +1 -16
  17. package/package.json +3 -2
  18. package/src/codemods/additions/additions.test.ts +12 -0
  19. package/src/codemods/additions/additions.ts +9 -22
  20. package/src/codemods/additions/scripts/example-addition.test.ts +14 -33
  21. package/src/codemods/additions/scripts/example-addition.ts +27 -44
  22. package/src/codemods/migrations/fixtures/migrations.ts +19 -18
  23. package/src/codemods/migrations/manager.test.ts +67 -73
  24. package/src/codemods/migrations/manager.ts +17 -50
  25. package/src/codemods/migrations/migrations.test.ts +8 -5
  26. package/src/codemods/migrations/migrations.ts +38 -34
  27. package/src/codemods/migrations/scripts/004-eslint9-flat-config.ts +2 -2
  28. package/src/codemods/migrations/scripts/005-react-18-3.test.ts +145 -0
  29. package/src/codemods/migrations/scripts/005-react-18-3.ts +19 -0
  30. package/src/codemods/migrations/scripts/example-migration.test.ts +1 -1
  31. package/src/codemods/migrations/scripts/example-migration.ts +20 -3
  32. package/src/codemods/runner.ts +57 -0
  33. package/src/codemods/schema-parser.ts +27 -0
  34. package/src/codemods/types.ts +9 -14
  35. package/src/codemods/{migrations/utils.test.ts → utils.test.ts} +8 -7
  36. package/src/codemods/utils.ts +28 -36
  37. package/src/commands/add.command.ts +26 -62
  38. package/src/commands/update.command.ts +8 -47
  39. package/src/migrations/migrations.ts +44 -0
  40. package/src/utils/utils.checks.ts +47 -0
  41. package/src/utils/utils.config.ts +1 -28
  42. package/templates/common/_package.json +7 -5
  43. package/templates/github/workflows/bundle-stats.yml +1 -1
  44. package/templates/github/workflows/ci.yml +11 -11
  45. package/templates/github/workflows/cp-update.yml +9 -14
  46. package/templates/github/workflows/is-compatible.yml +3 -3
  47. package/templates/github/workflows/release.yml +1 -1
  48. package/vitest.config.ts +12 -0
  49. package/dist/codemods/additions/manager.js +0 -115
  50. package/dist/codemods/additions/utils.js +0 -10
  51. package/dist/codemods/migrations/utils.js +0 -10
  52. package/src/codemods/additions/manager.ts +0 -145
  53. package/src/codemods/additions/utils.ts +0 -12
  54. package/src/codemods/migrations/utils.ts +0 -12
@@ -1,16 +1,15 @@
1
1
  import { getMigrationsToRun, runMigrations } from '../codemods/migrations/manager.js';
2
2
  import { getPackageManagerWithFallback, getPackageManagerExecCmd, getPackageManagerSilentInstallCmd } from '../utils/utils.packageManager.js';
3
3
  import { lt, gte } from 'semver';
4
- import { isGitDirectory, isGitDirectoryClean } from '../utils/utils.git.js';
4
+ import { performPreCodemodChecks } from '../utils/utils.checks.js';
5
5
  import { CURRENT_APP_VERSION } from '../utils/utils.version.js';
6
6
  import { LEGACY_UPDATE_CUTOFF_VERSION } from '../constants.js';
7
7
  import { getConfig } from '../utils/utils.config.js';
8
- import { isPluginDirectory } from '../utils/utils.plugin.js';
9
8
  import { output } from '../utils/utils.console.js';
10
9
  import { spawnSync } from 'node:child_process';
11
10
 
12
11
  const update = async (argv) => {
13
- await performPreUpdateChecks(argv);
12
+ await performPreCodemodChecks(argv);
14
13
  const { version } = getConfig();
15
14
  if (lt(version, LEGACY_UPDATE_CUTOFF_VERSION)) {
16
15
  preparePluginForMigrations(argv);
@@ -22,9 +21,12 @@ const update = async (argv) => {
22
21
  });
23
22
  process.exit(0);
24
23
  }
25
- const commitEachMigration = argv.commit;
26
24
  const migrations = getMigrationsToRun(version, CURRENT_APP_VERSION);
27
- await runMigrations(migrations, { commitEachMigration });
25
+ const { _, $0, ...codemodOptions } = argv;
26
+ await runMigrations(migrations, {
27
+ commitEachMigration: !!argv.commit,
28
+ codemodOptions
29
+ });
28
30
  output.success({
29
31
  title: `Successfully updated create-plugin from ${version} to ${CURRENT_APP_VERSION}.`
30
32
  });
@@ -38,42 +40,6 @@ const update = async (argv) => {
38
40
  process.exit(1);
39
41
  }
40
42
  };
41
- async function performPreUpdateChecks(argv) {
42
- if (!await isGitDirectory() && !argv.force) {
43
- output.error({
44
- title: "You are not inside a git directory",
45
- body: [
46
- `In order to proceed please run ${output.formatCode("git init")} in the root of your project and commit your changes.`,
47
- `(This check is necessary to make sure that the updates are easy to revert and don't interfere with any changes you currently have.`,
48
- `In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
49
- ]
50
- });
51
- process.exit(1);
52
- }
53
- if (!await isGitDirectoryClean() && !argv.force) {
54
- output.error({
55
- title: "Please clean your repository working tree before updating.",
56
- body: [
57
- "Commit your changes or stash them.",
58
- `(This check is necessary to make sure that the updates are easy to revert and don't mess with any changes you currently have.`,
59
- `In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
60
- ]
61
- });
62
- process.exit(1);
63
- }
64
- if (!isPluginDirectory() && !argv.force) {
65
- output.error({
66
- title: "Are you inside a plugin directory?",
67
- body: [
68
- `We couldn't find a "src/plugin.json" file under your current directory.`,
69
- `(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(
70
- "--force"
71
- )} flag.)`
72
- ]
73
- });
74
- process.exit(1);
75
- }
76
- }
77
43
  function preparePluginForMigrations(argv) {
78
44
  const { packageManagerName, packageManagerVersion } = getPackageManagerWithFallback();
79
45
  const packageManagerExecCmd = getPackageManagerExecCmd(packageManagerName, packageManagerVersion);
@@ -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, isFeatureEnabled, setFeatureFlag, setRootConfig };
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.19133609453.0",
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": "d84f5bcb2be40efa1f28ad4a86b52a8a1566e85c"
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
- export type AdditionMeta<TFeatureName extends string = string> = {
2
- name: string;
3
- description: string;
4
- scriptPath: string;
5
- featureName: TFeatureName;
6
- };
7
-
8
- const additions = {
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[];
@@ -4,23 +4,12 @@ import { Context } from '../../context.js';
4
4
  import migrate from './example-addition.js';
5
5
 
6
6
  describe('example-addition', () => {
7
- it('should be idempotent', async () => {
8
- const context = new Context('/virtual');
9
-
10
- // Set up a minimal project structure
11
- context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
12
- context.addFile('src/index.ts', 'export const foo = "bar";');
13
-
14
- const migrateWithOptions = (ctx: Context) => migrate(ctx, { featureName: 'testFeature', enabled: true });
15
- await expect(migrateWithOptions).toBeIdempotent(context);
16
- });
17
-
18
7
  it('should add example script to package.json', () => {
19
8
  const context = new Context('/virtual');
20
9
 
21
10
  context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
22
11
 
23
- const result = migrate(context, { featureName: 'testFeature', enabled: true });
12
+ const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
24
13
 
25
14
  const packageJson = JSON.parse(result.getFile('package.json') || '{}');
26
15
  expect(packageJson.scripts['example-script']).toBe('echo "Running testFeature"');
@@ -31,39 +20,31 @@ describe('example-addition', () => {
31
20
 
32
21
  context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
33
22
 
34
- const result = migrate(context, { featureName: 'myFeature', enabled: false });
23
+ const result = migrate(context, { featureName: 'myFeature', enabled: false, frameworks: ['react'] });
35
24
 
36
25
  const packageJson = JSON.parse(result.getFile('package.json') || '{}');
37
26
  expect(packageJson.devDependencies['example-dev-dep']).toBe('^1.0.0');
38
27
  });
39
28
 
40
- it('should create config file with feature settings', () => {
29
+ it('should create feature TypeScript file with options', () => {
41
30
  const context = new Context('/virtual');
42
31
 
43
32
  context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
44
33
 
45
- const result = migrate(context, { featureName: 'coolFeature', enabled: true });
46
-
47
- expect(result.doesFileExist('src/config.json')).toBe(true);
48
- const config = JSON.parse(result.getFile('src/config.json') || '{}');
49
- expect(config.features.coolFeature).toEqual({
50
- enabled: true,
51
- description: 'Example feature configuration',
34
+ const result = migrate(context, {
35
+ featureName: 'myFeature',
36
+ enabled: false,
37
+ port: 4000,
38
+ frameworks: ['react', 'vue'],
52
39
  });
53
- });
54
-
55
- it('should create feature TypeScript file', () => {
56
- const context = new Context('/virtual');
57
-
58
- context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
59
-
60
- const result = migrate(context, { featureName: 'myFeature', enabled: false });
61
40
 
62
41
  expect(result.doesFileExist('src/features/myFeature.ts')).toBe(true);
63
42
  const featureCode = result.getFile('src/features/myFeature.ts');
64
43
  expect(featureCode).toContain('export const myFeature');
65
44
  expect(featureCode).toContain('enabled: false');
66
- expect(featureCode).toContain('myFeature initialized');
45
+ expect(featureCode).toContain('port: 4000');
46
+ expect(featureCode).toContain('frameworks: ["react","vue"]');
47
+ expect(featureCode).toContain('myFeature initialized on port 4000');
67
48
  });
68
49
 
69
50
  it('should delete deprecated file if it exists', () => {
@@ -72,7 +53,7 @@ describe('example-addition', () => {
72
53
  context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
73
54
  context.addFile('src/deprecated.ts', 'export const old = true;');
74
55
 
75
- const result = migrate(context, { featureName: 'testFeature', enabled: true });
56
+ const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
76
57
 
77
58
  expect(result.doesFileExist('src/deprecated.ts')).toBe(false);
78
59
  });
@@ -83,7 +64,7 @@ describe('example-addition', () => {
83
64
  context.addFile('package.json', JSON.stringify({ scripts: {}, dependencies: {}, devDependencies: {} }));
84
65
  context.addFile('src/old-config.json', JSON.stringify({ old: true }));
85
66
 
86
- const result = migrate(context, { featureName: 'testFeature', enabled: true });
67
+ const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
87
68
 
88
69
  expect(result.doesFileExist('src/old-config.json')).toBe(false);
89
70
  expect(result.doesFileExist('src/new-config.json')).toBe(true);
@@ -103,7 +84,7 @@ describe('example-addition', () => {
103
84
  })
104
85
  );
105
86
 
106
- const result = migrate(context, { featureName: 'testFeature', enabled: true });
87
+ const result = migrate(context, { featureName: 'testFeature', enabled: true, frameworks: ['react'] });
107
88
 
108
89
  const packageJson = JSON.parse(result.getFile('package.json') || '{}');
109
90
  expect(packageJson.scripts['example-script']).toBe('existing command');
@@ -1,37 +1,30 @@
1
+ import * as v from 'valibot';
1
2
  import type { Context } from '../../context.js';
2
- import type { FlagDefinition } from '../../types.js';
3
3
  import { addDependenciesToPackageJson } from '../../utils.js';
4
4
 
5
- export type ExampleOptions = {
6
- featureName: string;
7
- enabled: boolean;
8
- };
9
-
10
- export const flags: FlagDefinition[] = [
11
- {
12
- name: 'feature-name',
13
- description: 'Name of the feature to add',
14
- required: false,
15
- },
16
- {
17
- name: 'enabled',
18
- description: 'Whether the feature should be enabled by default',
19
- required: false,
20
- },
21
- ];
22
-
23
- export function parseFlags(argv: any): ExampleOptions {
24
- return {
25
- featureName: argv['feature-name'] || 'myFeature',
26
- enabled: argv.enabled === 'true' || argv.enabled === true,
27
- };
28
- }
29
-
30
- export default function migrate(
31
- context: Context,
32
- options: ExampleOptions = { featureName: 'myFeature', enabled: true }
33
- ): Context {
34
- const { featureName, enabled } = options;
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;
35
28
 
36
29
  const rawPkgJson = context.getFile('./package.json') ?? '{}';
37
30
  const packageJson = JSON.parse(rawPkgJson);
@@ -43,24 +36,14 @@ export default function migrate(
43
36
 
44
37
  addDependenciesToPackageJson(context, {}, { 'example-dev-dep': '^1.0.0' });
45
38
 
46
- if (!context.doesFileExist('./src/config.json')) {
47
- const config = {
48
- features: {
49
- [featureName]: {
50
- enabled,
51
- description: 'Example feature configuration',
52
- },
53
- },
54
- };
55
- context.addFile('./src/config.json', JSON.stringify(config, null, 2));
56
- }
57
-
58
39
  if (!context.doesFileExist(`./src/features/${featureName}.ts`)) {
59
40
  const featureCode = `export const ${featureName} = {
60
41
  name: '${featureName}',
61
42
  enabled: ${enabled},
43
+ port: ${port ?? 3000},
44
+ frameworks: ${JSON.stringify(frameworks)},
62
45
  init() {
63
- console.log('${featureName} initialized');
46
+ console.log('${featureName} initialized on port ${port ?? 3000}');
64
47
  },
65
48
  };
66
49
  `;
@@ -1,19 +1,20 @@
1
- export default {
2
- migrations: {
3
- 'migration-key1': {
4
- version: '5.0.0',
5
- description: 'Update project to use new cache directory',
6
- migrationScript: './5-0-0-cache-directory.js',
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
+ ];