@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.
- package/.github/workflows/create-release.yml +1 -1
- package/dist/commands/generate.plopfile.js +46 -17
- package/package.json +2 -1
- package/src/commands/generate.plopfile.ts +65 -19
- package/templates/common/.config/README.md +12 -7
- package/templates/common/.config/webpack/constants.ts +0 -1
- package/templates/common/.config/webpack/utils.ts +24 -0
- package/templates/common/.config/webpack/webpack.config.ts +4 -6
- package/templates/common/package.json +2 -0
|
@@ -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.
|
|
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
|
-
|
|
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": '
|
|
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
|
|
71
|
-
var
|
|
72
|
-
var
|
|
73
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
75
|
-
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
110
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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,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
|
|
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",
|