@grafana/create-plugin 6.1.1 → 6.2.0-canary.2233.18586197736.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 CHANGED
@@ -1,15 +1,3 @@
1
- # v6.1.1 (Wed Oct 15 2025)
2
-
3
- #### 🐛 Bug Fix
4
-
5
- - Fix: Use proper install command for legacy plugin migrations [#2221](https://github.com/grafana/plugin-tools/pull/2221) ([@sunker](https://github.com/sunker))
6
-
7
- #### Authors: 1
8
-
9
- - Erik Sundell ([@sunker](https://github.com/sunker))
10
-
11
- ---
12
-
13
1
  # v6.1.0 (Wed Oct 08 2025)
14
2
 
15
3
  #### 🚀 Enhancement
package/README.md CHANGED
@@ -122,6 +122,45 @@ For more information see our [documentation](https://grafana.com/developers/plug
122
122
 
123
123
  ---
124
124
 
125
+ ## Add optional features to your existing plugin
126
+
127
+ You can add optional features to your plugin using the `add` command. This allows you to enhance your plugin with additional capabilities without starting from scratch.
128
+
129
+ ### Add internationalization (i18n) support
130
+
131
+ Add translation support to make your plugin available in multiple languages:
132
+
133
+ ```bash
134
+ # Run this command from the root of your plugin
135
+ cd ./my-plugin
136
+
137
+ npx @grafana/create-plugin@latest add i18n
138
+ ```
139
+
140
+ This will:
141
+
142
+ - Update your `plugin.json` with the selected languages
143
+ - Create locale folders and translation files
144
+ - Add the necessary dependencies to `package.json`
145
+ - Configure your docker-compose.yaml with the required feature toggle
146
+ - Add i18n imports to your module file
147
+ - Set up the i18n extraction script
148
+
149
+ The command will prompt you to select which locales you want to support. You can choose from common locales like:
150
+
151
+ - English (US) - `en-US`
152
+ - Spanish (Spain) - `es-ES`
153
+ - French (France) - `fr-FR`
154
+ - German (Germany) - `de-DE`
155
+ - Swedish (Sweden) - `sv-SE`
156
+ - And more...
157
+
158
+ You can also add custom locale codes during the interactive prompt.
159
+
160
+ For more information about plugin internationalization, see our [documentation](https://grafana.com/developers/plugin-tools/how-to-guides/plugin-internationalization).
161
+
162
+ ---
163
+
125
164
  ## Contributing
126
165
 
127
166
  We are always grateful for contributions! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information.
@@ -0,0 +1,11 @@
1
+ var defaultAdditions = {
2
+ additions: {
3
+ i18n: {
4
+ name: "i18n",
5
+ description: "Add internationalization (i18n) support to your plugin",
6
+ scriptPath: "./scripts/add-i18n.js"
7
+ }
8
+ }
9
+ };
10
+
11
+ export { defaultAdditions as default };
@@ -0,0 +1,48 @@
1
+ import { additionsDebug, printChanges } from './utils.js';
2
+ import defaultAdditions from './additions.js';
3
+ import { Context } from '../migrations/context.js';
4
+ import { gitCommitNoVerify } from '../utils/utils.git.js';
5
+ import { output } from '../utils/utils.console.js';
6
+ import { formatFiles, flushChanges, installNPMDependencies } from '../migrations/utils.js';
7
+
8
+ function getAvailableAdditions(additions = defaultAdditions.additions) {
9
+ return additions;
10
+ }
11
+ function getAdditionByName(name, additions = defaultAdditions.additions) {
12
+ return additions[name];
13
+ }
14
+ async function runAddition(addition, additionOptions = {}, runOptions = {}) {
15
+ const basePath = process.cwd();
16
+ output.log({
17
+ title: `Running addition: ${addition.name}`,
18
+ body: [addition.description]
19
+ });
20
+ try {
21
+ const context = new Context(basePath);
22
+ const updatedContext = await executeAddition(addition, context, additionOptions);
23
+ const shouldCommit = runOptions.commitChanges && updatedContext.hasChanges();
24
+ additionsDebug(`context for "${addition.name} (${addition.scriptPath})":`);
25
+ additionsDebug("%O", updatedContext.listChanges());
26
+ await formatFiles(updatedContext);
27
+ flushChanges(updatedContext);
28
+ printChanges(updatedContext, addition.name, addition);
29
+ installNPMDependencies(updatedContext);
30
+ if (shouldCommit) {
31
+ await gitCommitNoVerify(`chore: add ${addition.name} support via create-plugin`);
32
+ }
33
+ output.success({
34
+ title: `Successfully added ${addition.name} to your plugin.`
35
+ });
36
+ } catch (error) {
37
+ if (error instanceof Error) {
38
+ throw new Error(`Error running addition "${addition.name} (${addition.scriptPath})": ${error.message}`);
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ async function executeAddition(addition, context, options = {}) {
44
+ const module = await import(addition.scriptPath);
45
+ return module.default(context, options);
46
+ }
47
+
48
+ export { executeAddition, getAdditionByName, getAvailableAdditions, runAddition };
@@ -0,0 +1,28 @@
1
+ export { addDependenciesToPackageJson, flushChanges, formatFiles, installNPMDependencies } from '../migrations/utils.js';
2
+ import chalk from 'chalk';
3
+ import { debug } from '../utils/utils.cli.js';
4
+ import { output } from '../utils/utils.console.js';
5
+
6
+ const additionsDebug = debug.extend("additions");
7
+ function printChanges(context, key, addition) {
8
+ const changes = context.listChanges();
9
+ const lines = [];
10
+ for (const [filePath, { changeType }] of Object.entries(changes)) {
11
+ if (changeType === "add") {
12
+ lines.push(`${chalk.green("ADD")} ${filePath}`);
13
+ } else if (changeType === "update") {
14
+ lines.push(`${chalk.yellow("UPDATE")} ${filePath}`);
15
+ } else if (changeType === "delete") {
16
+ lines.push(`${chalk.red("DELETE")} ${filePath}`);
17
+ }
18
+ }
19
+ output.addHorizontalLine("gray");
20
+ output.logSingleLine(`${key} (${addition.description})`);
21
+ if (lines.length === 0) {
22
+ output.logSingleLine("No changes were made");
23
+ } else {
24
+ output.log({ title: "Changes:", withPrefix: false, body: output.bulletList(lines) });
25
+ }
26
+ }
27
+
28
+ export { additionsDebug, printChanges };
package/dist/bin/run.js CHANGED
@@ -4,8 +4,9 @@ import { update } from '../commands/update.command.js';
4
4
  import { migrate } from '../commands/migrate.command.js';
5
5
  import { version } from '../commands/version.command.js';
6
6
  import { provisioning } from '../commands/provisioning.command.js';
7
- import { isUnsupportedPlatform } from '../utils/utils.os.js';
7
+ import { add } from '../commands/add.command.js';
8
8
  import { argv, commandName } from '../utils/utils.cli.js';
9
+ import { isUnsupportedPlatform } from '../utils/utils.os.js';
9
10
  import { output } from '../utils/utils.console.js';
10
11
 
11
12
  if (isUnsupportedPlatform()) {
@@ -21,7 +22,8 @@ const commands = {
21
22
  generate,
22
23
  update,
23
24
  version,
24
- provisioning
25
+ provisioning,
26
+ add
25
27
  };
26
28
  const command = commands[commandName] || "generate";
27
29
  command(argv);
@@ -0,0 +1,72 @@
1
+ import Enquirer from 'enquirer';
2
+ import { output } from '../../utils/utils.console.js';
3
+
4
+ const COMMON_LOCALES = [
5
+ { name: "en-US", message: "English (US)" },
6
+ { name: "es-ES", message: "Spanish (Spain)" },
7
+ { name: "fr-FR", message: "French (France)" },
8
+ { name: "de-DE", message: "German (Germany)" },
9
+ { name: "zh-Hans", message: "Chinese (Simplified)" },
10
+ { name: "pt-BR", message: "Portuguese (Brazil)" },
11
+ { name: "sv-SE", message: "Swedish (Sweden)" },
12
+ { name: "nl-NL", message: "Dutch (Netherlands)" },
13
+ { name: "ja-JP", message: "Japanese (Japan)" },
14
+ { name: "it-IT", message: "Italian (Italy)" }
15
+ ];
16
+ async function promptI18nOptions() {
17
+ const enquirer = new Enquirer();
18
+ output.log({
19
+ title: "Configure internationalization (i18n) for your plugin",
20
+ body: [
21
+ "Select the locales you want to support. At least one locale must be selected.",
22
+ "Use space to select, enter to continue."
23
+ ]
24
+ });
25
+ const localeChoices = COMMON_LOCALES.map((locale) => ({
26
+ name: locale.name,
27
+ message: locale.message,
28
+ value: locale.name
29
+ }));
30
+ let selectedLocales = [];
31
+ try {
32
+ const result = await enquirer.prompt({
33
+ type: "multiselect",
34
+ name: "locales",
35
+ message: "Select locales to support:",
36
+ choices: localeChoices,
37
+ initial: [0],
38
+ // Pre-select en-US by default
39
+ validate(value) {
40
+ if (value.length === 0) {
41
+ return "At least one locale must be selected";
42
+ }
43
+ return true;
44
+ }
45
+ });
46
+ selectedLocales = result.locales;
47
+ } catch (error) {
48
+ output.warning({ title: "Addition cancelled by user." });
49
+ process.exit(0);
50
+ }
51
+ try {
52
+ const addMoreResult = await enquirer.prompt({
53
+ type: "input",
54
+ name: "additionalLocales",
55
+ message: 'Enter additional locale codes (comma-separated, e.g., "ko-KR,ru-RU") or press enter to skip:'
56
+ });
57
+ const additionalLocalesInput = addMoreResult.additionalLocales;
58
+ if (additionalLocalesInput && additionalLocalesInput.trim()) {
59
+ const additionalLocales = additionalLocalesInput.split(",").map((locale) => locale.trim()).filter((locale) => locale.length > 0 && !selectedLocales.includes(locale));
60
+ selectedLocales.push(...additionalLocales);
61
+ }
62
+ } catch (error) {
63
+ }
64
+ output.log({
65
+ title: `Selected locales: ${selectedLocales.join(", ")}`
66
+ });
67
+ return {
68
+ locales: selectedLocales
69
+ };
70
+ }
71
+
72
+ export { promptI18nOptions };
@@ -0,0 +1,92 @@
1
+ import { getAvailableAdditions, getAdditionByName, runAddition } from '../additions/manager.js';
2
+ import { isGitDirectory, isGitDirectoryClean } from '../utils/utils.git.js';
3
+ import { isPluginDirectory } from '../utils/utils.plugin.js';
4
+ import { output } from '../utils/utils.console.js';
5
+ import { promptI18nOptions } from './add/prompts.js';
6
+
7
+ const add = async (argv) => {
8
+ const subCommand = argv._[1];
9
+ if (!subCommand) {
10
+ const availableAdditions = getAvailableAdditions();
11
+ const additionsList = Object.values(availableAdditions).map(
12
+ (addition2) => `${addition2.name} - ${addition2.description}`
13
+ );
14
+ output.error({
15
+ title: "No addition specified",
16
+ body: [
17
+ "Usage: npx @grafana/create-plugin add <addition-name>",
18
+ "",
19
+ "Available additions:",
20
+ ...output.bulletList(additionsList)
21
+ ]
22
+ });
23
+ process.exit(1);
24
+ }
25
+ await performPreAddChecks(argv);
26
+ const addition = getAdditionByName(subCommand);
27
+ if (!addition) {
28
+ const availableAdditions = getAvailableAdditions();
29
+ const additionsList = Object.values(availableAdditions).map((addition2) => addition2.name);
30
+ output.error({
31
+ title: `Unknown addition: ${subCommand}`,
32
+ body: ["Available additions:", ...output.bulletList(additionsList)]
33
+ });
34
+ process.exit(1);
35
+ }
36
+ try {
37
+ let options = {};
38
+ switch (addition.name) {
39
+ case "i18n":
40
+ options = await promptI18nOptions();
41
+ break;
42
+ default:
43
+ break;
44
+ }
45
+ const commitChanges = argv.commit;
46
+ await runAddition(addition, options, { commitChanges });
47
+ } catch (error) {
48
+ if (error instanceof Error) {
49
+ output.error({
50
+ title: "Addition failed",
51
+ body: [error.message]
52
+ });
53
+ }
54
+ process.exit(1);
55
+ }
56
+ };
57
+ async function performPreAddChecks(argv) {
58
+ if (!await isGitDirectory() && !argv.force) {
59
+ output.error({
60
+ title: "You are not inside a git directory",
61
+ body: [
62
+ `In order to proceed please run ${output.formatCode("git init")} in the root of your project and commit your changes.`,
63
+ `(This check is necessary to make sure that the changes are easy to revert and don't interfere with any changes you currently have.`,
64
+ `In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
65
+ ]
66
+ });
67
+ process.exit(1);
68
+ }
69
+ if (!await isGitDirectoryClean() && !argv.force) {
70
+ output.error({
71
+ title: "Please clean your repository working tree before adding features.",
72
+ body: [
73
+ "Commit your changes or stash them.",
74
+ `(This check is necessary to make sure that the changes are easy to revert and don't mess with any changes you currently have.`,
75
+ `In case you want to proceed as is please use the ${output.formatCode("--force")} flag.)`
76
+ ]
77
+ });
78
+ process.exit(1);
79
+ }
80
+ if (!isPluginDirectory() && !argv.force) {
81
+ output.error({
82
+ title: "Are you inside a plugin directory?",
83
+ body: [
84
+ `We couldn't find a "src/plugin.json" file under your current directory.`,
85
+ `(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.)`
86
+ ]
87
+ });
88
+ process.exit(1);
89
+ }
90
+ }
91
+
92
+ export { add };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "6.1.1",
3
+ "version": "6.2.0-canary.2233.18586197736.0",
4
4
  "repository": {
5
5
  "directory": "packages/create-plugin",
6
6
  "url": "https://github.com/grafana/plugin-tools"
@@ -61,5 +61,5 @@
61
61
  "engines": {
62
62
  "node": ">=20"
63
63
  },
64
- "gitHead": "6b891abe4df5c9c3c1e191b5286298182efa70a0"
64
+ "gitHead": "0e9c9708ed87727b5956b44fcdaa3bbc0d8221f8"
65
65
  }
@@ -0,0 +1,19 @@
1
+ export type AdditionMeta = {
2
+ name: string;
3
+ description: string;
4
+ scriptPath: string;
5
+ };
6
+
7
+ type Additions = {
8
+ additions: Record<string, AdditionMeta>;
9
+ };
10
+
11
+ export default {
12
+ additions: {
13
+ i18n: {
14
+ name: 'i18n',
15
+ description: 'Add internationalization (i18n) support to your plugin',
16
+ scriptPath: './scripts/add-i18n.js',
17
+ },
18
+ },
19
+ } as Additions;
@@ -0,0 +1,77 @@
1
+ import { additionsDebug, flushChanges, formatFiles, installNPMDependencies, printChanges } from './utils.js';
2
+ import defaultAdditions, { AdditionMeta } from './additions.js';
3
+
4
+ import { Context } from '../migrations/context.js';
5
+ import { gitCommitNoVerify } from '../utils/utils.git.js';
6
+ import { output } from '../utils/utils.console.js';
7
+
8
+ export type AdditionFn = (context: Context, options?: AdditionOptions) => Context | Promise<Context>;
9
+
10
+ export type AdditionOptions = Record<string, any>;
11
+
12
+ type RunAdditionOptions = {
13
+ commitChanges?: boolean;
14
+ };
15
+
16
+ export function getAvailableAdditions(
17
+ additions: Record<string, AdditionMeta> = defaultAdditions.additions
18
+ ): Record<string, AdditionMeta> {
19
+ return additions;
20
+ }
21
+
22
+ export function getAdditionByName(
23
+ name: string,
24
+ additions: Record<string, AdditionMeta> = defaultAdditions.additions
25
+ ): AdditionMeta | undefined {
26
+ return additions[name];
27
+ }
28
+
29
+ export async function runAddition(
30
+ addition: AdditionMeta,
31
+ additionOptions: AdditionOptions = {},
32
+ runOptions: RunAdditionOptions = {}
33
+ ): Promise<void> {
34
+ const basePath = process.cwd();
35
+
36
+ output.log({
37
+ title: `Running addition: ${addition.name}`,
38
+ body: [addition.description],
39
+ });
40
+
41
+ try {
42
+ const context = new Context(basePath);
43
+ const updatedContext = await executeAddition(addition, context, additionOptions);
44
+ const shouldCommit = runOptions.commitChanges && updatedContext.hasChanges();
45
+
46
+ additionsDebug(`context for "${addition.name} (${addition.scriptPath})":`);
47
+ additionsDebug('%O', updatedContext.listChanges());
48
+
49
+ await formatFiles(updatedContext);
50
+ flushChanges(updatedContext);
51
+ printChanges(updatedContext, addition.name, addition);
52
+
53
+ installNPMDependencies(updatedContext);
54
+
55
+ if (shouldCommit) {
56
+ await gitCommitNoVerify(`chore: add ${addition.name} support via create-plugin`);
57
+ }
58
+
59
+ output.success({
60
+ title: `Successfully added ${addition.name} to your plugin.`,
61
+ });
62
+ } catch (error) {
63
+ if (error instanceof Error) {
64
+ throw new Error(`Error running addition "${addition.name} (${addition.scriptPath})": ${error.message}`);
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ export async function executeAddition(
71
+ addition: AdditionMeta,
72
+ context: Context,
73
+ options: AdditionOptions = {}
74
+ ): Promise<Context> {
75
+ const module: { default: AdditionFn } = await import(addition.scriptPath);
76
+ return module.default(context, options);
77
+ }