@grafana/create-plugin 0.0.1 → 0.1.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.
@@ -41,7 +41,7 @@ jobs:
41
41
  - name: Create Release Notes
42
42
  uses: actions/github-script@v4.0.2
43
43
  with:
44
- github-token: ${{secrets.GH_TOKEN}}
44
+ github-token: ${{secrets.GH_BOT_ACCESS_TOKEN}}
45
45
  script: |
46
46
  await github.request(`POST /repos/${{ github.repository }}/releases`, {
47
47
  tag_name: "${{ github.ref }}",
@@ -26,19 +26,30 @@ function default_1(plop) {
26
26
  {
27
27
  name: 'pluginName',
28
28
  type: 'input',
29
- message: 'What is going to be the name of your plugin?'
29
+ message: 'What is going to be the name of your plugin?',
30
+ validate: function (value) {
31
+ if (/.+/.test(value)) {
32
+ return true;
33
+ }
34
+ return 'Plugin name is required';
35
+ }
30
36
  },
31
37
  {
32
38
  name: 'orgName',
33
39
  type: 'input',
34
40
  message: 'What is the organization name of your plugin?',
35
- "default": 'my-org'
41
+ validate: function (value) {
42
+ if (/.+/.test(value)) {
43
+ return true;
44
+ }
45
+ return 'Organization name is required';
46
+ }
36
47
  },
37
48
  {
38
49
  name: 'pluginDescription',
39
50
  type: 'input',
40
51
  message: 'How would you describe your plugin?',
41
- "default": '<plugin description>'
52
+ "default": ''
42
53
  },
43
54
  {
44
55
  name: 'pluginType',
@@ -66,44 +77,54 @@ function default_1(plop) {
66
77
  },
67
78
  ],
68
79
  actions: function (_a) {
69
- var pluginType = _a.pluginType, hasBackend = _a.hasBackend, hasGithubWorkflows = _a.hasGithubWorkflows, hasGithubLevitateWorkflow = _a.hasGithubLevitateWorkflow;
70
- var commonActions = getActionsForTemplateFolder(constants_1.TEMPLATE_PATHS.common);
71
- var pluginTypeSpecificActions = getActionsForTemplateFolder(constants_1.TEMPLATE_PATHS[pluginType]);
72
- var backendActions = hasBackend ? getActionsForTemplateFolder(constants_1.TEMPLATE_PATHS.backend) : [];
73
- var ciWorkflowActions = hasGithubWorkflows ? getActionsForTemplateFolder(constants_1.TEMPLATE_PATHS.ciWorkflows) : [];
80
+ var pluginName = _a.pluginName, orgName = _a.orgName, pluginType = _a.pluginType, hasBackend = _a.hasBackend, hasGithubWorkflows = _a.hasGithubWorkflows, hasGithubLevitateWorkflow = _a.hasGithubLevitateWorkflow;
81
+ var exportPath = getExportPath(pluginName, orgName, pluginType);
82
+ var commonActions = getActionsForTemplateFolder({ folderPath: constants_1.TEMPLATE_PATHS.common, exportPath: exportPath });
83
+ var pluginTypeSpecificActions = getActionsForTemplateFolder({
84
+ folderPath: constants_1.TEMPLATE_PATHS[pluginType],
85
+ exportPath: exportPath
86
+ });
87
+ var backendActions = hasBackend
88
+ ? getActionsForTemplateFolder({ folderPath: constants_1.TEMPLATE_PATHS.backend, exportPath: exportPath })
89
+ : [];
90
+ var ciWorkflowActions = hasGithubWorkflows
91
+ ? getActionsForTemplateFolder({ folderPath: constants_1.TEMPLATE_PATHS.ciWorkflows, exportPath: exportPath })
92
+ : [];
74
93
  var isCompatibleWorkflowActions = hasGithubLevitateWorkflow
75
- ? getActionsForTemplateFolder(constants_1.TEMPLATE_PATHS.isCompatibleWorkflow)
94
+ ? getActionsForTemplateFolder({ folderPath: constants_1.TEMPLATE_PATHS.isCompatibleWorkflow, exportPath: exportPath })
76
95
  : [];
77
- var readmeActions = getActionsForReadme();
96
+ var readmeActions = getActionsForReadme({ exportPath: exportPath });
78
97
  return __spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], commonActions, true), pluginTypeSpecificActions, true), backendActions, true), ciWorkflowActions, true), readmeActions, true), isCompatibleWorkflowActions, true);
79
98
  }
80
99
  });
81
100
  }
82
101
  exports["default"] = default_1;
83
- function getActionsForReadme() {
102
+ function getActionsForReadme(_a) {
103
+ var exportPath = _a.exportPath;
84
104
  return [
85
- replacePatternWithTemplateInReadme('-- INSERT FRONTEND GETTING STARTED --', 'frontend-getting-started.md'),
86
- replacePatternWithTemplateInReadme('-- INSERT BACKEND GETTING STARTED --', 'backend-getting-started.md'),
105
+ replacePatternWithTemplateInReadme('-- INSERT FRONTEND GETTING STARTED --', 'frontend-getting-started.md', exportPath),
106
+ replacePatternWithTemplateInReadme('-- INSERT BACKEND GETTING STARTED --', 'backend-getting-started.md', exportPath),
87
107
  ];
88
108
  }
89
- function replacePatternWithTemplateInReadme(pattern, partialsFile) {
109
+ function replacePatternWithTemplateInReadme(pattern, partialsFile, exportPath) {
90
110
  return {
91
111
  type: 'modify',
92
- path: path_1["default"].join(constants_1.EXPORT_PATH_PREFIX, 'README.md'),
112
+ path: path_1["default"].join(exportPath, 'README.md'),
93
113
  pattern: pattern,
94
114
  template: undefined,
95
115
  templateFile: path_1["default"].join(constants_1.PARTIALS_DIR, partialsFile),
96
116
  data: constants_1.EXTRA_TEMPLATE_VARIABLES
97
117
  };
98
118
  }
99
- function getActionsForTemplateFolder(folderPath) {
119
+ function getActionsForTemplateFolder(_a) {
120
+ var folderPath = _a.folderPath, exportPath = _a.exportPath;
100
121
  var files = glob_1["default"].sync("".concat(folderPath, "/**"), { dot: true });
101
122
  var getExportFileName = function (f) { return (path_1["default"].extname(f) === '.hbs' ? path_1["default"].basename(f, '.hbs') : path_1["default"].basename(f)); };
102
123
  var getExportPath = function (f) { return path_1["default"].relative(folderPath, path_1["default"].dirname(f)); };
103
124
  return files.filter(isFile).map(function (f) { return ({
104
125
  type: 'add',
105
126
  templateFile: f,
106
- path: path_1["default"].join(constants_1.EXPORT_PATH_PREFIX, getExportPath(f), getExportFileName(f)),
127
+ path: path_1["default"].join(exportPath, getExportPath(f), getExportFileName(f)),
107
128
  force: constants_1.IS_DEV,
108
129
  abortOnFail: false,
109
130
  data: constants_1.EXTRA_TEMPLATE_VARIABLES
@@ -117,3 +138,11 @@ function isFile(path) {
117
138
  return false;
118
139
  }
119
140
  }
141
+ function getExportPath(pluginName, orgName, pluginType) {
142
+ if (constants_1.IS_DEV) {
143
+ return constants_1.DEV_EXPORT_DIR;
144
+ }
145
+ else {
146
+ return path_1["default"].join(process.cwd(), (0, utils_handlebars_1.normalizeId)(pluginName, orgName, pluginType));
147
+ }
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/create-plugin",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:grafana/create-plugin.git",
6
6
  "author": "Grafana",
@@ -40,6 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@grafana/eslint-config": "^2.5.0",
42
42
  "@swc/jest": "^0.2.20",
43
+ "@types/glob": "^7.0.0",
43
44
  "@types/jest": "^27.4.1",
44
45
  "@typescript-eslint/eslint-plugin": "^4.33.0",
45
46
  "@typescript-eslint/parser": "^4.33.0",
@@ -4,21 +4,22 @@ import path from 'path';
4
4
  import fs from 'fs';
5
5
  import { ifEq, normalizeId } from '../utils/utils.handlebars';
6
6
  import {
7
- EXPORT_PATH_PREFIX,
8
7
  IS_DEV,
9
8
  TEMPLATE_PATHS,
10
9
  PARTIALS_DIR,
11
10
  PLUGIN_TYPES,
12
11
  EXTRA_TEMPLATE_VARIABLES,
12
+ DEV_EXPORT_DIR,
13
13
  } from '../constants';
14
14
 
15
15
  type CliArgs = {
16
16
  pluginName: string;
17
17
  pluginDescription: string;
18
18
  orgName: string;
19
- pluginType: string;
19
+ pluginType: PLUGIN_TYPES;
20
20
  hasBackend: boolean;
21
21
  hasGithubWorkflows: boolean;
22
+ hasGithubLevitateWorkflow: boolean;
22
23
  };
23
24
 
24
25
  // Plopfile API documentation: https://plopjs.com/documentation/#plopfile-api
@@ -33,18 +34,29 @@ export default function (plop: NodePlopAPI) {
33
34
  name: 'pluginName',
34
35
  type: 'input',
35
36
  message: 'What is going to be the name of your plugin?',
37
+ validate: (value: string) => {
38
+ if (/.+/.test(value)) {
39
+ return true;
40
+ }
41
+ return 'Plugin name is required';
42
+ },
36
43
  },
37
44
  {
38
45
  name: 'orgName',
39
46
  type: 'input',
40
47
  message: 'What is the organization name of your plugin?',
41
- default: 'my-org',
48
+ validate: (value: string) => {
49
+ if (/.+/.test(value)) {
50
+ return true;
51
+ }
52
+ return 'Organization name is required';
53
+ },
42
54
  },
43
55
  {
44
56
  name: 'pluginDescription',
45
57
  type: 'input',
46
58
  message: 'How would you describe your plugin?',
47
- default: '<plugin description>',
59
+ default: '',
48
60
  },
49
61
  {
50
62
  name: 'pluginType',
@@ -71,26 +83,40 @@ export default function (plop: NodePlopAPI) {
71
83
  default: false,
72
84
  },
73
85
  ],
74
- // @ts-ignore - We would like to specify the Answers type correctly
75
- actions: function ({ pluginType, hasBackend, hasGithubWorkflows, hasGithubLevitateWorkflow }: CliArgs) {
86
+ actions: function ({
87
+ pluginName,
88
+ orgName,
89
+ pluginType,
90
+ hasBackend,
91
+ hasGithubWorkflows,
92
+ hasGithubLevitateWorkflow,
93
+ }: CliArgs) {
94
+ const exportPath = getExportPath(pluginName, orgName, pluginType);
76
95
  // Copy over files that are shared between plugins types
77
- const commonActions = getActionsForTemplateFolder(TEMPLATE_PATHS.common);
96
+ const commonActions = getActionsForTemplateFolder({ folderPath: TEMPLATE_PATHS.common, exportPath });
78
97
 
79
98
  // Copy over files from the plugin type specific folder, e.g. "tempaltes/app" for "app" plugins ("app" | "panel" | "datasource").
80
- const pluginTypeSpecificActions = getActionsForTemplateFolder(TEMPLATE_PATHS[pluginType]);
99
+ const pluginTypeSpecificActions = getActionsForTemplateFolder({
100
+ folderPath: TEMPLATE_PATHS[pluginType],
101
+ exportPath,
102
+ });
81
103
 
82
104
  // Copy over backend-specific files (if selected)
83
- const backendActions = hasBackend ? getActionsForTemplateFolder(TEMPLATE_PATHS.backend) : [];
105
+ const backendActions = hasBackend
106
+ ? getActionsForTemplateFolder({ folderPath: TEMPLATE_PATHS.backend, exportPath })
107
+ : [];
84
108
 
85
109
  // Copy over Github workflow files (if selected)
86
- const ciWorkflowActions = hasGithubWorkflows ? getActionsForTemplateFolder(TEMPLATE_PATHS.ciWorkflows) : [];
110
+ const ciWorkflowActions = hasGithubWorkflows
111
+ ? getActionsForTemplateFolder({ folderPath: TEMPLATE_PATHS.ciWorkflows, exportPath })
112
+ : [];
87
113
 
88
114
  const isCompatibleWorkflowActions = hasGithubLevitateWorkflow
89
- ? getActionsForTemplateFolder(TEMPLATE_PATHS.isCompatibleWorkflow)
115
+ ? getActionsForTemplateFolder({ folderPath: TEMPLATE_PATHS.isCompatibleWorkflow, exportPath })
90
116
  : [];
91
117
 
92
118
  // Replace conditional bits in the Readme files
93
- const readmeActions = getActionsForReadme();
119
+ const readmeActions = getActionsForReadme({ exportPath });
94
120
 
95
121
  return [
96
122
  ...commonActions,
@@ -104,17 +130,29 @@ export default function (plop: NodePlopAPI) {
104
130
  });
105
131
  }
106
132
 
107
- function getActionsForReadme(): ModifyActionConfig[] {
133
+ function getActionsForReadme({ exportPath }: { exportPath: string }): ModifyActionConfig[] {
108
134
  return [
109
- replacePatternWithTemplateInReadme('-- INSERT FRONTEND GETTING STARTED --', 'frontend-getting-started.md'),
110
- replacePatternWithTemplateInReadme('-- INSERT BACKEND GETTING STARTED --', 'backend-getting-started.md'),
135
+ replacePatternWithTemplateInReadme(
136
+ '-- INSERT FRONTEND GETTING STARTED --',
137
+ 'frontend-getting-started.md',
138
+ exportPath
139
+ ),
140
+ replacePatternWithTemplateInReadme(
141
+ '-- INSERT BACKEND GETTING STARTED --',
142
+ 'backend-getting-started.md',
143
+ exportPath
144
+ ),
111
145
  ];
112
146
  }
113
147
 
114
- function replacePatternWithTemplateInReadme(pattern: string, partialsFile: string): ModifyActionConfig {
148
+ function replacePatternWithTemplateInReadme(
149
+ pattern: string,
150
+ partialsFile: string,
151
+ exportPath: string
152
+ ): ModifyActionConfig {
115
153
  return {
116
154
  type: 'modify',
117
- path: path.join(EXPORT_PATH_PREFIX, 'README.md'),
155
+ path: path.join(exportPath, 'README.md'),
118
156
  pattern,
119
157
  // @ts-ignore
120
158
  template: undefined,
@@ -124,7 +162,7 @@ function replacePatternWithTemplateInReadme(pattern: string, partialsFile: strin
124
162
  }
125
163
 
126
164
  // TODO<use Plop action `addMany` instead>
127
- function getActionsForTemplateFolder(folderPath: string) {
165
+ function getActionsForTemplateFolder({ folderPath, exportPath }: { folderPath: string; exportPath: string }) {
128
166
  const files = glob.sync(`${folderPath}/**`, { dot: true });
129
167
  const getExportFileName = (f: string) => (path.extname(f) === '.hbs' ? path.basename(f, '.hbs') : path.basename(f));
130
168
  const getExportPath = (f: string) => path.relative(folderPath, path.dirname(f));
@@ -133,7 +171,7 @@ function getActionsForTemplateFolder(folderPath: string) {
133
171
  type: 'add',
134
172
  templateFile: f,
135
173
  // The target path where the compiled template is saved to
136
- path: path.join(EXPORT_PATH_PREFIX, getExportPath(f), getExportFileName(f)),
174
+ path: path.join(exportPath, getExportPath(f), getExportFileName(f)),
137
175
  // We would like to override generated files in development mode
138
176
  force: IS_DEV,
139
177
  // We would still like to scaffold as many files as possible even if one fails
@@ -149,3 +187,11 @@ function isFile(path: string) {
149
187
  return false;
150
188
  }
151
189
  }
190
+
191
+ function getExportPath(pluginName: string, orgName: string, pluginType: PLUGIN_TYPES) {
192
+ if (IS_DEV) {
193
+ return DEV_EXPORT_DIR;
194
+ } else {
195
+ return path.join(process.cwd(), normalizeId(pluginName, orgName, pluginType));
196
+ }
197
+ }
@@ -84,18 +84,23 @@ We are going to use [`webpack-merge`](https://github.com/survivejs/webpack-merge
84
84
 
85
85
  ```typescript
86
86
  // webpack.config.ts
87
+ import type { Configuration } from 'webpack';
87
88
  import { merge } from 'webpack-merge';
88
89
  import grafanaConfig from './.config/webpack/webpack.config';
89
90
 
90
- const config = (env) =>
91
- merge(grafanaConfig(env), {
92
- // Add custom config here...
93
- output: {
94
- asyncChunks: true,
95
- },
96
- });
91
+ const config = async (env): Promise<Configuration> => {
92
+ const baseConfig = await grafanaConfig(env);
93
+
94
+ return merge(baseConfig, {
95
+ // Add custom config here...
96
+ output: {
97
+ asyncChunks: true,
98
+ },
99
+ });
100
+ };
97
101
 
98
102
  export default config;
103
+
99
104
  ```
100
105
 
101
106
  #### 3. Update the `package.json` to use the new Webpack config
@@ -1,3 +1,2 @@
1
1
  export const SOURCE_DIR = 'src';
2
2
  export const DIST_DIR = 'dist';
3
- export const ENTRY_FILE = `${SOURCE_DIR}/module.ts`;
@@ -1,7 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import util from 'util';
4
+ import glob from 'glob';
3
5
  import { SOURCE_DIR } from './constants';
4
6
 
7
+ const globAsync = util.promisify(glob);
8
+
5
9
  export function getPackageJson() {
6
10
  return require(path.resolve(process.cwd(), 'package.json'));
7
11
  }
@@ -15,3 +19,23 @@ export function getPluginId() {
15
19
  export function hasReadme() {
16
20
  return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md'));
17
21
  }
22
+
23
+ export async function getEntries(): Promise<Record<string, string>> {
24
+ const parent = '..';
25
+ const pluginsJson = await globAsync('**/src/**/plugin.json');
26
+
27
+ const plugins = await Promise.all(pluginsJson.map(pluginJson => {
28
+ const folder = path.dirname(pluginJson);
29
+ return globAsync(`${folder}/module.{ts,tsx,js}`);
30
+ }));
31
+
32
+ return plugins.reduce((result, modules) => {
33
+ return modules.reduce((result, module) => {
34
+ const pluginPath = path.resolve(path.dirname(module), parent);
35
+ const pluginName = path.basename(pluginPath);
36
+
37
+ result[`${pluginName}/module`] = path.join(parent, module);
38
+ return result;
39
+ }, result);
40
+ }, {});
41
+ }
@@ -12,10 +12,10 @@ import path from 'path';
12
12
  import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
13
13
  import { Configuration } from 'webpack';
14
14
 
15
- import { getPackageJson, getPluginId, hasReadme } from './utils';
16
- import { SOURCE_DIR, DIST_DIR, ENTRY_FILE } from './constants';
15
+ import { getPackageJson, getPluginId, hasReadme, getEntries } from './utils';
16
+ import { SOURCE_DIR, DIST_DIR } from './constants';
17
17
 
18
- const config = (env): Configuration => ({
18
+ const config = async (env): Promise<Configuration> => ({
19
19
  cache: {
20
20
  type: 'filesystem',
21
21
  buildDependencies: {
@@ -27,9 +27,7 @@ const config = (env): Configuration => ({
27
27
 
28
28
  devtool: env.production ? 'source-map' : 'eval-source-map',
29
29
 
30
- entry: {
31
- module: path.resolve(process.cwd(), ENTRY_FILE),
32
- },
30
+ entry: await getEntries(),
33
31
 
34
32
  externals: [
35
33
  'lodash',
@@ -27,6 +27,7 @@
27
27
  "@swc/jest": "^0.2.20",
28
28
  "@testing-library/jest-dom": "^5.16.2",
29
29
  "@testing-library/react": "^12.1.3",
30
+ "@types/glob": "^8.0.0",
30
31
  "@types/jest": "^27.4.1",{{#if_eq pluginType "app"}}
31
32
  "@types/react-router-dom": "^5.3.3",{{/if_eq}}
32
33
  "@types/node": "^17.0.19",
@@ -40,6 +41,7 @@
40
41
  "eslint-plugin-react": "^7.26.1",
41
42
  "eslint-plugin-react-hooks": "^4.2.0",
42
43
  "eslint-webpack-plugin": "^3.1.1",
44
+ "glob": "^8.0.3",
43
45
  "jest": "27.5.0",
44
46
  "fork-ts-checker-webpack-plugin": "^7.2.0",
45
47
  "prettier": "^2.5.0",