Skip to content

Commit c7b2367

Browse files
committed
feat(create-plugin): implement 'add' command with pre-flight checks
- Add new 'add' command to CLI (npx @grafana/create-plugin add <feature>) - Implement pre-flight checks (git directory, clean working tree, plugin directory) - Add interactive prompts for i18n locale selection using enquirer - Support for multiple locale selection with multiselect UI - Allow custom locale codes to be entered manually - Register command in CLI entry point and command index
1 parent 5b2771f commit c7b2367

File tree

4 files changed

+207
-3
lines changed

4 files changed

+207
-3
lines changed

packages/create-plugin/src/bin/run.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env node
22

3-
import minimist from 'minimist';
4-
import { generate, update, migrate, version, provisioning } from '../commands/index.js';
5-
import { isUnsupportedPlatform } from '../utils/utils.os.js';
3+
import { add, generate, migrate, provisioning, update, version } from '../commands/index.js';
64
import { argv, commandName } from '../utils/utils.cli.js';
5+
6+
import { isUnsupportedPlatform } from '../utils/utils.os.js';
7+
import minimist from 'minimist';
78
import { output } from '../utils/utils.console.js';
89

910
// Exit early if operating system isn't supported.
@@ -23,6 +24,7 @@ const commands: Record<string, (argv: minimist.ParsedArgs) => void> = {
2324
update,
2425
version,
2526
provisioning,
27+
add,
2628
};
2729
const command = commands[commandName] || 'generate';
2830

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { getAdditionByName, getAvailableAdditions, runAddition } from '../additions/manager.js';
2+
import { isGitDirectory, isGitDirectoryClean } from '../utils/utils.git.js';
3+
4+
import { isPluginDirectory } from '../utils/utils.plugin.js';
5+
import minimist from 'minimist';
6+
import { output } from '../utils/utils.console.js';
7+
import { promptI18nOptions } from './add/prompts.js';
8+
9+
export const add = async (argv: minimist.ParsedArgs) => {
10+
const subCommand = argv._[1];
11+
12+
if (!subCommand) {
13+
const availableAdditions = getAvailableAdditions();
14+
const additionsList = Object.values(availableAdditions).map(
15+
(addition) => `${addition.name} - ${addition.description}`
16+
);
17+
18+
output.error({
19+
title: 'No addition specified',
20+
body: [
21+
'Usage: npx @grafana/create-plugin add <addition-name>',
22+
'',
23+
'Available additions:',
24+
...output.bulletList(additionsList),
25+
],
26+
});
27+
process.exit(1);
28+
}
29+
30+
await performPreAddChecks(argv);
31+
32+
const addition = getAdditionByName(subCommand);
33+
34+
if (!addition) {
35+
const availableAdditions = getAvailableAdditions();
36+
const additionsList = Object.values(availableAdditions).map((addition) => addition.name);
37+
38+
output.error({
39+
title: `Unknown addition: ${subCommand}`,
40+
body: ['Available additions:', ...output.bulletList(additionsList)],
41+
});
42+
process.exit(1);
43+
}
44+
45+
try {
46+
// Gather options based on the addition type
47+
let options = {};
48+
49+
switch (addition.name) {
50+
case 'i18n':
51+
options = await promptI18nOptions();
52+
break;
53+
default:
54+
break;
55+
}
56+
57+
const commitChanges = argv.commit;
58+
await runAddition(addition, options, { commitChanges });
59+
} catch (error) {
60+
if (error instanceof Error) {
61+
output.error({
62+
title: 'Addition failed',
63+
body: [error.message],
64+
});
65+
}
66+
process.exit(1);
67+
}
68+
};
69+
70+
async function performPreAddChecks(argv: minimist.ParsedArgs) {
71+
if (!(await isGitDirectory()) && !argv.force) {
72+
output.error({
73+
title: 'You are not inside a git directory',
74+
body: [
75+
`In order to proceed please run ${output.formatCode('git init')} in the root of your project and commit your changes.`,
76+
`(This check is necessary to make sure that the changes are easy to revert and don't interfere with any changes you currently have.`,
77+
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
78+
],
79+
});
80+
81+
process.exit(1);
82+
}
83+
84+
if (!(await isGitDirectoryClean()) && !argv.force) {
85+
output.error({
86+
title: 'Please clean your repository working tree before adding features.',
87+
body: [
88+
'Commit your changes or stash them.',
89+
`(This check is necessary to make sure that the changes are easy to revert and don't mess with any changes you currently have.`,
90+
`In case you want to proceed as is please use the ${output.formatCode('--force')} flag.)`,
91+
],
92+
});
93+
94+
process.exit(1);
95+
}
96+
97+
if (!isPluginDirectory() && !argv.force) {
98+
output.error({
99+
title: 'Are you inside a plugin directory?',
100+
body: [
101+
`We couldn't find a "src/plugin.json" file under your current directory.`,
102+
`(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.)`,
103+
],
104+
});
105+
106+
process.exit(1);
107+
}
108+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import Enquirer from 'enquirer';
2+
import { output } from '../../utils/utils.console.js';
3+
4+
// Common locales supported by Grafana
5+
// Reference: https://github.com/grafana/grafana/blob/main/packages/grafana-i18n/src/constants.ts
6+
const COMMON_LOCALES = [
7+
{ name: 'en-US', message: 'English (US)' },
8+
{ name: 'es-ES', message: 'Spanish (Spain)' },
9+
{ name: 'fr-FR', message: 'French (France)' },
10+
{ name: 'de-DE', message: 'German (Germany)' },
11+
{ name: 'zh-Hans', message: 'Chinese (Simplified)' },
12+
{ name: 'pt-BR', message: 'Portuguese (Brazil)' },
13+
{ name: 'sv-SE', message: 'Swedish (Sweden)' },
14+
{ name: 'nl-NL', message: 'Dutch (Netherlands)' },
15+
{ name: 'ja-JP', message: 'Japanese (Japan)' },
16+
{ name: 'it-IT', message: 'Italian (Italy)' },
17+
];
18+
19+
export type I18nOptions = {
20+
locales: string[];
21+
};
22+
23+
export async function promptI18nOptions(): Promise<I18nOptions> {
24+
const enquirer = new Enquirer();
25+
26+
output.log({
27+
title: 'Configure internationalization (i18n) for your plugin',
28+
body: [
29+
'Select the locales you want to support. At least one locale must be selected.',
30+
'Use space to select, enter to continue.',
31+
],
32+
});
33+
34+
const localeChoices = COMMON_LOCALES.map((locale) => ({
35+
name: locale.name,
36+
message: locale.message,
37+
value: locale.name,
38+
}));
39+
40+
let selectedLocales: string[] = [];
41+
42+
try {
43+
const result = (await enquirer.prompt({
44+
type: 'multiselect',
45+
name: 'locales',
46+
message: 'Select locales to support:',
47+
choices: localeChoices,
48+
initial: [0], // Pre-select en-US by default
49+
validate(value: string[]) {
50+
if (value.length === 0) {
51+
return 'At least one locale must be selected';
52+
}
53+
return true;
54+
},
55+
} as any)) as { locales: string[] };
56+
57+
selectedLocales = result.locales;
58+
} catch (error) {
59+
// User cancelled the prompt
60+
output.warning({ title: 'Addition cancelled by user.' });
61+
process.exit(0);
62+
}
63+
64+
// Ask if they want to add additional locales
65+
try {
66+
const addMoreResult = (await enquirer.prompt({
67+
type: 'input',
68+
name: 'additionalLocales',
69+
message: 'Enter additional locale codes (comma-separated, e.g., "ko-KR,ru-RU") or press enter to skip:',
70+
} as any)) as { additionalLocales: string };
71+
72+
const additionalLocalesInput = addMoreResult.additionalLocales;
73+
74+
if (additionalLocalesInput && additionalLocalesInput.trim()) {
75+
const additionalLocales = additionalLocalesInput
76+
.split(',')
77+
.map((locale: string) => locale.trim())
78+
.filter((locale: string) => locale.length > 0 && !selectedLocales.includes(locale));
79+
80+
selectedLocales.push(...additionalLocales);
81+
}
82+
} catch (error) {
83+
// User cancelled, just continue with what we have
84+
}
85+
86+
output.log({
87+
title: `Selected locales: ${selectedLocales.join(', ')}`,
88+
});
89+
90+
return {
91+
locales: selectedLocales,
92+
};
93+
}

packages/create-plugin/src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './update.command.js';
33
export * from './migrate.command.js';
44
export * from './version.command.js';
55
export * from './provisioning.command.js';
6+
export * from './add.command.js';

0 commit comments

Comments
 (0)