es-guard 1.6.0 → 1.7.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/dist/cli.js +52 -76
- package/dist/lib/detectTarget.js +72 -59
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,8 @@ import * as fs from "fs";
|
|
|
4
4
|
import packageJson from "../package.json" with { type: "json" };
|
|
5
5
|
import { checkCompatibility, formatViolationMessage } from "./lib/checkCompatiblity.js";
|
|
6
6
|
import { getBrowserTargetsFromString } from "./lib/getBrowserTargets.js";
|
|
7
|
-
import {
|
|
7
|
+
import { detectProjectConfig, getConfigFileNames } from "./lib/detectTarget.js";
|
|
8
8
|
import { setVerboseMode } from "./lib/globalState.js";
|
|
9
|
-
import path from "path";
|
|
10
9
|
const version = packageJson.version;
|
|
11
10
|
// Create the main program
|
|
12
11
|
const program = new Command();
|
|
@@ -71,22 +70,27 @@ program.action(async (directory, options) => {
|
|
|
71
70
|
try {
|
|
72
71
|
// Set global verbose mode
|
|
73
72
|
setVerboseMode(options.verbose || false);
|
|
74
|
-
// Auto-detect
|
|
73
|
+
// Auto-detect configuration if not specified
|
|
75
74
|
let scanDirectory;
|
|
76
75
|
let outputDirSource;
|
|
76
|
+
let target;
|
|
77
|
+
let targetSource;
|
|
78
|
+
let browserTargets;
|
|
79
|
+
let browserslistSource;
|
|
80
|
+
if (options.verbose) {
|
|
81
|
+
console.log("🔍 Auto-detecting project configuration...");
|
|
82
|
+
console.log(`📂 Searching in: ${process.cwd()}`);
|
|
83
|
+
console.log("");
|
|
84
|
+
}
|
|
85
|
+
// Use unified detection to get all configuration at once
|
|
86
|
+
const detectedConfig = detectProjectConfig(process.cwd());
|
|
87
|
+
// Handle directory detection
|
|
77
88
|
if (!directory) {
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log("");
|
|
82
|
-
}
|
|
83
|
-
// Try to detect output directory from current working directory
|
|
84
|
-
const detectedOutput = detectOutputDir(process.cwd());
|
|
85
|
-
if (detectedOutput) {
|
|
86
|
-
scanDirectory = detectedOutput.outputDir;
|
|
87
|
-
outputDirSource = `auto-detected from ${detectedOutput.source}`;
|
|
89
|
+
if (detectedConfig.outputDir) {
|
|
90
|
+
scanDirectory = detectedConfig.outputDir;
|
|
91
|
+
outputDirSource = `auto-detected from ${detectedConfig.outputSource}`;
|
|
88
92
|
if (options.verbose) {
|
|
89
|
-
console.log(`✅ Found output directory: ${scanDirectory} in ${
|
|
93
|
+
console.log(`✅ Found output directory: ${scanDirectory} in ${detectedConfig.outputSource}`);
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
else {
|
|
@@ -113,80 +117,51 @@ program.action(async (directory, options) => {
|
|
|
113
117
|
console.error(`Error: "${scanDirectory}" is not a directory`);
|
|
114
118
|
process.exit(1);
|
|
115
119
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
// Handle target detection
|
|
121
|
+
if (options.target) {
|
|
122
|
+
target = options.target;
|
|
123
|
+
targetSource = "specified";
|
|
124
|
+
}
|
|
125
|
+
else if (detectedConfig.target) {
|
|
126
|
+
target = detectedConfig.target;
|
|
127
|
+
targetSource = `auto-detected from ${detectedConfig.targetSource}`;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
120
130
|
if (options.verbose) {
|
|
121
|
-
console.log("
|
|
122
|
-
console.log(
|
|
131
|
+
console.log("❌ No valid configuration files found for target detection");
|
|
132
|
+
console.log("📋 Searched for:");
|
|
133
|
+
const configFileNames = getConfigFileNames();
|
|
134
|
+
configFileNames.forEach((filename, index) => {
|
|
135
|
+
console.log(` ${index + 1}. ${filename}`);
|
|
136
|
+
});
|
|
123
137
|
console.log("");
|
|
124
138
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
target = detectedResult.target;
|
|
129
|
-
targetSource = `auto-detected from ${detectedResult.source}`;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
if (options.verbose) {
|
|
133
|
-
console.log("❌ No valid configuration files found for target detection");
|
|
134
|
-
console.log("📋 Searched for:");
|
|
135
|
-
console.log(" - package.json (browserslist field)");
|
|
136
|
-
console.log(" - .browserslistrc/.browserslist");
|
|
137
|
-
console.log(" - tsconfig.json (compilerOptions.target)");
|
|
138
|
-
console.log(" - babel.config.js/.babelrc (@babel/preset-env targets)");
|
|
139
|
-
console.log(" - vite.config.js/ts (esbuild target)");
|
|
140
|
-
console.log(" - webpack.config.js/ts (target)");
|
|
141
|
-
console.log(" - next.config.js/ts (output directory)");
|
|
142
|
-
console.log("");
|
|
143
|
-
}
|
|
144
|
-
console.error("Error: No target specified and could not auto-detect from project configuration files.");
|
|
145
|
-
console.error("Please specify a target with --target or ensure your project has a valid configuration file.");
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
139
|
+
console.error("Error: No target specified and could not auto-detect from project configuration files.");
|
|
140
|
+
console.error("Please specify a target with --target or ensure your project has a valid configuration file.");
|
|
141
|
+
process.exit(1);
|
|
148
142
|
}
|
|
149
|
-
//
|
|
150
|
-
let browserTargets;
|
|
143
|
+
// Handle browser targets detection
|
|
151
144
|
if (options.browsers) {
|
|
152
145
|
browserTargets = options.browsers;
|
|
146
|
+
browserslistSource = "specified";
|
|
153
147
|
if (options.verbose) {
|
|
154
148
|
console.log(`🌐 Using specified browser targets: ${browserTargets}`);
|
|
155
149
|
}
|
|
156
150
|
}
|
|
157
|
-
else {
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const pkgPath = path.join(process.cwd(), "package.json");
|
|
164
|
-
if (fs.existsSync(pkgPath)) {
|
|
165
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
166
|
-
if (pkg.browserslist) {
|
|
167
|
-
browserslistEntries = Array.isArray(pkg.browserslist) ? pkg.browserslist : [pkg.browserslist];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else if (configFile === ".browserslistrc" || configFile === ".browserslist") {
|
|
172
|
-
const blPath = path.join(process.cwd(), configFile);
|
|
173
|
-
if (fs.existsSync(blPath)) {
|
|
174
|
-
browserslistEntries = fs
|
|
175
|
-
.readFileSync(blPath, "utf-8")
|
|
176
|
-
.split(/\r?\n/)
|
|
177
|
-
.map((l) => l.trim())
|
|
178
|
-
.filter((l) => l && !l.startsWith("#"));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const esVersionRegex = /^es\d{1,4}$/i;
|
|
182
|
-
const invalidEntries = browserslistEntries.filter((entry) => esVersionRegex.test(entry));
|
|
183
|
-
if (invalidEntries.length > 0) {
|
|
184
|
-
console.warn(`Warning: Detected ES version string(s) in browserslist (${invalidEntries.join(", ")}). These are not valid Browserslist queries and will be ignored for browser compatibility checks.`);
|
|
185
|
-
}
|
|
151
|
+
else if (detectedConfig.browserslist) {
|
|
152
|
+
// Use the detected browserslist
|
|
153
|
+
browserTargets = detectedConfig.browserslist.join(", ");
|
|
154
|
+
browserslistSource = `auto-detected from ${detectedConfig.browserslistSource}`;
|
|
155
|
+
if (options.verbose) {
|
|
156
|
+
console.log(`🌐 Using detected browserslist: ${browserTargets} (from ${detectedConfig.browserslistSource})`);
|
|
186
157
|
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// No browserslist detected, auto-determine from target
|
|
187
161
|
browserTargets = getBrowserTargetsFromString(target);
|
|
162
|
+
browserslistSource = "auto-determined from target";
|
|
188
163
|
if (options.verbose) {
|
|
189
|
-
console.log(`🌐
|
|
164
|
+
console.log(`🌐 No browserslist detected, auto-determining from target: ${browserTargets}`);
|
|
190
165
|
}
|
|
191
166
|
}
|
|
192
167
|
if (options.verbose) {
|
|
@@ -195,6 +170,7 @@ program.action(async (directory, options) => {
|
|
|
195
170
|
console.log(` Target ES version: ${target}`);
|
|
196
171
|
console.log(` Target source: ${targetSource}`);
|
|
197
172
|
console.log(` Browser targets: ${browserTargets}`);
|
|
173
|
+
console.log(` Browserslist source: ${browserslistSource}`);
|
|
198
174
|
console.log(` Scan directory: ${scanDirectory}`);
|
|
199
175
|
console.log(` Output directory source: ${outputDirSource}`);
|
|
200
176
|
console.log("");
|
|
@@ -202,7 +178,7 @@ program.action(async (directory, options) => {
|
|
|
202
178
|
console.log(`🔍 ES-Guard v${version}`);
|
|
203
179
|
console.log(`📁 Scanning directory: ${scanDirectory}`);
|
|
204
180
|
console.log(`🎯 Target ES version: ${target} (${targetSource})`);
|
|
205
|
-
console.log(`🌐 Browser targets: ${browserTargets}${
|
|
181
|
+
console.log(`🌐 Browser targets: ${browserTargets} (${browserslistSource})`);
|
|
206
182
|
console.log("");
|
|
207
183
|
const { errors, warnings } = await checkCompatibility({
|
|
208
184
|
dir: scanDirectory,
|
package/dist/lib/detectTarget.js
CHANGED
|
@@ -24,23 +24,6 @@ const CONFIG_FILE_NAMES = [
|
|
|
24
24
|
"next.config.cjs",
|
|
25
25
|
"next.config.mjs",
|
|
26
26
|
];
|
|
27
|
-
/**
|
|
28
|
-
* Parse ES version from string (e.g., "es6", "es2020", "ES2015")
|
|
29
|
-
* Returns the year as a string, or null if not found
|
|
30
|
-
*/
|
|
31
|
-
const parseESVersion = (str) => {
|
|
32
|
-
const esMatch = str.match(/es(\d+)/i);
|
|
33
|
-
if (esMatch) {
|
|
34
|
-
const esVersion = parseInt(esMatch[1]);
|
|
35
|
-
// If it's a 4-digit year (like 2020), use it directly
|
|
36
|
-
if (esVersion >= 2000) {
|
|
37
|
-
return esVersion.toString();
|
|
38
|
-
}
|
|
39
|
-
// Otherwise convert ES version to year: ES6=2015, ES7=2016, ES8=2017, etc.
|
|
40
|
-
return (2009 + esVersion).toString();
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
};
|
|
44
27
|
/**
|
|
45
28
|
* Common target mapping for TypeScript, Vite, and Webpack configs
|
|
46
29
|
*/
|
|
@@ -172,19 +155,18 @@ const getParser = (filename) => {
|
|
|
172
155
|
/**
|
|
173
156
|
* Get all possible config file names for detection
|
|
174
157
|
*/
|
|
175
|
-
const getConfigFileNames = () => {
|
|
158
|
+
export const getConfigFileNames = () => {
|
|
176
159
|
return CONFIG_FILE_NAMES;
|
|
177
160
|
};
|
|
178
161
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* Returns an object with detected target and output directory information
|
|
162
|
+
* Detection function that extracts all configuration in a single pass
|
|
163
|
+
* This is more efficient than separate detection functions
|
|
182
164
|
*/
|
|
183
|
-
export const
|
|
165
|
+
export const detectProjectConfig = (cwd = process.cwd()) => {
|
|
184
166
|
const configFileNames = getConfigFileNames();
|
|
185
167
|
const result = {};
|
|
186
168
|
if (verboseMode) {
|
|
187
|
-
console.log("🔍 Starting configuration
|
|
169
|
+
console.log("🔍 Starting configuration detection...");
|
|
188
170
|
console.log(`📂 Scanning directory: ${cwd}`);
|
|
189
171
|
console.log("📋 Files to check:");
|
|
190
172
|
configFileNames.forEach((filename, index) => {
|
|
@@ -241,10 +223,24 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
241
223
|
else if (verboseMode) {
|
|
242
224
|
console.log(` 📁 Output directory: not found`);
|
|
243
225
|
}
|
|
244
|
-
//
|
|
245
|
-
if (
|
|
226
|
+
// Update browserslist if found and not already set
|
|
227
|
+
if (detection.browserslist && !result.browserslist) {
|
|
228
|
+
result.browserslist = detection.browserslist;
|
|
229
|
+
result.browserslistSource = filename;
|
|
246
230
|
if (verboseMode) {
|
|
247
|
-
console.log(`
|
|
231
|
+
console.log(` 🌐 Browserslist: ${detection.browserslist.join(", ")} (from ${result.browserslistSource})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (verboseMode && detection.browserslist) {
|
|
235
|
+
console.log(` 🌐 Browserslist: ${detection.browserslist.join(", ")} (already found in ${result.browserslistSource})`);
|
|
236
|
+
}
|
|
237
|
+
else if (verboseMode) {
|
|
238
|
+
console.log(` 🌐 Browserslist: not found`);
|
|
239
|
+
}
|
|
240
|
+
// If we found all three (target, output directory, and browserslist), we can stop searching
|
|
241
|
+
if (result.target && result.outputDir && result.browserslist) {
|
|
242
|
+
if (verboseMode) {
|
|
243
|
+
console.log(` ✅ All configuration found, stopping search`);
|
|
248
244
|
}
|
|
249
245
|
break;
|
|
250
246
|
}
|
|
@@ -278,6 +274,12 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
278
274
|
else {
|
|
279
275
|
console.log(` 📁 Output directory: not found`);
|
|
280
276
|
}
|
|
277
|
+
if (result.browserslist) {
|
|
278
|
+
console.log(` 🌐 Browserslist: ${result.browserslist.join(", ")} (from ${result.browserslistSource})`);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
console.log(` 🌐 Browserslist: not found`);
|
|
282
|
+
}
|
|
281
283
|
console.log("");
|
|
282
284
|
}
|
|
283
285
|
return result;
|
|
@@ -286,16 +288,37 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
286
288
|
* Legacy function for backward compatibility - detects only ES target
|
|
287
289
|
*/
|
|
288
290
|
export const detectTarget = (cwd = process.cwd()) => {
|
|
289
|
-
const result =
|
|
291
|
+
const result = detectProjectConfig(cwd);
|
|
290
292
|
return result.target ? { target: result.target, source: result.targetSource } : null;
|
|
291
293
|
};
|
|
292
294
|
/**
|
|
293
295
|
* Legacy function for backward compatibility - detects only output directory
|
|
294
296
|
*/
|
|
295
297
|
export const detectOutputDir = (cwd = process.cwd()) => {
|
|
296
|
-
const result =
|
|
298
|
+
const result = detectProjectConfig(cwd);
|
|
297
299
|
return result.outputDir ? { outputDir: result.outputDir, source: result.outputSource } : null;
|
|
298
300
|
};
|
|
301
|
+
/**
|
|
302
|
+
* Legacy function for backward compatibility - detects only browserslist
|
|
303
|
+
*/
|
|
304
|
+
export const detectBrowserslist = (cwd = process.cwd()) => {
|
|
305
|
+
const result = detectProjectConfig(cwd);
|
|
306
|
+
return result.browserslist ? { browserslist: result.browserslist, source: result.browserslistSource } : null;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Legacy function for backward compatibility - detects target and output directory
|
|
310
|
+
*/
|
|
311
|
+
export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
312
|
+
const result = detectProjectConfig(cwd);
|
|
313
|
+
return {
|
|
314
|
+
target: result.target,
|
|
315
|
+
targetSource: result.targetSource,
|
|
316
|
+
outputDir: result.outputDir,
|
|
317
|
+
outputSource: result.outputSource,
|
|
318
|
+
browserslist: result.browserslist,
|
|
319
|
+
browserslistSource: result.browserslistSource,
|
|
320
|
+
};
|
|
321
|
+
};
|
|
299
322
|
/**
|
|
300
323
|
* Parse package.json for both target and output directory
|
|
301
324
|
*/
|
|
@@ -306,19 +329,11 @@ const parsePackageJson = (filePath) => {
|
|
|
306
329
|
return {};
|
|
307
330
|
}
|
|
308
331
|
const result = {};
|
|
309
|
-
// Check for browserslist field
|
|
332
|
+
// Check for browserslist field
|
|
310
333
|
if (pkg.browserslist) {
|
|
311
334
|
const browserslist = Array.isArray(pkg.browserslist) ? pkg.browserslist : [pkg.browserslist];
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
if (typeof browser === "string") {
|
|
315
|
-
const target = parseESVersion(browser);
|
|
316
|
-
if (target) {
|
|
317
|
-
result.target = target;
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
335
|
+
// Store the full browserslist for CLI defaults
|
|
336
|
+
result.browserslist = browserslist.filter((browser) => typeof browser === "string");
|
|
322
337
|
}
|
|
323
338
|
// Check for output directory hints
|
|
324
339
|
if (pkg.dist) {
|
|
@@ -363,6 +378,7 @@ const parseTsConfig = (filePath) => {
|
|
|
363
378
|
*/
|
|
364
379
|
const parseBabelConfig = (filePath) => {
|
|
365
380
|
const content = readTextFile(filePath);
|
|
381
|
+
const result = {};
|
|
366
382
|
// Look for @babel/preset-env configuration
|
|
367
383
|
const presetEnvMatch = content.match(/@babel\/preset-env.*?targets.*?(\{[^}]*\})/s);
|
|
368
384
|
if (presetEnvMatch) {
|
|
@@ -371,13 +387,16 @@ const parseBabelConfig = (filePath) => {
|
|
|
371
387
|
const browsersMatch = targetsStr.match(/browsers.*?\[(.*?)\]/);
|
|
372
388
|
if (browsersMatch) {
|
|
373
389
|
const browsers = browsersMatch[1];
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
390
|
+
// Parse browsers array from string
|
|
391
|
+
const browsersList = browsers
|
|
392
|
+
.split(",")
|
|
393
|
+
.map((b) => b.trim().replace(/['"]/g, ""))
|
|
394
|
+
.filter((b) => b);
|
|
395
|
+
// Store the full browserslist for CLI defaults
|
|
396
|
+
result.browserslist = browsersList;
|
|
378
397
|
}
|
|
379
398
|
}
|
|
380
|
-
return
|
|
399
|
+
return result;
|
|
381
400
|
};
|
|
382
401
|
/**
|
|
383
402
|
* Parse .babelrc for target
|
|
@@ -388,23 +407,20 @@ const parseBabelRc = (filePath) => {
|
|
|
388
407
|
console.warn(`Warning: ${filePath} does not look like a valid .babelrc (missing or invalid presets field).`);
|
|
389
408
|
return {};
|
|
390
409
|
}
|
|
410
|
+
const result = {};
|
|
391
411
|
if (config.presets) {
|
|
392
412
|
for (const preset of config.presets) {
|
|
393
413
|
if (Array.isArray(preset) && preset[0] === "@babel/preset-env") {
|
|
394
414
|
const options = preset[1];
|
|
395
415
|
if (options?.targets?.browsers) {
|
|
396
416
|
const browsers = options.targets.browsers;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (target) {
|
|
400
|
-
return { target };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
417
|
+
// Store the full browserslist for CLI defaults
|
|
418
|
+
result.browserslist = browsers;
|
|
403
419
|
}
|
|
404
420
|
}
|
|
405
421
|
}
|
|
406
422
|
}
|
|
407
|
-
return
|
|
423
|
+
return result;
|
|
408
424
|
};
|
|
409
425
|
/**
|
|
410
426
|
* Parse vite.config.js/ts for both target and output directory
|
|
@@ -454,7 +470,7 @@ const parseWebpackConfig = (filePath) => {
|
|
|
454
470
|
return result;
|
|
455
471
|
};
|
|
456
472
|
/**
|
|
457
|
-
* Parse .browserslistrc or .browserslist file for
|
|
473
|
+
* Parse .browserslistrc or .browserslist file for browserslist
|
|
458
474
|
*/
|
|
459
475
|
const parseBrowserslistFile = (filePath) => {
|
|
460
476
|
const content = readTextFile(filePath);
|
|
@@ -462,13 +478,10 @@ const parseBrowserslistFile = (filePath) => {
|
|
|
462
478
|
.split(/\r?\n/)
|
|
463
479
|
.map((line) => line.trim())
|
|
464
480
|
.filter((line) => line && !line.startsWith("#"));
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
return {};
|
|
481
|
+
const result = {};
|
|
482
|
+
// Store the full browserslist for CLI defaults
|
|
483
|
+
result.browserslist = lines;
|
|
484
|
+
return result;
|
|
472
485
|
};
|
|
473
486
|
/**
|
|
474
487
|
* Parse next.config.js/ts/cjs/mjs for output directory
|