@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.
- 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 +19 -33
- 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 +14 -33
- package/src/codemods/additions/scripts/example-addition.ts +27 -44
- 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/utils.js +0 -10
- package/dist/codemods/migrations/utils.js +0 -10
- package/src/codemods/additions/manager.ts +0 -145
- package/src/codemods/additions/utils.ts +0 -12
- package/src/codemods/migrations/utils.ts +0 -12
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
1
|
+
import defaultAdditions from '../codemods/additions/additions.js';
|
|
2
|
+
import { runCodemod } from '../codemods/runner.js';
|
|
3
|
+
import { getPackageManagerExecCmd, getPackageManagerFromUserAgent } from '../utils/utils.packageManager.js';
|
|
4
|
+
import { performPreCodemodChecks } from '../utils/utils.checks.js';
|
|
5
5
|
import minimist from 'minimist';
|
|
6
6
|
import { output } from '../utils/utils.console.js';
|
|
7
7
|
|
|
@@ -13,10 +13,27 @@ export const add = async (argv: minimist.ParsedArgs) => {
|
|
|
13
13
|
process.exit(1);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
await
|
|
16
|
+
await performPreCodemodChecks(argv);
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
const addition = defaultAdditions.find((addition) => addition.name === subCommand);
|
|
20
|
+
if (!addition) {
|
|
21
|
+
const additionsList = defaultAdditions.map((addition) => addition.name);
|
|
22
|
+
throw new Error(`Unknown addition: ${subCommand}\n\nAvailable additions: ${additionsList.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
output.log({
|
|
26
|
+
title: `Running addition: ${addition.name}`,
|
|
27
|
+
body: [addition.description],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// filter out minimist internal properties (_ and $0) before passing to codemod
|
|
31
|
+
const { _, $0, ...codemodOptions } = argv;
|
|
32
|
+
await runCodemod(addition, codemodOptions);
|
|
33
|
+
|
|
34
|
+
output.success({
|
|
35
|
+
title: `Successfully added ${addition.name} to your plugin.`,
|
|
36
|
+
});
|
|
20
37
|
} catch (error) {
|
|
21
38
|
if (error instanceof Error) {
|
|
22
39
|
output.error({
|
|
@@ -29,69 +46,16 @@ export const add = async (argv: minimist.ParsedArgs) => {
|
|
|
29
46
|
};
|
|
30
47
|
|
|
31
48
|
async function showAdditionsHelp() {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
Object.values(availableAdditions).map(async (addition) => {
|
|
35
|
-
let info = `${addition.name} - ${addition.description}`;
|
|
36
|
-
const flags = await getAdditionFlags(addition);
|
|
37
|
-
if (flags.length > 0) {
|
|
38
|
-
const flagDocs = flags.map((flag) => {
|
|
39
|
-
const req = flag.required ? ' (required)' : ' (optional)';
|
|
40
|
-
return ` --${flag.name}: ${flag.description}${req}`;
|
|
41
|
-
});
|
|
42
|
-
info += '\n' + flagDocs.join('\n');
|
|
43
|
-
}
|
|
44
|
-
return info;
|
|
45
|
-
})
|
|
46
|
-
);
|
|
49
|
+
const additionsList = defaultAdditions.map((addition) => addition.name);
|
|
50
|
+
const { packageManagerName, packageManagerVersion } = getPackageManagerFromUserAgent();
|
|
47
51
|
|
|
48
52
|
output.error({
|
|
49
53
|
title: 'No addition specified',
|
|
50
54
|
body: [
|
|
51
|
-
|
|
55
|
+
`Usage: ${getPackageManagerExecCmd(packageManagerName, packageManagerVersion)} add <addition-name> [options]`,
|
|
52
56
|
'',
|
|
53
57
|
'Available additions:',
|
|
54
58
|
...output.bulletList(additionsList),
|
|
55
59
|
],
|
|
56
60
|
});
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
async function performPreAddChecks(argv: minimist.ParsedArgs) {
|
|
60
|
-
if (!(await isGitDirectory()) && !argv.force) {
|
|
61
|
-
output.error({
|
|
62
|
-
title: 'You are not inside a git directory',
|
|
63
|
-
body: [
|
|
64
|
-
`In order to proceed please run ${output.formatCode('git init')} in the root of your project and commit your changes.`,
|
|
65
|
-
`(This check is necessary to make sure that the changes are easy to revert and don't interfere with any changes you currently have.`,
|
|
66
|
-
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
|
|
67
|
-
],
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!(await isGitDirectoryClean()) && !argv.force) {
|
|
74
|
-
output.error({
|
|
75
|
-
title: 'Please clean your repository working tree before adding features.',
|
|
76
|
-
body: [
|
|
77
|
-
'Commit your changes or stash them.',
|
|
78
|
-
`(This check is necessary to make sure that the changes are easy to revert and don't mess with any changes you currently have.`,
|
|
79
|
-
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!isPluginDirectory() && !argv.force) {
|
|
87
|
-
output.error({
|
|
88
|
-
title: 'Are you inside a plugin directory?',
|
|
89
|
-
body: [
|
|
90
|
-
`We couldn't find a "src/plugin.json" file under your current directory.`,
|
|
91
|
-
`(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.)`,
|
|
92
|
-
],
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -5,18 +5,17 @@ import {
|
|
|
5
5
|
getPackageManagerWithFallback,
|
|
6
6
|
} from '../utils/utils.packageManager.js';
|
|
7
7
|
import { gte, lt } from 'semver';
|
|
8
|
-
import {
|
|
8
|
+
import { performPreCodemodChecks } from '../utils/utils.checks.js';
|
|
9
9
|
|
|
10
10
|
import { CURRENT_APP_VERSION } from '../utils/utils.version.js';
|
|
11
11
|
import { LEGACY_UPDATE_CUTOFF_VERSION } from '../constants.js';
|
|
12
12
|
import { getConfig } from '../utils/utils.config.js';
|
|
13
|
-
import { isPluginDirectory } from '../utils/utils.plugin.js';
|
|
14
13
|
import minimist from 'minimist';
|
|
15
14
|
import { output } from '../utils/utils.console.js';
|
|
16
15
|
import { spawnSync } from 'node:child_process';
|
|
17
16
|
|
|
18
17
|
export const update = async (argv: minimist.ParsedArgs) => {
|
|
19
|
-
await
|
|
18
|
+
await performPreCodemodChecks(argv);
|
|
20
19
|
const { version } = getConfig();
|
|
21
20
|
|
|
22
21
|
if (lt(version, LEGACY_UPDATE_CUTOFF_VERSION)) {
|
|
@@ -32,9 +31,13 @@ export const update = async (argv: minimist.ParsedArgs) => {
|
|
|
32
31
|
process.exit(0);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const commitEachMigration = argv.commit;
|
|
36
34
|
const migrations = getMigrationsToRun(version, CURRENT_APP_VERSION);
|
|
37
|
-
|
|
35
|
+
// filter out minimist internal properties (_ and $0) before passing to codemod
|
|
36
|
+
const { _, $0, ...codemodOptions } = argv;
|
|
37
|
+
await runMigrations(migrations, {
|
|
38
|
+
commitEachMigration: !!argv.commit,
|
|
39
|
+
codemodOptions,
|
|
40
|
+
});
|
|
38
41
|
output.success({
|
|
39
42
|
title: `Successfully updated create-plugin from ${version} to ${CURRENT_APP_VERSION}.`,
|
|
40
43
|
});
|
|
@@ -49,48 +52,6 @@ export const update = async (argv: minimist.ParsedArgs) => {
|
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
|
|
52
|
-
async function performPreUpdateChecks(argv: minimist.ParsedArgs) {
|
|
53
|
-
if (!(await isGitDirectory()) && !argv.force) {
|
|
54
|
-
output.error({
|
|
55
|
-
title: 'You are not inside a git directory',
|
|
56
|
-
body: [
|
|
57
|
-
`In order to proceed please run ${output.formatCode('git init')} in the root of your project and commit your changes.`,
|
|
58
|
-
`(This check is necessary to make sure that the updates are easy to revert and don't interfere 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
|
-
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!(await isGitDirectoryClean()) && !argv.force) {
|
|
67
|
-
output.error({
|
|
68
|
-
title: 'Please clean your repository working tree before updating.',
|
|
69
|
-
body: [
|
|
70
|
-
'Commit your changes or stash them.',
|
|
71
|
-
`(This check is necessary to make sure that the updates are easy to revert and don't mess with any changes you currently have.`,
|
|
72
|
-
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
|
|
73
|
-
],
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!isPluginDirectory() && !argv.force) {
|
|
80
|
-
output.error({
|
|
81
|
-
title: 'Are you inside a plugin directory?',
|
|
82
|
-
body: [
|
|
83
|
-
`We couldn't find a "src/plugin.json" file under your current directory.`,
|
|
84
|
-
`(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(
|
|
85
|
-
'--force'
|
|
86
|
-
)} flag.)`,
|
|
87
|
-
],
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
55
|
/**
|
|
95
56
|
* Prepares a plugin for migrations by running the legacy update command and installing dependencies.
|
|
96
57
|
* This is a one time operation that ensures the plugin configs are "as expected" by the new migration system.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { LEGACY_UPDATE_CUTOFF_VERSION } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
export type MigrationMeta = {
|
|
4
|
+
version: string;
|
|
5
|
+
description: string;
|
|
6
|
+
migrationScript: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Migrations = {
|
|
10
|
+
migrations: Record<string, MigrationMeta>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It was used to force migrations to run
|
|
14
|
+
// for those written before the switch to updates as migrations.
|
|
15
|
+
export default {
|
|
16
|
+
migrations: {
|
|
17
|
+
'001-update-grafana-compose-extend': {
|
|
18
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
19
|
+
description: 'Update ./docker-compose.yaml to extend from ./.config/docker-compose-base.yaml.',
|
|
20
|
+
migrationScript: './scripts/001-update-grafana-compose-extend.js',
|
|
21
|
+
},
|
|
22
|
+
'002-update-is-compatible-workflow': {
|
|
23
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
24
|
+
description:
|
|
25
|
+
'Update ./.github/workflows/is-compatible.yml to use is-compatible github action instead of calling levitate directly',
|
|
26
|
+
migrationScript: './scripts/002-update-is-compatible-workflow.js',
|
|
27
|
+
},
|
|
28
|
+
'003-update-eslint-deprecation-rule': {
|
|
29
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
30
|
+
description: 'Replace deprecated eslint-plugin-deprecation with @typescript-eslint/no-deprecated rule.',
|
|
31
|
+
migrationScript: './scripts/003-update-eslint-deprecation-rule.js',
|
|
32
|
+
},
|
|
33
|
+
'004-eslint9-flat-config': {
|
|
34
|
+
version: LEGACY_UPDATE_CUTOFF_VERSION,
|
|
35
|
+
description: 'Migrate eslint config to flat config format and update devDependencies to latest versions.',
|
|
36
|
+
migrationScript: './scripts/004-eslint9-flat-config.js',
|
|
37
|
+
},
|
|
38
|
+
'005-react-18-3': {
|
|
39
|
+
version: '6.1.9',
|
|
40
|
+
description: 'Update React and ReactDOM 18.x versions to ^18.3.0 to surface React 19 compatibility issues.',
|
|
41
|
+
migrationScript: './scripts/005-react-18-3.js',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} as Migrations;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import minimist from 'minimist';
|
|
2
|
+
import { isGitDirectory, isGitDirectoryClean } from './utils.git.js';
|
|
3
|
+
import { isPluginDirectory } from './utils.plugin.js';
|
|
4
|
+
import { output } from './utils.console.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ensures git directory exists, is clean, and we're in a plugin directory
|
|
8
|
+
*/
|
|
9
|
+
export async function performPreCodemodChecks(argv: minimist.ParsedArgs): Promise<void> {
|
|
10
|
+
if (!(await isGitDirectory()) && !argv.force) {
|
|
11
|
+
output.error({
|
|
12
|
+
title: 'You are not inside a git directory',
|
|
13
|
+
body: [
|
|
14
|
+
`In order to proceed please run ${output.formatCode('git init')} in the root of your project and commit your changes.`,
|
|
15
|
+
`(This check is necessary to make sure that changes are easy to revert and don't interfere with any changes you currently have.`,
|
|
16
|
+
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!(await isGitDirectoryClean()) && !argv.force) {
|
|
24
|
+
output.error({
|
|
25
|
+
title: 'Please clean your repository working tree before making changes.',
|
|
26
|
+
body: [
|
|
27
|
+
'Commit your changes or stash them.',
|
|
28
|
+
`(This check is necessary to make sure that changes are easy to revert and don't mess with any changes you currently have.`,
|
|
29
|
+
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isPluginDirectory() && !argv.force) {
|
|
37
|
+
output.error({
|
|
38
|
+
title: 'Are you inside a plugin directory?',
|
|
39
|
+
body: [
|
|
40
|
+
`We couldn't find a "src/plugin.json" file under your current directory.`,
|
|
41
|
+
`(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.)`,
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { argv, commandName } from './utils.cli.js';
|
|
2
|
-
|
|
3
|
-
import type { AdditionFeatureName } from '../codemods/additions/additions.js';
|
|
4
2
|
import { CURRENT_APP_VERSION } from './utils.version.js';
|
|
5
3
|
import { DEFAULT_FEATURE_FLAGS } from '../constants.js';
|
|
6
4
|
import fs from 'node:fs';
|
|
@@ -10,7 +8,7 @@ import path from 'node:path';
|
|
|
10
8
|
import { writeFile } from 'node:fs/promises';
|
|
11
9
|
import { EOL } from 'node:os';
|
|
12
10
|
|
|
13
|
-
type
|
|
11
|
+
type FeatureFlags = {
|
|
14
12
|
bundleGrafanaUI?: boolean;
|
|
15
13
|
|
|
16
14
|
// If set to true, the plugin will be scaffolded with React Router v6. Defaults to true.
|
|
@@ -21,16 +19,6 @@ type CoreFeatureFlags = {
|
|
|
21
19
|
useExperimentalUpdates?: boolean;
|
|
22
20
|
};
|
|
23
21
|
|
|
24
|
-
type AdditionFeatureFlags = {
|
|
25
|
-
[K in AdditionFeatureName]?: boolean;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type FeatureFlags = CoreFeatureFlags & AdditionFeatureFlags;
|
|
29
|
-
|
|
30
|
-
export function isFeatureEnabled(features: FeatureFlags, featureName: AdditionFeatureName): boolean {
|
|
31
|
-
return features[featureName] === true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
22
|
export type CreatePluginConfig = UserConfig & {
|
|
35
23
|
version: string;
|
|
36
24
|
};
|
|
@@ -144,18 +132,3 @@ export async function setRootConfig(configOverride: Partial<CreatePluginConfig>
|
|
|
144
132
|
|
|
145
133
|
return updatedConfig;
|
|
146
134
|
}
|
|
147
|
-
|
|
148
|
-
export async function setFeatureFlag(featureName: string, enabled = true): Promise<void> {
|
|
149
|
-
const userConfig = getUserConfig() || { features: {} };
|
|
150
|
-
const userConfigPath = path.resolve(process.cwd(), '.cprc.json');
|
|
151
|
-
|
|
152
|
-
const updatedConfig = {
|
|
153
|
-
...userConfig,
|
|
154
|
-
features: {
|
|
155
|
-
...userConfig.features,
|
|
156
|
-
[featureName]: enabled,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
await writeFile(userConfigPath, JSON.stringify(updatedConfig, null, 2) + EOL);
|
|
161
|
-
}
|