@grafana/create-plugin 7.16.0 → 7.18.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 (50) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/codemods/additions/additions.js +7 -0
  3. package/dist/codemods/additions/prompts/rspack.md +118 -0
  4. package/dist/codemods/additions/scripts/docs.js +2 -2
  5. package/dist/codemods/additions/scripts/rspack.js +322 -0
  6. package/dist/codemods/agentic/prompts.js +1 -1
  7. package/dist/codemods/migrations/migrations.js +13 -1
  8. package/dist/codemods/migrations/scripts/019-remove-glob-dependency.js +93 -0
  9. package/dist/codemods/migrations/scripts/020-rename-rspack-feature-flag.js +8 -0
  10. package/dist/codemods/utils.ast.js +44 -1
  11. package/dist/codemods/utils.rspack.js +38 -0
  12. package/dist/commands/generate.command.js +1 -9
  13. package/dist/constants.js +0 -1
  14. package/dist/utils/utils.templates.js +4 -4
  15. package/package.json +1 -1
  16. package/src/codemods/additions/additions.test.ts +9 -0
  17. package/src/codemods/additions/additions.ts +7 -0
  18. package/src/codemods/additions/prompts/rspack.md +118 -0
  19. package/src/codemods/additions/scripts/docs.test.ts +4 -4
  20. package/src/codemods/additions/scripts/docs.ts +2 -2
  21. package/src/codemods/additions/scripts/rspack.test.ts +768 -0
  22. package/src/codemods/additions/scripts/rspack.ts +428 -0
  23. package/src/codemods/agentic/prompts.test.ts +11 -2
  24. package/src/codemods/agentic/prompts.ts +1 -1
  25. package/src/codemods/migrations/migrations.ts +14 -1
  26. package/src/codemods/migrations/scripts/019-remove-glob-dependency.test.ts +146 -0
  27. package/src/codemods/migrations/scripts/019-remove-glob-dependency.ts +121 -0
  28. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.test.ts +94 -0
  29. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.ts +8 -0
  30. package/src/codemods/utils.ast.test.ts +151 -0
  31. package/src/codemods/utils.ast.ts +60 -0
  32. package/src/codemods/utils.rspack.ts +58 -0
  33. package/src/codemods/utils.test.ts +5 -2
  34. package/src/commands/generate.command.ts +2 -10
  35. package/src/constants.ts +0 -1
  36. package/src/types.ts +1 -1
  37. package/src/utils/tests/utils.config.test.ts +10 -0
  38. package/src/utils/utils.config.ts +2 -1
  39. package/src/utils/utils.templates.ts +4 -4
  40. package/templates/common/.config/AGENTS/instructions.md +2 -2
  41. package/templates/common/.config/README.md +56 -0
  42. package/templates/common/.config/bundler/externals.ts +2 -2
  43. package/templates/common/.config/bundler/utils.ts +10 -9
  44. package/templates/common/.config/rspack/DiagnosticsRspackPlugin.ts +29 -0
  45. package/templates/common/.config/rspack/LicenseRspackPlugin.ts +43 -0
  46. package/templates/common/.config/rspack/LiveReloadRspackPlugin.ts +95 -0
  47. package/templates/common/.config/rspack/rspack.config.ts +41 -32
  48. package/templates/common/.cprc.json +1 -2
  49. package/templates/common/_package.json +19 -20
  50. package/templates/common/.config/rspack/liveReloadPlugin.ts +0 -133
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.18.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.17.0...@grafana/create-plugin@7.18.0) (2026-11-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * add AI-agent assisted rspack addition ([#2912](https://github.com/grafana/plugin-tools/issues/2912)) ([250a2d3](https://github.com/grafana/plugin-tools/commit/250a2d362527b10f9aae0c3d7bd62003d2b17819))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **create-plugin:** bump deps in `add docs` codemod ([#3043](https://github.com/grafana/plugin-tools/issues/3043)) ([cee49b3](https://github.com/grafana/plugin-tools/commit/cee49b367aee2a0c0bd7b23531f8b9f938cbe398))
14
+
15
+ ## [7.17.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.16.0...@grafana/create-plugin@7.17.0) (2026-10-28)
16
+
17
+
18
+ ### Features
19
+
20
+ * remove dependency on glob ([#2655](https://github.com/grafana/plugin-tools/issues/2655)) ([9db0649](https://github.com/grafana/plugin-tools/commit/9db0649fd4dee9b5745a248ba421998cd9b86764))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * **templates:** Update dependency @grafana/plugin-e2e to v3.15.2 ([#3022](https://github.com/grafana/plugin-tools/issues/3022)) ([2115b27](https://github.com/grafana/plugin-tools/commit/2115b27767554a555640300c7b4dc9c9e1842909))
26
+
3
27
  ## [7.16.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.15.1...@grafana/create-plugin@7.16.0) (2026-10-22)
4
28
 
5
29
 
@@ -10,6 +10,13 @@ const additions = [
10
10
  description: "Externalizes the react JSX runtime to help migrate plugins to React 19",
11
11
  scriptPath: import.meta.resolve("./scripts/externalize-jsx-runtime.js")
12
12
  },
13
+ {
14
+ name: "rspack",
15
+ description: "Migrates the plugin frontend build from webpack or experimental rspack to rspack 2",
16
+ scriptPath: import.meta.resolve("./scripts/rspack.js"),
17
+ prompt: import.meta.resolve("./prompts/rspack.md"),
18
+ verify: ["typecheck", "build"]
19
+ },
13
20
  {
14
21
  name: "experimental-app-sdk",
15
22
  description: "Adds grafana-app-sdk CUE kind code generation to an app plugin",
@@ -0,0 +1,118 @@
1
+ # Migrate the plugin's custom build configuration to rspack
2
+
3
+ ## Context
4
+
5
+ This plugin is moving its frontend build to rspack 2. The `create-plugin add rspack` codemod has already done the parts that create-plugin owns:
6
+
7
+ - Rendered the rspack base config in `.config/rspack/` and the shared helpers in `.config/bundler/`, and set `useRspack` in `.config/.cprc.json`.
8
+ - Deleted the create-plugin files in `.config/webpack/`. It kept any file there that the plugin added and listed it in its output.
9
+ - Added the rspack packages (`@rspack/core`, `@rspack/cli`, `rspack-merge`, `diagnostics-webpack-plugin` and others) and removed webpack-only packages. It kept any webpack package that a root `webpack.*` or `rspack.*` file still imports and listed it.
10
+ - Rewrote `webpack` commands in `package.json` scripts to `rspack`. It listed any script it could not rewrite.
11
+ - If the plugin has a root `webpack.config.*`, added a root `rspack.config.ts` that throws an `[rspack]` error until the custom config is ported.
12
+ - Raised `engines.node` to `>=22.23` if it allowed older versions.
13
+
14
+ What is left is the plugin's own build customisation, which needs judgement to port. The previous base config is still in git: `git show HEAD:.config/webpack/webpack.config.ts`. Compare it with `.config/rspack/rspack.config.ts` when you need to know how a base rule or plugin changed.
15
+
16
+ ## Check first
17
+
18
+ 1. Install dependencies with the plugin's package manager, then run the `build` script. Keep the output; it is evidence, not a to-do list.
19
+ 2. Stop and report that nothing was needed if all of these are true:
20
+ - The build passes.
21
+ - There is no root `webpack.config.*` file.
22
+ - Root `rspack.config.ts`, if present, does not contain the `[rspack]` error.
23
+ - No script in `package.json` calls `webpack`.
24
+ - No plugin code builds a runtime URL to a file the new base config no longer copies into `dist` (see step 6).
25
+ 3. Otherwise, work out the starting point:
26
+ - **From webpack**: there is a root `webpack.config.*`, or `rspack.config.ts` contains the `[rspack]` error. Follow all the steps below.
27
+ - **From experimental rspack**: there is a root `rspack.config.ts` without the `[rspack]` error, and there is no root `webpack.config.*`. The config is already rspack but may use rspack 1 APIs. Skip step 2 and upgrade that file in place using steps 3 to 7.
28
+ - **No custom config**: there is neither a root `webpack.config.*` nor a root `rspack.config.ts`. Only step 6 applies.
29
+
30
+ With a custom config, the build is expected to fail at this point with the `[rspack]` error. Do not chase build errors one by one; the steps below drive the work.
31
+
32
+ ## Steps
33
+
34
+ 1. Find every file that is part of the custom build: the root `webpack.config.*`, any file it imports (for example `webpack.config.utils.ts`), and any file the codemod reported as kept in `.config/webpack/`.
35
+
36
+ 2. Replace the body of root `rspack.config.ts` with a port of the root webpack config:
37
+ - Import the base config with `import grafanaConfig, { type Env } from './.config/rspack/rspack.config.ts';`.
38
+ - Merge with `rspack-merge` (`merge`, `mergeWithRules`, `mergeWithCustomize`, `unique`). It has the same API as `webpack-merge`, typed for rspack. Await the base config before merging.
39
+ - Keep the structure, comments and behaviour of the original. Port it; do not improve it.
40
+ - Remove the `[rspack]` error and the `TODO(rspack)` comment above it only when the port is complete.
41
+
42
+ 3. Replace webpack APIs and packages with their rspack equivalents. Import from `@rspack/core` with named imports, for example `import { DefinePlugin, NormalModuleReplacementPlugin } from '@rspack/core';`.
43
+
44
+ | webpack | rspack |
45
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `webpack` built-in plugins (`DefinePlugin`, `EnvironmentPlugin`, `BannerPlugin`, `ProvidePlugin`, `NormalModuleReplacementPlugin`, `ContextReplacementPlugin`) and `Compilation` | the same names from `@rspack/core` |
47
+ | `webpack-merge` | `rspack-merge` |
48
+ | `copy-webpack-plugin` | `CopyRspackPlugin` from `@rspack/core` |
49
+ | `swc-loader`, `babel-loader` | `builtin:swc-loader` |
50
+ | `fork-ts-checker-webpack-plugin` | `TsCheckerRspackPlugin` from `ts-checker-rspack-plugin` |
51
+ | `eslint-webpack-plugin`, `eslint-rspack-plugin`, or `diagnostics-webpack-plugin` used directly | `DiagnosticsRspackPlugin` from `./.config/rspack/DiagnosticsRspackPlugin.ts`, with `checks: [{ use: 'eslint', ... }]`. It wraps `diagnostics-webpack-plugin`, which fails under rspack on its own. `failOnError` and `severity` become `reportAs` |
52
+ | `webpack-livereload-plugin` | already in the base config for development builds |
53
+ | `mini-css-extract-plugin` | `CssExtractRspackPlugin` from `@rspack/core` |
54
+ | `css-minimizer-webpack-plugin` | `LightningCssMinimizerRspackPlugin` from `@rspack/core` |
55
+ | `terser-webpack-plugin` added by the plugin | `SwcJsMinimizerRspackPlugin` from `@rspack/core`, or keep terser |
56
+ | `html-webpack-plugin` | `HtmlRspackPlugin` from `@rspack/core` |
57
+ | `tsconfig-paths-webpack-plugin` | the `resolve.tsConfig` option |
58
+ | `file-loader`, `url-loader`, `raw-loader` | asset modules: `type: 'asset/resource'`, `'asset/inline'`, `'asset/source'` |
59
+ | plugins built on unplugin (`x/webpack`) | the `x/rspack` entry point, where one exists |
60
+ | imports from `.config/webpack/utils` or `.config/webpack/constants` | `.config/bundler/utils.ts` and `.config/bundler/constants.ts` |
61
+
62
+ Check the [rspack migration guide](https://rspack.rs/guide/migration/webpack) for anything not listed here.
63
+
64
+ 4. Fix code that depends on how the base config is built. Each of these works in webpack and silently does nothing, or throws, in rspack:
65
+ - **Looking up a rule by loader name**, such as `rule.use.loader === 'swc-loader'`. The base config uses `builtin:swc-loader`, so match that. Prefer merging a rule over mutating the base one.
66
+ - **Looking up a plugin with `instanceof` or `constructor.name`** against webpack plugin classes, such as `CopyWebpackPlugin` or `LiveReloadPlugin`. Use the rspack class names, or merge instead of mutating.
67
+ - **`webpack-merge` rules keyed on old names**: `unique('plugins', ['CopyPlugin'], …)` or `mergeWithRules` matching `use.loader: 'swc-loader'`. Update the names.
68
+ - **A `filter` function on a `CopyWebpackPlugin` pattern**. `CopyRspackPlugin` has no `filter`; use `globOptions: { ignore: [...] }`. The base copy patterns come from `.config/bundler/copyFiles.ts`.
69
+ - **Removing the live reload plugin by filtering the plugins array**. The rspack base only adds it in development; filter on the rspack plugin class.
70
+ - **Native ESM loading**: the rspack CLI loads `rspack.config.ts` as native ESM. Replace `__dirname` with `import.meta.dirname`, `require.resolve(x)` with `createRequire(import.meta.url).resolve(x)`, and `require(x)` with `import`. Give relative imports explicit `.ts` extensions. `ReferenceError: __dirname is not defined` at build time means this step is not done.
71
+ - **rspack 2 defaults**: imports of exports that do not exist now fail the build (`exportsPresence: 'error'`). Fix the import in the plugin's code if it is clearly wrong; otherwise report it rather than changing the setting.
72
+ - **rspack 2 option moves**: `experiments.cache`, `experiments.incremental` and `experiments.lazyCompilation` are now top level. `experiments.css` is replaced by `module.rules[].type: 'css' | 'css/module' | 'css/auto'`. `experiments.outputModule` is `output.module`. `output.libraryTarget`, `libraryExport` and `umdNamedDefine` move under `output.library`. `SubresourceIntegrityPlugin` is no longer under `experiments`.
73
+ - **Persistent cache**: the base config has none. If the webpack config set `cache.type: 'filesystem'`, drop it unless the plugin clearly needs it.
74
+
75
+ 5. Check whether the config adds a second transpiler for the plugin's own code. A rule that runs `esbuild-loader`, `babel-loader` or `ts-loader` on `.ts`, `.tsx`, `.js` or `.jsx` files outside `node_modules` overlaps with the base `builtin:swc-loader` rule. Every matching rule applies, so each file is transpiled twice, and the build still passes, so nothing flags it.
76
+ - Do not port the extra rule as it is.
77
+ - If the base `builtin:swc-loader` settings cover what the rule did, leave the rule out of `rspack.config.ts` and remove its loader package once nothing else uses it.
78
+ - If the rule does something the base config does not, for example a different JSX runtime or decorator support, move that setting into the `builtin:swc-loader` rule (see step 4) and leave the extra rule out.
79
+ - If you cannot tell, keep the rule with a `TODO(rspack):` comment above it explaining that it duplicates the base transpiler.
80
+ - Either way, name the rule in your report.
81
+
82
+ 6. Check for files the old base config copied into `dist` that the new one does not. The old base copied every `img/**`, `**/*.svg`, `**/*.png`, `**/*.html`, `libs/**` and `static/**` file; compare `git show HEAD:.config/webpack/webpack.config.ts` with `.config/bundler/copyFiles.ts`. The new base copies only `plugin.json` and other JSON files, the README, CHANGELOG and LICENSE, `query_help.md`, and the logos and screenshots listed in `plugin.json`. Code that builds a URL to one of the other files at runtime, for example `public/plugins/<plugin-id>/img/…` or `` `${pluginPublicPath}/img/how-it-works.svg` ``, now gets a 404. The build still passes, so nothing flags it.
83
+ - Where the path is a literal, import the file and use the imported URL, for example `import howItWorks from '../img/how-it-works.svg';` and `src={howItWorks}`. rspack then hashes the file and resolves its public path, including when the plugin is served from a CDN.
84
+ - Check what an import returns before converting. If the config sends `.svg` imports to `@svgr/webpack` or another loader, the import is a React component, not a URL. Use that loader's URL form if it has one, such as a `?url` resource query, or copy the file instead.
85
+ - The base asset rules only cover `png`, `jpg`, `jpeg`, `gif`, `svg` and font files. For other types, such as `webp`, add an `asset/resource` rule in `rspack.config.ts`.
86
+ - Copy the file instead of importing it when the path is built at runtime, for example `` `${base}/img/${name}.svg` ``, or when something outside the bundle refers to it, such as the README, documentation or another plugin. Add a `CopyRspackPlugin` to `rspack.config.ts` for just those files, with a comment saying why they are copied. Do not copy whole folders to be safe: every copied file ends up in the plugin archive.
87
+
88
+ 7. If something has no rspack equivalent, for example a monkey-patch of webpack internals, a custom `RuntimeModule` plugin, or an SWC Wasm plugin whose ABI does not match rspack's built-in SWC:
89
+ - Port what you can.
90
+ - Comment out the rest, keeping the original code in the comment, so it does not run and the build does not fail.
91
+ - Put a `TODO(rspack):` comment above it that says what the code did, why it has no direct equivalent, and a suggested approach.
92
+ - Never drop it silently.
93
+
94
+ 8. Fix any script the codemod reported as not rewritten so it calls `rspack` with `./rspack.config.ts` or `./.config/rspack/rspack.config.ts`, keeping its other commands and flags. Remove the CLI flags rspack does not support: `--progress`, `--color`, `--bail`, `--output-pathinfo`.
95
+
96
+ 9. For each file the codemod kept in `.config/webpack/`, copy what is still needed to a folder outside `.config/` (for example `build/`), port it to rspack, and update the imports. Do not delete the originals; list them so the user can delete them.
97
+
98
+ 10. Remove the webpack packages the codemod kept, once nothing imports them. Keep `webpack` itself if anything still imports it.
99
+
100
+ 11. Delete the root `webpack.config.*` and its helper files once everything in them is ported or commented with `TODO(rspack):`.
101
+
102
+ ## Verify
103
+
104
+ 1. Install dependencies, then run the `typecheck` and `build` scripts. Both must pass, with any unportable code commented out.
105
+ 2. Check that `dist/` contains `module.js`, `plugin.json`, and every file that plugin code or `plugin.json` refers to by path at runtime. Imported images are hashed, so their names change.
106
+ 3. Run the `dev` script briefly and check that it compiles and keeps watching.
107
+ 4. Fix only breakage caused by this migration.
108
+
109
+ ## Report
110
+
111
+ End with a list of every `TODO(rspack):` comment you added (file, line, one-line reason), every file the user should delete, any duplicate transpiler rule you removed or kept (step 5), every image reference you changed to an import or kept as a copy (step 6), and any `exportsPresence` errors you left for the user.
112
+
113
+ ## Out of scope
114
+
115
+ - Do not modify anything under `.config/`. It is managed by create-plugin and is overwritten by `create-plugin update`.
116
+ - Do not change the plugin id or type in `src/plugin.json`.
117
+ - Do not upgrade unrelated dependencies, and do not reformat files you did not change.
118
+ - Do not refactor plugin source code, unless the build fails because of the bundler change or step 6 needs an image import.
@@ -35,7 +35,7 @@ const schema = v.object({
35
35
  )
36
36
  )
37
37
  });
38
- const PLUGIN_DOCS_CLI_VERSION = "^0.8.0";
38
+ const PLUGIN_DOCS_CLI_VERSION = "^0.13.0";
39
39
  const SUPPORTED_PLUGIN_TYPES = ["panel", "datasource", "app"];
40
40
  function docs(context, options) {
41
41
  const pluginType = assertSupportedPluginType(context);
@@ -465,7 +465,7 @@ const LOCK_FILES = {
465
465
  const RELEASE_WORKFLOW_PATH = ".github/workflows/release.yml";
466
466
  const REQUIRED_ACTION_REFS = {
467
467
  "grafana/plugin-actions/build-plugin": "build-plugin/v1.2.0",
468
- "grafana/plugin-actions/package-plugin": "package-plugin/v1.3.3"
468
+ "grafana/plugin-actions/package-plugin": "package-plugin/v1.3.6"
469
469
  };
470
470
  const DOCS_SCRIPTS = {
471
471
  "docs:serve": "plugin-docs-cli serve --port 3001 --reload",
@@ -0,0 +1,322 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { validRange, minVersion, lt, major } from 'semver';
4
+ import { replaceExperimentalRspackFlag } from '../../utils.rspack.js';
5
+ import { renderHandlebarsTemplate } from '../../../utils/utils.handlebars.js';
6
+ import { getTemplateData } from '../../../utils/utils.templates.js';
7
+ import { renderTemplate, additionsDebug, addDependenciesToPackageJson, removeDependenciesFromPackageJson, readJsonFile } from '../../utils.js';
8
+
9
+ const RSPACK_TEMPLATE_DATA_OVERRIDES = {
10
+ useRspack: true,
11
+ frontendBundler: "rspack"
12
+ };
13
+ const RSPACK_CONFIG_FILES = [
14
+ ".config/rspack/rspack.config.ts",
15
+ ".config/rspack/BuildModeRspackPlugin.ts",
16
+ ".config/rspack/LiveReloadRspackPlugin.ts",
17
+ ".config/rspack/LicenseRspackPlugin.ts",
18
+ ".config/rspack/DiagnosticsRspackPlugin.ts"
19
+ ];
20
+ const RETIRED_RSPACK_FILES = [".config/rspack/liveReloadPlugin.ts"];
21
+ const DOC_FILES = [".config/AGENTS/instructions.md", ".config/README.md"];
22
+ const BUNDLER_FILES = [
23
+ ".config/bundler/constants.ts",
24
+ ".config/bundler/copyFiles.ts",
25
+ ".config/bundler/externals.ts",
26
+ ".config/bundler/utils.ts"
27
+ ];
28
+ const WEBPACK_CONFIG_PATH = ".config/webpack/webpack.config.ts";
29
+ const RSPACK_CONFIG_PATH = ".config/rspack/rspack.config.ts";
30
+ const WEBPACK_TEMPLATE_FILES = [
31
+ ".config/webpack/BuildModeWebpackPlugin.ts",
32
+ ".config/webpack/PluginSchemaWebpackPlugin.ts",
33
+ ".config/webpack/constants.ts",
34
+ ".config/webpack/generateCode.ts",
35
+ ".config/webpack/publicPath.ts",
36
+ ".config/webpack/tsconfig.webpack.json",
37
+ ".config/webpack/utils.ts",
38
+ ".config/webpack/watchPluginJson.ts",
39
+ ".config/webpack/webpack.config.ts",
40
+ ".config/webpack/webpack.parts.ts"
41
+ ];
42
+ function rspack(context) {
43
+ const hasWebpackSetup = context.doesFileExist(WEBPACK_CONFIG_PATH);
44
+ const hasRspackSetup = context.doesFileExist(RSPACK_CONFIG_PATH);
45
+ if (!hasWebpackSetup && !hasRspackSetup) {
46
+ context.setMessage({
47
+ level: "warning",
48
+ title: "No create-plugin bundler configuration found, so nothing was changed.",
49
+ body: [`The rspack addition expects ${WEBPACK_CONFIG_PATH} or ${RSPACK_CONFIG_PATH} to exist.`]
50
+ });
51
+ return context;
52
+ }
53
+ const followUps = [];
54
+ deleteRetiredRspackFiles(context);
55
+ renderTemplateFiles(context, RSPACK_CONFIG_FILES);
56
+ renderTemplateFiles(context, BUNDLER_FILES);
57
+ renderTemplateFiles(context, DOC_FILES, false);
58
+ replaceExperimentalRspackFlag(context, { enable: true });
59
+ followUps.push(...addRootRspackConfigStub(context));
60
+ followUps.push(...updatePackageJson(context));
61
+ followUps.push(...deleteWebpackTemplateFiles(context));
62
+ reportFollowUps(context, followUps);
63
+ return context;
64
+ }
65
+ function deleteRetiredRspackFiles(context) {
66
+ for (const filePath of RETIRED_RSPACK_FILES) {
67
+ if (context.doesFileExist(filePath)) {
68
+ context.deleteFile(filePath);
69
+ }
70
+ }
71
+ }
72
+ function reportFollowUps(context, followUps) {
73
+ if (followUps.length === 0) {
74
+ return;
75
+ }
76
+ context.setMessage({
77
+ level: "warning",
78
+ title: "The rspack addition left some things for you to review.",
79
+ body: followUps
80
+ });
81
+ }
82
+ const resolveTemplatePath = (relativePath) => fileURLToPath(new URL(`../../../../templates/common/${relativePath}`, import.meta.url));
83
+ function renderTemplateFiles(context, filePaths, includeWarning = true) {
84
+ const templateData = { ...getTemplateData(), ...RSPACK_TEMPLATE_DATA_OVERRIDES };
85
+ for (const filePath of filePaths) {
86
+ const rendered = renderTemplate(resolveTemplatePath(filePath), includeWarning, templateData);
87
+ if (context.doesFileExist(filePath)) {
88
+ context.updateFile(filePath, rendered);
89
+ } else {
90
+ context.addFile(filePath, rendered);
91
+ }
92
+ }
93
+ }
94
+ const ROOT_CONFIG_EXTENSIONS = ["ts", "js", "cjs", "mjs", "cts", "mts"];
95
+ function addRootRspackConfigStub(context) {
96
+ const rootWebpackConfig = ROOT_CONFIG_EXTENSIONS.map((extension) => `webpack.config.${extension}`).find(
97
+ (filePath) => context.doesFileExist(filePath)
98
+ );
99
+ if (!rootWebpackConfig) {
100
+ return [];
101
+ }
102
+ const existingRootRspackConfig = ROOT_CONFIG_EXTENSIONS.map((extension) => `rspack.config.${extension}`).find(
103
+ (filePath) => context.doesFileExist(filePath)
104
+ );
105
+ if (existingRootRspackConfig) {
106
+ return [
107
+ `${existingRootRspackConfig} already exists, so it was left as is. Check that it includes everything from ${rootWebpackConfig}.`
108
+ ];
109
+ }
110
+ additionsDebug(`Custom ${rootWebpackConfig} detected. Adding a rspack.config.ts stub that fails until it is ported.`);
111
+ context.addFile("rspack.config.ts", getRootRspackConfigStub(rootWebpackConfig));
112
+ return [
113
+ `rspack.config.ts was added and fails the build until the customisations in ${rootWebpackConfig} are ported to it.`
114
+ ];
115
+ }
116
+ function updatePackageJson(context) {
117
+ if (!context.doesFileExist("package.json")) {
118
+ additionsDebug("No package.json found. Skipping dependency and script updates.");
119
+ return [];
120
+ }
121
+ const followUps = [];
122
+ const templateVersions = getTemplateDevDependencyVersions();
123
+ const userBuildFileSources = readUserBuildFileSources(context);
124
+ const stillUsedDevDependencies = WEBPACK_ONLY_DEV_DEPENDENCIES.filter(
125
+ (name) => userBuildFileSources.some((source) => referencesPackage(source, name))
126
+ );
127
+ const unusedDevDependencies = WEBPACK_ONLY_DEV_DEPENDENCIES.filter(
128
+ (name) => !stillUsedDevDependencies.includes(name)
129
+ );
130
+ addDependenciesToPackageJson(
131
+ context,
132
+ {},
133
+ {
134
+ ...pickVersions(getMissingPackages(context, BASE_CONFIG_DEV_DEPENDENCIES), templateVersions),
135
+ ...pickVersions(RSPACK_DEV_DEPENDENCIES, templateVersions)
136
+ }
137
+ );
138
+ removeDependenciesFromPackageJson(context, [], unusedDevDependencies);
139
+ if (stillUsedDevDependencies.length > 0) {
140
+ followUps.push(
141
+ "These webpack packages were kept because your root build config still uses them. Remove them once the config is ported to rspack:",
142
+ ...stillUsedDevDependencies.map((name) => ` ${name}`)
143
+ );
144
+ }
145
+ const packageJson = readJsonFile(context, "package.json");
146
+ const { scripts, unparsedScripts } = rewriteWebpackScripts(packageJson.scripts ?? {});
147
+ if (packageJson.scripts) {
148
+ context.updateFile("package.json", JSON.stringify({ ...packageJson, scripts }, null, 2));
149
+ }
150
+ raiseNodeEngine(context);
151
+ dropLockedRspackOnePackages(context);
152
+ if (unparsedScripts.length > 0) {
153
+ followUps.push(
154
+ "These scripts still call webpack in a way the addition could not rewrite. Change them to call rspack with ./rspack.config.ts or ./.config/rspack/rspack.config.ts:",
155
+ ...unparsedScripts.map(([name, command]) => ` ${name}: ${command}`)
156
+ );
157
+ }
158
+ return followUps;
159
+ }
160
+ const MIN_NODE_VERSION = "22.23.0";
161
+ function raiseNodeEngine(context) {
162
+ const packageJson = readJsonFile(context, "package.json");
163
+ const nodeRange = packageJson.engines?.node;
164
+ if (!nodeRange || !validRange(nodeRange)) {
165
+ return;
166
+ }
167
+ const lowestAllowedVersion = minVersion(nodeRange);
168
+ if (!lowestAllowedVersion || !lt(lowestAllowedVersion, MIN_NODE_VERSION)) {
169
+ return;
170
+ }
171
+ const engines = { ...packageJson.engines, node: ">=22.23" };
172
+ context.updateFile("package.json", JSON.stringify({ ...packageJson, engines }, null, 2));
173
+ }
174
+ const LOCKED_RSPACK_PACKAGE_PATTERN = /(^|\/)node_modules\/@rspack\//;
175
+ function dropLockedRspackOnePackages(context) {
176
+ if (!context.doesFileExist("package-lock.json")) {
177
+ return;
178
+ }
179
+ const lockfile = readJsonFile(context, "package-lock.json");
180
+ const lockedCoreVersion = lockfile.packages?.["node_modules/@rspack/core"]?.version;
181
+ if (!lockfile.packages || !lockedCoreVersion || major(lockedCoreVersion) >= 2) {
182
+ return;
183
+ }
184
+ const packages = Object.fromEntries(
185
+ Object.entries(lockfile.packages).filter(([path]) => !LOCKED_RSPACK_PACKAGE_PATTERN.test(path))
186
+ );
187
+ context.updateFile("package-lock.json", JSON.stringify({ ...lockfile, packages }, null, 2) + "\n");
188
+ }
189
+ const WEBPACK_COMMAND_PATTERN = /(?<=(?:^|&&|\|\||;|\|)\s*(?:\w+=\S*\s+)*(?:npx\s+)?)webpack(?:-cli)?(?=\s|$)/g;
190
+ const ROOT_WEBPACK_CONFIG_ARG_PATTERN = /((?:^|\s)(?:-c|--config)(?:\s+|=))(?:\.\/)?webpack\.config\.[cm]?[jt]s(?=\s|$)/g;
191
+ const TEMPLATE_WEBPACK_CONFIG_ARG_PATTERN = /((?:^|\s)(?:-c|--config)(?:\s+|=))(?:\.\/)?\.config\/webpack\/webpack\.config\.ts(?=\s|$)/g;
192
+ const REMAINING_WEBPACK_PATTERN = /(?:^|[\s/])webpack(?:-cli)?(?:[\s/]|$)|\.config\/webpack\//;
193
+ function rewriteWebpackScripts(scripts) {
194
+ const unparsedScripts = [];
195
+ const rewrittenScripts = Object.fromEntries(
196
+ Object.entries(scripts).map(([name, command]) => {
197
+ const rewritten = command.replace(WEBPACK_COMMAND_PATTERN, "rspack").replace(ROOT_WEBPACK_CONFIG_ARG_PATTERN, "$1./rspack.config.ts").replace(TEMPLATE_WEBPACK_CONFIG_ARG_PATTERN, "$1./.config/rspack/rspack.config.ts");
198
+ if (REMAINING_WEBPACK_PATTERN.test(rewritten)) {
199
+ unparsedScripts.push([name, command]);
200
+ return [name, command];
201
+ }
202
+ return [name, rewritten];
203
+ })
204
+ );
205
+ return { scripts: rewrittenScripts, unparsedScripts };
206
+ }
207
+ const USER_BUILD_FILE_PATTERN = /^(webpack|rspack)\.[\w.-]*\.[cm]?[jt]s$/;
208
+ function readUserBuildFileSources(context) {
209
+ return context.readDir(".").filter((filePath) => USER_BUILD_FILE_PATTERN.test(filePath)).map((filePath) => context.getFile(filePath) ?? "");
210
+ }
211
+ function referencesPackage(source, packageName) {
212
+ const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
213
+ return new RegExp(`['"\`]${escapedName}(/[^'"\`]*)?['"\`]`).test(source);
214
+ }
215
+ const RSPACK_DEV_DEPENDENCIES = [
216
+ "@rspack/cli",
217
+ "@rspack/core",
218
+ "@types/ws",
219
+ "diagnostics-webpack-plugin",
220
+ "rspack-merge",
221
+ "rspack-plugin-virtual-module",
222
+ "ts-checker-rspack-plugin",
223
+ // style-loader and imports-loader still need webpack, and diagnostics-webpack-plugin peers on webpack ^5.106
224
+ "webpack",
225
+ "ws"
226
+ ];
227
+ const WEBPACK_ONLY_DEV_DEPENDENCIES = [
228
+ "copy-webpack-plugin",
229
+ "eslint-webpack-plugin",
230
+ "fork-ts-checker-webpack-plugin",
231
+ "swc-loader",
232
+ "terser-webpack-plugin",
233
+ "webpack-cli",
234
+ "webpack-livereload-plugin",
235
+ "webpack-subresource-integrity",
236
+ "webpack-virtual-modules"
237
+ ];
238
+ const BASE_CONFIG_DEV_DEPENDENCIES = [
239
+ "@swc/helpers",
240
+ "css-loader",
241
+ "imports-loader",
242
+ "replace-in-file-webpack-plugin",
243
+ "sass",
244
+ "sass-loader",
245
+ "style-loader"
246
+ ];
247
+ const PACKAGE_JSON_TEMPLATE_DATA = {
248
+ ...RSPACK_TEMPLATE_DATA_OVERRIDES,
249
+ pluginName: "plugin",
250
+ orgName: "org",
251
+ pluginType: "panel",
252
+ packageManagerName: "npm",
253
+ packageManagerVersion: "10.0.0",
254
+ scenesVersion: "*",
255
+ isAppType: false,
256
+ isNPM: true
257
+ };
258
+ function getTemplateDevDependencyVersions() {
259
+ const template = readFileSync(resolveTemplatePath("_package.json"), "utf-8");
260
+ const rendered = JSON.parse(
261
+ renderHandlebarsTemplate(template, PACKAGE_JSON_TEMPLATE_DATA)
262
+ );
263
+ return rendered.devDependencies ?? {};
264
+ }
265
+ function pickVersions(names, templateVersions) {
266
+ return Object.fromEntries(
267
+ names.map((name) => {
268
+ const version = templateVersions[name];
269
+ if (!version) {
270
+ throw new Error(`The package.json template has no version for ${name}, which the rspack addition installs.`);
271
+ }
272
+ return [name, version];
273
+ })
274
+ );
275
+ }
276
+ function getMissingPackages(context, names) {
277
+ const packageJson = readJsonFile(context, "package.json");
278
+ const installed = { ...packageJson.dependencies, ...packageJson.devDependencies };
279
+ return names.filter((name) => !(name in installed));
280
+ }
281
+ function deleteWebpackTemplateFiles(context) {
282
+ for (const filePath of WEBPACK_TEMPLATE_FILES) {
283
+ if (context.doesFileExist(filePath)) {
284
+ context.deleteFile(filePath);
285
+ }
286
+ }
287
+ const leftoverFiles = context.readDir(".config/webpack");
288
+ if (leftoverFiles.length === 0) {
289
+ return [];
290
+ }
291
+ return [
292
+ "These files in .config/webpack were not created by create-plugin, so they were kept. Move anything you still need out of .config and port it to rspack:",
293
+ ...leftoverFiles.map((filePath) => ` ${filePath}`)
294
+ ];
295
+ }
296
+ function getRootRspackConfigStub(rootWebpackConfig) {
297
+ return `import type { Configuration } from '@rspack/core';
298
+ import { merge } from 'rspack-merge';
299
+
300
+ import grafanaConfig, { type Env } from './.config/rspack/rspack.config.ts';
301
+
302
+ // TODO(rspack): port the customisations in ./${rootWebpackConfig} to this file, then delete it.
303
+ // Run \`npx @grafana/create-plugin@latest add rspack --agent\` to have an AI agent do the port,
304
+ // or follow https://grafana.com/developers/plugin-tools/how-to-guides/extend-configurations
305
+ // This file is loaded as native ESM, so CommonJS globals are not available. Use import.meta.dirname,
306
+ // createRequire(import.meta.url) and .ts extensions on relative imports.
307
+ throw new Error(
308
+ '[rspack] ${rootWebpackConfig} has not been ported to rspack.config.ts yet. ' +
309
+ 'Run \`npx @grafana/create-plugin@latest add rspack --agent\` or port it by hand, then remove this error.'
310
+ );
311
+
312
+ const config = async (env: Env): Promise<Configuration> => {
313
+ const baseConfig = await grafanaConfig(env);
314
+
315
+ return merge(baseConfig, {});
316
+ };
317
+
318
+ export default config;
319
+ `;
320
+ }
321
+
322
+ export { BASE_CONFIG_DEV_DEPENDENCIES, RSPACK_DEV_DEPENDENCIES, WEBPACK_ONLY_DEV_DEPENDENCIES, rspack as default, getTemplateDevDependencyVersions };
@@ -34,7 +34,7 @@ Before changing any file, print a short plan of what you intend to do so the use
34
34
 
35
35
  <scope_rules>
36
36
  Make only the changes the addition instructions require. Do not run package installs, builds, or tests unless the instructions ask for them. Run the repository formatter on files you touched if one is configured. Do not reformat files you did not change.
37
- When a check fails (typecheck, lint, build or tests), fix the cause. Never make it pass by suppressing it: no disable comments, skipped or deleted tests, loosened compiler or lint settings, or changed scripts. If you cannot fix the cause, leave it failing and say so in your summary.
37
+ When a check fails (typecheck, lint, build or tests), fix the cause. Never hide a failure: no disable comments, skipped or deleted tests, loosened compiler or lint settings, or scripts changed to skip the check. If the addition instructions say how to set aside something that cannot be applied, such as commenting it out under a \`TODO(<addition-name>):\` marker, follow them. Otherwise leave the check failing and say so in your summary.
38
38
  </scope_rules>
39
39
 
40
40
  <handoff_contract>
@@ -108,11 +108,23 @@ var defaultMigrations = [
108
108
  version: "7.13.0",
109
109
  description: "Replace eslint-webpack-plugin with diagnostics-webpack-plugin in the webpack config. eslint-webpack-plugin depends on micromatch, which pulls in the braces dependency flagged by vulnerability scanners.",
110
110
  scriptPath: import.meta.resolve("./scripts/018-replace-eslint-webpack-plugin.js")
111
+ },
112
+ {
113
+ name: "019-remove-glob-dependency",
114
+ version: "7.17.0",
115
+ description: "Replace the glob package with the Node.js built-in fs.glob in bundler utils.",
116
+ scriptPath: import.meta.resolve("./scripts/019-remove-glob-dependency.js")
117
+ },
118
+ {
119
+ name: "020-rename-rspack-feature-flag",
120
+ version: "7.18.0",
121
+ description: "Rename the useExperimentalRspack feature flag to useRspack in .config/.cprc.json, and remove it from the plugin .cprc.json.",
122
+ scriptPath: import.meta.resolve("./scripts/020-rename-rspack-feature-flag.js")
111
123
  }
112
124
  // Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
113
125
  // for those written before the switch to updates as migrations.
114
126
  //
115
- // New migrations never pick a version: copy the unreleased version line from ../AGENTS.md exactly.
127
+ // New migrations should never specify a version: copy the unreleased version line from ../AGENTS.md exactly.
116
128
  // Do not paste it into a comment in this file, as release-please rewrites every line that carries it.
117
129
  ];
118
130
 
@@ -0,0 +1,93 @@
1
+ import { parseAsTypescript, findImports, findNamedImport, findFunctionDeclaration, callsFunction, insertImports, createImport, printAST } from '../../utils.ast.js';
2
+ import { removeDependenciesFromPackageJson, migrationsDebug } from '../../utils.js';
3
+
4
+ const OLD_PACKAGE_NAME = "glob";
5
+ const NEW_PACKAGE_NAME = "node:fs/promises";
6
+ const UTILS_PATHS = [".config/webpack/utils.ts", ".config/bundler/utils.ts"];
7
+ const GET_ENTRIES_SOURCE = `export async function getEntries() {
8
+ const plugins: string[][] = [];
9
+ for await (const pluginJson of glob('**/src/**/plugin.json')) {
10
+ const folder = path.dirname(pluginJson);
11
+ const modules: string[] = [];
12
+ for await (const module of glob(\`\${folder}/module.{ts,tsx,js,jsx}\`)) {
13
+ modules.push(path.resolve(module));
14
+ }
15
+ plugins.push(modules);
16
+ }
17
+
18
+ return plugins.reduce<Record<string, string>>((result, modules) => {
19
+ return modules.reduce((innerResult, module) => {
20
+ const pluginPath = path.dirname(module);
21
+ const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\\/?/i, '');
22
+ const entryName = pluginName === '' ? 'module' : \`\${pluginName}/module\`;
23
+
24
+ innerResult[entryName] = module;
25
+ return innerResult;
26
+ }, result);
27
+ }, {});
28
+ }`;
29
+ function migrate(context) {
30
+ const updatedPaths = UTILS_PATHS.filter((filePath) => replaceGlobWithFsGlob(context, filePath));
31
+ if (updatedPaths.length === 0) {
32
+ return context;
33
+ }
34
+ const isStillUsed = UTILS_PATHS.some((filePath) => {
35
+ const source = context.getFile(filePath);
36
+ if (!source) {
37
+ return false;
38
+ }
39
+ const parsed = parseAsTypescript(source);
40
+ return !parsed.success || findImports(parsed.ast, OLD_PACKAGE_NAME).length > 0;
41
+ });
42
+ if (context.doesFileExist("package.json") && !isStillUsed) {
43
+ removeDependenciesFromPackageJson(context, [OLD_PACKAGE_NAME], [OLD_PACKAGE_NAME]);
44
+ }
45
+ return context;
46
+ }
47
+ function replaceGlobWithFsGlob(context, utilsPath) {
48
+ if (!context.doesFileExist(utilsPath)) {
49
+ return false;
50
+ }
51
+ const source = context.getFile(utilsPath);
52
+ if (!source || !source.includes(OLD_PACKAGE_NAME)) {
53
+ return false;
54
+ }
55
+ const parsed = parseAsTypescript(source);
56
+ if (!parsed.success) {
57
+ migrationsDebug(`Failed to parse ${utilsPath}. Error: ${parsed.error.message}`);
58
+ return false;
59
+ }
60
+ const globImport = findNamedImport(parsed.ast, OLD_PACKAGE_NAME, "glob");
61
+ if (!globImport) {
62
+ return false;
63
+ }
64
+ const getEntries = findFunctionDeclaration(parsed.ast, "getEntries");
65
+ if (!getEntries) {
66
+ return false;
67
+ }
68
+ if (!callsFunction(getEntries.body, "glob")) {
69
+ return false;
70
+ }
71
+ const { declaration, specifier } = globImport;
72
+ declaration.specifiers = (declaration.specifiers ?? []).filter((candidate) => candidate !== specifier);
73
+ if (declaration.specifiers.length === 0) {
74
+ parsed.ast.program.body.splice(parsed.ast.program.body.indexOf(declaration), 1);
75
+ }
76
+ insertImports(parsed.ast, [createImport({ named: [{ name: "glob" }] }, NEW_PACKAGE_NAME)]);
77
+ getEntries.body = getFunctionBody(GET_ENTRIES_SOURCE);
78
+ context.updateFile(utilsPath, printAST(parsed.ast));
79
+ return true;
80
+ }
81
+ function getFunctionBody(source) {
82
+ const parsed = parseAsTypescript(source);
83
+ if (!parsed.success) {
84
+ throw parsed.error;
85
+ }
86
+ const [statement] = parsed.ast.program.body;
87
+ if (statement?.type !== "ExportNamedDeclaration" || statement.declaration?.type !== "FunctionDeclaration") {
88
+ throw new Error("Failed to parse target function body");
89
+ }
90
+ return statement.declaration.body;
91
+ }
92
+
93
+ export { migrate as default };
@@ -0,0 +1,8 @@
1
+ import { replaceExperimentalRspackFlag } from '../../utils.rspack.js';
2
+
3
+ function migrate(context) {
4
+ replaceExperimentalRspackFlag(context, { enable: false });
5
+ return context;
6
+ }
7
+
8
+ export { migrate as default };