@eslint-config-snapshot/cli 0.2.0 → 0.3.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 +11 -0
- package/README.md +8 -0
- package/dist/index.cjs +60 -105
- package/dist/index.js +59 -103
- package/package.json +3 -2
- package/src/index.ts +66 -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,16 @@
|
|
|
1
1
|
# @eslint-config-snapshot/cli
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Release minor with inquirer-based init UX and recommended dynamic grouping behavior.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @eslint-config-snapshot/api@0.3.0
|
|
13
|
+
|
|
3
14
|
## 0.2.0
|
|
4
15
|
|
|
5
16
|
### 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 uses checkbox selection for non-default workspaces and group selection.
|
|
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 (default group "*" + static overrides)', 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,32 @@ 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
|
-
process.stdout.write("Please provide a positive integer (1, 2, 3, ...).\n");
|
|
635
|
+
async function askRecommendedGroupAssignments(workspaces) {
|
|
636
|
+
const { checkbox, select } = await import("@inquirer/prompts");
|
|
637
|
+
process.stdout.write('Recommended setup: select only workspaces that should leave default group "*".\n');
|
|
638
|
+
const overrides = await checkbox({
|
|
639
|
+
message: "Workspaces outside default group:",
|
|
640
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
641
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
642
|
+
});
|
|
643
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
644
|
+
let nextGroup = 1;
|
|
645
|
+
for (const workspace of overrides) {
|
|
646
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
647
|
+
while (usedGroups.includes(nextGroup)) {
|
|
648
|
+
nextGroup += 1;
|
|
701
649
|
}
|
|
702
|
-
|
|
703
|
-
|
|
650
|
+
const selected = await select({
|
|
651
|
+
message: `Select group for ${workspace}`,
|
|
652
|
+
choices: [
|
|
653
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
654
|
+
{ name: `create new group (group-${nextGroup})`, value: "new" }
|
|
655
|
+
]
|
|
656
|
+
});
|
|
657
|
+
const groupNumber = selected === "new" ? nextGroup : selected;
|
|
658
|
+
assignments.set(workspace, groupNumber);
|
|
704
659
|
}
|
|
660
|
+
return assignments;
|
|
705
661
|
}
|
|
706
662
|
function toConfigScaffold(configObject) {
|
|
707
663
|
if (Object.keys(configObject).length === 0) {
|
|
@@ -876,8 +832,7 @@ function formatShortConfig(payload) {
|
|
|
876
832
|
}
|
|
877
833
|
// Annotate the CommonJS export names for ESM import in node:
|
|
878
834
|
0 && (module.exports = {
|
|
835
|
+
buildRecommendedConfigFromAssignments,
|
|
879
836
|
main,
|
|
880
|
-
parseInitPresetChoice,
|
|
881
|
-
parseInitTargetChoice,
|
|
882
837
|
runCli
|
|
883
838
|
});
|
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 uses checkbox selection for non-default workspaces and group selection.
|
|
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 (default group "*" + static overrides)', 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,32 @@ 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
|
-
process.stdout.write("Please provide a positive integer (1, 2, 3, ...).\n");
|
|
615
|
+
async function askRecommendedGroupAssignments(workspaces) {
|
|
616
|
+
const { checkbox, select } = await import("@inquirer/prompts");
|
|
617
|
+
process.stdout.write('Recommended setup: select only workspaces that should leave default group "*".\n');
|
|
618
|
+
const overrides = await checkbox({
|
|
619
|
+
message: "Workspaces outside default group:",
|
|
620
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
621
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
622
|
+
});
|
|
623
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
624
|
+
let nextGroup = 1;
|
|
625
|
+
for (const workspace of overrides) {
|
|
626
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b);
|
|
627
|
+
while (usedGroups.includes(nextGroup)) {
|
|
628
|
+
nextGroup += 1;
|
|
680
629
|
}
|
|
681
|
-
|
|
682
|
-
|
|
630
|
+
const selected = await select({
|
|
631
|
+
message: `Select group for ${workspace}`,
|
|
632
|
+
choices: [
|
|
633
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
634
|
+
{ name: `create new group (group-${nextGroup})`, value: "new" }
|
|
635
|
+
]
|
|
636
|
+
});
|
|
637
|
+
const groupNumber = selected === "new" ? nextGroup : selected;
|
|
638
|
+
assignments.set(workspace, groupNumber);
|
|
683
639
|
}
|
|
640
|
+
return assignments;
|
|
684
641
|
}
|
|
685
642
|
function toConfigScaffold(configObject) {
|
|
686
643
|
if (Object.keys(configObject).length === 0) {
|
|
@@ -854,8 +811,7 @@ function formatShortConfig(payload) {
|
|
|
854
811
|
`;
|
|
855
812
|
}
|
|
856
813
|
export {
|
|
814
|
+
buildRecommendedConfigFromAssignments,
|
|
857
815
|
main,
|
|
858
|
-
parseInitPresetChoice,
|
|
859
|
-
parseInitTargetChoice,
|
|
860
816
|
runCli
|
|
861
817
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-config-snapshot/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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.
|
|
33
|
+
"@eslint-config-snapshot/api": "0.3.0"
|
|
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 uses checkbox selection for non-default workspaces and group selection.
|
|
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
|
-
}
|
|
596
|
+
const { select } = await import('@inquirer/prompts')
|
|
597
|
+
const target = await askInitTarget(select)
|
|
598
|
+
const preset = await askInitPreset(select)
|
|
599
|
+
return { target, preset }
|
|
606
600
|
}
|
|
607
601
|
|
|
608
|
-
async function askInitTarget(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
export function parseInitTargetChoice(value: string): InitTarget | undefined {
|
|
637
|
-
const normalized = value.trim().toLowerCase()
|
|
638
|
-
if (normalized === '') {
|
|
639
|
-
return 'package-json'
|
|
640
|
-
}
|
|
641
|
-
if (normalized === '1' || normalized === 'package-json' || normalized === 'packagejson' || normalized === 'package' || normalized === 'pkg') {
|
|
642
|
-
return 'package-json'
|
|
643
|
-
}
|
|
644
|
-
if (normalized === '2' || normalized === 'file') {
|
|
645
|
-
return 'file'
|
|
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 (default group "*" + static overrides)', 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,35 @@ 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
|
-
if (/^\d+$/.test(normalized)) {
|
|
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('Recommended setup: select only workspaces that should leave default group "*".\n')
|
|
802
|
+
const overrides = await checkbox<string>({
|
|
803
|
+
message: 'Workspaces outside default group:',
|
|
804
|
+
choices: workspaces.map((workspace) => ({ name: workspace, value: workspace })),
|
|
805
|
+
pageSize: Math.min(12, Math.max(4, workspaces.length))
|
|
806
|
+
})
|
|
859
807
|
|
|
860
|
-
|
|
808
|
+
const assignments = new Map<string, number>()
|
|
809
|
+
let nextGroup = 1
|
|
810
|
+
for (const workspace of overrides) {
|
|
811
|
+
const usedGroups = [...new Set(assignments.values())].sort((a, b) => a - b)
|
|
812
|
+
while (usedGroups.includes(nextGroup)) {
|
|
813
|
+
nextGroup += 1
|
|
861
814
|
}
|
|
862
|
-
|
|
863
|
-
|
|
815
|
+
|
|
816
|
+
const selected = await select<number | 'new'>({
|
|
817
|
+
message: `Select group for ${workspace}`,
|
|
818
|
+
choices: [
|
|
819
|
+
...usedGroups.map((group) => ({ name: `group-${group}`, value: group })),
|
|
820
|
+
{ name: `create new group (group-${nextGroup})`, value: 'new' }
|
|
821
|
+
]
|
|
822
|
+
})
|
|
823
|
+
const groupNumber = selected === 'new' ? nextGroup : selected
|
|
824
|
+
assignments.set(workspace, groupNumber)
|
|
864
825
|
}
|
|
826
|
+
|
|
827
|
+
return assignments
|
|
865
828
|
}
|
|
866
829
|
|
|
867
830
|
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 uses checkbox selection')
|
|
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 } }))
|