@santi020k/eslint-config-basic 3.1.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 +26 -0
- package/IMPROVEMENTS.md +270 -0
- package/dist/cli-migration.js +367 -15
- package/dist/cli-preset.d.ts +31 -0
- package/dist/cli-preset.js +315 -0
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +359 -49
- package/dist/define-config-metadata.d.ts +10 -0
- package/dist/define-config-metadata.js +18 -0
- package/dist/frameworks.js +4 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +63 -6
- package/dist/integrations.js +3 -1
- package/dist/optional-package-errors.d.ts +3 -0
- package/dist/optional-package-errors.js +14 -0
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./agent-skill-generator.js";
|
|
12
12
|
import { handleCompatibility, handleExplainRule } from "./cli-advanced.js";
|
|
13
13
|
import { getExplicitConfigFeaturePackages, handleMigrateV3 } from "./cli-migration.js";
|
|
14
|
+
import { handleExplainPreset } from "./cli-preset.js";
|
|
14
15
|
import {
|
|
15
16
|
handleBaseline,
|
|
16
17
|
handleProfile,
|
|
@@ -157,7 +158,7 @@ const getInstallPackages = (summary, declaredDependencies, explicitFeaturePackag
|
|
|
157
158
|
if (summary.typescript) packages.add(TYPESCRIPT_PACKAGE_NAME);
|
|
158
159
|
return [...packages].filter((packageName) => !declaredDependencies.has(packageName));
|
|
159
160
|
};
|
|
160
|
-
const createInstallCommand = (packageManager, packages, workspaceRoot = false) => {
|
|
161
|
+
const createInstallCommand = (packageManager, packages, workspaceRoot = false, catalog = false) => {
|
|
161
162
|
const packageList = packages.join(" ");
|
|
162
163
|
switch (packageManager) {
|
|
163
164
|
case "bun":
|
|
@@ -167,10 +168,10 @@ const createInstallCommand = (packageManager, packages, workspaceRoot = false) =
|
|
|
167
168
|
case "yarn":
|
|
168
169
|
return `yarn add -D ${packageList}`;
|
|
169
170
|
default:
|
|
170
|
-
return `pnpm add -D${workspaceRoot ? " --workspace-root" : ""} ${packageList}`;
|
|
171
|
+
return `pnpm add -D${workspaceRoot ? " --workspace-root" : ""}` + (catalog === true ? " --save-catalog" : "") + `${typeof catalog === "string" ? ` --save-catalog-name=${catalog}` : ""} ${packageList}`;
|
|
171
172
|
}
|
|
172
173
|
};
|
|
173
|
-
const createInstallInvocation = (packageManager, packages, workspaceRoot = false) => {
|
|
174
|
+
const createInstallInvocation = (packageManager, packages, workspaceRoot = false, catalog = false) => {
|
|
174
175
|
switch (packageManager) {
|
|
175
176
|
case "bun":
|
|
176
177
|
return ["bun", ["add", "-d", ...packages]];
|
|
@@ -179,15 +180,134 @@ const createInstallInvocation = (packageManager, packages, workspaceRoot = false
|
|
|
179
180
|
case "yarn":
|
|
180
181
|
return ["yarn", ["add", "-D", ...packages]];
|
|
181
182
|
default:
|
|
182
|
-
return ["pnpm", [
|
|
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;
|
|
183
200
|
}
|
|
184
201
|
};
|
|
185
202
|
const detectPackageManager = (cwd) => {
|
|
186
|
-
if (
|
|
203
|
+
if (findPnpmWorkspaceRoot(cwd) || existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
187
204
|
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
188
205
|
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
189
206
|
return "npm";
|
|
190
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
|
+
};
|
|
191
311
|
const hasLintScript = (cwd) => {
|
|
192
312
|
const packageJson = readPackageJson(cwd);
|
|
193
313
|
return Boolean(packageJson?.scripts?.lint);
|
|
@@ -237,13 +357,13 @@ const detectWorkspaceProjects = (cwd) => {
|
|
|
237
357
|
const createExplicitOptions = (cwd) => {
|
|
238
358
|
const summary = getProjectSummary(cwd);
|
|
239
359
|
const options = [];
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
];
|
|
360
|
+
const features = [.../* @__PURE__ */ new Set([
|
|
361
|
+
...summary.extensions,
|
|
362
|
+
...summary.formats,
|
|
363
|
+
...summary.libraries,
|
|
364
|
+
...summary.testing,
|
|
365
|
+
...summary.tools
|
|
366
|
+
])].sort();
|
|
247
367
|
if (summary.typescript) options.push(" typescript: true,");
|
|
248
368
|
if (summary.frameworks.length > 0) {
|
|
249
369
|
options.push(
|
|
@@ -252,10 +372,12 @@ const createExplicitOptions = (cwd) => {
|
|
|
252
372
|
" },"
|
|
253
373
|
);
|
|
254
374
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
375
|
+
if (features.length > 0) {
|
|
376
|
+
options.push(
|
|
377
|
+
" features: {",
|
|
378
|
+
...features.map((feature) => ` ${toPropertyKey(feature)}: true,`),
|
|
379
|
+
" },"
|
|
380
|
+
);
|
|
259
381
|
}
|
|
260
382
|
return options;
|
|
261
383
|
};
|
|
@@ -297,7 +419,11 @@ const getProjectSummary = (cwd) => {
|
|
|
297
419
|
};
|
|
298
420
|
const getInstallProjectSummary = (cwd) => {
|
|
299
421
|
const rootSummary = getProjectSummary(cwd);
|
|
300
|
-
const
|
|
422
|
+
const projectPaths = [.../* @__PURE__ */ new Set([
|
|
423
|
+
...rootSummary.detectedProjects,
|
|
424
|
+
...rootSummary.workspaceProjects
|
|
425
|
+
])];
|
|
426
|
+
const projectSummaries = projectPaths.map((projectPath) => getProjectSummary(join(cwd, projectPath)));
|
|
301
427
|
const summaries = [rootSummary, ...projectSummaries];
|
|
302
428
|
return {
|
|
303
429
|
...rootSummary,
|
|
@@ -368,6 +494,7 @@ const printUsage = () => {
|
|
|
368
494
|
" init Create eslint.config.js/mjs if missing",
|
|
369
495
|
" update Regenerate eslint.config.js/mjs from detection",
|
|
370
496
|
" explain Print detected v3 config inputs",
|
|
497
|
+
" explain-preset Compare the current config with a preset",
|
|
371
498
|
" inspect Print detected inputs and active config features",
|
|
372
499
|
" install Install missing packages for detected v3 features",
|
|
373
500
|
" doctor Check project setup for common v3 adoption issues",
|
|
@@ -387,7 +514,7 @@ const printUsage = () => {
|
|
|
387
514
|
" --create generate-skill: scaffold a root AGENTS.md when missing",
|
|
388
515
|
" --dry-run install: print the detected install command without running it",
|
|
389
516
|
" --explicit init: write detected settings explicitly",
|
|
390
|
-
" --file profile/snapshot/diff: representative file or lint target (repeatable)",
|
|
517
|
+
" --file explain/profile/snapshot/diff: representative file or lint target (repeatable)",
|
|
391
518
|
" --fix doctor: safely repair generated config and package metadata",
|
|
392
519
|
" --full migrate --to v3: choose the batteries-included package",
|
|
393
520
|
" --json Print JSON for commands that support it",
|
|
@@ -395,6 +522,8 @@ const printUsage = () => {
|
|
|
395
522
|
" --max-duration profile: maximum duration in milliseconds",
|
|
396
523
|
" --max-rule-time profile: maximum time for the slowest rule in milliseconds",
|
|
397
524
|
" --max-warnings profile: maximum warning count",
|
|
525
|
+
" --compatibility explain-preset: write a temporary compatibility override",
|
|
526
|
+
" --output explain-preset: compatibility override path",
|
|
398
527
|
" --preset baseline: enable ci or pedantic strict mode before suppressing",
|
|
399
528
|
" --prune baseline: remove suppressions for resolved violations",
|
|
400
529
|
" --snapshot-path snapshot/diff: override .eslint-config-snapshot.json",
|
|
@@ -405,6 +534,94 @@ const printUsage = () => {
|
|
|
405
534
|
" --version, -v Show CLI version"
|
|
406
535
|
].join("\n"));
|
|
407
536
|
};
|
|
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
|
+
};
|
|
408
625
|
const handleInit = (cwd = process.cwd(), check = false, explicit = false) => {
|
|
409
626
|
const configPath = resolveConfigPath(cwd);
|
|
410
627
|
if (check) {
|
|
@@ -585,6 +802,16 @@ const validateConfigContent = (configContent, hasV1FrameworkImports, declaredDep
|
|
|
585
802
|
);
|
|
586
803
|
}
|
|
587
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
|
+
}
|
|
588
815
|
return warnings;
|
|
589
816
|
};
|
|
590
817
|
const validateProjectSetup = (cwd, configPath, activeConfig, hasV1FrameworkImports) => {
|
|
@@ -644,7 +871,7 @@ const buildDoctorDiagnosis = (cwd, configPath, configContent, hasV1FrameworkImpo
|
|
|
644
871
|
checkDuplicateEslint(cwd, warnings);
|
|
645
872
|
return { issues, warnings };
|
|
646
873
|
};
|
|
647
|
-
const outputDoctorResult = (json, configPath, packageManager, summary,
|
|
874
|
+
const outputDoctorResult = (json, configPath, packageManager, summary, installCommand, requiredPackages, issues, warnings, fixes = []) => {
|
|
648
875
|
let status = "passed";
|
|
649
876
|
if (issues.length > 0) {
|
|
650
877
|
status = "failed";
|
|
@@ -654,9 +881,10 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
654
881
|
const payload = {
|
|
655
882
|
configFile: configPath ? basename(configPath) : null,
|
|
656
883
|
fixes,
|
|
884
|
+
...installCommand ? { installCommand } : {},
|
|
657
885
|
issues,
|
|
658
|
-
liteInstallCommand,
|
|
659
886
|
packageManager,
|
|
887
|
+
requiredPackages,
|
|
660
888
|
status,
|
|
661
889
|
warnings,
|
|
662
890
|
workspaceProjects: summary.workspaceProjects
|
|
@@ -671,6 +899,8 @@ const outputDoctorResult = (json, configPath, packageManager, summary, liteInsta
|
|
|
671
899
|
`- Package manager: ${payload.packageManager}`,
|
|
672
900
|
`- Config file: ${payload.configFile ?? "none"}`,
|
|
673
901
|
`- Workspace projects: ${formatList(summary.workspaceProjects)}`,
|
|
902
|
+
`- Required packages: ${formatList(requiredPackages)}`,
|
|
903
|
+
...installCommand ? [`- Install command: ${installCommand}`] : [],
|
|
674
904
|
...fixes.length > 0 ? ["", "Fixes applied:", ...fixes.map((fix) => `- ${fix}`)] : [],
|
|
675
905
|
...issues.length > 0 ? ["", "Issues:", ...issues.map((issue) => `- ${issue}`)] : [],
|
|
676
906
|
...warnings.length > 0 ? ["", "Warnings:", ...warnings.map((warning) => `- ${warning}`)] : []
|
|
@@ -742,18 +972,29 @@ const applyDoctorFixes = (cwd, packageJson, configPath, configContent, summary)
|
|
|
742
972
|
};
|
|
743
973
|
const hasV1FrameworkPackageImports = (configContent) => configContent !== null && Object.keys(FRAMEWORK_PACKAGE_TO_KEY).some((packageName) => configContent.includes(packageName));
|
|
744
974
|
const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = false, fix = false) => {
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
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);
|
|
749
982
|
let declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
750
|
-
let summary = getInstallProjectSummary(
|
|
751
|
-
let activeConfig = await analyzeEslintConfig(
|
|
983
|
+
let summary = getInstallProjectSummary(projectRoot);
|
|
984
|
+
let activeConfig = await analyzeEslintConfig(projectRoot);
|
|
752
985
|
let configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
753
|
-
let liteInstallPackages = getLiteInstallPackages(summary, declaredDependencies);
|
|
754
|
-
let liteInstallCommand = createInstallCommand(packageManager, liteInstallPackages, workspaceRoot);
|
|
755
986
|
let hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
756
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
|
+
);
|
|
757
998
|
if (json) {
|
|
758
999
|
console.log(JSON.stringify({ command: liteInstallCommand, packageManager, packages: liteInstallPackages }, null, 2));
|
|
759
1000
|
} else {
|
|
@@ -761,20 +1002,29 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
761
1002
|
}
|
|
762
1003
|
return;
|
|
763
1004
|
}
|
|
764
|
-
const fixes = fix ? applyDoctorFixes(
|
|
1005
|
+
const fixes = fix ? applyDoctorFixes(projectRoot, packageJson, configPath, configContent, summary) : [];
|
|
765
1006
|
if (fixes.length > 0) {
|
|
766
|
-
configPath = getConfigPathIfPresent(
|
|
767
|
-
packageJson = readPackageJson(
|
|
1007
|
+
configPath = getConfigPathIfPresent(projectRoot);
|
|
1008
|
+
packageJson = readPackageJson(projectRoot);
|
|
768
1009
|
declaredDependencies = getDeclaredDependencyNames(packageJson);
|
|
769
|
-
summary = getInstallProjectSummary(
|
|
770
|
-
activeConfig = await analyzeEslintConfig(
|
|
1010
|
+
summary = getInstallProjectSummary(projectRoot);
|
|
1011
|
+
activeConfig = await analyzeEslintConfig(projectRoot);
|
|
771
1012
|
configContent = configPath ? readFileSync(configPath, "utf8") : null;
|
|
772
|
-
liteInstallPackages = getLiteInstallPackages(summary, declaredDependencies);
|
|
773
|
-
liteInstallCommand = createInstallCommand(packageManager, liteInstallPackages, workspaceRoot);
|
|
774
1013
|
hasV1FrameworkImports = hasV1FrameworkPackageImports(configContent);
|
|
775
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;
|
|
776
1026
|
const { issues, warnings } = buildDoctorDiagnosis(
|
|
777
|
-
|
|
1027
|
+
projectRoot,
|
|
778
1028
|
configPath,
|
|
779
1029
|
configContent,
|
|
780
1030
|
hasV1FrameworkImports,
|
|
@@ -782,41 +1032,74 @@ const handleDoctor = async (cwd = process.cwd(), json = false, liteInstall = fal
|
|
|
782
1032
|
declaredDependencies,
|
|
783
1033
|
summary
|
|
784
1034
|
);
|
|
785
|
-
outputDoctorResult(
|
|
1035
|
+
outputDoctorResult(
|
|
1036
|
+
json,
|
|
1037
|
+
configPath,
|
|
1038
|
+
packageManager,
|
|
1039
|
+
summary,
|
|
1040
|
+
installCommand,
|
|
1041
|
+
requiredPackages,
|
|
1042
|
+
issues,
|
|
1043
|
+
warnings,
|
|
1044
|
+
fixes
|
|
1045
|
+
);
|
|
786
1046
|
};
|
|
787
|
-
const
|
|
788
|
-
|
|
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);
|
|
789
1052
|
if (!packageJson) {
|
|
790
1053
|
console.error("\u274C package.json is missing or invalid.");
|
|
791
1054
|
process.exitCode = 1;
|
|
792
1055
|
return;
|
|
793
1056
|
}
|
|
794
|
-
const packageManager = detectPackageManager(
|
|
795
|
-
const configPath = getConfigPathIfPresent(
|
|
1057
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
1058
|
+
const configPath = getConfigPathIfPresent(projectRoot);
|
|
796
1059
|
const explicitFeaturePackages = configPath ? getExplicitConfigFeaturePackages(readFileSync(configPath, "utf8")) : [];
|
|
797
1060
|
const packages = getInstallPackages(
|
|
798
|
-
getInstallProjectSummary(
|
|
1061
|
+
getInstallProjectSummary(projectRoot),
|
|
799
1062
|
getDeclaredDependencyNames(packageJson),
|
|
800
1063
|
explicitFeaturePackages
|
|
801
1064
|
);
|
|
802
|
-
const workspaceRoot = packageManager === "pnpm" &&
|
|
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
|
+
);
|
|
803
1071
|
if (packages.length === 0) {
|
|
804
1072
|
console.log("\u2705 All packages required by the detected ESLint configuration are already declared.");
|
|
805
1073
|
return;
|
|
806
1074
|
}
|
|
807
|
-
const installCommand = createInstallCommand(packageManager,
|
|
1075
|
+
const installCommand = createInstallCommand(packageManager, installPackages, workspaceRoot, catalog);
|
|
808
1076
|
if (dryRun) {
|
|
809
1077
|
console.log(installCommand);
|
|
810
1078
|
return;
|
|
811
1079
|
}
|
|
812
1080
|
console.log(`Installing detected ESLint dependencies:
|
|
813
1081
|
${installCommand}`);
|
|
814
|
-
const [command, args] = createInstallInvocation(packageManager,
|
|
815
|
-
const result =
|
|
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);
|
|
816
1093
|
if (result.error) {
|
|
817
1094
|
console.error(`\u274C Failed to run ${packageManager}: ${result.error.message}`);
|
|
818
1095
|
process.exitCode = 1;
|
|
819
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
|
+
}
|
|
820
1103
|
process.exitCode = result.status ?? 1;
|
|
821
1104
|
}
|
|
822
1105
|
};
|
|
@@ -964,6 +1247,23 @@ const dispatchCommand = (command, cwd, flags) => {
|
|
|
964
1247
|
}
|
|
965
1248
|
break;
|
|
966
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
|
+
});
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
967
1267
|
case "generate-skill": {
|
|
968
1268
|
handleGenerateSkill(cwd, flags.hasForce, {
|
|
969
1269
|
check: flags.hasCheck,
|
|
@@ -1065,9 +1365,9 @@ const getNumericFlagValue = (argv, flag) => {
|
|
|
1065
1365
|
};
|
|
1066
1366
|
const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
1067
1367
|
const command = argv[2];
|
|
1068
|
-
const isHelp =
|
|
1368
|
+
const isHelp = argv.slice(3).some((argument) => argument === "--help" || argument === "-h");
|
|
1069
1369
|
const isVersion = command === "--version" || command === "-v";
|
|
1070
|
-
if (!command ||
|
|
1370
|
+
if (!command || command === "--help" || command === "-h") {
|
|
1071
1371
|
printUsage();
|
|
1072
1372
|
return;
|
|
1073
1373
|
}
|
|
@@ -1075,10 +1375,18 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
1075
1375
|
console.log(getCliVersion());
|
|
1076
1376
|
return;
|
|
1077
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;
|
|
1078
1385
|
dispatchCommand(command, cwd, {
|
|
1079
1386
|
concurrency: getFlagValue(argv, "--concurrency"),
|
|
1080
1387
|
files: getFlagValues(argv, "--file"),
|
|
1081
1388
|
hasCheck: argv.includes("--check"),
|
|
1389
|
+
hasCompatibility: argv.includes("--compatibility"),
|
|
1082
1390
|
hasCreate: argv.includes("--create"),
|
|
1083
1391
|
hasDryRun: argv.includes("--dry-run"),
|
|
1084
1392
|
hasExplicit: argv.includes("--explicit"),
|
|
@@ -1093,8 +1401,10 @@ const runCli = (argv = process.argv, cwd = process.cwd()) => {
|
|
|
1093
1401
|
maxDurationMs: getNumericFlagValue(argv, "--max-duration"),
|
|
1094
1402
|
maxRuleTimeMs: getNumericFlagValue(argv, "--max-rule-time"),
|
|
1095
1403
|
maxWarnings: getNumericFlagValue(argv, "--max-warnings"),
|
|
1404
|
+
output: getFlagValue(argv, "--output"),
|
|
1405
|
+
positional: validation.positional,
|
|
1096
1406
|
preset: getFlagValue(argv, "--preset"),
|
|
1097
|
-
rule:
|
|
1407
|
+
rule: command === "explain" ? validation.positional : void 0,
|
|
1098
1408
|
snapshotPath: getFlagValue(argv, "--snapshot-path"),
|
|
1099
1409
|
target: getFlagValue(argv, "--to")
|
|
1100
1410
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConfigInput, EslintConfigOptions, FlatConfigArray } from '@santi020k/eslint-config-core';
|
|
2
|
+
|
|
3
|
+
interface DefineConfigMetadata {
|
|
4
|
+
extraConfigs: ConfigInput[];
|
|
5
|
+
options?: EslintConfigOptions;
|
|
6
|
+
}
|
|
7
|
+
declare const attachDefineConfigMetadata: (config: FlatConfigArray, metadata: DefineConfigMetadata) => FlatConfigArray;
|
|
8
|
+
declare const getDefineConfigMetadata: (config: unknown) => DefineConfigMetadata | undefined;
|
|
9
|
+
|
|
10
|
+
export { type DefineConfigMetadata, attachDefineConfigMetadata, getDefineConfigMetadata };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const DEFINE_CONFIG_METADATA = /* @__PURE__ */ Symbol.for("@santi020k/eslint-config-basic/define-config-metadata");
|
|
2
|
+
const attachDefineConfigMetadata = (config, metadata) => {
|
|
3
|
+
Object.defineProperty(config, DEFINE_CONFIG_METADATA, {
|
|
4
|
+
configurable: false,
|
|
5
|
+
enumerable: false,
|
|
6
|
+
value: metadata,
|
|
7
|
+
writable: false
|
|
8
|
+
});
|
|
9
|
+
return config;
|
|
10
|
+
};
|
|
11
|
+
const getDefineConfigMetadata = (config) => (
|
|
12
|
+
// eslint-disable-next-line security/detect-object-injection -- shared symbol is an internal fixed metadata key
|
|
13
|
+
Array.isArray(config) ? config[DEFINE_CONFIG_METADATA] : void 0
|
|
14
|
+
);
|
|
15
|
+
export {
|
|
16
|
+
attachDefineConfigMetadata,
|
|
17
|
+
getDefineConfigMetadata
|
|
18
|
+
};
|
package/dist/frameworks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createModuleLoader
|
|
3
3
|
} from "@santi020k/eslint-config-core";
|
|
4
|
+
import { isMissingRequestedPackage } from "./optional-package-errors.js";
|
|
4
5
|
const loadModule = createModuleLoader((specifier) => import.meta.resolve(specifier, import.meta.url));
|
|
5
6
|
const createDetectedFrameworkFlags = (detectedFrameworks = []) => Object.fromEntries(
|
|
6
7
|
detectedFrameworks.map((framework) => [framework, true])
|
|
@@ -33,8 +34,10 @@ const loadFrameworkConfigInput = (frameworkName) => {
|
|
|
33
34
|
if (!loader) return Promise.resolve([]);
|
|
34
35
|
const pending = loader().catch((error) => {
|
|
35
36
|
frameworkConfigCache.delete(frameworkName);
|
|
37
|
+
const specifier = `@santi020k/eslint-config-${frameworkName}`;
|
|
38
|
+
const remediation = isMissingRequestedPackage(error, specifier) ? `Install "${specifier}" or remove that framework from your defineConfig options.` : `The installed package "${specifier}" failed while evaluating. Inspect this error's cause for the original dependency or runtime failure.`;
|
|
36
39
|
throw new Error(
|
|
37
|
-
`Unable to load optional framework config "${frameworkName}".
|
|
40
|
+
`Unable to load optional framework config "${frameworkName}". ` + remediation,
|
|
38
41
|
{ cause: error }
|
|
39
42
|
);
|
|
40
43
|
});
|