create-kumiko-app 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/src/picker.ts ADDED
@@ -0,0 +1,76 @@
1
+ // Interactive feature picker. Renders the vendored manifest as a grouped
2
+ // multi-select (by uiHints.category), default-checks features marked
3
+ // recommended:true, and only offers features that have a constructor entry
4
+ // in FEATURE_CONSTRUCTORS (the rest aren't mountable yet, hidden until
5
+ // fast-follow lands their constructor).
6
+ //
7
+ // Output is the user-confirmed selection; the caller (cli.ts) feeds it
8
+ // through resolveDeps + maps each name to its ScaffoldFeatureEntry.
9
+
10
+ import { checkbox, Separator } from "@inquirer/prompts";
11
+ import { FEATURE_CONSTRUCTORS } from "./feature-constructors";
12
+ import type { Manifest, ManifestFeatureEntry } from "./manifest";
13
+
14
+ export type PickerChoice = {
15
+ readonly name: string;
16
+ readonly displayLabel: string;
17
+ readonly category: string;
18
+ readonly recommended: boolean;
19
+ readonly description: string | null;
20
+ };
21
+
22
+ export function buildChoices(manifest: Manifest): readonly PickerChoice[] {
23
+ return manifest.features
24
+ .filter((f) => Object.hasOwn(FEATURE_CONSTRUCTORS, f.name))
25
+ .map((f) => toChoice(f));
26
+ }
27
+
28
+ function toChoice(f: ManifestFeatureEntry): PickerChoice {
29
+ return {
30
+ name: f.name,
31
+ displayLabel: f.uiHints?.displayLabel ?? f.name,
32
+ category: f.uiHints?.category ?? "other",
33
+ recommended: f.uiHints?.recommended ?? false,
34
+ description: f.description,
35
+ };
36
+ }
37
+
38
+ export async function runPicker(manifest: Manifest): Promise<readonly string[]> {
39
+ const choices = buildChoices(manifest);
40
+ const grouped = groupByCategory(choices);
41
+ const items: Array<
42
+ { name: string; value: string; checked: boolean } | InstanceType<typeof Separator>
43
+ > = [];
44
+ for (const [category, group] of grouped) {
45
+ items.push(new Separator(`── ${category} ──`));
46
+ for (const c of group) {
47
+ items.push({
48
+ name: `${c.displayLabel}${c.recommended ? " (recommended)" : ""}`,
49
+ value: c.name,
50
+ checked: c.recommended,
51
+ });
52
+ }
53
+ }
54
+ const selected = await checkbox({
55
+ message: "Welche Features?",
56
+ choices: items,
57
+ pageSize: 20,
58
+ loop: false,
59
+ });
60
+ return selected;
61
+ }
62
+
63
+ function groupByCategory(
64
+ choices: readonly PickerChoice[],
65
+ ): ReadonlyArray<readonly [string, readonly PickerChoice[]]> {
66
+ const buckets = new Map<string, PickerChoice[]>();
67
+ for (const c of choices) {
68
+ const list = buckets.get(c.category) ?? [];
69
+ list.push(c);
70
+ buckets.set(c.category, list);
71
+ }
72
+ return [...buckets.entries()].map(([cat, list]) => [
73
+ cat,
74
+ [...list].sort((a, b) => a.displayLabel.localeCompare(b.displayLabel)),
75
+ ]);
76
+ }