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 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 { detectTarget, detectOutputDir } from "./lib/detectTarget.js";
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 output directory if not specified
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 (options.verbose) {
79
- console.log("🔍 No directory specified, auto-detecting output directory from project configuration files...");
80
- console.log(`📂 Searching in: ${process.cwd()}`);
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 ${detectedOutput.source}`);
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
- // Determine target - use provided target or auto-detect
117
- let target = options.target;
118
- let targetSource = "specified";
119
- if (!target) {
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("🔍 Auto-detecting ES target from project configuration files...");
122
- console.log(`📂 Searching in: ${process.cwd()}`);
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
- // Try to detect target from current working directory
126
- const detectedResult = detectTarget(process.cwd());
127
- if (detectedResult) {
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
- // Determine browser targets
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
- // If auto-detected from package.json or browserslist file, check for ES version strings
159
- if (targetSource.startsWith("auto-detected from ")) {
160
- const configFile = targetSource.replace("auto-detected from ", "");
161
- let browserslistEntries = [];
162
- if (configFile === "package.json") {
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(`🌐 Auto-determined browser targets: ${browserTargets}`);
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}${options.browsers ? "" : " (auto-determined)"}`);
181
+ console.log(`🌐 Browser targets: ${browserTargets} (${browserslistSource})`);
206
182
  console.log("");
207
183
  const { errors, warnings } = await checkCompatibility({
208
184
  dir: scanDirectory,
@@ -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
- * Detects both ES target and output directory from common frontend project configuration files.
180
- * Searches in order of preference: package.json, .browserslistrc/.browserslist, tsconfig.json, babel.config.js, .babelrc, vite.config.js, webpack.config.js
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 detectTargetAndOutput = (cwd = process.cwd()) => {
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 file detection...");
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
- // If we found both target and output directory, we can stop searching
245
- if (result.target && result.outputDir) {
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(` Both target and output directory found, stopping search`);
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 = detectTargetAndOutput(cwd);
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 = detectTargetAndOutput(cwd);
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 for target
332
+ // Check for browserslist field
310
333
  if (pkg.browserslist) {
311
334
  const browserslist = Array.isArray(pkg.browserslist) ? pkg.browserslist : [pkg.browserslist];
312
- // Look for ES target in browserslist
313
- for (const browser of browserslist) {
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
- const target = parseESVersion(browsers);
375
- if (target) {
376
- return { target };
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
- for (const browser of browsers) {
398
- const target = parseESVersion(browser);
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 ES target
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
- for (const browser of lines) {
466
- const target = parseESVersion(browser);
467
- if (target) {
468
- return { target };
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "es-guard",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "A tool to check JavaScript compatibility with target environments",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",