@santi020k/eslint-config-basic 3.2.0 → 3.3.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 +28 -0
- package/dist/agent-skill-generator.d.ts +7 -18
- package/dist/agent-skill-generator.js +54 -18
- package/dist/agent-skill-generator.js.map +1 -0
- package/dist/cli-advanced.d.ts +7 -0
- package/dist/cli-advanced.js +74 -15
- package/dist/cli-advanced.js.map +1 -0
- package/dist/cli-migration.js +22 -20
- package/dist/cli-migration.js.map +1 -0
- package/dist/cli-package-manager.d.ts +10 -0
- package/dist/cli-package-manager.js +135 -0
- package/dist/cli-package-manager.js.map +1 -0
- package/dist/cli-preset.d.ts +74 -2
- package/dist/cli-preset.js +253 -6
- package/dist/cli-preset.js.map +1 -0
- package/dist/cli-workflows.js +1 -0
- package/dist/cli-workflows.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +461 -176
- package/dist/cli.js.map +1 -0
- package/dist/define-config-metadata.js +2 -4
- package/dist/define-config-metadata.js.map +1 -0
- package/dist/frameworks.d.ts +1 -0
- package/dist/frameworks.js +2 -1
- package/dist/frameworks.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +83 -22
- package/dist/index.js.map +1 -0
- package/dist/integrations.js +1 -0
- package/dist/integrations.js.map +1 -0
- package/dist/optional-package-errors.js +1 -0
- package/dist/optional-package-errors.js.map +1 -0
- package/dist/recommended.js +1 -0
- package/dist/recommended.js.map +1 -0
- package/dist/resolvers.js +1 -0
- package/dist/resolvers.js.map +1 -0
- package/dist/tailwind.d.ts +3 -1
- package/dist/tailwind.js +115 -13
- package/dist/tailwind.js.map +1 -0
- package/package.json +21 -21
- package/IMPROVEMENTS.md +0 -270
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
4
|
+
import { createRequire, findPackageJSON } from "node:module";
|
|
5
5
|
import { basename, dirname, join } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { findTailwindEntryPoint } from "@santi020k/eslint-config-core";
|
|
7
8
|
import {
|
|
8
9
|
analyzeEslintConfig,
|
|
9
10
|
ESLINT_CONFIG_FILENAMES,
|
|
@@ -11,6 +12,15 @@ import {
|
|
|
11
12
|
} from "./agent-skill-generator.js";
|
|
12
13
|
import { handleCompatibility, handleExplainRule } from "./cli-advanced.js";
|
|
13
14
|
import { getExplicitConfigFeaturePackages, handleMigrateV3 } from "./cli-migration.js";
|
|
15
|
+
import {
|
|
16
|
+
addCompatibleConfigVersions,
|
|
17
|
+
createInstallCommand,
|
|
18
|
+
createInstallInvocation,
|
|
19
|
+
detectPackageManager,
|
|
20
|
+
findPnpmWorkspaceRoot,
|
|
21
|
+
getCatalogPreference,
|
|
22
|
+
getCompatibleConfigVersion
|
|
23
|
+
} from "./cli-package-manager.js";
|
|
14
24
|
import { handleExplainPreset } from "./cli-preset.js";
|
|
15
25
|
import {
|
|
16
26
|
handleBaseline,
|
|
@@ -19,6 +29,7 @@ import {
|
|
|
19
29
|
handleSnapshotDiff
|
|
20
30
|
} from "./cli-workflows.js";
|
|
21
31
|
import { detectProjectOptions } from "./index.js";
|
|
32
|
+
import { findCssComponentClasses } from "./tailwind.js";
|
|
22
33
|
const getDefaultConfigFilename = (cwd) => {
|
|
23
34
|
const packageJsonPath = join(cwd, "package.json");
|
|
24
35
|
if (!existsSync(packageJsonPath)) return "eslint.config.mjs";
|
|
@@ -81,6 +92,13 @@ const CATEGORY_PACKAGES = {
|
|
|
81
92
|
testing: "@santi020k/eslint-config-testing",
|
|
82
93
|
tools: "@santi020k/eslint-config-tools"
|
|
83
94
|
};
|
|
95
|
+
const PRESET_INSTALL_PACKAGES = {
|
|
96
|
+
all: Object.values(CATEGORY_PACKAGES),
|
|
97
|
+
app: [CATEGORY_PACKAGES.testing, CATEGORY_PACKAGES.tools],
|
|
98
|
+
ci: [CATEGORY_PACKAGES.extensions, CATEGORY_PACKAGES.tools],
|
|
99
|
+
library: [CATEGORY_PACKAGES.extensions, CATEGORY_PACKAGES.tools],
|
|
100
|
+
monorepo: [CATEGORY_PACKAGES.extensions, CATEGORY_PACKAGES.tools]
|
|
101
|
+
};
|
|
84
102
|
const getConfigPathIfPresent = (cwd) => {
|
|
85
103
|
const configPath = ESLINT_CONFIG_FILENAMES.map((filename) => join(cwd, filename)).find((p) => existsSync(p));
|
|
86
104
|
return configPath ?? null;
|
|
@@ -154,160 +172,13 @@ const getInstallPackages = (summary, declaredDependencies, explicitFeaturePackag
|
|
|
154
172
|
for (const packageName of explicitFeaturePackages) {
|
|
155
173
|
packages.add(packageName);
|
|
156
174
|
}
|
|
175
|
+
for (const packageName of PRESET_INSTALL_PACKAGES[summary.preset] ?? []) {
|
|
176
|
+
packages.add(packageName);
|
|
177
|
+
}
|
|
157
178
|
}
|
|
158
179
|
if (summary.typescript) packages.add(TYPESCRIPT_PACKAGE_NAME);
|
|
159
180
|
return [...packages].filter((packageName) => !declaredDependencies.has(packageName));
|
|
160
181
|
};
|
|
161
|
-
const createInstallCommand = (packageManager, packages, workspaceRoot = false, catalog = false) => {
|
|
162
|
-
const packageList = packages.join(" ");
|
|
163
|
-
switch (packageManager) {
|
|
164
|
-
case "bun":
|
|
165
|
-
return `bun add -d ${packageList}`;
|
|
166
|
-
case "npm":
|
|
167
|
-
return `npm install -D ${packageList}`;
|
|
168
|
-
case "yarn":
|
|
169
|
-
return `yarn add -D ${packageList}`;
|
|
170
|
-
default:
|
|
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;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
const detectPackageManager = (cwd) => {
|
|
203
|
-
if (findPnpmWorkspaceRoot(cwd) || existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
204
|
-
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
205
|
-
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
206
|
-
return "npm";
|
|
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
|
-
};
|
|
311
182
|
const hasLintScript = (cwd) => {
|
|
312
183
|
const packageJson = readPackageJson(cwd);
|
|
313
184
|
return Boolean(packageJson?.scripts?.lint);
|
|
@@ -402,6 +273,10 @@ const formatList = (values) => {
|
|
|
402
273
|
const getProjectSummary = (cwd) => {
|
|
403
274
|
const options = detectProjectOptions(cwd);
|
|
404
275
|
const workspaceProjects = detectWorkspaceProjects(cwd);
|
|
276
|
+
let typescriptMode = "off";
|
|
277
|
+
if (options.typescript) {
|
|
278
|
+
typescriptMode = options.typescript === "syntax" ? "syntax" : "type-aware";
|
|
279
|
+
}
|
|
405
280
|
return {
|
|
406
281
|
detectedProjects: Object.keys(options.projects ?? {}).sort(),
|
|
407
282
|
extensions: options.extensions ?? [],
|
|
@@ -414,9 +289,42 @@ const getProjectSummary = (cwd) => {
|
|
|
414
289
|
testing: options.testing ?? [],
|
|
415
290
|
tools: options.tools ?? [],
|
|
416
291
|
typescript: Boolean(options.typescript),
|
|
292
|
+
typescriptMode,
|
|
417
293
|
workspaceProjects
|
|
418
294
|
};
|
|
419
295
|
};
|
|
296
|
+
const getDetectedProjectPaths = (cwd) => {
|
|
297
|
+
const summary = getProjectSummary(cwd);
|
|
298
|
+
return summary.workspaceProjects.length > 0 ? [.../* @__PURE__ */ new Set([...summary.detectedProjects, ...summary.workspaceProjects])] : ["."];
|
|
299
|
+
};
|
|
300
|
+
const getTailwindUnknownClassPolicy = (detected, entryPoint, componentClasses = 0) => {
|
|
301
|
+
if (!detected) return "off";
|
|
302
|
+
if (!entryPoint) return "fallback-off";
|
|
303
|
+
return componentClasses > 0 ? "strict-with-css-components" : "strict";
|
|
304
|
+
};
|
|
305
|
+
const getTailwindEntryPoints = (cwd) => getDetectedProjectPaths(cwd).flatMap((projectPath) => {
|
|
306
|
+
const projectRoot = projectPath === "." ? cwd : join(cwd, projectPath);
|
|
307
|
+
const summary = getProjectSummary(projectRoot);
|
|
308
|
+
if (!summary.libraries.includes("tailwind")) return [];
|
|
309
|
+
const entryPoint = findTailwindEntryPoint(projectRoot) ?? null;
|
|
310
|
+
const componentClasses = entryPoint ? findCssComponentClasses(projectRoot, entryPoint).length : 0;
|
|
311
|
+
return [{
|
|
312
|
+
componentClasses,
|
|
313
|
+
entryPoint,
|
|
314
|
+
path: projectPath,
|
|
315
|
+
unknownClassPolicy: getTailwindUnknownClassPolicy(true, entryPoint, componentClasses)
|
|
316
|
+
}];
|
|
317
|
+
});
|
|
318
|
+
const getDeclarationProjectWarnings = (cwd) => getDetectedProjectPaths(cwd).flatMap((projectPath) => {
|
|
319
|
+
const projectRoot = projectPath === "." ? cwd : join(cwd, projectPath);
|
|
320
|
+
const summary = getProjectSummary(projectRoot);
|
|
321
|
+
if (summary.typescriptMode !== "syntax") return [];
|
|
322
|
+
const packageName = readPackageJson(projectRoot)?.name;
|
|
323
|
+
const packageLabel = typeof packageName === "string" ? `${projectPath} (${packageName})` : projectPath;
|
|
324
|
+
return [
|
|
325
|
+
`TypeScript declarations were detected in ${packageLabel}, but no supported package-local tsconfig was found. The package uses syntax-only linting to avoid a project-service parse failure. Add a tsconfig.json that includes the declarations for type-aware linting, or add their patterns to \`typescript.untypedFiles\` when a parent type-aware project intentionally owns them.`
|
|
326
|
+
];
|
|
327
|
+
});
|
|
420
328
|
const getInstallProjectSummary = (cwd) => {
|
|
421
329
|
const rootSummary = getProjectSummary(cwd);
|
|
422
330
|
const projectPaths = [.../* @__PURE__ */ new Set([
|
|
@@ -425,6 +333,12 @@ const getInstallProjectSummary = (cwd) => {
|
|
|
425
333
|
])];
|
|
426
334
|
const projectSummaries = projectPaths.map((projectPath) => getProjectSummary(join(cwd, projectPath)));
|
|
427
335
|
const summaries = [rootSummary, ...projectSummaries];
|
|
336
|
+
let typescriptMode = "off";
|
|
337
|
+
if (summaries.some((summary) => summary.typescriptMode === "type-aware")) {
|
|
338
|
+
typescriptMode = "type-aware";
|
|
339
|
+
} else if (summaries.some((summary) => summary.typescriptMode === "syntax")) {
|
|
340
|
+
typescriptMode = "syntax";
|
|
341
|
+
}
|
|
428
342
|
return {
|
|
429
343
|
...rootSummary,
|
|
430
344
|
extensions: [...new Set(summaries.flatMap((summary) => summary.extensions))],
|
|
@@ -433,7 +347,8 @@ const getInstallProjectSummary = (cwd) => {
|
|
|
433
347
|
libraries: [...new Set(summaries.flatMap((summary) => summary.libraries))],
|
|
434
348
|
testing: [...new Set(summaries.flatMap((summary) => summary.testing))],
|
|
435
349
|
tools: [...new Set(summaries.flatMap((summary) => summary.tools))],
|
|
436
|
-
typescript: summaries.some((summary) => summary.typescript)
|
|
350
|
+
typescript: summaries.some((summary) => summary.typescript),
|
|
351
|
+
typescriptMode
|
|
437
352
|
};
|
|
438
353
|
};
|
|
439
354
|
const getWorkspaceDeclaredConfigPackages = (cwd, summary) => {
|
|
@@ -508,13 +423,14 @@ const printUsage = () => {
|
|
|
508
423
|
" generate-skill Generate AI agent standards files",
|
|
509
424
|
"",
|
|
510
425
|
"Options:",
|
|
426
|
+
" --analyze-source explain-preset: lint source and preview autofix without writing",
|
|
511
427
|
" --force Overwrite existing generated skill sections/files",
|
|
512
428
|
" --check Verify generated files, migrations, or snapshots without writing",
|
|
513
429
|
" --concurrency profile: off, auto, or a worker count",
|
|
514
430
|
" --create generate-skill: scaffold a root AGENTS.md when missing",
|
|
515
431
|
" --dry-run install: print the detected install command without running it",
|
|
516
432
|
" --explicit init: write detected settings explicitly",
|
|
517
|
-
" --file explain/profile/snapshot/diff:
|
|
433
|
+
" --file explain/explain-preset/profile/snapshot/diff: file or lint target (repeatable)",
|
|
518
434
|
" --fix doctor: safely repair generated config and package metadata",
|
|
519
435
|
" --full migrate --to v3: choose the batteries-included package",
|
|
520
436
|
" --json Print JSON for commands that support it",
|
|
@@ -528,6 +444,7 @@ const printUsage = () => {
|
|
|
528
444
|
" --prune baseline: remove suppressions for resolved violations",
|
|
529
445
|
" --snapshot-path snapshot/diff: override .eslint-config-snapshot.json",
|
|
530
446
|
" --to migrate: target version (v2 or v3)",
|
|
447
|
+
" --verbose doctor: print per-project activation details",
|
|
531
448
|
" --with-eslint-mcp generate-skill: scaffold the official ESLint MCP server",
|
|
532
449
|
" --write Apply safe migrations for commands that support it",
|
|
533
450
|
" --help, -h Show this help message",
|
|
@@ -539,9 +456,9 @@ const COMMAND_OPTIONS = {
|
|
|
539
456
|
compatibility: ["--json"],
|
|
540
457
|
diff: ["--file", "--json", "--snapshot-path"],
|
|
541
458
|
docs: [],
|
|
542
|
-
doctor: ["--fix", "--json", "--lite-install"],
|
|
459
|
+
doctor: ["--fix", "--json", "--lite-install", "--verbose"],
|
|
543
460
|
explain: ["--file", "--json"],
|
|
544
|
-
"explain-preset": ["--compatibility", "--file", "--json", "--output"],
|
|
461
|
+
"explain-preset": ["--analyze-source", "--compatibility", "--file", "--json", "--output"],
|
|
545
462
|
"generate-skill": ["--check", "--create", "--force", "--with-eslint-mcp"],
|
|
546
463
|
init: ["--check", "--explicit"],
|
|
547
464
|
inspect: ["--json"],
|
|
@@ -683,7 +600,8 @@ const handleInspect = async (cwd = process.cwd(), json = false) => {
|
|
|
683
600
|
const payload = {
|
|
684
601
|
activeConfig,
|
|
685
602
|
detected: summary,
|
|
686
|
-
packageManager: detectPackageManager(cwd)
|
|
603
|
+
packageManager: detectPackageManager(cwd),
|
|
604
|
+
tailwindEntryPoints: getTailwindEntryPoints(cwd)
|
|
687
605
|
};
|
|
688
606
|
if (json) {
|
|
689
607
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -707,31 +625,135 @@ const handleInspect = async (cwd = process.cwd(), json = false) => {
|
|
|
707
625
|
].join("\n"));
|
|
708
626
|
};
|
|
709
627
|
const semver = createRequire(import.meta.url)("semver");
|
|
710
|
-
const
|
|
628
|
+
const intersectTwoSemverRanges = (left, right) => {
|
|
629
|
+
if (semver.subset(left, right)) return left;
|
|
630
|
+
if (semver.subset(right, left)) return right;
|
|
631
|
+
const intersections = /* @__PURE__ */ new Set();
|
|
632
|
+
const leftRange = new semver.Range(left);
|
|
633
|
+
const rightRange = new semver.Range(right);
|
|
634
|
+
for (const leftComparators of leftRange.set) {
|
|
635
|
+
for (const rightComparators of rightRange.set) {
|
|
636
|
+
const candidate = [...leftComparators, ...rightComparators].map((comparator) => comparator.value).filter(Boolean).join(" ");
|
|
637
|
+
const normalized = new semver.Range(candidate || "*").range || "*";
|
|
638
|
+
if (semver.minVersion(normalized)) intersections.add(normalized);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return intersections.size > 0 ? [...intersections].join(" || ") : null;
|
|
642
|
+
};
|
|
643
|
+
const intersectSemverRanges = (ranges) => {
|
|
644
|
+
const validRanges = ranges.filter((range) => semver.validRange(range) !== null);
|
|
645
|
+
if (validRanges.length !== ranges.length || validRanges.length === 0) return null;
|
|
646
|
+
let intersection = validRanges[0];
|
|
647
|
+
for (const range of validRanges.slice(1)) {
|
|
648
|
+
intersection = intersectTwoSemverRanges(intersection, range) ?? "";
|
|
649
|
+
if (!intersection) return null;
|
|
650
|
+
}
|
|
651
|
+
return intersection;
|
|
652
|
+
};
|
|
653
|
+
const readPackageMetadataFromMain = (mainPath) => {
|
|
711
654
|
let directory = dirname(mainPath);
|
|
712
655
|
for (let depth = 0; depth < 4; depth++) {
|
|
713
656
|
const packagePath = join(directory, "package.json");
|
|
714
657
|
if (existsSync(packagePath)) {
|
|
715
658
|
const metadata = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
716
|
-
if (metadata.name
|
|
659
|
+
if (metadata.name && metadata.version) return metadata;
|
|
717
660
|
}
|
|
718
661
|
directory = dirname(directory);
|
|
719
662
|
}
|
|
720
663
|
return null;
|
|
721
664
|
};
|
|
722
665
|
const resolvePackageMetadata = (cwd, packageName) => {
|
|
723
|
-
|
|
724
|
-
|
|
666
|
+
const candidateRoots = [...new Set([
|
|
667
|
+
cwd,
|
|
668
|
+
findPnpmWorkspaceRoot(cwd)
|
|
669
|
+
].filter((root) => Boolean(root)))];
|
|
670
|
+
for (const candidateRoot of candidateRoots) {
|
|
725
671
|
try {
|
|
726
|
-
|
|
672
|
+
const manifestPath = findPackageJSON(
|
|
673
|
+
packageName,
|
|
674
|
+
pathToFileURL(join(candidateRoot, "package.json"))
|
|
675
|
+
);
|
|
676
|
+
if (manifestPath) {
|
|
677
|
+
const metadata = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
678
|
+
return metadata;
|
|
679
|
+
}
|
|
727
680
|
} catch {
|
|
728
|
-
const integrationsMain = projectRequire.resolve(INTEGRATIONS_PACKAGE_NAME);
|
|
729
|
-
const integrationsRequire = createRequire(integrationsMain);
|
|
730
|
-
return readPackageMetadataFromMain(integrationsRequire.resolve(packageName), packageName);
|
|
731
681
|
}
|
|
732
|
-
|
|
733
|
-
|
|
682
|
+
try {
|
|
683
|
+
const projectRequire = createRequire(join(candidateRoot, "package.json"));
|
|
684
|
+
try {
|
|
685
|
+
const metadata = readPackageMetadataFromMain(projectRequire.resolve(packageName));
|
|
686
|
+
if (metadata) return metadata;
|
|
687
|
+
} catch {
|
|
688
|
+
const integrationsMain = projectRequire.resolve(INTEGRATIONS_PACKAGE_NAME);
|
|
689
|
+
const integrationsRequire = createRequire(integrationsMain);
|
|
690
|
+
const metadata = readPackageMetadataFromMain(integrationsRequire.resolve(packageName));
|
|
691
|
+
if (metadata) return metadata;
|
|
692
|
+
}
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
for (const projectPath of detectWorkspaceProjects(candidateRoot)) {
|
|
696
|
+
const metadata = readPackageJson(join(candidateRoot, projectPath));
|
|
697
|
+
if (metadata?.name === packageName) return metadata;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
};
|
|
702
|
+
const assessConsumerNodeEngine = (cwd, packageJson) => {
|
|
703
|
+
const declaredConfigPackages = [...getDeclaredDependencyNames(packageJson)].filter((packageName) => packageName.startsWith("@santi020k/eslint-config-"));
|
|
704
|
+
const requiredRanges = declaredConfigPackages.flatMap((packageName) => {
|
|
705
|
+
const range = resolvePackageMetadata(cwd, packageName)?.engines?.node;
|
|
706
|
+
return range ? [range] : [];
|
|
707
|
+
});
|
|
708
|
+
const requiredRange = intersectSemverRanges(requiredRanges);
|
|
709
|
+
const engines = packageJson?.engines;
|
|
710
|
+
const consumerRange = engines && typeof engines === "object" && !Array.isArray(engines) ? engines.node : null;
|
|
711
|
+
const normalizedConsumerRange = typeof consumerRange === "string" ? consumerRange : null;
|
|
712
|
+
if (requiredRanges.length === 0) {
|
|
713
|
+
return {
|
|
714
|
+
compatible: true,
|
|
715
|
+
consumerRange: normalizedConsumerRange,
|
|
716
|
+
requiredRange: null,
|
|
717
|
+
targetRange: null
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (!requiredRange) {
|
|
721
|
+
return {
|
|
722
|
+
compatible: false,
|
|
723
|
+
consumerRange: normalizedConsumerRange,
|
|
724
|
+
requiredRange: null,
|
|
725
|
+
targetRange: null
|
|
726
|
+
};
|
|
734
727
|
}
|
|
728
|
+
if (!normalizedConsumerRange || !semver.validRange(normalizedConsumerRange)) {
|
|
729
|
+
return {
|
|
730
|
+
compatible: false,
|
|
731
|
+
consumerRange: normalizedConsumerRange,
|
|
732
|
+
requiredRange,
|
|
733
|
+
targetRange: requiredRange
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const compatible = requiredRanges.every((range) => semver.subset(normalizedConsumerRange, range));
|
|
737
|
+
return {
|
|
738
|
+
compatible,
|
|
739
|
+
consumerRange: normalizedConsumerRange,
|
|
740
|
+
requiredRange,
|
|
741
|
+
targetRange: compatible ? null : intersectSemverRanges([normalizedConsumerRange, ...requiredRanges])
|
|
742
|
+
};
|
|
743
|
+
};
|
|
744
|
+
const getConsumerNodeEngineWarning = (cwd, packageJson) => {
|
|
745
|
+
const assessment = assessConsumerNodeEngine(cwd, packageJson);
|
|
746
|
+
if (assessment.compatible) return null;
|
|
747
|
+
if (!assessment.requiredRange) {
|
|
748
|
+
return "Installed config packages do not share a compatible Node engine range.";
|
|
749
|
+
}
|
|
750
|
+
if (!assessment.consumerRange) {
|
|
751
|
+
return `package.json does not declare engines.node; installed config packages require ${assessment.requiredRange}.`;
|
|
752
|
+
}
|
|
753
|
+
if (!semver.validRange(assessment.consumerRange)) {
|
|
754
|
+
return `package.json engines.node ${assessment.consumerRange} is invalid; installed config packages require ${assessment.requiredRange}.`;
|
|
755
|
+
}
|
|
756
|
+
return `package.json engines.node ${assessment.consumerRange} permits unsupported runtimes; installed config packages require ${assessment.requiredRange}.`;
|
|
735
757
|
};
|
|
736
758
|
const getAstroDoctorNodeWarning = (metadata) => {
|
|
737
759
|
const nodeRange = metadata.engines?.node;
|
|
@@ -779,7 +801,11 @@ const validateConfigContent = (configContent, hasV1FrameworkImports, declaredDep
|
|
|
779
801
|
if (hasV1FrameworkImports) {
|
|
780
802
|
warnings.push("Config still imports v1 framework packages. Run `basic-eslint migrate --write` or switch to framework booleans.");
|
|
781
803
|
}
|
|
782
|
-
const
|
|
804
|
+
const usesComposerCall = /from\s*['"]@santi020k\/eslint-config-(?:basic|full|lite)['"]/.test(configContent) && /\b(?:defineConfig|eslintConfig)\s*\(/.test(configContent);
|
|
805
|
+
const usesRecommendedComposer = /from\s*['"]@santi020k\/eslint-config-(?:basic|full)\/recommended['"]/.test(
|
|
806
|
+
configContent
|
|
807
|
+
);
|
|
808
|
+
const usesBasicComposer = usesComposerCall || usesRecommendedComposer;
|
|
783
809
|
const hasDetectedProjectScopes = summary.workspaceProjects.every((project) => summary.detectedProjects.includes(project));
|
|
784
810
|
if (summary.workspaceProjects.length > 0 && !configContent.includes("projects:") && !(usesBasicComposer && hasDetectedProjectScopes)) {
|
|
785
811
|
warnings.push("Workspace packages were detected, but the root config does not use `projects` scoping.");
|
|
@@ -861,7 +887,13 @@ const checkDuplicateEslint = (cwd, warnings) => {
|
|
|
861
887
|
);
|
|
862
888
|
}
|
|
863
889
|
};
|
|
864
|
-
const
|
|
890
|
+
const getTailwindEntryPointWarnings = (cwd) => getTailwindEntryPoints(cwd).flatMap(({ entryPoint, path: projectPath }) => {
|
|
891
|
+
if (entryPoint) return [];
|
|
892
|
+
return [
|
|
893
|
+
`Tailwind CSS was detected in ${projectPath}, but no conventional CSS entry point was found. \`better-tailwindcss/no-unknown-classes\` falls back to off; set \`tailwind.entryPoint\` to enable strict validation or \`tailwind.noUnknownClasses: false\` to document a mixed CSS system.`
|
|
894
|
+
];
|
|
895
|
+
});
|
|
896
|
+
const buildDoctorDiagnosis = (cwd, packageJson, configPath, configContent, hasV1FrameworkImports, activeConfig, declaredDependencies, summary) => {
|
|
865
897
|
const { issues, warnings } = validateProjectSetup(cwd, configPath, activeConfig, hasV1FrameworkImports);
|
|
866
898
|
if (configContent) {
|
|
867
899
|
warnings.push(...validateConfigContent(configContent, hasV1FrameworkImports, declaredDependencies, summary));
|
|
@@ -869,9 +901,238 @@ const buildDoctorDiagnosis = (cwd, configPath, configContent, hasV1FrameworkImpo
|
|
|
869
901
|
const astroDoctorEnabled = checkAstroDoctorStatus(activeConfig, configContent, summary, warnings);
|
|
870
902
|
warnings.push(...getAstroDoctorCompatibilityWarnings(cwd, astroDoctorEnabled));
|
|
871
903
|
checkDuplicateEslint(cwd, warnings);
|
|
904
|
+
warnings.push(...getDeclarationProjectWarnings(cwd));
|
|
905
|
+
warnings.push(...getTailwindEntryPointWarnings(cwd));
|
|
906
|
+
const nodeEngineWarning = getConsumerNodeEngineWarning(cwd, packageJson);
|
|
907
|
+
if (nodeEngineWarning) warnings.push(nodeEngineWarning);
|
|
872
908
|
return { issues, warnings };
|
|
873
909
|
};
|
|
874
|
-
const
|
|
910
|
+
const DOCTOR_FEATURE_ALIASES = {
|
|
911
|
+
"astro-doctor": "Astro Doctor",
|
|
912
|
+
"best-practices": "Best Practices",
|
|
913
|
+
compat: "Browser Compat",
|
|
914
|
+
nest: "NestJS",
|
|
915
|
+
next: "Next.js",
|
|
916
|
+
node: "Node.js",
|
|
917
|
+
solid: "SolidJS",
|
|
918
|
+
"tanstack-start": "TanStack Start",
|
|
919
|
+
tailwind: "Tailwind CSS"
|
|
920
|
+
};
|
|
921
|
+
const normalizeDoctorFeature = (value) => {
|
|
922
|
+
const comparableValue = DOCTOR_FEATURE_ALIASES[value] ?? value;
|
|
923
|
+
return comparableValue.toLowerCase().replaceAll(/[^a-z0-9]/g, "");
|
|
924
|
+
};
|
|
925
|
+
const isDoctorFeatureEnabled = (name, enabled) => {
|
|
926
|
+
const normalizedName = normalizeDoctorFeature(name);
|
|
927
|
+
return enabled.some((value) => normalizeDoctorFeature(value) === normalizedName);
|
|
928
|
+
};
|
|
929
|
+
const getTypeScriptConfig = (cwd) => [
|
|
930
|
+
"tsconfig.json",
|
|
931
|
+
"tsconfig.base.json",
|
|
932
|
+
"tsconfig.app.json",
|
|
933
|
+
"tsconfig.node.json",
|
|
934
|
+
"tsconfig.eslint.json"
|
|
935
|
+
].find((fileName) => existsSync(join(cwd, fileName))) ?? null;
|
|
936
|
+
const getCategoryPackageInstalled = (cwd, packageName) => [
|
|
937
|
+
packageName,
|
|
938
|
+
INTEGRATIONS_PACKAGE_NAME,
|
|
939
|
+
FULL_PACKAGE_NAME
|
|
940
|
+
].some((candidate) => resolvePackageMetadata(cwd, candidate) !== null);
|
|
941
|
+
const createDoctorFeatureActivations = (cwd, category, detected, enabled, includeEnabled = false) => {
|
|
942
|
+
const names = [...detected];
|
|
943
|
+
if (includeEnabled) {
|
|
944
|
+
for (const enabledName of enabled) {
|
|
945
|
+
if (!isDoctorFeatureEnabled(enabledName, names)) names.push(enabledName);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return names.map((name) => {
|
|
949
|
+
const packageName = category === "frameworks" ? Object.entries(FRAMEWORK_KEY_TO_PACKAGE).find(([framework]) => isDoctorFeatureEnabled(name, [framework]))?.[1] : CATEGORY_PACKAGES[category];
|
|
950
|
+
if (!packageName) throw new Error(`Unknown doctor ${category} feature: ${name}`);
|
|
951
|
+
const installed = getCategoryPackageInstalled(cwd, packageName);
|
|
952
|
+
const isDetected = isDoctorFeatureEnabled(name, detected);
|
|
953
|
+
const isEnabled = installed && isDoctorFeatureEnabled(name, enabled);
|
|
954
|
+
let reason;
|
|
955
|
+
if (!installed) {
|
|
956
|
+
reason = isDetected ? `Detected, but ${packageName} is not installed.` : `Enabled in the active config, but ${packageName} is not installed.`;
|
|
957
|
+
} else if (!isEnabled) {
|
|
958
|
+
reason = "Detected and installed, but the active config did not load this pack.";
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
detected: isDetected,
|
|
962
|
+
enabled: isEnabled,
|
|
963
|
+
installed,
|
|
964
|
+
name,
|
|
965
|
+
package: packageName,
|
|
966
|
+
...reason ? { reason } : {}
|
|
967
|
+
};
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
const getInactiveDoctorPackages = (cwd, detectedFrameworks, summary, activeConfig) => {
|
|
971
|
+
const inactivePackages = Object.entries(FRAMEWORK_KEY_TO_PACKAGE).filter(([framework, packageName]) => !detectedFrameworks.includes(framework) && !isDoctorFeatureEnabled(framework, activeConfig?.frameworks ?? []) && resolvePackageMetadata(cwd, packageName) !== null).map(([, packageName]) => ({
|
|
972
|
+
package: packageName,
|
|
973
|
+
reason: "Installed, but no matching framework signal was detected in this project."
|
|
974
|
+
}));
|
|
975
|
+
for (const [category, packageName] of Object.entries(CATEGORY_PACKAGES)) {
|
|
976
|
+
if (summary[category].length === 0 && (activeConfig?.[category]?.length ?? 0) === 0 && resolvePackageMetadata(cwd, packageName) !== null) {
|
|
977
|
+
inactivePackages.push({
|
|
978
|
+
package: packageName,
|
|
979
|
+
reason: `Installed, but no ${category} signals were detected in this project.`
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return inactivePackages;
|
|
984
|
+
};
|
|
985
|
+
const getDoctorProjectActivations = async (cwd) => {
|
|
986
|
+
const rootSummary = getProjectSummary(cwd);
|
|
987
|
+
const projectPaths = [.../* @__PURE__ */ new Set([
|
|
988
|
+
".",
|
|
989
|
+
...rootSummary.detectedProjects,
|
|
990
|
+
...rootSummary.workspaceProjects
|
|
991
|
+
])].sort((left, right) => {
|
|
992
|
+
if (left === ".") return -1;
|
|
993
|
+
if (right === ".") return 1;
|
|
994
|
+
return left.localeCompare(right);
|
|
995
|
+
});
|
|
996
|
+
return Promise.all(projectPaths.map(async (projectPath) => {
|
|
997
|
+
const projectRoot = projectPath === "." ? cwd : join(cwd, projectPath);
|
|
998
|
+
const summary = getProjectSummary(projectRoot);
|
|
999
|
+
const projectActiveConfig = await analyzeEslintConfig(cwd, { projectPath, projectPaths }) ?? void 0;
|
|
1000
|
+
const tsconfig = getTypeScriptConfig(projectRoot);
|
|
1001
|
+
const typescriptInstalled = resolvePackageMetadata(projectRoot, TYPESCRIPT_PACKAGE_NAME) !== null;
|
|
1002
|
+
const typescriptEnabled = summary.typescript && Boolean(projectActiveConfig?.typescript);
|
|
1003
|
+
const tailwindDetected = summary.libraries.includes("tailwind");
|
|
1004
|
+
const tailwindEntryPoint = tailwindDetected ? findTailwindEntryPoint(projectRoot) ?? null : null;
|
|
1005
|
+
const tailwindComponentClasses = tailwindEntryPoint ? findCssComponentClasses(projectRoot, tailwindEntryPoint).length : 0;
|
|
1006
|
+
let typescriptMode = "off";
|
|
1007
|
+
let typescriptReason;
|
|
1008
|
+
if (typescriptEnabled) {
|
|
1009
|
+
typescriptMode = summary.typescriptMode;
|
|
1010
|
+
}
|
|
1011
|
+
if (!typescriptInstalled && summary.typescript) {
|
|
1012
|
+
typescriptReason = summary.typescriptMode === "syntax" ? "TypeScript declarations were detected, but TypeScript is not installed." : "A tsconfig was detected, but TypeScript is not installed.";
|
|
1013
|
+
} else if (typescriptInstalled && !summary.typescript) {
|
|
1014
|
+
typescriptReason = "Installed, but no supported tsconfig was detected in this project.";
|
|
1015
|
+
} else if (typescriptInstalled && summary.typescript && !typescriptEnabled) {
|
|
1016
|
+
typescriptReason = "Detected and installed, but the active config did not enable TypeScript.";
|
|
1017
|
+
}
|
|
1018
|
+
return {
|
|
1019
|
+
extensions: createDoctorFeatureActivations(
|
|
1020
|
+
projectRoot,
|
|
1021
|
+
"extensions",
|
|
1022
|
+
summary.extensions,
|
|
1023
|
+
projectActiveConfig?.extensions ?? [],
|
|
1024
|
+
true
|
|
1025
|
+
),
|
|
1026
|
+
formats: createDoctorFeatureActivations(
|
|
1027
|
+
projectRoot,
|
|
1028
|
+
"formats",
|
|
1029
|
+
summary.formats,
|
|
1030
|
+
projectActiveConfig?.formats ?? [],
|
|
1031
|
+
true
|
|
1032
|
+
),
|
|
1033
|
+
frameworks: createDoctorFeatureActivations(
|
|
1034
|
+
projectRoot,
|
|
1035
|
+
"frameworks",
|
|
1036
|
+
summary.frameworks,
|
|
1037
|
+
projectActiveConfig?.frameworks ?? [],
|
|
1038
|
+
true
|
|
1039
|
+
),
|
|
1040
|
+
ignores: projectActiveConfig?.ignores ?? [],
|
|
1041
|
+
inactivePackages: getInactiveDoctorPackages(projectRoot, summary.frameworks, summary, projectActiveConfig),
|
|
1042
|
+
libraries: createDoctorFeatureActivations(
|
|
1043
|
+
projectRoot,
|
|
1044
|
+
"libraries",
|
|
1045
|
+
summary.libraries,
|
|
1046
|
+
projectActiveConfig?.libraries ?? [],
|
|
1047
|
+
true
|
|
1048
|
+
),
|
|
1049
|
+
path: projectPath,
|
|
1050
|
+
runtime: summary.runtime,
|
|
1051
|
+
tailwind: {
|
|
1052
|
+
componentClasses: tailwindComponentClasses,
|
|
1053
|
+
detected: tailwindDetected,
|
|
1054
|
+
entryPoint: tailwindEntryPoint,
|
|
1055
|
+
unknownClassPolicy: getTailwindUnknownClassPolicy(
|
|
1056
|
+
tailwindDetected,
|
|
1057
|
+
tailwindEntryPoint,
|
|
1058
|
+
tailwindComponentClasses
|
|
1059
|
+
)
|
|
1060
|
+
},
|
|
1061
|
+
testing: createDoctorFeatureActivations(
|
|
1062
|
+
projectRoot,
|
|
1063
|
+
"testing",
|
|
1064
|
+
summary.testing,
|
|
1065
|
+
projectActiveConfig?.testing ?? [],
|
|
1066
|
+
true
|
|
1067
|
+
),
|
|
1068
|
+
tools: createDoctorFeatureActivations(
|
|
1069
|
+
projectRoot,
|
|
1070
|
+
"tools",
|
|
1071
|
+
summary.tools,
|
|
1072
|
+
projectActiveConfig?.tools ?? [],
|
|
1073
|
+
true
|
|
1074
|
+
),
|
|
1075
|
+
typescript: {
|
|
1076
|
+
detected: summary.typescript,
|
|
1077
|
+
enabled: typescriptEnabled,
|
|
1078
|
+
installed: typescriptInstalled,
|
|
1079
|
+
mode: typescriptMode,
|
|
1080
|
+
package: TYPESCRIPT_PACKAGE_NAME,
|
|
1081
|
+
...typescriptReason ? { reason: typescriptReason } : {},
|
|
1082
|
+
tsconfig
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}));
|
|
1086
|
+
};
|
|
1087
|
+
const formatDoctorActivation = (features) => features.length === 0 ? "none" : features.map((feature) => {
|
|
1088
|
+
const states = [
|
|
1089
|
+
feature.installed ? "I" : "-",
|
|
1090
|
+
feature.detected ? "D" : "-",
|
|
1091
|
+
feature.enabled ? "E" : "-"
|
|
1092
|
+
].join("");
|
|
1093
|
+
return `${feature.name}[${states}]`;
|
|
1094
|
+
}).join(",");
|
|
1095
|
+
const formatDoctorProjectTable = (projects) => [
|
|
1096
|
+
"",
|
|
1097
|
+
"Per-project activation (I=installed, D=detected, E=enabled):",
|
|
1098
|
+
"Project | Runtime | TypeScript | Tailwind entry | Frameworks | Formats | Libraries | Extensions | Testing | Tools | Ignores",
|
|
1099
|
+
"--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---",
|
|
1100
|
+
...projects.flatMap((project) => {
|
|
1101
|
+
const typescriptStates = [
|
|
1102
|
+
project.typescript.installed ? "I" : "-",
|
|
1103
|
+
project.typescript.detected ? "D" : "-",
|
|
1104
|
+
project.typescript.enabled ? "E" : "-"
|
|
1105
|
+
].join("");
|
|
1106
|
+
const row = [
|
|
1107
|
+
project.path,
|
|
1108
|
+
project.runtime,
|
|
1109
|
+
`${project.typescript.mode}:${project.typescript.tsconfig ?? "none"}[${typescriptStates}]`,
|
|
1110
|
+
project.tailwind.componentClasses > 0 ? `${project.tailwind.entryPoint ?? "none"} (${project.tailwind.componentClasses} CSS classes)` : project.tailwind.entryPoint ?? "none",
|
|
1111
|
+
formatDoctorActivation(project.frameworks),
|
|
1112
|
+
formatDoctorActivation(project.formats),
|
|
1113
|
+
formatDoctorActivation(project.libraries),
|
|
1114
|
+
formatDoctorActivation(project.extensions),
|
|
1115
|
+
formatDoctorActivation(project.testing),
|
|
1116
|
+
formatDoctorActivation(project.tools),
|
|
1117
|
+
formatList(project.ignores)
|
|
1118
|
+
].join(" | ");
|
|
1119
|
+
const explanations = [
|
|
1120
|
+
project.typescript.reason,
|
|
1121
|
+
...project.frameworks.map((feature) => feature.reason),
|
|
1122
|
+
...project.formats.map((feature) => feature.reason),
|
|
1123
|
+
...project.libraries.map((feature) => feature.reason),
|
|
1124
|
+
...project.extensions.map((feature) => feature.reason),
|
|
1125
|
+
...project.testing.map((feature) => feature.reason),
|
|
1126
|
+
...project.tools.map((feature) => feature.reason),
|
|
1127
|
+
...project.inactivePackages.map((item) => `${item.package}: ${item.reason}`)
|
|
1128
|
+
].filter((reason) => Boolean(reason));
|
|
1129
|
+
return [
|
|
1130
|
+
row,
|
|
1131
|
+
...explanations.map((reason) => ` ${project.path}: ${reason}`)
|
|
1132
|
+
];
|
|
1133
|
+
})
|
|
1134
|
+
];
|
|
1135
|
+
const outputDoctorResult = (json, verbose, configPath, packageManager, summary, installCommand, requiredPackages, issues, warnings, projects, fixes = []) => {
|
|
875
1136
|
let status = "passed";
|
|
876
1137
|
if (issues.length > 0) {
|
|
877
1138
|
status = "failed";
|
|
@@ -884,6 +1145,7 @@ const outputDoctorResult = (json, configPath, packageManager, summary, installCo
|
|
|
884
1145
|
...installCommand ? { installCommand } : {},
|
|
885
1146
|
issues,
|
|
886
1147
|
packageManager,
|
|
1148
|
+
projects,
|
|
887
1149
|
requiredPackages,
|
|
888
1150
|
status,
|
|
889
1151
|
warnings,
|
|
@@ -901,6 +1163,7 @@ const outputDoctorResult = (json, configPath, packageManager, summary, installCo
|
|
|
901
1163
|
`- Workspace projects: ${formatList(summary.workspaceProjects)}`,
|
|
902
1164
|
`- Required packages: ${formatList(requiredPackages)}`,
|
|
903
1165
|
...installCommand ? [`- Install command: ${installCommand}`] : [],
|
|
1166
|
+
...verbose ? formatDoctorProjectTable(projects) : [],
|
|
904
1167
|
...fixes.length > 0 ? ["", "Fixes applied:", ...fixes.map((fix) => `- ${fix}`)] : [],
|
|
905
1168
|
...issues.length > 0 ? ["", "Issues:", ...issues.map((issue) => `- ${issue}`)] : [],
|
|
906
1169
|
...warnings.length > 0 ? ["", "Warnings:", ...warnings.map((warning) => `- ${warning}`)] : []
|
|
@@ -945,6 +1208,15 @@ const applyDoctorFixes = (cwd, packageJson, configPath, configContent, summary)
|
|
|
945
1208
|
fixes.push(`Declared ${packageName}.`);
|
|
946
1209
|
packageChanged = true;
|
|
947
1210
|
}
|
|
1211
|
+
const nodeEngine = assessConsumerNodeEngine(cwd, packageJson);
|
|
1212
|
+
if (!nodeEngine.compatible && nodeEngine.targetRange) {
|
|
1213
|
+
updated.engines ??= {};
|
|
1214
|
+
updated.engines.node = nodeEngine.targetRange;
|
|
1215
|
+
packageChanged = true;
|
|
1216
|
+
fixes.push(
|
|
1217
|
+
nodeEngine.consumerRange ? `Changed engines.node from ${nodeEngine.consumerRange} to ${nodeEngine.targetRange}.` : `Declared engines.node as ${nodeEngine.targetRange}.`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
948
1220
|
if (packageChanged) {
|
|
949
1221
|
const packagePath = join(cwd, "package.json");
|
|
950
1222
|
const backupPath = createDoctorBackupPath(packagePath);
|
|
@@ -971,7 +1243,7 @@ const applyDoctorFixes = (cwd, packageJson, configPath, configContent, summary)
|
|
|
971
1243
|
return fixes;
|
|
972
1244
|
};
|
|
973
1245
|
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) => {
|
|
1246
|
+
const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = false, fix = false, verbose = false) => {
|
|
975
1247
|
const pnpmWorkspaceRoot = findPnpmWorkspaceRoot(cwd);
|
|
976
1248
|
const projectRoot = pnpmWorkspaceRoot ?? cwd;
|
|
977
1249
|
const packageManager = detectPackageManager(projectRoot);
|
|
@@ -990,13 +1262,17 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
990
1262
|
packageManager,
|
|
991
1263
|
addCompatibleConfigVersions(
|
|
992
1264
|
liteInstallPackages,
|
|
993
|
-
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
1265
|
+
getCompatibleConfigVersion(packageJson, getCliVersion(), pnpmWorkspaceRoot)
|
|
994
1266
|
),
|
|
995
1267
|
workspaceRoot,
|
|
996
1268
|
catalog
|
|
997
1269
|
);
|
|
998
1270
|
if (json) {
|
|
999
|
-
console.log(JSON.stringify({
|
|
1271
|
+
console.log(JSON.stringify({
|
|
1272
|
+
command: liteInstallCommand,
|
|
1273
|
+
packageManager,
|
|
1274
|
+
packages: liteInstallPackages
|
|
1275
|
+
}, null, 2));
|
|
1000
1276
|
} else {
|
|
1001
1277
|
console.log(liteInstallCommand);
|
|
1002
1278
|
}
|
|
@@ -1018,13 +1294,14 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
1018
1294
|
packageManager,
|
|
1019
1295
|
addCompatibleConfigVersions(
|
|
1020
1296
|
requiredPackages,
|
|
1021
|
-
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
1297
|
+
getCompatibleConfigVersion(packageJson, getCliVersion(), pnpmWorkspaceRoot)
|
|
1022
1298
|
),
|
|
1023
1299
|
workspaceRoot,
|
|
1024
1300
|
catalog
|
|
1025
1301
|
) : void 0;
|
|
1026
1302
|
const { issues, warnings } = buildDoctorDiagnosis(
|
|
1027
1303
|
projectRoot,
|
|
1304
|
+
packageJson,
|
|
1028
1305
|
configPath,
|
|
1029
1306
|
configContent,
|
|
1030
1307
|
hasV1FrameworkImports,
|
|
@@ -1032,8 +1309,10 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
1032
1309
|
declaredDependencies,
|
|
1033
1310
|
summary
|
|
1034
1311
|
);
|
|
1312
|
+
const projects = await getDoctorProjectActivations(projectRoot);
|
|
1035
1313
|
outputDoctorResult(
|
|
1036
1314
|
json,
|
|
1315
|
+
verbose,
|
|
1037
1316
|
configPath,
|
|
1038
1317
|
packageManager,
|
|
1039
1318
|
summary,
|
|
@@ -1041,6 +1320,7 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
1041
1320
|
requiredPackages,
|
|
1042
1321
|
issues,
|
|
1043
1322
|
warnings,
|
|
1323
|
+
projects,
|
|
1044
1324
|
fixes
|
|
1045
1325
|
);
|
|
1046
1326
|
};
|
|
@@ -1066,7 +1346,7 @@ const handleInstall = (cwd = process.cwd(), dryRun = false, runner = spawnSync)
|
|
|
1066
1346
|
const catalog = workspaceRoot ? getCatalogPreference(packageJson) : false;
|
|
1067
1347
|
const installPackages = addCompatibleConfigVersions(
|
|
1068
1348
|
packages,
|
|
1069
|
-
getCompatibleConfigVersion(packageJson, pnpmWorkspaceRoot)
|
|
1349
|
+
getCompatibleConfigVersion(packageJson, getCliVersion(), pnpmWorkspaceRoot)
|
|
1070
1350
|
);
|
|
1071
1351
|
if (packages.length === 0) {
|
|
1072
1352
|
console.log("\u2705 All packages required by the detected ESLint configuration are already declared.");
|
|
@@ -1226,7 +1506,7 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
1226
1506
|
break;
|
|
1227
1507
|
}
|
|
1228
1508
|
case "doctor": {
|
|
1229
|
-
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall, flags.hasFix).catch((error) => {
|
|
1509
|
+
handleDoctor(cwd, flags.hasJson, flags.hasLiteInstall, flags.hasFix, flags.hasVerbose).catch((error) => {
|
|
1230
1510
|
console.error(`\u274C Failed to run doctor: ${String(error)}`);
|
|
1231
1511
|
process.exitCode = 1;
|
|
1232
1512
|
});
|
|
@@ -1254,8 +1534,10 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
1254
1534
|
break;
|
|
1255
1535
|
}
|
|
1256
1536
|
handleExplainPreset(cwd, flags.positional, {
|
|
1537
|
+
analyzeSource: flags.hasAnalyzeSource,
|
|
1257
1538
|
compatibility: flags.hasCompatibility,
|
|
1258
1539
|
file: flags.files[0],
|
|
1540
|
+
files: flags.files,
|
|
1259
1541
|
json: flags.hasJson,
|
|
1260
1542
|
output: flags.output
|
|
1261
1543
|
}).catch((error) => {
|
|
@@ -1385,6 +1667,7 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
1385
1667
|
dispatchCommand(command, cwd, {
|
|
1386
1668
|
concurrency: getFlagValue(argv, "--concurrency"),
|
|
1387
1669
|
files: getFlagValues(argv, "--file"),
|
|
1670
|
+
hasAnalyzeSource: argv.includes("--analyze-source"),
|
|
1388
1671
|
hasCheck: argv.includes("--check"),
|
|
1389
1672
|
hasCompatibility: argv.includes("--compatibility"),
|
|
1390
1673
|
hasCreate: argv.includes("--create"),
|
|
@@ -1396,6 +1679,7 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
1396
1679
|
hasJson: argv.includes("--json"),
|
|
1397
1680
|
hasLiteInstall: argv.includes("--lite-install"),
|
|
1398
1681
|
hasPrune: argv.includes("--prune"),
|
|
1682
|
+
hasVerbose: argv.includes("--verbose"),
|
|
1399
1683
|
hasWithEslintMcp: argv.includes("--with-eslint-mcp"),
|
|
1400
1684
|
hasWrite: argv.includes("--write"),
|
|
1401
1685
|
maxDurationMs: getNumericFlagValue(argv, "--max-duration"),
|
|
@@ -1424,3 +1708,4 @@ export {
|
|
|
1424
1708
|
handleUpdate,
|
|
1425
1709
|
runCli
|
|
1426
1710
|
};
|
|
1711
|
+
//# sourceMappingURL=cli.js.map
|