@santi020k/eslint-config-basic 3.0.0 → 3.1.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 +67 -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 +782 -0
- package/dist/cli-workflows.d.ts +63 -0
- package/dist/cli-workflows.js +444 -0
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +447 -50
- package/dist/frameworks.d.ts +1 -25
- package/dist/frameworks.js +2 -23
- package/dist/index.d.ts +4 -9
- package/dist/index.js +83 -68
- package/dist/integrations.js +29 -23
- package/package.json +70 -51
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
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 {
|
|
15
|
+
handleBaseline,
|
|
16
|
+
handleProfile,
|
|
17
|
+
handleSnapshot,
|
|
18
|
+
handleSnapshotDiff
|
|
19
|
+
} from "./cli-workflows.js";
|
|
7
20
|
import { detectProjectOptions } from "./index.js";
|
|
8
21
|
const getDefaultConfigFilename = (cwd) => {
|
|
9
22
|
const packageJsonPath = join(cwd, "package.json");
|
|
@@ -16,15 +29,12 @@ const getDefaultConfigFilename = (cwd) => {
|
|
|
16
29
|
}
|
|
17
30
|
};
|
|
18
31
|
const resolveConfigPath = (cwd) => {
|
|
19
|
-
const existingConfigPath =
|
|
20
|
-
"eslint.config.js",
|
|
21
|
-
"eslint.config.mjs"
|
|
22
|
-
].map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
32
|
+
const existingConfigPath = ESLINT_CONFIG_FILENAMES.map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
23
33
|
return existingConfigPath ?? join(cwd, getDefaultConfigFilename(cwd));
|
|
24
34
|
};
|
|
25
35
|
const getFrameworkKeys = (detectedFrameworks) => {
|
|
26
36
|
const frameworkKeys = new Set(detectedFrameworks ?? []);
|
|
27
|
-
if (frameworkKeys.has("next") || frameworkKeys.has("expo") || frameworkKeys.has("
|
|
37
|
+
if (frameworkKeys.has("next") || frameworkKeys.has("expo") || frameworkKeys.has("react-router") || frameworkKeys.has("tanstack-start") && !frameworkKeys.has("solid")) {
|
|
28
38
|
frameworkKeys.add("react");
|
|
29
39
|
}
|
|
30
40
|
return [...frameworkKeys].sort();
|
|
@@ -43,7 +53,7 @@ const FRAMEWORK_PACKAGE_TO_KEY = {
|
|
|
43
53
|
"@santi020k/eslint-config-qwik": "qwik",
|
|
44
54
|
"@santi020k/eslint-config-react": "react",
|
|
45
55
|
"@santi020k/eslint-config-react-router": "react-router",
|
|
46
|
-
"@santi020k/eslint-config-remix": "
|
|
56
|
+
"@santi020k/eslint-config-remix": "react-router",
|
|
47
57
|
"@santi020k/eslint-config-slidev": "slidev",
|
|
48
58
|
"@santi020k/eslint-config-solid": "solid",
|
|
49
59
|
"@santi020k/eslint-config-svelte": "svelte",
|
|
@@ -51,19 +61,27 @@ const FRAMEWORK_PACKAGE_TO_KEY = {
|
|
|
51
61
|
"@santi020k/eslint-config-vite": "vite",
|
|
52
62
|
"@santi020k/eslint-config-vue": "vue"
|
|
53
63
|
};
|
|
54
|
-
const FRAMEWORK_KEY_TO_PACKAGE =
|
|
55
|
-
Object.
|
|
56
|
-
)
|
|
64
|
+
const FRAMEWORK_KEY_TO_PACKAGE = {
|
|
65
|
+
...Object.fromEntries(
|
|
66
|
+
Object.entries(FRAMEWORK_PACKAGE_TO_KEY).filter(([packageName]) => packageName !== "@santi020k/eslint-config-remix").map(([packageName, framework]) => [framework, packageName])
|
|
67
|
+
),
|
|
68
|
+
"react-router": "@santi020k/eslint-config-react-router"
|
|
69
|
+
};
|
|
57
70
|
const LITE_PACKAGE_NAME = "@santi020k/eslint-config-lite";
|
|
58
71
|
const INTEGRATIONS_PACKAGE_NAME = "@santi020k/eslint-config-integrations";
|
|
59
72
|
const ASTRO_DOCTOR_PACKAGE_NAME = "@santi020k/eslint-plugin-astro-doctor";
|
|
73
|
+
const BASIC_PACKAGE_NAME = "@santi020k/eslint-config-basic";
|
|
74
|
+
const FULL_PACKAGE_NAME = "@santi020k/eslint-config-full";
|
|
60
75
|
const TYPESCRIPT_PACKAGE_NAME = "typescript";
|
|
76
|
+
const CATEGORY_PACKAGES = {
|
|
77
|
+
extensions: "@santi020k/eslint-config-extensions",
|
|
78
|
+
formats: "@santi020k/eslint-config-formats",
|
|
79
|
+
libraries: "@santi020k/eslint-config-libraries",
|
|
80
|
+
testing: "@santi020k/eslint-config-testing",
|
|
81
|
+
tools: "@santi020k/eslint-config-tools"
|
|
82
|
+
};
|
|
61
83
|
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));
|
|
84
|
+
const configPath = ESLINT_CONFIG_FILENAMES.map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
67
85
|
return configPath ?? null;
|
|
68
86
|
};
|
|
69
87
|
const readPackageJson = (cwd) => {
|
|
@@ -113,7 +131,33 @@ const getLiteInstallPackages = (summary, declaredDependencies) => {
|
|
|
113
131
|
}
|
|
114
132
|
return [...packages];
|
|
115
133
|
};
|
|
116
|
-
const
|
|
134
|
+
const getInstallPackages = (summary, declaredDependencies, explicitFeaturePackages = []) => {
|
|
135
|
+
const usesFullPackage = declaredDependencies.has(FULL_PACKAGE_NAME);
|
|
136
|
+
const packages = /* @__PURE__ */ new Set(["eslint"]);
|
|
137
|
+
if (!usesFullPackage) packages.add(BASIC_PACKAGE_NAME);
|
|
138
|
+
if (!usesFullPackage) {
|
|
139
|
+
for (const framework of summary.frameworks) {
|
|
140
|
+
const packageName = FRAMEWORK_KEY_TO_PACKAGE[framework];
|
|
141
|
+
if (packageName) packages.add(packageName);
|
|
142
|
+
}
|
|
143
|
+
const categorySelections = [
|
|
144
|
+
[summary.extensions, CATEGORY_PACKAGES.extensions],
|
|
145
|
+
[summary.formats, CATEGORY_PACKAGES.formats],
|
|
146
|
+
[summary.libraries, CATEGORY_PACKAGES.libraries],
|
|
147
|
+
[summary.testing, CATEGORY_PACKAGES.testing],
|
|
148
|
+
[summary.tools, CATEGORY_PACKAGES.tools]
|
|
149
|
+
];
|
|
150
|
+
for (const [features, packageName] of categorySelections) {
|
|
151
|
+
if (features.length > 0) packages.add(packageName);
|
|
152
|
+
}
|
|
153
|
+
for (const packageName of explicitFeaturePackages) {
|
|
154
|
+
packages.add(packageName);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (summary.typescript) packages.add(TYPESCRIPT_PACKAGE_NAME);
|
|
158
|
+
return [...packages].filter((packageName) => !declaredDependencies.has(packageName));
|
|
159
|
+
};
|
|
160
|
+
const createInstallCommand = (packageManager, packages, workspaceRoot = false) => {
|
|
117
161
|
const packageList = packages.join(" ");
|
|
118
162
|
switch (packageManager) {
|
|
119
163
|
case "bun":
|
|
@@ -123,7 +167,19 @@ const createInstallCommand = (packageManager, packages) => {
|
|
|
123
167
|
case "yarn":
|
|
124
168
|
return `yarn add -D ${packageList}`;
|
|
125
169
|
default:
|
|
126
|
-
return `pnpm add -D ${packageList}`;
|
|
170
|
+
return `pnpm add -D${workspaceRoot ? " --workspace-root" : ""} ${packageList}`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const createInstallInvocation = (packageManager, packages, workspaceRoot = false) => {
|
|
174
|
+
switch (packageManager) {
|
|
175
|
+
case "bun":
|
|
176
|
+
return ["bun", ["add", "-d", ...packages]];
|
|
177
|
+
case "npm":
|
|
178
|
+
return ["npm", ["install", "-D", ...packages]];
|
|
179
|
+
case "yarn":
|
|
180
|
+
return ["yarn", ["add", "-D", ...packages]];
|
|
181
|
+
default:
|
|
182
|
+
return ["pnpm", ["add", "-D", ...workspaceRoot ? ["--workspace-root"] : [], ...packages]];
|
|
127
183
|
}
|
|
128
184
|
};
|
|
129
185
|
const detectPackageManager = (cwd) => {
|
|
@@ -178,8 +234,40 @@ const detectWorkspaceProjects = (cwd) => {
|
|
|
178
234
|
];
|
|
179
235
|
return [...new Set(patterns.flatMap((pattern) => expandWorkspacePattern(cwd, pattern)))].sort();
|
|
180
236
|
};
|
|
181
|
-
const
|
|
182
|
-
const
|
|
237
|
+
const createExplicitOptions = (cwd) => {
|
|
238
|
+
const summary = getProjectSummary(cwd);
|
|
239
|
+
const options = [];
|
|
240
|
+
const optionalCategories = [
|
|
241
|
+
["libraries", summary.libraries],
|
|
242
|
+
["testing", summary.testing],
|
|
243
|
+
["formats", summary.formats],
|
|
244
|
+
["tools", summary.tools],
|
|
245
|
+
["extensions", summary.extensions]
|
|
246
|
+
];
|
|
247
|
+
if (summary.typescript) options.push(" typescript: true,");
|
|
248
|
+
if (summary.frameworks.length > 0) {
|
|
249
|
+
options.push(
|
|
250
|
+
" frameworks: {",
|
|
251
|
+
...summary.frameworks.map((framework) => ` ${toPropertyKey(framework)}: true,`),
|
|
252
|
+
" },"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
for (const [category, values] of optionalCategories) {
|
|
256
|
+
if (values.length > 0) {
|
|
257
|
+
options.push(` ${category}: ${JSON.stringify(values)},`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return options;
|
|
261
|
+
};
|
|
262
|
+
const createConfigContent = (cwd, explicit = false) => {
|
|
263
|
+
const explicitOptions = explicit ? createExplicitOptions(cwd) : [];
|
|
264
|
+
const invocation = explicitOptions.length > 0 ? ["export default defineConfig({", ...explicitOptions, "})"] : ["export default defineConfig()"];
|
|
265
|
+
const configContent = [
|
|
266
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
267
|
+
"",
|
|
268
|
+
...invocation,
|
|
269
|
+
""
|
|
270
|
+
].join("\n");
|
|
183
271
|
return {
|
|
184
272
|
configContent,
|
|
185
273
|
configPath: resolveConfigPath(cwd)
|
|
@@ -193,6 +281,7 @@ const getProjectSummary = (cwd) => {
|
|
|
193
281
|
const options = detectProjectOptions(cwd);
|
|
194
282
|
const workspaceProjects = detectWorkspaceProjects(cwd);
|
|
195
283
|
return {
|
|
284
|
+
detectedProjects: Object.keys(options.projects ?? {}).sort(),
|
|
196
285
|
extensions: options.extensions ?? [],
|
|
197
286
|
formats: options.formats ?? [],
|
|
198
287
|
frameworks: getFrameworkKeys(options.detectedFrameworks),
|
|
@@ -206,6 +295,30 @@ const getProjectSummary = (cwd) => {
|
|
|
206
295
|
workspaceProjects
|
|
207
296
|
};
|
|
208
297
|
};
|
|
298
|
+
const getInstallProjectSummary = (cwd) => {
|
|
299
|
+
const rootSummary = getProjectSummary(cwd);
|
|
300
|
+
const projectSummaries = rootSummary.detectedProjects.map((projectPath) => getProjectSummary(join(cwd, projectPath)));
|
|
301
|
+
const summaries = [rootSummary, ...projectSummaries];
|
|
302
|
+
return {
|
|
303
|
+
...rootSummary,
|
|
304
|
+
extensions: [...new Set(summaries.flatMap((summary) => summary.extensions))],
|
|
305
|
+
formats: [...new Set(summaries.flatMap((summary) => summary.formats))],
|
|
306
|
+
frameworks: [...new Set(summaries.flatMap((summary) => summary.frameworks))].sort(),
|
|
307
|
+
libraries: [...new Set(summaries.flatMap((summary) => summary.libraries))],
|
|
308
|
+
testing: [...new Set(summaries.flatMap((summary) => summary.testing))],
|
|
309
|
+
tools: [...new Set(summaries.flatMap((summary) => summary.tools))],
|
|
310
|
+
typescript: summaries.some((summary) => summary.typescript)
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
const getWorkspaceDeclaredConfigPackages = (cwd, summary) => {
|
|
314
|
+
const packages = /* @__PURE__ */ new Set();
|
|
315
|
+
for (const projectPath of summary.detectedProjects) {
|
|
316
|
+
for (const packageName of getDeclaredDependencyNames(readPackageJson(join(cwd, projectPath)))) {
|
|
317
|
+
if (packageName.startsWith("@santi020k/eslint-config-")) packages.add(packageName);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return [...packages];
|
|
321
|
+
};
|
|
209
322
|
const createStandardsContent = (cwd) => {
|
|
210
323
|
const summary = getProjectSummary(cwd);
|
|
211
324
|
return [
|
|
@@ -229,9 +342,9 @@ const createStandardsContent = (cwd) => {
|
|
|
229
342
|
"## Recommended Config",
|
|
230
343
|
"",
|
|
231
344
|
"```js",
|
|
232
|
-
"import {
|
|
345
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
233
346
|
"",
|
|
234
|
-
"export default await
|
|
347
|
+
"export default await defineConfig()",
|
|
235
348
|
"```",
|
|
236
349
|
"",
|
|
237
350
|
"Use `basic-eslint explain` when you want to inspect what the zero-config setup detects."
|
|
@@ -256,23 +369,43 @@ const printUsage = () => {
|
|
|
256
369
|
" update Regenerate eslint.config.js/mjs from detection",
|
|
257
370
|
" explain Print detected v3 config inputs",
|
|
258
371
|
" inspect Print detected inputs and active config features",
|
|
372
|
+
" install Install missing packages for detected v3 features",
|
|
259
373
|
" doctor Check project setup for common v3 adoption issues",
|
|
374
|
+
" compatibility Check runtime and config package compatibility",
|
|
260
375
|
" docs Generate ESLINT_STANDARDS.md from detection",
|
|
261
|
-
" migrate
|
|
376
|
+
" migrate Plan or apply v1-to-v2 and v2-to-v3 migrations",
|
|
377
|
+
" baseline Suppress existing violations for incremental adoption",
|
|
378
|
+
" profile Compare ESLint performance and report slow rules",
|
|
379
|
+
" snapshot Save the effective rules for representative files",
|
|
380
|
+
" diff Compare effective rules with the saved snapshot",
|
|
262
381
|
" generate-skill Generate AI agent standards files",
|
|
263
382
|
"",
|
|
264
383
|
"Options:",
|
|
265
384
|
" --force Overwrite existing generated skill sections/files",
|
|
266
|
-
" --check
|
|
385
|
+
" --check Verify generated files, migrations, or snapshots without writing",
|
|
386
|
+
" --concurrency profile: off, auto, or a worker count",
|
|
267
387
|
" --create generate-skill: scaffold a root AGENTS.md when missing",
|
|
388
|
+
" --dry-run install: print the detected install command without running it",
|
|
389
|
+
" --explicit init: write detected settings explicitly",
|
|
390
|
+
" --file profile/snapshot/diff: representative file or lint target (repeatable)",
|
|
391
|
+
" --fix doctor: safely repair generated config and package metadata",
|
|
392
|
+
" --full migrate --to v3: choose the batteries-included package",
|
|
268
393
|
" --json Print JSON for commands that support it",
|
|
269
|
-
" --lite-install doctor: print the
|
|
394
|
+
" --lite-install doctor: print the deprecated v2 Lite install command (removed in v4)",
|
|
395
|
+
" --max-duration profile: maximum duration in milliseconds",
|
|
396
|
+
" --max-rule-time profile: maximum time for the slowest rule in milliseconds",
|
|
397
|
+
" --max-warnings profile: maximum warning count",
|
|
398
|
+
" --preset baseline: enable ci or pedantic strict mode before suppressing",
|
|
399
|
+
" --prune baseline: remove suppressions for resolved violations",
|
|
400
|
+
" --snapshot-path snapshot/diff: override .eslint-config-snapshot.json",
|
|
401
|
+
" --to migrate: target version (v2 or v3)",
|
|
402
|
+
" --with-eslint-mcp generate-skill: scaffold the official ESLint MCP server",
|
|
270
403
|
" --write Apply safe migrations for commands that support it",
|
|
271
404
|
" --help, -h Show this help message",
|
|
272
405
|
" --version, -v Show CLI version"
|
|
273
406
|
].join("\n"));
|
|
274
407
|
};
|
|
275
|
-
const handleInit = (cwd = process.cwd(), check = false) => {
|
|
408
|
+
const handleInit = (cwd = process.cwd(), check = false, explicit = false) => {
|
|
276
409
|
const configPath = resolveConfigPath(cwd);
|
|
277
410
|
if (check) {
|
|
278
411
|
const exists = existsSync(configPath);
|
|
@@ -289,7 +422,7 @@ const handleInit = (cwd = process.cwd(), check = false) => {
|
|
|
289
422
|
return;
|
|
290
423
|
}
|
|
291
424
|
console.log("\u{1F50D} Detecting project settings...");
|
|
292
|
-
const { configContent } = createConfigContent(cwd);
|
|
425
|
+
const { configContent } = createConfigContent(cwd, explicit);
|
|
293
426
|
writeFileSync(configPath, configContent);
|
|
294
427
|
console.log(`\u2705 Created ${basename(configPath)} with auto-detected settings!`);
|
|
295
428
|
console.log("\u{1F680} Ready to lint!");
|
|
@@ -429,7 +562,9 @@ const validateConfigContent = (configContent, hasV1FrameworkImports, declaredDep
|
|
|
429
562
|
if (hasV1FrameworkImports) {
|
|
430
563
|
warnings.push("Config still imports v1 framework packages. Run `basic-eslint migrate --write` or switch to framework booleans.");
|
|
431
564
|
}
|
|
432
|
-
|
|
565
|
+
const usesBasicComposer = /from\s*['"]@santi020k\/eslint-config-(?:basic|full|lite)['"]/.test(configContent) && /\b(?:defineConfig|eslintConfig)\s*\(/.test(configContent);
|
|
566
|
+
const hasDetectedProjectScopes = summary.workspaceProjects.every((project) => summary.detectedProjects.includes(project));
|
|
567
|
+
if (summary.workspaceProjects.length > 0 && !configContent.includes("projects:") && !(usesBasicComposer && hasDetectedProjectScopes)) {
|
|
433
568
|
warnings.push("Workspace packages were detected, but the root config does not use `projects` scoping.");
|
|
434
569
|
}
|
|
435
570
|
if (configContent.includes(LITE_PACKAGE_NAME)) {
|
|
@@ -509,7 +644,7 @@ const buildDoctorDiagnosis = (cwd, configPath, configContent, hasV1FrameworkImpo
|
|
|
509
644
|
checkDuplicateEslint(cwd, warnings);
|
|
510
645
|
return { issues, warnings };
|
|
511
646
|
};
|
|
512
|
-
const outputDoctorResult = (json, configPath, packageManager, summary, liteInstallCommand, issues, warnings) => {
|
|
647
|
+
const outputDoctorResult = (json, configPath, packageManager, summary, liteInstallCommand, issues, warnings, fixes = []) => {
|
|
513
648
|
let status = "passed";
|
|
514
649
|
if (issues.length > 0) {
|
|
515
650
|
status = "failed";
|
|
@@ -518,6 +653,7 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
518
653
|
}
|
|
519
654
|
const payload = {
|
|
520
655
|
configFile: configPath ? basename(configPath) : null,
|
|
656
|
+
fixes,
|
|
521
657
|
issues,
|
|
522
658
|
liteInstallCommand,
|
|
523
659
|
packageManager,
|
|
@@ -535,22 +671,88 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
535
671
|
`- Package manager: ${payload.packageManager}`,
|
|
536
672
|
`- Config file: ${payload.configFile ?? "none"}`,
|
|
537
673
|
`- Workspace projects: ${formatList(summary.workspaceProjects)}`,
|
|
674
|
+
...fixes.length > 0 ? ["", "Fixes applied:", ...fixes.map((fix) => `- ${fix}`)] : [],
|
|
538
675
|
...issues.length > 0 ? ["", "Issues:", ...issues.map((issue) => `- ${issue}`)] : [],
|
|
539
676
|
...warnings.length > 0 ? ["", "Warnings:", ...warnings.map((warning) => `- ${warning}`)] : []
|
|
540
677
|
].join("\n"));
|
|
541
678
|
if (issues.length > 0) process.exitCode = 1;
|
|
542
679
|
};
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
680
|
+
const createDoctorBackupPath = (filePath) => {
|
|
681
|
+
const preferred = `${filePath}.doctor.bak`;
|
|
682
|
+
if (!existsSync(preferred)) return preferred;
|
|
683
|
+
let index = 2;
|
|
684
|
+
while (existsSync(`${preferred}.${index}`)) index++;
|
|
685
|
+
return `${preferred}.${index}`;
|
|
686
|
+
};
|
|
687
|
+
const getDoctorDependencyVersion = (packageName) => {
|
|
688
|
+
if (packageName === "eslint") return "^10.0.0";
|
|
689
|
+
if (packageName === TYPESCRIPT_PACKAGE_NAME) return "^5.0.0";
|
|
690
|
+
return "^3.0.0";
|
|
691
|
+
};
|
|
692
|
+
const applyDoctorFixes = (cwd, packageJson, configPath, configContent, summary) => {
|
|
693
|
+
const fixes = [];
|
|
694
|
+
if (packageJson) {
|
|
695
|
+
const updated = structuredClone(packageJson);
|
|
696
|
+
let packageChanged = false;
|
|
697
|
+
updated.scripts ??= {};
|
|
698
|
+
if (!updated.scripts.lint) {
|
|
699
|
+
updated.scripts.lint = "eslint .";
|
|
700
|
+
packageChanged = true;
|
|
701
|
+
fixes.push("Added the `lint` script.");
|
|
702
|
+
}
|
|
703
|
+
updated.devDependencies ??= {};
|
|
704
|
+
const declared = getDeclaredDependencyNames(packageJson);
|
|
705
|
+
const explicitFeaturePackages = configContent ? getExplicitConfigFeaturePackages(configContent) : [];
|
|
706
|
+
const missingPackages = getInstallPackages(summary, declared, explicitFeaturePackages);
|
|
707
|
+
const missingDependencies = Object.fromEntries(
|
|
708
|
+
missingPackages.map((packageName) => [packageName, getDoctorDependencyVersion(packageName)])
|
|
709
|
+
);
|
|
710
|
+
updated.devDependencies = {
|
|
711
|
+
...updated.devDependencies,
|
|
712
|
+
...missingDependencies
|
|
713
|
+
};
|
|
714
|
+
for (const packageName of missingPackages) {
|
|
715
|
+
fixes.push(`Declared ${packageName}.`);
|
|
716
|
+
packageChanged = true;
|
|
717
|
+
}
|
|
718
|
+
if (packageChanged) {
|
|
719
|
+
const packagePath = join(cwd, "package.json");
|
|
720
|
+
const backupPath = createDoctorBackupPath(packagePath);
|
|
721
|
+
writeFileSync(backupPath, `${JSON.stringify(packageJson, null, 2)}
|
|
722
|
+
`);
|
|
723
|
+
writeFileSync(packagePath, `${JSON.stringify(updated, null, 2)}
|
|
724
|
+
`);
|
|
725
|
+
fixes.push(`Backed up package.json to ${basename(backupPath)}.`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (!configPath) {
|
|
729
|
+
const generated = createConfigContent(cwd);
|
|
730
|
+
writeFileSync(generated.configPath, generated.configContent);
|
|
731
|
+
fixes.push(`Created ${basename(generated.configPath)}.`);
|
|
732
|
+
} else if (configContent && Object.keys(FRAMEWORK_PACKAGE_TO_KEY).some((packageName) => configContent.includes(packageName))) {
|
|
733
|
+
const result = migrateConfigContent(configContent);
|
|
734
|
+
if (result.changed) {
|
|
735
|
+
const backupPath = createDoctorBackupPath(configPath);
|
|
736
|
+
writeFileSync(backupPath, configContent);
|
|
737
|
+
writeFileSync(configPath, result.content);
|
|
738
|
+
fixes.push(`Migrated v1 framework imports and backed up the config to ${basename(backupPath)}.`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return fixes;
|
|
742
|
+
};
|
|
743
|
+
const hasV1FrameworkPackageImports = (configContent) => configContent !== null && Object.keys(FRAMEWORK_PACKAGE_TO_KEY).some((packageName) => configContent.includes(packageName));
|
|
744
|
+
const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = false, fix = false) => {
|
|
550
745
|
const packageManager = detectPackageManager(cwd);
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
746
|
+
const workspaceRoot = packageManager === "pnpm" && existsSync(join(cwd, "pnpm-workspace.yaml"));
|
|
747
|
+
let configPath = getConfigPathIfPresent(cwd);
|
|
748
|
+
let packageJson = readPackageJson(cwd);
|
|
749
|
+
let declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
750
|
+
let summary = getInstallProjectSummary(cwd);
|
|
751
|
+
let activeConfig = await analyzeEslintConfig(cwd);
|
|
752
|
+
let configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
753
|
+
let liteInstallPackages = getLiteInstallPackages(summary, declaredDependencies);
|
|
754
|
+
let liteInstallCommand = createInstallCommand(packageManager, liteInstallPackages, workspaceRoot);
|
|
755
|
+
let hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
554
756
|
if (liteInstall) {
|
|
555
757
|
if (json) {
|
|
556
758
|
console.log(JSON.stringify({ command: liteInstallCommand, packageManager, packages: liteInstallPackages }, null, 2));
|
|
@@ -559,8 +761,64 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
559
761
|
}
|
|
560
762
|
return;
|
|
561
763
|
}
|
|
562
|
-
const
|
|
563
|
-
|
|
764
|
+
const fixes = fix ? applyDoctorFixes(cwd, packageJson, configPath, configContent, summary) : [];
|
|
765
|
+
if (fixes.length > 0) {
|
|
766
|
+
configPath = getConfigPathIfPresent(cwd);
|
|
767
|
+
packageJson = readPackageJson(cwd);
|
|
768
|
+
declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
769
|
+
summary = getInstallProjectSummary(cwd);
|
|
770
|
+
activeConfig = await analyzeEslintConfig(cwd);
|
|
771
|
+
configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
772
|
+
liteInstallPackages = getLiteInstallPackages(summary, declaredDependencies);
|
|
773
|
+
liteInstallCommand = createInstallCommand(packageManager, liteInstallPackages, workspaceRoot);
|
|
774
|
+
hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
775
|
+
}
|
|
776
|
+
const { issues, warnings } = buildDoctorDiagnosis(
|
|
777
|
+
cwd,
|
|
778
|
+
configPath,
|
|
779
|
+
configContent,
|
|
780
|
+
hasV1FrameworkImports,
|
|
781
|
+
activeConfig,
|
|
782
|
+
declaredDependencies,
|
|
783
|
+
summary
|
|
784
|
+
);
|
|
785
|
+
outputDoctorResult(json, configPath, packageManager, summary, liteInstallCommand, issues, warnings, fixes);
|
|
786
|
+
};
|
|
787
|
+
const handleInstall = (cwd = process.cwd(), dryRun = false) => {
|
|
788
|
+
const packageJson = readPackageJson(cwd);
|
|
789
|
+
if (!packageJson) {
|
|
790
|
+
console.error("\u274C package.json is missing or invalid.");
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const packageManager = detectPackageManager(cwd);
|
|
795
|
+
const configPath = getConfigPathIfPresent(cwd);
|
|
796
|
+
const explicitFeaturePackages = configPath ? getExplicitConfigFeaturePackages(readFileSync(configPath, "utf8")) : [];
|
|
797
|
+
const packages = getInstallPackages(
|
|
798
|
+
getInstallProjectSummary(cwd),
|
|
799
|
+
getDeclaredDependencyNames(packageJson),
|
|
800
|
+
explicitFeaturePackages
|
|
801
|
+
);
|
|
802
|
+
const workspaceRoot = packageManager === "pnpm" && existsSync(join(cwd, "pnpm-workspace.yaml"));
|
|
803
|
+
if (packages.length === 0) {
|
|
804
|
+
console.log("\u2705 All packages required by the detected ESLint configuration are already declared.");
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const installCommand = createInstallCommand(packageManager, packages, workspaceRoot);
|
|
808
|
+
if (dryRun) {
|
|
809
|
+
console.log(installCommand);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
console.log(`Installing detected ESLint dependencies:
|
|
813
|
+
${installCommand}`);
|
|
814
|
+
const [command, args] = createInstallInvocation(packageManager, packages, workspaceRoot);
|
|
815
|
+
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
816
|
+
if (result.error) {
|
|
817
|
+
console.error(`\u274C Failed to run ${packageManager}: ${result.error.message}`);
|
|
818
|
+
process.exitCode = 1;
|
|
819
|
+
} else if (result.status !== 0) {
|
|
820
|
+
process.exitCode = result.status ?? 1;
|
|
821
|
+
}
|
|
564
822
|
};
|
|
565
823
|
const handleDocs = (cwd = process.cwd()) => {
|
|
566
824
|
const outputPath = join(cwd, "ESLINT_STANDARDS.md");
|
|
@@ -574,9 +832,9 @@ const migrateConfigContent = (content) => {
|
|
|
574
832
|
}
|
|
575
833
|
const frameworks = getFrameworkKeys(importedFrameworks);
|
|
576
834
|
const migrated = [
|
|
577
|
-
"import {
|
|
835
|
+
"import { defineConfig } from '@santi020k/eslint-config-basic'",
|
|
578
836
|
"",
|
|
579
|
-
"export default await
|
|
837
|
+
"export default await defineConfig({",
|
|
580
838
|
" frameworks: {",
|
|
581
839
|
` ${frameworks.map((key) => `${toPropertyKey(key)}: true`).join(",\n ")}`,
|
|
582
840
|
" }",
|
|
@@ -609,14 +867,32 @@ const processConfigMigration = (configPath, write, suggestions) => {
|
|
|
609
867
|
}
|
|
610
868
|
return applied;
|
|
611
869
|
};
|
|
612
|
-
const handleMigrate = (cwd = process.cwd(), write = false, json = false) => {
|
|
870
|
+
const handleMigrate = (cwd = process.cwd(), write = false, json = false, target = "v2", check = false, full = false) => {
|
|
871
|
+
if (target === "v3" || target === "3") {
|
|
872
|
+
const summary = getInstallProjectSummary(cwd);
|
|
873
|
+
handleMigrateV3(cwd, {
|
|
874
|
+
declaredConfigPackages: getWorkspaceDeclaredConfigPackages(cwd, summary),
|
|
875
|
+
extensions: summary.extensions,
|
|
876
|
+
formats: summary.formats,
|
|
877
|
+
frameworks: summary.frameworks,
|
|
878
|
+
libraries: summary.libraries,
|
|
879
|
+
packageManager: detectPackageManager(cwd),
|
|
880
|
+
testing: summary.testing,
|
|
881
|
+
tools: summary.tools,
|
|
882
|
+
typescript: summary.typescript
|
|
883
|
+
}, { check, full, json, write });
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (target !== "v2" && target !== "2") {
|
|
887
|
+
throw new Error(`Unsupported migration target "${target}". Use --to v2 or --to v3.`);
|
|
888
|
+
}
|
|
613
889
|
const configPath = getConfigPathIfPresent(cwd) ?? join(cwd, getDefaultConfigFilename(cwd));
|
|
614
890
|
const suggestions = [
|
|
615
891
|
"v1 to v2 migration suggestions:",
|
|
616
892
|
"- Install only @santi020k/eslint-config-basic for the public config API.",
|
|
617
893
|
"- Replace framework imports with bundled booleans such as frameworks: { react: true, next: true }.",
|
|
618
894
|
"- Remove app-level @santi020k/eslint-config-react/next/vue/etc. config package imports.",
|
|
619
|
-
"- Try
|
|
895
|
+
"- Try defineConfig() first; v3 auto-detects supported frameworks and integrations.",
|
|
620
896
|
"- Use basic-eslint explain to review what v3 detects before committing the migration."
|
|
621
897
|
];
|
|
622
898
|
const applied = processConfigMigration(configPath, write, suggestions);
|
|
@@ -634,30 +910,73 @@ const handleMigrate = (cwd = process.cwd(), write = false, json = false) => {
|
|
|
634
910
|
};
|
|
635
911
|
const dispatchCommand = (command, cwd, flags) => {
|
|
636
912
|
switch (command) {
|
|
913
|
+
case "baseline": {
|
|
914
|
+
try {
|
|
915
|
+
handleBaseline(cwd, {
|
|
916
|
+
json: flags.hasJson,
|
|
917
|
+
preset: flags.preset,
|
|
918
|
+
prune: flags.hasPrune
|
|
919
|
+
});
|
|
920
|
+
} catch (error) {
|
|
921
|
+
console.error(`\u274C Failed to create ESLint baseline: ${String(error)}`);
|
|
922
|
+
process.exitCode = 1;
|
|
923
|
+
}
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
case "compatibility": {
|
|
927
|
+
handleCompatibility(cwd, flags.hasJson);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case "diff": {
|
|
931
|
+
handleSnapshotDiff(cwd, {
|
|
932
|
+
files: flags.files,
|
|
933
|
+
json: flags.hasJson,
|
|
934
|
+
snapshotPath: flags.snapshotPath
|
|
935
|
+
}).catch((error) => {
|
|
936
|
+
console.error(`\u274C Failed to diff ESLint configuration: ${String(error)}`);
|
|
937
|
+
process.exitCode = 1;
|
|
938
|
+
});
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
637
941
|
case "docs": {
|
|
638
942
|
handleDocs(cwd);
|
|
639
943
|
break;
|
|
640
944
|
}
|
|
641
945
|
case "doctor": {
|
|
642
|
-
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall).catch((error) => {
|
|
946
|
+
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall, flags.hasFix).catch((error) => {
|
|
643
947
|
console.error(`\u274C Failed to run doctor: ${String(error)}`);
|
|
644
948
|
process.exitCode = 1;
|
|
645
949
|
});
|
|
646
950
|
break;
|
|
647
951
|
}
|
|
648
952
|
case "explain": {
|
|
649
|
-
|
|
953
|
+
if (flags.rule) {
|
|
954
|
+
handleExplainRule(cwd, {
|
|
955
|
+
file: flags.files[0],
|
|
956
|
+
json: flags.hasJson,
|
|
957
|
+
rule: flags.rule
|
|
958
|
+
}).catch((error) => {
|
|
959
|
+
console.error(`\u274C Failed to explain ESLint rule: ${String(error)}`);
|
|
960
|
+
process.exitCode = 1;
|
|
961
|
+
});
|
|
962
|
+
} else {
|
|
963
|
+
handleExplain(cwd, flags.hasJson);
|
|
964
|
+
}
|
|
650
965
|
break;
|
|
651
966
|
}
|
|
652
967
|
case "generate-skill": {
|
|
653
|
-
handleGenerateSkill(cwd, flags.hasForce, {
|
|
968
|
+
handleGenerateSkill(cwd, flags.hasForce, {
|
|
969
|
+
check: flags.hasCheck,
|
|
970
|
+
createAgentsMd: flags.hasCreate,
|
|
971
|
+
withEslintMcp: flags.hasWithEslintMcp
|
|
972
|
+
}).catch((error) => {
|
|
654
973
|
console.error(`\u274C Failed to generate skill files: ${String(error)}`);
|
|
655
974
|
process.exitCode = 1;
|
|
656
975
|
});
|
|
657
976
|
break;
|
|
658
977
|
}
|
|
659
978
|
case "init": {
|
|
660
|
-
handleInit(cwd, flags.hasCheck);
|
|
979
|
+
handleInit(cwd, flags.hasCheck, flags.hasExplicit);
|
|
661
980
|
break;
|
|
662
981
|
}
|
|
663
982
|
case "inspect": {
|
|
@@ -667,8 +986,52 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
667
986
|
});
|
|
668
987
|
break;
|
|
669
988
|
}
|
|
989
|
+
case "install": {
|
|
990
|
+
handleInstall(cwd, flags.hasDryRun);
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
670
993
|
case "migrate": {
|
|
671
|
-
|
|
994
|
+
try {
|
|
995
|
+
handleMigrate(
|
|
996
|
+
cwd,
|
|
997
|
+
flags.hasWrite,
|
|
998
|
+
flags.hasJson,
|
|
999
|
+
flags.target ?? "v2",
|
|
1000
|
+
flags.hasCheck,
|
|
1001
|
+
flags.hasFull
|
|
1002
|
+
);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
console.error(`\u274C Failed to migrate ESLint configuration: ${String(error)}`);
|
|
1005
|
+
process.exitCode = 1;
|
|
1006
|
+
}
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
case "profile": {
|
|
1010
|
+
try {
|
|
1011
|
+
handleProfile(cwd, {
|
|
1012
|
+
concurrency: flags.concurrency,
|
|
1013
|
+
files: flags.files,
|
|
1014
|
+
json: flags.hasJson,
|
|
1015
|
+
maxDurationMs: flags.maxDurationMs,
|
|
1016
|
+
maxRuleTimeMs: flags.maxRuleTimeMs,
|
|
1017
|
+
maxWarnings: flags.maxWarnings
|
|
1018
|
+
});
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
console.error(`\u274C Failed to profile ESLint: ${String(error)}`);
|
|
1021
|
+
process.exitCode = 1;
|
|
1022
|
+
}
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
case "snapshot": {
|
|
1026
|
+
handleSnapshot(cwd, {
|
|
1027
|
+
check: flags.hasCheck,
|
|
1028
|
+
files: flags.files,
|
|
1029
|
+
json: flags.hasJson,
|
|
1030
|
+
snapshotPath: flags.snapshotPath
|
|
1031
|
+
}).catch((error) => {
|
|
1032
|
+
console.error(`\u274C Failed to snapshot ESLint configuration: ${String(error)}`);
|
|
1033
|
+
process.exitCode = 1;
|
|
1034
|
+
});
|
|
672
1035
|
break;
|
|
673
1036
|
}
|
|
674
1037
|
case "update": {
|
|
@@ -682,6 +1045,24 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
682
1045
|
}
|
|
683
1046
|
}
|
|
684
1047
|
};
|
|
1048
|
+
const getFlagValue = (argv, flag) => {
|
|
1049
|
+
const inline = argv.find((argument) => argument.startsWith(`${flag}=`));
|
|
1050
|
+
if (inline) return inline.slice(flag.length + 1);
|
|
1051
|
+
const index = argv.indexOf(flag);
|
|
1052
|
+
return index >= 0 ? argv[index + 1] : void 0;
|
|
1053
|
+
};
|
|
1054
|
+
const getFlagValues = (argv, flag) => argv.flatMap((argument, index) => {
|
|
1055
|
+
if (argument.startsWith(`${flag}=`)) return [argument.slice(flag.length + 1)];
|
|
1056
|
+
if (argument === flag && argv[index + 1]) return [argv[index + 1]];
|
|
1057
|
+
return [];
|
|
1058
|
+
});
|
|
1059
|
+
const getNumericFlagValue = (argv, flag) => {
|
|
1060
|
+
const value = getFlagValue(argv, flag);
|
|
1061
|
+
const present = argv.includes(flag) || argv.some((argument) => argument.startsWith(`${flag}=`));
|
|
1062
|
+
if (!present) return void 0;
|
|
1063
|
+
if (value === void 0 || value.trim() === "" || value.startsWith("--")) return Number.NaN;
|
|
1064
|
+
return Number(value);
|
|
1065
|
+
};
|
|
685
1066
|
const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
686
1067
|
const command = argv[2];
|
|
687
1068
|
const isHelp = command === "--help" || command === "-h";
|
|
@@ -695,12 +1076,27 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
695
1076
|
return;
|
|
696
1077
|
}
|
|
697
1078
|
dispatchCommand(command, cwd, {
|
|
1079
|
+
concurrency: getFlagValue(argv, "--concurrency"),
|
|
1080
|
+
files: getFlagValues(argv, "--file"),
|
|
698
1081
|
hasCheck: argv.includes("--check"),
|
|
699
1082
|
hasCreate: argv.includes("--create"),
|
|
1083
|
+
hasDryRun: argv.includes("--dry-run"),
|
|
1084
|
+
hasExplicit: argv.includes("--explicit"),
|
|
1085
|
+
hasFix: argv.includes("--fix"),
|
|
700
1086
|
hasForce: argv.includes("--force"),
|
|
1087
|
+
hasFull: argv.includes("--full"),
|
|
701
1088
|
hasJson: argv.includes("--json"),
|
|
702
1089
|
hasLiteInstall: argv.includes("--lite-install"),
|
|
703
|
-
|
|
1090
|
+
hasPrune: argv.includes("--prune"),
|
|
1091
|
+
hasWithEslintMcp: argv.includes("--with-eslint-mcp"),
|
|
1092
|
+
hasWrite: argv.includes("--write"),
|
|
1093
|
+
maxDurationMs: getNumericFlagValue(argv, "--max-duration"),
|
|
1094
|
+
maxRuleTimeMs: getNumericFlagValue(argv, "--max-rule-time"),
|
|
1095
|
+
maxWarnings: getNumericFlagValue(argv, "--max-warnings"),
|
|
1096
|
+
preset: getFlagValue(argv, "--preset"),
|
|
1097
|
+
rule: argv[3] && !argv[3].startsWith("-") ? argv[3] : void 0,
|
|
1098
|
+
snapshotPath: getFlagValue(argv, "--snapshot-path"),
|
|
1099
|
+
target: getFlagValue(argv, "--to")
|
|
704
1100
|
});
|
|
705
1101
|
};
|
|
706
1102
|
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 +1109,7 @@ export {
|
|
|
713
1109
|
handleExplain,
|
|
714
1110
|
handleInit,
|
|
715
1111
|
handleInspect,
|
|
1112
|
+
handleInstall,
|
|
716
1113
|
handleMigrate,
|
|
717
1114
|
handleUpdate,
|
|
718
1115
|
runCli
|