@eslint-config-snapshot/cli 0.2.0 → 0.3.2
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 +27 -0
- package/README.md +8 -0
- package/dist/index.cjs +63 -105
- package/dist/index.js +62 -103
- package/package.json +3 -2
- package/src/index.ts +69 -103
- package/test/cli.integration.test.ts +32 -26
- package/test/cli.terminal.integration.test.ts +22 -16
- package/test/fixtures/repo/packages/ws-a/node_modules/eslint/bin/eslint.js +0 -1
- package/test/fixtures/repo/packages/ws-a/node_modules/eslint/package.json +0 -4
- package/test/fixtures/repo/packages/ws-b/node_modules/eslint/bin/eslint.js +0 -1
- package/test/fixtures/repo/packages/ws-b/node_modules/eslint/package.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @eslint-config-snapshot/cli
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix deterministic aggregation when same-severity ESLint rule options differ across sampled files, preventing update crashes.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @eslint-config-snapshot/api@0.3.2
|
|
10
|
+
|
|
11
|
+
## 0.3.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Release patch bump after init UX clarity improvements for default catch-all group messaging.
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- @eslint-config-snapshot/api@0.3.1
|
|
18
|
+
|
|
19
|
+
## 0.3.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- Release minor with inquirer-based init UX and recommended dynamic grouping behavior.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
- @eslint-config-snapshot/api@0.3.0
|
|
29
|
+
|
|
3
30
|
## 0.2.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -30,6 +30,14 @@ Check drift:
|
|
|
30
30
|
eslint-config-snapshot
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Recommended setup flow:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
eslint-config-snapshot init
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In `recommended` preset, keep default `*` group and only select outlier workspaces via checkbox, assigning numeric groups to those exceptions.
|
|
40
|
+
|
|
33
41
|
## Commands
|
|
34
42
|
|
|
35
43
|
- `check`
|
package/dist/index.cjs
CHANGED
|
@@ -31,9 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
|
+
buildRecommendedConfigFromAssignments: () => buildRecommendedConfigFromAssignments,
|
|
34
35
|
main: () => main,
|
|
35
|
-
parseInitPresetChoice: () => parseInitPresetChoice,
|
|
36
|
-
parseInitTargetChoice: () => parseInitTargetChoice,
|
|
37
36
|
runCli: () => runCli
|
|
38
37
|
});
|
|
39
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -124,10 +123,8 @@ function createProgram(cwd, onActionExit) {
|
|
|
124
123
|
`
|
|
125
124
|
Examples:
|
|
126
125
|
$ eslint-config-snapshot init
|
|
127
|
-
Runs interactive
|
|
128
|
-
|
|
129
|
-
preset: 1) recommended, 2) minimal, 3) full
|
|
130
|
-
recommended preset supports per-workspace group number assignment.
|
|
126
|
+
Runs interactive select prompts for target/preset.
|
|
127
|
+
Recommended preset keeps a dynamic catch-all default group ("*") and asks only for static exception groups.
|
|
131
128
|
|
|
132
129
|
$ eslint-config-snapshot init --yes --target package-json --preset recommended --show-effective
|
|
133
130
|
Non-interactive recommended setup in package.json, with effective preview.
|
|
@@ -469,69 +466,29 @@ ${JSON.stringify(configObject, null, 2)}
|
|
|
469
466
|
return runInitInFile(cwd, configObject, force);
|
|
470
467
|
}
|
|
471
468
|
async function askInitPreferences() {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
"Select config target:\n 1) package-json (recommended)\n 2) file\nChoose [1]: "
|
|
486
|
-
);
|
|
487
|
-
const parsed = parseInitTargetChoice(answer);
|
|
488
|
-
if (parsed) {
|
|
489
|
-
return parsed;
|
|
490
|
-
}
|
|
491
|
-
process.stdout.write("Please choose 1 (package-json) or 2 (file).\n");
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
async function askInitPreset(rl) {
|
|
495
|
-
while (true) {
|
|
496
|
-
const answer = await askQuestion(
|
|
497
|
-
rl,
|
|
498
|
-
"Select preset:\n 1) recommended (group by workspace numbers)\n 2) minimal\n 3) full\nChoose [1]: "
|
|
499
|
-
);
|
|
500
|
-
const parsed = parseInitPresetChoice(answer);
|
|
501
|
-
if (parsed) {
|
|
502
|
-
return parsed;
|
|
503
|
-
}
|
|
504
|
-
process.stdout.write("Please choose 1 (recommended), 2 (minimal), or 3 (full).\n");
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
function parseInitTargetChoice(value) {
|
|
508
|
-
const normalized = value.trim().toLowerCase();
|
|
509
|
-
if (normalized === "") {
|
|
510
|
-
return "package-json";
|
|
511
|
-
}
|
|
512
|
-
if (normalized === "1" || normalized === "package-json" || normalized === "packagejson" || normalized === "package" || normalized === "pkg") {
|
|
513
|
-
return "package-json";
|
|
514
|
-
}
|
|
515
|
-
if (normalized === "2" || normalized === "file") {
|
|
516
|
-
return "file";
|
|
517
|
-
}
|
|
518
|
-
return void 0;
|
|
469
|
+
const { select } = await import("@inquirer/prompts");
|
|
470
|
+
const target = await askInitTarget(select);
|
|
471
|
+
const preset = await askInitPreset(select);
|
|
472
|
+
return { target, preset };
|
|
473
|
+
}
|
|
474
|
+
async function askInitTarget(selectPrompt) {
|
|
475
|
+
return selectPrompt({
|
|
476
|
+
message: "Select config target",
|
|
477
|
+
choices: [
|
|
478
|
+
{ name: "package-json (recommended)", value: "package-json" },
|
|
479
|
+
{ name: "file", value: "file" }
|
|
480
|
+
]
|
|
481
|
+
});
|
|
519
482
|
}
|
|
520
|
-
function
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return "minimal";
|
|
530
|
-
}
|
|
531
|
-
if (normalized === "3" || normalized === "full") {
|
|
532
|
-
return "full";
|
|
533
|
-
}
|
|
534
|
-
return void 0;
|
|
483
|
+
async function askInitPreset(selectPrompt) {
|
|
484
|
+
return selectPrompt({
|
|
485
|
+
message: "Select preset",
|
|
486
|
+
choices: [
|
|
487
|
+
{ name: 'recommended (dynamic catch-all "*" + optional static exceptions)', value: "recommended" },
|
|
488
|
+
{ name: "minimal", value: "minimal" },
|
|
489
|
+
{ name: "full", value: "full" }
|
|
490
|
+
]
|
|
491
|
+
});
|
|
535
492
|
}
|
|
536
493
|
function askQuestion(rl, prompt) {
|
|
537
494
|
return new Promise((resolve) => {
|
|
@@ -616,30 +573,23 @@ async function resolveInitConfigObject(cwd, preset, nonInteractive) {
|
|
|
616
573
|
}
|
|
617
574
|
async function buildRecommendedPresetObject(cwd, nonInteractive) {
|
|
618
575
|
const workspaces = await discoverInitWorkspaces(cwd);
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
assignments.set(workspace, answer);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
576
|
+
const useInteractiveGrouping = !nonInteractive && process.stdin.isTTY && process.stdout.isTTY;
|
|
577
|
+
const assignments = useInteractiveGrouping ? await askRecommendedGroupAssignments(workspaces) : /* @__PURE__ */ new Map();
|
|
578
|
+
return buildRecommendedConfigFromAssignments(workspaces, assignments);
|
|
579
|
+
}
|
|
580
|
+
function buildRecommendedConfigFromAssignments(workspaces, assignments) {
|
|
627
581
|
const groupNumbers = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
628
|
-
|
|
582
|
+
if (groupNumbers.length === 0) {
|
|
583
|
+
return {};
|
|
584
|
+
}
|
|
585
|
+
const explicitGroups = groupNumbers.map((number) => ({
|
|
629
586
|
name: `group-${number}`,
|
|
630
587
|
match: workspaces.filter((workspace) => assignments.get(workspace) === number)
|
|
631
588
|
}));
|
|
632
589
|
return {
|
|
633
|
-
workspaceInput: { mode: "manual", workspaces },
|
|
634
590
|
grouping: {
|
|
635
591
|
mode: "match",
|
|
636
|
-
groups
|
|
637
|
-
},
|
|
638
|
-
sampling: {
|
|
639
|
-
maxFilesPerWorkspace: 8,
|
|
640
|
-
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs}"],
|
|
641
|
-
excludeGlobs: ["**/node_modules/**", "**/dist/**"],
|
|
642
|
-
hintGlobs: []
|
|
592
|
+
groups: [...explicitGroups, { name: "default", match: ["**/*"] }]
|
|
643
593
|
}
|
|
644
594
|
};
|
|
645
595
|
}
|
|
@@ -682,26 +632,35 @@ function trimTrailingSlashes(value) {
|
|
|
682
632
|
}
|
|
683
633
|
return normalized;
|
|
684
634
|
}
|
|
685
|
-
async function
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
635
|
+
async function askRecommendedGroupAssignments(workspaces) {
|
|
636
|
+
const { checkbox, select } = await import("@inquirer/prompts");
|
|
637
|
+
process.stdout.write(
|
|
638
|
+
'Recommended setup: default group "*" is a dynamic catch-all for every discovered workspace.\n'
|
|
639
|
+
);
|
|
640
|
+
process.stdout.write("Select only workspaces that should move to explicit static groups.\n");
|
|
641
|
+
const overrides = await checkbox({
|
|
642
|
+
message: 'Choose exception workspaces (leave empty to keep all in default "*"):',
|
|
643
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
644
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
645
|
+
});
|
|
646
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
647
|
+
let nextGroup = 1;
|
|
648
|
+
for (const workspace of overrides) {
|
|
649
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
650
|
+
while (usedGroups.includes(nextGroup)) {
|
|
651
|
+
nextGroup += 1;
|
|
701
652
|
}
|
|
702
|
-
|
|
703
|
-
|
|
653
|
+
const selected = await select({
|
|
654
|
+
message: `Select group for ${workspace}`,
|
|
655
|
+
choices: [
|
|
656
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
657
|
+
{ name: `create new group (group-${nextGroup})`, value: "new" }
|
|
658
|
+
]
|
|
659
|
+
});
|
|
660
|
+
const groupNumber = selected === "new" ? nextGroup : selected;
|
|
661
|
+
assignments.set(workspace, groupNumber);
|
|
704
662
|
}
|
|
663
|
+
return assignments;
|
|
705
664
|
}
|
|
706
665
|
function toConfigScaffold(configObject) {
|
|
707
666
|
if (Object.keys(configObject).length === 0) {
|
|
@@ -876,8 +835,7 @@ function formatShortConfig(payload) {
|
|
|
876
835
|
}
|
|
877
836
|
// Annotate the CommonJS export names for ESM import in node:
|
|
878
837
|
0 && (module.exports = {
|
|
838
|
+
buildRecommendedConfigFromAssignments,
|
|
879
839
|
main,
|
|
880
|
-
parseInitPresetChoice,
|
|
881
|
-
parseInitTargetChoice,
|
|
882
840
|
runCli
|
|
883
841
|
});
|
package/dist/index.js
CHANGED
|
@@ -103,10 +103,8 @@ function createProgram(cwd, onActionExit) {
|
|
|
103
103
|
`
|
|
104
104
|
Examples:
|
|
105
105
|
$ eslint-config-snapshot init
|
|
106
|
-
Runs interactive
|
|
107
|
-
|
|
108
|
-
preset: 1) recommended, 2) minimal, 3) full
|
|
109
|
-
recommended preset supports per-workspace group number assignment.
|
|
106
|
+
Runs interactive select prompts for target/preset.
|
|
107
|
+
Recommended preset keeps a dynamic catch-all default group ("*") and asks only for static exception groups.
|
|
110
108
|
|
|
111
109
|
$ eslint-config-snapshot init --yes --target package-json --preset recommended --show-effective
|
|
112
110
|
Non-interactive recommended setup in package.json, with effective preview.
|
|
@@ -448,69 +446,29 @@ ${JSON.stringify(configObject, null, 2)}
|
|
|
448
446
|
return runInitInFile(cwd, configObject, force);
|
|
449
447
|
}
|
|
450
448
|
async function askInitPreferences() {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
"Select config target:\n 1) package-json (recommended)\n 2) file\nChoose [1]: "
|
|
465
|
-
);
|
|
466
|
-
const parsed = parseInitTargetChoice(answer);
|
|
467
|
-
if (parsed) {
|
|
468
|
-
return parsed;
|
|
469
|
-
}
|
|
470
|
-
process.stdout.write("Please choose 1 (package-json) or 2 (file).\n");
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
async function askInitPreset(rl) {
|
|
474
|
-
while (true) {
|
|
475
|
-
const answer = await askQuestion(
|
|
476
|
-
rl,
|
|
477
|
-
"Select preset:\n 1) recommended (group by workspace numbers)\n 2) minimal\n 3) full\nChoose [1]: "
|
|
478
|
-
);
|
|
479
|
-
const parsed = parseInitPresetChoice(answer);
|
|
480
|
-
if (parsed) {
|
|
481
|
-
return parsed;
|
|
482
|
-
}
|
|
483
|
-
process.stdout.write("Please choose 1 (recommended), 2 (minimal), or 3 (full).\n");
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
function parseInitTargetChoice(value) {
|
|
487
|
-
const normalized = value.trim().toLowerCase();
|
|
488
|
-
if (normalized === "") {
|
|
489
|
-
return "package-json";
|
|
490
|
-
}
|
|
491
|
-
if (normalized === "1" || normalized === "package-json" || normalized === "packagejson" || normalized === "package" || normalized === "pkg") {
|
|
492
|
-
return "package-json";
|
|
493
|
-
}
|
|
494
|
-
if (normalized === "2" || normalized === "file") {
|
|
495
|
-
return "file";
|
|
496
|
-
}
|
|
497
|
-
return void 0;
|
|
449
|
+
const { select } = await import("@inquirer/prompts");
|
|
450
|
+
const target = await askInitTarget(select);
|
|
451
|
+
const preset = await askInitPreset(select);
|
|
452
|
+
return { target, preset };
|
|
453
|
+
}
|
|
454
|
+
async function askInitTarget(selectPrompt) {
|
|
455
|
+
return selectPrompt({
|
|
456
|
+
message: "Select config target",
|
|
457
|
+
choices: [
|
|
458
|
+
{ name: "package-json (recommended)", value: "package-json" },
|
|
459
|
+
{ name: "file", value: "file" }
|
|
460
|
+
]
|
|
461
|
+
});
|
|
498
462
|
}
|
|
499
|
-
function
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return "minimal";
|
|
509
|
-
}
|
|
510
|
-
if (normalized === "3" || normalized === "full") {
|
|
511
|
-
return "full";
|
|
512
|
-
}
|
|
513
|
-
return void 0;
|
|
463
|
+
async function askInitPreset(selectPrompt) {
|
|
464
|
+
return selectPrompt({
|
|
465
|
+
message: "Select preset",
|
|
466
|
+
choices: [
|
|
467
|
+
{ name: 'recommended (dynamic catch-all "*" + optional static exceptions)', value: "recommended" },
|
|
468
|
+
{ name: "minimal", value: "minimal" },
|
|
469
|
+
{ name: "full", value: "full" }
|
|
470
|
+
]
|
|
471
|
+
});
|
|
514
472
|
}
|
|
515
473
|
function askQuestion(rl, prompt) {
|
|
516
474
|
return new Promise((resolve) => {
|
|
@@ -595,30 +553,23 @@ async function resolveInitConfigObject(cwd, preset, nonInteractive) {
|
|
|
595
553
|
}
|
|
596
554
|
async function buildRecommendedPresetObject(cwd, nonInteractive) {
|
|
597
555
|
const workspaces = await discoverInitWorkspaces(cwd);
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
assignments.set(workspace, answer);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
556
|
+
const useInteractiveGrouping = !nonInteractive && process.stdin.isTTY && process.stdout.isTTY;
|
|
557
|
+
const assignments = useInteractiveGrouping ? await askRecommendedGroupAssignments(workspaces) : /* @__PURE__ */ new Map();
|
|
558
|
+
return buildRecommendedConfigFromAssignments(workspaces, assignments);
|
|
559
|
+
}
|
|
560
|
+
function buildRecommendedConfigFromAssignments(workspaces, assignments) {
|
|
606
561
|
const groupNumbers = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
607
|
-
|
|
562
|
+
if (groupNumbers.length === 0) {
|
|
563
|
+
return {};
|
|
564
|
+
}
|
|
565
|
+
const explicitGroups = groupNumbers.map((number) => ({
|
|
608
566
|
name: `group-${number}`,
|
|
609
567
|
match: workspaces.filter((workspace) => assignments.get(workspace) === number)
|
|
610
568
|
}));
|
|
611
569
|
return {
|
|
612
|
-
workspaceInput: { mode: "manual", workspaces },
|
|
613
570
|
grouping: {
|
|
614
571
|
mode: "match",
|
|
615
|
-
groups
|
|
616
|
-
},
|
|
617
|
-
sampling: {
|
|
618
|
-
maxFilesPerWorkspace: 8,
|
|
619
|
-
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs}"],
|
|
620
|
-
excludeGlobs: ["**/node_modules/**", "**/dist/**"],
|
|
621
|
-
hintGlobs: []
|
|
572
|
+
groups: [...explicitGroups, { name: "default", match: ["**/*"] }]
|
|
622
573
|
}
|
|
623
574
|
};
|
|
624
575
|
}
|
|
@@ -661,26 +612,35 @@ function trimTrailingSlashes(value) {
|
|
|
661
612
|
}
|
|
662
613
|
return normalized;
|
|
663
614
|
}
|
|
664
|
-
async function
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
615
|
+
async function askRecommendedGroupAssignments(workspaces) {
|
|
616
|
+
const { checkbox, select } = await import("@inquirer/prompts");
|
|
617
|
+
process.stdout.write(
|
|
618
|
+
'Recommended setup: default group "*" is a dynamic catch-all for every discovered workspace.\n'
|
|
619
|
+
);
|
|
620
|
+
process.stdout.write("Select only workspaces that should move to explicit static groups.\n");
|
|
621
|
+
const overrides = await checkbox({
|
|
622
|
+
message: 'Choose exception workspaces (leave empty to keep all in default "*"):',
|
|
623
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
624
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
625
|
+
});
|
|
626
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
627
|
+
let nextGroup = 1;
|
|
628
|
+
for (const workspace of overrides) {
|
|
629
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
630
|
+
while (usedGroups.includes(nextGroup)) {
|
|
631
|
+
nextGroup += 1;
|
|
680
632
|
}
|
|
681
|
-
|
|
682
|
-
|
|
633
|
+
const selected = await select({
|
|
634
|
+
message: `Select group for ${workspace}`,
|
|
635
|
+
choices: [
|
|
636
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
637
|
+
{ name: `create new group (group-${nextGroup})`, value: "new" }
|
|
638
|
+
]
|
|
639
|
+
});
|
|
640
|
+
const groupNumber = selected === "new" ? nextGroup : selected;
|
|
641
|
+
assignments.set(workspace, groupNumber);
|
|
683
642
|
}
|
|
643
|
+
return assignments;
|
|
684
644
|
}
|
|
685
645
|
function toConfigScaffold(configObject) {
|
|
686
646
|
if (Object.keys(configObject).length === 0) {
|
|
@@ -854,8 +814,7 @@ function formatShortConfig(payload) {
|
|
|
854
814
|
`;
|
|
855
815
|
}
|
|
856
816
|
export {
|
|
817
|
+
buildRecommendedConfigFromAssignments,
|
|
857
818
|
main,
|
|
858
|
-
parseInitPresetChoice,
|
|
859
|
-
parseInitTargetChoice,
|
|
860
819
|
runCli
|
|
861
820
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-config-snapshot/cli",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^8.2.0",
|
|
30
31
|
"commander": "^14.0.3",
|
|
31
32
|
"fast-glob": "^3.3.3",
|
|
32
|
-
"@eslint-config-snapshot/api": "0.2
|
|
33
|
+
"@eslint-config-snapshot/api": "0.3.2"
|
|
33
34
|
}
|
|
34
35
|
}
|
package/src/index.ts
CHANGED
|
@@ -163,10 +163,8 @@ function createProgram(cwd: string, onActionExit: (code: number) => void): Comma
|
|
|
163
163
|
`
|
|
164
164
|
Examples:
|
|
165
165
|
$ eslint-config-snapshot init
|
|
166
|
-
Runs interactive
|
|
167
|
-
|
|
168
|
-
preset: 1) recommended, 2) minimal, 3) full
|
|
169
|
-
recommended preset supports per-workspace group number assignment.
|
|
166
|
+
Runs interactive select prompts for target/preset.
|
|
167
|
+
Recommended preset keeps a dynamic catch-all default group ("*") and asks only for static exception groups.
|
|
170
168
|
|
|
171
169
|
$ eslint-config-snapshot init --yes --target package-json --preset recommended --show-effective
|
|
172
170
|
Non-interactive recommended setup in package.json, with effective preview.
|
|
@@ -595,73 +593,35 @@ async function runInit(
|
|
|
595
593
|
}
|
|
596
594
|
|
|
597
595
|
async function askInitPreferences(): Promise<{ target: InitTarget; preset: InitPreset }> {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return { target, preset }
|
|
603
|
-
} finally {
|
|
604
|
-
rl.close()
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async function askInitTarget(rl: ReturnType<typeof createInterface>): Promise<InitTarget> {
|
|
609
|
-
while (true) {
|
|
610
|
-
const answer = await askQuestion(
|
|
611
|
-
rl,
|
|
612
|
-
'Select config target:\n 1) package-json (recommended)\n 2) file\nChoose [1]: '
|
|
613
|
-
)
|
|
614
|
-
const parsed = parseInitTargetChoice(answer)
|
|
615
|
-
if (parsed) {
|
|
616
|
-
return parsed
|
|
617
|
-
}
|
|
618
|
-
process.stdout.write('Please choose 1 (package-json) or 2 (file).\n')
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
async function askInitPreset(rl: ReturnType<typeof createInterface>): Promise<InitPreset> {
|
|
623
|
-
while (true) {
|
|
624
|
-
const answer = await askQuestion(
|
|
625
|
-
rl,
|
|
626
|
-
'Select preset:\n 1) recommended (group by workspace numbers)\n 2) minimal\n 3) full\nChoose [1]: '
|
|
627
|
-
)
|
|
628
|
-
const parsed = parseInitPresetChoice(answer)
|
|
629
|
-
if (parsed) {
|
|
630
|
-
return parsed
|
|
631
|
-
}
|
|
632
|
-
process.stdout.write('Please choose 1 (recommended), 2 (minimal), or 3 (full).\n')
|
|
633
|
-
}
|
|
596
|
+
const { select } = await import('@inquirer/prompts')
|
|
597
|
+
const target = await askInitTarget(select)
|
|
598
|
+
const preset = await askInitPreset(select)
|
|
599
|
+
return { target, preset }
|
|
634
600
|
}
|
|
635
601
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
return undefined
|
|
602
|
+
async function askInitTarget(
|
|
603
|
+
selectPrompt: (options: { message: string; choices: Array<{ name: string; value: InitTarget }> }) => Promise<InitTarget>
|
|
604
|
+
): Promise<InitTarget> {
|
|
605
|
+
return selectPrompt({
|
|
606
|
+
message: 'Select config target',
|
|
607
|
+
choices: [
|
|
608
|
+
{ name: 'package-json (recommended)', value: 'package-json' },
|
|
609
|
+
{ name: 'file', value: 'file' }
|
|
610
|
+
]
|
|
611
|
+
})
|
|
648
612
|
}
|
|
649
613
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
if (normalized === '3' || normalized === 'full') {
|
|
662
|
-
return 'full'
|
|
663
|
-
}
|
|
664
|
-
return undefined
|
|
614
|
+
async function askInitPreset(
|
|
615
|
+
selectPrompt: (options: { message: string; choices: Array<{ name: string; value: InitPreset }> }) => Promise<InitPreset>
|
|
616
|
+
): Promise<InitPreset> {
|
|
617
|
+
return selectPrompt({
|
|
618
|
+
message: 'Select preset',
|
|
619
|
+
choices: [
|
|
620
|
+
{ name: 'recommended (dynamic catch-all "*" + optional static exceptions)', value: 'recommended' },
|
|
621
|
+
{ name: 'minimal', value: 'minimal' },
|
|
622
|
+
{ name: 'full', value: 'full' }
|
|
623
|
+
]
|
|
624
|
+
})
|
|
665
625
|
}
|
|
666
626
|
|
|
667
627
|
function askQuestion(rl: ReturnType<typeof createInterface>, prompt: string): Promise<string> {
|
|
@@ -763,33 +723,29 @@ async function resolveInitConfigObject(
|
|
|
763
723
|
|
|
764
724
|
async function buildRecommendedPresetObject(cwd: string, nonInteractive: boolean): Promise<Record<string, unknown>> {
|
|
765
725
|
const workspaces = await discoverInitWorkspaces(cwd)
|
|
766
|
-
const
|
|
726
|
+
const useInteractiveGrouping = !nonInteractive && process.stdin.isTTY && process.stdout.isTTY
|
|
727
|
+
const assignments = useInteractiveGrouping ? await askRecommendedGroupAssignments(workspaces) : new Map<string, number>()
|
|
728
|
+
return buildRecommendedConfigFromAssignments(workspaces, assignments)
|
|
729
|
+
}
|
|
767
730
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
731
|
+
export function buildRecommendedConfigFromAssignments(
|
|
732
|
+
workspaces: string[],
|
|
733
|
+
assignments: Map<string, number>
|
|
734
|
+
): Record<string, unknown> {
|
|
735
|
+
const groupNumbers = [...new Set(assignments.values())].sort((a, b) => a - b)
|
|
736
|
+
if (groupNumbers.length === 0) {
|
|
737
|
+
return {}
|
|
774
738
|
}
|
|
775
739
|
|
|
776
|
-
const
|
|
777
|
-
const groups = groupNumbers.map((number) => ({
|
|
740
|
+
const explicitGroups = groupNumbers.map((number) => ({
|
|
778
741
|
name: `group-${number}`,
|
|
779
742
|
match: workspaces.filter((workspace) => assignments.get(workspace) === number)
|
|
780
743
|
}))
|
|
781
744
|
|
|
782
745
|
return {
|
|
783
|
-
workspaceInput: { mode: 'manual', workspaces },
|
|
784
746
|
grouping: {
|
|
785
747
|
mode: 'match',
|
|
786
|
-
groups
|
|
787
|
-
},
|
|
788
|
-
sampling: {
|
|
789
|
-
maxFilesPerWorkspace: 8,
|
|
790
|
-
includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
|
|
791
|
-
excludeGlobs: ['**/node_modules/**', '**/dist/**'],
|
|
792
|
-
hintGlobs: []
|
|
748
|
+
groups: [...explicitGroups, { name: 'default', match: ['**/*'] }]
|
|
793
749
|
}
|
|
794
750
|
}
|
|
795
751
|
}
|
|
@@ -840,28 +796,38 @@ function trimTrailingSlashes(value: string): string {
|
|
|
840
796
|
return normalized
|
|
841
797
|
}
|
|
842
798
|
|
|
843
|
-
async function
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const parsed = Number.parseInt(normalized, 10)
|
|
855
|
-
if (parsed >= 1) {
|
|
856
|
-
return parsed
|
|
857
|
-
}
|
|
858
|
-
}
|
|
799
|
+
async function askRecommendedGroupAssignments(workspaces: string[]): Promise<Map<string, number>> {
|
|
800
|
+
const { checkbox, select } = await import('@inquirer/prompts')
|
|
801
|
+
process.stdout.write(
|
|
802
|
+
'Recommended setup: default group "*" is a dynamic catch-all for every discovered workspace.\n'
|
|
803
|
+
)
|
|
804
|
+
process.stdout.write('Select only workspaces that should move to explicit static groups.\n')
|
|
805
|
+
const overrides = await checkbox<string>({
|
|
806
|
+
message: 'Choose exception workspaces (leave empty to keep all in default "*"):',
|
|
807
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
808
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
809
|
+
})
|
|
859
810
|
|
|
860
|
-
|
|
811
|
+
const assignments = new Map<string, number>()
|
|
812
|
+
let nextGroup = 1
|
|
813
|
+
for (const workspace of overrides) {
|
|
814
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b)
|
|
815
|
+
while (usedGroups.includes(nextGroup)) {
|
|
816
|
+
nextGroup += 1
|
|
861
817
|
}
|
|
862
|
-
|
|
863
|
-
|
|
818
|
+
|
|
819
|
+
const selected = await select<number | 'new'>({
|
|
820
|
+
message: `Select group for ${workspace}`,
|
|
821
|
+
choices: [
|
|
822
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
823
|
+
{ name: `create new group (group-${nextGroup})`, value: 'new' }
|
|
824
|
+
]
|
|
825
|
+
})
|
|
826
|
+
const groupNumber = selected === 'new' ? nextGroup : selected
|
|
827
|
+
assignments.set(workspace, groupNumber)
|
|
864
828
|
}
|
|
829
|
+
|
|
830
|
+
return assignments
|
|
865
831
|
}
|
|
866
832
|
|
|
867
833
|
function toConfigScaffold(configObject: Record<string, unknown>): string {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import {
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { buildRecommendedConfigFromAssignments, runCli } from '../src/index.js'
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await rm(path.join(fixtureRoot, '.eslint-config-snapshot'), { recursive: true, force: true })
|
|
12
|
-
})
|
|
8
|
+
const fixtureTemplateRoot = path.resolve('test/fixtures/repo')
|
|
9
|
+
let tmpDir = ''
|
|
10
|
+
let fixtureRoot = ''
|
|
13
11
|
|
|
14
12
|
beforeEach(async () => {
|
|
15
|
-
await
|
|
13
|
+
tmpDir = await mkdtemp(path.join(os.tmpdir(), 'snapshot-cli-integration-'))
|
|
14
|
+
fixtureRoot = path.join(tmpDir, 'repo')
|
|
15
|
+
await cp(fixtureTemplateRoot, fixtureRoot, { recursive: true })
|
|
16
|
+
|
|
16
17
|
await mkdir(path.join(fixtureRoot, 'packages/ws-a/node_modules/eslint/bin'), { recursive: true })
|
|
17
18
|
await mkdir(path.join(fixtureRoot, 'packages/ws-b/node_modules/eslint/bin'), { recursive: true })
|
|
18
19
|
|
|
@@ -35,26 +36,31 @@ beforeEach(async () => {
|
|
|
35
36
|
)
|
|
36
37
|
})
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
afterEach(async () => {
|
|
40
|
+
if (tmpDir) {
|
|
41
|
+
await rm(tmpDir, { recursive: true, force: true })
|
|
42
|
+
tmpDir = ''
|
|
43
|
+
fixtureRoot = ''
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe.sequential('cli integration', () => {
|
|
48
|
+
it('builds recommended config as dynamic-only when no static overrides are selected', () => {
|
|
49
|
+
const config = buildRecommendedConfigFromAssignments(['packages/ws-a', 'packages/ws-b'], new Map())
|
|
50
|
+
expect(config).toEqual({})
|
|
47
51
|
})
|
|
48
52
|
|
|
49
|
-
it('
|
|
50
|
-
|
|
51
|
-
expect(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
it('builds recommended config with static overrides plus dynamic catch-all', () => {
|
|
54
|
+
const config = buildRecommendedConfigFromAssignments(['packages/ws-a', 'packages/ws-b'], new Map([['packages/ws-b', 2]]))
|
|
55
|
+
expect(config).toEqual({
|
|
56
|
+
grouping: {
|
|
57
|
+
mode: 'match',
|
|
58
|
+
groups: [
|
|
59
|
+
{ name: 'group-2', match: ['packages/ws-b'] },
|
|
60
|
+
{ name: 'default', match: ['**/*'] }
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
58
64
|
})
|
|
59
65
|
|
|
60
66
|
it('snapshot writes deterministic snapshot files', async () => {
|
|
@@ -275,20 +275,27 @@ no-debugger: off
|
|
|
275
275
|
|
|
276
276
|
const packageJsonRaw = await readFile(path.join(repoRoot, 'package.json'), 'utf8')
|
|
277
277
|
const parsed = JSON.parse(packageJsonRaw) as {
|
|
278
|
-
'eslint-config-snapshot'?:
|
|
279
|
-
workspaceInput?: { mode?: string; workspaces?: string[] }
|
|
280
|
-
grouping?: { mode?: string; groups?: Array<{ name: string; match: string[] }> }
|
|
281
|
-
}
|
|
278
|
+
'eslint-config-snapshot'?: Record<string, unknown>
|
|
282
279
|
}
|
|
283
280
|
|
|
284
|
-
expect(parsed['eslint-config-snapshot']
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
expect(parsed['eslint-config-snapshot']).toEqual({})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('init recommended --show-effective prints preview without explicit sampling block', async () => {
|
|
285
|
+
const initRoot = path.join(tmpDir, 'init-recommended-preview-case')
|
|
286
|
+
await rm(initRoot, { recursive: true, force: true })
|
|
287
|
+
await cp(fixtureRoot, initRoot, { recursive: true })
|
|
288
|
+
repoRoot = initRoot
|
|
289
|
+
|
|
290
|
+
await rm(path.join(repoRoot, 'eslint-config-snapshot.config.mjs'), { force: true })
|
|
291
|
+
|
|
292
|
+
const result = run(['init', '--yes', '--target', 'package-json', '--preset', 'recommended', '--show-effective'])
|
|
293
|
+
expect(result.status).toBe(0)
|
|
294
|
+
expect(result.stdout).toContain('Effective config preview:')
|
|
295
|
+
expect(result.stdout).toContain('{}')
|
|
296
|
+
expect(result.stdout).not.toContain('"workspaceInput"')
|
|
297
|
+
expect(result.stdout).not.toContain('"grouping"')
|
|
298
|
+
expect(result.stdout).not.toContain('"sampling"')
|
|
292
299
|
})
|
|
293
300
|
|
|
294
301
|
it('init fails early on existing config unless --force is provided', async () => {
|
|
@@ -376,14 +383,13 @@ no-debugger: off
|
|
|
376
383
|
expect(result.stderr).toBe('')
|
|
377
384
|
})
|
|
378
385
|
|
|
379
|
-
it('prints init help with
|
|
386
|
+
it('prints init help with select-prompt and force guidance', () => {
|
|
380
387
|
const result = run(['init', '--help'])
|
|
381
388
|
expect(result.status).toBe(0)
|
|
382
389
|
expect(result.stdout).toContain('Initialize config (file or package.json)')
|
|
383
390
|
expect(result.stdout).toContain('-f, --force')
|
|
384
|
-
expect(result.stdout).toContain('Runs interactive
|
|
385
|
-
expect(result.stdout).toContain('
|
|
386
|
-
expect(result.stdout).toContain('preset: 1) recommended, 2) minimal, 3) full')
|
|
391
|
+
expect(result.stdout).toContain('Runs interactive select prompts for target/preset.')
|
|
392
|
+
expect(result.stdout).toContain('Recommended preset keeps a dynamic catch-all default group ("*")')
|
|
387
393
|
expect(result.stdout).toContain('--show-effective')
|
|
388
394
|
expect(result.stdout).toContain('--yes --force --target file --preset full')
|
|
389
395
|
expect(result.stderr).toBe('')
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log(JSON.stringify({ rules: { 'no-console': 1, eqeqeq: [2, 'always'] } }))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log(JSON.stringify({ rules: { 'no-console': 2, 'no-debugger': 0 } }))
|