@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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @eslint-config-snapshot/cli
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Patch release: improve OSS compatibility workflow checks and align CLI zero-config print tolerance.
8
+ - Updated dependencies
9
+ - @eslint-config-snapshot/api@1.1.1
10
+
11
+ ## 1.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - Minor release: improve skipped workspace messaging and OSS compatibility documentation.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - @eslint-config-snapshot/api@1.1.0
21
+
3
22
  ## 1.0.0
4
23
 
5
24
  ### Major Changes
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ __export(index_exports, {
38
38
  module.exports = __toCommonJS(index_exports);
39
39
  var import_commander = require("commander");
40
40
  var import_debug2 = __toESM(require("debug"), 1);
41
- var import_node_path4 = __toESM(require("path"), 1);
41
+ var import_node_path5 = __toESM(require("path"), 1);
42
42
 
43
43
  // src/commands/check.ts
44
44
  var import_api3 = require("@eslint-config-snapshot/api");
@@ -216,6 +216,12 @@ function formatStoredSnapshotSummary(storedSnapshots) {
216
216
  const summary = summarizeSnapshots(storedSnapshots);
217
217
  return `${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off)`;
218
218
  }
219
+ function formatBaselineSummaryLines(summary, workspaceCount) {
220
+ return `- \u{1F4E6} baseline: ${summary.groups} groups, ${summary.rules} rules
221
+ - \u{1F5C2}\uFE0F workspaces scanned: ${workspaceCount}
222
+ - \u{1F39A}\uFE0F severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off
223
+ `;
224
+ }
219
225
  function countRuleSeverities(ruleObjects) {
220
226
  let rules = 0;
221
227
  let error = 0;
@@ -395,19 +401,24 @@ var import_node_path2 = __toESM(require("path"), 1);
395
401
  var debugWorkspace = (0, import_debug.default)("eslint-config-snapshot:workspace");
396
402
  var debugDiff = (0, import_debug.default)("eslint-config-snapshot:diff");
397
403
  var debugTiming = (0, import_debug.default)("eslint-config-snapshot:timing");
398
- async function computeCurrentSnapshots(cwd) {
404
+ async function computeCurrentSnapshots(cwd, options) {
405
+ const allowWorkspaceExtractionFailure = options?.allowWorkspaceExtractionFailure ?? false;
406
+ const onWorkspacesDiscovered = options?.onWorkspacesDiscovered;
407
+ const onWorkspaceSkipped = options?.onWorkspaceSkipped;
399
408
  const computeStartedAt = Date.now();
400
409
  const configStartedAt = Date.now();
401
410
  const config = await (0, import_api2.loadConfig)(cwd);
402
411
  debugTiming("phase=loadConfig elapsedMs=%d", Date.now() - configStartedAt);
403
412
  const assignmentStartedAt = Date.now();
404
413
  const { discovery, assignments } = await resolveWorkspaceAssignments(cwd, config);
414
+ onWorkspacesDiscovered?.(discovery.workspacesRel);
405
415
  debugTiming("phase=resolveWorkspaceAssignments elapsedMs=%d", Date.now() - assignmentStartedAt);
406
416
  debugWorkspace("root=%s groups=%d workspaces=%d", discovery.rootAbs, assignments.length, discovery.workspacesRel.length);
407
417
  const snapshots = /* @__PURE__ */ new Map();
408
418
  for (const group of assignments) {
409
419
  const groupStartedAt = Date.now();
410
420
  const extractedForGroup = [];
421
+ const extractedWorkspaces = [];
411
422
  debugWorkspace("group=%s workspaces=%o", group.name, group.workspaces);
412
423
  for (const workspaceRel of group.workspaces) {
413
424
  const workspaceAbs = import_node_path2.default.resolve(discovery.rootAbs, workspaceRel);
@@ -440,7 +451,7 @@ async function computeCurrentSnapshots(cwd) {
440
451
  continue;
441
452
  }
442
453
  const message = result.error instanceof Error ? result.error.message : String(result.error);
443
- if (isRecoverableExtractionError(message)) {
454
+ if (isRecoverableExtractionError(message) || allowWorkspaceExtractionFailure) {
444
455
  lastExtractionError = message;
445
456
  continue;
446
457
  }
@@ -448,10 +459,23 @@ async function computeCurrentSnapshots(cwd) {
448
459
  }
449
460
  if (extractedCount === 0) {
450
461
  const context = lastExtractionError ? ` Last error: ${lastExtractionError}` : "";
451
- throw new Error(
452
- `Unable to extract ESLint config for workspace ${workspaceRel}. All sampled files were ignored or produced non-JSON output.${context}`
453
- );
462
+ if (allowWorkspaceExtractionFailure && isSkippableWorkspaceExtractionFailure(lastExtractionError)) {
463
+ onWorkspaceSkipped?.({
464
+ groupId: group.name,
465
+ workspaceRel,
466
+ reason: lastExtractionError ?? "unknown extraction failure"
467
+ });
468
+ debugWorkspace(
469
+ "group=%s workspace=%s skipped=true reason=%s",
470
+ group.name,
471
+ workspaceRel,
472
+ lastExtractionError ?? "unknown extraction failure"
473
+ );
474
+ continue;
475
+ }
476
+ throw new Error(`Unable to extract ESLint config for workspace ${workspaceRel}.${context}`);
454
477
  }
478
+ extractedWorkspaces.push(workspaceRel);
455
479
  debugWorkspace(
456
480
  "group=%s workspace=%s extracted=%d failed=%d",
457
481
  group.name,
@@ -460,8 +484,15 @@ async function computeCurrentSnapshots(cwd) {
460
484
  results.length - extractedCount
461
485
  );
462
486
  }
487
+ if (extractedForGroup.length === 0) {
488
+ if (allowWorkspaceExtractionFailure) {
489
+ debugWorkspace("group=%s skipped=true reason=no-extracted-workspaces", group.name);
490
+ continue;
491
+ }
492
+ throw new Error(`Unable to extract ESLint config for group ${group.name}: no workspace produced a valid config`);
493
+ }
463
494
  const aggregated = (0, import_api2.aggregateRules)(extractedForGroup);
464
- snapshots.set(group.name, (0, import_api2.buildSnapshot)(group.name, group.workspaces, aggregated));
495
+ snapshots.set(group.name, (0, import_api2.buildSnapshot)(group.name, extractedWorkspaces, aggregated));
465
496
  debugWorkspace(
466
497
  "group=%s aggregatedRules=%d groupElapsedMs=%d",
467
498
  group.name,
@@ -470,11 +501,20 @@ async function computeCurrentSnapshots(cwd) {
470
501
  );
471
502
  }
472
503
  debugTiming("phase=computeCurrentSnapshots elapsedMs=%d", Date.now() - computeStartedAt);
504
+ if (snapshots.size === 0) {
505
+ throw new Error("Unable to extract ESLint config from discovered workspaces in zero-config mode");
506
+ }
473
507
  return snapshots;
474
508
  }
475
509
  function isRecoverableExtractionError(message) {
476
510
  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");
477
511
  }
512
+ function isSkippableWorkspaceExtractionFailure(message) {
513
+ if (!message) {
514
+ return true;
515
+ }
516
+ 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");
517
+ }
478
518
  async function resolveWorkspaceAssignments(cwd, config) {
479
519
  const discovery = await (0, import_api2.discoverWorkspaces)({ cwd, workspaceInput: config.workspaceInput });
480
520
  const assignments = config.grouping.mode === "standalone" ? discovery.workspacesRel.map((workspace) => ({ name: workspace, workspaces: [workspace] })) : (0, import_api2.assignGroupsByMatch)(discovery.workspacesRel, config.grouping.groups ?? [{ name: "default", match: ["**/*"] }]);
@@ -544,6 +584,63 @@ async function resolveGroupEslintVersions(cwd) {
544
584
  return result;
545
585
  }
546
586
 
587
+ // src/commands/skipped-workspaces.ts
588
+ var import_node_path3 = __toESM(require("path"), 1);
589
+ function writeSkippedWorkspaceSummary(terminal, cwd, configPath, skippedWorkspaces) {
590
+ if (skippedWorkspaces.length === 0) {
591
+ return;
592
+ }
593
+ const workspacePaths = collectSkippedWorkspacePaths(skippedWorkspaces);
594
+ terminal.warning(
595
+ `Heads up: ${workspacePaths.length} workspace(s) were skipped because ESLint auto-discovery could not extract an effective config for them.
596
+ `
597
+ );
598
+ terminal.warning(`Skipped workspaces: ${workspacePaths.join(", ")}
599
+ `);
600
+ terminal.subtle(formatScopedConfigHint(cwd, configPath, workspacePaths));
601
+ }
602
+ function collectSkippedWorkspacePaths(skippedWorkspaces) {
603
+ const unique = /* @__PURE__ */ new Set();
604
+ for (const skipped of skippedWorkspaces) {
605
+ unique.add(skipped.workspaceRel);
606
+ }
607
+ return [...unique].sort();
608
+ }
609
+ function toExcludeGlobs(workspacePaths) {
610
+ return workspacePaths.map((workspacePath) => `${workspacePath}/**`);
611
+ }
612
+ function formatScopedConfigHint(cwd, configPath, workspacePaths) {
613
+ const excludeGlobs = toExcludeGlobs(workspacePaths);
614
+ if (configPath && import_node_path3.default.basename(configPath) === "package.json") {
615
+ return `Tip: if these workspaces are intentionally out of scope, add this under "eslint-config-snapshot" in package.json:
616
+ ${JSON.stringify(
617
+ {
618
+ sampling: {
619
+ excludeGlobs
620
+ }
621
+ },
622
+ null,
623
+ 2
624
+ )}
625
+ `;
626
+ }
627
+ const objectLiteral = `{
628
+ sampling: {
629
+ excludeGlobs: [
630
+ ${excludeGlobs.map((value) => ` '${value}'`).join(",\n")}
631
+ ]
632
+ }
633
+ }
634
+ `;
635
+ if (configPath) {
636
+ const relConfigPath = import_node_path3.default.relative(cwd, configPath) || import_node_path3.default.basename(configPath);
637
+ return `Tip: if these workspaces are intentionally out of scope, add this in ${relConfigPath}:
638
+ ${objectLiteral}`;
639
+ }
640
+ return `Tip: if these workspaces are intentionally out of scope, run \`eslint-config-snapshot init\` and add this config:
641
+ ${objectLiteral}`;
642
+ }
643
+
547
644
  // src/commands/check.ts
548
645
  var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
549
646
  async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
@@ -561,10 +658,21 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
561
658
  );
562
659
  }
563
660
  let currentSnapshots;
661
+ const skippedWorkspaces = [];
662
+ let discoveredWorkspaces = [];
663
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config);
564
664
  try {
565
- currentSnapshots = await computeCurrentSnapshots(cwd);
665
+ currentSnapshots = await computeCurrentSnapshots(cwd, {
666
+ allowWorkspaceExtractionFailure,
667
+ onWorkspacesDiscovered: (workspacesRel) => {
668
+ discoveredWorkspaces = workspacesRel;
669
+ },
670
+ onWorkspaceSkipped: (skipped) => {
671
+ skippedWorkspaces.push(skipped);
672
+ }
673
+ });
566
674
  } catch (error) {
567
- if (!foundConfig) {
675
+ if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
568
676
  terminal.write(
569
677
  "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
570
678
  );
@@ -572,6 +680,10 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
572
680
  }
573
681
  throw error;
574
682
  }
683
+ if (!foundConfig) {
684
+ writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
685
+ }
686
+ writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
575
687
  if (storedSnapshots.size === 0) {
576
688
  const summary = summarizeSnapshots(currentSnapshots);
577
689
  terminal.write(
@@ -631,12 +743,7 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
631
743
  if (changes.length === 0) {
632
744
  terminal.write(color.green("\u2705 Great news: no snapshot drift detected.\n"));
633
745
  terminal.section("\u{1F4CA} Summary");
634
- terminal.write(
635
- `- \u{1F4E6} baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
636
- - \u{1F5C2}\uFE0F workspaces scanned: ${workspaceCount}
637
- - \u{1F39A}\uFE0F severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
638
- `
639
- );
746
+ terminal.write(formatBaselineSummaryLines(currentSummary, workspaceCount));
640
747
  writeEslintVersionSummary(terminal, eslintVersionsByGroup);
641
748
  return 0;
642
749
  }
@@ -671,6 +778,21 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
671
778
  terminal.subtle(UPDATE_HINT);
672
779
  return 1;
673
780
  }
781
+ function isWorkspaceDiscoveryDefaultsError(error) {
782
+ const message = error instanceof Error ? error.message : String(error);
783
+ return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
784
+ }
785
+ function isDefaultEquivalentConfig(config) {
786
+ return JSON.stringify(config) === JSON.stringify(import_api3.DEFAULT_CONFIG);
787
+ }
788
+ function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
789
+ if (workspacesRel.length === 0) {
790
+ terminal.subtle("Auto-discovered workspaces: none\n");
791
+ return;
792
+ }
793
+ terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
794
+ `);
795
+ }
674
796
 
675
797
  // src/commands/print.ts
676
798
  var import_api4 = require("@eslint-config-snapshot/api");
@@ -681,7 +803,8 @@ async function executePrint(cwd, terminal, snapshotDir, format) {
681
803
  if (terminal.showProgress) {
682
804
  terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
683
805
  }
684
- const currentSnapshots = await computeCurrentSnapshots(cwd);
806
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig2(foundConfig.config);
807
+ const currentSnapshots = await computeCurrentSnapshots(cwd, { allowWorkspaceExtractionFailure });
685
808
  if (format === "short") {
686
809
  terminal.write(formatShortPrint([...currentSnapshots.values()]));
687
810
  return;
@@ -720,6 +843,9 @@ async function executeConfig(cwd, terminal, snapshotDir, format) {
720
843
  terminal.write(`${JSON.stringify(payload, null, 2)}
721
844
  `);
722
845
  }
846
+ function isDefaultEquivalentConfig2(config) {
847
+ return JSON.stringify(config) === JSON.stringify(import_api4.DEFAULT_CONFIG);
848
+ }
723
849
 
724
850
  // src/commands/update.ts
725
851
  var import_api5 = require("@eslint-config-snapshot/api");
@@ -736,10 +862,21 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
736
862
  );
737
863
  }
738
864
  let currentSnapshots;
865
+ const skippedWorkspaces = [];
866
+ let discoveredWorkspaces = [];
867
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig3(foundConfig.config);
739
868
  try {
740
- currentSnapshots = await computeCurrentSnapshots(cwd);
869
+ currentSnapshots = await computeCurrentSnapshots(cwd, {
870
+ allowWorkspaceExtractionFailure,
871
+ onWorkspacesDiscovered: (workspacesRel) => {
872
+ discoveredWorkspaces = workspacesRel;
873
+ },
874
+ onWorkspaceSkipped: (skipped) => {
875
+ skippedWorkspaces.push(skipped);
876
+ }
877
+ });
741
878
  } catch (error) {
742
- if (!foundConfig) {
879
+ if (!foundConfig && isWorkspaceDiscoveryDefaultsError2(error)) {
743
880
  terminal.write(
744
881
  "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
745
882
  );
@@ -747,28 +884,45 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
747
884
  }
748
885
  throw error;
749
886
  }
887
+ if (!foundConfig) {
888
+ writeDiscoveredWorkspacesSummary2(terminal, discoveredWorkspaces);
889
+ }
890
+ writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
750
891
  await writeSnapshots(cwd, snapshotDir, currentSnapshots);
751
892
  if (printSummary) {
752
893
  const summary = summarizeSnapshots(currentSnapshots);
753
894
  const workspaceCount = countUniqueWorkspaces(currentSnapshots);
754
895
  const eslintVersionsByGroup = terminal.showProgress ? await resolveGroupEslintVersions(cwd) : /* @__PURE__ */ new Map();
896
+ const baselineAction = storedSnapshots.size === 0 ? "created" : "updated";
897
+ terminal.success(`\u2705 Great news: baseline was successfully ${baselineAction} for your project.
898
+ `);
755
899
  terminal.section("\u{1F4CA} Summary");
756
- terminal.write(
757
- `Baseline updated: ${summary.groups} groups, ${summary.rules} rules.
758
- Workspaces scanned: ${workspaceCount}.
759
- Severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off.
760
- `
761
- );
900
+ terminal.write(formatBaselineSummaryLines(summary, workspaceCount));
762
901
  writeEslintVersionSummary(terminal, eslintVersionsByGroup);
763
902
  }
764
903
  return 0;
765
904
  }
905
+ function isWorkspaceDiscoveryDefaultsError2(error) {
906
+ const message = error instanceof Error ? error.message : String(error);
907
+ return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
908
+ }
909
+ function isDefaultEquivalentConfig3(config) {
910
+ return JSON.stringify(config) === JSON.stringify(import_api5.DEFAULT_CONFIG);
911
+ }
912
+ function writeDiscoveredWorkspacesSummary2(terminal, workspacesRel) {
913
+ if (workspacesRel.length === 0) {
914
+ terminal.subtle("Auto-discovered workspaces: none\n");
915
+ return;
916
+ }
917
+ terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
918
+ `);
919
+ }
766
920
 
767
921
  // src/init.ts
768
922
  var import_api6 = require("@eslint-config-snapshot/api");
769
923
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
770
924
  var import_promises2 = require("fs/promises");
771
- var import_node_path3 = __toESM(require("path"), 1);
925
+ var import_node_path4 = __toESM(require("path"), 1);
772
926
  async function runInit(cwd, opts, runtime) {
773
927
  const force = opts.force ?? false;
774
928
  const showEffective = opts.showEffective ?? false;
@@ -836,7 +990,7 @@ async function runInitInFile(cwd, configObject, force, runtime) {
836
990
  ];
837
991
  for (const candidate of candidates) {
838
992
  try {
839
- await (0, import_promises2.access)(import_node_path3.default.join(cwd, candidate));
993
+ await (0, import_promises2.access)(import_node_path4.default.join(cwd, candidate));
840
994
  if (!force) {
841
995
  runtime.writeStderr(`Config already exists: ${candidate}
842
996
  `);
@@ -845,14 +999,14 @@ async function runInitInFile(cwd, configObject, force, runtime) {
845
999
  } catch {
846
1000
  }
847
1001
  }
848
- const target = import_node_path3.default.join(cwd, "eslint-config-snapshot.config.mjs");
1002
+ const target = import_node_path4.default.join(cwd, "eslint-config-snapshot.config.mjs");
849
1003
  await (0, import_promises2.writeFile)(target, toConfigScaffold(configObject), "utf8");
850
- runtime.writeStdout(`Created ${import_node_path3.default.basename(target)}
1004
+ runtime.writeStdout(`Created ${import_node_path4.default.basename(target)}
851
1005
  `);
852
1006
  return 0;
853
1007
  }
854
1008
  async function runInitInPackageJson(cwd, configObject, force, runtime) {
855
- const packageJsonPath = import_node_path3.default.join(cwd, "package.json");
1009
+ const packageJsonPath = import_node_path4.default.join(cwd, "package.json");
856
1010
  let packageJsonRaw;
857
1011
  try {
858
1012
  packageJsonRaw = await (0, import_promises2.readFile)(packageJsonPath, "utf8");
@@ -913,7 +1067,7 @@ async function discoverInitWorkspaces(cwd) {
913
1067
  if (!(discovered.workspacesRel.length === 1 && discovered.workspacesRel[0] === ".")) {
914
1068
  return discovered.workspacesRel;
915
1069
  }
916
- const packageJsonPath = import_node_path3.default.join(cwd, "package.json");
1070
+ const packageJsonPath = import_node_path4.default.join(cwd, "package.json");
917
1071
  try {
918
1072
  const raw = await (0, import_promises2.readFile)(packageJsonPath, "utf8");
919
1073
  const parsed = JSON.parse(raw);
@@ -931,7 +1085,7 @@ async function discoverInitWorkspaces(cwd) {
931
1085
  onlyFiles: true,
932
1086
  dot: true
933
1087
  });
934
- const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => (0, import_api6.normalizePath)(import_node_path3.default.dirname(entry))))].sort(
1088
+ const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => (0, import_api6.normalizePath)(import_node_path4.default.dirname(entry))))].sort(
935
1089
  (a, b) => a.localeCompare(b)
936
1090
  );
937
1091
  if (workspaceDirs.length > 0) {
@@ -1342,7 +1496,7 @@ function isDirectCliExecution() {
1342
1496
  if (!entry) {
1343
1497
  return false;
1344
1498
  }
1345
- const normalized = import_node_path4.default.basename(entry).toLowerCase();
1499
+ const normalized = import_node_path5.default.basename(entry).toLowerCase();
1346
1500
  return normalized === "index.js" || normalized === "index.cjs" || normalized === "index.ts" || normalized === "eslint-config-snapshot";
1347
1501
  }
1348
1502
  if (isDirectCliExecution()) {