es-guard 1.6.0 → 1.8.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 +56 -76
- package/dist/lib/defaults.js +77 -0
- package/dist/lib/detectTarget.js +86 -136
- package/dist/lib/globalState.js +30 -1
- package/dist/lib/projectType.js +46 -0
- package/dist/lib/utils.js +76 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,9 +4,9 @@ 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
|
+
import { getCurrentProjectType } from "./lib/projectType.js";
|
|
8
9
|
import { setVerboseMode } from "./lib/globalState.js";
|
|
9
|
-
import path from "path";
|
|
10
10
|
const version = packageJson.version;
|
|
11
11
|
// Create the main program
|
|
12
12
|
const program = new Command();
|
|
@@ -71,22 +71,30 @@ program.action(async (directory, options) => {
|
|
|
71
71
|
try {
|
|
72
72
|
// Set global verbose mode
|
|
73
73
|
setVerboseMode(options.verbose || false);
|
|
74
|
-
// Auto-detect
|
|
74
|
+
// Auto-detect configuration if not specified
|
|
75
75
|
let scanDirectory;
|
|
76
76
|
let outputDirSource;
|
|
77
|
+
let target;
|
|
78
|
+
let targetSource;
|
|
79
|
+
let browserTargets;
|
|
80
|
+
let browserslistSource;
|
|
81
|
+
if (options.verbose) {
|
|
82
|
+
console.log("🔍 Auto-detecting project configuration...");
|
|
83
|
+
console.log(`📂 Searching in: ${process.cwd()}`);
|
|
84
|
+
// Detect and log project type
|
|
85
|
+
const projectType = getCurrentProjectType();
|
|
86
|
+
console.log(`🏗️ Project type: ${projectType}`);
|
|
87
|
+
console.log("");
|
|
88
|
+
}
|
|
89
|
+
// Use unified detection to get all configuration at once
|
|
90
|
+
const detectedConfig = detectProjectConfig(process.cwd());
|
|
91
|
+
// Handle directory detection
|
|
77
92
|
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}`;
|
|
93
|
+
if (detectedConfig.outputDir) {
|
|
94
|
+
scanDirectory = detectedConfig.outputDir;
|
|
95
|
+
outputDirSource = `auto-detected from ${detectedConfig.outputSource}`;
|
|
88
96
|
if (options.verbose) {
|
|
89
|
-
console.log(`✅ Found output directory: ${scanDirectory} in ${
|
|
97
|
+
console.log(`✅ Found output directory: ${scanDirectory} in ${detectedConfig.outputSource}`);
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
else {
|
|
@@ -113,80 +121,51 @@ program.action(async (directory, options) => {
|
|
|
113
121
|
console.error(`Error: "${scanDirectory}" is not a directory`);
|
|
114
122
|
process.exit(1);
|
|
115
123
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
// Handle target detection
|
|
125
|
+
if (options.target) {
|
|
126
|
+
target = options.target;
|
|
127
|
+
targetSource = "specified";
|
|
128
|
+
}
|
|
129
|
+
else if (detectedConfig.target) {
|
|
130
|
+
target = detectedConfig.target;
|
|
131
|
+
targetSource = `auto-detected from ${detectedConfig.targetSource}`;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
120
134
|
if (options.verbose) {
|
|
121
|
-
console.log("
|
|
122
|
-
console.log(
|
|
135
|
+
console.log("❌ No valid configuration files found for target detection");
|
|
136
|
+
console.log("📋 Searched for:");
|
|
137
|
+
const configFileNames = getConfigFileNames();
|
|
138
|
+
configFileNames.forEach((filename, index) => {
|
|
139
|
+
console.log(` ${index + 1}. ${filename}`);
|
|
140
|
+
});
|
|
123
141
|
console.log("");
|
|
124
142
|
}
|
|
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
|
-
}
|
|
143
|
+
console.error("Error: No target specified and could not auto-detect from project configuration files.");
|
|
144
|
+
console.error("Please specify a target with --target or ensure your project has a valid configuration file.");
|
|
145
|
+
process.exit(1);
|
|
148
146
|
}
|
|
149
|
-
//
|
|
150
|
-
let browserTargets;
|
|
147
|
+
// Handle browser targets detection
|
|
151
148
|
if (options.browsers) {
|
|
152
149
|
browserTargets = options.browsers;
|
|
150
|
+
browserslistSource = "specified";
|
|
153
151
|
if (options.verbose) {
|
|
154
152
|
console.log(`🌐 Using specified browser targets: ${browserTargets}`);
|
|
155
153
|
}
|
|
156
154
|
}
|
|
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
|
-
}
|
|
155
|
+
else if (detectedConfig.browserslist) {
|
|
156
|
+
// Use the detected browserslist
|
|
157
|
+
browserTargets = detectedConfig.browserslist.join(", ");
|
|
158
|
+
browserslistSource = `auto-detected from ${detectedConfig.browserslistSource}`;
|
|
159
|
+
if (options.verbose) {
|
|
160
|
+
console.log(`🌐 Using detected browserslist: ${browserTargets} (from ${detectedConfig.browserslistSource})`);
|
|
186
161
|
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// No browserslist detected, auto-determine from target
|
|
187
165
|
browserTargets = getBrowserTargetsFromString(target);
|
|
166
|
+
browserslistSource = "auto-determined from target";
|
|
188
167
|
if (options.verbose) {
|
|
189
|
-
console.log(`🌐
|
|
168
|
+
console.log(`🌐 No browserslist detected, auto-determining from target: ${browserTargets}`);
|
|
190
169
|
}
|
|
191
170
|
}
|
|
192
171
|
if (options.verbose) {
|
|
@@ -195,6 +174,7 @@ program.action(async (directory, options) => {
|
|
|
195
174
|
console.log(` Target ES version: ${target}`);
|
|
196
175
|
console.log(` Target source: ${targetSource}`);
|
|
197
176
|
console.log(` Browser targets: ${browserTargets}`);
|
|
177
|
+
console.log(` Browserslist source: ${browserslistSource}`);
|
|
198
178
|
console.log(` Scan directory: ${scanDirectory}`);
|
|
199
179
|
console.log(` Output directory source: ${outputDirSource}`);
|
|
200
180
|
console.log("");
|
|
@@ -202,7 +182,7 @@ program.action(async (directory, options) => {
|
|
|
202
182
|
console.log(`🔍 ES-Guard v${version}`);
|
|
203
183
|
console.log(`📁 Scanning directory: ${scanDirectory}`);
|
|
204
184
|
console.log(`🎯 Target ES version: ${target} (${targetSource})`);
|
|
205
|
-
console.log(`🌐 Browser targets: ${browserTargets}${
|
|
185
|
+
console.log(`🌐 Browser targets: ${browserTargets} (${browserslistSource})`);
|
|
206
186
|
console.log("");
|
|
207
187
|
const { errors, warnings } = await checkCompatibility({
|
|
208
188
|
dir: scanDirectory,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configurations for different project types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Next.js default browserslist configuration
|
|
6
|
+
* Source: https://nextjs.org/docs/basic-features/supported-browsers-features
|
|
7
|
+
*/
|
|
8
|
+
export const NEXTJS_DEFAULT_BROWSERSLIST = ["chrome 64", "edge 79", "firefox 67", "opera 51", "safari 12"];
|
|
9
|
+
/**
|
|
10
|
+
* Default output directories for different project types
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_OUTPUT_DIRS = {
|
|
13
|
+
nextjs: ".next/static",
|
|
14
|
+
vite: "dist",
|
|
15
|
+
webpack: "dist",
|
|
16
|
+
rollup: "dist",
|
|
17
|
+
parcel: "dist",
|
|
18
|
+
generic: "dist",
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Project type detection helpers
|
|
22
|
+
*/
|
|
23
|
+
export const PROJECT_TYPES = {
|
|
24
|
+
nextjs: "nextjs",
|
|
25
|
+
vite: "vite",
|
|
26
|
+
webpack: "webpack",
|
|
27
|
+
rollup: "rollup",
|
|
28
|
+
parcel: "parcel",
|
|
29
|
+
generic: "generic",
|
|
30
|
+
};
|
|
31
|
+
export const ProjectTypeKeys = new Set(Object.keys(PROJECT_TYPES));
|
|
32
|
+
export const isProjectType = (projectType) => {
|
|
33
|
+
return ProjectTypeKeys.has(projectType);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Get default browserslist for a project type
|
|
37
|
+
*/
|
|
38
|
+
export function getDefaultBrowserslist(projectType) {
|
|
39
|
+
switch (projectType) {
|
|
40
|
+
case PROJECT_TYPES.nextjs:
|
|
41
|
+
return [...NEXTJS_DEFAULT_BROWSERSLIST];
|
|
42
|
+
// Add more project types here as needed
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get default output directory for a project type
|
|
49
|
+
*/
|
|
50
|
+
export function getDefaultOutputDir(projectType) {
|
|
51
|
+
if (isProjectType(projectType)) {
|
|
52
|
+
return DEFAULT_OUTPUT_DIRS[projectType];
|
|
53
|
+
}
|
|
54
|
+
return DEFAULT_OUTPUT_DIRS.generic;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Detect project type from package.json dependencies
|
|
58
|
+
*/
|
|
59
|
+
export function detectProjectType(dependencies = {}, devDependencies = {}) {
|
|
60
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
61
|
+
if (allDeps.next) {
|
|
62
|
+
return PROJECT_TYPES.nextjs;
|
|
63
|
+
}
|
|
64
|
+
if (allDeps.vite) {
|
|
65
|
+
return PROJECT_TYPES.vite;
|
|
66
|
+
}
|
|
67
|
+
if (allDeps.webpack) {
|
|
68
|
+
return PROJECT_TYPES.webpack;
|
|
69
|
+
}
|
|
70
|
+
if (allDeps.rollup) {
|
|
71
|
+
return PROJECT_TYPES.rollup;
|
|
72
|
+
}
|
|
73
|
+
if (allDeps.parcel) {
|
|
74
|
+
return PROJECT_TYPES.parcel;
|
|
75
|
+
}
|
|
76
|
+
return PROJECT_TYPES.generic;
|
|
77
|
+
}
|
package/dist/lib/detectTarget.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { verboseMode } from "./globalState.js";
|
|
4
|
+
import { NEXTJS_DEFAULT_BROWSERSLIST } from "./defaults.js";
|
|
5
|
+
import { getCurrentProjectType } from "./projectType.js";
|
|
6
|
+
import { readJsonFile, readTextFile, evaluateJsFile, isPackageJson, isTsConfig, isBabelRc, isViteConfig, isWebpackConfig, isNextConfig, } from "./utils.js";
|
|
4
7
|
// Shared utilities for ES version parsing and conversion
|
|
5
8
|
const CONFIG_FILE_NAMES = [
|
|
6
9
|
"package.json",
|
|
@@ -24,23 +27,6 @@ const CONFIG_FILE_NAMES = [
|
|
|
24
27
|
"next.config.cjs",
|
|
25
28
|
"next.config.mjs",
|
|
26
29
|
];
|
|
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
30
|
/**
|
|
45
31
|
* Common target mapping for TypeScript, Vite, and Webpack configs
|
|
46
32
|
*/
|
|
@@ -69,80 +55,6 @@ const TARGET_MAP = {
|
|
|
69
55
|
es6: "2015",
|
|
70
56
|
es5: "2009",
|
|
71
57
|
};
|
|
72
|
-
/**
|
|
73
|
-
* Helper to read and parse JSON file safely
|
|
74
|
-
*/
|
|
75
|
-
const readJsonFile = (filePath) => {
|
|
76
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
77
|
-
return JSON.parse(content);
|
|
78
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* Helper to read text file safely
|
|
81
|
-
*/
|
|
82
|
-
const readTextFile = (filePath) => {
|
|
83
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
|
-
* Helper to safely evaluate JavaScript files (for config files)
|
|
87
|
-
*/
|
|
88
|
-
const evaluateJsFile = (filePath) => {
|
|
89
|
-
const content = readTextFile(filePath);
|
|
90
|
-
// Create a safe evaluation context
|
|
91
|
-
const module = { exports: {} };
|
|
92
|
-
const require = (id) => {
|
|
93
|
-
if (id === "path")
|
|
94
|
-
return path;
|
|
95
|
-
if (id === "fs")
|
|
96
|
-
return fs;
|
|
97
|
-
throw new Error(`Cannot require '${id}' in config evaluation`);
|
|
98
|
-
};
|
|
99
|
-
try {
|
|
100
|
-
// Use Function constructor to create a safe evaluation environment
|
|
101
|
-
const fn = new Function("module", "exports", "require", "path", "fs", "__dirname", content);
|
|
102
|
-
fn(module, module.exports, require, path, fs, path.dirname(filePath));
|
|
103
|
-
return module.exports;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
console.warn(`Error evaluating ${filePath}:`, error);
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* Type guard for package.json structure
|
|
112
|
-
*/
|
|
113
|
-
const isPackageJson = (obj) => {
|
|
114
|
-
return typeof obj === "object" && obj !== null;
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Type guard for tsconfig.json structure
|
|
118
|
-
*/
|
|
119
|
-
const isTsConfig = (obj) => {
|
|
120
|
-
return typeof obj === "object" && obj !== null && "compilerOptions" in obj;
|
|
121
|
-
};
|
|
122
|
-
/**
|
|
123
|
-
* Type guard for .babelrc structure
|
|
124
|
-
*/
|
|
125
|
-
const isBabelRc = (obj) => {
|
|
126
|
-
return typeof obj === "object" && obj !== null && "presets" in obj;
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* Type guard for vite config structure
|
|
130
|
-
*/
|
|
131
|
-
const isViteConfig = (obj) => {
|
|
132
|
-
return typeof obj === "object" && obj !== null;
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* Type guard for webpack config structure
|
|
136
|
-
*/
|
|
137
|
-
const isWebpackConfig = (obj) => {
|
|
138
|
-
return typeof obj === "object" && obj !== null;
|
|
139
|
-
};
|
|
140
|
-
/**
|
|
141
|
-
* Type guard for next.js config structure
|
|
142
|
-
*/
|
|
143
|
-
const isNextConfig = (obj) => {
|
|
144
|
-
return typeof obj === "object" && obj !== null;
|
|
145
|
-
};
|
|
146
58
|
/**
|
|
147
59
|
* Get parser function for a given config file
|
|
148
60
|
*/
|
|
@@ -172,19 +84,18 @@ const getParser = (filename) => {
|
|
|
172
84
|
/**
|
|
173
85
|
* Get all possible config file names for detection
|
|
174
86
|
*/
|
|
175
|
-
const getConfigFileNames = () => {
|
|
87
|
+
export const getConfigFileNames = () => {
|
|
176
88
|
return CONFIG_FILE_NAMES;
|
|
177
89
|
};
|
|
178
90
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* Returns an object with detected target and output directory information
|
|
91
|
+
* Detection function that extracts all configuration in a single pass
|
|
92
|
+
* This is more efficient than separate detection functions
|
|
182
93
|
*/
|
|
183
|
-
export const
|
|
94
|
+
export const detectProjectConfig = (cwd = process.cwd()) => {
|
|
184
95
|
const configFileNames = getConfigFileNames();
|
|
185
96
|
const result = {};
|
|
186
97
|
if (verboseMode) {
|
|
187
|
-
console.log("🔍 Starting configuration
|
|
98
|
+
console.log("🔍 Starting configuration detection...");
|
|
188
99
|
console.log(`📂 Scanning directory: ${cwd}`);
|
|
189
100
|
console.log("📋 Files to check:");
|
|
190
101
|
configFileNames.forEach((filename, index) => {
|
|
@@ -230,7 +141,7 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
230
141
|
// Update output directory if found and not already set
|
|
231
142
|
if (detection.outputDir && !result.outputDir) {
|
|
232
143
|
result.outputDir = detection.outputDir;
|
|
233
|
-
result.outputSource = filename;
|
|
144
|
+
result.outputSource = detection.outputSource || filename;
|
|
234
145
|
if (verboseMode) {
|
|
235
146
|
console.log(` 📁 Output directory: ${detection.outputDir}`);
|
|
236
147
|
}
|
|
@@ -241,10 +152,24 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
241
152
|
else if (verboseMode) {
|
|
242
153
|
console.log(` 📁 Output directory: not found`);
|
|
243
154
|
}
|
|
244
|
-
//
|
|
245
|
-
if (
|
|
155
|
+
// Update browserslist if found and not already set
|
|
156
|
+
if (detection.browserslist && !result.browserslist) {
|
|
157
|
+
result.browserslist = detection.browserslist;
|
|
158
|
+
result.browserslistSource = detection.browserslistSource || filename;
|
|
246
159
|
if (verboseMode) {
|
|
247
|
-
console.log(`
|
|
160
|
+
console.log(` 🌐 Browserslist: ${detection.browserslist.join(", ")} (from ${result.browserslistSource})`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (verboseMode && detection.browserslist) {
|
|
164
|
+
console.log(` 🌐 Browserslist: ${detection.browserslist.join(", ")} (already found in ${result.browserslistSource})`);
|
|
165
|
+
}
|
|
166
|
+
else if (verboseMode) {
|
|
167
|
+
console.log(` 🌐 Browserslist: not found`);
|
|
168
|
+
}
|
|
169
|
+
// If we found all three (target, output directory, and browserslist), we can stop searching
|
|
170
|
+
if (result.target && result.outputDir && result.browserslist) {
|
|
171
|
+
if (verboseMode) {
|
|
172
|
+
console.log(` ✅ All configuration found, stopping search`);
|
|
248
173
|
}
|
|
249
174
|
break;
|
|
250
175
|
}
|
|
@@ -278,6 +203,12 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
278
203
|
else {
|
|
279
204
|
console.log(` 📁 Output directory: not found`);
|
|
280
205
|
}
|
|
206
|
+
if (result.browserslist) {
|
|
207
|
+
console.log(` 🌐 Browserslist: ${result.browserslist.join(", ")} (from ${result.browserslistSource})`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.log(` 🌐 Browserslist: not found`);
|
|
211
|
+
}
|
|
281
212
|
console.log("");
|
|
282
213
|
}
|
|
283
214
|
return result;
|
|
@@ -286,16 +217,37 @@ export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
|
286
217
|
* Legacy function for backward compatibility - detects only ES target
|
|
287
218
|
*/
|
|
288
219
|
export const detectTarget = (cwd = process.cwd()) => {
|
|
289
|
-
const result =
|
|
220
|
+
const result = detectProjectConfig(cwd);
|
|
290
221
|
return result.target ? { target: result.target, source: result.targetSource } : null;
|
|
291
222
|
};
|
|
292
223
|
/**
|
|
293
224
|
* Legacy function for backward compatibility - detects only output directory
|
|
294
225
|
*/
|
|
295
226
|
export const detectOutputDir = (cwd = process.cwd()) => {
|
|
296
|
-
const result =
|
|
227
|
+
const result = detectProjectConfig(cwd);
|
|
297
228
|
return result.outputDir ? { outputDir: result.outputDir, source: result.outputSource } : null;
|
|
298
229
|
};
|
|
230
|
+
/**
|
|
231
|
+
* Legacy function for backward compatibility - detects only browserslist
|
|
232
|
+
*/
|
|
233
|
+
export const detectBrowserslist = (cwd = process.cwd()) => {
|
|
234
|
+
const result = detectProjectConfig(cwd);
|
|
235
|
+
return result.browserslist ? { browserslist: result.browserslist, source: result.browserslistSource } : null;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Legacy function for backward compatibility - detects target and output directory
|
|
239
|
+
*/
|
|
240
|
+
export const detectTargetAndOutput = (cwd = process.cwd()) => {
|
|
241
|
+
const result = detectProjectConfig(cwd);
|
|
242
|
+
return {
|
|
243
|
+
target: result.target,
|
|
244
|
+
targetSource: result.targetSource,
|
|
245
|
+
outputDir: result.outputDir,
|
|
246
|
+
outputSource: result.outputSource,
|
|
247
|
+
browserslist: result.browserslist,
|
|
248
|
+
browserslistSource: result.browserslistSource,
|
|
249
|
+
};
|
|
250
|
+
};
|
|
299
251
|
/**
|
|
300
252
|
* Parse package.json for both target and output directory
|
|
301
253
|
*/
|
|
@@ -306,20 +258,14 @@ const parsePackageJson = (filePath) => {
|
|
|
306
258
|
return {};
|
|
307
259
|
}
|
|
308
260
|
const result = {};
|
|
309
|
-
// Check for browserslist field
|
|
261
|
+
// Check for browserslist field
|
|
310
262
|
if (pkg.browserslist) {
|
|
311
263
|
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
|
-
}
|
|
264
|
+
// Store the full browserslist for CLI defaults
|
|
265
|
+
result.browserslist = browserslist.filter((browser) => typeof browser === "string");
|
|
322
266
|
}
|
|
267
|
+
// Use global project type detection (lazy initialization)
|
|
268
|
+
const projectType = getCurrentProjectType(path.dirname(filePath));
|
|
323
269
|
// Check for output directory hints
|
|
324
270
|
if (pkg.dist) {
|
|
325
271
|
result.outputDir = pkg.dist;
|
|
@@ -330,9 +276,15 @@ const parsePackageJson = (filePath) => {
|
|
|
330
276
|
else if (pkg.main && pkg.main.startsWith("./dist/")) {
|
|
331
277
|
result.outputDir = "dist";
|
|
332
278
|
}
|
|
333
|
-
else if (
|
|
334
|
-
//
|
|
279
|
+
else if (projectType === "nextjs") {
|
|
280
|
+
// Set default output directory for Next.js projects
|
|
335
281
|
result.outputDir = ".next/static";
|
|
282
|
+
result.outputSource = "package.json (default)";
|
|
283
|
+
}
|
|
284
|
+
// If no browserslist was found, use default for detected project type
|
|
285
|
+
if (!result.browserslist && projectType === "nextjs") {
|
|
286
|
+
result.browserslist = [...NEXTJS_DEFAULT_BROWSERSLIST];
|
|
287
|
+
result.browserslistSource = "Next.js default";
|
|
336
288
|
}
|
|
337
289
|
return result;
|
|
338
290
|
};
|
|
@@ -363,6 +315,7 @@ const parseTsConfig = (filePath) => {
|
|
|
363
315
|
*/
|
|
364
316
|
const parseBabelConfig = (filePath) => {
|
|
365
317
|
const content = readTextFile(filePath);
|
|
318
|
+
const result = {};
|
|
366
319
|
// Look for @babel/preset-env configuration
|
|
367
320
|
const presetEnvMatch = content.match(/@babel\/preset-env.*?targets.*?(\{[^}]*\})/s);
|
|
368
321
|
if (presetEnvMatch) {
|
|
@@ -371,13 +324,16 @@ const parseBabelConfig = (filePath) => {
|
|
|
371
324
|
const browsersMatch = targetsStr.match(/browsers.*?\[(.*?)\]/);
|
|
372
325
|
if (browsersMatch) {
|
|
373
326
|
const browsers = browsersMatch[1];
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
327
|
+
// Parse browsers array from string
|
|
328
|
+
const browsersList = browsers
|
|
329
|
+
.split(",")
|
|
330
|
+
.map((b) => b.trim().replace(/['"]/g, ""))
|
|
331
|
+
.filter((b) => b);
|
|
332
|
+
// Store the full browserslist for CLI defaults
|
|
333
|
+
result.browserslist = browsersList;
|
|
378
334
|
}
|
|
379
335
|
}
|
|
380
|
-
return
|
|
336
|
+
return result;
|
|
381
337
|
};
|
|
382
338
|
/**
|
|
383
339
|
* Parse .babelrc for target
|
|
@@ -388,23 +344,20 @@ const parseBabelRc = (filePath) => {
|
|
|
388
344
|
console.warn(`Warning: ${filePath} does not look like a valid .babelrc (missing or invalid presets field).`);
|
|
389
345
|
return {};
|
|
390
346
|
}
|
|
347
|
+
const result = {};
|
|
391
348
|
if (config.presets) {
|
|
392
349
|
for (const preset of config.presets) {
|
|
393
350
|
if (Array.isArray(preset) && preset[0] === "@babel/preset-env") {
|
|
394
351
|
const options = preset[1];
|
|
395
352
|
if (options?.targets?.browsers) {
|
|
396
353
|
const browsers = options.targets.browsers;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (target) {
|
|
400
|
-
return { target };
|
|
401
|
-
}
|
|
402
|
-
}
|
|
354
|
+
// Store the full browserslist for CLI defaults
|
|
355
|
+
result.browserslist = browsers;
|
|
403
356
|
}
|
|
404
357
|
}
|
|
405
358
|
}
|
|
406
359
|
}
|
|
407
|
-
return
|
|
360
|
+
return result;
|
|
408
361
|
};
|
|
409
362
|
/**
|
|
410
363
|
* Parse vite.config.js/ts for both target and output directory
|
|
@@ -454,7 +407,7 @@ const parseWebpackConfig = (filePath) => {
|
|
|
454
407
|
return result;
|
|
455
408
|
};
|
|
456
409
|
/**
|
|
457
|
-
* Parse .browserslistrc or .browserslist file for
|
|
410
|
+
* Parse .browserslistrc or .browserslist file for browserslist
|
|
458
411
|
*/
|
|
459
412
|
const parseBrowserslistFile = (filePath) => {
|
|
460
413
|
const content = readTextFile(filePath);
|
|
@@ -462,13 +415,10 @@ const parseBrowserslistFile = (filePath) => {
|
|
|
462
415
|
.split(/\r?\n/)
|
|
463
416
|
.map((line) => line.trim())
|
|
464
417
|
.filter((line) => line && !line.startsWith("#"));
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
return {};
|
|
418
|
+
const result = {};
|
|
419
|
+
// Store the full browserslist for CLI defaults
|
|
420
|
+
result.browserslist = lines;
|
|
421
|
+
return result;
|
|
472
422
|
};
|
|
473
423
|
/**
|
|
474
424
|
* Parse next.config.js/ts/cjs/mjs for output directory
|
package/dist/lib/globalState.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// Global state management for CLI options
|
|
1
|
+
// Global state management for CLI options and project configuration
|
|
2
2
|
// Static global state variables
|
|
3
3
|
export let verboseMode = false;
|
|
4
4
|
export let debugMode = false;
|
|
5
|
+
export let projectType = undefined;
|
|
6
|
+
export let projectTypeDetected = false;
|
|
5
7
|
/**
|
|
6
8
|
* Set the global verbose mode
|
|
7
9
|
*/
|
|
@@ -21,6 +23,8 @@ export const getGlobalState = () => {
|
|
|
21
23
|
return {
|
|
22
24
|
verbose: verboseMode,
|
|
23
25
|
debug: debugMode,
|
|
26
|
+
projectType,
|
|
27
|
+
projectTypeDetected,
|
|
24
28
|
};
|
|
25
29
|
};
|
|
26
30
|
/**
|
|
@@ -31,6 +35,10 @@ export const setGlobalState = (options) => {
|
|
|
31
35
|
verboseMode = options.verbose;
|
|
32
36
|
if (options.debug !== undefined)
|
|
33
37
|
debugMode = options.debug;
|
|
38
|
+
if (options.projectType !== undefined)
|
|
39
|
+
projectType = options.projectType;
|
|
40
|
+
if (options.projectTypeDetected !== undefined)
|
|
41
|
+
projectTypeDetected = options.projectTypeDetected;
|
|
34
42
|
};
|
|
35
43
|
/**
|
|
36
44
|
* Reset global state to defaults
|
|
@@ -38,4 +46,25 @@ export const setGlobalState = (options) => {
|
|
|
38
46
|
export const resetGlobalState = () => {
|
|
39
47
|
verboseMode = false;
|
|
40
48
|
debugMode = false;
|
|
49
|
+
projectType = undefined;
|
|
50
|
+
projectTypeDetected = false;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Set the detected project type globally
|
|
54
|
+
*/
|
|
55
|
+
export const setProjectType = (detectedType) => {
|
|
56
|
+
projectType = detectedType;
|
|
57
|
+
projectTypeDetected = true;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Get the detected project type (lazy detection if not already done)
|
|
61
|
+
*/
|
|
62
|
+
export const getProjectType = () => {
|
|
63
|
+
return projectType;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Check if project type has been detected
|
|
67
|
+
*/
|
|
68
|
+
export const isProjectTypeDetected = () => {
|
|
69
|
+
return projectTypeDetected;
|
|
41
70
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { verboseMode, setProjectType, getProjectType, isProjectTypeDetected } from "./globalState.js";
|
|
4
|
+
import { detectProjectType } from "./defaults.js";
|
|
5
|
+
import { readJsonFile, isPackageJson } from "./utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* Detect and cache project type globally (lazy initialization)
|
|
8
|
+
*/
|
|
9
|
+
export const detectAndCacheProjectType = (cwd = process.cwd()) => {
|
|
10
|
+
// Return cached result if already detected
|
|
11
|
+
if (isProjectTypeDetected()) {
|
|
12
|
+
return getProjectType() || null;
|
|
13
|
+
}
|
|
14
|
+
// Detect project type from package.json
|
|
15
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
16
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = readJsonFile(packageJsonPath);
|
|
19
|
+
if (isPackageJson(pkg)) {
|
|
20
|
+
const detectedType = detectProjectType(pkg.dependencies, pkg.devDependencies);
|
|
21
|
+
setProjectType(detectedType);
|
|
22
|
+
if (verboseMode) {
|
|
23
|
+
console.log(`🔍 Project type detected: ${detectedType}`);
|
|
24
|
+
}
|
|
25
|
+
return detectedType;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// Ignore errors when detecting project type
|
|
30
|
+
if (verboseMode) {
|
|
31
|
+
console.log(` ⚠️ Error detecting project type: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// No project type detected
|
|
36
|
+
if (verboseMode) {
|
|
37
|
+
console.log(`🔍 No project type detected`);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Get the current project type (detects if not already cached)
|
|
43
|
+
*/
|
|
44
|
+
export const getCurrentProjectType = (cwd = process.cwd()) => {
|
|
45
|
+
return detectAndCacheProjectType(cwd) || "generic";
|
|
46
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Helper to read and parse JSON file safely
|
|
5
|
+
*/
|
|
6
|
+
export const readJsonFile = (filePath) => {
|
|
7
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
8
|
+
return JSON.parse(content);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Helper to read text file safely
|
|
12
|
+
*/
|
|
13
|
+
export const readTextFile = (filePath) => {
|
|
14
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Helper to safely evaluate JavaScript files (for config files)
|
|
18
|
+
*/
|
|
19
|
+
export const evaluateJsFile = (filePath) => {
|
|
20
|
+
const content = readTextFile(filePath);
|
|
21
|
+
// Create a safe evaluation context
|
|
22
|
+
const module = { exports: {} };
|
|
23
|
+
const require = (id) => {
|
|
24
|
+
if (id === "path")
|
|
25
|
+
return path;
|
|
26
|
+
if (id === "fs")
|
|
27
|
+
return fs;
|
|
28
|
+
throw new Error(`Cannot require '${id}' in config evaluation`);
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
// Use Function constructor to create a safe evaluation environment
|
|
32
|
+
const fn = new Function("module", "exports", "require", "path", "fs", "__dirname", content);
|
|
33
|
+
fn(module, module.exports, require, path, fs, path.dirname(filePath));
|
|
34
|
+
return module.exports;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.warn(`Error evaluating ${filePath}:`, error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Type guard for package.json structure
|
|
43
|
+
*/
|
|
44
|
+
export const isPackageJson = (obj) => {
|
|
45
|
+
return typeof obj === "object" && obj !== null;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Type guard for tsconfig.json structure
|
|
49
|
+
*/
|
|
50
|
+
export const isTsConfig = (obj) => {
|
|
51
|
+
return typeof obj === "object" && obj !== null && "compilerOptions" in obj;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Type guard for .babelrc structure
|
|
55
|
+
*/
|
|
56
|
+
export const isBabelRc = (obj) => {
|
|
57
|
+
return typeof obj === "object" && obj !== null && "presets" in obj;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Type guard for vite config structure
|
|
61
|
+
*/
|
|
62
|
+
export const isViteConfig = (obj) => {
|
|
63
|
+
return typeof obj === "object" && obj !== null;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Type guard for webpack config structure
|
|
67
|
+
*/
|
|
68
|
+
export const isWebpackConfig = (obj) => {
|
|
69
|
+
return typeof obj === "object" && obj !== null;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Type guard for next.js config structure
|
|
73
|
+
*/
|
|
74
|
+
export const isNextConfig = (obj) => {
|
|
75
|
+
return typeof obj === "object" && obj !== null;
|
|
76
|
+
};
|