@claude-collective/cli 0.2.0 → 0.8.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 +178 -0
- package/README.md +1 -1
- package/dist/chunk-3HBTELJN.js +114 -0
- package/dist/chunk-3HBTELJN.js.map +1 -0
- package/dist/chunk-3ZCB5K33.js +54 -0
- package/dist/chunk-3ZCB5K33.js.map +1 -0
- package/dist/chunk-66UDJBF6.js +96 -0
- package/dist/chunk-66UDJBF6.js.map +1 -0
- package/dist/chunk-6LS7XO3H.js +31 -0
- package/dist/chunk-6LS7XO3H.js.map +1 -0
- package/dist/chunk-A3J6IAXK.js +57 -0
- package/dist/chunk-A3J6IAXK.js.map +1 -0
- package/dist/chunk-A65SBAAJ.js +69 -0
- package/dist/chunk-A65SBAAJ.js.map +1 -0
- package/dist/chunk-ALEPJ6YN.js +80 -0
- package/dist/chunk-ALEPJ6YN.js.map +1 -0
- package/dist/chunk-C4ZTIYFR.js +84 -0
- package/dist/chunk-C4ZTIYFR.js.map +1 -0
- package/dist/chunk-CIY5UBRB.js +453 -0
- package/dist/chunk-CIY5UBRB.js.map +1 -0
- package/dist/chunk-DHET7RCE.js +50 -0
- package/dist/chunk-DHET7RCE.js.map +1 -0
- package/dist/chunk-DHFFRMF6.js +31 -0
- package/dist/chunk-DHFFRMF6.js.map +1 -0
- package/dist/chunk-DKGL77IY.js +307 -0
- package/dist/chunk-DKGL77IY.js.map +1 -0
- package/dist/chunk-ED73HCW2.js +315 -0
- package/dist/chunk-ED73HCW2.js.map +1 -0
- package/dist/chunk-FNOYEXUE.js +308 -0
- package/dist/chunk-FNOYEXUE.js.map +1 -0
- package/dist/chunk-G2FBJOZG.js +141 -0
- package/dist/chunk-G2FBJOZG.js.map +1 -0
- package/dist/chunk-HNDT5QRB.js +120 -0
- package/dist/chunk-HNDT5QRB.js.map +1 -0
- package/dist/chunk-K7PTOVX4.js +158 -0
- package/dist/chunk-K7PTOVX4.js.map +1 -0
- package/dist/chunk-LQTST4WY.js +91 -0
- package/dist/chunk-LQTST4WY.js.map +1 -0
- package/dist/chunk-LVKRVFYR.js +54 -0
- package/dist/chunk-LVKRVFYR.js.map +1 -0
- package/dist/chunk-M7YCPFIX.js +108 -0
- package/dist/chunk-M7YCPFIX.js.map +1 -0
- package/dist/chunk-MJSFR562.js +57 -0
- package/dist/chunk-MJSFR562.js.map +1 -0
- package/dist/chunk-MMDXNZPF.js +69 -0
- package/dist/chunk-MMDXNZPF.js.map +1 -0
- package/dist/chunk-MYAVQ23U.js +356 -0
- package/dist/chunk-MYAVQ23U.js.map +1 -0
- package/dist/chunk-NGBFJJ7Q.js +124 -0
- package/dist/chunk-NGBFJJ7Q.js.map +1 -0
- package/dist/chunk-OLBOTK3O.js +64 -0
- package/dist/chunk-OLBOTK3O.js.map +1 -0
- package/dist/chunk-PPNTD5LO.js +330 -0
- package/dist/chunk-PPNTD5LO.js.map +1 -0
- package/dist/chunk-Q2LH2DAB.js +392 -0
- package/dist/chunk-Q2LH2DAB.js.map +1 -0
- package/dist/chunk-Q6DR5QUH.js +547 -0
- package/dist/chunk-Q6DR5QUH.js.map +1 -0
- package/dist/chunk-QESUUPOE.js +241 -0
- package/dist/chunk-QESUUPOE.js.map +1 -0
- package/dist/chunk-QGGSLMO3.js +607 -0
- package/dist/chunk-QGGSLMO3.js.map +1 -0
- package/dist/chunk-SEBPPFUW.js +478 -0
- package/dist/chunk-SEBPPFUW.js.map +1 -0
- package/dist/chunk-SYQ7R2JO.js +95 -0
- package/dist/chunk-SYQ7R2JO.js.map +1 -0
- package/dist/chunk-TOPAIL5W.js +22 -0
- package/dist/chunk-TOPAIL5W.js.map +1 -0
- package/dist/chunk-U4VYHKPM.js +110 -0
- package/dist/chunk-U4VYHKPM.js.map +1 -0
- package/dist/chunk-UOWHJ6BE.js +83 -0
- package/dist/chunk-UOWHJ6BE.js.map +1 -0
- package/dist/chunk-XKEG3SCV.js +86 -0
- package/dist/chunk-XKEG3SCV.js.map +1 -0
- package/dist/chunk-XY3XDVMI.js +15599 -0
- package/dist/chunk-XY3XDVMI.js.map +1 -0
- package/dist/chunk-Y3V43XCU.js +76 -0
- package/dist/chunk-Y3V43XCU.js.map +1 -0
- package/dist/chunk-YKXBGCFD.js +129 -0
- package/dist/chunk-YKXBGCFD.js.map +1 -0
- package/dist/cli-v2/defaults/agent-mappings.yaml +185 -0
- package/dist/commands/build/marketplace.js +254 -0
- package/dist/commands/build/marketplace.js.map +1 -0
- package/dist/commands/build/plugins.js +324 -0
- package/dist/commands/build/plugins.js.map +1 -0
- package/dist/commands/build/stack.js +169 -0
- package/dist/commands/build/stack.js.map +1 -0
- package/dist/commands/compile.js +461 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/config/get.js +60 -0
- package/dist/commands/config/get.js.map +1 -0
- package/dist/commands/config/index.js +22 -0
- package/dist/commands/config/index.js.map +1 -0
- package/dist/commands/config/path.js +35 -0
- package/dist/commands/config/path.js.map +1 -0
- package/dist/commands/config/set-project.js +61 -0
- package/dist/commands/config/set-project.js.map +1 -0
- package/dist/commands/config/set.js +60 -0
- package/dist/commands/config/set.js.map +1 -0
- package/dist/commands/config/show.js +13 -0
- package/dist/commands/config/show.js.map +1 -0
- package/dist/commands/config/unset-project.js +57 -0
- package/dist/commands/config/unset-project.js.map +1 -0
- package/dist/commands/config/unset.js +56 -0
- package/dist/commands/config/unset.js.map +1 -0
- package/dist/commands/diff.js +755 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/doctor.js +413 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/edit.js +254 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/eject.js +208 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/info.js +205 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.js +915 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.js +44 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/new/agent.js +230 -0
- package/dist/commands/new/agent.js.map +1 -0
- package/dist/commands/new/skill.js +204 -0
- package/dist/commands/new/skill.js.map +1 -0
- package/dist/commands/outdated.js +242 -0
- package/dist/commands/outdated.js.map +1 -0
- package/dist/commands/search.js +115 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/test-imports.js +92 -0
- package/dist/commands/test-imports.js.map +1 -0
- package/dist/commands/uninstall.js +309 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.js +428 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.js +375 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/version/bump.js +95 -0
- package/dist/commands/version/bump.js.map +1 -0
- package/dist/commands/version/index.js +70 -0
- package/dist/commands/version/index.js.map +1 -0
- package/dist/commands/version/set.js +101 -0
- package/dist/commands/version/set.js.map +1 -0
- package/dist/commands/version/show.js +70 -0
- package/dist/commands/version/show.js.map +1 -0
- package/dist/components/common/confirm.js +9 -0
- package/dist/components/common/confirm.js.map +1 -0
- package/dist/components/common/message.js +24 -0
- package/dist/components/common/message.js.map +1 -0
- package/dist/components/common/spinner.js +14 -0
- package/dist/components/common/spinner.js.map +1 -0
- package/dist/components/wizard/category-grid.js +9 -0
- package/dist/components/wizard/category-grid.js.map +1 -0
- package/dist/components/wizard/category-grid.test.js +728 -0
- package/dist/components/wizard/category-grid.test.js.map +1 -0
- package/dist/components/wizard/section-progress.js +9 -0
- package/dist/components/wizard/section-progress.js.map +1 -0
- package/dist/components/wizard/section-progress.test.js +281 -0
- package/dist/components/wizard/section-progress.test.js.map +1 -0
- package/dist/components/wizard/step-approach.js +11 -0
- package/dist/components/wizard/step-approach.js.map +1 -0
- package/dist/components/wizard/step-build.js +15 -0
- package/dist/components/wizard/step-build.js.map +1 -0
- package/dist/components/wizard/step-build.test.js +729 -0
- package/dist/components/wizard/step-build.test.js.map +1 -0
- package/dist/components/wizard/step-confirm.js +9 -0
- package/dist/components/wizard/step-confirm.js.map +1 -0
- package/dist/components/wizard/step-refine.js +9 -0
- package/dist/components/wizard/step-refine.js.map +1 -0
- package/dist/components/wizard/step-refine.test.js +235 -0
- package/dist/components/wizard/step-refine.test.js.map +1 -0
- package/dist/components/wizard/step-stack-options.js +11 -0
- package/dist/components/wizard/step-stack-options.js.map +1 -0
- package/dist/components/wizard/step-stack.js +11 -0
- package/dist/components/wizard/step-stack.js.map +1 -0
- package/dist/components/wizard/wizard-tabs.js +11 -0
- package/dist/components/wizard/wizard-tabs.js.map +1 -0
- package/dist/components/wizard/wizard.js +20 -0
- package/dist/components/wizard/wizard.js.map +1 -0
- package/dist/hooks/init.js +41 -0
- package/dist/hooks/init.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/magic-string.es-RGXYGAW3.js +1316 -0
- package/dist/magic-string.es-RGXYGAW3.js.map +1 -0
- package/dist/stores/wizard-store.js +10 -0
- package/dist/stores/wizard-store.js.map +1 -0
- package/dist/stores/wizard-store.test.js +405 -0
- package/dist/stores/wizard-store.test.js.map +1 -0
- package/package.json +44 -25
- package/dist/cli/index.js +0 -6314
- package/dist/cli/index.js.map +0 -1
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ENTER,
|
|
4
|
+
ESCAPE,
|
|
5
|
+
INPUT_DELAY_MS,
|
|
6
|
+
RENDER_DELAY_MS,
|
|
7
|
+
delay
|
|
8
|
+
} from "../../chunk-6LS7XO3H.js";
|
|
9
|
+
import {
|
|
10
|
+
render
|
|
11
|
+
} from "../../chunk-66UDJBF6.js";
|
|
12
|
+
import {
|
|
13
|
+
afterEach,
|
|
14
|
+
describe,
|
|
15
|
+
globalExpect,
|
|
16
|
+
it,
|
|
17
|
+
vi
|
|
18
|
+
} from "../../chunk-XY3XDVMI.js";
|
|
19
|
+
import {
|
|
20
|
+
StepBuild,
|
|
21
|
+
getDisplayLabel,
|
|
22
|
+
validateBuildStep
|
|
23
|
+
} from "../../chunk-Q6DR5QUH.js";
|
|
24
|
+
import "../../chunk-LVKRVFYR.js";
|
|
25
|
+
import "../../chunk-PPNTD5LO.js";
|
|
26
|
+
import {
|
|
27
|
+
init_esm_shims
|
|
28
|
+
} from "../../chunk-DHET7RCE.js";
|
|
29
|
+
|
|
30
|
+
// src/cli-v2/components/wizard/step-build.test.tsx
|
|
31
|
+
init_esm_shims();
|
|
32
|
+
import { jsx } from "react/jsx-runtime";
|
|
33
|
+
var createCategory = (id, name, overrides = {}) => ({
|
|
34
|
+
id,
|
|
35
|
+
name,
|
|
36
|
+
description: `${name} category`,
|
|
37
|
+
exclusive: true,
|
|
38
|
+
required: false,
|
|
39
|
+
order: 0,
|
|
40
|
+
...overrides
|
|
41
|
+
});
|
|
42
|
+
var createSkill = (id, name, category, overrides = {}) => ({
|
|
43
|
+
id,
|
|
44
|
+
name,
|
|
45
|
+
description: `${name} skill`,
|
|
46
|
+
category,
|
|
47
|
+
categoryExclusive: true,
|
|
48
|
+
tags: [],
|
|
49
|
+
author: "test",
|
|
50
|
+
conflictsWith: [],
|
|
51
|
+
recommends: [],
|
|
52
|
+
recommendedBy: [],
|
|
53
|
+
requires: [],
|
|
54
|
+
requiredBy: [],
|
|
55
|
+
alternatives: [],
|
|
56
|
+
discourages: [],
|
|
57
|
+
requiresSetup: [],
|
|
58
|
+
providesSetupFor: [],
|
|
59
|
+
path: `test/${id}`,
|
|
60
|
+
...overrides
|
|
61
|
+
});
|
|
62
|
+
var createTestMatrix = (categories, skills) => ({
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
categories: Object.fromEntries(categories.map((c) => [c.id, c])),
|
|
65
|
+
skills: Object.fromEntries(skills.map((s) => [s.id, s])),
|
|
66
|
+
suggestedStacks: [],
|
|
67
|
+
aliases: Object.fromEntries(
|
|
68
|
+
skills.filter((s) => s.alias).map((s) => [s.alias, s.id])
|
|
69
|
+
),
|
|
70
|
+
aliasesReverse: Object.fromEntries(
|
|
71
|
+
skills.filter((s) => s.alias).map((s) => [s.id, s.alias])
|
|
72
|
+
),
|
|
73
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74
|
+
});
|
|
75
|
+
var webCategory = createCategory("web", "Web", {
|
|
76
|
+
description: "Web development",
|
|
77
|
+
order: 0
|
|
78
|
+
});
|
|
79
|
+
var apiCategory = createCategory("api", "API", {
|
|
80
|
+
description: "API development",
|
|
81
|
+
order: 1
|
|
82
|
+
});
|
|
83
|
+
var frameworkCategory = createCategory("framework", "Framework", {
|
|
84
|
+
parent: "web",
|
|
85
|
+
domain: "web",
|
|
86
|
+
required: true,
|
|
87
|
+
order: 0
|
|
88
|
+
});
|
|
89
|
+
var stylingCategory = createCategory("styling", "Styling", {
|
|
90
|
+
parent: "web",
|
|
91
|
+
domain: "web",
|
|
92
|
+
required: true,
|
|
93
|
+
order: 1
|
|
94
|
+
});
|
|
95
|
+
var stateCategory = createCategory("client-state", "Client State", {
|
|
96
|
+
parent: "web",
|
|
97
|
+
domain: "web",
|
|
98
|
+
required: false,
|
|
99
|
+
order: 2
|
|
100
|
+
});
|
|
101
|
+
var apiFrameworkCategory = createCategory("api-framework", "API Framework", {
|
|
102
|
+
parent: "api",
|
|
103
|
+
domain: "api",
|
|
104
|
+
required: true,
|
|
105
|
+
order: 0
|
|
106
|
+
});
|
|
107
|
+
var databaseCategory = createCategory("database", "Database", {
|
|
108
|
+
parent: "api",
|
|
109
|
+
domain: "api",
|
|
110
|
+
required: false,
|
|
111
|
+
order: 1
|
|
112
|
+
});
|
|
113
|
+
var reactSkill = createSkill("react (@vince)", "React", "framework", {
|
|
114
|
+
alias: "react"
|
|
115
|
+
});
|
|
116
|
+
var vueSkill = createSkill("vue (@vince)", "Vue", "framework", {
|
|
117
|
+
alias: "vue"
|
|
118
|
+
});
|
|
119
|
+
var tailwindSkill = createSkill("tailwind (@vince)", "Tailwind", "styling", {
|
|
120
|
+
alias: "tailwind"
|
|
121
|
+
});
|
|
122
|
+
var scssSkill = createSkill("scss (@vince)", "SCSS Modules", "styling", {
|
|
123
|
+
alias: "scss"
|
|
124
|
+
});
|
|
125
|
+
var zustandSkill = createSkill(
|
|
126
|
+
"zustand (@vince)",
|
|
127
|
+
"Zustand",
|
|
128
|
+
"client-state",
|
|
129
|
+
{
|
|
130
|
+
alias: "zustand"
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
var honoSkill = createSkill("hono (@vince)", "Hono", "api-framework", {
|
|
134
|
+
alias: "hono"
|
|
135
|
+
});
|
|
136
|
+
var expressSkill = createSkill(
|
|
137
|
+
"express (@vince)",
|
|
138
|
+
"Express",
|
|
139
|
+
"api-framework",
|
|
140
|
+
{
|
|
141
|
+
alias: "express"
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
var postgresSkill = createSkill(
|
|
145
|
+
"postgres (@vince)",
|
|
146
|
+
"PostgreSQL",
|
|
147
|
+
"database",
|
|
148
|
+
{
|
|
149
|
+
alias: "postgres"
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
var defaultMatrix = createTestMatrix(
|
|
153
|
+
[
|
|
154
|
+
webCategory,
|
|
155
|
+
apiCategory,
|
|
156
|
+
frameworkCategory,
|
|
157
|
+
stylingCategory,
|
|
158
|
+
stateCategory,
|
|
159
|
+
apiFrameworkCategory,
|
|
160
|
+
databaseCategory
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
reactSkill,
|
|
164
|
+
vueSkill,
|
|
165
|
+
tailwindSkill,
|
|
166
|
+
scssSkill,
|
|
167
|
+
zustandSkill,
|
|
168
|
+
honoSkill,
|
|
169
|
+
expressSkill,
|
|
170
|
+
postgresSkill
|
|
171
|
+
]
|
|
172
|
+
);
|
|
173
|
+
var defaultProps = {
|
|
174
|
+
matrix: defaultMatrix,
|
|
175
|
+
domain: "web",
|
|
176
|
+
selectedDomains: ["web"],
|
|
177
|
+
currentDomainIndex: 0,
|
|
178
|
+
selections: {},
|
|
179
|
+
allSelections: [],
|
|
180
|
+
focusedRow: 0,
|
|
181
|
+
focusedCol: 0,
|
|
182
|
+
showDescriptions: false,
|
|
183
|
+
expertMode: false,
|
|
184
|
+
onToggle: vi.fn(),
|
|
185
|
+
onFocusChange: vi.fn(),
|
|
186
|
+
onToggleDescriptions: vi.fn(),
|
|
187
|
+
onToggleExpertMode: vi.fn(),
|
|
188
|
+
onContinue: vi.fn(),
|
|
189
|
+
onBack: vi.fn()
|
|
190
|
+
};
|
|
191
|
+
var renderStepBuild = (props = {}) => {
|
|
192
|
+
return render(/* @__PURE__ */ jsx(StepBuild, { ...defaultProps, ...props }));
|
|
193
|
+
};
|
|
194
|
+
describe("StepBuild component", () => {
|
|
195
|
+
let cleanup;
|
|
196
|
+
afterEach(() => {
|
|
197
|
+
cleanup?.();
|
|
198
|
+
cleanup = void 0;
|
|
199
|
+
vi.clearAllMocks();
|
|
200
|
+
});
|
|
201
|
+
describe("rendering", () => {
|
|
202
|
+
it("should render CategoryGrid with correct categories for domain", () => {
|
|
203
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
204
|
+
cleanup = unmount;
|
|
205
|
+
const output = lastFrame();
|
|
206
|
+
globalExpect(output).toContain("Framework");
|
|
207
|
+
globalExpect(output).toContain("Styling");
|
|
208
|
+
globalExpect(output).toContain("Client State");
|
|
209
|
+
globalExpect(output).not.toContain("API Framework");
|
|
210
|
+
globalExpect(output).not.toContain("Database");
|
|
211
|
+
});
|
|
212
|
+
it("should render API categories when domain is api", () => {
|
|
213
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
214
|
+
domain: "api"
|
|
215
|
+
});
|
|
216
|
+
cleanup = unmount;
|
|
217
|
+
const output = lastFrame();
|
|
218
|
+
globalExpect(output).toContain("API Framework");
|
|
219
|
+
globalExpect(output).toContain("Database");
|
|
220
|
+
globalExpect(output).not.toContain("Styling");
|
|
221
|
+
globalExpect(output).not.toContain("Client State");
|
|
222
|
+
});
|
|
223
|
+
it("should render skills as options", () => {
|
|
224
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
225
|
+
cleanup = unmount;
|
|
226
|
+
const output = lastFrame();
|
|
227
|
+
globalExpect(output).toContain("React");
|
|
228
|
+
globalExpect(output).toContain("Vue");
|
|
229
|
+
globalExpect(output).toContain("Tailwind");
|
|
230
|
+
globalExpect(output).toContain("SCSS");
|
|
231
|
+
});
|
|
232
|
+
it("should show required indicator (*) for required categories", () => {
|
|
233
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
234
|
+
cleanup = unmount;
|
|
235
|
+
const output = lastFrame();
|
|
236
|
+
globalExpect(output).toContain("*");
|
|
237
|
+
});
|
|
238
|
+
it("should show footer with keyboard hints", () => {
|
|
239
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
240
|
+
cleanup = unmount;
|
|
241
|
+
const output = lastFrame();
|
|
242
|
+
globalExpect(output).toContain("ESC");
|
|
243
|
+
globalExpect(output).toContain("ENTER");
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe("progress indicator", () => {
|
|
247
|
+
it("should show SectionProgress when multiple domains selected", () => {
|
|
248
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
249
|
+
selectedDomains: ["web", "api"],
|
|
250
|
+
currentDomainIndex: 0
|
|
251
|
+
});
|
|
252
|
+
cleanup = unmount;
|
|
253
|
+
const output = lastFrame();
|
|
254
|
+
globalExpect(output).toContain("Domain:");
|
|
255
|
+
globalExpect(output).toContain("Web");
|
|
256
|
+
globalExpect(output).toContain("[1/2]");
|
|
257
|
+
globalExpect(output).toContain("Next: API");
|
|
258
|
+
});
|
|
259
|
+
it("should hide SectionProgress when single domain selected", () => {
|
|
260
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
261
|
+
selectedDomains: ["web"],
|
|
262
|
+
currentDomainIndex: 0
|
|
263
|
+
});
|
|
264
|
+
cleanup = unmount;
|
|
265
|
+
const output = lastFrame();
|
|
266
|
+
globalExpect(output).not.toContain("[1/1]");
|
|
267
|
+
});
|
|
268
|
+
it("should show 'Last step' when on final domain", () => {
|
|
269
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
270
|
+
selectedDomains: ["web", "api"],
|
|
271
|
+
domain: "api",
|
|
272
|
+
currentDomainIndex: 1
|
|
273
|
+
});
|
|
274
|
+
cleanup = unmount;
|
|
275
|
+
const output = lastFrame();
|
|
276
|
+
globalExpect(output).toContain("Domain:");
|
|
277
|
+
globalExpect(output).toContain("API");
|
|
278
|
+
globalExpect(output).toContain("[2/2]");
|
|
279
|
+
globalExpect(output).toContain("Last step");
|
|
280
|
+
});
|
|
281
|
+
it("should show correct domain display names", () => {
|
|
282
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
283
|
+
selectedDomains: ["web", "api", "cli"],
|
|
284
|
+
currentDomainIndex: 1,
|
|
285
|
+
domain: "api"
|
|
286
|
+
});
|
|
287
|
+
cleanup = unmount;
|
|
288
|
+
const output = lastFrame();
|
|
289
|
+
globalExpect(output).toContain("API");
|
|
290
|
+
globalExpect(output).toContain("Next: CLI");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
describe("category filtering", () => {
|
|
294
|
+
it("should filter categories correctly by domain", () => {
|
|
295
|
+
const { lastFrame: webFrame, unmount: webUnmount } = renderStepBuild({
|
|
296
|
+
domain: "web"
|
|
297
|
+
});
|
|
298
|
+
const webOutput = webFrame();
|
|
299
|
+
webUnmount();
|
|
300
|
+
const { lastFrame: apiFrame, unmount: apiUnmount } = renderStepBuild({
|
|
301
|
+
domain: "api"
|
|
302
|
+
});
|
|
303
|
+
cleanup = apiUnmount;
|
|
304
|
+
const apiOutput = apiFrame();
|
|
305
|
+
globalExpect(webOutput).toContain("Framework");
|
|
306
|
+
globalExpect(webOutput).toContain("Styling");
|
|
307
|
+
globalExpect(webOutput).toContain("Client State");
|
|
308
|
+
globalExpect(webOutput).not.toContain("API Framework");
|
|
309
|
+
globalExpect(apiOutput).toContain("API Framework");
|
|
310
|
+
globalExpect(apiOutput).toContain("Database");
|
|
311
|
+
globalExpect(apiOutput).not.toContain("Styling");
|
|
312
|
+
});
|
|
313
|
+
it("should sort categories by order", () => {
|
|
314
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
315
|
+
cleanup = unmount;
|
|
316
|
+
const output = lastFrame();
|
|
317
|
+
const frameworkIndex = output?.indexOf("Framework") ?? -1;
|
|
318
|
+
const stylingIndex = output?.indexOf("Styling") ?? -1;
|
|
319
|
+
const stateIndex = output?.indexOf("Client State") ?? -1;
|
|
320
|
+
globalExpect(frameworkIndex).toBeLessThan(stylingIndex);
|
|
321
|
+
globalExpect(stylingIndex).toBeLessThan(stateIndex);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
describe("option states", () => {
|
|
325
|
+
it("should show selected options correctly", () => {
|
|
326
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
327
|
+
allSelections: ["react (@vince)"]
|
|
328
|
+
});
|
|
329
|
+
cleanup = unmount;
|
|
330
|
+
const output = lastFrame();
|
|
331
|
+
globalExpect(output).toContain("\u25CF");
|
|
332
|
+
});
|
|
333
|
+
it("should pass expertMode to CategoryGrid", () => {
|
|
334
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
335
|
+
expertMode: true
|
|
336
|
+
});
|
|
337
|
+
cleanup = unmount;
|
|
338
|
+
const output = lastFrame();
|
|
339
|
+
globalExpect(output).toContain("Expert Mode: ON");
|
|
340
|
+
});
|
|
341
|
+
it("should pass showDescriptions to CategoryGrid", () => {
|
|
342
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
343
|
+
showDescriptions: true
|
|
344
|
+
});
|
|
345
|
+
cleanup = unmount;
|
|
346
|
+
const output = lastFrame();
|
|
347
|
+
globalExpect(output).toContain("Show descriptions: ON");
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
describe("keyboard navigation", () => {
|
|
351
|
+
it("should call onContinue when Enter is pressed with valid selections", async () => {
|
|
352
|
+
const onContinue = vi.fn();
|
|
353
|
+
const { stdin, unmount } = renderStepBuild({
|
|
354
|
+
onContinue,
|
|
355
|
+
selections: {
|
|
356
|
+
framework: ["react"],
|
|
357
|
+
styling: ["tailwind"]
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
cleanup = unmount;
|
|
361
|
+
await delay(RENDER_DELAY_MS);
|
|
362
|
+
await stdin.write(ENTER);
|
|
363
|
+
await delay(INPUT_DELAY_MS);
|
|
364
|
+
globalExpect(onContinue).toHaveBeenCalled();
|
|
365
|
+
});
|
|
366
|
+
it("should call onBack when Escape is pressed", async () => {
|
|
367
|
+
const onBack = vi.fn();
|
|
368
|
+
const { stdin, unmount } = renderStepBuild({ onBack });
|
|
369
|
+
cleanup = unmount;
|
|
370
|
+
await delay(RENDER_DELAY_MS);
|
|
371
|
+
await stdin.write(ESCAPE);
|
|
372
|
+
await delay(INPUT_DELAY_MS);
|
|
373
|
+
globalExpect(onBack).toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
it("should pass focus callbacks to CategoryGrid", async () => {
|
|
376
|
+
const onFocusChange = vi.fn();
|
|
377
|
+
const { stdin, unmount } = renderStepBuild({
|
|
378
|
+
onFocusChange,
|
|
379
|
+
focusedRow: 0,
|
|
380
|
+
focusedCol: 0
|
|
381
|
+
});
|
|
382
|
+
cleanup = unmount;
|
|
383
|
+
await delay(RENDER_DELAY_MS);
|
|
384
|
+
await stdin.write("\x1B[B");
|
|
385
|
+
await delay(INPUT_DELAY_MS);
|
|
386
|
+
globalExpect(onFocusChange).toHaveBeenCalled();
|
|
387
|
+
});
|
|
388
|
+
it("should pass toggle callback to CategoryGrid", async () => {
|
|
389
|
+
const onToggle = vi.fn();
|
|
390
|
+
const { stdin, unmount } = renderStepBuild({
|
|
391
|
+
onToggle,
|
|
392
|
+
focusedRow: 0,
|
|
393
|
+
focusedCol: 0
|
|
394
|
+
});
|
|
395
|
+
cleanup = unmount;
|
|
396
|
+
await delay(RENDER_DELAY_MS);
|
|
397
|
+
await stdin.write(" ");
|
|
398
|
+
await delay(INPUT_DELAY_MS);
|
|
399
|
+
globalExpect(onToggle).toHaveBeenCalled();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
describe("toggle callbacks", () => {
|
|
403
|
+
it("should pass onToggleDescriptions to CategoryGrid", async () => {
|
|
404
|
+
const onToggleDescriptions = vi.fn();
|
|
405
|
+
const { stdin, unmount } = renderStepBuild({ onToggleDescriptions });
|
|
406
|
+
cleanup = unmount;
|
|
407
|
+
await delay(RENDER_DELAY_MS);
|
|
408
|
+
await stdin.write(" ");
|
|
409
|
+
await delay(INPUT_DELAY_MS);
|
|
410
|
+
globalExpect(onToggleDescriptions).toHaveBeenCalled();
|
|
411
|
+
});
|
|
412
|
+
it("should pass onToggleExpertMode to CategoryGrid", async () => {
|
|
413
|
+
const onToggleExpertMode = vi.fn();
|
|
414
|
+
const { stdin, unmount } = renderStepBuild({ onToggleExpertMode });
|
|
415
|
+
cleanup = unmount;
|
|
416
|
+
await delay(RENDER_DELAY_MS);
|
|
417
|
+
await stdin.write("e");
|
|
418
|
+
await delay(INPUT_DELAY_MS);
|
|
419
|
+
globalExpect(onToggleExpertMode).toHaveBeenCalled();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
describe("edge cases", () => {
|
|
423
|
+
it("should handle domain with no categories", () => {
|
|
424
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
425
|
+
domain: "mobile"
|
|
426
|
+
// No mobile categories in test matrix
|
|
427
|
+
});
|
|
428
|
+
cleanup = unmount;
|
|
429
|
+
const output = lastFrame();
|
|
430
|
+
globalExpect(output).toContain("No categories to display");
|
|
431
|
+
});
|
|
432
|
+
it("should handle unknown domain gracefully", () => {
|
|
433
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
434
|
+
domain: "unknown"
|
|
435
|
+
});
|
|
436
|
+
cleanup = unmount;
|
|
437
|
+
const output = lastFrame();
|
|
438
|
+
globalExpect(output).toBeDefined();
|
|
439
|
+
});
|
|
440
|
+
it("should handle empty allSelections", () => {
|
|
441
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
442
|
+
allSelections: []
|
|
443
|
+
});
|
|
444
|
+
cleanup = unmount;
|
|
445
|
+
const output = lastFrame();
|
|
446
|
+
globalExpect(output).toContain("Framework");
|
|
447
|
+
});
|
|
448
|
+
it("should handle allSelections with skills from other domains", () => {
|
|
449
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
450
|
+
domain: "web",
|
|
451
|
+
allSelections: ["hono (@vince)", "postgres (@vince)"]
|
|
452
|
+
// API skills
|
|
453
|
+
});
|
|
454
|
+
cleanup = unmount;
|
|
455
|
+
const output = lastFrame();
|
|
456
|
+
globalExpect(output).toContain("Framework");
|
|
457
|
+
globalExpect(output).toContain("Styling");
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
describe("multi-domain scenarios", () => {
|
|
461
|
+
it("should work for first domain in multi-domain flow", () => {
|
|
462
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
463
|
+
domain: "web",
|
|
464
|
+
selectedDomains: ["web", "api"],
|
|
465
|
+
currentDomainIndex: 0
|
|
466
|
+
});
|
|
467
|
+
cleanup = unmount;
|
|
468
|
+
const output = lastFrame();
|
|
469
|
+
globalExpect(output).toContain("Domain:");
|
|
470
|
+
globalExpect(output).toContain("Web");
|
|
471
|
+
globalExpect(output).toContain("[1/2]");
|
|
472
|
+
globalExpect(output).toContain("Next: API");
|
|
473
|
+
});
|
|
474
|
+
it("should work for last domain in multi-domain flow", () => {
|
|
475
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
476
|
+
domain: "api",
|
|
477
|
+
selectedDomains: ["web", "api"],
|
|
478
|
+
currentDomainIndex: 1
|
|
479
|
+
});
|
|
480
|
+
cleanup = unmount;
|
|
481
|
+
const output = lastFrame();
|
|
482
|
+
globalExpect(output).toContain("Domain:");
|
|
483
|
+
globalExpect(output).toContain("API");
|
|
484
|
+
globalExpect(output).toContain("[2/2]");
|
|
485
|
+
globalExpect(output).toContain("Last step");
|
|
486
|
+
});
|
|
487
|
+
it("should show three-domain progress correctly", () => {
|
|
488
|
+
const cliFrameworkCategory = createCategory(
|
|
489
|
+
"cli-framework",
|
|
490
|
+
"CLI Framework",
|
|
491
|
+
{
|
|
492
|
+
parent: "cli",
|
|
493
|
+
domain: "cli",
|
|
494
|
+
required: true,
|
|
495
|
+
order: 0
|
|
496
|
+
}
|
|
497
|
+
);
|
|
498
|
+
const cliCategory = createCategory("cli", "CLI", {
|
|
499
|
+
description: "CLI development",
|
|
500
|
+
order: 2
|
|
501
|
+
});
|
|
502
|
+
const commanderSkill = createSkill(
|
|
503
|
+
"commander (@vince)",
|
|
504
|
+
"Commander",
|
|
505
|
+
"cli-framework",
|
|
506
|
+
{
|
|
507
|
+
alias: "commander"
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
const matrixWithCli = createTestMatrix(
|
|
511
|
+
[
|
|
512
|
+
webCategory,
|
|
513
|
+
apiCategory,
|
|
514
|
+
cliCategory,
|
|
515
|
+
frameworkCategory,
|
|
516
|
+
stylingCategory,
|
|
517
|
+
stateCategory,
|
|
518
|
+
apiFrameworkCategory,
|
|
519
|
+
databaseCategory,
|
|
520
|
+
cliFrameworkCategory
|
|
521
|
+
],
|
|
522
|
+
[
|
|
523
|
+
reactSkill,
|
|
524
|
+
vueSkill,
|
|
525
|
+
tailwindSkill,
|
|
526
|
+
scssSkill,
|
|
527
|
+
zustandSkill,
|
|
528
|
+
honoSkill,
|
|
529
|
+
expressSkill,
|
|
530
|
+
postgresSkill,
|
|
531
|
+
commanderSkill
|
|
532
|
+
]
|
|
533
|
+
);
|
|
534
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
535
|
+
matrix: matrixWithCli,
|
|
536
|
+
domain: "api",
|
|
537
|
+
selectedDomains: ["web", "api", "cli"],
|
|
538
|
+
currentDomainIndex: 1
|
|
539
|
+
});
|
|
540
|
+
cleanup = unmount;
|
|
541
|
+
const output = lastFrame();
|
|
542
|
+
globalExpect(output).toContain("[2/3]");
|
|
543
|
+
globalExpect(output).toContain("Next: CLI");
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
describe("header and selection count", () => {
|
|
547
|
+
it("should show header with domain name", () => {
|
|
548
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
549
|
+
cleanup = unmount;
|
|
550
|
+
const output = lastFrame();
|
|
551
|
+
globalExpect(output).toContain("Configure your");
|
|
552
|
+
globalExpect(output).toContain("Web");
|
|
553
|
+
globalExpect(output).toContain("stack:");
|
|
554
|
+
});
|
|
555
|
+
it("should show selection count in header", () => {
|
|
556
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
557
|
+
cleanup = unmount;
|
|
558
|
+
const output = lastFrame();
|
|
559
|
+
globalExpect(output).toContain("selected");
|
|
560
|
+
});
|
|
561
|
+
it("should update selection count when items are selected", () => {
|
|
562
|
+
const { lastFrame, unmount } = renderStepBuild({
|
|
563
|
+
allSelections: ["react (@vince)", "tailwind (@vince)"]
|
|
564
|
+
});
|
|
565
|
+
cleanup = unmount;
|
|
566
|
+
const output = lastFrame();
|
|
567
|
+
globalExpect(output).toContain("selected");
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
describe("keyboard help text", () => {
|
|
571
|
+
it("should show keyboard shortcuts help in footer", () => {
|
|
572
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
573
|
+
cleanup = unmount;
|
|
574
|
+
const output = lastFrame();
|
|
575
|
+
globalExpect(output).toContain("SPACE select");
|
|
576
|
+
globalExpect(output).toContain("TAB descriptions");
|
|
577
|
+
globalExpect(output).toContain("ENTER continue");
|
|
578
|
+
globalExpect(output).toContain("ESC");
|
|
579
|
+
globalExpect(output).toContain("back");
|
|
580
|
+
});
|
|
581
|
+
it("should show arrow key navigation hints", () => {
|
|
582
|
+
const { lastFrame, unmount } = renderStepBuild();
|
|
583
|
+
cleanup = unmount;
|
|
584
|
+
const output = lastFrame();
|
|
585
|
+
globalExpect(output).toContain("options");
|
|
586
|
+
globalExpect(output).toContain("categories");
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
describe("validateBuildStep", () => {
|
|
590
|
+
it("should return valid when required categories have selections", () => {
|
|
591
|
+
const categories = [
|
|
592
|
+
{
|
|
593
|
+
id: "framework",
|
|
594
|
+
name: "Framework",
|
|
595
|
+
required: true,
|
|
596
|
+
exclusive: true,
|
|
597
|
+
options: [
|
|
598
|
+
{ id: "react", label: "React", state: "normal", selected: true }
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
];
|
|
602
|
+
const selections = { framework: ["react"] };
|
|
603
|
+
const result = validateBuildStep(categories, selections);
|
|
604
|
+
globalExpect(result.valid).toBe(true);
|
|
605
|
+
globalExpect(result.message).toBeUndefined();
|
|
606
|
+
});
|
|
607
|
+
it("should return invalid when required category has no selection", () => {
|
|
608
|
+
const categories = [
|
|
609
|
+
{
|
|
610
|
+
id: "framework",
|
|
611
|
+
name: "Framework",
|
|
612
|
+
required: true,
|
|
613
|
+
exclusive: true,
|
|
614
|
+
options: [
|
|
615
|
+
{ id: "react", label: "React", state: "normal", selected: false }
|
|
616
|
+
]
|
|
617
|
+
}
|
|
618
|
+
];
|
|
619
|
+
const selections = {};
|
|
620
|
+
const result = validateBuildStep(categories, selections);
|
|
621
|
+
globalExpect(result.valid).toBe(false);
|
|
622
|
+
globalExpect(result.message).toContain("Framework");
|
|
623
|
+
});
|
|
624
|
+
it("should return valid when optional categories have no selections", () => {
|
|
625
|
+
const categories = [
|
|
626
|
+
{
|
|
627
|
+
id: "state",
|
|
628
|
+
name: "State Management",
|
|
629
|
+
required: false,
|
|
630
|
+
exclusive: true,
|
|
631
|
+
options: [
|
|
632
|
+
{
|
|
633
|
+
id: "zustand",
|
|
634
|
+
label: "Zustand",
|
|
635
|
+
state: "normal",
|
|
636
|
+
selected: false
|
|
637
|
+
}
|
|
638
|
+
]
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
const selections = {};
|
|
642
|
+
const result = validateBuildStep(categories, selections);
|
|
643
|
+
globalExpect(result.valid).toBe(true);
|
|
644
|
+
});
|
|
645
|
+
it("should return invalid for first missing required category", () => {
|
|
646
|
+
const categories = [
|
|
647
|
+
{
|
|
648
|
+
id: "framework",
|
|
649
|
+
name: "Framework",
|
|
650
|
+
required: true,
|
|
651
|
+
exclusive: true,
|
|
652
|
+
options: []
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: "styling",
|
|
656
|
+
name: "Styling",
|
|
657
|
+
required: true,
|
|
658
|
+
exclusive: true,
|
|
659
|
+
options: []
|
|
660
|
+
}
|
|
661
|
+
];
|
|
662
|
+
const selections = {};
|
|
663
|
+
const result = validateBuildStep(categories, selections);
|
|
664
|
+
globalExpect(result.valid).toBe(false);
|
|
665
|
+
globalExpect(result.message).toContain("Framework");
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
describe("validation on continue", () => {
|
|
669
|
+
it("should show validation error when trying to continue without required selection", async () => {
|
|
670
|
+
const onContinue = vi.fn();
|
|
671
|
+
const { stdin, lastFrame, unmount } = renderStepBuild({
|
|
672
|
+
onContinue,
|
|
673
|
+
selections: {}
|
|
674
|
+
});
|
|
675
|
+
cleanup = unmount;
|
|
676
|
+
await delay(RENDER_DELAY_MS);
|
|
677
|
+
await stdin.write(ENTER);
|
|
678
|
+
await delay(INPUT_DELAY_MS);
|
|
679
|
+
const output = lastFrame();
|
|
680
|
+
globalExpect(output).toContain("Please select");
|
|
681
|
+
globalExpect(onContinue).not.toHaveBeenCalled();
|
|
682
|
+
});
|
|
683
|
+
it("should call onContinue when validation passes", async () => {
|
|
684
|
+
const onContinue = vi.fn();
|
|
685
|
+
const { stdin, unmount } = renderStepBuild({
|
|
686
|
+
onContinue,
|
|
687
|
+
selections: {
|
|
688
|
+
framework: ["react"],
|
|
689
|
+
styling: ["tailwind"]
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
cleanup = unmount;
|
|
693
|
+
await delay(RENDER_DELAY_MS);
|
|
694
|
+
await stdin.write(ENTER);
|
|
695
|
+
await delay(INPUT_DELAY_MS);
|
|
696
|
+
globalExpect(onContinue).toHaveBeenCalled();
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
describe("getDisplayLabel", () => {
|
|
701
|
+
it("should strip author suffix from name", () => {
|
|
702
|
+
globalExpect(getDisplayLabel({ name: "React (@vince)" })).toBe("React");
|
|
703
|
+
});
|
|
704
|
+
it("should preserve original capitalization", () => {
|
|
705
|
+
globalExpect(getDisplayLabel({ name: "SCSS Modules (@vince)" })).toBe(
|
|
706
|
+
"SCSS Modules"
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
it("should handle hyphenated author names", () => {
|
|
710
|
+
globalExpect(getDisplayLabel({ name: "React (@vince-team)" })).toBe("React");
|
|
711
|
+
});
|
|
712
|
+
it("should handle names without author suffix", () => {
|
|
713
|
+
globalExpect(getDisplayLabel({ name: "React" })).toBe("React");
|
|
714
|
+
});
|
|
715
|
+
it("should handle extra whitespace before author suffix", () => {
|
|
716
|
+
globalExpect(getDisplayLabel({ name: "React (@vince)" })).toBe("React");
|
|
717
|
+
});
|
|
718
|
+
it("should not strip non-author parentheses", () => {
|
|
719
|
+
globalExpect(getDisplayLabel({ name: "React (library)" })).toBe(
|
|
720
|
+
"React (library)"
|
|
721
|
+
);
|
|
722
|
+
});
|
|
723
|
+
it("should ignore alias and use name for display", () => {
|
|
724
|
+
globalExpect(
|
|
725
|
+
getDisplayLabel({ alias: "scss-modules", name: "SCSS Modules (@vince)" })
|
|
726
|
+
).toBe("SCSS Modules");
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
//# sourceMappingURL=step-build.test.js.map
|