@eslint-config-snapshot/cli 1.0.0 → 1.1.1
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 +19 -0
- package/dist/index.cjs +186 -32
- package/dist/index.js +189 -35
- package/package.json +2 -2
- package/src/commands/check.ts +51 -7
- package/src/commands/print.ts +7 -2
- package/src/commands/skipped-workspaces.ts +66 -0
- package/src/commands/update.ts +46 -8
- package/src/formatters.ts +7 -0
- package/src/runtime.ts +61 -6
- package/test/cli.integration.test.ts +68 -1
- package/test/cli.terminal.integration.test.ts +14 -3
package/dist/index.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
5
5
|
import createDebug2 from "debug";
|
|
6
|
-
import
|
|
6
|
+
import path5 from "path";
|
|
7
7
|
|
|
8
8
|
// src/commands/check.ts
|
|
9
|
-
import { findConfigPath } from "@eslint-config-snapshot/api";
|
|
9
|
+
import { DEFAULT_CONFIG, findConfigPath } from "@eslint-config-snapshot/api";
|
|
10
10
|
|
|
11
11
|
// src/formatters.ts
|
|
12
12
|
function formatDiff(groupId, diff) {
|
|
@@ -181,6 +181,12 @@ function formatStoredSnapshotSummary(storedSnapshots) {
|
|
|
181
181
|
const summary = summarizeSnapshots(storedSnapshots);
|
|
182
182
|
return `${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off)`;
|
|
183
183
|
}
|
|
184
|
+
function formatBaselineSummaryLines(summary, workspaceCount) {
|
|
185
|
+
return `- \u{1F4E6} baseline: ${summary.groups} groups, ${summary.rules} rules
|
|
186
|
+
- \u{1F5C2}\uFE0F workspaces scanned: ${workspaceCount}
|
|
187
|
+
- \u{1F39A}\uFE0F severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
184
190
|
function countRuleSeverities(ruleObjects) {
|
|
185
191
|
let rules = 0;
|
|
186
192
|
let error = 0;
|
|
@@ -373,19 +379,24 @@ import path2 from "path";
|
|
|
373
379
|
var debugWorkspace = createDebug("eslint-config-snapshot:workspace");
|
|
374
380
|
var debugDiff = createDebug("eslint-config-snapshot:diff");
|
|
375
381
|
var debugTiming = createDebug("eslint-config-snapshot:timing");
|
|
376
|
-
async function computeCurrentSnapshots(cwd) {
|
|
382
|
+
async function computeCurrentSnapshots(cwd, options) {
|
|
383
|
+
const allowWorkspaceExtractionFailure = options?.allowWorkspaceExtractionFailure ?? false;
|
|
384
|
+
const onWorkspacesDiscovered = options?.onWorkspacesDiscovered;
|
|
385
|
+
const onWorkspaceSkipped = options?.onWorkspaceSkipped;
|
|
377
386
|
const computeStartedAt = Date.now();
|
|
378
387
|
const configStartedAt = Date.now();
|
|
379
388
|
const config = await loadConfig(cwd);
|
|
380
389
|
debugTiming("phase=loadConfig elapsedMs=%d", Date.now() - configStartedAt);
|
|
381
390
|
const assignmentStartedAt = Date.now();
|
|
382
391
|
const { discovery, assignments } = await resolveWorkspaceAssignments(cwd, config);
|
|
392
|
+
onWorkspacesDiscovered?.(discovery.workspacesRel);
|
|
383
393
|
debugTiming("phase=resolveWorkspaceAssignments elapsedMs=%d", Date.now() - assignmentStartedAt);
|
|
384
394
|
debugWorkspace("root=%s groups=%d workspaces=%d", discovery.rootAbs, assignments.length, discovery.workspacesRel.length);
|
|
385
395
|
const snapshots = /* @__PURE__ */ new Map();
|
|
386
396
|
for (const group of assignments) {
|
|
387
397
|
const groupStartedAt = Date.now();
|
|
388
398
|
const extractedForGroup = [];
|
|
399
|
+
const extractedWorkspaces = [];
|
|
389
400
|
debugWorkspace("group=%s workspaces=%o", group.name, group.workspaces);
|
|
390
401
|
for (const workspaceRel of group.workspaces) {
|
|
391
402
|
const workspaceAbs = path2.resolve(discovery.rootAbs, workspaceRel);
|
|
@@ -418,7 +429,7 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
418
429
|
continue;
|
|
419
430
|
}
|
|
420
431
|
const message = result.error instanceof Error ? result.error.message : String(result.error);
|
|
421
|
-
if (isRecoverableExtractionError(message)) {
|
|
432
|
+
if (isRecoverableExtractionError(message) || allowWorkspaceExtractionFailure) {
|
|
422
433
|
lastExtractionError = message;
|
|
423
434
|
continue;
|
|
424
435
|
}
|
|
@@ -426,10 +437,23 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
426
437
|
}
|
|
427
438
|
if (extractedCount === 0) {
|
|
428
439
|
const context = lastExtractionError ? ` Last error: ${lastExtractionError}` : "";
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
440
|
+
if (allowWorkspaceExtractionFailure && isSkippableWorkspaceExtractionFailure(lastExtractionError)) {
|
|
441
|
+
onWorkspaceSkipped?.({
|
|
442
|
+
groupId: group.name,
|
|
443
|
+
workspaceRel,
|
|
444
|
+
reason: lastExtractionError ?? "unknown extraction failure"
|
|
445
|
+
});
|
|
446
|
+
debugWorkspace(
|
|
447
|
+
"group=%s workspace=%s skipped=true reason=%s",
|
|
448
|
+
group.name,
|
|
449
|
+
workspaceRel,
|
|
450
|
+
lastExtractionError ?? "unknown extraction failure"
|
|
451
|
+
);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
throw new Error(`Unable to extract ESLint config for workspace ${workspaceRel}.${context}`);
|
|
432
455
|
}
|
|
456
|
+
extractedWorkspaces.push(workspaceRel);
|
|
433
457
|
debugWorkspace(
|
|
434
458
|
"group=%s workspace=%s extracted=%d failed=%d",
|
|
435
459
|
group.name,
|
|
@@ -438,8 +462,15 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
438
462
|
results.length - extractedCount
|
|
439
463
|
);
|
|
440
464
|
}
|
|
465
|
+
if (extractedForGroup.length === 0) {
|
|
466
|
+
if (allowWorkspaceExtractionFailure) {
|
|
467
|
+
debugWorkspace("group=%s skipped=true reason=no-extracted-workspaces", group.name);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
throw new Error(`Unable to extract ESLint config for group ${group.name}: no workspace produced a valid config`);
|
|
471
|
+
}
|
|
441
472
|
const aggregated = aggregateRules(extractedForGroup);
|
|
442
|
-
snapshots.set(group.name, buildSnapshot(group.name,
|
|
473
|
+
snapshots.set(group.name, buildSnapshot(group.name, extractedWorkspaces, aggregated));
|
|
443
474
|
debugWorkspace(
|
|
444
475
|
"group=%s aggregatedRules=%d groupElapsedMs=%d",
|
|
445
476
|
group.name,
|
|
@@ -448,11 +479,20 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
448
479
|
);
|
|
449
480
|
}
|
|
450
481
|
debugTiming("phase=computeCurrentSnapshots elapsedMs=%d", Date.now() - computeStartedAt);
|
|
482
|
+
if (snapshots.size === 0) {
|
|
483
|
+
throw new Error("Unable to extract ESLint config from discovered workspaces in zero-config mode");
|
|
484
|
+
}
|
|
451
485
|
return snapshots;
|
|
452
486
|
}
|
|
453
487
|
function isRecoverableExtractionError(message) {
|
|
454
488
|
return message.startsWith("Invalid JSON from eslint --print-config") || message.startsWith("Empty ESLint print-config output") || message.includes("File ignored because of a matching ignore pattern") || message.includes("File ignored by default");
|
|
455
489
|
}
|
|
490
|
+
function isSkippableWorkspaceExtractionFailure(message) {
|
|
491
|
+
if (!message) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
return isRecoverableExtractionError(message) || message.startsWith("Failed to load config") || message.startsWith("Failed to run eslint --print-config") || message.startsWith("Unable to resolve eslint from workspace");
|
|
495
|
+
}
|
|
456
496
|
async function resolveWorkspaceAssignments(cwd, config) {
|
|
457
497
|
const discovery = await discoverWorkspaces({ cwd, workspaceInput: config.workspaceInput });
|
|
458
498
|
const assignments = config.grouping.mode === "standalone" ? discovery.workspacesRel.map((workspace) => ({ name: workspace, workspaces: [workspace] })) : assignGroupsByMatch(discovery.workspacesRel, config.grouping.groups ?? [{ name: "default", match: ["**/*"] }]);
|
|
@@ -522,6 +562,63 @@ async function resolveGroupEslintVersions(cwd) {
|
|
|
522
562
|
return result;
|
|
523
563
|
}
|
|
524
564
|
|
|
565
|
+
// src/commands/skipped-workspaces.ts
|
|
566
|
+
import path3 from "path";
|
|
567
|
+
function writeSkippedWorkspaceSummary(terminal, cwd, configPath, skippedWorkspaces) {
|
|
568
|
+
if (skippedWorkspaces.length === 0) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const workspacePaths = collectSkippedWorkspacePaths(skippedWorkspaces);
|
|
572
|
+
terminal.warning(
|
|
573
|
+
`Heads up: ${workspacePaths.length} workspace(s) were skipped because ESLint auto-discovery could not extract an effective config for them.
|
|
574
|
+
`
|
|
575
|
+
);
|
|
576
|
+
terminal.warning(`Skipped workspaces: ${workspacePaths.join(", ")}
|
|
577
|
+
`);
|
|
578
|
+
terminal.subtle(formatScopedConfigHint(cwd, configPath, workspacePaths));
|
|
579
|
+
}
|
|
580
|
+
function collectSkippedWorkspacePaths(skippedWorkspaces) {
|
|
581
|
+
const unique = /* @__PURE__ */ new Set();
|
|
582
|
+
for (const skipped of skippedWorkspaces) {
|
|
583
|
+
unique.add(skipped.workspaceRel);
|
|
584
|
+
}
|
|
585
|
+
return [...unique].sort();
|
|
586
|
+
}
|
|
587
|
+
function toExcludeGlobs(workspacePaths) {
|
|
588
|
+
return workspacePaths.map((workspacePath) => `${workspacePath}/**`);
|
|
589
|
+
}
|
|
590
|
+
function formatScopedConfigHint(cwd, configPath, workspacePaths) {
|
|
591
|
+
const excludeGlobs = toExcludeGlobs(workspacePaths);
|
|
592
|
+
if (configPath && path3.basename(configPath) === "package.json") {
|
|
593
|
+
return `Tip: if these workspaces are intentionally out of scope, add this under "eslint-config-snapshot" in package.json:
|
|
594
|
+
${JSON.stringify(
|
|
595
|
+
{
|
|
596
|
+
sampling: {
|
|
597
|
+
excludeGlobs
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
null,
|
|
601
|
+
2
|
|
602
|
+
)}
|
|
603
|
+
`;
|
|
604
|
+
}
|
|
605
|
+
const objectLiteral = `{
|
|
606
|
+
sampling: {
|
|
607
|
+
excludeGlobs: [
|
|
608
|
+
${excludeGlobs.map((value) => ` '${value}'`).join(",\n")}
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
`;
|
|
613
|
+
if (configPath) {
|
|
614
|
+
const relConfigPath = path3.relative(cwd, configPath) || path3.basename(configPath);
|
|
615
|
+
return `Tip: if these workspaces are intentionally out of scope, add this in ${relConfigPath}:
|
|
616
|
+
${objectLiteral}`;
|
|
617
|
+
}
|
|
618
|
+
return `Tip: if these workspaces are intentionally out of scope, run \`eslint-config-snapshot init\` and add this config:
|
|
619
|
+
${objectLiteral}`;
|
|
620
|
+
}
|
|
621
|
+
|
|
525
622
|
// src/commands/check.ts
|
|
526
623
|
var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
|
|
527
624
|
async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
|
|
@@ -539,10 +636,21 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
|
|
|
539
636
|
);
|
|
540
637
|
}
|
|
541
638
|
let currentSnapshots;
|
|
639
|
+
const skippedWorkspaces = [];
|
|
640
|
+
let discoveredWorkspaces = [];
|
|
641
|
+
const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config);
|
|
542
642
|
try {
|
|
543
|
-
currentSnapshots = await computeCurrentSnapshots(cwd
|
|
643
|
+
currentSnapshots = await computeCurrentSnapshots(cwd, {
|
|
644
|
+
allowWorkspaceExtractionFailure,
|
|
645
|
+
onWorkspacesDiscovered: (workspacesRel) => {
|
|
646
|
+
discoveredWorkspaces = workspacesRel;
|
|
647
|
+
},
|
|
648
|
+
onWorkspaceSkipped: (skipped) => {
|
|
649
|
+
skippedWorkspaces.push(skipped);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
544
652
|
} catch (error) {
|
|
545
|
-
if (!foundConfig) {
|
|
653
|
+
if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
|
|
546
654
|
terminal.write(
|
|
547
655
|
"Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
|
|
548
656
|
);
|
|
@@ -550,6 +658,10 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
|
|
|
550
658
|
}
|
|
551
659
|
throw error;
|
|
552
660
|
}
|
|
661
|
+
if (!foundConfig) {
|
|
662
|
+
writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
|
|
663
|
+
}
|
|
664
|
+
writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
|
|
553
665
|
if (storedSnapshots.size === 0) {
|
|
554
666
|
const summary = summarizeSnapshots(currentSnapshots);
|
|
555
667
|
terminal.write(
|
|
@@ -609,12 +721,7 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
|
|
|
609
721
|
if (changes.length === 0) {
|
|
610
722
|
terminal.write(color.green("\u2705 Great news: no snapshot drift detected.\n"));
|
|
611
723
|
terminal.section("\u{1F4CA} Summary");
|
|
612
|
-
terminal.write(
|
|
613
|
-
`- \u{1F4E6} baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
|
|
614
|
-
- \u{1F5C2}\uFE0F workspaces scanned: ${workspaceCount}
|
|
615
|
-
- \u{1F39A}\uFE0F severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
|
|
616
|
-
`
|
|
617
|
-
);
|
|
724
|
+
terminal.write(formatBaselineSummaryLines(currentSummary, workspaceCount));
|
|
618
725
|
writeEslintVersionSummary(terminal, eslintVersionsByGroup);
|
|
619
726
|
return 0;
|
|
620
727
|
}
|
|
@@ -649,9 +756,24 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
|
|
|
649
756
|
terminal.subtle(UPDATE_HINT);
|
|
650
757
|
return 1;
|
|
651
758
|
}
|
|
759
|
+
function isWorkspaceDiscoveryDefaultsError(error) {
|
|
760
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
761
|
+
return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
|
|
762
|
+
}
|
|
763
|
+
function isDefaultEquivalentConfig(config) {
|
|
764
|
+
return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG);
|
|
765
|
+
}
|
|
766
|
+
function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
|
|
767
|
+
if (workspacesRel.length === 0) {
|
|
768
|
+
terminal.subtle("Auto-discovered workspaces: none\n");
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
|
|
772
|
+
`);
|
|
773
|
+
}
|
|
652
774
|
|
|
653
775
|
// src/commands/print.ts
|
|
654
|
-
import { findConfigPath as findConfigPath2, loadConfig as loadConfig2 } from "@eslint-config-snapshot/api";
|
|
776
|
+
import { DEFAULT_CONFIG as DEFAULT_CONFIG2, findConfigPath as findConfigPath2, loadConfig as loadConfig2 } from "@eslint-config-snapshot/api";
|
|
655
777
|
async function executePrint(cwd, terminal, snapshotDir, format) {
|
|
656
778
|
const foundConfig = await findConfigPath2(cwd);
|
|
657
779
|
const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
|
|
@@ -659,7 +781,8 @@ async function executePrint(cwd, terminal, snapshotDir, format) {
|
|
|
659
781
|
if (terminal.showProgress) {
|
|
660
782
|
terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
|
|
661
783
|
}
|
|
662
|
-
const
|
|
784
|
+
const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig2(foundConfig.config);
|
|
785
|
+
const currentSnapshots = await computeCurrentSnapshots(cwd, { allowWorkspaceExtractionFailure });
|
|
663
786
|
if (format === "short") {
|
|
664
787
|
terminal.write(formatShortPrint([...currentSnapshots.values()]));
|
|
665
788
|
return;
|
|
@@ -698,9 +821,12 @@ async function executeConfig(cwd, terminal, snapshotDir, format) {
|
|
|
698
821
|
terminal.write(`${JSON.stringify(payload, null, 2)}
|
|
699
822
|
`);
|
|
700
823
|
}
|
|
824
|
+
function isDefaultEquivalentConfig2(config) {
|
|
825
|
+
return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG2);
|
|
826
|
+
}
|
|
701
827
|
|
|
702
828
|
// src/commands/update.ts
|
|
703
|
-
import { findConfigPath as findConfigPath3 } from "@eslint-config-snapshot/api";
|
|
829
|
+
import { DEFAULT_CONFIG as DEFAULT_CONFIG3, findConfigPath as findConfigPath3 } from "@eslint-config-snapshot/api";
|
|
704
830
|
async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
|
|
705
831
|
const foundConfig = await findConfigPath3(cwd);
|
|
706
832
|
const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
|
|
@@ -714,10 +840,21 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
|
|
|
714
840
|
);
|
|
715
841
|
}
|
|
716
842
|
let currentSnapshots;
|
|
843
|
+
const skippedWorkspaces = [];
|
|
844
|
+
let discoveredWorkspaces = [];
|
|
845
|
+
const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig3(foundConfig.config);
|
|
717
846
|
try {
|
|
718
|
-
currentSnapshots = await computeCurrentSnapshots(cwd
|
|
847
|
+
currentSnapshots = await computeCurrentSnapshots(cwd, {
|
|
848
|
+
allowWorkspaceExtractionFailure,
|
|
849
|
+
onWorkspacesDiscovered: (workspacesRel) => {
|
|
850
|
+
discoveredWorkspaces = workspacesRel;
|
|
851
|
+
},
|
|
852
|
+
onWorkspaceSkipped: (skipped) => {
|
|
853
|
+
skippedWorkspaces.push(skipped);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
719
856
|
} catch (error) {
|
|
720
|
-
if (!foundConfig) {
|
|
857
|
+
if (!foundConfig && isWorkspaceDiscoveryDefaultsError2(error)) {
|
|
721
858
|
terminal.write(
|
|
722
859
|
"Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
|
|
723
860
|
);
|
|
@@ -725,28 +862,45 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
|
|
|
725
862
|
}
|
|
726
863
|
throw error;
|
|
727
864
|
}
|
|
865
|
+
if (!foundConfig) {
|
|
866
|
+
writeDiscoveredWorkspacesSummary2(terminal, discoveredWorkspaces);
|
|
867
|
+
}
|
|
868
|
+
writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
|
|
728
869
|
await writeSnapshots(cwd, snapshotDir, currentSnapshots);
|
|
729
870
|
if (printSummary) {
|
|
730
871
|
const summary = summarizeSnapshots(currentSnapshots);
|
|
731
872
|
const workspaceCount = countUniqueWorkspaces(currentSnapshots);
|
|
732
873
|
const eslintVersionsByGroup = terminal.showProgress ? await resolveGroupEslintVersions(cwd) : /* @__PURE__ */ new Map();
|
|
874
|
+
const baselineAction = storedSnapshots.size === 0 ? "created" : "updated";
|
|
875
|
+
terminal.success(`\u2705 Great news: baseline was successfully ${baselineAction} for your project.
|
|
876
|
+
`);
|
|
733
877
|
terminal.section("\u{1F4CA} Summary");
|
|
734
|
-
terminal.write(
|
|
735
|
-
`Baseline updated: ${summary.groups} groups, ${summary.rules} rules.
|
|
736
|
-
Workspaces scanned: ${workspaceCount}.
|
|
737
|
-
Severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off.
|
|
738
|
-
`
|
|
739
|
-
);
|
|
878
|
+
terminal.write(formatBaselineSummaryLines(summary, workspaceCount));
|
|
740
879
|
writeEslintVersionSummary(terminal, eslintVersionsByGroup);
|
|
741
880
|
}
|
|
742
881
|
return 0;
|
|
743
882
|
}
|
|
883
|
+
function isWorkspaceDiscoveryDefaultsError2(error) {
|
|
884
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
885
|
+
return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
|
|
886
|
+
}
|
|
887
|
+
function isDefaultEquivalentConfig3(config) {
|
|
888
|
+
return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG3);
|
|
889
|
+
}
|
|
890
|
+
function writeDiscoveredWorkspacesSummary2(terminal, workspacesRel) {
|
|
891
|
+
if (workspacesRel.length === 0) {
|
|
892
|
+
terminal.subtle("Auto-discovered workspaces: none\n");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
|
|
896
|
+
`);
|
|
897
|
+
}
|
|
744
898
|
|
|
745
899
|
// src/init.ts
|
|
746
900
|
import { discoverWorkspaces as discoverWorkspaces2, findConfigPath as findConfigPath4, getConfigScaffold, normalizePath as normalizePath2 } from "@eslint-config-snapshot/api";
|
|
747
901
|
import fg2 from "fast-glob";
|
|
748
902
|
import { access, readFile, writeFile } from "fs/promises";
|
|
749
|
-
import
|
|
903
|
+
import path4 from "path";
|
|
750
904
|
async function runInit(cwd, opts, runtime) {
|
|
751
905
|
const force = opts.force ?? false;
|
|
752
906
|
const showEffective = opts.showEffective ?? false;
|
|
@@ -814,7 +968,7 @@ async function runInitInFile(cwd, configObject, force, runtime) {
|
|
|
814
968
|
];
|
|
815
969
|
for (const candidate of candidates) {
|
|
816
970
|
try {
|
|
817
|
-
await access(
|
|
971
|
+
await access(path4.join(cwd, candidate));
|
|
818
972
|
if (!force) {
|
|
819
973
|
runtime.writeStderr(`Config already exists: ${candidate}
|
|
820
974
|
`);
|
|
@@ -823,14 +977,14 @@ async function runInitInFile(cwd, configObject, force, runtime) {
|
|
|
823
977
|
} catch {
|
|
824
978
|
}
|
|
825
979
|
}
|
|
826
|
-
const target =
|
|
980
|
+
const target = path4.join(cwd, "eslint-config-snapshot.config.mjs");
|
|
827
981
|
await writeFile(target, toConfigScaffold(configObject), "utf8");
|
|
828
|
-
runtime.writeStdout(`Created ${
|
|
982
|
+
runtime.writeStdout(`Created ${path4.basename(target)}
|
|
829
983
|
`);
|
|
830
984
|
return 0;
|
|
831
985
|
}
|
|
832
986
|
async function runInitInPackageJson(cwd, configObject, force, runtime) {
|
|
833
|
-
const packageJsonPath =
|
|
987
|
+
const packageJsonPath = path4.join(cwd, "package.json");
|
|
834
988
|
let packageJsonRaw;
|
|
835
989
|
try {
|
|
836
990
|
packageJsonRaw = await readFile(packageJsonPath, "utf8");
|
|
@@ -891,7 +1045,7 @@ async function discoverInitWorkspaces(cwd) {
|
|
|
891
1045
|
if (!(discovered.workspacesRel.length === 1 && discovered.workspacesRel[0] === ".")) {
|
|
892
1046
|
return discovered.workspacesRel;
|
|
893
1047
|
}
|
|
894
|
-
const packageJsonPath =
|
|
1048
|
+
const packageJsonPath = path4.join(cwd, "package.json");
|
|
895
1049
|
try {
|
|
896
1050
|
const raw = await readFile(packageJsonPath, "utf8");
|
|
897
1051
|
const parsed = JSON.parse(raw);
|
|
@@ -909,7 +1063,7 @@ async function discoverInitWorkspaces(cwd) {
|
|
|
909
1063
|
onlyFiles: true,
|
|
910
1064
|
dot: true
|
|
911
1065
|
});
|
|
912
|
-
const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => normalizePath2(
|
|
1066
|
+
const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => normalizePath2(path4.dirname(entry))))].sort(
|
|
913
1067
|
(a, b) => a.localeCompare(b)
|
|
914
1068
|
);
|
|
915
1069
|
if (workspaceDirs.length > 0) {
|
|
@@ -1320,7 +1474,7 @@ function isDirectCliExecution() {
|
|
|
1320
1474
|
if (!entry) {
|
|
1321
1475
|
return false;
|
|
1322
1476
|
}
|
|
1323
|
-
const normalized =
|
|
1477
|
+
const normalized = path5.basename(entry).toLowerCase();
|
|
1324
1478
|
return normalized === "index.js" || normalized === "index.cjs" || normalized === "index.ts" || normalized === "eslint-config-snapshot";
|
|
1325
1479
|
}
|
|
1326
1480
|
if (isDirectCliExecution()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-config-snapshot/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"commander": "^14.0.3",
|
|
32
32
|
"debug": "^4.4.3",
|
|
33
33
|
"fast-glob": "^3.3.3",
|
|
34
|
-
"@eslint-config-snapshot/api": "1.
|
|
34
|
+
"@eslint-config-snapshot/api": "1.1.1"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/commands/check.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { findConfigPath } from '@eslint-config-snapshot/api'
|
|
1
|
+
import { DEFAULT_CONFIG, findConfigPath, type SnapshotConfig } from '@eslint-config-snapshot/api'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
countUniqueWorkspaces,
|
|
5
|
+
decorateDiffLine,
|
|
6
|
+
formatBaselineSummaryLines,
|
|
7
|
+
formatDiff,
|
|
8
|
+
summarizeChanges,
|
|
9
|
+
summarizeSnapshots
|
|
10
|
+
} from '../formatters.js'
|
|
4
11
|
import { writeEslintVersionSummary, writeRunContextHeader } from '../run-context.js'
|
|
5
12
|
import {
|
|
6
13
|
type BuiltSnapshot,
|
|
@@ -9,10 +16,12 @@ import {
|
|
|
9
16
|
type GroupEslintVersions,
|
|
10
17
|
loadStoredSnapshots,
|
|
11
18
|
resolveGroupEslintVersions,
|
|
19
|
+
type SkippedWorkspace,
|
|
12
20
|
type SnapshotDiff,
|
|
13
21
|
writeSnapshots
|
|
14
22
|
} from '../runtime.js'
|
|
15
23
|
import { type TerminalIO } from '../terminal.js'
|
|
24
|
+
import { writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
|
|
16
25
|
|
|
17
26
|
export type CheckFormat = 'summary' | 'status' | 'diff'
|
|
18
27
|
|
|
@@ -42,10 +51,21 @@ export async function executeCheck(
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
let currentSnapshots: Map<string, BuiltSnapshot>
|
|
54
|
+
const skippedWorkspaces: SkippedWorkspace[] = []
|
|
55
|
+
let discoveredWorkspaces: string[] = []
|
|
56
|
+
const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config)
|
|
45
57
|
try {
|
|
46
|
-
currentSnapshots = await computeCurrentSnapshots(cwd
|
|
58
|
+
currentSnapshots = await computeCurrentSnapshots(cwd, {
|
|
59
|
+
allowWorkspaceExtractionFailure,
|
|
60
|
+
onWorkspacesDiscovered: (workspacesRel) => {
|
|
61
|
+
discoveredWorkspaces = workspacesRel
|
|
62
|
+
},
|
|
63
|
+
onWorkspaceSkipped: (skipped) => {
|
|
64
|
+
skippedWorkspaces.push(skipped)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
47
67
|
} catch (error: unknown) {
|
|
48
|
-
if (!foundConfig) {
|
|
68
|
+
if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
|
|
49
69
|
terminal.write(
|
|
50
70
|
'Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n'
|
|
51
71
|
)
|
|
@@ -54,6 +74,10 @@ export async function executeCheck(
|
|
|
54
74
|
|
|
55
75
|
throw error
|
|
56
76
|
}
|
|
77
|
+
if (!foundConfig) {
|
|
78
|
+
writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces)
|
|
79
|
+
}
|
|
80
|
+
writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces)
|
|
57
81
|
if (storedSnapshots.size === 0) {
|
|
58
82
|
const summary = summarizeSnapshots(currentSnapshots)
|
|
59
83
|
terminal.write(
|
|
@@ -126,9 +150,7 @@ function printWhatChanged(
|
|
|
126
150
|
if (changes.length === 0) {
|
|
127
151
|
terminal.write(color.green('✅ Great news: no snapshot drift detected.\n'))
|
|
128
152
|
terminal.section('📊 Summary')
|
|
129
|
-
terminal.write(
|
|
130
|
-
`- 📦 baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules\n- 🗂️ workspaces scanned: ${workspaceCount}\n- 🎚️ severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off\n`
|
|
131
|
-
)
|
|
153
|
+
terminal.write(formatBaselineSummaryLines(currentSummary, workspaceCount))
|
|
132
154
|
writeEslintVersionSummary(terminal, eslintVersionsByGroup)
|
|
133
155
|
return 0
|
|
134
156
|
}
|
|
@@ -155,3 +177,25 @@ function printWhatChanged(
|
|
|
155
177
|
|
|
156
178
|
return 1
|
|
157
179
|
}
|
|
180
|
+
|
|
181
|
+
function isWorkspaceDiscoveryDefaultsError(error: unknown): boolean {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
183
|
+
return (
|
|
184
|
+
message.includes('Unable to discover workspaces') ||
|
|
185
|
+
message.includes('Unmatched workspaces') ||
|
|
186
|
+
message.includes('zero-config mode')
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isDefaultEquivalentConfig(config: SnapshotConfig): boolean {
|
|
191
|
+
return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function writeDiscoveredWorkspacesSummary(terminal: TerminalIO, workspacesRel: string[]): void {
|
|
195
|
+
if (workspacesRel.length === 0) {
|
|
196
|
+
terminal.subtle('Auto-discovered workspaces: none\n')
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(', ')}\n`)
|
|
201
|
+
}
|
package/src/commands/print.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findConfigPath, loadConfig } from '@eslint-config-snapshot/api'
|
|
1
|
+
import { DEFAULT_CONFIG, findConfigPath, loadConfig, type SnapshotConfig } from '@eslint-config-snapshot/api'
|
|
2
2
|
|
|
3
3
|
import { formatShortConfig, formatShortPrint } from '../formatters.js'
|
|
4
4
|
import { writeRunContextHeader } from '../run-context.js'
|
|
@@ -14,7 +14,8 @@ export async function executePrint(cwd: string, terminal: TerminalIO, snapshotDi
|
|
|
14
14
|
if (terminal.showProgress) {
|
|
15
15
|
terminal.subtle('🔎 Checking current ESLint configuration...\n')
|
|
16
16
|
}
|
|
17
|
-
const
|
|
17
|
+
const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config)
|
|
18
|
+
const currentSnapshots = await computeCurrentSnapshots(cwd, { allowWorkspaceExtractionFailure })
|
|
18
19
|
|
|
19
20
|
if (format === 'short') {
|
|
20
21
|
terminal.write(formatShortPrint([...currentSnapshots.values()]))
|
|
@@ -56,3 +57,7 @@ export async function executeConfig(cwd: string, terminal: TerminalIO, snapshotD
|
|
|
56
57
|
|
|
57
58
|
terminal.write(`${JSON.stringify(payload, null, 2)}\n`)
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
function isDefaultEquivalentConfig(config: SnapshotConfig): boolean {
|
|
62
|
+
return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG)
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { type SkippedWorkspace } from '../runtime.js'
|
|
4
|
+
import { type TerminalIO } from '../terminal.js'
|
|
5
|
+
|
|
6
|
+
export function writeSkippedWorkspaceSummary(
|
|
7
|
+
terminal: TerminalIO,
|
|
8
|
+
cwd: string,
|
|
9
|
+
configPath: string | undefined,
|
|
10
|
+
skippedWorkspaces: SkippedWorkspace[]
|
|
11
|
+
): void {
|
|
12
|
+
if (skippedWorkspaces.length === 0) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const workspacePaths = collectSkippedWorkspacePaths(skippedWorkspaces)
|
|
17
|
+
terminal.warning(
|
|
18
|
+
`Heads up: ${workspacePaths.length} workspace(s) were skipped because ESLint auto-discovery could not extract an effective config for them.\n`
|
|
19
|
+
)
|
|
20
|
+
terminal.warning(`Skipped workspaces: ${workspacePaths.join(', ')}\n`)
|
|
21
|
+
terminal.subtle(formatScopedConfigHint(cwd, configPath, workspacePaths))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function collectSkippedWorkspacePaths(skippedWorkspaces: SkippedWorkspace[]): string[] {
|
|
25
|
+
const unique = new Set<string>()
|
|
26
|
+
for (const skipped of skippedWorkspaces) {
|
|
27
|
+
unique.add(skipped.workspaceRel)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return [...unique].sort()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toExcludeGlobs(workspacePaths: string[]): string[] {
|
|
34
|
+
return workspacePaths.map((workspacePath) => `${workspacePath}/**`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatScopedConfigHint(cwd: string, configPath: string | undefined, workspacePaths: string[]): string {
|
|
38
|
+
const excludeGlobs = toExcludeGlobs(workspacePaths)
|
|
39
|
+
|
|
40
|
+
if (configPath && path.basename(configPath) === 'package.json') {
|
|
41
|
+
return `Tip: if these workspaces are intentionally out of scope, add this under "eslint-config-snapshot" in package.json:\n${JSON.stringify(
|
|
42
|
+
{
|
|
43
|
+
sampling: {
|
|
44
|
+
excludeGlobs
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
null,
|
|
48
|
+
2
|
|
49
|
+
)}\n`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const objectLiteral = `{
|
|
53
|
+
sampling: {
|
|
54
|
+
excludeGlobs: [
|
|
55
|
+
${excludeGlobs.map((value) => ` '${value}'`).join(',\n')}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}\n`
|
|
59
|
+
|
|
60
|
+
if (configPath) {
|
|
61
|
+
const relConfigPath = path.relative(cwd, configPath) || path.basename(configPath)
|
|
62
|
+
return `Tip: if these workspaces are intentionally out of scope, add this in ${relConfigPath}:\n${objectLiteral}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return `Tip: if these workspaces are intentionally out of scope, run \`eslint-config-snapshot init\` and add this config:\n${objectLiteral}`
|
|
66
|
+
}
|