@eslint-config-snapshot/cli 0.3.2 → 0.5.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 +22 -0
- package/dist/index.cjs +318 -71
- package/dist/index.js +320 -72
- package/package.json +2 -2
- package/src/index.ts +370 -77
- package/test/cli.integration.test.ts +8 -0
- package/test/cli.npm-isolated.integration.test.ts +27 -6
- package/test/cli.pnpm-isolated.integration.test.ts +27 -10
- package/test/cli.terminal.integration.test.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -7,51 +7,66 @@ import {
|
|
|
7
7
|
buildSnapshot,
|
|
8
8
|
diffSnapshots,
|
|
9
9
|
discoverWorkspaces,
|
|
10
|
-
|
|
10
|
+
extractRulesForWorkspaceSamples,
|
|
11
11
|
findConfigPath,
|
|
12
12
|
getConfigScaffold,
|
|
13
13
|
hasDiff,
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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("Analyzing 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("Analyzing 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"
|
|
@@ -246,12 +274,25 @@ async function executeUpdate(cwd, printSummary) {
|
|
|
246
274
|
await writeSnapshots(cwd, currentSnapshots);
|
|
247
275
|
if (printSummary) {
|
|
248
276
|
const summary = summarizeSnapshots(currentSnapshots);
|
|
249
|
-
|
|
250
|
-
|
|
277
|
+
const color = createColorizer();
|
|
278
|
+
const eslintVersionsByGroup = shouldShowRunLogs() ? await resolveGroupEslintVersions(cwd) : /* @__PURE__ */ new Map();
|
|
279
|
+
writeSectionTitle("Summary", color);
|
|
280
|
+
process.stdout.write(
|
|
281
|
+
`Baseline updated: ${summary.groups} groups, ${summary.rules} rules.
|
|
282
|
+
Severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off.
|
|
283
|
+
`
|
|
284
|
+
);
|
|
285
|
+
writeEslintVersionSummary(eslintVersionsByGroup);
|
|
251
286
|
}
|
|
252
287
|
return 0;
|
|
253
288
|
}
|
|
254
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("Analyzing current ESLint configuration...\n");
|
|
295
|
+
}
|
|
255
296
|
const currentSnapshots = await computeCurrentSnapshots(cwd);
|
|
256
297
|
if (format === "short") {
|
|
257
298
|
process.stdout.write(formatShortPrint([...currentSnapshots.values()]));
|
|
@@ -266,6 +307,11 @@ async function executePrint(cwd, format) {
|
|
|
266
307
|
}
|
|
267
308
|
async function executeConfig(cwd, format) {
|
|
268
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("Resolving effective runtime configuration...\n");
|
|
314
|
+
}
|
|
269
315
|
const config = await loadConfig(cwd);
|
|
270
316
|
const resolved = await resolveWorkspaceAssignments(cwd, config);
|
|
271
317
|
const payload = {
|
|
@@ -297,19 +343,20 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
297
343
|
const sampled = await sampleWorkspaceFiles(workspaceAbs, config.sampling);
|
|
298
344
|
let extractedCount = 0;
|
|
299
345
|
let lastExtractionError;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
346
|
+
const sampledAbs = sampled.map((sampledRel) => path.resolve(workspaceAbs, sampledRel));
|
|
347
|
+
const results = await extractRulesForWorkspaceSamples(workspaceAbs, sampledAbs);
|
|
348
|
+
for (const result of results) {
|
|
349
|
+
if (result.rules) {
|
|
350
|
+
extractedForGroup.push(result.rules);
|
|
304
351
|
extractedCount += 1;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
throw error;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const message = result.error instanceof Error ? result.error.message : String(result.error);
|
|
355
|
+
if (isRecoverableExtractionError(message)) {
|
|
356
|
+
lastExtractionError = message;
|
|
357
|
+
continue;
|
|
312
358
|
}
|
|
359
|
+
throw result.error ?? new Error(message);
|
|
313
360
|
}
|
|
314
361
|
if (extractedCount === 0) {
|
|
315
362
|
const context = lastExtractionError ? ` Last error: ${lastExtractionError}` : "";
|
|
@@ -323,6 +370,9 @@ async function computeCurrentSnapshots(cwd) {
|
|
|
323
370
|
}
|
|
324
371
|
return snapshots;
|
|
325
372
|
}
|
|
373
|
+
function isRecoverableExtractionError(message) {
|
|
374
|
+
return message.startsWith("Invalid JSON from eslint --print-config") || message.startsWith("Empty ESLint print-config output") || message.includes("File ignored because of a matching ignore pattern") || message.includes("File ignored by default");
|
|
375
|
+
}
|
|
326
376
|
async function resolveWorkspaceAssignments(cwd, config) {
|
|
327
377
|
const discovery = await discoverWorkspaces({ cwd, workspaceInput: config.workspaceInput });
|
|
328
378
|
const assignments = config.grouping.mode === "standalone" ? discovery.workspacesRel.map((workspace) => ({ name: workspace, workspaces: [workspace] })) : assignGroupsByMatch(discovery.workspacesRel, config.grouping.groups ?? [{ name: "default", match: ["**/*"] }]);
|
|
@@ -447,8 +497,8 @@ ${JSON.stringify(configObject, null, 2)}
|
|
|
447
497
|
}
|
|
448
498
|
async function askInitPreferences() {
|
|
449
499
|
const { select } = await import("@inquirer/prompts");
|
|
450
|
-
const target = await askInitTarget(select);
|
|
451
|
-
const preset = await askInitPreset(select);
|
|
500
|
+
const target = await runPromptWithPausedTimer(() => askInitTarget(select));
|
|
501
|
+
const preset = await runPromptWithPausedTimer(() => askInitPreset(select));
|
|
452
502
|
return { target, preset };
|
|
453
503
|
}
|
|
454
504
|
async function askInitTarget(selectPrompt) {
|
|
@@ -471,8 +521,10 @@ async function askInitPreset(selectPrompt) {
|
|
|
471
521
|
});
|
|
472
522
|
}
|
|
473
523
|
function askQuestion(rl, prompt) {
|
|
524
|
+
pauseRunTimer();
|
|
474
525
|
return new Promise((resolve) => {
|
|
475
526
|
rl.question(prompt, (answer) => {
|
|
527
|
+
resumeRunTimer();
|
|
476
528
|
resolve(answer);
|
|
477
529
|
});
|
|
478
530
|
});
|
|
@@ -618,11 +670,13 @@ async function askRecommendedGroupAssignments(workspaces) {
|
|
|
618
670
|
'Recommended setup: default group "*" is a dynamic catch-all for every discovered workspace.\n'
|
|
619
671
|
);
|
|
620
672
|
process.stdout.write("Select only workspaces that should move to explicit static groups.\n");
|
|
621
|
-
const overrides = await
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
);
|
|
626
680
|
const assignments = /* @__PURE__ */ new Map();
|
|
627
681
|
let nextGroup = 1;
|
|
628
682
|
for (const workspace of overrides) {
|
|
@@ -630,13 +684,15 @@ async function askRecommendedGroupAssignments(workspaces) {
|
|
|
630
684
|
while (usedGroups.includes(nextGroup)) {
|
|
631
685
|
nextGroup += 1;
|
|
632
686
|
}
|
|
633
|
-
const selected = await
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
);
|
|
640
696
|
const groupNumber = selected === "new" ? nextGroup : selected;
|
|
641
697
|
assignments.set(workspace, groupNumber);
|
|
642
698
|
}
|
|
@@ -679,28 +735,38 @@ function isDirectCliExecution() {
|
|
|
679
735
|
if (isDirectCliExecution()) {
|
|
680
736
|
void main();
|
|
681
737
|
}
|
|
682
|
-
function printWhatChanged(changes, currentSnapshots) {
|
|
738
|
+
function printWhatChanged(changes, currentSnapshots, eslintVersionsByGroup) {
|
|
683
739
|
const color = createColorizer();
|
|
684
740
|
const currentSummary = summarizeSnapshots(currentSnapshots);
|
|
685
741
|
const changeSummary = summarizeChanges(changes);
|
|
686
742
|
if (changes.length === 0) {
|
|
687
743
|
process.stdout.write(color.green("Great news: no snapshot drift detected.\n"));
|
|
744
|
+
writeSectionTitle("Summary", color);
|
|
688
745
|
process.stdout.write(
|
|
689
|
-
|
|
746
|
+
`- baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
|
|
747
|
+
- severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
|
|
690
748
|
`
|
|
691
749
|
);
|
|
750
|
+
writeEslintVersionSummary(eslintVersionsByGroup);
|
|
692
751
|
return 0;
|
|
693
752
|
}
|
|
694
753
|
process.stdout.write(color.red("Heads up: snapshot drift detected.\n"));
|
|
754
|
+
writeSectionTitle("Summary", color);
|
|
695
755
|
process.stdout.write(
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
756
|
+
`- changed groups: ${changes.length}
|
|
757
|
+
- introduced rules: ${changeSummary.introduced}
|
|
758
|
+
- removed rules: ${changeSummary.removed}
|
|
759
|
+
- severity changes: ${changeSummary.severity}
|
|
760
|
+
- options changes: ${changeSummary.options}
|
|
761
|
+
- workspace membership changes: ${changeSummary.workspace}
|
|
762
|
+
- current baseline: ${currentSummary.groups} groups, ${currentSummary.rules} rules
|
|
763
|
+
- current severity mix: ${currentSummary.error} errors, ${currentSummary.warn} warnings, ${currentSummary.off} off
|
|
701
764
|
|
|
702
765
|
`
|
|
703
766
|
);
|
|
767
|
+
writeEslintVersionSummary(eslintVersionsByGroup);
|
|
768
|
+
process.stdout.write("\n");
|
|
769
|
+
writeSectionTitle("Changes", color);
|
|
704
770
|
for (const change of changes) {
|
|
705
771
|
process.stdout.write(color.bold(`group ${change.groupId}
|
|
706
772
|
`));
|
|
@@ -715,6 +781,10 @@ function printWhatChanged(changes, currentSnapshots) {
|
|
|
715
781
|
writeSubtleInfo(UPDATE_HINT);
|
|
716
782
|
return 1;
|
|
717
783
|
}
|
|
784
|
+
function writeSectionTitle(title, color) {
|
|
785
|
+
process.stdout.write(`${color.bold(title)}
|
|
786
|
+
`);
|
|
787
|
+
}
|
|
718
788
|
function summarizeChanges(changes) {
|
|
719
789
|
let introduced = 0;
|
|
720
790
|
let removed = 0;
|
|
@@ -731,22 +801,7 @@ function summarizeChanges(changes) {
|
|
|
731
801
|
return { introduced, removed, severity, options, workspace };
|
|
732
802
|
}
|
|
733
803
|
function summarizeSnapshots(snapshots) {
|
|
734
|
-
|
|
735
|
-
let error = 0;
|
|
736
|
-
let warn = 0;
|
|
737
|
-
let off = 0;
|
|
738
|
-
for (const snapshot of snapshots.values()) {
|
|
739
|
-
for (const entry of Object.values(snapshot.rules)) {
|
|
740
|
-
rules += 1;
|
|
741
|
-
if (entry[0] === "error") {
|
|
742
|
-
error += 1;
|
|
743
|
-
} else if (entry[0] === "warn") {
|
|
744
|
-
warn += 1;
|
|
745
|
-
} else {
|
|
746
|
-
off += 1;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
804
|
+
const { rules, error, warn, off } = countRuleSeverities([...snapshots.values()].map((snapshot) => snapshot.rules));
|
|
750
805
|
return { groups: snapshots.size, rules, error, warn, off };
|
|
751
806
|
}
|
|
752
807
|
function decorateDiffLine(line, color) {
|
|
@@ -776,6 +831,199 @@ function writeSubtleInfo(text) {
|
|
|
776
831
|
const color = createColorizer();
|
|
777
832
|
process.stdout.write(color.dim(text));
|
|
778
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 status = exitCode === 0 ? color.green("done") : color.red("failed");
|
|
876
|
+
const seconds = (elapsedMs / 1e3).toFixed(2);
|
|
877
|
+
writeSubtleInfo(`Run ${status} in ${seconds}s
|
|
878
|
+
`);
|
|
879
|
+
activeRunTimer = void 0;
|
|
880
|
+
}
|
|
881
|
+
function pauseRunTimer() {
|
|
882
|
+
if (!activeRunTimer || activeRunTimer.pauseStartedAtMs !== void 0) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
activeRunTimer.pauseStartedAtMs = Date.now();
|
|
886
|
+
}
|
|
887
|
+
function resumeRunTimer() {
|
|
888
|
+
if (!activeRunTimer || activeRunTimer.pauseStartedAtMs === void 0) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
activeRunTimer.pausedMs += Date.now() - activeRunTimer.pauseStartedAtMs;
|
|
892
|
+
activeRunTimer.pauseStartedAtMs = void 0;
|
|
893
|
+
}
|
|
894
|
+
async function runPromptWithPausedTimer(prompt) {
|
|
895
|
+
pauseRunTimer();
|
|
896
|
+
try {
|
|
897
|
+
return await prompt();
|
|
898
|
+
} finally {
|
|
899
|
+
resumeRunTimer();
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function readCliVersion() {
|
|
903
|
+
if (cachedCliVersion !== void 0) {
|
|
904
|
+
return cachedCliVersion;
|
|
905
|
+
}
|
|
906
|
+
const scriptPath = process.argv[1];
|
|
907
|
+
if (!scriptPath) {
|
|
908
|
+
cachedCliVersion = "unknown";
|
|
909
|
+
return cachedCliVersion;
|
|
910
|
+
}
|
|
911
|
+
let current = path.resolve(path.dirname(scriptPath));
|
|
912
|
+
while (true) {
|
|
913
|
+
const packageJsonPath = path.join(current, "package.json");
|
|
914
|
+
if (existsSync(packageJsonPath)) {
|
|
915
|
+
try {
|
|
916
|
+
const raw = readFileSync(packageJsonPath, "utf8");
|
|
917
|
+
const parsed = JSON.parse(raw);
|
|
918
|
+
cachedCliVersion = parsed.version ?? "unknown";
|
|
919
|
+
return cachedCliVersion;
|
|
920
|
+
} catch {
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const parent = path.dirname(current);
|
|
925
|
+
if (parent === current) {
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
current = parent;
|
|
929
|
+
}
|
|
930
|
+
cachedCliVersion = "unknown";
|
|
931
|
+
return cachedCliVersion;
|
|
932
|
+
}
|
|
933
|
+
function writeRunContextHeader(cwd, commandLabel, configPath, storedSnapshots) {
|
|
934
|
+
if (!shouldShowRunLogs()) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const color = createColorizer();
|
|
938
|
+
process.stdout.write(color.bold(`eslint-config-snapshot v${readCliVersion()}
|
|
939
|
+
`));
|
|
940
|
+
process.stdout.write(`Command: ${commandLabel}
|
|
941
|
+
`);
|
|
942
|
+
process.stdout.write(`Repository: ${cwd}
|
|
943
|
+
`);
|
|
944
|
+
process.stdout.write(`Config: ${formatConfigSource(cwd, configPath)}
|
|
945
|
+
`);
|
|
946
|
+
process.stdout.write(`Baseline: ${formatStoredSnapshotSummary(storedSnapshots)}
|
|
947
|
+
|
|
948
|
+
`);
|
|
949
|
+
}
|
|
950
|
+
function formatConfigSource(cwd, configPath) {
|
|
951
|
+
if (!configPath) {
|
|
952
|
+
return "built-in defaults";
|
|
953
|
+
}
|
|
954
|
+
const rel = normalizePath(path.relative(cwd, configPath));
|
|
955
|
+
if (path.basename(configPath) === "package.json") {
|
|
956
|
+
return `${rel} (eslint-config-snapshot field)`;
|
|
957
|
+
}
|
|
958
|
+
return rel;
|
|
959
|
+
}
|
|
960
|
+
function formatStoredSnapshotSummary(storedSnapshots) {
|
|
961
|
+
if (storedSnapshots.size === 0) {
|
|
962
|
+
return "none";
|
|
963
|
+
}
|
|
964
|
+
const summary = summarizeStoredSnapshots(storedSnapshots);
|
|
965
|
+
return `${summary.groups} groups, ${summary.rules} rules (severity mix: ${summary.error} errors, ${summary.warn} warnings, ${summary.off} off)`;
|
|
966
|
+
}
|
|
967
|
+
async function resolveGroupEslintVersions(cwd) {
|
|
968
|
+
const config = await loadConfig(cwd);
|
|
969
|
+
const { discovery, assignments } = await resolveWorkspaceAssignments(cwd, config);
|
|
970
|
+
const result = /* @__PURE__ */ new Map();
|
|
971
|
+
for (const group of assignments) {
|
|
972
|
+
const versions = /* @__PURE__ */ new Set();
|
|
973
|
+
for (const workspaceRel of group.workspaces) {
|
|
974
|
+
const workspaceAbs = path.resolve(discovery.rootAbs, workspaceRel);
|
|
975
|
+
versions.add(resolveEslintVersionForWorkspace(workspaceAbs));
|
|
976
|
+
}
|
|
977
|
+
result.set(group.name, [...versions].sort((a, b) => a.localeCompare(b)));
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
}
|
|
981
|
+
function writeEslintVersionSummary(eslintVersionsByGroup) {
|
|
982
|
+
if (!shouldShowRunLogs() || eslintVersionsByGroup.size === 0) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const allVersions = /* @__PURE__ */ new Set();
|
|
986
|
+
for (const versions of eslintVersionsByGroup.values()) {
|
|
987
|
+
for (const version of versions) {
|
|
988
|
+
allVersions.add(version);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const sortedAllVersions = [...allVersions].sort((a, b) => a.localeCompare(b));
|
|
992
|
+
if (sortedAllVersions.length === 1) {
|
|
993
|
+
process.stdout.write(`- eslint runtime: ${sortedAllVersions[0]} (all groups)
|
|
994
|
+
`);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
process.stdout.write("- eslint runtime by group:\n");
|
|
998
|
+
const sortedEntries = [...eslintVersionsByGroup.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
999
|
+
for (const [groupName, versions] of sortedEntries) {
|
|
1000
|
+
process.stdout.write(` - ${groupName}: ${versions.join(", ")}
|
|
1001
|
+
`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function summarizeStoredSnapshots(snapshots) {
|
|
1005
|
+
const { rules, error, warn, off } = countRuleSeverities([...snapshots.values()].map((snapshot) => snapshot.rules));
|
|
1006
|
+
return { groups: snapshots.size, rules, error, warn, off };
|
|
1007
|
+
}
|
|
1008
|
+
function countRuleSeverities(ruleObjects) {
|
|
1009
|
+
let rules = 0;
|
|
1010
|
+
let error = 0;
|
|
1011
|
+
let warn = 0;
|
|
1012
|
+
let off = 0;
|
|
1013
|
+
for (const rulesObject of ruleObjects) {
|
|
1014
|
+
for (const entry of Object.values(rulesObject)) {
|
|
1015
|
+
rules += 1;
|
|
1016
|
+
if (entry[0] === "error") {
|
|
1017
|
+
error += 1;
|
|
1018
|
+
} else if (entry[0] === "warn") {
|
|
1019
|
+
warn += 1;
|
|
1020
|
+
} else {
|
|
1021
|
+
off += 1;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return { rules, error, warn, off };
|
|
1026
|
+
}
|
|
779
1027
|
function formatShortPrint(snapshots) {
|
|
780
1028
|
const lines = [];
|
|
781
1029
|
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.
|
|
3
|
+
"version": "0.5.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.
|
|
33
|
+
"@eslint-config-snapshot/api": "0.5.0"
|
|
34
34
|
}
|
|
35
35
|
}
|