@eslint-config-snapshot/cli 0.6.0 → 0.9.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,38 @@
1
1
  # @eslint-config-snapshot/cli
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Release minor version with robust CLI version resolution and improved runtime log UX.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @eslint-config-snapshot/api@0.9.0
13
+
14
+ ## 0.8.0
15
+
16
+ ### Minor Changes
17
+
18
+ - Release minor version with improved human-readable CLI runtime logs and consistent output spacing.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @eslint-config-snapshot/api@0.8.0
24
+
25
+ ## 0.7.0
26
+
27
+ ### Minor Changes
28
+
29
+ - Release minor version after improving runtime command header messaging and UX consistency.
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies
34
+ - @eslint-config-snapshot/api@0.7.0
35
+
3
36
  ## 0.6.0
4
37
 
5
38
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ var import_commander = require("commander");
41
41
  var import_fast_glob = __toESM(require("fast-glob"), 1);
42
42
  var import_node_fs = require("fs");
43
43
  var import_promises = require("fs/promises");
44
+ var import_node_module = require("module");
44
45
  var import_node_path = __toESM(require("path"), 1);
45
46
  var import_node_readline = require("readline");
46
47
  var SNAPSHOT_DIR = ".eslint-config-snapshot";
@@ -218,13 +219,13 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
218
219
  if (storedSnapshots.size === 0) {
219
220
  const summary = summarizeSnapshots(currentSnapshots);
220
221
  process.stdout.write(
221
- `Current rule state: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).
222
+ `Rules found in this analysis: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).
222
223
  `
223
224
  );
224
225
  const canPromptBaseline = defaultInvocation || format === "summary";
225
226
  if (canPromptBaseline && process.stdin.isTTY && process.stdout.isTTY) {
226
227
  const shouldCreateBaseline = await askYesNo(
227
- "No baseline yet. Use current rule state as your baseline now? [Y/n] ",
228
+ "No baseline yet. Do you want to save this analyzed rule state as your baseline now? [Y/n] ",
228
229
  true
229
230
  );
230
231
  if (shouldCreateBaseline) {
@@ -329,7 +330,7 @@ async function executeConfig(cwd, format) {
329
330
  const storedSnapshots = await loadStoredSnapshots(cwd);
330
331
  writeRunContextHeader(cwd, `config:${format}`, foundConfig?.path, storedSnapshots);
331
332
  if (shouldShowRunLogs()) {
332
- writeSubtleInfo("\u{1F50E} Resolving effective runtime configuration...\n");
333
+ writeSubtleInfo("\u2699\uFE0F Resolving effective runtime configuration...\n");
333
334
  }
334
335
  const config = await (0, import_api.loadConfig)(cwd);
335
336
  const resolved = await resolveWorkspaceAssignments(cwd, config);
@@ -780,7 +781,6 @@ function printWhatChanged(changes, currentSnapshots, eslintVersionsByGroup) {
780
781
  - workspace membership changes: ${changeSummary.workspace}
781
782
  - current baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
782
783
  - current severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
783
-
784
784
  `
785
785
  );
786
786
  writeEslintVersionSummary(eslintVersionsByGroup);
@@ -890,13 +890,12 @@ function endRunTimer(exitCode) {
890
890
  activeRunTimer.pauseStartedAtMs = void 0;
891
891
  }
892
892
  const elapsedMs = Math.max(0, Date.now() - activeRunTimer.startedAtMs - activeRunTimer.pausedMs);
893
- const color = createColorizer();
894
893
  const seconds = (elapsedMs / 1e3).toFixed(2);
895
894
  if (exitCode === 0) {
896
- writeSubtleInfo(`${color.green("\u2705")} Finished in ${seconds}s
895
+ writeSubtleInfo(`Finished in ${seconds}s
897
896
  `);
898
897
  } else {
899
- writeSubtleInfo(`${color.red("\u274C")} Finished in ${seconds}s
898
+ writeSubtleInfo(`Finished with errors in ${seconds}s
900
899
  `);
901
900
  }
902
901
  activeRunTimer = void 0;
@@ -926,22 +925,45 @@ function readCliVersion() {
926
925
  if (cachedCliVersion !== void 0) {
927
926
  return cachedCliVersion;
928
927
  }
928
+ const envPackageName = process.env.npm_package_name;
929
+ const envPackageVersion = process.env.npm_package_version;
930
+ if (isCliPackageName(envPackageName) && typeof envPackageVersion === "string" && envPackageVersion.length > 0) {
931
+ cachedCliVersion = envPackageVersion;
932
+ return cachedCliVersion;
933
+ }
929
934
  const scriptPath = process.argv[1];
930
935
  if (!scriptPath) {
931
936
  cachedCliVersion = "unknown";
932
937
  return cachedCliVersion;
933
938
  }
939
+ try {
940
+ const req = (0, import_node_module.createRequire)(import_node_path.default.resolve(scriptPath));
941
+ const resolvedCliEntry = req.resolve("@eslint-config-snapshot/cli");
942
+ const resolvedVersion = readVersionFromResolvedEntry(resolvedCliEntry);
943
+ if (resolvedVersion !== void 0) {
944
+ cachedCliVersion = resolvedVersion;
945
+ return cachedCliVersion;
946
+ }
947
+ } catch {
948
+ }
934
949
  let current = import_node_path.default.resolve(import_node_path.default.dirname(scriptPath));
950
+ let fallbackVersion;
935
951
  while (true) {
936
952
  const packageJsonPath = import_node_path.default.join(current, "package.json");
937
953
  if ((0, import_node_fs.existsSync)(packageJsonPath)) {
938
954
  try {
939
955
  const raw = (0, import_node_fs.readFileSync)(packageJsonPath, "utf8");
940
956
  const parsed = JSON.parse(raw);
941
- cachedCliVersion = parsed.version ?? "unknown";
942
- return cachedCliVersion;
957
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
958
+ if (isCliPackageName(parsed.name)) {
959
+ cachedCliVersion = parsed.version;
960
+ return cachedCliVersion;
961
+ }
962
+ if (fallbackVersion === void 0) {
963
+ fallbackVersion = parsed.version;
964
+ }
965
+ }
943
966
  } catch {
944
- break;
945
967
  }
946
968
  }
947
969
  const parent = import_node_path.default.dirname(current);
@@ -950,25 +972,86 @@ function readCliVersion() {
950
972
  }
951
973
  current = parent;
952
974
  }
953
- cachedCliVersion = "unknown";
975
+ cachedCliVersion = fallbackVersion ?? "unknown";
954
976
  return cachedCliVersion;
955
977
  }
978
+ function isCliPackageName(value) {
979
+ return value === "@eslint-config-snapshot/cli" || value === "eslint-config-snapshot";
980
+ }
981
+ function readVersionFromResolvedEntry(entryAbs) {
982
+ let current = import_node_path.default.resolve(import_node_path.default.dirname(entryAbs));
983
+ while (true) {
984
+ const packageJsonPath = import_node_path.default.join(current, "package.json");
985
+ if ((0, import_node_fs.existsSync)(packageJsonPath)) {
986
+ try {
987
+ const raw = (0, import_node_fs.readFileSync)(packageJsonPath, "utf8");
988
+ const parsed = JSON.parse(raw);
989
+ if (isCliPackageName(parsed.name) && typeof parsed.version === "string" && parsed.version.length > 0) {
990
+ return parsed.version;
991
+ }
992
+ } catch {
993
+ }
994
+ }
995
+ const parent = import_node_path.default.dirname(current);
996
+ if (parent === current) {
997
+ break;
998
+ }
999
+ current = parent;
1000
+ }
1001
+ return void 0;
1002
+ }
956
1003
  function writeRunContextHeader(cwd, commandLabel, configPath, storedSnapshots) {
957
1004
  if (!shouldShowRunLogs()) {
958
1005
  return;
959
1006
  }
960
1007
  const color = createColorizer();
961
- process.stdout.write(color.bold(`\u2728 eslint-config-snapshot v${readCliVersion()}
1008
+ process.stdout.write(color.bold(`eslint-config-snapshot v${readCliVersion()} \u2022 ${formatCommandDisplayLabel(commandLabel)}
962
1009
  `));
963
- process.stdout.write(`\u{1F9ED} Command: ${commandLabel}
964
- `);
965
1010
  process.stdout.write(`\u{1F4C1} Repository: ${cwd}
966
1011
  `);
967
- process.stdout.write(`\u2699\uFE0F Config source: ${formatConfigSource(cwd, configPath)}
1012
+ process.stdout.write(`\u{1F4C1} Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
968
1013
  `);
969
- process.stdout.write(`\u{1F5C2}\uFE0F Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
970
-
1014
+ process.stdout.write(`\u2699\uFE0F Config source: ${formatConfigSource(cwd, configPath)}
971
1015
  `);
1016
+ process.stdout.write("\n");
1017
+ }
1018
+ function formatCommandDisplayLabel(commandLabel) {
1019
+ switch (commandLabel) {
1020
+ case "check":
1021
+ case "check:summary": {
1022
+ return "Check drift against baseline (summary)";
1023
+ }
1024
+ case "check:diff": {
1025
+ return "Check drift against baseline (detailed diff)";
1026
+ }
1027
+ case "check:status": {
1028
+ return "Check drift against baseline (status only)";
1029
+ }
1030
+ case "update": {
1031
+ return "Update baseline snapshot";
1032
+ }
1033
+ case "print:json": {
1034
+ return "Print aggregated rules (JSON)";
1035
+ }
1036
+ case "print:short": {
1037
+ return "Print aggregated rules (short view)";
1038
+ }
1039
+ case "config:json": {
1040
+ return "Show effective runtime config (JSON)";
1041
+ }
1042
+ case "config:short": {
1043
+ return "Show effective runtime config (short view)";
1044
+ }
1045
+ case "init": {
1046
+ return "Initialize local configuration";
1047
+ }
1048
+ case "help": {
1049
+ return "Show CLI help";
1050
+ }
1051
+ default: {
1052
+ return commandLabel;
1053
+ }
1054
+ }
972
1055
  }
973
1056
  function formatConfigSource(cwd, configPath) {
974
1057
  if (!configPath) {
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ import { Command, CommanderError, InvalidArgumentError } from "commander";
22
22
  import fg from "fast-glob";
23
23
  import { existsSync, readFileSync } from "fs";
24
24
  import { access, mkdir, readFile, writeFile } from "fs/promises";
25
+ import { createRequire } from "module";
25
26
  import path from "path";
26
27
  import { createInterface } from "readline";
27
28
  var SNAPSHOT_DIR = ".eslint-config-snapshot";
@@ -199,13 +200,13 @@ async function executeCheck(cwd, format, defaultInvocation = false) {
199
200
  if (storedSnapshots.size === 0) {
200
201
  const summary = summarizeSnapshots(currentSnapshots);
201
202
  process.stdout.write(
202
- `Current rule state: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).
203
+ `Rules found in this analysis: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).
203
204
  `
204
205
  );
205
206
  const canPromptBaseline = defaultInvocation || format === "summary";
206
207
  if (canPromptBaseline && process.stdin.isTTY && process.stdout.isTTY) {
207
208
  const shouldCreateBaseline = await askYesNo(
208
- "No baseline yet. Use current rule state as your baseline now? [Y/n] ",
209
+ "No baseline yet. Do you want to save this analyzed rule state as your baseline now? [Y/n] ",
209
210
  true
210
211
  );
211
212
  if (shouldCreateBaseline) {
@@ -310,7 +311,7 @@ async function executeConfig(cwd, format) {
310
311
  const storedSnapshots = await loadStoredSnapshots(cwd);
311
312
  writeRunContextHeader(cwd, `config:${format}`, foundConfig?.path, storedSnapshots);
312
313
  if (shouldShowRunLogs()) {
313
- writeSubtleInfo("\u{1F50E} Resolving effective runtime configuration...\n");
314
+ writeSubtleInfo("\u2699\uFE0F Resolving effective runtime configuration...\n");
314
315
  }
315
316
  const config = await loadConfig(cwd);
316
317
  const resolved = await resolveWorkspaceAssignments(cwd, config);
@@ -761,7 +762,6 @@ function printWhatChanged(changes, currentSnapshots, eslintVersionsByGroup) {
761
762
  - workspace membership changes: ${changeSummary.workspace}
762
763
  - current baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
763
764
  - current severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
764
-
765
765
  `
766
766
  );
767
767
  writeEslintVersionSummary(eslintVersionsByGroup);
@@ -871,13 +871,12 @@ function endRunTimer(exitCode) {
871
871
  activeRunTimer.pauseStartedAtMs = void 0;
872
872
  }
873
873
  const elapsedMs = Math.max(0, Date.now() - activeRunTimer.startedAtMs - activeRunTimer.pausedMs);
874
- const color = createColorizer();
875
874
  const seconds = (elapsedMs / 1e3).toFixed(2);
876
875
  if (exitCode === 0) {
877
- writeSubtleInfo(`${color.green("\u2705")} Finished in ${seconds}s
876
+ writeSubtleInfo(`Finished in ${seconds}s
878
877
  `);
879
878
  } else {
880
- writeSubtleInfo(`${color.red("\u274C")} Finished in ${seconds}s
879
+ writeSubtleInfo(`Finished with errors in ${seconds}s
881
880
  `);
882
881
  }
883
882
  activeRunTimer = void 0;
@@ -907,22 +906,45 @@ function readCliVersion() {
907
906
  if (cachedCliVersion !== void 0) {
908
907
  return cachedCliVersion;
909
908
  }
909
+ const envPackageName = process.env.npm_package_name;
910
+ const envPackageVersion = process.env.npm_package_version;
911
+ if (isCliPackageName(envPackageName) && typeof envPackageVersion === "string" && envPackageVersion.length > 0) {
912
+ cachedCliVersion = envPackageVersion;
913
+ return cachedCliVersion;
914
+ }
910
915
  const scriptPath = process.argv[1];
911
916
  if (!scriptPath) {
912
917
  cachedCliVersion = "unknown";
913
918
  return cachedCliVersion;
914
919
  }
920
+ try {
921
+ const req = createRequire(path.resolve(scriptPath));
922
+ const resolvedCliEntry = req.resolve("@eslint-config-snapshot/cli");
923
+ const resolvedVersion = readVersionFromResolvedEntry(resolvedCliEntry);
924
+ if (resolvedVersion !== void 0) {
925
+ cachedCliVersion = resolvedVersion;
926
+ return cachedCliVersion;
927
+ }
928
+ } catch {
929
+ }
915
930
  let current = path.resolve(path.dirname(scriptPath));
931
+ let fallbackVersion;
916
932
  while (true) {
917
933
  const packageJsonPath = path.join(current, "package.json");
918
934
  if (existsSync(packageJsonPath)) {
919
935
  try {
920
936
  const raw = readFileSync(packageJsonPath, "utf8");
921
937
  const parsed = JSON.parse(raw);
922
- cachedCliVersion = parsed.version ?? "unknown";
923
- return cachedCliVersion;
938
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
939
+ if (isCliPackageName(parsed.name)) {
940
+ cachedCliVersion = parsed.version;
941
+ return cachedCliVersion;
942
+ }
943
+ if (fallbackVersion === void 0) {
944
+ fallbackVersion = parsed.version;
945
+ }
946
+ }
924
947
  } catch {
925
- break;
926
948
  }
927
949
  }
928
950
  const parent = path.dirname(current);
@@ -931,25 +953,86 @@ function readCliVersion() {
931
953
  }
932
954
  current = parent;
933
955
  }
934
- cachedCliVersion = "unknown";
956
+ cachedCliVersion = fallbackVersion ?? "unknown";
935
957
  return cachedCliVersion;
936
958
  }
959
+ function isCliPackageName(value) {
960
+ return value === "@eslint-config-snapshot/cli" || value === "eslint-config-snapshot";
961
+ }
962
+ function readVersionFromResolvedEntry(entryAbs) {
963
+ let current = path.resolve(path.dirname(entryAbs));
964
+ while (true) {
965
+ const packageJsonPath = path.join(current, "package.json");
966
+ if (existsSync(packageJsonPath)) {
967
+ try {
968
+ const raw = readFileSync(packageJsonPath, "utf8");
969
+ const parsed = JSON.parse(raw);
970
+ if (isCliPackageName(parsed.name) && typeof parsed.version === "string" && parsed.version.length > 0) {
971
+ return parsed.version;
972
+ }
973
+ } catch {
974
+ }
975
+ }
976
+ const parent = path.dirname(current);
977
+ if (parent === current) {
978
+ break;
979
+ }
980
+ current = parent;
981
+ }
982
+ return void 0;
983
+ }
937
984
  function writeRunContextHeader(cwd, commandLabel, configPath, storedSnapshots) {
938
985
  if (!shouldShowRunLogs()) {
939
986
  return;
940
987
  }
941
988
  const color = createColorizer();
942
- process.stdout.write(color.bold(`\u2728 eslint-config-snapshot v${readCliVersion()}
989
+ process.stdout.write(color.bold(`eslint-config-snapshot v${readCliVersion()} \u2022 ${formatCommandDisplayLabel(commandLabel)}
943
990
  `));
944
- process.stdout.write(`\u{1F9ED} Command: ${commandLabel}
945
- `);
946
991
  process.stdout.write(`\u{1F4C1} Repository: ${cwd}
947
992
  `);
948
- process.stdout.write(`\u2699\uFE0F Config source: ${formatConfigSource(cwd, configPath)}
993
+ process.stdout.write(`\u{1F4C1} Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
949
994
  `);
950
- process.stdout.write(`\u{1F5C2}\uFE0F Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
951
-
995
+ process.stdout.write(`\u2699\uFE0F Config source: ${formatConfigSource(cwd, configPath)}
952
996
  `);
997
+ process.stdout.write("\n");
998
+ }
999
+ function formatCommandDisplayLabel(commandLabel) {
1000
+ switch (commandLabel) {
1001
+ case "check":
1002
+ case "check:summary": {
1003
+ return "Check drift against baseline (summary)";
1004
+ }
1005
+ case "check:diff": {
1006
+ return "Check drift against baseline (detailed diff)";
1007
+ }
1008
+ case "check:status": {
1009
+ return "Check drift against baseline (status only)";
1010
+ }
1011
+ case "update": {
1012
+ return "Update baseline snapshot";
1013
+ }
1014
+ case "print:json": {
1015
+ return "Print aggregated rules (JSON)";
1016
+ }
1017
+ case "print:short": {
1018
+ return "Print aggregated rules (short view)";
1019
+ }
1020
+ case "config:json": {
1021
+ return "Show effective runtime config (JSON)";
1022
+ }
1023
+ case "config:short": {
1024
+ return "Show effective runtime config (short view)";
1025
+ }
1026
+ case "init": {
1027
+ return "Initialize local configuration";
1028
+ }
1029
+ case "help": {
1030
+ return "Show CLI help";
1031
+ }
1032
+ default: {
1033
+ return commandLabel;
1034
+ }
1035
+ }
953
1036
  }
954
1037
  function formatConfigSource(cwd, configPath) {
955
1038
  if (!configPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/cli",
3
- "version": "0.6.0",
3
+ "version": "0.9.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.6.0"
33
+ "@eslint-config-snapshot/api": "0.9.0"
34
34
  }
35
35
  }
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ import { Command, CommanderError, InvalidArgumentError } from 'commander'
20
20
  import fg from 'fast-glob'
21
21
  import { existsSync, readFileSync } from 'node:fs'
22
22
  import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
23
+ import { createRequire } from 'node:module'
23
24
  import path from 'node:path'
24
25
  import { createInterface } from 'node:readline'
25
26
 
@@ -293,18 +294,18 @@ async function executeCheck(cwd: string, format: CheckFormat, defaultInvocation
293
294
 
294
295
  throw error
295
296
  }
296
- if (storedSnapshots.size === 0) {
297
- const summary = summarizeSnapshots(currentSnapshots)
298
- process.stdout.write(
299
- `Current rule state: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).\n`
300
- )
301
-
302
- const canPromptBaseline = defaultInvocation || format === 'summary'
303
- if (canPromptBaseline && process.stdin.isTTY && process.stdout.isTTY) {
304
- const shouldCreateBaseline = await askYesNo(
305
- 'No baseline yet. Use current rule state as your baseline now? [Y/n] ',
306
- true
297
+ if (storedSnapshots.size === 0) {
298
+ const summary = summarizeSnapshots(currentSnapshots)
299
+ process.stdout.write(
300
+ `Rules found in this analysis: ${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off).\n`
307
301
  )
302
+
303
+ const canPromptBaseline = defaultInvocation || format === 'summary'
304
+ if (canPromptBaseline && process.stdin.isTTY && process.stdout.isTTY) {
305
+ const shouldCreateBaseline = await askYesNo(
306
+ 'No baseline yet. Do you want to save this analyzed rule state as your baseline now? [Y/n] ',
307
+ true
308
+ )
308
309
  if (shouldCreateBaseline) {
309
310
  await writeSnapshots(cwd, currentSnapshots)
310
311
  const summary = summarizeSnapshots(currentSnapshots)
@@ -420,7 +421,7 @@ async function executeConfig(cwd: string, format: PrintFormat): Promise<void> {
420
421
  const storedSnapshots = await loadStoredSnapshots(cwd)
421
422
  writeRunContextHeader(cwd, `config:${format}`, foundConfig?.path, storedSnapshots)
422
423
  if (shouldShowRunLogs()) {
423
- writeSubtleInfo('🔎 Resolving effective runtime configuration...\n')
424
+ writeSubtleInfo('⚙️ Resolving effective runtime configuration...\n')
424
425
  }
425
426
  const config = await loadConfig(cwd)
426
427
  const resolved = await resolveWorkspaceAssignments(cwd, config)
@@ -969,7 +970,7 @@ function printWhatChanged(
969
970
  process.stdout.write(color.red('Heads up: snapshot drift detected.\n'))
970
971
  writeSectionTitle('Summary', color)
971
972
  process.stdout.write(
972
- `- changed groups: ${changes.length}\n- introduced rules: ${changeSummary.introduced}\n- removed rules: ${changeSummary.removed}\n- severity changes: ${changeSummary.severity}\n- options changes: ${changeSummary.options}\n- workspace membership changes: ${changeSummary.workspace}\n- current baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules\n- current severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off\n\n`
973
+ `- changed groups: ${changes.length}\n- introduced rules: ${changeSummary.introduced}\n- removed rules: ${changeSummary.removed}\n- severity changes: ${changeSummary.severity}\n- options changes: ${changeSummary.options}\n- workspace membership changes: ${changeSummary.workspace}\n- current baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules\n- current severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off\n`
973
974
  )
974
975
  writeEslintVersionSummary(eslintVersionsByGroup)
975
976
  process.stdout.write('\n')
@@ -1090,12 +1091,11 @@ function endRunTimer(exitCode: number): void {
1090
1091
  }
1091
1092
 
1092
1093
  const elapsedMs = Math.max(0, Date.now() - activeRunTimer.startedAtMs - activeRunTimer.pausedMs)
1093
- const color = createColorizer()
1094
1094
  const seconds = (elapsedMs / 1000).toFixed(2)
1095
1095
  if (exitCode === 0) {
1096
- writeSubtleInfo(`${color.green('✅')} Finished in ${seconds}s\n`)
1096
+ writeSubtleInfo(`Finished in ${seconds}s\n`)
1097
1097
  } else {
1098
- writeSubtleInfo(`${color.red('❌')} Finished in ${seconds}s\n`)
1098
+ writeSubtleInfo(`Finished with errors in ${seconds}s\n`)
1099
1099
  }
1100
1100
  activeRunTimer = undefined
1101
1101
  }
@@ -1130,23 +1130,51 @@ function readCliVersion(): string {
1130
1130
  return cachedCliVersion
1131
1131
  }
1132
1132
 
1133
+ const envPackageName = process.env.npm_package_name
1134
+ const envPackageVersion = process.env.npm_package_version
1135
+ if (isCliPackageName(envPackageName) && typeof envPackageVersion === 'string' && envPackageVersion.length > 0) {
1136
+ cachedCliVersion = envPackageVersion
1137
+ return cachedCliVersion
1138
+ }
1139
+
1133
1140
  const scriptPath = process.argv[1]
1134
1141
  if (!scriptPath) {
1135
1142
  cachedCliVersion = 'unknown'
1136
1143
  return cachedCliVersion
1137
1144
  }
1138
1145
 
1146
+ try {
1147
+ const req = createRequire(path.resolve(scriptPath))
1148
+ const resolvedCliEntry = req.resolve('@eslint-config-snapshot/cli')
1149
+ const resolvedVersion = readVersionFromResolvedEntry(resolvedCliEntry)
1150
+ if (resolvedVersion !== undefined) {
1151
+ cachedCliVersion = resolvedVersion
1152
+ return cachedCliVersion
1153
+ }
1154
+ } catch {
1155
+ // continue to path-walk fallback
1156
+ }
1157
+
1139
1158
  let current = path.resolve(path.dirname(scriptPath))
1159
+ let fallbackVersion: string | undefined
1140
1160
  while (true) {
1141
1161
  const packageJsonPath = path.join(current, 'package.json')
1142
1162
  if (existsSync(packageJsonPath)) {
1143
1163
  try {
1144
1164
  const raw = readFileSync(packageJsonPath, 'utf8')
1145
- const parsed = JSON.parse(raw) as { version?: string }
1146
- cachedCliVersion = parsed.version ?? 'unknown'
1147
- return cachedCliVersion
1165
+ const parsed = JSON.parse(raw) as { name?: string; version?: string }
1166
+ if (typeof parsed.version === 'string' && parsed.version.length > 0) {
1167
+ if (isCliPackageName(parsed.name)) {
1168
+ cachedCliVersion = parsed.version
1169
+ return cachedCliVersion
1170
+ }
1171
+
1172
+ if (fallbackVersion === undefined) {
1173
+ fallbackVersion = parsed.version
1174
+ }
1175
+ }
1148
1176
  } catch {
1149
- break
1177
+ // continue walking up
1150
1178
  }
1151
1179
  }
1152
1180
 
@@ -1157,10 +1185,41 @@ function readCliVersion(): string {
1157
1185
  current = parent
1158
1186
  }
1159
1187
 
1160
- cachedCliVersion = 'unknown'
1188
+ cachedCliVersion = fallbackVersion ?? 'unknown'
1161
1189
  return cachedCliVersion
1162
1190
  }
1163
1191
 
1192
+ function isCliPackageName(value: string | undefined): boolean {
1193
+ return value === '@eslint-config-snapshot/cli' || value === 'eslint-config-snapshot'
1194
+ }
1195
+
1196
+ function readVersionFromResolvedEntry(entryAbs: string): string | undefined {
1197
+ let current = path.resolve(path.dirname(entryAbs))
1198
+
1199
+ while (true) {
1200
+ const packageJsonPath = path.join(current, 'package.json')
1201
+ if (existsSync(packageJsonPath)) {
1202
+ try {
1203
+ const raw = readFileSync(packageJsonPath, 'utf8')
1204
+ const parsed = JSON.parse(raw) as { name?: string; version?: string }
1205
+ if (isCliPackageName(parsed.name) && typeof parsed.version === 'string' && parsed.version.length > 0) {
1206
+ return parsed.version
1207
+ }
1208
+ } catch {
1209
+ // continue walking up
1210
+ }
1211
+ }
1212
+
1213
+ const parent = path.dirname(current)
1214
+ if (parent === current) {
1215
+ break
1216
+ }
1217
+ current = parent
1218
+ }
1219
+
1220
+ return undefined
1221
+ }
1222
+
1164
1223
  function writeRunContextHeader(
1165
1224
  cwd: string,
1166
1225
  commandLabel: string,
@@ -1172,11 +1231,50 @@ function writeRunContextHeader(
1172
1231
  }
1173
1232
 
1174
1233
  const color = createColorizer()
1175
- process.stdout.write(color.bold(`✨ eslint-config-snapshot v${readCliVersion()}\n`))
1176
- process.stdout.write(`🧭 Command: ${commandLabel}\n`)
1234
+ process.stdout.write(color.bold(`eslint-config-snapshot v${readCliVersion()} • ${formatCommandDisplayLabel(commandLabel)}\n`))
1177
1235
  process.stdout.write(`📁 Repository: ${cwd}\n`)
1236
+ process.stdout.write(`📁 Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}\n`)
1178
1237
  process.stdout.write(`⚙️ Config source: ${formatConfigSource(cwd, configPath)}\n`)
1179
- process.stdout.write(`🗂️ Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}\n\n`)
1238
+ process.stdout.write('\n')
1239
+ }
1240
+
1241
+ function formatCommandDisplayLabel(commandLabel: string): string {
1242
+ switch (commandLabel) {
1243
+ case 'check':
1244
+ case 'check:summary': {
1245
+ return 'Check drift against baseline (summary)'
1246
+ }
1247
+ case 'check:diff': {
1248
+ return 'Check drift against baseline (detailed diff)'
1249
+ }
1250
+ case 'check:status': {
1251
+ return 'Check drift against baseline (status only)'
1252
+ }
1253
+ case 'update': {
1254
+ return 'Update baseline snapshot'
1255
+ }
1256
+ case 'print:json': {
1257
+ return 'Print aggregated rules (JSON)'
1258
+ }
1259
+ case 'print:short': {
1260
+ return 'Print aggregated rules (short view)'
1261
+ }
1262
+ case 'config:json': {
1263
+ return 'Show effective runtime config (JSON)'
1264
+ }
1265
+ case 'config:short': {
1266
+ return 'Show effective runtime config (short view)'
1267
+ }
1268
+ case 'init': {
1269
+ return 'Initialize local configuration'
1270
+ }
1271
+ case 'help': {
1272
+ return 'Show CLI help'
1273
+ }
1274
+ default: {
1275
+ return commandLabel
1276
+ }
1277
+ }
1180
1278
  }
1181
1279
 
1182
1280
  function formatConfigSource(cwd: string, configPath: string | undefined): string {
@@ -101,7 +101,7 @@ describe('cli terminal invocation', () => {
101
101
  const result = run([])
102
102
  expect(result.status).toBe(1)
103
103
  expect(result.stdout).toBe(
104
- 'Current rule state: 1 groups, 3 rules (severity mix: 2 errors, 0 warnings, 1 off).\nYou are almost set: no baseline snapshot found yet.\nRun `eslint-config-snapshot --update` to create your first baseline.\n'
104
+ 'Rules found in this analysis: 1 groups, 3 rules (severity mix: 2 errors, 0 warnings, 1 off).\nYou are almost set: no baseline snapshot found yet.\nRun `eslint-config-snapshot --update` to create your first baseline.\n'
105
105
  )
106
106
  })
107
107