@eslint-config-snapshot/cli 1.1.0 → 1.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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @eslint-config-snapshot/cli
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Minor release: centralize shared command execution flow with reusable snapshot preparation executor.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @eslint-config-snapshot/api@1.2.0
13
+
14
+ ## 1.1.1
15
+
16
+ ### Patch Changes
17
+
18
+ - Patch release: improve OSS compatibility workflow checks and align CLI zero-config print tolerance.
19
+ - Updated dependencies
20
+ - @eslint-config-snapshot/api@1.1.1
21
+
3
22
  ## 1.1.0
4
23
 
5
24
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -40,9 +40,6 @@ var import_commander = require("commander");
40
40
  var import_debug2 = __toESM(require("debug"), 1);
41
41
  var import_node_path5 = __toESM(require("path"), 1);
42
42
 
43
- // src/commands/check.ts
44
- var import_api3 = require("@eslint-config-snapshot/api");
45
-
46
43
  // src/formatters.ts
47
44
  function formatDiff(groupId, diff) {
48
45
  const lines = [`group: ${groupId}`];
@@ -586,6 +583,14 @@ async function resolveGroupEslintVersions(cwd) {
586
583
 
587
584
  // src/commands/skipped-workspaces.ts
588
585
  var import_node_path3 = __toESM(require("path"), 1);
586
+ function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
587
+ if (workspacesRel.length === 0) {
588
+ terminal.subtle("Auto-discovered workspaces: none\n");
589
+ return;
590
+ }
591
+ terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
592
+ `);
593
+ }
589
594
  function writeSkippedWorkspaceSummary(terminal, cwd, configPath, skippedWorkspaces) {
590
595
  if (skippedWorkspaces.length === 0) {
591
596
  return;
@@ -641,28 +646,30 @@ ${objectLiteral}`;
641
646
  ${objectLiteral}`;
642
647
  }
643
648
 
644
- // src/commands/check.ts
645
- var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
646
- async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
649
+ // src/commands/snapshot-executor.ts
650
+ var import_api3 = require("@eslint-config-snapshot/api");
651
+ async function prepareSnapshotExecution(options) {
652
+ const { cwd, snapshotDir, terminal, commandLabel, progressMessage, showContext = true } = options;
647
653
  const foundConfig = await (0, import_api3.findConfigPath)(cwd);
648
654
  const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
649
- if (format !== "status") {
650
- writeRunContextHeader(terminal, cwd, defaultInvocation ? "check" : `check:${format}`, foundConfig?.path, storedSnapshots);
651
- if (terminal.showProgress) {
652
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
653
- }
655
+ if (showContext) {
656
+ writeRunContextHeader(terminal, cwd, commandLabel, foundConfig?.path, storedSnapshots);
654
657
  }
655
- if (!foundConfig) {
658
+ if (showContext && terminal.showProgress && progressMessage.length > 0) {
659
+ terminal.subtle(progressMessage);
660
+ }
661
+ if (showContext && !foundConfig) {
656
662
  terminal.subtle(
657
663
  "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
658
664
  );
659
665
  }
660
- let currentSnapshots;
661
666
  const skippedWorkspaces = [];
662
667
  let discoveredWorkspaces = [];
668
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config);
669
+ let currentSnapshots;
663
670
  try {
664
671
  currentSnapshots = await computeCurrentSnapshots(cwd, {
665
- allowWorkspaceExtractionFailure: !foundConfig,
672
+ allowWorkspaceExtractionFailure,
666
673
  onWorkspacesDiscovered: (workspacesRel) => {
667
674
  discoveredWorkspaces = workspacesRel;
668
675
  },
@@ -671,18 +678,56 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
671
678
  }
672
679
  });
673
680
  } catch (error) {
674
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
675
- terminal.write(
676
- "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
677
- );
678
- return 1;
681
+ if (allowWorkspaceExtractionFailure && isWorkspaceDiscoveryDefaultsError(error)) {
682
+ if (showContext) {
683
+ terminal.write(
684
+ "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
685
+ );
686
+ }
687
+ return { ok: false, exitCode: 1 };
679
688
  }
680
689
  throw error;
681
690
  }
682
- if (!foundConfig) {
691
+ return {
692
+ ok: true,
693
+ foundConfig,
694
+ storedSnapshots,
695
+ currentSnapshots,
696
+ discoveredWorkspaces,
697
+ skippedWorkspaces
698
+ };
699
+ }
700
+ function isWorkspaceDiscoveryDefaultsError(error) {
701
+ const message = error instanceof Error ? error.message : String(error);
702
+ return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
703
+ }
704
+ function isDefaultEquivalentConfig(config) {
705
+ return JSON.stringify(config) === JSON.stringify(import_api3.DEFAULT_CONFIG);
706
+ }
707
+
708
+ // src/commands/check.ts
709
+ var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
710
+ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
711
+ const isStatusFormat = format === "status";
712
+ const commandLabel = defaultInvocation ? "check" : `check:${format}`;
713
+ const prepared = await prepareSnapshotExecution({
714
+ cwd,
715
+ snapshotDir,
716
+ terminal,
717
+ commandLabel: isStatusFormat ? "check:status" : commandLabel,
718
+ progressMessage: isStatusFormat ? "" : "\u{1F50E} Checking current ESLint configuration...\n",
719
+ showContext: isStatusFormat === false
720
+ });
721
+ if (prepared.ok === false) {
722
+ return prepared.exitCode;
723
+ }
724
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared;
725
+ if (foundConfig === null && isStatusFormat === false) {
683
726
  writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
684
727
  }
685
- writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
728
+ if (isStatusFormat === false) {
729
+ writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
730
+ }
686
731
  if (storedSnapshots.size === 0) {
687
732
  const summary = summarizeSnapshots(currentSnapshots);
688
733
  terminal.write(
@@ -777,32 +822,24 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
777
822
  terminal.subtle(UPDATE_HINT);
778
823
  return 1;
779
824
  }
780
- function isWorkspaceDiscoveryDefaultsError(error) {
781
- const message = error instanceof Error ? error.message : String(error);
782
- return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
783
- }
784
- function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
785
- if (workspacesRel.length === 0) {
786
- terminal.subtle("Auto-discovered workspaces: none\n");
787
- return;
788
- }
789
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
790
- `);
791
- }
792
825
 
793
826
  // src/commands/print.ts
794
827
  var import_api4 = require("@eslint-config-snapshot/api");
795
828
  async function executePrint(cwd, terminal, snapshotDir, format) {
796
- const foundConfig = await (0, import_api4.findConfigPath)(cwd);
797
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
798
- writeRunContextHeader(terminal, cwd, `print:${format}`, foundConfig?.path, storedSnapshots);
799
- if (terminal.showProgress) {
800
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
829
+ const prepared = await prepareSnapshotExecution({
830
+ cwd,
831
+ snapshotDir,
832
+ terminal,
833
+ commandLabel: `print:${format}`,
834
+ progressMessage: "\u{1F50E} Checking current ESLint configuration...\n"
835
+ });
836
+ if (!prepared.ok) {
837
+ return prepared.exitCode;
801
838
  }
802
- const currentSnapshots = await computeCurrentSnapshots(cwd);
839
+ const { currentSnapshots } = prepared;
803
840
  if (format === "short") {
804
841
  terminal.write(formatShortPrint([...currentSnapshots.values()]));
805
- return;
842
+ return 0;
806
843
  }
807
844
  const output = [...currentSnapshots.values()].map((snapshot) => ({
808
845
  groupId: snapshot.groupId,
@@ -810,14 +847,20 @@ async function executePrint(cwd, terminal, snapshotDir, format) {
810
847
  }));
811
848
  terminal.write(`${JSON.stringify(output, null, 2)}
812
849
  `);
850
+ return 0;
813
851
  }
814
852
  async function executeConfig(cwd, terminal, snapshotDir, format) {
815
- const foundConfig = await (0, import_api4.findConfigPath)(cwd);
816
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
817
- writeRunContextHeader(terminal, cwd, `config:${format}`, foundConfig?.path, storedSnapshots);
818
- if (terminal.showProgress) {
819
- terminal.subtle("\u2699\uFE0F Resolving effective runtime configuration...\n");
853
+ const prepared = await prepareSnapshotExecution({
854
+ cwd,
855
+ snapshotDir,
856
+ terminal,
857
+ commandLabel: `config:${format}`,
858
+ progressMessage: "\u2699\uFE0F Resolving effective runtime configuration...\n"
859
+ });
860
+ if (!prepared.ok) {
861
+ return prepared.exitCode;
820
862
  }
863
+ const { foundConfig } = prepared;
821
864
  const config = await (0, import_api4.loadConfig)(cwd);
822
865
  const resolved = await resolveWorkspaceAssignments(cwd, config);
823
866
  const payload = {
@@ -833,50 +876,28 @@ async function executeConfig(cwd, terminal, snapshotDir, format) {
833
876
  };
834
877
  if (format === "short") {
835
878
  terminal.write(formatShortConfig(payload));
836
- return;
879
+ return 0;
837
880
  }
838
881
  terminal.write(`${JSON.stringify(payload, null, 2)}
839
882
  `);
883
+ return 0;
840
884
  }
841
885
 
842
886
  // src/commands/update.ts
843
- var import_api5 = require("@eslint-config-snapshot/api");
844
887
  async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
845
- const foundConfig = await (0, import_api5.findConfigPath)(cwd);
846
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
847
- writeRunContextHeader(terminal, cwd, "update", foundConfig?.path, storedSnapshots);
848
- if (terminal.showProgress) {
849
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
850
- }
851
- if (!foundConfig) {
852
- terminal.subtle(
853
- "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
854
- );
855
- }
856
- let currentSnapshots;
857
- const skippedWorkspaces = [];
858
- let discoveredWorkspaces = [];
859
- try {
860
- currentSnapshots = await computeCurrentSnapshots(cwd, {
861
- allowWorkspaceExtractionFailure: !foundConfig,
862
- onWorkspacesDiscovered: (workspacesRel) => {
863
- discoveredWorkspaces = workspacesRel;
864
- },
865
- onWorkspaceSkipped: (skipped) => {
866
- skippedWorkspaces.push(skipped);
867
- }
868
- });
869
- } catch (error) {
870
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError2(error)) {
871
- terminal.write(
872
- "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
873
- );
874
- return 1;
875
- }
876
- throw error;
888
+ const prepared = await prepareSnapshotExecution({
889
+ cwd,
890
+ snapshotDir,
891
+ terminal,
892
+ commandLabel: "update",
893
+ progressMessage: "\u{1F50E} Checking current ESLint configuration...\n"
894
+ });
895
+ if (prepared.ok === false) {
896
+ return prepared.exitCode;
877
897
  }
898
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared;
878
899
  if (!foundConfig) {
879
- writeDiscoveredWorkspacesSummary2(terminal, discoveredWorkspaces);
900
+ writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
880
901
  }
881
902
  writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
882
903
  await writeSnapshots(cwd, snapshotDir, currentSnapshots);
@@ -893,28 +914,16 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
893
914
  }
894
915
  return 0;
895
916
  }
896
- function isWorkspaceDiscoveryDefaultsError2(error) {
897
- const message = error instanceof Error ? error.message : String(error);
898
- return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
899
- }
900
- function writeDiscoveredWorkspacesSummary2(terminal, workspacesRel) {
901
- if (workspacesRel.length === 0) {
902
- terminal.subtle("Auto-discovered workspaces: none\n");
903
- return;
904
- }
905
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
906
- `);
907
- }
908
917
 
909
918
  // src/init.ts
910
- var import_api6 = require("@eslint-config-snapshot/api");
919
+ var import_api5 = require("@eslint-config-snapshot/api");
911
920
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
912
921
  var import_promises2 = require("fs/promises");
913
922
  var import_node_path4 = __toESM(require("path"), 1);
914
923
  async function runInit(cwd, opts, runtime) {
915
924
  const force = opts.force ?? false;
916
925
  const showEffective = opts.showEffective ?? false;
917
- const existing = await (0, import_api6.findConfigPath)(cwd);
926
+ const existing = await (0, import_api5.findConfigPath)(cwd);
918
927
  if (existing && !force) {
919
928
  runtime.writeStderr(
920
929
  `Existing config detected at ${existing.path}. Creating another config can cause conflicts. Remove the existing config or rerun with --force.
@@ -1051,7 +1060,7 @@ function buildRecommendedConfigFromAssignments(workspaces, assignments) {
1051
1060
  };
1052
1061
  }
1053
1062
  async function discoverInitWorkspaces(cwd) {
1054
- const discovered = await (0, import_api6.discoverWorkspaces)({ cwd, workspaceInput: { mode: "discover" } });
1063
+ const discovered = await (0, import_api5.discoverWorkspaces)({ cwd, workspaceInput: { mode: "discover" } });
1055
1064
  if (!(discovered.workspacesRel.length === 1 && discovered.workspacesRel[0] === ".")) {
1056
1065
  return discovered.workspacesRel;
1057
1066
  }
@@ -1073,7 +1082,7 @@ async function discoverInitWorkspaces(cwd) {
1073
1082
  onlyFiles: true,
1074
1083
  dot: true
1075
1084
  });
1076
- const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => (0, import_api6.normalizePath)(import_node_path4.default.dirname(entry))))].sort(
1085
+ const workspaceDirs = [...new Set(workspacePackageFiles.map((entry) => (0, import_api5.normalizePath)(import_node_path4.default.dirname(entry))))].sort(
1077
1086
  (a, b) => a.localeCompare(b)
1078
1087
  );
1079
1088
  if (workspaceDirs.length > 0) {
@@ -1124,7 +1133,7 @@ async function askRecommendedGroupAssignments(workspaces, runtime) {
1124
1133
  }
1125
1134
  function toConfigScaffold(configObject) {
1126
1135
  if (Object.keys(configObject).length === 0) {
1127
- return (0, import_api6.getConfigScaffold)("minimal");
1136
+ return (0, import_api5.getConfigScaffold)("minimal");
1128
1137
  }
1129
1138
  return `export default ${JSON.stringify(configObject, null, 2)}
1130
1139
  `;
@@ -1404,13 +1413,11 @@ function createProgram(cwd, terminal, onActionExit) {
1404
1413
  });
1405
1414
  program.command("print").description("Print aggregated rules").option("--format <format>", "Output format: json|short", parsePrintFormat, "json").option("--short", "Alias for --format short").action(async (opts) => {
1406
1415
  const format = opts.short ? "short" : opts.format;
1407
- await executePrint(cwd, terminal, SNAPSHOT_DIR, format);
1408
- onActionExit(0);
1416
+ onActionExit(await executePrint(cwd, terminal, SNAPSHOT_DIR, format));
1409
1417
  });
1410
1418
  program.command("config").description("Print effective evaluated config").option("--format <format>", "Output format: json|short", parsePrintFormat, "json").option("--short", "Alias for --format short").action(async (opts) => {
1411
1419
  const format = opts.short ? "short" : opts.format;
1412
- await executeConfig(cwd, terminal, SNAPSHOT_DIR, format);
1413
- onActionExit(0);
1420
+ onActionExit(await executeConfig(cwd, terminal, SNAPSHOT_DIR, format));
1414
1421
  });
1415
1422
  program.command("init").description("Initialize config (file or package.json)").option("--target <target>", "Config target: file|package-json", parseInitTarget).option("--preset <preset>", "Config preset: recommended|minimal|full", parseInitPreset).option("--show-effective", "Print the evaluated config that will be written").option("-f, --force", "Allow init even when an existing config is detected").option("-y, --yes", "Skip prompts and use defaults/options").addHelpText(
1416
1423
  "after",
package/dist/index.js CHANGED
@@ -5,9 +5,6 @@ import { Command, CommanderError, InvalidArgumentError } from "commander";
5
5
  import createDebug2 from "debug";
6
6
  import path5 from "path";
7
7
 
8
- // src/commands/check.ts
9
- import { findConfigPath } from "@eslint-config-snapshot/api";
10
-
11
8
  // src/formatters.ts
12
9
  function formatDiff(groupId, diff) {
13
10
  const lines = [`group: ${groupId}`];
@@ -564,6 +561,14 @@ async function resolveGroupEslintVersions(cwd) {
564
561
 
565
562
  // src/commands/skipped-workspaces.ts
566
563
  import path3 from "path";
564
+ function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
565
+ if (workspacesRel.length === 0) {
566
+ terminal.subtle("Auto-discovered workspaces: none\n");
567
+ return;
568
+ }
569
+ terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
570
+ `);
571
+ }
567
572
  function writeSkippedWorkspaceSummary(terminal, cwd, configPath, skippedWorkspaces) {
568
573
  if (skippedWorkspaces.length === 0) {
569
574
  return;
@@ -619,28 +624,30 @@ ${objectLiteral}`;
619
624
  ${objectLiteral}`;
620
625
  }
621
626
 
622
- // src/commands/check.ts
623
- var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
624
- async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
627
+ // src/commands/snapshot-executor.ts
628
+ import { DEFAULT_CONFIG, findConfigPath } from "@eslint-config-snapshot/api";
629
+ async function prepareSnapshotExecution(options) {
630
+ const { cwd, snapshotDir, terminal, commandLabel, progressMessage, showContext = true } = options;
625
631
  const foundConfig = await findConfigPath(cwd);
626
632
  const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
627
- if (format !== "status") {
628
- writeRunContextHeader(terminal, cwd, defaultInvocation ? "check" : `check:${format}`, foundConfig?.path, storedSnapshots);
629
- if (terminal.showProgress) {
630
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
631
- }
633
+ if (showContext) {
634
+ writeRunContextHeader(terminal, cwd, commandLabel, foundConfig?.path, storedSnapshots);
632
635
  }
633
- if (!foundConfig) {
636
+ if (showContext && terminal.showProgress && progressMessage.length > 0) {
637
+ terminal.subtle(progressMessage);
638
+ }
639
+ if (showContext && !foundConfig) {
634
640
  terminal.subtle(
635
641
  "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
636
642
  );
637
643
  }
638
- let currentSnapshots;
639
644
  const skippedWorkspaces = [];
640
645
  let discoveredWorkspaces = [];
646
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config);
647
+ let currentSnapshots;
641
648
  try {
642
649
  currentSnapshots = await computeCurrentSnapshots(cwd, {
643
- allowWorkspaceExtractionFailure: !foundConfig,
650
+ allowWorkspaceExtractionFailure,
644
651
  onWorkspacesDiscovered: (workspacesRel) => {
645
652
  discoveredWorkspaces = workspacesRel;
646
653
  },
@@ -649,18 +656,56 @@ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocatio
649
656
  }
650
657
  });
651
658
  } catch (error) {
652
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
653
- terminal.write(
654
- "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
655
- );
656
- return 1;
659
+ if (allowWorkspaceExtractionFailure && isWorkspaceDiscoveryDefaultsError(error)) {
660
+ if (showContext) {
661
+ terminal.write(
662
+ "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
663
+ );
664
+ }
665
+ return { ok: false, exitCode: 1 };
657
666
  }
658
667
  throw error;
659
668
  }
660
- if (!foundConfig) {
669
+ return {
670
+ ok: true,
671
+ foundConfig,
672
+ storedSnapshots,
673
+ currentSnapshots,
674
+ discoveredWorkspaces,
675
+ skippedWorkspaces
676
+ };
677
+ }
678
+ function isWorkspaceDiscoveryDefaultsError(error) {
679
+ const message = error instanceof Error ? error.message : String(error);
680
+ return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
681
+ }
682
+ function isDefaultEquivalentConfig(config) {
683
+ return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG);
684
+ }
685
+
686
+ // src/commands/check.ts
687
+ var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
688
+ async function executeCheck(cwd, format, terminal, snapshotDir, defaultInvocation = false) {
689
+ const isStatusFormat = format === "status";
690
+ const commandLabel = defaultInvocation ? "check" : `check:${format}`;
691
+ const prepared = await prepareSnapshotExecution({
692
+ cwd,
693
+ snapshotDir,
694
+ terminal,
695
+ commandLabel: isStatusFormat ? "check:status" : commandLabel,
696
+ progressMessage: isStatusFormat ? "" : "\u{1F50E} Checking current ESLint configuration...\n",
697
+ showContext: isStatusFormat === false
698
+ });
699
+ if (prepared.ok === false) {
700
+ return prepared.exitCode;
701
+ }
702
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared;
703
+ if (foundConfig === null && isStatusFormat === false) {
661
704
  writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
662
705
  }
663
- writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
706
+ if (isStatusFormat === false) {
707
+ writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
708
+ }
664
709
  if (storedSnapshots.size === 0) {
665
710
  const summary = summarizeSnapshots(currentSnapshots);
666
711
  terminal.write(
@@ -755,32 +800,24 @@ function printWhatChanged(terminal, changes, currentSnapshots, eslintVersionsByG
755
800
  terminal.subtle(UPDATE_HINT);
756
801
  return 1;
757
802
  }
758
- function isWorkspaceDiscoveryDefaultsError(error) {
759
- const message = error instanceof Error ? error.message : String(error);
760
- return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
761
- }
762
- function writeDiscoveredWorkspacesSummary(terminal, workspacesRel) {
763
- if (workspacesRel.length === 0) {
764
- terminal.subtle("Auto-discovered workspaces: none\n");
765
- return;
766
- }
767
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
768
- `);
769
- }
770
803
 
771
804
  // src/commands/print.ts
772
- import { findConfigPath as findConfigPath2, loadConfig as loadConfig2 } from "@eslint-config-snapshot/api";
805
+ import { loadConfig as loadConfig2 } from "@eslint-config-snapshot/api";
773
806
  async function executePrint(cwd, terminal, snapshotDir, format) {
774
- const foundConfig = await findConfigPath2(cwd);
775
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
776
- writeRunContextHeader(terminal, cwd, `print:${format}`, foundConfig?.path, storedSnapshots);
777
- if (terminal.showProgress) {
778
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
807
+ const prepared = await prepareSnapshotExecution({
808
+ cwd,
809
+ snapshotDir,
810
+ terminal,
811
+ commandLabel: `print:${format}`,
812
+ progressMessage: "\u{1F50E} Checking current ESLint configuration...\n"
813
+ });
814
+ if (!prepared.ok) {
815
+ return prepared.exitCode;
779
816
  }
780
- const currentSnapshots = await computeCurrentSnapshots(cwd);
817
+ const { currentSnapshots } = prepared;
781
818
  if (format === "short") {
782
819
  terminal.write(formatShortPrint([...currentSnapshots.values()]));
783
- return;
820
+ return 0;
784
821
  }
785
822
  const output = [...currentSnapshots.values()].map((snapshot) => ({
786
823
  groupId: snapshot.groupId,
@@ -788,14 +825,20 @@ async function executePrint(cwd, terminal, snapshotDir, format) {
788
825
  }));
789
826
  terminal.write(`${JSON.stringify(output, null, 2)}
790
827
  `);
828
+ return 0;
791
829
  }
792
830
  async function executeConfig(cwd, terminal, snapshotDir, format) {
793
- const foundConfig = await findConfigPath2(cwd);
794
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
795
- writeRunContextHeader(terminal, cwd, `config:${format}`, foundConfig?.path, storedSnapshots);
796
- if (terminal.showProgress) {
797
- terminal.subtle("\u2699\uFE0F Resolving effective runtime configuration...\n");
831
+ const prepared = await prepareSnapshotExecution({
832
+ cwd,
833
+ snapshotDir,
834
+ terminal,
835
+ commandLabel: `config:${format}`,
836
+ progressMessage: "\u2699\uFE0F Resolving effective runtime configuration...\n"
837
+ });
838
+ if (!prepared.ok) {
839
+ return prepared.exitCode;
798
840
  }
841
+ const { foundConfig } = prepared;
799
842
  const config = await loadConfig2(cwd);
800
843
  const resolved = await resolveWorkspaceAssignments(cwd, config);
801
844
  const payload = {
@@ -811,50 +854,28 @@ async function executeConfig(cwd, terminal, snapshotDir, format) {
811
854
  };
812
855
  if (format === "short") {
813
856
  terminal.write(formatShortConfig(payload));
814
- return;
857
+ return 0;
815
858
  }
816
859
  terminal.write(`${JSON.stringify(payload, null, 2)}
817
860
  `);
861
+ return 0;
818
862
  }
819
863
 
820
864
  // src/commands/update.ts
821
- import { findConfigPath as findConfigPath3 } from "@eslint-config-snapshot/api";
822
865
  async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
823
- const foundConfig = await findConfigPath3(cwd);
824
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir);
825
- writeRunContextHeader(terminal, cwd, "update", foundConfig?.path, storedSnapshots);
826
- if (terminal.showProgress) {
827
- terminal.subtle("\u{1F50E} Checking current ESLint configuration...\n");
828
- }
829
- if (!foundConfig) {
830
- terminal.subtle(
831
- "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
832
- );
833
- }
834
- let currentSnapshots;
835
- const skippedWorkspaces = [];
836
- let discoveredWorkspaces = [];
837
- try {
838
- currentSnapshots = await computeCurrentSnapshots(cwd, {
839
- allowWorkspaceExtractionFailure: !foundConfig,
840
- onWorkspacesDiscovered: (workspacesRel) => {
841
- discoveredWorkspaces = workspacesRel;
842
- },
843
- onWorkspaceSkipped: (skipped) => {
844
- skippedWorkspaces.push(skipped);
845
- }
846
- });
847
- } catch (error) {
848
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError2(error)) {
849
- terminal.write(
850
- "Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n"
851
- );
852
- return 1;
853
- }
854
- throw error;
866
+ const prepared = await prepareSnapshotExecution({
867
+ cwd,
868
+ snapshotDir,
869
+ terminal,
870
+ commandLabel: "update",
871
+ progressMessage: "\u{1F50E} Checking current ESLint configuration...\n"
872
+ });
873
+ if (prepared.ok === false) {
874
+ return prepared.exitCode;
855
875
  }
876
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared;
856
877
  if (!foundConfig) {
857
- writeDiscoveredWorkspacesSummary2(terminal, discoveredWorkspaces);
878
+ writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces);
858
879
  }
859
880
  writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces);
860
881
  await writeSnapshots(cwd, snapshotDir, currentSnapshots);
@@ -871,28 +892,16 @@ async function executeUpdate(cwd, terminal, snapshotDir, printSummary) {
871
892
  }
872
893
  return 0;
873
894
  }
874
- function isWorkspaceDiscoveryDefaultsError2(error) {
875
- const message = error instanceof Error ? error.message : String(error);
876
- return message.includes("Unable to discover workspaces") || message.includes("Unmatched workspaces") || message.includes("zero-config mode");
877
- }
878
- function writeDiscoveredWorkspacesSummary2(terminal, workspacesRel) {
879
- if (workspacesRel.length === 0) {
880
- terminal.subtle("Auto-discovered workspaces: none\n");
881
- return;
882
- }
883
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(", ")}
884
- `);
885
- }
886
895
 
887
896
  // src/init.ts
888
- import { discoverWorkspaces as discoverWorkspaces2, findConfigPath as findConfigPath4, getConfigScaffold, normalizePath as normalizePath2 } from "@eslint-config-snapshot/api";
897
+ import { discoverWorkspaces as discoverWorkspaces2, findConfigPath as findConfigPath2, getConfigScaffold, normalizePath as normalizePath2 } from "@eslint-config-snapshot/api";
889
898
  import fg2 from "fast-glob";
890
899
  import { access, readFile, writeFile } from "fs/promises";
891
900
  import path4 from "path";
892
901
  async function runInit(cwd, opts, runtime) {
893
902
  const force = opts.force ?? false;
894
903
  const showEffective = opts.showEffective ?? false;
895
- const existing = await findConfigPath4(cwd);
904
+ const existing = await findConfigPath2(cwd);
896
905
  if (existing && !force) {
897
906
  runtime.writeStderr(
898
907
  `Existing config detected at ${existing.path}. Creating another config can cause conflicts. Remove the existing config or rerun with --force.
@@ -1382,13 +1391,11 @@ function createProgram(cwd, terminal, onActionExit) {
1382
1391
  });
1383
1392
  program.command("print").description("Print aggregated rules").option("--format <format>", "Output format: json|short", parsePrintFormat, "json").option("--short", "Alias for --format short").action(async (opts) => {
1384
1393
  const format = opts.short ? "short" : opts.format;
1385
- await executePrint(cwd, terminal, SNAPSHOT_DIR, format);
1386
- onActionExit(0);
1394
+ onActionExit(await executePrint(cwd, terminal, SNAPSHOT_DIR, format));
1387
1395
  });
1388
1396
  program.command("config").description("Print effective evaluated config").option("--format <format>", "Output format: json|short", parsePrintFormat, "json").option("--short", "Alias for --format short").action(async (opts) => {
1389
1397
  const format = opts.short ? "short" : opts.format;
1390
- await executeConfig(cwd, terminal, SNAPSHOT_DIR, format);
1391
- onActionExit(0);
1398
+ onActionExit(await executeConfig(cwd, terminal, SNAPSHOT_DIR, format));
1392
1399
  });
1393
1400
  program.command("init").description("Initialize config (file or package.json)").option("--target <target>", "Config target: file|package-json", parseInitTarget).option("--preset <preset>", "Config preset: recommended|minimal|full", parseInitPreset).option("--show-effective", "Print the evaluated config that will be written").option("-f, --force", "Allow init even when an existing config is detected").option("-y, --yes", "Skip prompts and use defaults/options").addHelpText(
1394
1401
  "after",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
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.1.0"
34
+ "@eslint-config-snapshot/api": "1.2.0"
35
35
  }
36
36
  }
@@ -1,5 +1,3 @@
1
- import { findConfigPath } from '@eslint-config-snapshot/api'
2
-
3
1
  import {
4
2
  countUniqueWorkspaces,
5
3
  decorateDiffLine,
@@ -8,20 +6,18 @@ import {
8
6
  summarizeChanges,
9
7
  summarizeSnapshots
10
8
  } from '../formatters.js'
11
- import { writeEslintVersionSummary, writeRunContextHeader } from '../run-context.js'
9
+ import { writeEslintVersionSummary } from '../run-context.js'
12
10
  import {
13
11
  type BuiltSnapshot,
14
12
  compareSnapshotMaps,
15
- computeCurrentSnapshots,
16
13
  type GroupEslintVersions,
17
- loadStoredSnapshots,
18
14
  resolveGroupEslintVersions,
19
- type SkippedWorkspace,
20
15
  type SnapshotDiff,
21
16
  writeSnapshots
22
17
  } from '../runtime.js'
23
18
  import { type TerminalIO } from '../terminal.js'
24
- import { writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
19
+ import { writeDiscoveredWorkspacesSummary, writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
20
+ import { prepareSnapshotExecution } from './snapshot-executor.js'
25
21
 
26
22
  export type CheckFormat = 'summary' | 'status' | 'diff'
27
23
 
@@ -34,49 +30,27 @@ export async function executeCheck(
34
30
  snapshotDir: string,
35
31
  defaultInvocation = false
36
32
  ): Promise<number> {
37
- const foundConfig = await findConfigPath(cwd)
38
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir)
39
-
40
- if (format !== 'status') {
41
- writeRunContextHeader(terminal, cwd, defaultInvocation ? 'check' : `check:${format}`, foundConfig?.path, storedSnapshots)
42
- if (terminal.showProgress) {
43
- terminal.subtle('🔎 Checking current ESLint configuration...\n')
44
- }
45
- }
46
-
47
- if (!foundConfig) {
48
- terminal.subtle(
49
- 'Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n'
50
- )
33
+ const isStatusFormat = format === 'status'
34
+ const commandLabel = defaultInvocation ? 'check' : `check:${format}`
35
+ const prepared = await prepareSnapshotExecution({
36
+ cwd,
37
+ snapshotDir,
38
+ terminal,
39
+ commandLabel: isStatusFormat ? 'check:status' : commandLabel,
40
+ progressMessage: isStatusFormat ? '' : '🔎 Checking current ESLint configuration...\n',
41
+ showContext: isStatusFormat === false
42
+ })
43
+ if (prepared.ok === false) {
44
+ return prepared.exitCode
51
45
  }
52
46
 
53
- let currentSnapshots: Map<string, BuiltSnapshot>
54
- const skippedWorkspaces: SkippedWorkspace[] = []
55
- let discoveredWorkspaces: string[] = []
56
- try {
57
- currentSnapshots = await computeCurrentSnapshots(cwd, {
58
- allowWorkspaceExtractionFailure: !foundConfig,
59
- onWorkspacesDiscovered: (workspacesRel) => {
60
- discoveredWorkspaces = workspacesRel
61
- },
62
- onWorkspaceSkipped: (skipped) => {
63
- skippedWorkspaces.push(skipped)
64
- }
65
- })
66
- } catch (error: unknown) {
67
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
68
- terminal.write(
69
- 'Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n'
70
- )
71
- return 1
72
- }
73
-
74
- throw error
75
- }
76
- if (!foundConfig) {
47
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared
48
+ if (foundConfig === null && isStatusFormat === false) {
77
49
  writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces)
78
50
  }
79
- writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces)
51
+ if (isStatusFormat === false) {
52
+ writeSkippedWorkspaceSummary(terminal, cwd, foundConfig?.path, skippedWorkspaces)
53
+ }
80
54
  if (storedSnapshots.size === 0) {
81
55
  const summary = summarizeSnapshots(currentSnapshots)
82
56
  terminal.write(
@@ -176,21 +150,3 @@ function printWhatChanged(
176
150
 
177
151
  return 1
178
152
  }
179
-
180
- function isWorkspaceDiscoveryDefaultsError(error: unknown): boolean {
181
- const message = error instanceof Error ? error.message : String(error)
182
- return (
183
- message.includes('Unable to discover workspaces') ||
184
- message.includes('Unmatched workspaces') ||
185
- message.includes('zero-config mode')
186
- )
187
- }
188
-
189
- function writeDiscoveredWorkspacesSummary(terminal: TerminalIO, workspacesRel: string[]): void {
190
- if (workspacesRel.length === 0) {
191
- terminal.subtle('Auto-discovered workspaces: none\n')
192
- return
193
- }
194
-
195
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(', ')}\n`)
196
- }
@@ -1,24 +1,29 @@
1
- import { findConfigPath, loadConfig } from '@eslint-config-snapshot/api'
1
+ import { loadConfig } from '@eslint-config-snapshot/api'
2
2
 
3
3
  import { formatShortConfig, formatShortPrint } from '../formatters.js'
4
- import { writeRunContextHeader } from '../run-context.js'
5
- import { computeCurrentSnapshots, loadStoredSnapshots, resolveWorkspaceAssignments, type WorkspaceAssignments } from '../runtime.js'
4
+ import { resolveWorkspaceAssignments, type WorkspaceAssignments } from '../runtime.js'
6
5
  import { type TerminalIO } from '../terminal.js'
6
+ import { prepareSnapshotExecution } from './snapshot-executor.js'
7
7
 
8
8
  export type PrintFormat = 'json' | 'short'
9
9
 
10
- export async function executePrint(cwd: string, terminal: TerminalIO, snapshotDir: string, format: PrintFormat): Promise<void> {
11
- const foundConfig = await findConfigPath(cwd)
12
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir)
13
- writeRunContextHeader(terminal, cwd, `print:${format}`, foundConfig?.path, storedSnapshots)
14
- if (terminal.showProgress) {
15
- terminal.subtle('🔎 Checking current ESLint configuration...\n')
10
+ export async function executePrint(cwd: string, terminal: TerminalIO, snapshotDir: string, format: PrintFormat): Promise<number> {
11
+ const prepared = await prepareSnapshotExecution({
12
+ cwd,
13
+ snapshotDir,
14
+ terminal,
15
+ commandLabel: `print:${format}`,
16
+ progressMessage: '🔎 Checking current ESLint configuration...\n'
17
+ })
18
+ if (!prepared.ok) {
19
+ return prepared.exitCode
16
20
  }
17
- const currentSnapshots = await computeCurrentSnapshots(cwd)
21
+
22
+ const { currentSnapshots } = prepared
18
23
 
19
24
  if (format === 'short') {
20
25
  terminal.write(formatShortPrint([...currentSnapshots.values()]))
21
- return
26
+ return 0
22
27
  }
23
28
 
24
29
  const output = [...currentSnapshots.values()].map((snapshot) => ({
@@ -26,15 +31,22 @@ export async function executePrint(cwd: string, terminal: TerminalIO, snapshotDi
26
31
  rules: snapshot.rules
27
32
  }))
28
33
  terminal.write(`${JSON.stringify(output, null, 2)}\n`)
34
+ return 0
29
35
  }
30
36
 
31
- export async function executeConfig(cwd: string, terminal: TerminalIO, snapshotDir: string, format: PrintFormat): Promise<void> {
32
- const foundConfig = await findConfigPath(cwd)
33
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir)
34
- writeRunContextHeader(terminal, cwd, `config:${format}`, foundConfig?.path, storedSnapshots)
35
- if (terminal.showProgress) {
36
- terminal.subtle('⚙️ Resolving effective runtime configuration...\n')
37
+ export async function executeConfig(cwd: string, terminal: TerminalIO, snapshotDir: string, format: PrintFormat): Promise<number> {
38
+ const prepared = await prepareSnapshotExecution({
39
+ cwd,
40
+ snapshotDir,
41
+ terminal,
42
+ commandLabel: `config:${format}`,
43
+ progressMessage: '⚙️ Resolving effective runtime configuration...\n'
44
+ })
45
+ if (!prepared.ok) {
46
+ return prepared.exitCode
37
47
  }
48
+
49
+ const { foundConfig } = prepared
38
50
  const config = await loadConfig(cwd)
39
51
  const resolved: WorkspaceAssignments = await resolveWorkspaceAssignments(cwd, config)
40
52
  const payload = {
@@ -51,8 +63,9 @@ export async function executeConfig(cwd: string, terminal: TerminalIO, snapshotD
51
63
 
52
64
  if (format === 'short') {
53
65
  terminal.write(formatShortConfig(payload))
54
- return
66
+ return 0
55
67
  }
56
68
 
57
69
  terminal.write(`${JSON.stringify(payload, null, 2)}\n`)
70
+ return 0
58
71
  }
@@ -3,6 +3,15 @@ import path from 'node:path'
3
3
  import { type SkippedWorkspace } from '../runtime.js'
4
4
  import { type TerminalIO } from '../terminal.js'
5
5
 
6
+ export function writeDiscoveredWorkspacesSummary(terminal: TerminalIO, workspacesRel: string[]): void {
7
+ if (workspacesRel.length === 0) {
8
+ terminal.subtle('Auto-discovered workspaces: none\n')
9
+ return
10
+ }
11
+
12
+ terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(', ')}\n`)
13
+ }
14
+
6
15
  export function writeSkippedWorkspaceSummary(
7
16
  terminal: TerminalIO,
8
17
  cwd: string,
@@ -0,0 +1,97 @@
1
+ import { DEFAULT_CONFIG, findConfigPath, type SnapshotConfig } from '@eslint-config-snapshot/api'
2
+
3
+ import { writeRunContextHeader } from '../run-context.js'
4
+ import { type BuiltSnapshot, computeCurrentSnapshots, loadStoredSnapshots, type SkippedWorkspace, type StoredSnapshot } from '../runtime.js'
5
+ import { type TerminalIO } from '../terminal.js'
6
+
7
+ type SnapshotPreparationSuccess = {
8
+ ok: true
9
+ foundConfig: Awaited<ReturnType<typeof findConfigPath>>
10
+ storedSnapshots: Map<string, StoredSnapshot>
11
+ currentSnapshots: Map<string, BuiltSnapshot>
12
+ discoveredWorkspaces: string[]
13
+ skippedWorkspaces: SkippedWorkspace[]
14
+ }
15
+
16
+ type SnapshotPreparationFailure = {
17
+ ok: false
18
+ exitCode: number
19
+ }
20
+
21
+ export type SnapshotPreparationResult = SnapshotPreparationSuccess | SnapshotPreparationFailure
22
+
23
+ export async function prepareSnapshotExecution(options: {
24
+ cwd: string
25
+ snapshotDir: string
26
+ terminal: TerminalIO
27
+ commandLabel: string
28
+ progressMessage: string
29
+ showContext?: boolean
30
+ }): Promise<SnapshotPreparationResult> {
31
+ const { cwd, snapshotDir, terminal, commandLabel, progressMessage, showContext = true } = options
32
+
33
+ const foundConfig = await findConfigPath(cwd)
34
+ const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir)
35
+ if (showContext) {
36
+ writeRunContextHeader(terminal, cwd, commandLabel, foundConfig?.path, storedSnapshots)
37
+ }
38
+ if (showContext && terminal.showProgress && progressMessage.length > 0) {
39
+ terminal.subtle(progressMessage)
40
+ }
41
+
42
+ if (showContext && !foundConfig) {
43
+ terminal.subtle(
44
+ 'Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n'
45
+ )
46
+ }
47
+
48
+ const skippedWorkspaces: SkippedWorkspace[] = []
49
+ let discoveredWorkspaces: string[] = []
50
+ const allowWorkspaceExtractionFailure = !foundConfig || isDefaultEquivalentConfig(foundConfig.config)
51
+
52
+ let currentSnapshots: Map<string, BuiltSnapshot>
53
+ try {
54
+ currentSnapshots = await computeCurrentSnapshots(cwd, {
55
+ allowWorkspaceExtractionFailure,
56
+ onWorkspacesDiscovered: (workspacesRel) => {
57
+ discoveredWorkspaces = workspacesRel
58
+ },
59
+ onWorkspaceSkipped: (skipped) => {
60
+ skippedWorkspaces.push(skipped)
61
+ }
62
+ })
63
+ } catch (error: unknown) {
64
+ if (allowWorkspaceExtractionFailure && isWorkspaceDiscoveryDefaultsError(error)) {
65
+ if (showContext) {
66
+ terminal.write(
67
+ 'Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n'
68
+ )
69
+ }
70
+ return { ok: false, exitCode: 1 }
71
+ }
72
+
73
+ throw error
74
+ }
75
+
76
+ return {
77
+ ok: true,
78
+ foundConfig,
79
+ storedSnapshots,
80
+ currentSnapshots,
81
+ discoveredWorkspaces,
82
+ skippedWorkspaces
83
+ }
84
+ }
85
+
86
+ export function isWorkspaceDiscoveryDefaultsError(error: unknown): boolean {
87
+ const message = error instanceof Error ? error.message : String(error)
88
+ return (
89
+ message.includes('Unable to discover workspaces') ||
90
+ message.includes('Unmatched workspaces') ||
91
+ message.includes('zero-config mode')
92
+ )
93
+ }
94
+
95
+ function isDefaultEquivalentConfig(config: SnapshotConfig): boolean {
96
+ return JSON.stringify(config) === JSON.stringify(DEFAULT_CONFIG)
97
+ }
@@ -1,48 +1,23 @@
1
- import { findConfigPath } from '@eslint-config-snapshot/api'
2
-
3
1
  import { countUniqueWorkspaces, formatBaselineSummaryLines, summarizeSnapshots } from '../formatters.js'
4
- import { writeEslintVersionSummary, writeRunContextHeader } from '../run-context.js'
5
- import { computeCurrentSnapshots, loadStoredSnapshots, resolveGroupEslintVersions, type SkippedWorkspace, writeSnapshots } from '../runtime.js'
2
+ import { writeEslintVersionSummary } from '../run-context.js'
3
+ import { resolveGroupEslintVersions, writeSnapshots } from '../runtime.js'
6
4
  import { type TerminalIO } from '../terminal.js'
7
- import { writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
5
+ import { writeDiscoveredWorkspacesSummary, writeSkippedWorkspaceSummary } from './skipped-workspaces.js'
6
+ import { prepareSnapshotExecution } from './snapshot-executor.js'
8
7
 
9
8
  export async function executeUpdate(cwd: string, terminal: TerminalIO, snapshotDir: string, printSummary: boolean): Promise<number> {
10
- const foundConfig = await findConfigPath(cwd)
11
- const storedSnapshots = await loadStoredSnapshots(cwd, snapshotDir)
12
- writeRunContextHeader(terminal, cwd, 'update', foundConfig?.path, storedSnapshots)
13
- if (terminal.showProgress) {
14
- terminal.subtle('🔎 Checking current ESLint configuration...\n')
15
- }
16
-
17
- if (!foundConfig) {
18
- terminal.subtle(
19
- 'Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n'
20
- )
9
+ const prepared = await prepareSnapshotExecution({
10
+ cwd,
11
+ snapshotDir,
12
+ terminal,
13
+ commandLabel: 'update',
14
+ progressMessage: '🔎 Checking current ESLint configuration...\n'
15
+ })
16
+ if (prepared.ok === false) {
17
+ return prepared.exitCode
21
18
  }
22
19
 
23
- let currentSnapshots
24
- const skippedWorkspaces: SkippedWorkspace[] = []
25
- let discoveredWorkspaces: string[] = []
26
- try {
27
- currentSnapshots = await computeCurrentSnapshots(cwd, {
28
- allowWorkspaceExtractionFailure: !foundConfig,
29
- onWorkspacesDiscovered: (workspacesRel) => {
30
- discoveredWorkspaces = workspacesRel
31
- },
32
- onWorkspaceSkipped: (skipped) => {
33
- skippedWorkspaces.push(skipped)
34
- }
35
- })
36
- } catch (error: unknown) {
37
- if (!foundConfig && isWorkspaceDiscoveryDefaultsError(error)) {
38
- terminal.write(
39
- 'Automatic workspace discovery could not complete with defaults.\nRun `eslint-config-snapshot init` to configure workspaces, then run `eslint-config-snapshot --update`.\n'
40
- )
41
- return 1
42
- }
43
-
44
- throw error
45
- }
20
+ const { foundConfig, storedSnapshots, currentSnapshots, discoveredWorkspaces, skippedWorkspaces } = prepared
46
21
  if (!foundConfig) {
47
22
  writeDiscoveredWorkspacesSummary(terminal, discoveredWorkspaces)
48
23
  }
@@ -62,21 +37,3 @@ export async function executeUpdate(cwd: string, terminal: TerminalIO, snapshotD
62
37
 
63
38
  return 0
64
39
  }
65
-
66
- function isWorkspaceDiscoveryDefaultsError(error: unknown): boolean {
67
- const message = error instanceof Error ? error.message : String(error)
68
- return (
69
- message.includes('Unable to discover workspaces') ||
70
- message.includes('Unmatched workspaces') ||
71
- message.includes('zero-config mode')
72
- )
73
- }
74
-
75
- function writeDiscoveredWorkspacesSummary(terminal: TerminalIO, workspacesRel: string[]): void {
76
- if (workspacesRel.length === 0) {
77
- terminal.subtle('Auto-discovered workspaces: none\n')
78
- return
79
- }
80
-
81
- terminal.subtle(`Auto-discovered workspaces (${workspacesRel.length}): ${workspacesRel.join(', ')}\n`)
82
- }
package/src/index.ts CHANGED
@@ -141,8 +141,7 @@ function createProgram(cwd: string, terminal: TerminalIO, onActionExit: (code: n
141
141
  .option('--short', 'Alias for --format short')
142
142
  .action(async (opts: { format: PrintFormat; short?: boolean }) => {
143
143
  const format: PrintFormat = opts.short ? 'short' : opts.format
144
- await executePrint(cwd, terminal, SNAPSHOT_DIR, format)
145
- onActionExit(0)
144
+ onActionExit(await executePrint(cwd, terminal, SNAPSHOT_DIR, format))
146
145
  })
147
146
 
148
147
  program
@@ -152,8 +151,7 @@ function createProgram(cwd: string, terminal: TerminalIO, onActionExit: (code: n
152
151
  .option('--short', 'Alias for --format short')
153
152
  .action(async (opts: { format: PrintFormat; short?: boolean }) => {
154
153
  const format: PrintFormat = opts.short ? 'short' : opts.format
155
- await executeConfig(cwd, terminal, SNAPSHOT_DIR, format)
156
- onActionExit(0)
154
+ onActionExit(await executeConfig(cwd, terminal, SNAPSHOT_DIR, format))
157
155
  })
158
156
 
159
157
  program
@@ -142,6 +142,36 @@ describe.sequential('cli integration', () => {
142
142
  })
143
143
  })
144
144
 
145
+ it('update treats explicit empty package config as zero-config tolerant mode', async () => {
146
+ await rm(path.join(fixtureRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
147
+ const packageJsonPath = path.join(fixtureRoot, 'package.json')
148
+ const packageJsonRaw = await readFile(packageJsonPath, 'utf8')
149
+ const packageJson = JSON.parse(packageJsonRaw) as Record<string, unknown>
150
+ packageJson['eslint-config-snapshot'] = {}
151
+ await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`)
152
+
153
+ await writeFile(
154
+ path.join(fixtureRoot, 'packages/ws-b/node_modules/eslint/bin/eslint.js'),
155
+ "console.error('Failed to load config \"next/core-web-vitals\" to extend from.'); process.exit(1)\n"
156
+ )
157
+
158
+ const code = await runCli('update', fixtureRoot)
159
+ expect(code).toBe(0)
160
+
161
+ const snapshotRaw = await readFile(path.join(fixtureRoot, '.eslint-config-snapshot/default.json'), 'utf8')
162
+ const snapshot = JSON.parse(snapshotRaw)
163
+
164
+ expect(snapshot).toEqual({
165
+ formatVersion: 1,
166
+ groupId: 'default',
167
+ workspaces: ['packages/ws-a'],
168
+ rules: {
169
+ eqeqeq: ['error', 'always'],
170
+ 'no-console': ['warn']
171
+ }
172
+ })
173
+ })
174
+
145
175
  it('status is minimal and exits 0 when clean', async () => {
146
176
  await runCli('snapshot', fixtureRoot)
147
177