create-lumen 2.0.0-alpha.1 → 2.0.0-alpha.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/README.md +15 -7
- package/bin/cli.js +1 -0
- package/package.json +2 -2
- package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +1 -1
- package/schema/fixtures/v2/valid/react-vite-type-based.json +1 -1
- package/schema/lumen.config.v2.json +0 -1
- package/src/cli-args.js +65 -3
- package/src/configure.js +4 -0
- package/src/css.js +14 -30
- package/src/dependencies.js +28 -18
- package/src/engine/capabilities.js +174 -4
- package/src/engine/composition.js +644 -0
- package/src/engine/index.js +1 -0
- package/src/injector.js +294 -446
- package/src/main.js +6 -7
- package/src/manifest/schema.js +1 -1
- package/src/prompts.js +459 -247
- package/src/readme.js +23 -2
- package/src/scaffold.js +17 -0
- package/templates/architectures/hybrid/main.jsx +10 -0
- package/templates/architectures/hybrid/main.tsx +10 -0
- package/templates/architectures/hybrid/src/App.jsx +20 -0
- package/templates/architectures/hybrid/src/App.tsx +20 -0
- package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +31 -0
- package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +36 -0
- package/templates/architectures/hybrid/src/shared/components/ui/index.js +1 -0
- package/templates/architectures/hybrid/src/shared/components/ui/index.ts +1 -0
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +28 -0
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +28 -0
- package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/types/index.js +20 -0
- package/templates/architectures/hybrid/src/shared/types/index.ts +16 -0
- package/templates/architectures/hybrid/src/shared/utils/cn.jsx +3 -0
- package/templates/architectures/hybrid/src/shared/utils/cn.ts +5 -0
- package/templates/architectures/none/main.jsx +10 -0
- package/templates/architectures/none/main.tsx +10 -0
- package/templates/architectures/none/src/App.jsx +15 -0
- package/templates/architectures/none/src/App.tsx +15 -0
- package/templates/architectures/none/src/styles/.gitkeep +1 -0
- package/templates/architectures/none/src/types/index.js +20 -0
- package/templates/architectures/none/src/types/index.ts +16 -0
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +11 -0
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/jest/none/src/__tests__/App.test.jsx +11 -0
- package/templates/conditional/testing/jest/none/src/__tests__/App.test.tsx +11 -0
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +10 -0
- package/templates/conditional/testing/vitest/none/src/__tests__/App.test.jsx +10 -0
- package/templates/conditional/testing/vitest/none/src/__tests__/App.test.tsx +10 -0
- package/templates/css/tailwind/src/globals.css +10 -0
- package/templates/css/tailwind/src/themes.css +86 -4
- package/templates/ui/shadcn/lib/utils.js +6 -0
- package/templates/ui/shadcn/lib/utils.ts +6 -0
- package/templates/ui/shadcn/primitives/Button.jsx +48 -0
- package/templates/ui/shadcn/primitives/Button.tsx +59 -0
- package/templates/ui/shadcn/primitives/Card.jsx +55 -0
- package/templates/ui/shadcn/primitives/Card.tsx +73 -0
- package/templates/ui/shadcn/primitives/index.js +9 -0
- package/templates/ui/shadcn/primitives/index.ts +9 -0
- package/templates/css/bootstrap/src/themes.css +0 -14
- /package/templates/{css/bootstrap/src/globals.css → architectures/hybrid/src/features/.gitkeep} +0 -0
package/src/prompts.js
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCancel, note, select } from "@clack/prompts";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { configExists, loadConfig, saveConfig } from "@/config-cache.js";
|
|
4
|
+
import {
|
|
5
|
+
isPromptVisible,
|
|
6
|
+
filterCompatibleChoices,
|
|
7
|
+
getDefaultResponses,
|
|
8
|
+
sanitizeResponsesForFramework,
|
|
9
|
+
normalizeFramework,
|
|
10
|
+
} from "./engine/capabilities.js";
|
|
11
|
+
|
|
12
|
+
export const BACK = Symbol("back");
|
|
13
|
+
export const CANCEL = Symbol("cancel");
|
|
4
14
|
|
|
5
15
|
export function onCancel() {
|
|
6
16
|
console.log(chalk.gray("\n\nOperation cancelled.\n"));
|
|
7
17
|
process.exit(1);
|
|
8
18
|
}
|
|
9
19
|
|
|
10
|
-
function formatConfig(responses) {
|
|
20
|
+
export function formatConfig(responses) {
|
|
11
21
|
const lang = responses.language === "ts" ? "TypeScript" : "JavaScript";
|
|
12
22
|
const arch =
|
|
13
23
|
responses.architecture === "feature-based"
|
|
14
24
|
? "Feature-based"
|
|
15
25
|
: responses.architecture === "type-based" || responses.architecture === "component-based"
|
|
16
26
|
? "Type-based"
|
|
27
|
+
: responses.architecture === "hybrid"
|
|
28
|
+
? "Hybrid (shared + features)"
|
|
29
|
+
: responses.architecture === "none"
|
|
30
|
+
? "None (flat src)"
|
|
17
31
|
: responses.architecture;
|
|
18
32
|
const css =
|
|
19
33
|
responses.cssFramework === "none"
|
|
20
34
|
? "None"
|
|
21
|
-
:
|
|
22
|
-
? "Tailwind CSS"
|
|
23
|
-
: "Bootstrap";
|
|
35
|
+
: "Tailwind CSS";
|
|
24
36
|
const testing =
|
|
25
37
|
responses.testing === "none"
|
|
26
38
|
? "None"
|
|
@@ -34,275 +46,475 @@ function formatConfig(responses) {
|
|
|
34
46
|
? "Redux Toolkit"
|
|
35
47
|
: "Zustand";
|
|
36
48
|
|
|
37
|
-
const docsLang = responses.docsLanguage === "es" ? "ES" : responses.docsLanguage === "en" ? "EN" : "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`• ${chalk.bold("State:")} ${chalk.blue(state)}`,
|
|
45
|
-
`• ${chalk.bold("Icons:")} ${chalk.blue(responses.iconLibrary === "none" ? "None" : responses.iconLibrary === "lucide" ? "Lucide" : "Huge")}`,
|
|
46
|
-
`• ${chalk.bold("API Client:")} ${responses.apiClient === "axios" ? chalk.green("Axios") : responses.apiClient === "fetch" ? chalk.yellow("Fetch") : chalk.red("None")}`,
|
|
47
|
-
`• ${chalk.bold("Linter:")} ${responses.linter === "oxlint" ? chalk.green("Oxlint") : responses.linter === "eslint" ? chalk.yellow("ESLint") : chalk.red("None")}`,
|
|
48
|
-
`• ${chalk.bold("Formatter:")} ${responses.formatter === "oxfmt" ? chalk.green("Oxfmt") : responses.formatter === "prettier" ? chalk.yellow("Prettier") : chalk.red("None")}`,
|
|
49
|
-
`• ${chalk.bold("Docs:")} ${chalk.cyan(docsLang)}`,
|
|
50
|
-
`• ${chalk.bold("Git:")} ${responses.gitInit ? chalk.green("Yes") : chalk.red("No")}`,
|
|
51
|
-
`• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}`,
|
|
52
|
-
].join("\n");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function getUserInputs(projectName, { quickSetup = false } = {}) {
|
|
56
|
-
let oldConfig;
|
|
57
|
-
let useOldConfig;
|
|
49
|
+
const docsLang = responses.docsLanguage === "es" ? "ES" : responses.docsLanguage === "en" ? "EN" : "—";
|
|
50
|
+
const lines = [
|
|
51
|
+
`• ${chalk.bold("Architecture:")} ${chalk.green(arch)}`,
|
|
52
|
+
`• ${chalk.bold("Language:")} ${chalk.green(lang)}`,
|
|
53
|
+
`• ${chalk.bold("CSS:")} ${chalk.yellow(css)}`,
|
|
54
|
+
`• ${chalk.bold("Testing:")} ${chalk.magenta(testing)}`,
|
|
55
|
+
];
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const responses = {
|
|
62
|
-
projectName,
|
|
63
|
-
architecture: "feature-based",
|
|
64
|
-
language: "ts",
|
|
65
|
-
cssFramework: "tailwind",
|
|
66
|
-
testing: "vitest",
|
|
67
|
-
router: true,
|
|
68
|
-
stateManagement: "none",
|
|
69
|
-
iconLibrary: "none",
|
|
70
|
-
apiClient: "none",
|
|
71
|
-
linter: "eslint",
|
|
72
|
-
formatter: "prettier",
|
|
73
|
-
docsLanguage: "en",
|
|
74
|
-
gitInit: true,
|
|
75
|
-
readme: true,
|
|
76
|
-
};
|
|
77
|
-
await saveConfig(responses);
|
|
78
|
-
return responses;
|
|
57
|
+
if (responses.router !== undefined) {
|
|
58
|
+
lines.push(`• ${chalk.bold("Router:")} ${responses.router ? chalk.green("Yes") : chalk.red("No")}`);
|
|
79
59
|
}
|
|
60
|
+
if (responses.bundler) {
|
|
61
|
+
lines.push(`• ${chalk.bold("Bundler:")} ${chalk.cyan(responses.bundler)}`);
|
|
62
|
+
}
|
|
63
|
+
if (responses.adapter && responses.adapter !== "none") {
|
|
64
|
+
lines.push(`• ${chalk.bold("Adapter:")} ${chalk.cyan(responses.adapter)}`);
|
|
65
|
+
}
|
|
66
|
+
lines.push(
|
|
67
|
+
`• ${chalk.bold("State:")} ${chalk.blue(state)}`,
|
|
68
|
+
`• ${chalk.bold("Icons:")} ${chalk.blue(responses.iconLibrary === "none" ? "None" : responses.iconLibrary === "lucide" ? "Lucide" : "Huge")}`,
|
|
69
|
+
`• ${chalk.bold("API Client:")} ${responses.apiClient === "axios" ? chalk.green("Axios") : responses.apiClient === "fetch" ? chalk.yellow("Fetch") : chalk.red("None")}`,
|
|
70
|
+
`• ${chalk.bold("Linter:")} ${responses.linter === "oxlint" ? chalk.green("Oxlint") : responses.linter === "biome" ? chalk.cyan("Biome") : responses.linter === "eslint" ? chalk.yellow("ESLint") : chalk.red("None")}`,
|
|
71
|
+
`• ${chalk.bold("Formatter:")} ${responses.formatter === "oxfmt" ? chalk.green("Oxfmt") : responses.formatter === "prettier" ? chalk.yellow("Prettier") : chalk.red("None")}`,
|
|
72
|
+
`• ${chalk.bold("Docs:")} ${chalk.cyan(docsLang)}`,
|
|
73
|
+
`• ${chalk.bold("Git:")} ${responses.gitInit ? chalk.green("Yes") : chalk.red("No")}`,
|
|
74
|
+
`• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}`
|
|
75
|
+
);
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
}
|
|
80
78
|
|
|
81
|
-
if (await configExists()) {
|
|
82
|
-
oldConfig = await loadConfig();
|
|
83
|
-
oldConfig.projectName = projectName;
|
|
84
|
-
|
|
85
|
-
note(
|
|
86
|
-
`Previous setup found:\n\n${formatConfig(oldConfig)}\n`,
|
|
87
|
-
"Previous Configuration"
|
|
88
|
-
);
|
|
89
79
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Generic step-based runner with history stack and back navigation.
|
|
82
|
+
*/
|
|
83
|
+
export async function runPromptFlow({
|
|
84
|
+
steps,
|
|
85
|
+
initialAnswers = {},
|
|
86
|
+
selectFn = select,
|
|
87
|
+
noteFn = note,
|
|
88
|
+
onCancelFn = onCancel,
|
|
89
|
+
} = {}) {
|
|
90
|
+
const answers = { ...initialAnswers };
|
|
91
|
+
const history = [];
|
|
92
|
+
let currentIndex = 0;
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
while (currentIndex < steps.length) {
|
|
95
|
+
const step = steps[currentIndex];
|
|
101
96
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
oldConfig.formatter = "none";
|
|
108
|
-
}
|
|
109
|
-
if (oldConfig.docsLanguage === undefined) {
|
|
110
|
-
oldConfig.docsLanguage = "en";
|
|
111
|
-
}
|
|
112
|
-
return { ...oldConfig, projectName };
|
|
97
|
+
// Check skip condition
|
|
98
|
+
if (step.skip?.(answers)) {
|
|
99
|
+
step.onSkip?.(answers);
|
|
100
|
+
currentIndex++;
|
|
101
|
+
continue;
|
|
113
102
|
}
|
|
114
|
-
}
|
|
115
103
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
message:
|
|
119
|
-
"Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)",
|
|
120
|
-
initialValue: true,
|
|
121
|
-
});
|
|
122
|
-
if (isCancel(quickSetupChoice)) onCancel();
|
|
104
|
+
const canGoBack = history.length > 0;
|
|
105
|
+
let res;
|
|
123
106
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
linter: "eslint",
|
|
136
|
-
formatter: "prettier",
|
|
137
|
-
docsLanguage: "en",
|
|
138
|
-
gitInit: true,
|
|
139
|
-
readme: true,
|
|
140
|
-
};
|
|
141
|
-
await saveConfig(responses);
|
|
142
|
-
return responses;
|
|
143
|
-
}
|
|
107
|
+
if (step.render) {
|
|
108
|
+
res = await step.render(answers, canGoBack, { selectFn, noteFn });
|
|
109
|
+
} else {
|
|
110
|
+
const stepOptions =
|
|
111
|
+
typeof step.getOptions === "function" ? step.getOptions(answers) : step.options;
|
|
112
|
+
const options = [
|
|
113
|
+
...stepOptions,
|
|
114
|
+
...(canGoBack
|
|
115
|
+
? [{ label: chalk.dim(" Back"), value: BACK, hint: "Return to previous question" }]
|
|
116
|
+
: []),
|
|
117
|
+
];
|
|
144
118
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
label: "Type-based",
|
|
156
|
-
value: "type-based",
|
|
157
|
-
hint: "Small apps / component libraries — code grouped by UI type",
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
initialValue: "feature-based",
|
|
161
|
-
});
|
|
162
|
-
if (isCancel(architecture)) onCancel();
|
|
119
|
+
const prevValue = answers[step.id];
|
|
120
|
+
const hasPrevValue =
|
|
121
|
+
prevValue !== undefined && stepOptions.some((o) => o.value === prevValue);
|
|
122
|
+
const defaultInitial =
|
|
123
|
+
typeof step.defaultInitial === "function"
|
|
124
|
+
? step.defaultInitial(answers)
|
|
125
|
+
: step.defaultInitial;
|
|
126
|
+
const initialValue = hasPrevValue ? prevValue : defaultInitial;
|
|
163
127
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
initialValue: "ts",
|
|
171
|
-
});
|
|
172
|
-
if (isCancel(language)) onCancel();
|
|
128
|
+
res = await selectFn({
|
|
129
|
+
message: step.message,
|
|
130
|
+
options,
|
|
131
|
+
initialValue,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
173
134
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{ label: "Bootstrap", value: "bootstrap" },
|
|
179
|
-
{ label: "None", value: "none" },
|
|
180
|
-
],
|
|
181
|
-
initialValue: "tailwind",
|
|
182
|
-
});
|
|
183
|
-
if (isCancel(cssFramework)) onCancel();
|
|
135
|
+
if (isCancel(res) || res === CANCEL) {
|
|
136
|
+
onCancelFn();
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
184
139
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
],
|
|
192
|
-
initialValue: "vitest",
|
|
193
|
-
});
|
|
194
|
-
if (isCancel(testing)) onCancel();
|
|
140
|
+
if (res === BACK) {
|
|
141
|
+
if (history.length > 0) {
|
|
142
|
+
currentIndex = history.pop();
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
195
146
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
147
|
+
// Step produced a normal answer
|
|
148
|
+
if (step.onAnswer) {
|
|
149
|
+
const nextIndex = step.onAnswer(res, answers, { history, currentIndex, steps });
|
|
150
|
+
if (typeof nextIndex === "number") {
|
|
151
|
+
history.push(currentIndex);
|
|
152
|
+
currentIndex = nextIndex;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
} else if (step.id) {
|
|
156
|
+
answers[step.id] = res;
|
|
157
|
+
}
|
|
201
158
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{ label: "None", value: "none" },
|
|
206
|
-
{ label: "Redux Toolkit", value: "redux" },
|
|
207
|
-
{ label: "Zustand", value: "zustand" },
|
|
208
|
-
],
|
|
209
|
-
initialValue: "none",
|
|
210
|
-
});
|
|
211
|
-
if (isCancel(stateManagement)) onCancel();
|
|
159
|
+
history.push(currentIndex);
|
|
160
|
+
currentIndex++;
|
|
161
|
+
}
|
|
212
162
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
options: [
|
|
216
|
-
{ label: "None", value: "none" },
|
|
217
|
-
{ label: "Lucide Icons", value: "lucide" },
|
|
218
|
-
{ label: "Huge Icons", value: "huge" },
|
|
219
|
-
],
|
|
220
|
-
initialValue: "none",
|
|
221
|
-
});
|
|
222
|
-
if (isCancel(iconLibrary)) onCancel();
|
|
163
|
+
return answers;
|
|
164
|
+
}
|
|
223
165
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
166
|
+
export async function getUserInputs(
|
|
167
|
+
projectName,
|
|
168
|
+
{
|
|
169
|
+
quickSetup = false,
|
|
170
|
+
framework: rawFramework,
|
|
171
|
+
initialArch = null,
|
|
172
|
+
selectFn = select,
|
|
173
|
+
noteFn = note,
|
|
174
|
+
saveConfigFn = saveConfig,
|
|
175
|
+
configExistsFn = configExists,
|
|
176
|
+
loadConfigFn = loadConfig,
|
|
177
|
+
} = {}
|
|
178
|
+
) {
|
|
179
|
+
const framework = normalizeFramework(rawFramework);
|
|
180
|
+
// Non-interactive quick setup: capability-aware defaults without prompting.
|
|
181
|
+
if (quickSetup) {
|
|
182
|
+
const responses = getDefaultResponses(framework, { projectName });
|
|
183
|
+
if (initialArch) responses.architecture = initialArch;
|
|
184
|
+
await saveConfigFn(responses);
|
|
185
|
+
return responses;
|
|
186
|
+
}
|
|
234
187
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
188
|
+
let oldConfig = null;
|
|
189
|
+
const hasPrevious = await configExistsFn();
|
|
190
|
+
if (hasPrevious) {
|
|
191
|
+
oldConfig = await loadConfigFn();
|
|
192
|
+
oldConfig.projectName = projectName;
|
|
193
|
+
// Sanitize cached setup against current framework capabilities
|
|
194
|
+
oldConfig = sanitizeResponsesForFramework(framework, oldConfig);
|
|
195
|
+
}
|
|
240
196
|
|
|
241
|
-
const
|
|
242
|
-
message: "Which linter do you want to use?",
|
|
243
|
-
options: [
|
|
244
|
-
{ label: "ESLint", value: "eslint", hint: "Industry standard JavaScript linter (Recommended)" },
|
|
245
|
-
{ label: "Oxlint", value: "oxlint", hint: "Fast Rust-based linter" },
|
|
246
|
-
{ label: "None", value: "none" },
|
|
247
|
-
],
|
|
248
|
-
initialValue: "eslint",
|
|
249
|
-
});
|
|
250
|
-
if (isCancel(linter)) onCancel();
|
|
197
|
+
const steps = [];
|
|
251
198
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
199
|
+
if (hasPrevious) {
|
|
200
|
+
steps.push({
|
|
201
|
+
id: "useExisting",
|
|
202
|
+
render: async (_ans, _canGoBack, { selectFn: sFn, noteFn: nFn }) => {
|
|
203
|
+
nFn(
|
|
204
|
+
`Previous setup found:\n\n${formatConfig(oldConfig)}\n`,
|
|
205
|
+
"Previous Configuration"
|
|
206
|
+
);
|
|
207
|
+
return await sFn({
|
|
208
|
+
message: "How would you like to proceed?",
|
|
209
|
+
options: [
|
|
210
|
+
{ value: true, label: "Continue with previous setup" },
|
|
211
|
+
{ value: false, label: "Start fresh (new setup)" },
|
|
212
|
+
],
|
|
213
|
+
initialValue: true,
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
onAnswer: (val, ans) => {
|
|
217
|
+
ans.useExisting = val;
|
|
218
|
+
if (val === true) {
|
|
219
|
+
if (oldConfig.apiClient === undefined) {
|
|
220
|
+
oldConfig.apiClient = oldConfig.axios ? "axios" : "none";
|
|
221
|
+
}
|
|
222
|
+
if (oldConfig.formatter === undefined) {
|
|
223
|
+
oldConfig.formatter = "none";
|
|
224
|
+
}
|
|
225
|
+
if (oldConfig.docsLanguage === undefined) {
|
|
226
|
+
oldConfig.docsLanguage = "en";
|
|
227
|
+
}
|
|
228
|
+
return steps.length; // exit immediately
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
},
|
|
269
232
|
});
|
|
270
|
-
if (isCancel(formatter)) onCancel();
|
|
271
233
|
}
|
|
272
234
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
235
|
+
steps.push(
|
|
236
|
+
{
|
|
237
|
+
id: "quickSetup",
|
|
238
|
+
message: isPromptVisible(framework, "router")
|
|
239
|
+
? "Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)"
|
|
240
|
+
: "Quick Setup? (TypeScript + Tailwind + Feature-based + ESLint + Vitest)",
|
|
241
|
+
getOptions: () => [
|
|
242
|
+
{ label: "Yes, use recommended defaults", value: true },
|
|
243
|
+
{ label: "No, custom setup", value: false },
|
|
244
|
+
],
|
|
245
|
+
defaultInitial: true,
|
|
246
|
+
onAnswer: (val, ans, { steps: allSteps }) => {
|
|
247
|
+
ans.quickSetup = val;
|
|
248
|
+
if (val === true) {
|
|
249
|
+
const defaults = getDefaultResponses(framework, { projectName });
|
|
250
|
+
Object.assign(ans, defaults, { projectName, quickSetup: true });
|
|
251
|
+
if (initialArch) ans.architecture = initialArch;
|
|
252
|
+
// Jump to finalConfirm
|
|
253
|
+
return allSteps.length - 1;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "architecture",
|
|
260
|
+
message: "Which project architecture do you want?",
|
|
261
|
+
getOptions: () =>
|
|
262
|
+
filterCompatibleChoices(framework, "architecture", [
|
|
263
|
+
{
|
|
264
|
+
label: "Feature-based",
|
|
265
|
+
value: "feature-based",
|
|
266
|
+
hint: "Scales to large apps — code grouped by business domain",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
label: "Type-based",
|
|
270
|
+
value: "type-based",
|
|
271
|
+
hint: "Small apps / component libraries — code grouped by UI type",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: "Hybrid",
|
|
275
|
+
value: "hybrid",
|
|
276
|
+
hint: "Next.js: features + shared code",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
label: "None",
|
|
280
|
+
value: "none",
|
|
281
|
+
hint: "Minimal flat src/ scaffold — no imposed structure",
|
|
282
|
+
},
|
|
283
|
+
]),
|
|
284
|
+
defaultInitial: initialArch || "feature-based",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "language",
|
|
288
|
+
message: "Which language do you want to use?",
|
|
289
|
+
getOptions: () =>
|
|
290
|
+
filterCompatibleChoices(framework, "language", [
|
|
291
|
+
{ label: "TypeScript", value: "ts" },
|
|
292
|
+
{ label: "JavaScript", value: "js" },
|
|
293
|
+
]),
|
|
294
|
+
defaultInitial: "ts",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: "cssFramework",
|
|
298
|
+
message: "Which CSS framework do you want to use?",
|
|
299
|
+
getOptions: () =>
|
|
300
|
+
filterCompatibleChoices(framework, "styling", [
|
|
301
|
+
{ label: "Tailwind CSS", value: "tailwind" },
|
|
302
|
+
{ label: "None", value: "none" },
|
|
303
|
+
]),
|
|
304
|
+
defaultInitial: "tailwind",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "testing",
|
|
308
|
+
message: "Which testing framework do you want to set up?",
|
|
309
|
+
getOptions: () => [
|
|
310
|
+
{ label: "Vitest", value: "vitest" },
|
|
311
|
+
{ label: "Jest", value: "jest" },
|
|
312
|
+
{ label: "None", value: "none" },
|
|
313
|
+
],
|
|
314
|
+
defaultInitial: "vitest",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
id: "router",
|
|
318
|
+
skip: () => !isPromptVisible(framework, "router"),
|
|
319
|
+
onSkip: (ans) => {
|
|
320
|
+
ans.router = false;
|
|
321
|
+
},
|
|
322
|
+
message: "Would you like to install React Router?",
|
|
323
|
+
getOptions: () => [
|
|
324
|
+
{ label: "Yes", value: true },
|
|
325
|
+
{ label: "No", value: false },
|
|
326
|
+
],
|
|
327
|
+
defaultInitial: true,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "bundler",
|
|
331
|
+
skip: () => !isPromptVisible(framework, "bundler"),
|
|
332
|
+
onSkip: (ans) => {
|
|
333
|
+
ans.bundler = undefined;
|
|
334
|
+
},
|
|
335
|
+
message: "Which bundler do you want to use?",
|
|
336
|
+
getOptions: () =>
|
|
337
|
+
filterCompatibleChoices(framework, "bundler", [
|
|
338
|
+
{ label: "Turbopack", value: "turbopack" },
|
|
339
|
+
{ label: "Webpack", value: "webpack" },
|
|
340
|
+
]),
|
|
341
|
+
defaultInitial: "turbopack",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: "adapter",
|
|
345
|
+
skip: () => !isPromptVisible(framework, "adapter"),
|
|
346
|
+
onSkip: (ans) => {
|
|
347
|
+
ans.adapter = "none";
|
|
348
|
+
},
|
|
349
|
+
message: "Which deployment adapter do you want to use?",
|
|
350
|
+
getOptions: () =>
|
|
351
|
+
filterCompatibleChoices(framework, "adapter", [
|
|
352
|
+
{ label: "None (default)", value: "none" },
|
|
353
|
+
{ label: "Node", value: "node" },
|
|
354
|
+
{ label: "Vercel", value: "vercel" },
|
|
355
|
+
{ label: "Cloudflare", value: "cloudflare" },
|
|
356
|
+
{ label: "Static", value: "static" },
|
|
357
|
+
]),
|
|
358
|
+
defaultInitial: "none",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "stateManagement",
|
|
362
|
+
message: "Which state management library do you want to use?",
|
|
363
|
+
getOptions: () => [
|
|
364
|
+
{ label: "None", value: "none" },
|
|
365
|
+
{ label: "Redux Toolkit", value: "redux" },
|
|
366
|
+
{ label: "Zustand", value: "zustand" },
|
|
367
|
+
],
|
|
368
|
+
defaultInitial: "none",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: "iconLibrary",
|
|
372
|
+
message: "Which icon library would you like to use?",
|
|
373
|
+
getOptions: () => [
|
|
374
|
+
{ label: "None", value: "none" },
|
|
375
|
+
{ label: "Lucide Icons", value: "lucide" },
|
|
376
|
+
{ label: "Huge Icons", value: "huge" },
|
|
377
|
+
],
|
|
378
|
+
defaultInitial: "none",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: "apiClient",
|
|
382
|
+
message: "Which API client do you want to use?",
|
|
383
|
+
getOptions: () => [
|
|
384
|
+
{ label: "None", value: "none" },
|
|
385
|
+
{ label: "Axios", value: "axios" },
|
|
386
|
+
{ label: "Fetch (native)", value: "fetch" },
|
|
387
|
+
],
|
|
388
|
+
defaultInitial: "none",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "gitInit",
|
|
392
|
+
message: "Would you like to initialize a Git repository?",
|
|
393
|
+
getOptions: () => [
|
|
394
|
+
{ label: "Yes", value: true },
|
|
395
|
+
{ label: "No", value: false },
|
|
396
|
+
],
|
|
397
|
+
defaultInitial: false,
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "linter",
|
|
401
|
+
message: "Which linter do you want to use?",
|
|
402
|
+
getOptions: () =>
|
|
403
|
+
filterCompatibleChoices(framework, "linter", [
|
|
404
|
+
{
|
|
405
|
+
label: "ESLint",
|
|
406
|
+
value: "eslint",
|
|
407
|
+
hint: "Industry standard JavaScript linter (Recommended)",
|
|
408
|
+
},
|
|
409
|
+
{ label: "Oxlint", value: "oxlint", hint: "Fast Rust-based linter" },
|
|
410
|
+
{ label: "Biome", value: "biome", hint: "Toolchain for web projects" },
|
|
411
|
+
{ label: "None", value: "none" },
|
|
412
|
+
]),
|
|
413
|
+
defaultInitial: "eslint",
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "formatter",
|
|
417
|
+
skip: (ans) => ans.linter === "none",
|
|
418
|
+
onSkip: (ans) => {
|
|
419
|
+
ans.formatter = "none";
|
|
420
|
+
},
|
|
421
|
+
message: "Which formatter do you want to use?",
|
|
422
|
+
getOptions: (ans) =>
|
|
423
|
+
filterCompatibleChoices(
|
|
424
|
+
framework,
|
|
425
|
+
"formatter",
|
|
426
|
+
ans.linter === "eslint"
|
|
427
|
+
? [
|
|
428
|
+
{ label: "None", value: "none" },
|
|
429
|
+
{ label: "Prettier", value: "prettier" },
|
|
430
|
+
]
|
|
431
|
+
: [
|
|
432
|
+
{ label: "None", value: "none" },
|
|
433
|
+
{ label: "Oxfmt (Recommended)", value: "oxfmt" },
|
|
434
|
+
{ label: "Prettier", value: "prettier" },
|
|
435
|
+
]
|
|
436
|
+
),
|
|
437
|
+
defaultInitial: (ans) => {
|
|
438
|
+
const compatible = filterCompatibleChoices(
|
|
439
|
+
framework,
|
|
440
|
+
"formatter",
|
|
441
|
+
ans.linter === "eslint"
|
|
442
|
+
? [{ value: "none" }, { value: "prettier" }]
|
|
443
|
+
: [{ value: "none" }, { value: "oxfmt" }, { value: "prettier" }]
|
|
444
|
+
).map((o) => o.value);
|
|
445
|
+
const preferred = ans.linter === "eslint" ? "prettier" : "oxfmt";
|
|
446
|
+
return compatible.includes(preferred) ? preferred : compatible[0];
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "docsLanguage",
|
|
451
|
+
message: "Which docs language do you want?",
|
|
452
|
+
getOptions: () => [
|
|
453
|
+
{ label: "English", value: "en" },
|
|
454
|
+
{ label: "Español", value: "es" },
|
|
455
|
+
],
|
|
456
|
+
defaultInitial: "en",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
id: "readme",
|
|
460
|
+
message: "Would you like to generate a README.md and LICENSE?",
|
|
461
|
+
getOptions: () => [
|
|
462
|
+
{ label: "Yes", value: true },
|
|
463
|
+
{ label: "No", value: false },
|
|
464
|
+
],
|
|
465
|
+
defaultInitial: true,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: "finalConfirm",
|
|
469
|
+
render: async (ans, canGoBack, { selectFn: sFn, noteFn: nFn }) => {
|
|
470
|
+
nFn(`\n${formatConfig(ans)}\n`, "Project Configuration Summary");
|
|
471
|
+
return await sFn({
|
|
472
|
+
message: "Proceed with project generation?",
|
|
473
|
+
options: [
|
|
474
|
+
{ label: "Confirm and scaffold project", value: true },
|
|
475
|
+
...(canGoBack
|
|
476
|
+
? [{ label: chalk.dim("← Back to edit options"), value: BACK }]
|
|
477
|
+
: []),
|
|
478
|
+
],
|
|
479
|
+
initialValue: true,
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
}
|
|
483
|
+
);
|
|
282
484
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
485
|
+
const responses = await runPromptFlow({
|
|
486
|
+
steps,
|
|
487
|
+
initialAnswers: { projectName },
|
|
488
|
+
selectFn,
|
|
489
|
+
noteFn,
|
|
490
|
+
onCancelFn: onCancel,
|
|
286
491
|
});
|
|
287
|
-
if (isCancel(readme)) onCancel();
|
|
288
492
|
|
|
289
|
-
|
|
493
|
+
if (responses.useExisting) {
|
|
494
|
+
return { ...oldConfig, projectName };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const {
|
|
498
|
+
useExisting: _useExisting,
|
|
499
|
+
quickSetup: _quickSetup,
|
|
500
|
+
bundler,
|
|
501
|
+
adapter,
|
|
502
|
+
...picked
|
|
503
|
+
} = responses;
|
|
504
|
+
const finalResponses = {
|
|
290
505
|
projectName,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
testing,
|
|
295
|
-
router,
|
|
296
|
-
stateManagement,
|
|
297
|
-
iconLibrary,
|
|
298
|
-
apiClient,
|
|
299
|
-
linter,
|
|
300
|
-
formatter,
|
|
301
|
-
docsLanguage,
|
|
302
|
-
gitInit,
|
|
303
|
-
readme,
|
|
506
|
+
frameworkName: framework.name,
|
|
507
|
+
frameworkVariant: framework.variant,
|
|
508
|
+
...picked,
|
|
304
509
|
};
|
|
510
|
+
if (bundler !== undefined) {
|
|
511
|
+
finalResponses.bundler = bundler;
|
|
512
|
+
}
|
|
513
|
+
if (adapter !== undefined && adapter !== "none") {
|
|
514
|
+
finalResponses.adapter = adapter;
|
|
515
|
+
}
|
|
305
516
|
|
|
306
|
-
|
|
307
|
-
|
|
517
|
+
// No config cache write until final confirm!
|
|
518
|
+
await saveConfigFn(finalResponses);
|
|
519
|
+
return finalResponses;
|
|
308
520
|
}
|