@santi020k/eslint-config-basic 3.0.0 → 3.2.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/CHANGELOG.md +93 -0
- package/IMPROVEMENTS.md +270 -0
- package/README.md +6 -14
- package/dist/agent-skill-generator.d.ts +11 -2
- package/dist/agent-skill-generator.js +56 -8
- package/dist/cli-advanced.d.ts +23 -0
- package/dist/cli-advanced.js +217 -0
- package/dist/cli-migration.d.ts +26 -0
- package/dist/cli-migration.js +1134 -0
- package/dist/cli-preset.d.ts +31 -0
- package/dist/cli-preset.js +315 -0
- package/dist/cli-workflows.d.ts +63 -0
- package/dist/cli-workflows.js +444 -0
- package/dist/cli.d.ts +17 -4
- package/dist/cli.js +762 -55
- package/dist/define-config-metadata.d.ts +10 -0
- package/dist/define-config-metadata.js +18 -0
- package/dist/frameworks.d.ts +1 -25
- package/dist/frameworks.js +5 -23
- package/dist/index.d.ts +11 -10
- package/dist/index.js +146 -74
- package/dist/integrations.js +31 -23
- package/dist/optional-package-errors.d.ts +3 -0
- package/dist/optional-package-errors.js +14 -0
- package/package.json +72 -52
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { basename, dirname, join } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
analyzeEslintConfig,
|
|
9
|
+
ESLINT_CONFIG_FILENAMES,
|
|
10
|
+
handleGenerateSkill
|
|
11
|
+
} from "./agent-skill-generator.js";
|
|
12
|
+
import { handleCompatibility, handleExplainRule } from "./cli-advanced.js";
|
|
13
|
+
import { getExplicitConfigFeaturePackages, handleMigrateV3 } from "./cli-migration.js";
|
|
14
|
+
import { handleExplainPreset } from "./cli-preset.js";
|
|
15
|
+
import {
|
|
16
|
+
handleBaseline,
|
|
17
|
+
handleProfile,
|
|
18
|
+
handleSnapshot,
|
|
19
|
+
handleSnapshotDiff
|
|
20
|
+
} from "./cli-workflows.js";
|
|
7
21
|
import { detectProjectOptions } from "./index.js";
|
|
8
22
|
const getDefaultConfigFilename = (cwd) => {
|
|
9
23
|
const packageJsonPath = join(cwd, "package.json");
|
|
@@ -16,15 +30,12 @@ const getDefaultConfigFilename = (cwd) => {
|
|
|
16
30
|
}
|
|
17
31
|
};
|
|
18
32
|
const resolveConfigPath = (cwd) => {
|
|
19
|
-
const existingConfigPath =
|
|
20
|
-
"eslint.config.js",
|
|
21
|
-
"eslint.config.mjs"
|
|
22
|
-
].map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
33
|
+
const existingConfigPath = ESLINT_CONFIG_FILENAMES.map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
23
34
|
return existingConfigPath ?? join(cwd, getDefaultConfigFilename(cwd));
|
|
24
35
|
};
|
|
25
36
|
const getFrameworkKeys = (detectedFrameworks) => {
|
|
26
37
|
const frameworkKeys = new Set(detectedFrameworks ?? []);
|
|
27
|
-
if (frameworkKeys.has("next") || frameworkKeys.has("expo") || frameworkKeys.has("
|
|
38
|
+
if (frameworkKeys.has("next") || frameworkKeys.has("expo") || frameworkKeys.has("react-router") || frameworkKeys.has("tanstack-start") && !frameworkKeys.has("solid")) {
|
|
28
39
|
frameworkKeys.add("react");
|
|
29
40
|
}
|
|
30
41
|
return [...frameworkKeys].sort();
|
|
@@ -43,7 +54,7 @@ const FRAMEWORK_PACKAGE_TO_KEY = {
|
|
|
43
54
|
"@santi020k/eslint-config-qwik": "qwik",
|
|
44
55
|
"@santi020k/eslint-config-react": "react",
|
|
45
56
|
"@santi020k/eslint-config-react-router": "react-router",
|
|
46
|
-
"@santi020k/eslint-config-remix": "
|
|
57
|
+
"@santi020k/eslint-config-remix": "react-router",
|
|
47
58
|
"@santi020k/eslint-config-slidev": "slidev",
|
|
48
59
|
"@santi020k/eslint-config-solid": "solid",
|
|
49
60
|
"@santi020k/eslint-config-svelte": "svelte",
|
|
@@ -51,19 +62,27 @@ const FRAMEWORK_PACKAGE_TO_KEY = {
|
|
|
51
62
|
"@santi020k/eslint-config-vite": "vite",
|
|
52
63
|
"@santi020k/eslint-config-vue": "vue"
|
|
53
64
|
};
|
|
54
|
-
const FRAMEWORK_KEY_TO_PACKAGE =
|
|
55
|
-
Object.
|
|
56
|
-
)
|
|
65
|
+
const FRAMEWORK_KEY_TO_PACKAGE = {
|
|
66
|
+
...Object.fromEntries(
|
|
67
|
+
Object.entries(FRAMEWORK_PACKAGE_TO_KEY).filter(([packageName]) => packageName !== "@santi020k/eslint-config-remix").map(([packageName, framework]) => [framework, packageName])
|
|
68
|
+
),
|
|
69
|
+
"react-router": "@santi020k/eslint-config-react-router"
|
|
70
|
+
};
|
|
57
71
|
const LITE_PACKAGE_NAME = "@santi020k/eslint-config-lite";
|
|
58
72
|
const INTEGRATIONS_PACKAGE_NAME = "@santi020k/eslint-config-integrations";
|
|
59
73
|
const ASTRO_DOCTOR_PACKAGE_NAME = "@santi020k/eslint-plugin-astro-doctor";
|
|
74
|
+
const BASIC_PACKAGE_NAME = "@santi020k/eslint-config-basic";
|
|
75
|
+
const FULL_PACKAGE_NAME = "@santi020k/eslint-config-full";
|
|
60
76
|
const TYPESCRIPT_PACKAGE_NAME = "typescript";
|
|
77
|
+
const CATEGORY_PACKAGES = {
|
|
78
|
+
extensions: "@santi020k/eslint-config-extensions",
|
|
79
|
+
formats: "@santi020k/eslint-config-formats",
|
|
80
|
+
libraries: "@santi020k/eslint-config-libraries",
|
|
81
|
+
testing: "@santi020k/eslint-config-testing",
|
|
82
|
+
tools: "@santi020k/eslint-config-tools"
|
|
83
|
+
};
|
|
61
84
|
const getConfigPathIfPresent = (cwd) => {
|
|
62
|
-
const configPath =
|
|
63
|
-
"eslint.config.js",
|
|
64
|
-
"eslint.config.mjs",
|
|
65
|
-
"eslint.config.cjs"
|
|
66
|
-
].map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
85
|
+
const configPath = ESLINT_CONFIG_FILENAMES.map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
67
86
|
return configPath ?? null;
|
|
68
87
|
};
|
|
69
88
|
const readPackageJson = (cwd) => {
|
|
@@ -113,7 +132,33 @@ const getLiteInstallPackages = (summary, declaredDependencies) => {
|
|
|
113
132
|
}
|
|
114
133
|
return [...packages];
|
|
115
134
|
};
|
|
116
|
-
const
|
|
135
|
+
const getInstallPackages = (summary, declaredDependencies, explicitFeaturePackages = []) => {
|
|
136
|
+
const usesFullPackage = declaredDependencies.has(FULL_PACKAGE_NAME);
|
|
137
|
+
const packages = /* @__PURE__ */ new Set(["eslint"]);
|
|
138
|
+
if (!usesFullPackage) packages.add(BASIC_PACKAGE_NAME);
|
|
139
|
+
if (!usesFullPackage) {
|
|
140
|
+
for (const framework of summary.frameworks) {
|
|
141
|
+
const packageName = FRAMEWORK_KEY_TO_PACKAGE[framework];
|
|
142
|
+
if (packageName) packages.add(packageName);
|
|
143
|
+
}
|
|
144
|
+
const categorySelections = [
|
|
145
|
+
[summary.extensions, CATEGORY_PACKAGES.extensions],
|
|
146
|
+
[summary.formats, CATEGORY_PACKAGES.formats],
|
|
147
|
+
[summary.libraries, CATEGORY_PACKAGES.libraries],
|
|
148
|
+
[summary.testing, CATEGORY_PACKAGES.testing],
|
|
149
|
+
[summary.tools, CATEGORY_PACKAGES.tools]
|
|
150
|
+
];
|
|
151
|
+
for (const [features, packageName] of categorySelections) {
|
|
152
|
+
if (features.length > 0) packages.add(packageName);
|
|
153
|
+
}
|
|
154
|
+
for (const packageName of explicitFeaturePackages) {
|
|
155
|
+
packages.add(packageName);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (summary.typescript) packages.add(TYPESCRIPT_PACKAGE_NAME);
|
|
159
|
+
return [...packages].filter((packageName) => !declaredDependencies.has(packageName));
|
|
160
|
+
};
|
|
161
|
+
const createInstallCommand = (packageManager, packages, workspaceRoot = false, catalog = false) => {
|
|
117
162
|
const packageList = packages.join(" ");
|
|
118
163
|
switch (packageManager) {
|
|
119
164
|
case "bun":
|
|
@@ -123,15 +168,146 @@ const createInstallCommand = (packageManager, packages) => {
|
|
|
123
168
|
case "yarn":
|
|
124
169
|
return `yarn add -D ${packageList}`;
|
|
125
170
|
default:
|
|
126
|
-
return `pnpm add -D ${packageList}`;
|
|
171
|
+
return `pnpm add -D${workspaceRoot ? " --workspace-root" : ""}` + (catalog === true ? " --save-catalog" : "") + `${typeof catalog === "string" ? ` --save-catalog-name=${catalog}` : ""} ${packageList}`;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const createInstallInvocation = (packageManager, packages, workspaceRoot = false, catalog = false) => {
|
|
175
|
+
switch (packageManager) {
|
|
176
|
+
case "bun":
|
|
177
|
+
return ["bun", ["add", "-d", ...packages]];
|
|
178
|
+
case "npm":
|
|
179
|
+
return ["npm", ["install", "-D", ...packages]];
|
|
180
|
+
case "yarn":
|
|
181
|
+
return ["yarn", ["add", "-D", ...packages]];
|
|
182
|
+
default:
|
|
183
|
+
return ["pnpm", [
|
|
184
|
+
"add",
|
|
185
|
+
"-D",
|
|
186
|
+
...workspaceRoot ? ["--workspace-root"] : [],
|
|
187
|
+
...catalog === true ? ["--save-catalog"] : [],
|
|
188
|
+
...typeof catalog === "string" ? [`--save-catalog-name=${catalog}`] : [],
|
|
189
|
+
...packages
|
|
190
|
+
]];
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const findPnpmWorkspaceRoot = (cwd) => {
|
|
194
|
+
let current = cwd;
|
|
195
|
+
for (; ; ) {
|
|
196
|
+
if (existsSync(join(current, "pnpm-workspace.yaml"))) return current;
|
|
197
|
+
const parent = dirname(current);
|
|
198
|
+
if (parent === current) return void 0;
|
|
199
|
+
current = parent;
|
|
127
200
|
}
|
|
128
201
|
};
|
|
129
202
|
const detectPackageManager = (cwd) => {
|
|
130
|
-
if (
|
|
203
|
+
if (findPnpmWorkspaceRoot(cwd) || existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
131
204
|
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
132
205
|
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
133
206
|
return "npm";
|
|
134
207
|
};
|
|
208
|
+
const getCatalogPreference = (packageJson) => {
|
|
209
|
+
const dependencyFields = ["devDependencies", "dependencies"];
|
|
210
|
+
const dependencyRecords = [];
|
|
211
|
+
const parseCatalog = (value) => {
|
|
212
|
+
if (typeof value !== "string" || !value.startsWith("catalog:")) return false;
|
|
213
|
+
const name = value.slice("catalog:".length);
|
|
214
|
+
return name && name !== "default" ? name : true;
|
|
215
|
+
};
|
|
216
|
+
for (const field of dependencyFields) {
|
|
217
|
+
const dependencies = packageJson?.[field];
|
|
218
|
+
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
|
|
219
|
+
dependencyRecords.push(dependencies);
|
|
220
|
+
}
|
|
221
|
+
for (const dependencies of dependencyRecords) {
|
|
222
|
+
const basicCatalog = parseCatalog(Reflect.get(dependencies, BASIC_PACKAGE_NAME));
|
|
223
|
+
if (basicCatalog) return basicCatalog;
|
|
224
|
+
}
|
|
225
|
+
for (const dependencies of dependencyRecords) {
|
|
226
|
+
for (const value of Object.values(dependencies)) {
|
|
227
|
+
const catalog = parseCatalog(value);
|
|
228
|
+
if (catalog) return catalog;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
};
|
|
233
|
+
const parseYamlMappingLine = (line) => {
|
|
234
|
+
const match = /^(\s*)(?:"([^"]+)"|'([^']+)'|([^'"].*?))\s*:\s*(.*?)\s*$/.exec(line);
|
|
235
|
+
if (!match) return void 0;
|
|
236
|
+
const value = match[5].replace(/\s+#.*$/, "").trim();
|
|
237
|
+
const key = match[2] || match[3] || match[4];
|
|
238
|
+
return {
|
|
239
|
+
indent: match[1].length,
|
|
240
|
+
key: key.trim(),
|
|
241
|
+
value: /^(['"]).*\1$/.test(value) ? value.slice(1, -1) : value
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
const getCatalogVersion = (workspaceRoot, packageName, catalog) => {
|
|
245
|
+
const workspacePath = join(workspaceRoot, "pnpm-workspace.yaml");
|
|
246
|
+
if (!existsSync(workspacePath)) return void 0;
|
|
247
|
+
const lines = readFileSync(workspacePath, "utf8").split(/\r?\n/);
|
|
248
|
+
let sectionIndex = -1;
|
|
249
|
+
let sectionIndent = -1;
|
|
250
|
+
if (catalog === true) {
|
|
251
|
+
sectionIndex = lines.findIndex((line) => {
|
|
252
|
+
const mapping = parseYamlMappingLine(line);
|
|
253
|
+
return mapping?.indent === 0 && mapping.key === "catalog" && mapping.value === "";
|
|
254
|
+
});
|
|
255
|
+
sectionIndent = 0;
|
|
256
|
+
} else {
|
|
257
|
+
const catalogsIndex = lines.findIndex((line) => {
|
|
258
|
+
const mapping = parseYamlMappingLine(line);
|
|
259
|
+
return mapping?.indent === 0 && mapping.key === "catalogs" && mapping.value === "";
|
|
260
|
+
});
|
|
261
|
+
if (catalogsIndex >= 0) {
|
|
262
|
+
for (let index = catalogsIndex + 1; index < lines.length; index++) {
|
|
263
|
+
const mapping = parseYamlMappingLine(lines.at(index) ?? "");
|
|
264
|
+
if (!mapping) continue;
|
|
265
|
+
if (mapping.indent === 0) break;
|
|
266
|
+
if (mapping.key === catalog && mapping.value === "") {
|
|
267
|
+
sectionIndex = index;
|
|
268
|
+
sectionIndent = mapping.indent;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (sectionIndex < 0) return void 0;
|
|
275
|
+
for (let index = sectionIndex + 1; index < lines.length; index++) {
|
|
276
|
+
const mapping = parseYamlMappingLine(lines.at(index) ?? "");
|
|
277
|
+
if (!mapping) continue;
|
|
278
|
+
if (mapping.indent <= sectionIndent) break;
|
|
279
|
+
if (mapping.key === packageName) return mapping.value || void 0;
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
};
|
|
283
|
+
const getCompatibleConfigVersion = (packageJson, workspaceRoot) => {
|
|
284
|
+
const dependencyFields = ["devDependencies", "dependencies", "peerDependencies"];
|
|
285
|
+
let basicSpec;
|
|
286
|
+
for (const field of dependencyFields) {
|
|
287
|
+
const dependencies = packageJson?.[field];
|
|
288
|
+
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
|
|
289
|
+
const value = Object.entries(dependencies).find(([packageName]) => packageName === BASIC_PACKAGE_NAME)?.[1];
|
|
290
|
+
if (typeof value === "string") {
|
|
291
|
+
basicSpec = value;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const catalogName = basicSpec?.startsWith("catalog:") ? basicSpec.slice("catalog:".length) : void 0;
|
|
296
|
+
let selectedCatalog;
|
|
297
|
+
if (catalogName !== void 0) {
|
|
298
|
+
selectedCatalog = catalogName && catalogName !== "default" ? catalogName : true;
|
|
299
|
+
}
|
|
300
|
+
const resolvedSpec = selectedCatalog && workspaceRoot ? getCatalogVersion(workspaceRoot, BASIC_PACKAGE_NAME, selectedCatalog) : basicSpec;
|
|
301
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(resolvedSpec ?? getCliVersion());
|
|
302
|
+
return match ? `^${match[1]}.${match[2]}.${match[3]}` : "^3.1.0";
|
|
303
|
+
};
|
|
304
|
+
const addCompatibleConfigVersions = (packages, version) => {
|
|
305
|
+
const major = /\d+/.exec(version)?.[0];
|
|
306
|
+
const companionVersion = major ? `^${major}.0.0` : version;
|
|
307
|
+
return packages.map(
|
|
308
|
+
(packageName) => packageName.startsWith("@santi020k/eslint-config-") && packageName !== LITE_PACKAGE_NAME && packageName !== INTEGRATIONS_PACKAGE_NAME ? `${packageName}@${packageName === BASIC_PACKAGE_NAME ? version : companionVersion}` : packageName
|
|
309
|
+
);
|
|
310
|
+
};
|
|
135
311
|
const hasLintScript = (cwd) => {
|
|
136
312
|
const packageJson = readPackageJson(cwd);
|
|
137
313
|
return Boolean(packageJson?.scripts?.lint);
|
|
@@ -178,8 +354,42 @@ const detectWorkspaceProjects = (cwd) => {
|
|
|
178
354
|
];
|
|
179
355
|
return [...new Set(patterns.flatMap((pattern) => expandWorkspacePattern(cwd, pattern)))].sort();
|
|
180
356
|
};
|
|
181
|
-
const
|
|
182
|
-
const
|
|
357
|
+
const createExplicitOptions = (cwd) => {
|
|
358
|
+
const summary = getProjectSummary(cwd);
|
|
359
|
+
const options = [];
|
|
360
|
+
const features = [.../* @__PURE__ */ new Set([
|
|
361
|
+
...summary.extensions,
|
|
362
|
+
...summary.formats,
|
|
363
|
+
...summary.libraries,
|
|
364
|
+
...summary.testing,
|
|
365
|
+
...summary.tools
|
|
366
|
+
])].sort();
|
|
367
|
+
if (summary.typescript) options.push(" typescript: true,");
|
|
368
|
+
if (summary.frameworks.length > 0) {
|
|
369
|
+
options.push(
|
|
370
|
+
" frameworks: {",
|
|
371
|
+
...summary.frameworks.map((framework) => ` ${toPropertyKey(framework)}: true,`),
|
|
372
|
+
" },"
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (features.length > 0) {
|
|
376
|
+
options.push(
|
|
377
|
+
" features: {",
|
|
378
|
+
...features.map((feature) => ` ${toPropertyKey(feature)}: true,`),
|
|
379
|
+
" },"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return options;
|
|
383
|
+
};
|
|
384
|
+
const createConfigContent = (cwd, explicit = false) => {
|
|
385
|
+
const explicitOptions = explicit ? createExplicitOptions(cwd) : [];
|
|
386
|
+
const invocation = explicitOptions.length > 0 ? ["export default defineConfig({", ...explicitOptions, "})"] : ["export default defineConfig()"];
|
|
387
|
+
const configContent = [
|
|
388
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
389
|
+
"",
|
|
390
|
+
...invocation,
|
|
391
|
+
""
|
|
392
|
+
].join("\n");
|
|
183
393
|
return {
|
|
184
394
|
configContent,
|
|
185
395
|
configPath: resolveConfigPath(cwd)
|
|
@@ -193,6 +403,7 @@ const getProjectSummary = (cwd) => {
|
|
|
193
403
|
const options = detectProjectOptions(cwd);
|
|
194
404
|
const workspaceProjects = detectWorkspaceProjects(cwd);
|
|
195
405
|
return {
|
|
406
|
+
detectedProjects: Object.keys(options.projects ?? {}).sort(),
|
|
196
407
|
extensions: options.extensions ?? [],
|
|
197
408
|
formats: options.formats ?? [],
|
|
198
409
|
frameworks: getFrameworkKeys(options.detectedFrameworks),
|
|
@@ -206,6 +417,34 @@ const getProjectSummary = (cwd) => {
|
|
|
206
417
|
workspaceProjects
|
|
207
418
|
};
|
|
208
419
|
};
|
|
420
|
+
const getInstallProjectSummary = (cwd) => {
|
|
421
|
+
const rootSummary = getProjectSummary(cwd);
|
|
422
|
+
const projectPaths = [.../* @__PURE__ */ new Set([
|
|
423
|
+
...rootSummary.detectedProjects,
|
|
424
|
+
...rootSummary.workspaceProjects
|
|
425
|
+
])];
|
|
426
|
+
const projectSummaries = projectPaths.map((projectPath) => getProjectSummary(join(cwd, projectPath)));
|
|
427
|
+
const summaries = [rootSummary, ...projectSummaries];
|
|
428
|
+
return {
|
|
429
|
+
...rootSummary,
|
|
430
|
+
extensions: [...new Set(summaries.flatMap((summary) => summary.extensions))],
|
|
431
|
+
formats: [...new Set(summaries.flatMap((summary) => summary.formats))],
|
|
432
|
+
frameworks: [...new Set(summaries.flatMap((summary) => summary.frameworks))].sort(),
|
|
433
|
+
libraries: [...new Set(summaries.flatMap((summary) => summary.libraries))],
|
|
434
|
+
testing: [...new Set(summaries.flatMap((summary) => summary.testing))],
|
|
435
|
+
tools: [...new Set(summaries.flatMap((summary) => summary.tools))],
|
|
436
|
+
typescript: summaries.some((summary) => summary.typescript)
|
|
437
|
+
};
|
|
438
|
+
};
|
|
439
|
+
const getWorkspaceDeclaredConfigPackages = (cwd, summary) => {
|
|
440
|
+
const packages = /* @__PURE__ */ new Set();
|
|
441
|
+
for (const projectPath of summary.detectedProjects) {
|
|
442
|
+
for (const packageName of getDeclaredDependencyNames(readPackageJson(join(cwd, projectPath)))) {
|
|
443
|
+
if (packageName.startsWith("@santi020k/eslint-config-")) packages.add(packageName);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return [...packages];
|
|
447
|
+
};
|
|
209
448
|
const createStandardsContent = (cwd) => {
|
|
210
449
|
const summary = getProjectSummary(cwd);
|
|
211
450
|
return [
|
|
@@ -229,9 +468,9 @@ const createStandardsContent = (cwd) => {
|
|
|
229
468
|
"## Recommended Config",
|
|
230
469
|
"",
|
|
231
470
|
"```js",
|
|
232
|
-
"import {
|
|
471
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
233
472
|
"",
|
|
234
|
-
"export default await
|
|
473
|
+
"export default await defineConfig()",
|
|
235
474
|
"```",
|
|
236
475
|
"",
|
|
237
476
|
"Use `basic-eslint explain` when you want to inspect what the zero-config setup detects."
|
|
@@ -255,24 +494,135 @@ const printUsage = () => {
|
|
|
255
494
|
" init Create eslint.config.js/mjs if missing",
|
|
256
495
|
" update Regenerate eslint.config.js/mjs from detection",
|
|
257
496
|
" explain Print detected v3 config inputs",
|
|
497
|
+
" explain-preset Compare the current config with a preset",
|
|
258
498
|
" inspect Print detected inputs and active config features",
|
|
499
|
+
" install Install missing packages for detected v3 features",
|
|
259
500
|
" doctor Check project setup for common v3 adoption issues",
|
|
501
|
+
" compatibility Check runtime and config package compatibility",
|
|
260
502
|
" docs Generate ESLINT_STANDARDS.md from detection",
|
|
261
|
-
" migrate
|
|
503
|
+
" migrate Plan or apply v1-to-v2 and v2-to-v3 migrations",
|
|
504
|
+
" baseline Suppress existing violations for incremental adoption",
|
|
505
|
+
" profile Compare ESLint performance and report slow rules",
|
|
506
|
+
" snapshot Save the effective rules for representative files",
|
|
507
|
+
" diff Compare effective rules with the saved snapshot",
|
|
262
508
|
" generate-skill Generate AI agent standards files",
|
|
263
509
|
"",
|
|
264
510
|
"Options:",
|
|
265
511
|
" --force Overwrite existing generated skill sections/files",
|
|
266
|
-
" --check
|
|
512
|
+
" --check Verify generated files, migrations, or snapshots without writing",
|
|
513
|
+
" --concurrency profile: off, auto, or a worker count",
|
|
267
514
|
" --create generate-skill: scaffold a root AGENTS.md when missing",
|
|
515
|
+
" --dry-run install: print the detected install command without running it",
|
|
516
|
+
" --explicit init: write detected settings explicitly",
|
|
517
|
+
" --file explain/profile/snapshot/diff: representative file or lint target (repeatable)",
|
|
518
|
+
" --fix doctor: safely repair generated config and package metadata",
|
|
519
|
+
" --full migrate --to v3: choose the batteries-included package",
|
|
268
520
|
" --json Print JSON for commands that support it",
|
|
269
|
-
" --lite-install doctor: print the
|
|
521
|
+
" --lite-install doctor: print the deprecated v2 Lite install command (removed in v4)",
|
|
522
|
+
" --max-duration profile: maximum duration in milliseconds",
|
|
523
|
+
" --max-rule-time profile: maximum time for the slowest rule in milliseconds",
|
|
524
|
+
" --max-warnings profile: maximum warning count",
|
|
525
|
+
" --compatibility explain-preset: write a temporary compatibility override",
|
|
526
|
+
" --output explain-preset: compatibility override path",
|
|
527
|
+
" --preset baseline: enable ci or pedantic strict mode before suppressing",
|
|
528
|
+
" --prune baseline: remove suppressions for resolved violations",
|
|
529
|
+
" --snapshot-path snapshot/diff: override .eslint-config-snapshot.json",
|
|
530
|
+
" --to migrate: target version (v2 or v3)",
|
|
531
|
+
" --with-eslint-mcp generate-skill: scaffold the official ESLint MCP server",
|
|
270
532
|
" --write Apply safe migrations for commands that support it",
|
|
271
533
|
" --help, -h Show this help message",
|
|
272
534
|
" --version, -v Show CLI version"
|
|
273
535
|
].join("\n"));
|
|
274
536
|
};
|
|
275
|
-
const
|
|
537
|
+
const COMMAND_OPTIONS = {
|
|
538
|
+
baseline: ["--json", "--preset", "--prune"],
|
|
539
|
+
compatibility: ["--json"],
|
|
540
|
+
diff: ["--file", "--json", "--snapshot-path"],
|
|
541
|
+
docs: [],
|
|
542
|
+
doctor: ["--fix", "--json", "--lite-install"],
|
|
543
|
+
explain: ["--file", "--json"],
|
|
544
|
+
"explain-preset": ["--compatibility", "--file", "--json", "--output"],
|
|
545
|
+
"generate-skill": ["--check", "--create", "--force", "--with-eslint-mcp"],
|
|
546
|
+
init: ["--check", "--explicit"],
|
|
547
|
+
inspect: ["--json"],
|
|
548
|
+
install: ["--dry-run"],
|
|
549
|
+
migrate: ["--check", "--full", "--json", "--to", "--write"],
|
|
550
|
+
profile: ["--concurrency", "--file", "--json", "--max-duration", "--max-rule-time", "--max-warnings"],
|
|
551
|
+
snapshot: ["--check", "--file", "--json", "--snapshot-path"],
|
|
552
|
+
update: []
|
|
553
|
+
};
|
|
554
|
+
const VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
555
|
+
"--concurrency",
|
|
556
|
+
"--file",
|
|
557
|
+
"--max-duration",
|
|
558
|
+
"--max-rule-time",
|
|
559
|
+
"--max-warnings",
|
|
560
|
+
"--output",
|
|
561
|
+
"--preset",
|
|
562
|
+
"--snapshot-path",
|
|
563
|
+
"--to"
|
|
564
|
+
]);
|
|
565
|
+
const printCommandUsage = (command) => {
|
|
566
|
+
let positional = "";
|
|
567
|
+
if (command === "explain") positional = " [rule]";
|
|
568
|
+
else if (command === "explain-preset") positional = " <preset>";
|
|
569
|
+
const options = COMMAND_OPTIONS[command] ?? [];
|
|
570
|
+
console.log([
|
|
571
|
+
"Usage: basic-eslint <command> [options]",
|
|
572
|
+
"",
|
|
573
|
+
`Usage: basic-eslint ${command}${positional}${options.length > 0 ? " [options]" : ""}`,
|
|
574
|
+
...options.length > 0 ? ["", "Options:", ...options.map((option) => ` ${option}`)] : []
|
|
575
|
+
].join("\n"));
|
|
576
|
+
};
|
|
577
|
+
const validateCommandArguments = (command, args) => {
|
|
578
|
+
const allowed = COMMAND_OPTIONS[command];
|
|
579
|
+
if (!allowed) return { ok: true };
|
|
580
|
+
const allowedOptions = new Set(allowed);
|
|
581
|
+
const positionals = [];
|
|
582
|
+
for (let index = 0; index < args.length; index++) {
|
|
583
|
+
const argument = args[index];
|
|
584
|
+
if (!argument.startsWith("-")) {
|
|
585
|
+
positionals.push(argument);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const option = argument.includes("=") ? argument.slice(0, argument.indexOf("=")) : argument;
|
|
589
|
+
if (!allowedOptions.has(option)) {
|
|
590
|
+
console.error(`Unknown option for ${command}: ${option}`);
|
|
591
|
+
process.exitCode = 1;
|
|
592
|
+
return { ok: false };
|
|
593
|
+
}
|
|
594
|
+
if (argument.includes("=") && !VALUE_OPTIONS.has(option)) {
|
|
595
|
+
console.error(`Option ${option} does not accept a value.`);
|
|
596
|
+
process.exitCode = 1;
|
|
597
|
+
return { ok: false };
|
|
598
|
+
}
|
|
599
|
+
if (VALUE_OPTIONS.has(option) && !argument.includes("=")) {
|
|
600
|
+
const value = args[index + 1];
|
|
601
|
+
if (!value || value.startsWith("-")) {
|
|
602
|
+
console.error(
|
|
603
|
+
["--max-duration", "--max-rule-time", "--max-warnings"].includes(option) ? `${option} must be a non-negative number.` : `Option ${option} requires a value.`
|
|
604
|
+
);
|
|
605
|
+
process.exitCode = 1;
|
|
606
|
+
return { ok: false };
|
|
607
|
+
}
|
|
608
|
+
index++;
|
|
609
|
+
} else if (VALUE_OPTIONS.has(option) && argument.endsWith("=")) {
|
|
610
|
+
console.error(
|
|
611
|
+
["--max-duration", "--max-rule-time", "--max-warnings"].includes(option) ? `${option} must be a non-negative number.` : `Option ${option} requires a value.`
|
|
612
|
+
);
|
|
613
|
+
process.exitCode = 1;
|
|
614
|
+
return { ok: false };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const maximumPositionals = command === "explain" || command === "explain-preset" ? 1 : 0;
|
|
618
|
+
if (positionals.length > maximumPositionals) {
|
|
619
|
+
console.error(`Unexpected argument for ${command}: ${positionals[maximumPositionals]}`);
|
|
620
|
+
process.exitCode = 1;
|
|
621
|
+
return { ok: false };
|
|
622
|
+
}
|
|
623
|
+
return { ok: true, positional: positionals[0] };
|
|
624
|
+
};
|
|
625
|
+
const handleInit = (cwd = process.cwd(), check = false, explicit = false) => {
|
|
276
626
|
const configPath = resolveConfigPath(cwd);
|
|
277
627
|
if (check) {
|
|
278
628
|
const exists = existsSync(configPath);
|
|
@@ -289,7 +639,7 @@ const handleInit = (cwd = process.cwd(), check = false) => {
|
|
|
289
639
|
return;
|
|
290
640
|
}
|
|
291
641
|
console.log("\u{1F50D} Detecting project settings...");
|
|
292
|
-
const { configContent } = createConfigContent(cwd);
|
|
642
|
+
const { configContent } = createConfigContent(cwd, explicit);
|
|
293
643
|
writeFileSync(configPath, configContent);
|
|
294
644
|
console.log(`\u2705 Created ${basename(configPath)} with auto-detected settings!`);
|
|
295
645
|
console.log("\u{1F680} Ready to lint!");
|
|
@@ -429,7 +779,9 @@ const validateConfigContent = (configContent, hasV1FrameworkImports, declaredDep
|
|
|
429
779
|
if (hasV1FrameworkImports) {
|
|
430
780
|
warnings.push("Config still imports v1 framework packages. Run `basic-eslint migrate --write` or switch to framework booleans.");
|
|
431
781
|
}
|
|
432
|
-
|
|
782
|
+
const usesBasicComposer = /from\s*['"]@santi020k\/eslint-config-(?:basic|full|lite)['"]/.test(configContent) && /\b(?:defineConfig|eslintConfig)\s*\(/.test(configContent);
|
|
783
|
+
const hasDetectedProjectScopes = summary.workspaceProjects.every((project) => summary.detectedProjects.includes(project));
|
|
784
|
+
if (summary.workspaceProjects.length > 0 && !configContent.includes("projects:") && !(usesBasicComposer && hasDetectedProjectScopes)) {
|
|
433
785
|
warnings.push("Workspace packages were detected, but the root config does not use `projects` scoping.");
|
|
434
786
|
}
|
|
435
787
|
if (configContent.includes(LITE_PACKAGE_NAME)) {
|
|
@@ -450,6 +802,16 @@ const validateConfigContent = (configContent, hasV1FrameworkImports, declaredDep
|
|
|
450
802
|
);
|
|
451
803
|
}
|
|
452
804
|
}
|
|
805
|
+
const missingPackages = getInstallPackages(
|
|
806
|
+
summary,
|
|
807
|
+
declaredDependencies,
|
|
808
|
+
getExplicitConfigFeaturePackages(configContent)
|
|
809
|
+
);
|
|
810
|
+
if (missingPackages.length > 0) {
|
|
811
|
+
warnings.push(
|
|
812
|
+
`The active configuration requires undeclared packages: ${missingPackages.join(", ")}. Run \`basic-eslint install\` to add the modular v3 dependency set.`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
453
815
|
return warnings;
|
|
454
816
|
};
|
|
455
817
|
const validateProjectSetup = (cwd, configPath, activeConfig, hasV1FrameworkImports) => {
|
|
@@ -509,7 +871,7 @@ const buildDoctorDiagnosis = (cwd, configPath, configContent, hasV1FrameworkImpo
|
|
|
509
871
|
checkDuplicateEslint(cwd, warnings);
|
|
510
872
|
return { issues, warnings };
|
|
511
873
|
};
|
|
512
|
-
const outputDoctorResult = (json, configPath, packageManager, summary,
|
|
874
|
+
const outputDoctorResult = (json, configPath, packageManager, summary, installCommand, requiredPackages, issues, warnings, fixes = []) => {
|
|
513
875
|
let status = "passed";
|
|
514
876
|
if (issues.length > 0) {
|
|
515
877
|
status = "failed";
|
|
@@ -518,9 +880,11 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
518
880
|
}
|
|
519
881
|
const payload = {
|
|
520
882
|
configFile: configPath ? basename(configPath) : null,
|
|
883
|
+
fixes,
|
|
884
|
+
...installCommand ? { installCommand } : {},
|
|
521
885
|
issues,
|
|
522
|
-
liteInstallCommand,
|
|
523
886
|
packageManager,
|
|
887
|
+
requiredPackages,
|
|
524
888
|
status,
|
|
525
889
|
warnings,
|
|
526
890
|
workspaceProjects: summary.workspaceProjects
|
|
@@ -535,23 +899,102 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
535
899
|
`- Package manager: ${payload.packageManager}`,
|
|
536
900
|
`- Config file: ${payload.configFile ?? "none"}`,
|
|
537
901
|
`- Workspace projects: ${formatList(summary.workspaceProjects)}`,
|
|
902
|
+
`- Required packages: ${formatList(requiredPackages)}`,
|
|
903
|
+
...installCommand ? [`- Install command: ${installCommand}`] : [],
|
|
904
|
+
...fixes.length > 0 ? ["", "Fixes applied:", ...fixes.map((fix) => `- ${fix}`)] : [],
|
|
538
905
|
...issues.length > 0 ? ["", "Issues:", ...issues.map((issue) => `- ${issue}`)] : [],
|
|
539
906
|
...warnings.length > 0 ? ["", "Warnings:", ...warnings.map((warning) => `- ${warning}`)] : []
|
|
540
907
|
].join("\n"));
|
|
541
908
|
if (issues.length > 0) process.exitCode = 1;
|
|
542
909
|
};
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
910
|
+
const createDoctorBackupPath = (filePath) => {
|
|
911
|
+
const preferred = `${filePath}.doctor.bak`;
|
|
912
|
+
if (!existsSync(preferred)) return preferred;
|
|
913
|
+
let index = 2;
|
|
914
|
+
while (existsSync(`${preferred}.${index}`)) index++;
|
|
915
|
+
return `${preferred}.${index}`;
|
|
916
|
+
};
|
|
917
|
+
const getDoctorDependencyVersion = (packageName) => {
|
|
918
|
+
if (packageName === "eslint") return "^10.0.0";
|
|
919
|
+
if (packageName === TYPESCRIPT_PACKAGE_NAME) return "^5.0.0";
|
|
920
|
+
return "^3.0.0";
|
|
921
|
+
};
|
|
922
|
+
const applyDoctorFixes = (cwd, packageJson, configPath, configContent, summary) => {
|
|
923
|
+
const fixes = [];
|
|
924
|
+
if (packageJson) {
|
|
925
|
+
const updated = structuredClone(packageJson);
|
|
926
|
+
let packageChanged = false;
|
|
927
|
+
updated.scripts ??= {};
|
|
928
|
+
if (!updated.scripts.lint) {
|
|
929
|
+
updated.scripts.lint = "eslint .";
|
|
930
|
+
packageChanged = true;
|
|
931
|
+
fixes.push("Added the `lint` script.");
|
|
932
|
+
}
|
|
933
|
+
updated.devDependencies ??= {};
|
|
934
|
+
const declared = getDeclaredDependencyNames(packageJson);
|
|
935
|
+
const explicitFeaturePackages = configContent ? getExplicitConfigFeaturePackages(configContent) : [];
|
|
936
|
+
const missingPackages = getInstallPackages(summary, declared, explicitFeaturePackages);
|
|
937
|
+
const missingDependencies = Object.fromEntries(
|
|
938
|
+
missingPackages.map((packageName) => [packageName, getDoctorDependencyVersion(packageName)])
|
|
939
|
+
);
|
|
940
|
+
updated.devDependencies = {
|
|
941
|
+
...updated.devDependencies,
|
|
942
|
+
...missingDependencies
|
|
943
|
+
};
|
|
944
|
+
for (const packageName of missingPackages) {
|
|
945
|
+
fixes.push(`Declared ${packageName}.`);
|
|
946
|
+
packageChanged = true;
|
|
947
|
+
}
|
|
948
|
+
if (packageChanged) {
|
|
949
|
+
const packagePath = join(cwd, "package.json");
|
|
950
|
+
const backupPath = createDoctorBackupPath(packagePath);
|
|
951
|
+
writeFileSync(backupPath, `${JSON.stringify(packageJson, null, 2)}
|
|
952
|
+
`);
|
|
953
|
+
writeFileSync(packagePath, `${JSON.stringify(updated, null, 2)}
|
|
954
|
+
`);
|
|
955
|
+
fixes.push(`Backed up package.json to ${basename(backupPath)}.`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (!configPath) {
|
|
959
|
+
const generated = createConfigContent(cwd);
|
|
960
|
+
writeFileSync(generated.configPath, generated.configContent);
|
|
961
|
+
fixes.push(`Created ${basename(generated.configPath)}.`);
|
|
962
|
+
} else if (configContent && Object.keys(FRAMEWORK_PACKAGE_TO_KEY).some((packageName) => configContent.includes(packageName))) {
|
|
963
|
+
const result = migrateConfigContent(configContent);
|
|
964
|
+
if (result.changed) {
|
|
965
|
+
const backupPath = createDoctorBackupPath(configPath);
|
|
966
|
+
writeFileSync(backupPath, configContent);
|
|
967
|
+
writeFileSync(configPath, result.content);
|
|
968
|
+
fixes.push(`Migrated v1 framework imports and backed up the config to ${basename(backupPath)}.`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return fixes;
|
|
972
|
+
};
|
|
973
|
+
const hasV1FrameworkPackageImports = (configContent) => configContent !== null && Object.keys(FRAMEWORK_PACKAGE_TO_KEY).some((packageName) => configContent.includes(packageName));
|
|
974
|
+
const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = false, fix = false) => {
|
|
975
|
+
const pnpmWorkspaceRoot = findPnpmWorkspaceRoot(cwd);
|
|
976
|
+
const projectRoot = pnpmWorkspaceRoot ?? cwd;
|
|
977
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
978
|
+
const workspaceRoot = packageManager === "pnpm" && pnpmWorkspaceRoot !== void 0;
|
|
979
|
+
const catalog = workspaceRoot ? getCatalogPreference(readPackageJson(projectRoot)) : false;
|
|
980
|
+
let configPath = getConfigPathIfPresent(projectRoot);
|
|
981
|
+
let packageJson = readPackageJson(projectRoot);
|
|
982
|
+
let declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
983
|
+
let summary = getInstallProjectSummary(projectRoot);
|
|
984
|
+
let activeConfig = await analyzeEslintConfig(projectRoot);
|
|
985
|
+
let configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
986
|
+
let hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
554
987
|
if (liteInstall) {
|
|
988
|
+
const liteInstallPackages = getLiteInstallPackages(summary, declaredDependencies);
|
|
989
|
+
const liteInstallCommand = createInstallCommand(
|
|
990
|
+
packageManager,
|
|
991
|
+
addCompatibleConfigVersions(
|
|
992
|
+
liteInstallPackages,
|
|
993
|
+
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
994
|
+
),
|
|
995
|
+
workspaceRoot,
|
|
996
|
+
catalog
|
|
997
|
+
);
|
|
555
998
|
if (json) {
|
|
556
999
|
console.log(JSON.stringify({ command: liteInstallCommand, packageManager, packages: liteInstallPackages }, null, 2));
|
|
557
1000
|
} else {
|
|
@@ -559,8 +1002,106 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
559
1002
|
}
|
|
560
1003
|
return;
|
|
561
1004
|
}
|
|
562
|
-
const
|
|
563
|
-
|
|
1005
|
+
const fixes = fix ? applyDoctorFixes(projectRoot, packageJson, configPath, configContent, summary) : [];
|
|
1006
|
+
if (fixes.length > 0) {
|
|
1007
|
+
configPath = getConfigPathIfPresent(projectRoot);
|
|
1008
|
+
packageJson = readPackageJson(projectRoot);
|
|
1009
|
+
declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
1010
|
+
summary = getInstallProjectSummary(projectRoot);
|
|
1011
|
+
activeConfig = await analyzeEslintConfig(projectRoot);
|
|
1012
|
+
configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
1013
|
+
hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
1014
|
+
}
|
|
1015
|
+
const explicitFeaturePackages = configContent ? getExplicitConfigFeaturePackages(configContent) : [];
|
|
1016
|
+
const requiredPackages = getInstallPackages(summary, declaredDependencies, explicitFeaturePackages);
|
|
1017
|
+
const installCommand = requiredPackages.length > 0 ? createInstallCommand(
|
|
1018
|
+
packageManager,
|
|
1019
|
+
addCompatibleConfigVersions(
|
|
1020
|
+
requiredPackages,
|
|
1021
|
+
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
1022
|
+
),
|
|
1023
|
+
workspaceRoot,
|
|
1024
|
+
catalog
|
|
1025
|
+
) : void 0;
|
|
1026
|
+
const { issues, warnings } = buildDoctorDiagnosis(
|
|
1027
|
+
projectRoot,
|
|
1028
|
+
configPath,
|
|
1029
|
+
configContent,
|
|
1030
|
+
hasV1FrameworkImports,
|
|
1031
|
+
activeConfig,
|
|
1032
|
+
declaredDependencies,
|
|
1033
|
+
summary
|
|
1034
|
+
);
|
|
1035
|
+
outputDoctorResult(
|
|
1036
|
+
json,
|
|
1037
|
+
configPath,
|
|
1038
|
+
packageManager,
|
|
1039
|
+
summary,
|
|
1040
|
+
installCommand,
|
|
1041
|
+
requiredPackages,
|
|
1042
|
+
issues,
|
|
1043
|
+
warnings,
|
|
1044
|
+
fixes
|
|
1045
|
+
);
|
|
1046
|
+
};
|
|
1047
|
+
const isMinimumReleaseAgeFailure = (output) => /minimum[\s_-]*release[\s_-]*age/i.test(output) || /release (?:is )?(?:too young|not old enough)/i.test(output) || /blocked by (?:the )?minimum age/i.test(output);
|
|
1048
|
+
const handleInstall = (cwd = process.cwd(), dryRun = false, runner = spawnSync) => {
|
|
1049
|
+
const pnpmWorkspaceRoot = findPnpmWorkspaceRoot(cwd);
|
|
1050
|
+
const projectRoot = pnpmWorkspaceRoot ?? cwd;
|
|
1051
|
+
const packageJson = readPackageJson(projectRoot);
|
|
1052
|
+
if (!packageJson) {
|
|
1053
|
+
console.error("\u274C package.json is missing or invalid.");
|
|
1054
|
+
process.exitCode = 1;
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
1058
|
+
const configPath = getConfigPathIfPresent(projectRoot);
|
|
1059
|
+
const explicitFeaturePackages = configPath ? getExplicitConfigFeaturePackages(readFileSync(configPath, "utf8")) : [];
|
|
1060
|
+
const packages = getInstallPackages(
|
|
1061
|
+
getInstallProjectSummary(projectRoot),
|
|
1062
|
+
getDeclaredDependencyNames(packageJson),
|
|
1063
|
+
explicitFeaturePackages
|
|
1064
|
+
);
|
|
1065
|
+
const workspaceRoot = packageManager === "pnpm" && pnpmWorkspaceRoot !== void 0;
|
|
1066
|
+
const catalog = workspaceRoot ? getCatalogPreference(packageJson) : false;
|
|
1067
|
+
const installPackages = addCompatibleConfigVersions(
|
|
1068
|
+
packages,
|
|
1069
|
+
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
1070
|
+
);
|
|
1071
|
+
if (packages.length === 0) {
|
|
1072
|
+
console.log("\u2705 All packages required by the detected ESLint configuration are already declared.");
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const installCommand = createInstallCommand(packageManager, installPackages, workspaceRoot, catalog);
|
|
1076
|
+
if (dryRun) {
|
|
1077
|
+
console.log(installCommand);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
console.log(`Installing detected ESLint dependencies:
|
|
1081
|
+
${installCommand}`);
|
|
1082
|
+
const [command, args] = createInstallInvocation(packageManager, installPackages, workspaceRoot, catalog);
|
|
1083
|
+
const result = runner(command, args, {
|
|
1084
|
+
cwd: projectRoot,
|
|
1085
|
+
encoding: "utf8",
|
|
1086
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
1087
|
+
stdio: "pipe"
|
|
1088
|
+
});
|
|
1089
|
+
const stdout = String(result.stdout ?? "");
|
|
1090
|
+
const stderr = String(result.stderr ?? "");
|
|
1091
|
+
if (stdout) process.stdout.write(stdout);
|
|
1092
|
+
if (stderr) process.stderr.write(stderr);
|
|
1093
|
+
if (result.error) {
|
|
1094
|
+
console.error(`\u274C Failed to run ${packageManager}: ${result.error.message}`);
|
|
1095
|
+
process.exitCode = 1;
|
|
1096
|
+
} else if (result.status !== 0) {
|
|
1097
|
+
if (packageManager === "pnpm" && isMinimumReleaseAgeFailure(`${stdout}
|
|
1098
|
+
${stderr}`)) {
|
|
1099
|
+
console.error(
|
|
1100
|
+
`\u274C pnpm minimumReleaseAge temporarily blocks the compatible ESLint config release (${installPackages.join(", ")}). Keep the generated compatible range and either wait for the policy window or add the exact release to minimumReleaseAgeExclude in pnpm-workspace.yaml.`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
process.exitCode = result.status ?? 1;
|
|
1104
|
+
}
|
|
564
1105
|
};
|
|
565
1106
|
const handleDocs = (cwd = process.cwd()) => {
|
|
566
1107
|
const outputPath = join(cwd, "ESLINT_STANDARDS.md");
|
|
@@ -574,9 +1115,9 @@ const migrateConfigContent = (content) => {
|
|
|
574
1115
|
}
|
|
575
1116
|
const frameworks = getFrameworkKeys(importedFrameworks);
|
|
576
1117
|
const migrated = [
|
|
577
|
-
"import {
|
|
1118
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
578
1119
|
"",
|
|
579
|
-
"export default await
|
|
1120
|
+
"export default await defineConfig({",
|
|
580
1121
|
" frameworks: {",
|
|
581
1122
|
` ${frameworks.map((key) => `${toPropertyKey(key)}: true`).join(",\n ")}`,
|
|
582
1123
|
" }",
|
|
@@ -609,14 +1150,32 @@ const processConfigMigration = (configPath, write, suggestions) => {
|
|
|
609
1150
|
}
|
|
610
1151
|
return applied;
|
|
611
1152
|
};
|
|
612
|
-
const handleMigrate = (cwd = process.cwd(), write = false, json = false) => {
|
|
1153
|
+
const handleMigrate = (cwd = process.cwd(), write = false, json = false, target = "v2", check = false, full = false) => {
|
|
1154
|
+
if (target === "v3" || target === "3") {
|
|
1155
|
+
const summary = getInstallProjectSummary(cwd);
|
|
1156
|
+
handleMigrateV3(cwd, {
|
|
1157
|
+
declaredConfigPackages: getWorkspaceDeclaredConfigPackages(cwd, summary),
|
|
1158
|
+
extensions: summary.extensions,
|
|
1159
|
+
formats: summary.formats,
|
|
1160
|
+
frameworks: summary.frameworks,
|
|
1161
|
+
libraries: summary.libraries,
|
|
1162
|
+
packageManager: detectPackageManager(cwd),
|
|
1163
|
+
testing: summary.testing,
|
|
1164
|
+
tools: summary.tools,
|
|
1165
|
+
typescript: summary.typescript
|
|
1166
|
+
}, { check, full, json, write });
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (target !== "v2" && target !== "2") {
|
|
1170
|
+
throw new Error(`Unsupported migration target "${target}". Use --to v2 or --to v3.`);
|
|
1171
|
+
}
|
|
613
1172
|
const configPath = getConfigPathIfPresent(cwd) ?? join(cwd, getDefaultConfigFilename(cwd));
|
|
614
1173
|
const suggestions = [
|
|
615
1174
|
"v1 to v2 migration suggestions:",
|
|
616
1175
|
"- Install only @santi020k/eslint-config-basic for the public config API.",
|
|
617
1176
|
"- Replace framework imports with bundled booleans such as frameworks: { react: true, next: true }.",
|
|
618
1177
|
"- Remove app-level @santi020k/eslint-config-react/next/vue/etc. config package imports.",
|
|
619
|
-
"- Try
|
|
1178
|
+
"- Try defineConfig() first; v3 auto-detects supported frameworks and integrations.",
|
|
620
1179
|
"- Use basic-eslint explain to review what v3 detects before committing the migration."
|
|
621
1180
|
];
|
|
622
1181
|
const applied = processConfigMigration(configPath, write, suggestions);
|
|
@@ -634,30 +1193,90 @@ const handleMigrate = (cwd = process.cwd(), write = false, json = false) => {
|
|
|
634
1193
|
};
|
|
635
1194
|
const dispatchCommand = (command, cwd, flags) => {
|
|
636
1195
|
switch (command) {
|
|
1196
|
+
case "baseline": {
|
|
1197
|
+
try {
|
|
1198
|
+
handleBaseline(cwd, {
|
|
1199
|
+
json: flags.hasJson,
|
|
1200
|
+
preset: flags.preset,
|
|
1201
|
+
prune: flags.hasPrune
|
|
1202
|
+
});
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.error(`\u274C Failed to create ESLint baseline: ${String(error)}`);
|
|
1205
|
+
process.exitCode = 1;
|
|
1206
|
+
}
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
case "compatibility": {
|
|
1210
|
+
handleCompatibility(cwd, flags.hasJson);
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
case "diff": {
|
|
1214
|
+
handleSnapshotDiff(cwd, {
|
|
1215
|
+
files: flags.files,
|
|
1216
|
+
json: flags.hasJson,
|
|
1217
|
+
snapshotPath: flags.snapshotPath
|
|
1218
|
+
}).catch((error) => {
|
|
1219
|
+
console.error(`\u274C Failed to diff ESLint configuration: ${String(error)}`);
|
|
1220
|
+
process.exitCode = 1;
|
|
1221
|
+
});
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
637
1224
|
case "docs": {
|
|
638
1225
|
handleDocs(cwd);
|
|
639
1226
|
break;
|
|
640
1227
|
}
|
|
641
1228
|
case "doctor": {
|
|
642
|
-
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall).catch((error) => {
|
|
1229
|
+
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall, flags.hasFix).catch((error) => {
|
|
643
1230
|
console.error(`\u274C Failed to run doctor: ${String(error)}`);
|
|
644
1231
|
process.exitCode = 1;
|
|
645
1232
|
});
|
|
646
1233
|
break;
|
|
647
1234
|
}
|
|
648
1235
|
case "explain": {
|
|
649
|
-
|
|
1236
|
+
if (flags.rule) {
|
|
1237
|
+
handleExplainRule(cwd, {
|
|
1238
|
+
file: flags.files[0],
|
|
1239
|
+
json: flags.hasJson,
|
|
1240
|
+
rule: flags.rule
|
|
1241
|
+
}).catch((error) => {
|
|
1242
|
+
console.error(`\u274C Failed to explain ESLint rule: ${String(error)}`);
|
|
1243
|
+
process.exitCode = 1;
|
|
1244
|
+
});
|
|
1245
|
+
} else {
|
|
1246
|
+
handleExplain(cwd, flags.hasJson);
|
|
1247
|
+
}
|
|
1248
|
+
break;
|
|
1249
|
+
}
|
|
1250
|
+
case "explain-preset": {
|
|
1251
|
+
if (!flags.positional) {
|
|
1252
|
+
console.error("Missing preset. Usage: basic-eslint explain-preset <preset> [options]");
|
|
1253
|
+
process.exitCode = 1;
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
handleExplainPreset(cwd, flags.positional, {
|
|
1257
|
+
compatibility: flags.hasCompatibility,
|
|
1258
|
+
file: flags.files[0],
|
|
1259
|
+
json: flags.hasJson,
|
|
1260
|
+
output: flags.output
|
|
1261
|
+
}).catch((error) => {
|
|
1262
|
+
console.error(`\u274C Failed to explain preset: ${String(error)}`);
|
|
1263
|
+
process.exitCode = 1;
|
|
1264
|
+
});
|
|
650
1265
|
break;
|
|
651
1266
|
}
|
|
652
1267
|
case "generate-skill": {
|
|
653
|
-
handleGenerateSkill(cwd, flags.hasForce, {
|
|
1268
|
+
handleGenerateSkill(cwd, flags.hasForce, {
|
|
1269
|
+
check: flags.hasCheck,
|
|
1270
|
+
createAgentsMd: flags.hasCreate,
|
|
1271
|
+
withEslintMcp: flags.hasWithEslintMcp
|
|
1272
|
+
}).catch((error) => {
|
|
654
1273
|
console.error(`\u274C Failed to generate skill files: ${String(error)}`);
|
|
655
1274
|
process.exitCode = 1;
|
|
656
1275
|
});
|
|
657
1276
|
break;
|
|
658
1277
|
}
|
|
659
1278
|
case "init": {
|
|
660
|
-
handleInit(cwd, flags.hasCheck);
|
|
1279
|
+
handleInit(cwd, flags.hasCheck, flags.hasExplicit);
|
|
661
1280
|
break;
|
|
662
1281
|
}
|
|
663
1282
|
case "inspect": {
|
|
@@ -667,8 +1286,52 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
667
1286
|
});
|
|
668
1287
|
break;
|
|
669
1288
|
}
|
|
1289
|
+
case "install": {
|
|
1290
|
+
handleInstall(cwd, flags.hasDryRun);
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
670
1293
|
case "migrate": {
|
|
671
|
-
|
|
1294
|
+
try {
|
|
1295
|
+
handleMigrate(
|
|
1296
|
+
cwd,
|
|
1297
|
+
flags.hasWrite,
|
|
1298
|
+
flags.hasJson,
|
|
1299
|
+
flags.target ?? "v2",
|
|
1300
|
+
flags.hasCheck,
|
|
1301
|
+
flags.hasFull
|
|
1302
|
+
);
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
console.error(`\u274C Failed to migrate ESLint configuration: ${String(error)}`);
|
|
1305
|
+
process.exitCode = 1;
|
|
1306
|
+
}
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
case "profile": {
|
|
1310
|
+
try {
|
|
1311
|
+
handleProfile(cwd, {
|
|
1312
|
+
concurrency: flags.concurrency,
|
|
1313
|
+
files: flags.files,
|
|
1314
|
+
json: flags.hasJson,
|
|
1315
|
+
maxDurationMs: flags.maxDurationMs,
|
|
1316
|
+
maxRuleTimeMs: flags.maxRuleTimeMs,
|
|
1317
|
+
maxWarnings: flags.maxWarnings
|
|
1318
|
+
});
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
console.error(`\u274C Failed to profile ESLint: ${String(error)}`);
|
|
1321
|
+
process.exitCode = 1;
|
|
1322
|
+
}
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
case "snapshot": {
|
|
1326
|
+
handleSnapshot(cwd, {
|
|
1327
|
+
check: flags.hasCheck,
|
|
1328
|
+
files: flags.files,
|
|
1329
|
+
json: flags.hasJson,
|
|
1330
|
+
snapshotPath: flags.snapshotPath
|
|
1331
|
+
}).catch((error) => {
|
|
1332
|
+
console.error(`\u274C Failed to snapshot ESLint configuration: ${String(error)}`);
|
|
1333
|
+
process.exitCode = 1;
|
|
1334
|
+
});
|
|
672
1335
|
break;
|
|
673
1336
|
}
|
|
674
1337
|
case "update": {
|
|
@@ -682,11 +1345,29 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
682
1345
|
}
|
|
683
1346
|
}
|
|
684
1347
|
};
|
|
1348
|
+
const getFlagValue = (argv, flag) => {
|
|
1349
|
+
const inline = argv.find((argument) => argument.startsWith(`${flag}=`));
|
|
1350
|
+
if (inline) return inline.slice(flag.length + 1);
|
|
1351
|
+
const index = argv.indexOf(flag);
|
|
1352
|
+
return index >= 0 ? argv[index + 1] : void 0;
|
|
1353
|
+
};
|
|
1354
|
+
const getFlagValues = (argv, flag) => argv.flatMap((argument, index) => {
|
|
1355
|
+
if (argument.startsWith(`${flag}=`)) return [argument.slice(flag.length + 1)];
|
|
1356
|
+
if (argument === flag && argv[index + 1]) return [argv[index + 1]];
|
|
1357
|
+
return [];
|
|
1358
|
+
});
|
|
1359
|
+
const getNumericFlagValue = (argv, flag) => {
|
|
1360
|
+
const value = getFlagValue(argv, flag);
|
|
1361
|
+
const present = argv.includes(flag) || argv.some((argument) => argument.startsWith(`${flag}=`));
|
|
1362
|
+
if (!present) return void 0;
|
|
1363
|
+
if (value === void 0 || value.trim() === "" || value.startsWith("--")) return Number.NaN;
|
|
1364
|
+
return Number(value);
|
|
1365
|
+
};
|
|
685
1366
|
const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
686
1367
|
const command = argv[2];
|
|
687
|
-
const isHelp =
|
|
1368
|
+
const isHelp = argv.slice(3).some((argument) => argument === "--help" || argument === "-h");
|
|
688
1369
|
const isVersion = command === "--version" || command === "-v";
|
|
689
|
-
if (!command ||
|
|
1370
|
+
if (!command || command === "--help" || command === "-h") {
|
|
690
1371
|
printUsage();
|
|
691
1372
|
return;
|
|
692
1373
|
}
|
|
@@ -694,13 +1375,38 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
694
1375
|
console.log(getCliVersion());
|
|
695
1376
|
return;
|
|
696
1377
|
}
|
|
1378
|
+
if (isHelp) {
|
|
1379
|
+
if (COMMAND_OPTIONS[command]) printCommandUsage(command);
|
|
1380
|
+
else printUsage();
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const validation = validateCommandArguments(command, argv.slice(3));
|
|
1384
|
+
if (!validation.ok) return;
|
|
697
1385
|
dispatchCommand(command, cwd, {
|
|
1386
|
+
concurrency: getFlagValue(argv, "--concurrency"),
|
|
1387
|
+
files: getFlagValues(argv, "--file"),
|
|
698
1388
|
hasCheck: argv.includes("--check"),
|
|
1389
|
+
hasCompatibility: argv.includes("--compatibility"),
|
|
699
1390
|
hasCreate: argv.includes("--create"),
|
|
1391
|
+
hasDryRun: argv.includes("--dry-run"),
|
|
1392
|
+
hasExplicit: argv.includes("--explicit"),
|
|
1393
|
+
hasFix: argv.includes("--fix"),
|
|
700
1394
|
hasForce: argv.includes("--force"),
|
|
1395
|
+
hasFull: argv.includes("--full"),
|
|
701
1396
|
hasJson: argv.includes("--json"),
|
|
702
1397
|
hasLiteInstall: argv.includes("--lite-install"),
|
|
703
|
-
|
|
1398
|
+
hasPrune: argv.includes("--prune"),
|
|
1399
|
+
hasWithEslintMcp: argv.includes("--with-eslint-mcp"),
|
|
1400
|
+
hasWrite: argv.includes("--write"),
|
|
1401
|
+
maxDurationMs: getNumericFlagValue(argv, "--max-duration"),
|
|
1402
|
+
maxRuleTimeMs: getNumericFlagValue(argv, "--max-rule-time"),
|
|
1403
|
+
maxWarnings: getNumericFlagValue(argv, "--max-warnings"),
|
|
1404
|
+
output: getFlagValue(argv, "--output"),
|
|
1405
|
+
positional: validation.positional,
|
|
1406
|
+
preset: getFlagValue(argv, "--preset"),
|
|
1407
|
+
rule: command === "explain" ? validation.positional : void 0,
|
|
1408
|
+
snapshotPath: getFlagValue(argv, "--snapshot-path"),
|
|
1409
|
+
target: getFlagValue(argv, "--to")
|
|
704
1410
|
});
|
|
705
1411
|
};
|
|
706
1412
|
if (process.argv[1] && (process.argv[1] === fileURLToPath(import.meta.url) || process.argv[1].endsWith("cli.js") || process.argv[1].endsWith("cli.ts"))) {
|
|
@@ -713,6 +1419,7 @@ export {
|
|
|
713
1419
|
handleExplain,
|
|
714
1420
|
handleInit,
|
|
715
1421
|
handleInspect,
|
|
1422
|
+
handleInstall,
|
|
716
1423
|
handleMigrate,
|
|
717
1424
|
handleUpdate,
|
|
718
1425
|
runCli
|