@eslint-config-snapshot/cli 0.4.0 → 0.6.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/dist/index.js CHANGED
@@ -14,44 +14,59 @@ import {
14
14
  loadConfig,
15
15
  normalizePath,
16
16
  readSnapshotFile,
17
+ resolveEslintVersionForWorkspace,
17
18
  sampleWorkspaceFiles,
18
19
  writeSnapshotFile
19
20
  } from "@eslint-config-snapshot/api";
20
21
  import { Command, CommanderError, InvalidArgumentError } from "commander";
21
22
  import fg from "fast-glob";
23
+ import { existsSync, readFileSync } from "fs";
22
24
  import { access, mkdir, readFile, writeFile } from "fs/promises";
23
25
  import path from "path";
24
26
  import { createInterface } from "readline";
25
27
  var SNAPSHOT_DIR = ".eslint-config-snapshot";
26
28
  var UPDATE_HINT = "Tip: when you intentionally accept changes, run `eslint-config-snapshot --update` to refresh the baseline.\n";
29
+ var activeRunTimer;
30
+ var cachedCliVersion;
27
31
  async function runCli(command, cwd, flags = []) {
28
32
  const argv = command ? [command, ...flags] : [...flags];
29
33
  return runArgv(argv, cwd);
30
34
  }
31
35
  async function runArgv(argv, cwd) {
32
- const hasCommandToken = argv.some((token) => !token.startsWith("-"));
33
- if (!hasCommandToken) {
34
- return runDefaultInvocation(argv, cwd);
35
- }
36
- let actionCode;
37
- const program = createProgram(cwd, (code) => {
38
- actionCode = code;
39
- });
36
+ const invocationLabel = resolveInvocationLabel(argv);
37
+ beginRunTimer(invocationLabel);
38
+ let exitCode = 1;
40
39
  try {
41
- await program.parseAsync(argv, { from: "user" });
42
- } catch (error) {
43
- if (error instanceof CommanderError) {
44
- if (error.code === "commander.helpDisplayed") {
45
- return 0;
46
- }
47
- return error.exitCode;
40
+ const hasCommandToken = argv.some((token) => !token.startsWith("-"));
41
+ if (!hasCommandToken) {
42
+ exitCode = await runDefaultInvocation(argv, cwd);
43
+ return exitCode;
48
44
  }
49
- const message = error instanceof Error ? error.message : String(error);
50
- process.stderr.write(`${message}
45
+ let actionCode;
46
+ const program = createProgram(cwd, (code) => {
47
+ actionCode = code;
48
+ });
49
+ try {
50
+ await program.parseAsync(argv, { from: "user" });
51
+ } catch (error) {
52
+ if (error instanceof CommanderError) {
53
+ if (error.code === "commander.helpDisplayed") {
54
+ exitCode = 0;
55
+ return exitCode;
56
+ }
57
+ exitCode = error.exitCode;
58
+ return exitCode;
59
+ }
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ process.stderr.write(`${message}
51
62
  `);
52
- return 1;
63
+ return 1;
64
+ }
65
+ exitCode = actionCode ?? 0;
66
+ return exitCode;
67
+ } finally {
68
+ endRunTimer(exitCode);
53
69
  }
54
- return actionCode ?? 0;
55
70
  }
56
71
  async function runDefaultInvocation(argv, cwd) {
57
72
  const known = /* @__PURE__ */ new Set(["-u", "--update", "-h", "--help"]);
@@ -157,6 +172,13 @@ function parseInitPreset(value) {
157
172
  }
158
173
  async function executeCheck(cwd, format, defaultInvocation = false) {
159
174
  const foundConfig = await findConfigPath(cwd);
175
+ const storedSnapshots = await loadStoredSnapshots(cwd);
176
+ if (format !== "status") {
177
+ writeRunContextHeader(cwd, defaultInvocation ? "check" : `check:${format}`, foundConfig?.path, storedSnapshots);
178
+ if (shouldShowRunLogs()) {
179
+ writeSubtleInfo("\u{1F50E} Checking current ESLint configuration...\n");
180
+ }
181
+ }
160
182
  if (!foundConfig) {
161
183
  writeSubtleInfo(
162
184
  "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
@@ -174,7 +196,6 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
174
196
  }
175
197
  throw error;
176
198
  }
177
- const storedSnapshots = await loadStoredSnapshots(cwd);
178
199
  if (storedSnapshots.size === 0) {
179
200
  const summary = summarizeSnapshots(currentSnapshots);
180
201
  process.stdout.write(
@@ -201,6 +222,7 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
201
222
  return 1;
202
223
  }
203
224
  const changes = compareSnapshotMaps(storedSnapshots, currentSnapshots);
225
+ const eslintVersionsByGroup = shouldShowRunLogs() ? await resolveGroupEslintVersions(cwd) : /* @__PURE__ */ new Map();
204
226
  if (format === "status") {
205
227
  if (changes.length === 0) {
206
228
  process.stdout.write("clean\n");
@@ -213,6 +235,7 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
213
235
  if (format === "diff") {
214
236
  if (changes.length === 0) {
215
237
  process.stdout.write("Great news: no snapshot changes detected.\n");
238
+ writeEslintVersionSummary(eslintVersionsByGroup);
216
239
  return 0;
217
240
  }
218
241
  for (const change of changes) {
@@ -222,10 +245,15 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
222
245
  writeSubtleInfo(UPDATE_HINT);
223
246
  return 1;
224
247
  }
225
- return printWhatChanged(changes, currentSnapshots);
248
+ return printWhatChanged(changes, currentSnapshots, eslintVersionsByGroup);
226
249
  }
227
250
  async function executeUpdate(cwd, printSummary) {
228
251
  const foundConfig = await findConfigPath(cwd);
252
+ const storedSnapshots = await loadStoredSnapshots(cwd);
253
+ writeRunContextHeader(cwd, "update", foundConfig?.path, storedSnapshots);
254
+ if (shouldShowRunLogs()) {
255
+ writeSubtleInfo("\u{1F50E} Checking current ESLint configuration...\n");
256
+ }
229
257
  if (!foundConfig) {
230
258
  writeSubtleInfo(
231
259
  "Tip: no explicit config found. Using safe built-in defaults. Run `eslint-config-snapshot init` to customize when needed.\n"
@@ -247,16 +275,24 @@ async function executeUpdate(cwd, printSummary) {
247
275
  if (printSummary) {
248
276
  const summary = summarizeSnapshots(currentSnapshots);
249
277
  const color = createColorizer();
278
+ const eslintVersionsByGroup = shouldShowRunLogs() ? await resolveGroupEslintVersions(cwd) : /* @__PURE__ */ new Map();
250
279
  writeSectionTitle("Summary", color);
251
280
  process.stdout.write(
252
281
  `Baseline updated: ${summary.groups} groups, ${summary.rules} rules.
253
282
  Severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off.
254
283
  `
255
284
  );
285
+ writeEslintVersionSummary(eslintVersionsByGroup);
256
286
  }
257
287
  return 0;
258
288
  }
259
289
  async function executePrint(cwd, format) {
290
+ const foundConfig = await findConfigPath(cwd);
291
+ const storedSnapshots = await loadStoredSnapshots(cwd);
292
+ writeRunContextHeader(cwd, `print:${format}`, foundConfig?.path, storedSnapshots);
293
+ if (shouldShowRunLogs()) {
294
+ writeSubtleInfo("\u{1F50E} Checking current ESLint configuration...\n");
295
+ }
260
296
  const currentSnapshots = await computeCurrentSnapshots(cwd);
261
297
  if (format === "short") {
262
298
  process.stdout.write(formatShortPrint([...currentSnapshots.values()]));
@@ -271,6 +307,11 @@ async function executePrint(cwd, format) {
271
307
  }
272
308
  async function executeConfig(cwd, format) {
273
309
  const foundConfig = await findConfigPath(cwd);
310
+ const storedSnapshots = await loadStoredSnapshots(cwd);
311
+ writeRunContextHeader(cwd, `config:${format}`, foundConfig?.path, storedSnapshots);
312
+ if (shouldShowRunLogs()) {
313
+ writeSubtleInfo("\u{1F50E} Resolving effective runtime configuration...\n");
314
+ }
274
315
  const config = await loadConfig(cwd);
275
316
  const resolved = await resolveWorkspaceAssignments(cwd, config);
276
317
  const payload = {
@@ -456,8 +497,8 @@ ${JSON.stringify(configObject, null, 2)}
456
497
  }
457
498
  async function askInitPreferences() {
458
499
  const { select } = await import("@inquirer/prompts");
459
- const target = await askInitTarget(select);
460
- const preset = await askInitPreset(select);
500
+ const target = await runPromptWithPausedTimer(() => askInitTarget(select));
501
+ const preset = await runPromptWithPausedTimer(() => askInitPreset(select));
461
502
  return { target, preset };
462
503
  }
463
504
  async function askInitTarget(selectPrompt) {
@@ -480,8 +521,10 @@ async function askInitPreset(selectPrompt) {
480
521
  });
481
522
  }
482
523
  function askQuestion(rl, prompt) {
524
+ pauseRunTimer();
483
525
  return new Promise((resolve) => {
484
526
  rl.question(prompt, (answer) => {
527
+ resumeRunTimer();
485
528
  resolve(answer);
486
529
  });
487
530
  });
@@ -627,11 +670,13 @@ async function askRecommendedGroupAssignments(workspaces) {
627
670
  'Recommended setup: default group "*" is a dynamic catch-all for every discovered workspace.\n'
628
671
  );
629
672
  process.stdout.write("Select only workspaces that should move to explicit static groups.\n");
630
- const overrides = await checkbox({
631
- message: 'Choose exception workspaces (leave empty to keep all in default "*"):',
632
- choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
633
- pageSize: Math.min(12, Math.max(4, workspaces.length))
634
- });
673
+ const overrides = await runPromptWithPausedTimer(
674
+ () => checkbox({
675
+ message: 'Choose exception workspaces (leave empty to keep all in default "*"):',
676
+ choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
677
+ pageSize: Math.min(12, Math.max(4, workspaces.length))
678
+ })
679
+ );
635
680
  const assignments = /* @__PURE__ */ new Map();
636
681
  let nextGroup = 1;
637
682
  for (const workspace of overrides) {
@@ -639,13 +684,15 @@ async function askRecommendedGroupAssignments(workspaces) {
639
684
  while (usedGroups.includes(nextGroup)) {
640
685
  nextGroup += 1;
641
686
  }
642
- const selected = await select({
643
- message: `Select group for ${workspace}`,
644
- choices: [
645
- ...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
646
- { name: `create new group (group-${nextGroup})`, value: "new" }
647
- ]
648
- });
687
+ const selected = await runPromptWithPausedTimer(
688
+ () => select({
689
+ message: `Select group for ${workspace}`,
690
+ choices: [
691
+ ...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
692
+ { name: `create new group (group-${nextGroup})`, value: "new" }
693
+ ]
694
+ })
695
+ );
649
696
  const groupNumber = selected === "new" ? nextGroup : selected;
650
697
  assignments.set(workspace, groupNumber);
651
698
  }
@@ -688,7 +735,7 @@ function isDirectCliExecution() {
688
735
  if (isDirectCliExecution()) {
689
736
  void main();
690
737
  }
691
- function printWhatChanged(changes, currentSnapshots) {
738
+ function printWhatChanged(changes, currentSnapshots, eslintVersionsByGroup) {
692
739
  const color = createColorizer();
693
740
  const currentSummary = summarizeSnapshots(currentSnapshots);
694
741
  const changeSummary = summarizeChanges(changes);
@@ -700,6 +747,7 @@ function printWhatChanged(changes, currentSnapshots) {
700
747
  - severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
701
748
  `
702
749
  );
750
+ writeEslintVersionSummary(eslintVersionsByGroup);
703
751
  return 0;
704
752
  }
705
753
  process.stdout.write(color.red("Heads up: snapshot drift detected.\n"));
@@ -716,6 +764,8 @@ function printWhatChanged(changes, currentSnapshots) {
716
764
 
717
765
  `
718
766
  );
767
+ writeEslintVersionSummary(eslintVersionsByGroup);
768
+ process.stdout.write("\n");
719
769
  writeSectionTitle("Changes", color);
720
770
  for (const change of changes) {
721
771
  process.stdout.write(color.bold(`group ${change.groupId}
@@ -751,22 +801,7 @@ function summarizeChanges(changes) {
751
801
  return { introduced, removed, severity, options, workspace };
752
802
  }
753
803
  function summarizeSnapshots(snapshots) {
754
- let rules = 0;
755
- let error = 0;
756
- let warn = 0;
757
- let off = 0;
758
- for (const snapshot of snapshots.values()) {
759
- for (const entry of Object.values(snapshot.rules)) {
760
- rules += 1;
761
- if (entry[0] === "error") {
762
- error += 1;
763
- } else if (entry[0] === "warn") {
764
- warn += 1;
765
- } else {
766
- off += 1;
767
- }
768
- }
769
- }
804
+ const { rules, error, warn, off } = countRuleSeverities([...snapshots.values()].map((snapshot) => snapshot.rules));
770
805
  return { groups: snapshots.size, rules, error, warn, off };
771
806
  }
772
807
  function decorateDiffLine(line, color) {
@@ -796,6 +831,203 @@ function writeSubtleInfo(text) {
796
831
  const color = createColorizer();
797
832
  process.stdout.write(color.dim(text));
798
833
  }
834
+ function resolveInvocationLabel(argv) {
835
+ const commandToken = argv.find((entry) => !entry.startsWith("-"));
836
+ if (commandToken) {
837
+ return commandToken;
838
+ }
839
+ if (argv.includes("-u") || argv.includes("--update")) {
840
+ return "update";
841
+ }
842
+ if (argv.includes("-h") || argv.includes("--help")) {
843
+ return "help";
844
+ }
845
+ return "check";
846
+ }
847
+ function shouldShowRunLogs() {
848
+ if (process.env.ESLINT_CONFIG_SNAPSHOT_NO_PROGRESS === "1") {
849
+ return false;
850
+ }
851
+ return process.stdout.isTTY === true;
852
+ }
853
+ function beginRunTimer(label) {
854
+ if (!shouldShowRunLogs()) {
855
+ activeRunTimer = void 0;
856
+ return;
857
+ }
858
+ activeRunTimer = {
859
+ label,
860
+ startedAtMs: Date.now(),
861
+ pausedMs: 0,
862
+ pauseStartedAtMs: void 0
863
+ };
864
+ }
865
+ function endRunTimer(exitCode) {
866
+ if (!activeRunTimer || !shouldShowRunLogs()) {
867
+ return;
868
+ }
869
+ if (activeRunTimer.pauseStartedAtMs !== void 0) {
870
+ activeRunTimer.pausedMs += Date.now() - activeRunTimer.pauseStartedAtMs;
871
+ activeRunTimer.pauseStartedAtMs = void 0;
872
+ }
873
+ const elapsedMs = Math.max(0, Date.now() - activeRunTimer.startedAtMs - activeRunTimer.pausedMs);
874
+ const color = createColorizer();
875
+ const seconds = (elapsedMs / 1e3).toFixed(2);
876
+ if (exitCode === 0) {
877
+ writeSubtleInfo(`${color.green("\u2705")} Finished in ${seconds}s
878
+ `);
879
+ } else {
880
+ writeSubtleInfo(`${color.red("\u274C")} Finished in ${seconds}s
881
+ `);
882
+ }
883
+ activeRunTimer = void 0;
884
+ }
885
+ function pauseRunTimer() {
886
+ if (!activeRunTimer || activeRunTimer.pauseStartedAtMs !== void 0) {
887
+ return;
888
+ }
889
+ activeRunTimer.pauseStartedAtMs = Date.now();
890
+ }
891
+ function resumeRunTimer() {
892
+ if (!activeRunTimer || activeRunTimer.pauseStartedAtMs === void 0) {
893
+ return;
894
+ }
895
+ activeRunTimer.pausedMs += Date.now() - activeRunTimer.pauseStartedAtMs;
896
+ activeRunTimer.pauseStartedAtMs = void 0;
897
+ }
898
+ async function runPromptWithPausedTimer(prompt) {
899
+ pauseRunTimer();
900
+ try {
901
+ return await prompt();
902
+ } finally {
903
+ resumeRunTimer();
904
+ }
905
+ }
906
+ function readCliVersion() {
907
+ if (cachedCliVersion !== void 0) {
908
+ return cachedCliVersion;
909
+ }
910
+ const scriptPath = process.argv[1];
911
+ if (!scriptPath) {
912
+ cachedCliVersion = "unknown";
913
+ return cachedCliVersion;
914
+ }
915
+ let current = path.resolve(path.dirname(scriptPath));
916
+ while (true) {
917
+ const packageJsonPath = path.join(current, "package.json");
918
+ if (existsSync(packageJsonPath)) {
919
+ try {
920
+ const raw = readFileSync(packageJsonPath, "utf8");
921
+ const parsed = JSON.parse(raw);
922
+ cachedCliVersion = parsed.version ?? "unknown";
923
+ return cachedCliVersion;
924
+ } catch {
925
+ break;
926
+ }
927
+ }
928
+ const parent = path.dirname(current);
929
+ if (parent === current) {
930
+ break;
931
+ }
932
+ current = parent;
933
+ }
934
+ cachedCliVersion = "unknown";
935
+ return cachedCliVersion;
936
+ }
937
+ function writeRunContextHeader(cwd, commandLabel, configPath, storedSnapshots) {
938
+ if (!shouldShowRunLogs()) {
939
+ return;
940
+ }
941
+ const color = createColorizer();
942
+ process.stdout.write(color.bold(`\u2728 eslint-config-snapshot v${readCliVersion()}
943
+ `));
944
+ process.stdout.write(`\u{1F9ED} Command: ${commandLabel}
945
+ `);
946
+ process.stdout.write(`\u{1F4C1} Repository: ${cwd}
947
+ `);
948
+ process.stdout.write(`\u2699\uFE0F Config source: ${formatConfigSource(cwd, configPath)}
949
+ `);
950
+ process.stdout.write(`\u{1F5C2}\uFE0F Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
951
+
952
+ `);
953
+ }
954
+ function formatConfigSource(cwd, configPath) {
955
+ if (!configPath) {
956
+ return "built-in defaults";
957
+ }
958
+ const rel = normalizePath(path.relative(cwd, configPath));
959
+ if (path.basename(configPath) === "package.json") {
960
+ return `${rel} (eslint-config-snapshot field)`;
961
+ }
962
+ return rel;
963
+ }
964
+ function formatStoredSnapshotSummary(storedSnapshots) {
965
+ if (storedSnapshots.size === 0) {
966
+ return "none";
967
+ }
968
+ const summary = summarizeStoredSnapshots(storedSnapshots);
969
+ return `${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off)`;
970
+ }
971
+ async function resolveGroupEslintVersions(cwd) {
972
+ const config = await loadConfig(cwd);
973
+ const { discovery, assignments } = await resolveWorkspaceAssignments(cwd, config);
974
+ const result = /* @__PURE__ */ new Map();
975
+ for (const group of assignments) {
976
+ const versions = /* @__PURE__ */ new Set();
977
+ for (const workspaceRel of group.workspaces) {
978
+ const workspaceAbs = path.resolve(discovery.rootAbs, workspaceRel);
979
+ versions.add(resolveEslintVersionForWorkspace(workspaceAbs));
980
+ }
981
+ result.set(group.name, [...versions].sort((a, b) => a.localeCompare(b)));
982
+ }
983
+ return result;
984
+ }
985
+ function writeEslintVersionSummary(eslintVersionsByGroup) {
986
+ if (!shouldShowRunLogs() || eslintVersionsByGroup.size === 0) {
987
+ return;
988
+ }
989
+ const allVersions = /* @__PURE__ */ new Set();
990
+ for (const versions of eslintVersionsByGroup.values()) {
991
+ for (const version of versions) {
992
+ allVersions.add(version);
993
+ }
994
+ }
995
+ const sortedAllVersions = [...allVersions].sort((a, b) => a.localeCompare(b));
996
+ if (sortedAllVersions.length === 1) {
997
+ process.stdout.write(`- eslint runtime: ${sortedAllVersions[0]} (all groups)
998
+ `);
999
+ return;
1000
+ }
1001
+ process.stdout.write("- eslint runtime by group:\n");
1002
+ const sortedEntries = [...eslintVersionsByGroup.entries()].sort((a, b) => a[0].localeCompare(b[0]));
1003
+ for (const [groupName, versions] of sortedEntries) {
1004
+ process.stdout.write(` - ${groupName}: ${versions.join(", ")}
1005
+ `);
1006
+ }
1007
+ }
1008
+ function summarizeStoredSnapshots(snapshots) {
1009
+ const { rules, error, warn, off } = countRuleSeverities([...snapshots.values()].map((snapshot) => snapshot.rules));
1010
+ return { groups: snapshots.size, rules, error, warn, off };
1011
+ }
1012
+ function countRuleSeverities(ruleObjects) {
1013
+ let rules = 0;
1014
+ let error = 0;
1015
+ let warn = 0;
1016
+ let off = 0;
1017
+ for (const rulesObject of ruleObjects) {
1018
+ for (const entry of Object.values(rulesObject)) {
1019
+ rules += 1;
1020
+ if (entry[0] === "error") {
1021
+ error += 1;
1022
+ } else if (entry[0] === "warn") {
1023
+ warn += 1;
1024
+ } else {
1025
+ off += 1;
1026
+ }
1027
+ }
1028
+ }
1029
+ return { rules, error, warn, off };
1030
+ }
799
1031
  function formatShortPrint(snapshots) {
800
1032
  const lines = [];
801
1033
  const sorted = [...snapshots].sort((a, b) => a.groupId.localeCompare(b.groupId));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,6 +30,6 @@
30
30
  "@inquirer/prompts": "^8.2.0",
31
31
  "commander": "^14.0.3",
32
32
  "fast-glob": "^3.3.3",
33
- "@eslint-config-snapshot/api": "0.4.0"
33
+ "@eslint-config-snapshot/api": "0.6.0"
34
34
  }
35
35
  }