@grafana/create-plugin 2.10.0 → 2.11.0-canary.602.81e7a0e.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 +19 -0
- package/dist/commands/{generate-actions → generate}/prettify-files.js +8 -11
- package/dist/commands/{generate-actions → generate}/print-success-message.js +2 -2
- package/dist/commands/generate/prompt-user.js +145 -0
- package/dist/commands/{generate-actions → generate}/update-go-sdk-and-packages.js +10 -13
- package/dist/commands/generate.command.js +225 -9
- package/dist/utils/utils.console.js +5 -2
- package/dist/utils/utils.files.js +58 -1
- package/dist/utils/utils.handlebars.js +14 -3
- package/package.json +22 -15
- package/src/commands/{generate-actions → generate}/prettify-files.ts +1 -5
- package/src/commands/{generate-actions → generate}/print-success-message.ts +2 -3
- package/src/commands/generate/prompt-user.ts +102 -0
- package/src/commands/{generate-actions → generate}/update-go-sdk-and-packages.ts +1 -6
- package/src/commands/generate.command.ts +194 -11
- package/src/utils/utils.console.ts +5 -2
- package/src/utils/utils.files.ts +10 -0
- package/src/utils/utils.handlebars.ts +17 -4
- package/templates/app/README.md +6 -6
- package/templates/common/.config/webpack/webpack.config.ts +1 -0
- package/templates/datasource/README.md +3 -3
- package/templates/panel/README.md +2 -2
- package/templates/scenes-app/README.md +2 -2
- package/dist/commands/generate.plopfile.js +0 -214
- package/scripts/generate-app-backend.js +0 -29
- package/scripts/generate-app.js +0 -29
- package/scripts/generate-datasource-with-backend.js +0 -29
- package/scripts/generate-datasource-without-backend.js +0 -29
- package/scripts/generate-panel.js +0 -28
- package/scripts/generate-scenes-app.js +0 -29
- package/src/commands/generate.plopfile.ts +0 -272
|
@@ -27,12 +27,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
var _this = this;
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.renderHandlebarsTemplate = exports.
|
|
30
|
+
exports.renderHandlebarsTemplate = exports.normalizeId = exports.ifEq = void 0;
|
|
31
31
|
var handlebars_1 = __importDefault(require("handlebars"));
|
|
32
32
|
var changeCase = __importStar(require("change-case"));
|
|
33
33
|
var title_case_1 = __importDefault(require("title-case"));
|
|
34
34
|
var upper_case_1 = __importDefault(require("upper-case"));
|
|
35
35
|
var lower_case_1 = __importDefault(require("lower-case"));
|
|
36
|
+
var constants_1 = require("../constants");
|
|
37
|
+
var fs_1 = require("fs");
|
|
38
|
+
var path_1 = require("path");
|
|
36
39
|
var ifEq = function (a, b, options) {
|
|
37
40
|
return a === b ? options.fn(_this) : options.inverse(_this);
|
|
38
41
|
};
|
|
@@ -45,6 +48,8 @@ var normalizeId = function (pluginName, orgName, type) {
|
|
|
45
48
|
return changeCase.lowerCase(newOrgName) + '-' + changeCase.lowerCase(newPluginName) + "-".concat(type);
|
|
46
49
|
};
|
|
47
50
|
exports.normalizeId = normalizeId;
|
|
51
|
+
registerHandlebarsHelpers();
|
|
52
|
+
registerHandlebarsPartials();
|
|
48
53
|
function registerHandlebarsHelpers() {
|
|
49
54
|
var helpers = {
|
|
50
55
|
camelCase: changeCase.camelCase,
|
|
@@ -68,9 +73,15 @@ function registerHandlebarsHelpers() {
|
|
|
68
73
|
return handlebars_1.default.registerHelper(helperName, helpers[helperName]);
|
|
69
74
|
});
|
|
70
75
|
}
|
|
71
|
-
|
|
76
|
+
function registerHandlebarsPartials() {
|
|
77
|
+
var partialFiles = (0, fs_1.readdirSync)(constants_1.PARTIALS_DIR);
|
|
78
|
+
partialFiles.forEach(function (fileName) {
|
|
79
|
+
var name = (0, path_1.basename)(fileName, '.md');
|
|
80
|
+
var template = (0, fs_1.readFileSync)((0, path_1.join)(constants_1.PARTIALS_DIR, fileName), 'utf-8');
|
|
81
|
+
handlebars_1.default.registerPartial(name, template);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
72
84
|
function renderHandlebarsTemplate(template, data) {
|
|
73
|
-
registerHandlebarsHelpers();
|
|
74
85
|
return handlebars_1.default.compile(template)(data);
|
|
75
86
|
}
|
|
76
87
|
exports.renderHandlebarsTemplate = renderHandlebarsTemplate;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0-canary.602.81e7a0e.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/create-plugin",
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"clean": "rm -rf ./dist ./generated",
|
|
18
18
|
"clean-generated": "find ./generated -not -path \"./generated/node_modules*\" -not -path \"./generated\" -not -path \"./generated/package-lock.json\" -maxdepth 1 -print0 | xargs -0 -I {} rm -rf {}",
|
|
19
19
|
"build": "npm run clean && tsc && chmod +x ./dist/bin/run.js",
|
|
20
|
-
"dev": "
|
|
21
|
-
"dev-app": "
|
|
22
|
-
"dev-scenes-app": "
|
|
23
|
-
"dev-panel": "
|
|
24
|
-
"dev-datasource": "
|
|
25
|
-
"generate-app": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
26
|
-
"generate-scenes-app": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
27
|
-
"generate-app-backend": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
28
|
-
"generate-panel": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
29
|
-
"generate-datasource": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
30
|
-
"generate-datasource-backend": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./
|
|
20
|
+
"dev": "nodemon --exec 'tsc'",
|
|
21
|
+
"dev-app": "nodemon --exec 'npm run generate-app'",
|
|
22
|
+
"dev-scenes-app": "nodemon --exec 'npm run generate-scenes-app'",
|
|
23
|
+
"dev-panel": "nodemon --exec 'npm run generate-panel'",
|
|
24
|
+
"dev-datasource": "nodemon --exec 'npm run generate-datasource'",
|
|
25
|
+
"generate-app": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample app' --orgName='sample-org' --pluginDescription='This is a sample app.' --pluginType='app' --no-hasBackend --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
26
|
+
"generate-scenes-app": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample scenesapp' --orgName='sample-org' --pluginDescription='This is a sample scenes app.' --pluginType='scenesapp' --no-hasBackend --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
27
|
+
"generate-app-backend": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample app' --orgName='sample-org' --pluginDescription='This is a sample backend app.' --pluginType='app' --hasBackend --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
28
|
+
"generate-panel": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample panel' --orgName='sample-org' --pluginDescription='This is a sample panel.' --pluginType='panel' --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
29
|
+
"generate-datasource": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample datasource' --orgName='sample-org' --pluginDescription='This is a sample datasource.' --pluginType='datasource' --no-hasBackend --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
30
|
+
"generate-datasource-backend": "tsc && npm run clean-generated && CREATE_PLUGIN_DEV=true node ./dist/bin/run.js --pluginName='Sample datasource' --orgName='sample-org' --pluginDescription='This is a sample datasource.' --pluginType='datasource' --hasBackend --hasGithubWorkflows --hasGithubLevitateWorkflow",
|
|
31
31
|
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./src",
|
|
32
32
|
"lint:fix": "npm run lint -- --fix",
|
|
33
33
|
"test": "jest",
|
|
@@ -38,14 +38,18 @@
|
|
|
38
38
|
"@types/mkdirp": "^1.0.2",
|
|
39
39
|
"@types/semver": "^7.3.9",
|
|
40
40
|
"@types/which": "^2.0.2",
|
|
41
|
-
"
|
|
41
|
+
"chalk": "^4.1.2",
|
|
42
|
+
"change-case": "^3.1.0",
|
|
43
|
+
"enquirer": "^2.4.1",
|
|
42
44
|
"find-up": "^5.0.0",
|
|
43
45
|
"glob": "^7.1.7",
|
|
46
|
+
"handlebars": "^4.7.8",
|
|
44
47
|
"marked": "^10.0.0",
|
|
45
48
|
"marked-terminal": "^5.1.1",
|
|
49
|
+
"minimist": "^1.2.8",
|
|
46
50
|
"mkdirp": "^1.0.4",
|
|
47
|
-
"plop": "^2.7.4",
|
|
48
51
|
"semver": "^7.3.5",
|
|
52
|
+
"title-case": "^2.1.1",
|
|
49
53
|
"which": "^3.0.0"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
@@ -61,10 +65,13 @@
|
|
|
61
65
|
"ext": "*",
|
|
62
66
|
"events": {
|
|
63
67
|
"start": "cls || clear"
|
|
68
|
+
},
|
|
69
|
+
"env": {
|
|
70
|
+
"CREATE_PLUGIN_DEV": true
|
|
64
71
|
}
|
|
65
72
|
},
|
|
66
73
|
"engines": {
|
|
67
74
|
"node": ">=20"
|
|
68
75
|
},
|
|
69
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "81e7a0e9e06ec8f596722bd14f25f9ae6a640803"
|
|
70
77
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { exec as nodeExec } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import { getExportPath } from '../../utils/utils.path';
|
|
5
|
-
import { CliArgs } from '../types';
|
|
6
4
|
|
|
7
5
|
const exec = promisify(nodeExec);
|
|
8
6
|
|
|
9
|
-
export async function prettifyFiles(
|
|
10
|
-
const exportPath = getExportPath(pluginName, orgName, pluginType);
|
|
11
|
-
|
|
7
|
+
export async function prettifyFiles(exportPath: string) {
|
|
12
8
|
if (!fs.existsSync(exportPath)) {
|
|
13
9
|
return '';
|
|
14
10
|
}
|
|
@@ -19,8 +19,7 @@ export function printGenerateSuccessMessage(answers: CliArgs) {
|
|
|
19
19
|
'- Open http://localhost:3000 in your browser to create a dashboard to begin developing your plugin.',
|
|
20
20
|
];
|
|
21
21
|
|
|
22
|
-
const msg =
|
|
23
|
-
Congratulations on scaffolding a Grafana ${answers.pluginType} plugin! 🚀
|
|
22
|
+
const msg = `\n# Congratulations on scaffolding a Grafana ${answers.pluginType} plugin! 🚀
|
|
24
23
|
|
|
25
24
|
## What's next?
|
|
26
25
|
|
|
@@ -32,5 +31,5 @@ _Note: We strongly recommend creating a new Git repository by running \`git init
|
|
|
32
31
|
- Learn more about Grafana Plugin Development at https://grafana.com/developers/plugin-tools
|
|
33
32
|
`;
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
console.log(displayAsMarkdown(msg));
|
|
36
35
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import minimist from 'minimist';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
import { PLUGIN_TYPES } from '../../constants';
|
|
4
|
+
import { CliArgs } from '../types';
|
|
5
|
+
|
|
6
|
+
export async function promptUser(argv: minimist.ParsedArgs) {
|
|
7
|
+
let answers: Partial<CliArgs> = {};
|
|
8
|
+
const enquirer = new Enquirer();
|
|
9
|
+
|
|
10
|
+
for (const prompt of prompts) {
|
|
11
|
+
const { name, shouldPrompt } = prompt;
|
|
12
|
+
|
|
13
|
+
if (argv.hasOwnProperty(name)) {
|
|
14
|
+
answers = { ...answers, [name]: argv[name] };
|
|
15
|
+
} else {
|
|
16
|
+
if (typeof shouldPrompt === 'function' && !shouldPrompt(answers)) {
|
|
17
|
+
continue;
|
|
18
|
+
} else {
|
|
19
|
+
const result = await enquirer.prompt(prompt);
|
|
20
|
+
answers = { ...answers, ...result };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return answers as CliArgs;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Prompt = {
|
|
29
|
+
name: keyof CliArgs;
|
|
30
|
+
type: string | (() => string);
|
|
31
|
+
message: string | (() => string) | (() => Promise<string>);
|
|
32
|
+
validate?: (value: string) => boolean | string | Promise<boolean | string>;
|
|
33
|
+
initial?: any;
|
|
34
|
+
choices?: Array<string | Choice>;
|
|
35
|
+
shouldPrompt?: ((state: object) => boolean | Promise<boolean>) | boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Choice = {
|
|
39
|
+
name: string;
|
|
40
|
+
message?: string;
|
|
41
|
+
value?: unknown;
|
|
42
|
+
hint?: string;
|
|
43
|
+
role?: string;
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
disabled?: boolean | string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const prompts: Prompt[] = [
|
|
49
|
+
{
|
|
50
|
+
name: 'pluginName',
|
|
51
|
+
type: 'input',
|
|
52
|
+
message: 'What is going to be the name of your plugin?',
|
|
53
|
+
validate: (value: string) => {
|
|
54
|
+
if (/.+/.test(value)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return 'Plugin name is required';
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'orgName',
|
|
62
|
+
type: 'input',
|
|
63
|
+
message: 'What is the organization name of your plugin?',
|
|
64
|
+
validate: (value: string) => {
|
|
65
|
+
if (/.+/.test(value)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return 'Organization name is required';
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'pluginDescription',
|
|
73
|
+
type: 'input',
|
|
74
|
+
message: 'How would you describe your plugin?',
|
|
75
|
+
initial: '',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'pluginType',
|
|
79
|
+
type: 'select',
|
|
80
|
+
choices: [PLUGIN_TYPES.app, PLUGIN_TYPES.datasource, PLUGIN_TYPES.panel, PLUGIN_TYPES.scenes],
|
|
81
|
+
message: 'What type of plugin would you like?',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'hasBackend',
|
|
85
|
+
type: 'confirm',
|
|
86
|
+
message: 'Do you want a backend part of your plugin?',
|
|
87
|
+
initial: false,
|
|
88
|
+
shouldPrompt: (answers: CliArgs) => answers.pluginType !== PLUGIN_TYPES.panel,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'hasGithubWorkflows',
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
message: 'Do you want to add Github CI and Release workflows?',
|
|
94
|
+
initial: false,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'hasGithubLevitateWorkflow',
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
message: 'Do you want to add a Github workflow for automatically checking "Grafana API compatibility" on PRs?',
|
|
100
|
+
initial: false,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import { getExportPath } from '../../utils/utils.path';
|
|
2
|
-
import { CliArgs } from '../types';
|
|
3
1
|
import which from 'which';
|
|
4
2
|
import fs from 'fs';
|
|
5
3
|
import { exec } from 'child_process';
|
|
6
4
|
|
|
7
5
|
const SDK_GO_MODULE = 'github.com/grafana/grafana-plugin-sdk-go';
|
|
8
6
|
|
|
9
|
-
export async function updateGoSdkAndModules(
|
|
10
|
-
// plugin directory
|
|
11
|
-
const exportPath = getExportPath(pluginName, orgName, pluginType);
|
|
12
|
-
|
|
7
|
+
export async function updateGoSdkAndModules(exportPath: string) {
|
|
13
8
|
// check if there is a go.mod file in exportPath
|
|
14
9
|
const goModPath = `${exportPath}/go.mod`;
|
|
15
10
|
if (!fs.existsSync(goModPath)) {
|
|
@@ -1,15 +1,198 @@
|
|
|
1
|
-
import
|
|
1
|
+
import glob from 'glob';
|
|
2
2
|
import minimist from 'minimist';
|
|
3
|
-
import
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { EXTRA_TEMPLATE_VARIABLES, IS_DEV, PLUGIN_TYPES, TEMPLATE_PATHS } from '../constants';
|
|
7
|
+
import { getConfig } from '../utils/utils.config';
|
|
8
|
+
import { printError } from '../utils/utils.console';
|
|
9
|
+
import { directoryExists, getExportFileName, isFile } from '../utils/utils.files';
|
|
10
|
+
import { normalizeId } from '../utils/utils.handlebars';
|
|
11
|
+
import { getPackageManagerFromUserAgent, getPackageManagerInstallCmd } from '../utils/utils.packageManager';
|
|
12
|
+
import { getExportPath } from '../utils/utils.path';
|
|
13
|
+
import { renderTemplateFromFile } from '../utils/utils.templates';
|
|
14
|
+
import { getVersion } from '../utils/utils.version';
|
|
15
|
+
import { prettifyFiles } from './generate/prettify-files';
|
|
16
|
+
import { printGenerateSuccessMessage } from './generate/print-success-message';
|
|
17
|
+
import { promptUser } from './generate/prompt-user';
|
|
18
|
+
import { updateGoSdkAndModules } from './generate/update-go-sdk-and-packages';
|
|
19
|
+
import { CliArgs, TemplateData } from './types';
|
|
4
20
|
|
|
5
21
|
export const generate = async (argv: minimist.ParsedArgs) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
const answers = await promptUser(argv);
|
|
23
|
+
const templateData = getTemplateData(answers);
|
|
24
|
+
const exportPath = getExportPath(answers.pluginName, answers.orgName, answers.pluginType);
|
|
25
|
+
const exportPathExists = await directoryExists(exportPath);
|
|
26
|
+
const exportPathIsPopulated = exportPathExists ? (await readdir(exportPath)).length > 0 : false;
|
|
27
|
+
|
|
28
|
+
// Prevent generation from writing to an existing, populated directory unless in DEV mode.
|
|
29
|
+
if (exportPathIsPopulated && !IS_DEV) {
|
|
30
|
+
printError(`**Aborting plugin scaffold. '${exportPath}' exists and contains files.**`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const actions = getTemplateActions({ templateData, exportPath });
|
|
35
|
+
const { changes, failures } = await generateFiles({ actions });
|
|
36
|
+
|
|
37
|
+
changes.forEach((change) => {
|
|
38
|
+
console.log(`${chalk.green('✔︎ ++')} ${change.path}`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
failures.forEach((failure) => {
|
|
42
|
+
printError(`${failure.error}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (answers.hasBackend) {
|
|
46
|
+
await execPostScaffoldFunction(updateGoSdkAndModules, exportPath);
|
|
47
|
+
}
|
|
48
|
+
await execPostScaffoldFunction(prettifyFiles, exportPath);
|
|
49
|
+
|
|
50
|
+
printGenerateSuccessMessage(answers);
|
|
15
51
|
};
|
|
52
|
+
|
|
53
|
+
function getTemplateData(answers: CliArgs) {
|
|
54
|
+
const { pluginName, orgName, pluginType } = answers;
|
|
55
|
+
const { features } = getConfig();
|
|
56
|
+
const currentVersion = getVersion();
|
|
57
|
+
const pluginId = normalizeId(pluginName, orgName, pluginType);
|
|
58
|
+
// Support the users package manager of choice.
|
|
59
|
+
const { packageManagerName, packageManagerVersion } = getPackageManagerFromUserAgent();
|
|
60
|
+
const packageManagerInstallCmd = getPackageManagerInstallCmd(packageManagerName);
|
|
61
|
+
const isAppType = pluginType === PLUGIN_TYPES.app || pluginType === PLUGIN_TYPES.scenes;
|
|
62
|
+
const templateData: TemplateData = {
|
|
63
|
+
...answers,
|
|
64
|
+
pluginId,
|
|
65
|
+
packageManagerName,
|
|
66
|
+
packageManagerInstallCmd,
|
|
67
|
+
packageManagerVersion,
|
|
68
|
+
isAppType,
|
|
69
|
+
isNPM: packageManagerName === 'npm',
|
|
70
|
+
version: currentVersion,
|
|
71
|
+
bundleGrafanaUI: features.bundleGrafanaUI,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return templateData;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getTemplateActions({ exportPath, templateData }: { exportPath: string; templateData: any }) {
|
|
78
|
+
const commonActions = getActionsForTemplateFolder({
|
|
79
|
+
folderPath: TEMPLATE_PATHS.common,
|
|
80
|
+
exportPath,
|
|
81
|
+
templateData,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Copy over files from the plugin type specific folder, e.g. "templates/app" for "app" plugins ("app" | "panel" | "datasource").
|
|
85
|
+
const pluginTypeSpecificActions = getActionsForTemplateFolder({
|
|
86
|
+
folderPath: TEMPLATE_PATHS[templateData.pluginType],
|
|
87
|
+
exportPath,
|
|
88
|
+
templateData,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Copy over backend-specific files (if selected)
|
|
92
|
+
const backendFolderPath = templateData.isAppType ? TEMPLATE_PATHS.backendApp : TEMPLATE_PATHS.backend;
|
|
93
|
+
const backendActions = templateData.hasBackend
|
|
94
|
+
? getActionsForTemplateFolder({ folderPath: backendFolderPath, exportPath, templateData })
|
|
95
|
+
: [];
|
|
96
|
+
|
|
97
|
+
// Common, pluginType and backend actions can contain different templates for the same destination.
|
|
98
|
+
// This filtering removes the duplicate file additions to make sure the correct template is scaffolded.
|
|
99
|
+
// Note that the order is reversed so backend > pluginType > common
|
|
100
|
+
const pluginActions = [...backendActions, ...pluginTypeSpecificActions, ...commonActions].reduce((acc, file) => {
|
|
101
|
+
const actionExists = acc.find((f) => f.path === file.path);
|
|
102
|
+
// return early to prevent duplicate file additions
|
|
103
|
+
if (actionExists) {
|
|
104
|
+
return acc;
|
|
105
|
+
}
|
|
106
|
+
acc.push(file);
|
|
107
|
+
return acc;
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
// Copy over Github workflow files (if selected)
|
|
111
|
+
const ciWorkflowActions = templateData.hasGithubWorkflows
|
|
112
|
+
? getActionsForTemplateFolder({
|
|
113
|
+
folderPath: TEMPLATE_PATHS.ciWorkflows,
|
|
114
|
+
exportPath,
|
|
115
|
+
templateData,
|
|
116
|
+
})
|
|
117
|
+
: [];
|
|
118
|
+
|
|
119
|
+
const isCompatibleWorkflowActions = templateData.hasGithubLevitateWorkflow
|
|
120
|
+
? getActionsForTemplateFolder({
|
|
121
|
+
folderPath: TEMPLATE_PATHS.isCompatibleWorkflow,
|
|
122
|
+
exportPath,
|
|
123
|
+
templateData,
|
|
124
|
+
})
|
|
125
|
+
: [];
|
|
126
|
+
|
|
127
|
+
return [...pluginActions, ...ciWorkflowActions, ...isCompatibleWorkflowActions];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getActionsForTemplateFolder({
|
|
131
|
+
folderPath,
|
|
132
|
+
exportPath,
|
|
133
|
+
templateData,
|
|
134
|
+
}: {
|
|
135
|
+
folderPath: string;
|
|
136
|
+
exportPath: string;
|
|
137
|
+
templateData: TemplateData;
|
|
138
|
+
}) {
|
|
139
|
+
let files = glob.sync(`${folderPath}/**`, { dot: true });
|
|
140
|
+
|
|
141
|
+
// The npmrc file is only useful for `pnpm` settings. We can remove it for other package managers.
|
|
142
|
+
if (templateData.packageManagerName !== 'pnpm') {
|
|
143
|
+
files = files.filter((file) => path.basename(file) !== 'npmrc');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getFileExportPath(f: string) {
|
|
147
|
+
return path.relative(folderPath, path.dirname(f));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return files.filter(isFile).map((f) => ({
|
|
151
|
+
templateFile: f,
|
|
152
|
+
// The target path where the compiled template is saved to
|
|
153
|
+
path: path.join(exportPath, getFileExportPath(f), getExportFileName(f)),
|
|
154
|
+
data: {
|
|
155
|
+
...EXTRA_TEMPLATE_VARIABLES,
|
|
156
|
+
...templateData,
|
|
157
|
+
},
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function generateFiles({ actions }: { actions: any[] }) {
|
|
162
|
+
const failures = [];
|
|
163
|
+
const changes = [];
|
|
164
|
+
for (const action of actions) {
|
|
165
|
+
try {
|
|
166
|
+
const rootDir = path.dirname(action.path);
|
|
167
|
+
const pathExists = await directoryExists(rootDir);
|
|
168
|
+
if (!pathExists) {
|
|
169
|
+
await mkdir(rootDir, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const rendered = renderTemplateFromFile(action.templateFile, action.data);
|
|
173
|
+
await writeFile(action.path, rendered);
|
|
174
|
+
changes.push({
|
|
175
|
+
path: action.path,
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
failures.push({
|
|
179
|
+
path: action.path,
|
|
180
|
+
error: error.message || error.toString(),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { failures, changes };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
type AsyncFunction<T> = (...args: any[]) => Promise<T>;
|
|
188
|
+
|
|
189
|
+
async function execPostScaffoldFunction<T>(fn: AsyncFunction<T>, ...args: Parameters<AsyncFunction<T>>) {
|
|
190
|
+
try {
|
|
191
|
+
const resultMsg = await fn.apply(undefined, args);
|
|
192
|
+
if (resultMsg) {
|
|
193
|
+
console.log(`${chalk.green('✔︎')} ${resultMsg}`);
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
printError(`${error}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const { marked } = require('marked');
|
|
2
|
+
const chalk = require('chalk');
|
|
2
3
|
const TerminalRenderer = require('marked-terminal');
|
|
3
4
|
const { Confirm, Select } = require('enquirer');
|
|
4
5
|
|
|
5
6
|
marked.setOptions({
|
|
6
|
-
renderer: new TerminalRenderer(
|
|
7
|
+
renderer: new TerminalRenderer({
|
|
8
|
+
firstHeading: chalk.hex('#ff9900').underline.bold,
|
|
9
|
+
}),
|
|
7
10
|
});
|
|
8
11
|
|
|
9
12
|
export function displayAsMarkdown(msg: string) {
|
|
@@ -19,7 +22,7 @@ export function printMessage(msg: string) {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export function printSuccessMessage(msg: string) {
|
|
22
|
-
console.log(displayAsMarkdown(`\n✔ ${msg}`));
|
|
25
|
+
console.log(displayAsMarkdown(`\n✔ ${msg}`).trim());
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export function printError(error: string) {
|
package/src/utils/utils.files.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { TEMPLATE_PATHS } from '../constants';
|
|
4
|
+
import { access, constants } from 'fs/promises';
|
|
4
5
|
|
|
5
6
|
// Removes common template files from the list in case they have a plugin-specific override
|
|
6
7
|
export function filterOutCommonFiles(files: string[], pluginType: string) {
|
|
@@ -36,6 +37,15 @@ export function readJsonFile(filename: string) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
export async function directoryExists(path: string) {
|
|
41
|
+
try {
|
|
42
|
+
await access(path, constants.F_OK);
|
|
43
|
+
return true;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
export function getOnlyExistingInCwd(files: string[]) {
|
|
40
50
|
return files.filter((file) => fs.existsSync(path.join(process.cwd(), file)));
|
|
41
51
|
}
|
|
@@ -3,7 +3,9 @@ import * as changeCase from 'change-case';
|
|
|
3
3
|
import titleCase from 'title-case';
|
|
4
4
|
import upperCase from 'upper-case';
|
|
5
5
|
import lowerCase from 'lower-case';
|
|
6
|
-
import { PLUGIN_TYPES } from '../constants';
|
|
6
|
+
import { PARTIALS_DIR, PLUGIN_TYPES } from '../constants';
|
|
7
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
8
|
+
import { basename, join } from 'path';
|
|
7
9
|
|
|
8
10
|
// Why? The `{#if}` expression in Handlebars unfortunately only accepts a boolean, which makes it hard to compare values in templates.
|
|
9
11
|
export const ifEq = (a: any, b: any, options: HelperOptions) => {
|
|
@@ -19,8 +21,11 @@ export const normalizeId = (pluginName: string, orgName: string, type: PLUGIN_TY
|
|
|
19
21
|
return changeCase.lowerCase(newOrgName) + '-' + changeCase.lowerCase(newPluginName) + `-${type}`;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
+
// Register our helpers and partials with handlebars.
|
|
25
|
+
registerHandlebarsHelpers();
|
|
26
|
+
registerHandlebarsPartials();
|
|
27
|
+
|
|
28
|
+
function registerHandlebarsHelpers() {
|
|
24
29
|
const helpers = {
|
|
25
30
|
camelCase: changeCase.camelCase,
|
|
26
31
|
snakeCase: changeCase.snakeCase,
|
|
@@ -45,7 +50,15 @@ export function registerHandlebarsHelpers() {
|
|
|
45
50
|
);
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
function registerHandlebarsPartials() {
|
|
54
|
+
const partialFiles = readdirSync(PARTIALS_DIR);
|
|
55
|
+
partialFiles.forEach((fileName) => {
|
|
56
|
+
const name = basename(fileName, '.md');
|
|
57
|
+
const template = readFileSync(join(PARTIALS_DIR, fileName), 'utf-8');
|
|
58
|
+
Handlebars.registerPartial(name, template);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
export function renderHandlebarsTemplate(template: string, data?: any) {
|
|
49
|
-
registerHandlebarsHelpers();
|
|
50
63
|
return Handlebars.compile(template)(data);
|
|
51
64
|
}
|
package/templates/app/README.md
CHANGED
|
@@ -4,14 +4,14 @@ This template is a starting point for building an app plugin for Grafana.
|
|
|
4
4
|
|
|
5
5
|
## What are Grafana app plugins?
|
|
6
6
|
|
|
7
|
-
App plugins can let you create a custom out-of-the-box monitoring experience by custom pages, nested
|
|
7
|
+
App plugins can let you create a custom out-of-the-box monitoring experience by custom pages, nested data sources and panel plugins.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Get started
|
|
10
10
|
|
|
11
|
-
{{#if hasBackend}}
|
|
12
|
-
|
|
11
|
+
{{#if hasBackend}}{{> backend-getting-started }}{{/if}}
|
|
12
|
+
{{> frontend-getting-started packageManagerName=packageManagerName }}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
{{> distributing-your-plugin }}
|
|
15
15
|
|
|
16
16
|
## Learn more
|
|
17
17
|
|
|
@@ -19,4 +19,4 @@ Below you can find source code for existing app plugins and other related docume
|
|
|
19
19
|
|
|
20
20
|
- [Basic app plugin example](https://github.com/grafana/grafana-plugin-examples/tree/master/examples/app-basic#readme)
|
|
21
21
|
- [`plugin.json` documentation](https://grafana.com/developers/plugin-tools/reference-plugin-json)
|
|
22
|
-
- [
|
|
22
|
+
- [Sign a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin)
|
|
@@ -157,6 +157,7 @@ const config = async (env): Promise<Configuration> => {
|
|
|
157
157
|
{ from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
158
158
|
{ from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
159
159
|
{ from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
|
|
160
|
+
{ from: '**/query_help.md', to: '.', noErrorOnMissing: true}, // Optional
|
|
160
161
|
],
|
|
161
162
|
}),
|
|
162
163
|
// Replace certain template-variables in the README and plugin.json
|
|
@@ -8,10 +8,10 @@ Grafana supports a wide range of data sources, including Prometheus, MySQL, and
|
|
|
8
8
|
|
|
9
9
|
## Getting started
|
|
10
10
|
|
|
11
|
-
{{#if hasBackend}}
|
|
12
|
-
|
|
11
|
+
{{#if hasBackend}}{{> backend-getting-started }}{{/if}}
|
|
12
|
+
{{> frontend-getting-started packageManagerName=packageManagerName }}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
{{> distributing-your-plugin }}
|
|
15
15
|
|
|
16
16
|
## Learn more
|
|
17
17
|
|
|
@@ -10,9 +10,9 @@ Use panel plugins when you want to do things like visualize data returned by dat
|
|
|
10
10
|
|
|
11
11
|
## Getting started
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
{{> frontend-getting-started packageManagerName=packageManagerName }}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
{{> distributing-your-plugin }}
|
|
16
16
|
|
|
17
17
|
## Learn more
|
|
18
18
|
|
|
@@ -18,6 +18,6 @@ To learn more about @grafana/scenes usage please refer to the [documentation](ht
|
|
|
18
18
|
1. An example of a scene with tabs. See [Scene with tabs](./src/pages/WithTabs/WithTabs.tsx)
|
|
19
19
|
1. An example of a scene with drill down. See [Scene with drill down](./src/pages/WithDrilldown/WithDrilldown.tsx)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
{{> frontend-getting-started packageManagerName=packageManagerName }}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
{{> distributing-your-plugin }}
|