@caweb/css-audit-webpack-plugin 2.0.2 → 2.0.3
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/css-audit.config.js +2 -1
- package/index.js +131 -227
- package/package.json +5 -2
package/css-audit.config.js
CHANGED
|
@@ -9,11 +9,12 @@ export function hasOwnProperty( obj, prop ) {
|
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
format: 'html',
|
|
12
|
-
filename: '
|
|
12
|
+
filename: 'reports',
|
|
13
13
|
outputFolder: '/audits/css',
|
|
14
14
|
audits: [
|
|
15
15
|
'colors',
|
|
16
16
|
'important',
|
|
17
|
+
'alphas',
|
|
17
18
|
'display-none',
|
|
18
19
|
'selectors',
|
|
19
20
|
'media-queries',
|
package/index.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { sync as resolveBin } from 'resolve-bin';
|
|
7
7
|
import spawn from 'cross-spawn';
|
|
8
|
-
import { getAllFilesSync } from 'get-all-files'
|
|
9
8
|
import EntryDependency from "webpack/lib/dependencies/EntryDependency.js";
|
|
9
|
+
import { flagExists, getArgVal, getAllFlags } from '@caweb/webpack/lib/args.js';
|
|
10
10
|
|
|
11
|
-
import path from 'path';
|
|
11
|
+
import path, { resolve } from 'path';
|
|
12
12
|
import fs from 'fs';
|
|
13
13
|
import deepmerge from 'deepmerge';
|
|
14
14
|
import chalk from 'chalk';
|
|
@@ -16,44 +16,33 @@ import { fileURLToPath, URL } from 'url';
|
|
|
16
16
|
|
|
17
17
|
// default configuration
|
|
18
18
|
import {default as DefaultConfig} from './css-audit.config.js';
|
|
19
|
+
import { get } from 'http';
|
|
19
20
|
|
|
20
21
|
const boldWhite = chalk.bold.white;
|
|
21
22
|
const boldGreen = chalk.bold.green;
|
|
22
23
|
const boldBlue = chalk.bold.hex('#03a7fc');
|
|
23
24
|
const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
|
|
26
|
+
const pluginName = 'CAWebCSSAuditPlugin';
|
|
27
|
+
|
|
25
28
|
// CSS Audit Plugin
|
|
26
|
-
class
|
|
29
|
+
class CAWebCSSAuditPlugin {
|
|
27
30
|
config = {};
|
|
28
31
|
|
|
29
32
|
constructor(opts = {}) {
|
|
30
|
-
// the default publicPath is always the outputFolder
|
|
31
|
-
DefaultConfig.publicPath = DefaultConfig.outputFolder;
|
|
32
|
-
|
|
33
|
-
// the default output folder is always relative to the current working directory.
|
|
34
|
-
DefaultConfig.outputFolder = path.join( process.cwd(), DefaultConfig.outputFolder );
|
|
35
|
-
|
|
36
|
-
// if opts.outputFolder is defined
|
|
37
|
-
if( opts.outputFolder && ! path.isAbsolute(opts.outputFolder) ){
|
|
38
|
-
opts.publicPath = opts.outputFolder;
|
|
39
|
-
|
|
40
|
-
// we join the current working directory with the opts.outputFolder
|
|
41
|
-
opts.outputFolder = path.join( process.cwd(), opts.outputFolder );
|
|
42
|
-
}
|
|
43
|
-
|
|
44
33
|
this.config = deepmerge(DefaultConfig, opts);
|
|
45
34
|
}
|
|
46
35
|
|
|
47
36
|
apply(compiler) {
|
|
48
37
|
const staticDir = {
|
|
49
|
-
directory: this.config.outputFolder,
|
|
50
|
-
publicPath: encodeURI(this.config.
|
|
38
|
+
directory: path.join( process.cwd(), this.config.outputFolder ),
|
|
39
|
+
publicPath: encodeURI(this.config.outputFolder).replace(':', ''),
|
|
51
40
|
watch: true
|
|
52
41
|
}
|
|
53
42
|
|
|
54
43
|
let { devServer } = compiler.options;
|
|
55
44
|
let auditUrl = `${devServer.server}://${devServer.host}:${devServer.port}`;
|
|
56
|
-
let nodeModulePath = encodeURI(
|
|
45
|
+
let nodeModulePath = encodeURI(staticDir.publicPath) + '/node_modules';
|
|
57
46
|
let pathRewrite = {};
|
|
58
47
|
pathRewrite[`^${nodeModulePath}`] = '';
|
|
59
48
|
|
|
@@ -80,124 +69,89 @@ class CSSAuditPlugin {
|
|
|
80
69
|
// if dev server allows for multiple pages to be opened
|
|
81
70
|
// add filename.html to open property.
|
|
82
71
|
if( Array.isArray(devServer.open) ){
|
|
83
|
-
devServer.open.push(`${
|
|
72
|
+
devServer.open.push(`${auditUrl}${this.config.outputFolder}/${this.config.filename}.html`)
|
|
84
73
|
}else if( 'object' === typeof devServer.open && Array.isArray(devServer.open.target) ){
|
|
85
|
-
devServer.open.target.push(`${
|
|
74
|
+
devServer.open.target.push(`${auditUrl}${this.config.outputFolder}/${this.config.filename}.html`)
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
// we always make sure the output folder exists
|
|
89
|
-
fs.mkdirSync( staticDir.directory, { recursive: true } );
|
|
90
|
-
|
|
91
77
|
// Wait for configuration preset plugins to apply all configure webpack defaults
|
|
92
|
-
compiler.hooks.initialize.tap(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
78
|
+
// compiler.hooks.initialize.tap(pluginName, () => {
|
|
79
|
+
// compiler.hooks.compilation.tap(
|
|
80
|
+
// pluginName,
|
|
81
|
+
// (compilation, { normalModuleFactory }) => {
|
|
82
|
+
// compilation.dependencyFactories.set(
|
|
83
|
+
// EntryDependency,
|
|
84
|
+
// normalModuleFactory
|
|
85
|
+
// );
|
|
86
|
+
// }
|
|
87
|
+
// );
|
|
88
|
+
|
|
89
|
+
// // const { entry, options, context } = {
|
|
90
|
+
// // entry: path.join(staticDir.directory, 'css-audit.update.js'),
|
|
91
|
+
// // options: {
|
|
92
|
+
// // name: 'css-audit.update'
|
|
93
|
+
// // },
|
|
94
|
+
// // context: staticDir.directory
|
|
95
|
+
// // };
|
|
96
|
+
|
|
97
|
+
// // const dep = new EntryDependency(entry);
|
|
98
|
+
// // dep.loc = {
|
|
99
|
+
// // name: options.name
|
|
100
|
+
// // };
|
|
115
101
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
102
|
+
// // fs.writeFileSync(
|
|
103
|
+
// // path.join(staticDir.directory, `css-audit.update.js`),
|
|
104
|
+
// // `` // required for hot-update to compile on our page, blank script for now
|
|
105
|
+
// // );
|
|
106
|
+
|
|
107
|
+
// // compiler.hooks.thisCompilation.tap(pluginName,
|
|
108
|
+
// // /**
|
|
109
|
+
// // * Hook into the webpack compilation
|
|
110
|
+
// // * @param {Compilation} compilation
|
|
111
|
+
// // */
|
|
112
|
+
// // (compilation) => {
|
|
127
113
|
|
|
128
|
-
|
|
114
|
+
// // compiler.hooks.make.tapAsync(pluginName, (compilation, callback) => {
|
|
129
115
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
// // compilation.addEntry(
|
|
117
|
+
// // context,
|
|
118
|
+
// // dep,
|
|
119
|
+
// // options,
|
|
120
|
+
// // err => {
|
|
121
|
+
// // callback(err);
|
|
122
|
+
// // });
|
|
123
|
+
// // });
|
|
138
124
|
|
|
139
125
|
|
|
140
126
|
|
|
141
|
-
|
|
127
|
+
// // });
|
|
128
|
+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
142
129
|
|
|
143
|
-
compiler.hooks.done.tapAsync(
|
|
130
|
+
compiler.hooks.done.tapAsync(pluginName,
|
|
144
131
|
(stats, callback) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// if key already exists, push value to array
|
|
163
|
-
if( audits.hasOwnProperty(key) ){
|
|
164
|
-
audits[key].push(value);
|
|
165
|
-
}else{
|
|
166
|
-
// otherwise, if the audit is an array create a new array with the value
|
|
167
|
-
audits[key] = ! Array.isArray(audit) ? value : [value];
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
let result = this.audit(files, {
|
|
172
|
-
format: this.config.format,
|
|
173
|
-
filename: this.config.filename,
|
|
174
|
-
outputFolder: this.config.outputFolder,
|
|
175
|
-
...audits,
|
|
176
|
-
} );
|
|
177
|
-
|
|
178
|
-
if( result ){
|
|
179
|
-
// we have to inject the css-audit.update.js file into the head in order for the webpack-dev-server scripts to load.
|
|
180
|
-
let pageContent = fs.readFileSync(path.join(staticDir.directory, `${this.config.filename}.html`))
|
|
181
|
-
|
|
182
|
-
fs.writeFileSync(
|
|
183
|
-
path.join(staticDir.directory, `${this.config.filename}.html`),
|
|
184
|
-
pageContent.toString().replace('</head>', `<script src="./css-audit.update.js"></script>\n</head>`)
|
|
185
|
-
)
|
|
186
|
-
}
|
|
132
|
+
console.log('<i> \x1b[32m[webpack-dev-middleware] Running CSS Auditor...\x1b[0m');
|
|
133
|
+
/**
|
|
134
|
+
* We run the css audit
|
|
135
|
+
*
|
|
136
|
+
* we scan the output.publicPath if its set and not set to 'auto'
|
|
137
|
+
* otherwise we scan the output.path
|
|
138
|
+
*/
|
|
139
|
+
let scanDir = compiler.options.output.publicPath && 'auto' !== compiler.options.output.publicPath ?
|
|
140
|
+
compiler.options.output.publicPath :
|
|
141
|
+
compiler.options.output.path;
|
|
142
|
+
|
|
143
|
+
// get all .css files
|
|
144
|
+
let files = fs.readdirSync( scanDir, { recursive: true } ).filter( f => f.endsWith( '.css' ) ).map( f => path.join( scanDir, f ) );
|
|
145
|
+
|
|
146
|
+
this.audit(files, this.config );
|
|
147
|
+
console.log(`<i> \x1b[32m[webpack-dev-middleware] CSS Auditor Report can be viewed at \x1b[34m ${new URL(`${auditUrl}${staticDir.publicPath}/${this.config.outputFilename}.html`).toString()}\x1b[0m`);
|
|
187
148
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
callback();
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
|
-
|
|
149
|
+
});
|
|
194
150
|
});
|
|
151
|
+
// });
|
|
195
152
|
|
|
196
153
|
}
|
|
197
154
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
155
|
/**
|
|
202
156
|
* Run WordPress CSS Audit
|
|
203
157
|
*
|
|
@@ -222,143 +176,93 @@ class CSSAuditPlugin {
|
|
|
222
176
|
format,
|
|
223
177
|
filename,
|
|
224
178
|
outputFolder,
|
|
225
|
-
|
|
226
|
-
important,
|
|
227
|
-
displayNone,
|
|
228
|
-
selectors,
|
|
229
|
-
mediaQueries,
|
|
230
|
-
typography,
|
|
231
|
-
propertyValues
|
|
179
|
+
audits
|
|
232
180
|
}){
|
|
233
181
|
|
|
234
|
-
|
|
235
|
-
|
|
182
|
+
// outputFolder should not be absolute
|
|
183
|
+
outputFolder = path.isAbsolute(outputFolder) ? path.join( process.cwd(), outputFolder ) : outputFolder;
|
|
236
184
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// if given path is a directory
|
|
248
|
-
if( fs.statSync(resolvePath).isDirectory() ){
|
|
249
|
-
|
|
250
|
-
// get all .css files
|
|
251
|
-
getAllFilesSync(resolvePath).toArray().forEach(f => {
|
|
252
|
-
if( f.endsWith('.css') ){
|
|
253
|
-
filesToBeAudited.push(f)
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
// if given path is a file and a .css file
|
|
257
|
-
}else if( fs.statSync(paths).isFile() && (paths.endsWith('.css') || paths.endsWith('.scss')) ){
|
|
258
|
-
filesToBeAudited.push(paths)
|
|
259
|
-
}
|
|
260
|
-
// invalid path/file
|
|
261
|
-
} catch (error) {
|
|
262
|
-
filesWithIssues.push(paths)
|
|
185
|
+
|
|
186
|
+
// pass all audits except propertyValues since those take a value there may be multiple
|
|
187
|
+
let propertyValues = [];
|
|
188
|
+
let auditArgs = audits.map( (audit) => {
|
|
189
|
+
if('string' === typeof audit) {
|
|
190
|
+
return `--${audit}`;
|
|
191
|
+
}else if( Array.isArray(audit) && 'property-values' === audit[0] ){
|
|
192
|
+
// propertyValues += audit[1].split(',').map( v => v.trim() ).join(',');
|
|
193
|
+
propertyValues = propertyValues.concat( audit[1].split(',').map( v => v.trim() ) );
|
|
194
|
+
return false;
|
|
263
195
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if(
|
|
268
|
-
|
|
269
|
-
console.log('Auditor did not execute.');
|
|
270
|
-
|
|
271
|
-
fs.copyFileSync(
|
|
272
|
-
path.join(currentPath, 'sample', 'no-files.html'),
|
|
273
|
-
path.join(defaultOutputPath, `${filename}.html`),
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
return false;
|
|
196
|
+
}).filter( Boolean );
|
|
197
|
+
|
|
198
|
+
// push property values separately
|
|
199
|
+
if( propertyValues ){
|
|
200
|
+
auditArgs.push(`--property-values=${propertyValues.join(',')}`);
|
|
277
201
|
}
|
|
278
202
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const processArgs = [
|
|
283
|
-
...process.argv,
|
|
284
|
-
...process.argv0.split(' '),
|
|
285
|
-
|
|
286
|
-
]
|
|
287
|
-
|
|
288
|
-
// we also add args from env.NODE_OPTIONS
|
|
289
|
-
if( process.env.NODE_OPTIONS ){
|
|
290
|
-
processArgs.push( ...process.env.NODE_OPTIONS.split(' ').filter(e=>e).map((o) => o.replaceAll("'", '')) )
|
|
203
|
+
// add the format if set
|
|
204
|
+
if( format ){
|
|
205
|
+
auditArgs.push( `--format=${format}` );
|
|
291
206
|
}
|
|
292
|
-
|
|
207
|
+
|
|
293
208
|
/**
|
|
294
209
|
* the css audit uses the filename for the title, rather than the project name
|
|
295
210
|
* we fix that by passing the project name for the file name
|
|
296
211
|
* then renaming the file to the intended file name.
|
|
297
212
|
*/
|
|
298
|
-
|
|
299
|
-
colors && ! processArgs.includes('--no-colors') ? '--colors' : '',
|
|
300
|
-
important && ! processArgs.includes('--no-important') ? '--important' : '',
|
|
301
|
-
displayNone && ! processArgs.includes('--no-display-none') ? '--display-none' : '',
|
|
302
|
-
selectors && ! processArgs.includes('--no-selectors') ? '--selectors' : '',
|
|
303
|
-
// mediaQueries && ! processArgs.includes('--no-media-queries') ? '--media-queries' : '',
|
|
304
|
-
typography && ! processArgs.includes('--no-typography') ? '--typography' : '',
|
|
305
|
-
format ? `--format=${format}` : '',
|
|
306
|
-
filename ? `--filename=${path.basename(process.cwd())}` : ''
|
|
307
|
-
].filter( e => e)
|
|
308
|
-
|
|
309
|
-
if( propertyValues && ! processArgs.includes('--no-property-values') ){
|
|
310
|
-
propertyValues.forEach((p) => {
|
|
311
|
-
auditArgs.push(`--property-values=${p.replace(' ',',')}`)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
213
|
+
auditArgs.push( `--filename=${path.basename(process.cwd())}` );
|
|
314
214
|
|
|
315
215
|
let { stdout, stderr } = spawn.sync(
|
|
316
216
|
'node',
|
|
317
217
|
[
|
|
318
|
-
resolveBin(
|
|
319
|
-
|
|
320
|
-
...filesToBeAudited,
|
|
218
|
+
resolveBin(currentPath, {executable: 'auditor'}),
|
|
219
|
+
...files,
|
|
321
220
|
...auditArgs
|
|
322
221
|
],
|
|
323
222
|
{
|
|
324
|
-
shell: false,
|
|
325
223
|
stdio: 'pipe',
|
|
326
|
-
cwd:
|
|
224
|
+
cwd: path.join( currentPath, 'bin', 'auditor' ), // has to be set to the bin/auditor directory
|
|
327
225
|
}
|
|
328
226
|
)
|
|
329
227
|
|
|
228
|
+
// if there was an error with the audit process
|
|
330
229
|
if( stderr && stderr.toString() ){
|
|
331
|
-
console.log( stderr.toString() )
|
|
230
|
+
console.log( 'CSS Audit Error: ', stderr.toString() );
|
|
332
231
|
}
|
|
232
|
+
|
|
333
233
|
|
|
334
234
|
if( stdout && stdout.toString() ){
|
|
235
|
+
// the css audit tool always outputs to its own public directory
|
|
236
|
+
let defaultOutputPath = path.join(currentPath, 'bin', 'auditor', 'public');
|
|
237
|
+
|
|
238
|
+
// rename the file back to the intended file name instead of the project name
|
|
239
|
+
fs.renameSync(
|
|
240
|
+
path.join(defaultOutputPath, `${path.basename(process.cwd())}.html`),
|
|
241
|
+
path.join(defaultOutputPath, `${filename}.html`)
|
|
242
|
+
)
|
|
335
243
|
|
|
336
|
-
|
|
337
|
-
|
|
244
|
+
// ensure the output folder exists before moving files
|
|
245
|
+
fs.mkdirSync( outputFolder, { recursive: true } );
|
|
338
246
|
|
|
247
|
+
// move all files except .gitkeep to the output folder
|
|
248
|
+
fs.readdirSync( defaultOutputPath ).filter( f => ! f.endsWith('.gitkeep')).forEach( (f) => {
|
|
339
249
|
fs.renameSync(
|
|
340
|
-
path.join(defaultOutputPath,
|
|
341
|
-
|
|
250
|
+
path.join( defaultOutputPath, f ),
|
|
251
|
+
path.join( outputFolder, f )
|
|
342
252
|
)
|
|
343
|
-
|
|
344
|
-
// we also move the style.css as well case the output path is different than the default.
|
|
345
|
-
if( fs.existsSync( path.join( defaultOutputPath, 'style.css' ) ) ){
|
|
346
|
-
fs.renameSync(
|
|
347
|
-
path.join( defaultOutputPath, 'style.css' ),
|
|
348
|
-
path.join( outputFolder, 'style.css' )
|
|
349
|
-
)
|
|
350
|
-
}
|
|
253
|
+
})
|
|
351
254
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
255
|
+
// clean up the default output message
|
|
256
|
+
let msg = stdout.toString().replace('undefined', '').replace('template', 'css audit');
|
|
257
|
+
|
|
258
|
+
// the command was ran via cli
|
|
259
|
+
if( 'audit' === process.argv[2] ){
|
|
260
|
+
console.log( msg );
|
|
261
|
+
console.log( path.join(outputFolder, `${filename}.html`) )
|
|
262
|
+
// otherwise it's being applied during the webpack process.
|
|
263
|
+
}else{
|
|
264
|
+
return msg;
|
|
265
|
+
}
|
|
362
266
|
|
|
363
267
|
}
|
|
364
268
|
|
|
@@ -367,4 +271,4 @@ class CSSAuditPlugin {
|
|
|
367
271
|
} // end of class
|
|
368
272
|
|
|
369
273
|
|
|
370
|
-
export default
|
|
274
|
+
export default CAWebCSSAuditPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caweb/css-audit-webpack-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "CAWebPublishing Webpack Plugin to run WordPress CSS Audit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -36,9 +36,11 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"webpack": "webpack",
|
|
38
38
|
"postinstall": "cd bin/auditor && npm ci",
|
|
39
|
+
"serve": "webpack serve --config ./node_modules/@caweb/webpack/webpack.config.js ./tests/webpack.tests.js --merge",
|
|
39
40
|
"test": "echo \"Error: run tests from root\" && exit 0"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
43
|
+
"@caweb/webpack": "^1.6.3",
|
|
42
44
|
"chalk": "^5.6.2",
|
|
43
45
|
"cross-spawn": "^7.0.6",
|
|
44
46
|
"deepmerge": "^4.3.1",
|
|
@@ -47,7 +49,8 @@
|
|
|
47
49
|
"twig": "^1.17.1"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"webpack": "^
|
|
52
|
+
"@caweb/html-webpack-plugin": "^2.0.2",
|
|
53
|
+
"webpack": "^5.104.1",
|
|
51
54
|
"webpack-cli": "^6.0.1"
|
|
52
55
|
}
|
|
53
56
|
}
|