create-lumen 2.0.0-alpha.1 → 2.0.0-beta.1
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 +38 -12
- package/bin/cli.js +3 -1
- package/package.json +3 -3
- 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 +169 -7
- package/src/configure.js +12 -4
- package/src/css.js +14 -30
- package/src/dependencies.js +36 -20
- package/src/engine/capabilities.js +195 -4
- package/src/engine/composition.js +736 -0
- package/src/engine/index.js +1 -0
- package/src/engine/providers/base-provider.js +4 -4
- package/src/injector.js +294 -446
- package/src/main.js +38 -16
- package/src/manifest/emit.js +1 -1
- package/src/manifest/schema.js +1 -1
- package/src/prompts.js +581 -238
- 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/bases/next/app-router/js/.provenance.json +19 -0
- package/templates/bases/next/app-router/js/README.md +36 -0
- package/templates/bases/next/app-router/js/jsconfig.json +7 -0
- package/templates/bases/next/app-router/js/next.config.mjs +6 -0
- package/templates/bases/next/app-router/js/package.json +15 -0
- package/templates/bases/next/app-router/js/public/file.svg +1 -0
- package/templates/bases/next/app-router/js/public/globe.svg +1 -0
- package/templates/bases/next/app-router/js/public/next.svg +1 -0
- package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
- package/templates/bases/next/app-router/js/public/window.svg +1 -0
- package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
- package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
- package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
- package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
- package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
- package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
- package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
- package/templates/bases/next/app-router/js/src/app/page.js +69 -0
- package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
- package/templates/bases/next/app-router/ts/.provenance.json +19 -0
- package/templates/bases/next/app-router/ts/README.md +36 -0
- package/templates/bases/next/app-router/ts/next.config.ts +7 -0
- package/templates/bases/next/app-router/ts/package.json +21 -0
- package/templates/bases/next/app-router/ts/public/file.svg +1 -0
- package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
- package/templates/bases/next/app-router/ts/public/next.svg +1 -0
- package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
- package/templates/bases/next/app-router/ts/public/window.svg +1 -0
- package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
- package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
- package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
- package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
- package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
- package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
- package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
- package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
- package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
- package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
- package/templates/bases/next/pages-router/js/.provenance.json +19 -0
- package/templates/bases/next/pages-router/js/README.md +40 -0
- package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
- package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
- package/templates/bases/next/pages-router/js/package.json +15 -0
- package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
- package/templates/bases/next/pages-router/js/public/file.svg +1 -0
- package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
- package/templates/bases/next/pages-router/js/public/next.svg +1 -0
- package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
- package/templates/bases/next/pages-router/js/public/window.svg +1 -0
- package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
- package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
- package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
- package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
- package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
- package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
- package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
- package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
- package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
- package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
- package/templates/bases/next/pages-router/ts/README.md +40 -0
- package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
- package/templates/bases/next/pages-router/ts/package.json +21 -0
- package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
- package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
- package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
- package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
- package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
- package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
- package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
- package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
- package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
- package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
- package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
- package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
- package/templates/conditional/linting/biome/biome.json +31 -0
- package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
- package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
- package/templates/conditional/next/config/node/next.config.mjs +6 -0
- package/templates/conditional/next/config/node/next.config.ts +7 -0
- package/templates/conditional/next/config/static/next.config.mjs +6 -0
- package/templates/conditional/next/config/static/next.config.ts +7 -0
- package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
- package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
- package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
- package/templates/conditional/next/config/vercel/next.config.ts +7 -0
- package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
- package/templates/conditional/next/config/webpack/next.config.ts +10 -0
- package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
- package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
- package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
- package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
- package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
- package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
- package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
- package/templates/conditional/next/font/src/app/layout.js +25 -0
- package/templates/conditional/next/font/src/app/layout.tsx +30 -0
- package/templates/conditional/next/font/src/lib/fonts.js +11 -0
- package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
- package/templates/conditional/next/font/src/pages/_app.js +20 -0
- package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
- package/templates/conditional/next/font/src/pages/_document.js +18 -0
- package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
- package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
- package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
- package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
- package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
- package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
- package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
- package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
- package/templates/conditional/next/image/src/components/image/index.js +1 -0
- package/templates/conditional/next/image/src/components/image/index.ts +2 -0
- package/templates/conditional/next/server-components/README.md +13 -0
- package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
- package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
- package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
- package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
- package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -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"
|
|
@@ -35,274 +47,605 @@ function formatConfig(responses) {
|
|
|
35
47
|
: "Zustand";
|
|
36
48
|
|
|
37
49
|
const docsLang = responses.docsLanguage === "es" ? "ES" : responses.docsLanguage === "en" ? "EN" : "—";
|
|
38
|
-
|
|
50
|
+
const fwLabel =
|
|
51
|
+
responses.frameworkName === "next" && responses.frameworkVariant === "pages-router"
|
|
52
|
+
? "Next.js (Pages Router)"
|
|
53
|
+
: responses.frameworkName === "next"
|
|
54
|
+
? "Next.js (App Router)"
|
|
55
|
+
: "React + Vite";
|
|
56
|
+
const lines = [
|
|
57
|
+
`• ${chalk.bold("Framework:")} ${chalk.green(fwLabel)}`,
|
|
39
58
|
`• ${chalk.bold("Architecture:")} ${chalk.green(arch)}`,
|
|
40
59
|
`• ${chalk.bold("Language:")} ${chalk.green(lang)}`,
|
|
41
60
|
`• ${chalk.bold("CSS:")} ${chalk.yellow(css)}`,
|
|
61
|
+
`• ${chalk.bold("UI Kit:")} ${responses.uiKit === "shadcn" ? chalk.cyan("shadcn/ui") : chalk.gray("None")}`,
|
|
42
62
|
`• ${chalk.bold("Testing:")} ${chalk.magenta(testing)}`,
|
|
43
|
-
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (responses.router !== undefined) {
|
|
66
|
+
lines.push(`• ${chalk.bold("Router:")} ${responses.router ? chalk.green("Yes") : chalk.red("No")}`);
|
|
67
|
+
}
|
|
68
|
+
if (responses.bundler) {
|
|
69
|
+
lines.push(`• ${chalk.bold("Bundler:")} ${chalk.cyan(responses.bundler)}`);
|
|
70
|
+
}
|
|
71
|
+
if (responses.adapter && responses.adapter !== "none") {
|
|
72
|
+
lines.push(`• ${chalk.bold("Adapter:")} ${chalk.cyan(responses.adapter)}`);
|
|
73
|
+
}
|
|
74
|
+
if (responses.reactCompiler !== undefined) {
|
|
75
|
+
lines.push(`• ${chalk.bold("React Compiler:")} ${responses.reactCompiler ? chalk.green("Yes") : chalk.red("No")}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push(
|
|
44
78
|
`• ${chalk.bold("State:")} ${chalk.blue(state)}`,
|
|
45
79
|
`• ${chalk.bold("Icons:")} ${chalk.blue(responses.iconLibrary === "none" ? "None" : responses.iconLibrary === "lucide" ? "Lucide" : "Huge")}`,
|
|
46
80
|
`• ${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")}`,
|
|
81
|
+
`• ${chalk.bold("Linter:")} ${responses.linter === "oxlint" ? chalk.green("Oxlint") : responses.linter === "biome" ? chalk.cyan("Biome") : responses.linter === "eslint" ? chalk.yellow("ESLint") : chalk.red("None")}`,
|
|
48
82
|
`• ${chalk.bold("Formatter:")} ${responses.formatter === "oxfmt" ? chalk.green("Oxfmt") : responses.formatter === "prettier" ? chalk.yellow("Prettier") : chalk.red("None")}`,
|
|
49
83
|
`• ${chalk.bold("Docs:")} ${chalk.cyan(docsLang)}`,
|
|
84
|
+
`• ${chalk.bold("Agent Docs:")} ${responses.agentDocs ? chalk.green("Yes") : chalk.gray("No")}`,
|
|
50
85
|
`• ${chalk.bold("Git:")} ${responses.gitInit ? chalk.green("Yes") : chalk.red("No")}`,
|
|
51
|
-
`• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}
|
|
52
|
-
|
|
86
|
+
`• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}`
|
|
87
|
+
);
|
|
88
|
+
return lines.join("\n");
|
|
53
89
|
}
|
|
54
90
|
|
|
55
|
-
export async function getUserInputs(projectName, { quickSetup = false } = {}) {
|
|
56
|
-
let oldConfig;
|
|
57
|
-
let useOldConfig;
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
formatter: "prettier",
|
|
73
|
-
docsLanguage: "en",
|
|
74
|
-
gitInit: true,
|
|
75
|
-
readme: true,
|
|
76
|
-
};
|
|
77
|
-
await saveConfig(responses);
|
|
78
|
-
return responses;
|
|
79
|
-
}
|
|
92
|
+
/**
|
|
93
|
+
* Generic step-based runner with history stack and back navigation.
|
|
94
|
+
*/
|
|
95
|
+
export async function runPromptFlow({
|
|
96
|
+
steps,
|
|
97
|
+
initialAnswers = {},
|
|
98
|
+
selectFn = select,
|
|
99
|
+
noteFn = note,
|
|
100
|
+
onCancelFn = onCancel,
|
|
101
|
+
} = {}) {
|
|
102
|
+
const answers = { ...initialAnswers };
|
|
103
|
+
const history = [];
|
|
104
|
+
let currentIndex = 0;
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
oldConfig.projectName = projectName;
|
|
106
|
+
while (currentIndex < steps.length) {
|
|
107
|
+
const step = steps[currentIndex];
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const useExisting = await select({
|
|
91
|
-
message: "How would you like to proceed?",
|
|
92
|
-
options: [
|
|
93
|
-
{ value: true, label: "Continue with previous setup" },
|
|
94
|
-
{ value: false, label: "Start fresh (new setup)" },
|
|
95
|
-
],
|
|
96
|
-
initialValue: true,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (isCancel(useExisting)) onCancel();
|
|
100
|
-
useOldConfig = useExisting;
|
|
101
|
-
|
|
102
|
-
if (useOldConfig) {
|
|
103
|
-
if (oldConfig.apiClient === undefined) {
|
|
104
|
-
oldConfig.apiClient = oldConfig.axios ? "axios" : "none";
|
|
105
|
-
}
|
|
106
|
-
if (oldConfig.formatter === undefined) {
|
|
107
|
-
oldConfig.formatter = "none";
|
|
108
|
-
}
|
|
109
|
-
if (oldConfig.docsLanguage === undefined) {
|
|
110
|
-
oldConfig.docsLanguage = "en";
|
|
111
|
-
}
|
|
112
|
-
return { ...oldConfig, projectName };
|
|
109
|
+
// Check skip condition
|
|
110
|
+
if (step.skip?.(answers)) {
|
|
111
|
+
step.onSkip?.(answers);
|
|
112
|
+
currentIndex++;
|
|
113
|
+
continue;
|
|
113
114
|
}
|
|
114
|
-
}
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
message:
|
|
119
|
-
"Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)",
|
|
120
|
-
initialValue: true,
|
|
121
|
-
});
|
|
122
|
-
if (isCancel(quickSetupChoice)) onCancel();
|
|
116
|
+
const canGoBack = history.length > 0;
|
|
117
|
+
let res;
|
|
123
118
|
|
|
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
|
-
}
|
|
119
|
+
if (step.render) {
|
|
120
|
+
res = await step.render(answers, canGoBack, { selectFn, noteFn });
|
|
121
|
+
} else {
|
|
122
|
+
const stepOptions =
|
|
123
|
+
typeof step.getOptions === "function" ? step.getOptions(answers) : step.options;
|
|
124
|
+
const options = [
|
|
125
|
+
...stepOptions,
|
|
126
|
+
...(canGoBack
|
|
127
|
+
? [{ label: chalk.dim("← Back"), value: BACK, hint: "Return to previous question" }]
|
|
128
|
+
: []),
|
|
129
|
+
];
|
|
144
130
|
|
|
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();
|
|
131
|
+
const prevValue = answers[step.id];
|
|
132
|
+
const hasPrevValue =
|
|
133
|
+
prevValue !== undefined && stepOptions.some((o) => o.value === prevValue);
|
|
134
|
+
const defaultInitial =
|
|
135
|
+
typeof step.defaultInitial === "function"
|
|
136
|
+
? step.defaultInitial(answers)
|
|
137
|
+
: step.defaultInitial;
|
|
138
|
+
const initialValue = hasPrevValue ? prevValue : defaultInitial;
|
|
163
139
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
initialValue: "ts",
|
|
171
|
-
});
|
|
172
|
-
if (isCancel(language)) onCancel();
|
|
140
|
+
res = await selectFn({
|
|
141
|
+
message: step.message,
|
|
142
|
+
options,
|
|
143
|
+
initialValue,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
173
146
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{ label: "Bootstrap", value: "bootstrap" },
|
|
179
|
-
{ label: "None", value: "none" },
|
|
180
|
-
],
|
|
181
|
-
initialValue: "tailwind",
|
|
182
|
-
});
|
|
183
|
-
if (isCancel(cssFramework)) onCancel();
|
|
147
|
+
if (isCancel(res) || res === CANCEL) {
|
|
148
|
+
onCancelFn();
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
184
151
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
],
|
|
192
|
-
initialValue: "vitest",
|
|
193
|
-
});
|
|
194
|
-
if (isCancel(testing)) onCancel();
|
|
152
|
+
if (res === BACK) {
|
|
153
|
+
if (history.length > 0) {
|
|
154
|
+
currentIndex = history.pop();
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
195
158
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
159
|
+
// Step produced a normal answer
|
|
160
|
+
if (step.onAnswer) {
|
|
161
|
+
const nextIndex = step.onAnswer(res, answers, { history, currentIndex, steps });
|
|
162
|
+
if (typeof nextIndex === "number") {
|
|
163
|
+
history.push(currentIndex);
|
|
164
|
+
currentIndex = nextIndex;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
} else if (step.id) {
|
|
168
|
+
answers[step.id] = res;
|
|
169
|
+
}
|
|
201
170
|
|
|
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();
|
|
171
|
+
history.push(currentIndex);
|
|
172
|
+
currentIndex++;
|
|
173
|
+
}
|
|
212
174
|
|
|
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();
|
|
175
|
+
return answers;
|
|
176
|
+
}
|
|
223
177
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
178
|
+
export async function getUserInputs(
|
|
179
|
+
projectName,
|
|
180
|
+
{
|
|
181
|
+
quickSetup = false,
|
|
182
|
+
framework: rawFramework,
|
|
183
|
+
initialArch = null,
|
|
184
|
+
selectFn = select,
|
|
185
|
+
noteFn = note,
|
|
186
|
+
saveConfigFn = saveConfig,
|
|
187
|
+
configExistsFn = configExists,
|
|
188
|
+
loadConfigFn = loadConfig,
|
|
189
|
+
} = {}
|
|
190
|
+
) {
|
|
191
|
+
let framework = normalizeFramework(rawFramework);
|
|
192
|
+
// When no framework flag is given, ask interactively as the first step so all
|
|
193
|
+
// downstream prompts gate on the user's selection instead of the default.
|
|
194
|
+
const frameworkLocked =
|
|
195
|
+
rawFramework !== undefined && rawFramework !== null && String(rawFramework).trim() !== "";
|
|
196
|
+
// Variant is locked only when the flag names it explicitly (next:app-router).
|
|
197
|
+
// A bare `-f next` still asks App vs Pages interactively.
|
|
198
|
+
const variantLocked = frameworkLocked && String(rawFramework).includes(":");
|
|
199
|
+
// Non-interactive quick setup: capability-aware defaults without prompting.
|
|
200
|
+
if (quickSetup) {
|
|
201
|
+
const responses = getDefaultResponses(framework, { projectName });
|
|
202
|
+
if (initialArch) responses.architecture = initialArch;
|
|
203
|
+
await saveConfigFn(sanitizeResponsesForFramework(framework, responses));
|
|
204
|
+
return sanitizeResponsesForFramework(framework, responses);
|
|
205
|
+
}
|
|
234
206
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
207
|
+
let oldConfig = null;
|
|
208
|
+
const hasPrevious = await configExistsFn();
|
|
209
|
+
if (hasPrevious) {
|
|
210
|
+
oldConfig = await loadConfigFn();
|
|
211
|
+
oldConfig.projectName = projectName;
|
|
212
|
+
// Sanitize cached setup against current framework capabilities
|
|
213
|
+
oldConfig = sanitizeResponsesForFramework(framework, oldConfig);
|
|
214
|
+
}
|
|
240
215
|
|
|
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();
|
|
216
|
+
const steps = [];
|
|
251
217
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
218
|
+
if (hasPrevious) {
|
|
219
|
+
steps.push({
|
|
220
|
+
id: "useExisting",
|
|
221
|
+
render: async (_ans, _canGoBack, { selectFn: sFn, noteFn: nFn }) => {
|
|
222
|
+
nFn(
|
|
223
|
+
`Previous setup found:\n\n${formatConfig(oldConfig)}\n`,
|
|
224
|
+
"Previous Configuration"
|
|
225
|
+
);
|
|
226
|
+
return await sFn({
|
|
227
|
+
message: "How would you like to proceed?",
|
|
228
|
+
options: [
|
|
229
|
+
{ value: true, label: "Continue with previous setup" },
|
|
230
|
+
{ value: false, label: "Start fresh (new setup)" },
|
|
231
|
+
],
|
|
232
|
+
initialValue: true,
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
onAnswer: (val, ans) => {
|
|
236
|
+
ans.useExisting = val;
|
|
237
|
+
if (val === true) {
|
|
238
|
+
if (oldConfig.apiClient === undefined) {
|
|
239
|
+
oldConfig.apiClient = oldConfig.axios ? "axios" : "none";
|
|
240
|
+
}
|
|
241
|
+
if (oldConfig.formatter === undefined) {
|
|
242
|
+
oldConfig.formatter = "none";
|
|
243
|
+
}
|
|
244
|
+
if (oldConfig.docsLanguage === undefined) {
|
|
245
|
+
oldConfig.docsLanguage = "en";
|
|
246
|
+
}
|
|
247
|
+
return steps.length; // exit immediately
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
},
|
|
269
251
|
});
|
|
270
|
-
if (isCancel(formatter)) onCancel();
|
|
271
252
|
}
|
|
272
253
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
254
|
+
steps.push(
|
|
255
|
+
{
|
|
256
|
+
id: "framework",
|
|
257
|
+
skip: () => frameworkLocked,
|
|
258
|
+
onSkip: (ans) => {
|
|
259
|
+
ans.frameworkName = framework.name;
|
|
260
|
+
ans.frameworkVariant = framework.variant;
|
|
261
|
+
},
|
|
262
|
+
message: "Which framework do you want to scaffold?",
|
|
263
|
+
getOptions: () => [
|
|
264
|
+
{
|
|
265
|
+
label: "React + Vite",
|
|
266
|
+
value: "react:vite",
|
|
267
|
+
hint: "Client-side SPA with React Router",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
label: "Next.js",
|
|
271
|
+
value: "next",
|
|
272
|
+
hint: "Filesystem routing, server components, route handlers",
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
defaultInitial: "react:vite",
|
|
276
|
+
onAnswer: (val, ans, { steps: allSteps }) => {
|
|
277
|
+
const [name, ...rest] = String(val).split(":");
|
|
278
|
+
framework = normalizeFramework({ name, variant: rest.join(":") });
|
|
279
|
+
ans.frameworkName = framework.name;
|
|
280
|
+
ans.frameworkVariant = framework.variant;
|
|
281
|
+
const qs = allSteps.find((s) => s.id === "quickSetup");
|
|
282
|
+
if (qs) {
|
|
283
|
+
qs.message = isPromptVisible(framework, "router")
|
|
284
|
+
? "Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)"
|
|
285
|
+
: "Quick Setup? (TypeScript + Tailwind + Feature-based + ESLint + Vitest)";
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "nextRouter",
|
|
292
|
+
skip: () => framework.name !== "next" || variantLocked,
|
|
293
|
+
onSkip: (ans) => {
|
|
294
|
+
ans.frameworkVariant = framework.variant;
|
|
295
|
+
},
|
|
296
|
+
message: "Which Next.js router do you want to use?",
|
|
297
|
+
getOptions: () => [
|
|
298
|
+
{
|
|
299
|
+
label: "App Router",
|
|
300
|
+
value: "app-router",
|
|
301
|
+
hint: "Recommended: layouts, server components, route handlers",
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
label: "Pages Router",
|
|
305
|
+
value: "pages-router",
|
|
306
|
+
hint: "Classic pages directory with API routes",
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
defaultInitial: "app-router",
|
|
310
|
+
onAnswer: (val, ans) => {
|
|
311
|
+
framework = normalizeFramework({ name: "next", variant: String(val) });
|
|
312
|
+
ans.frameworkName = framework.name;
|
|
313
|
+
ans.frameworkVariant = framework.variant;
|
|
314
|
+
return null;
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "quickSetup",
|
|
319
|
+
message: isPromptVisible(framework, "router")
|
|
320
|
+
? "Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)"
|
|
321
|
+
: "Quick Setup? (TypeScript + Tailwind + Feature-based + ESLint + Vitest)",
|
|
322
|
+
getOptions: () => [
|
|
323
|
+
{ label: "Yes, use recommended defaults", value: true },
|
|
324
|
+
{ label: "No, custom setup", value: false },
|
|
325
|
+
],
|
|
326
|
+
defaultInitial: true,
|
|
327
|
+
onAnswer: (val, ans, { steps: allSteps }) => {
|
|
328
|
+
ans.quickSetup = val;
|
|
329
|
+
if (val === true) {
|
|
330
|
+
const defaults = getDefaultResponses(framework, { projectName });
|
|
331
|
+
Object.assign(ans, defaults, { projectName, quickSetup: true });
|
|
332
|
+
if (initialArch) ans.architecture = initialArch;
|
|
333
|
+
// Jump to finalConfirm
|
|
334
|
+
return allSteps.length - 1;
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "architecture",
|
|
341
|
+
message: "Which project architecture do you want?",
|
|
342
|
+
getOptions: () =>
|
|
343
|
+
filterCompatibleChoices(framework, "architecture", [
|
|
344
|
+
{
|
|
345
|
+
label: "Feature-based",
|
|
346
|
+
value: "feature-based",
|
|
347
|
+
hint: "Scales to large apps — code grouped by business domain",
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
label: "Type-based",
|
|
351
|
+
value: "type-based",
|
|
352
|
+
hint: "Small apps / component libraries — code grouped by UI type",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
label: "Hybrid",
|
|
356
|
+
value: "hybrid",
|
|
357
|
+
hint: "Next.js: features + shared code",
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
label: "None",
|
|
361
|
+
value: "none",
|
|
362
|
+
hint: "Minimal flat src/ scaffold — no imposed structure",
|
|
363
|
+
},
|
|
364
|
+
]),
|
|
365
|
+
defaultInitial: initialArch || "feature-based",
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: "language",
|
|
369
|
+
message: "Which language do you want to use?",
|
|
370
|
+
getOptions: () =>
|
|
371
|
+
filterCompatibleChoices(framework, "language", [
|
|
372
|
+
{ label: "TypeScript", value: "ts" },
|
|
373
|
+
{ label: "JavaScript", value: "js" },
|
|
374
|
+
]),
|
|
375
|
+
defaultInitial: "ts",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "cssFramework",
|
|
379
|
+
message: "Which CSS framework do you want to use?",
|
|
380
|
+
getOptions: () =>
|
|
381
|
+
filterCompatibleChoices(framework, "styling", [
|
|
382
|
+
{ label: "Tailwind CSS", value: "tailwind" },
|
|
383
|
+
{ label: "None", value: "none" },
|
|
384
|
+
]),
|
|
385
|
+
defaultInitial: "tailwind",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "uiKit",
|
|
389
|
+
skip: (ans) => ans.cssFramework !== "tailwind",
|
|
390
|
+
onSkip: (ans) => {
|
|
391
|
+
ans.uiKit = "none";
|
|
392
|
+
},
|
|
393
|
+
message: "Which UI kit do you want to use?",
|
|
394
|
+
getOptions: () =>
|
|
395
|
+
filterCompatibleChoices(framework, "uiKit", [
|
|
396
|
+
{ label: "None", value: "none" },
|
|
397
|
+
{
|
|
398
|
+
label: "shadcn/ui",
|
|
399
|
+
value: "shadcn",
|
|
400
|
+
hint: "Requires Tailwind CSS — primitives land in paths.ui",
|
|
401
|
+
},
|
|
402
|
+
]),
|
|
403
|
+
defaultInitial: "none",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "testing",
|
|
407
|
+
message: "Which testing framework do you want to set up?",
|
|
408
|
+
getOptions: () => [
|
|
409
|
+
{ label: "Vitest", value: "vitest" },
|
|
410
|
+
{ label: "Jest", value: "jest" },
|
|
411
|
+
{ label: "None", value: "none" },
|
|
412
|
+
],
|
|
413
|
+
defaultInitial: "vitest",
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: "router",
|
|
417
|
+
skip: () => !isPromptVisible(framework, "router"),
|
|
418
|
+
onSkip: (ans) => {
|
|
419
|
+
ans.router = false;
|
|
420
|
+
},
|
|
421
|
+
message: "Would you like to install React Router?",
|
|
422
|
+
getOptions: () => [
|
|
423
|
+
{ label: "Yes", value: true },
|
|
424
|
+
{ label: "No", value: false },
|
|
425
|
+
],
|
|
426
|
+
defaultInitial: true,
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "bundler",
|
|
430
|
+
skip: () => !isPromptVisible(framework, "bundler"),
|
|
431
|
+
onSkip: (ans) => {
|
|
432
|
+
ans.bundler = undefined;
|
|
433
|
+
},
|
|
434
|
+
message: "Which bundler do you want to use?",
|
|
435
|
+
getOptions: () =>
|
|
436
|
+
filterCompatibleChoices(framework, "bundler", [
|
|
437
|
+
{ label: "Turbopack", value: "turbopack" },
|
|
438
|
+
{ label: "Webpack", value: "webpack" },
|
|
439
|
+
]),
|
|
440
|
+
defaultInitial: "turbopack",
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
id: "adapter",
|
|
444
|
+
skip: () => !isPromptVisible(framework, "adapter"),
|
|
445
|
+
onSkip: (ans) => {
|
|
446
|
+
ans.adapter = "none";
|
|
447
|
+
},
|
|
448
|
+
message: "Which deployment adapter do you want to use?",
|
|
449
|
+
getOptions: () =>
|
|
450
|
+
filterCompatibleChoices(framework, "adapter", [
|
|
451
|
+
{ label: "None (default)", value: "none" },
|
|
452
|
+
{ label: "Node", value: "node" },
|
|
453
|
+
{ label: "Vercel", value: "vercel" },
|
|
454
|
+
{ label: "Cloudflare", value: "cloudflare" },
|
|
455
|
+
{ label: "Static", value: "static" },
|
|
456
|
+
]),
|
|
457
|
+
defaultInitial: "none",
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "reactCompiler",
|
|
461
|
+
skip: () => !isPromptVisible(framework, "reactCompiler"),
|
|
462
|
+
onSkip: (ans) => {
|
|
463
|
+
ans.reactCompiler = false;
|
|
464
|
+
},
|
|
465
|
+
message: "Would you like to enable the React Compiler (experimental)?",
|
|
466
|
+
getOptions: () => [
|
|
467
|
+
{ label: "No (default)", value: false },
|
|
468
|
+
{ label: "Yes", value: true },
|
|
469
|
+
],
|
|
470
|
+
defaultInitial: false,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: "stateManagement",
|
|
474
|
+
message: "Which state management library do you want to use?",
|
|
475
|
+
getOptions: () => [
|
|
476
|
+
{ label: "None", value: "none" },
|
|
477
|
+
{ label: "Redux Toolkit", value: "redux" },
|
|
478
|
+
{ label: "Zustand", value: "zustand" },
|
|
479
|
+
],
|
|
480
|
+
defaultInitial: "none",
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: "iconLibrary",
|
|
484
|
+
message: "Which icon library would you like to use?",
|
|
485
|
+
getOptions: () => [
|
|
486
|
+
{ label: "None", value: "none" },
|
|
487
|
+
{ label: "Lucide Icons", value: "lucide" },
|
|
488
|
+
{ label: "Huge Icons", value: "huge" },
|
|
489
|
+
],
|
|
490
|
+
defaultInitial: "none",
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
id: "apiClient",
|
|
494
|
+
message: "Which API client do you want to use?",
|
|
495
|
+
getOptions: () => [
|
|
496
|
+
{ label: "None", value: "none" },
|
|
497
|
+
{ label: "Axios", value: "axios" },
|
|
498
|
+
{ label: "Fetch (native)", value: "fetch" },
|
|
499
|
+
],
|
|
500
|
+
defaultInitial: "none",
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
id: "gitInit",
|
|
504
|
+
message: "Would you like to initialize a Git repository?",
|
|
505
|
+
getOptions: () => [
|
|
506
|
+
{ label: "Yes", value: true },
|
|
507
|
+
{ label: "No", value: false },
|
|
508
|
+
],
|
|
509
|
+
defaultInitial: false,
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: "linter",
|
|
513
|
+
message: "Which linter do you want to use?",
|
|
514
|
+
getOptions: () =>
|
|
515
|
+
filterCompatibleChoices(framework, "linter", [
|
|
516
|
+
{
|
|
517
|
+
label: "ESLint",
|
|
518
|
+
value: "eslint",
|
|
519
|
+
hint: "Industry standard JavaScript linter (Recommended)",
|
|
520
|
+
},
|
|
521
|
+
{ label: "Oxlint", value: "oxlint", hint: "Fast Rust-based linter" },
|
|
522
|
+
{ label: "Biome", value: "biome", hint: "Toolchain for web projects" },
|
|
523
|
+
{ label: "None", value: "none" },
|
|
524
|
+
]),
|
|
525
|
+
defaultInitial: "eslint",
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
id: "formatter",
|
|
529
|
+
skip: (ans) => ans.linter === "none",
|
|
530
|
+
onSkip: (ans) => {
|
|
531
|
+
ans.formatter = "none";
|
|
532
|
+
},
|
|
533
|
+
message: "Which formatter do you want to use?",
|
|
534
|
+
getOptions: (ans) =>
|
|
535
|
+
filterCompatibleChoices(
|
|
536
|
+
framework,
|
|
537
|
+
"formatter",
|
|
538
|
+
ans.linter === "eslint"
|
|
539
|
+
? [
|
|
540
|
+
{ label: "None", value: "none" },
|
|
541
|
+
{ label: "Prettier", value: "prettier" },
|
|
542
|
+
]
|
|
543
|
+
: [
|
|
544
|
+
{ label: "None", value: "none" },
|
|
545
|
+
{ label: "Oxfmt (Recommended)", value: "oxfmt" },
|
|
546
|
+
{ label: "Prettier", value: "prettier" },
|
|
547
|
+
]
|
|
548
|
+
),
|
|
549
|
+
defaultInitial: (ans) => {
|
|
550
|
+
const compatible = filterCompatibleChoices(
|
|
551
|
+
framework,
|
|
552
|
+
"formatter",
|
|
553
|
+
ans.linter === "eslint"
|
|
554
|
+
? [{ value: "none" }, { value: "prettier" }]
|
|
555
|
+
: [{ value: "none" }, { value: "oxfmt" }, { value: "prettier" }]
|
|
556
|
+
).map((o) => o.value);
|
|
557
|
+
const preferred = ans.linter === "eslint" ? "prettier" : "oxfmt";
|
|
558
|
+
return compatible.includes(preferred) ? preferred : compatible[0];
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: "agentDocs",
|
|
563
|
+
message: "Generate .lumen/ agent context for AI assistants?",
|
|
564
|
+
getOptions: () => [
|
|
565
|
+
{ label: "No (default)", value: false },
|
|
566
|
+
{ label: "Yes", value: true, hint: "Emits .lumen/ with project, architecture and conventions" },
|
|
567
|
+
],
|
|
568
|
+
defaultInitial: false,
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
id: "docsLanguage",
|
|
572
|
+
message: "Which docs language do you want?",
|
|
573
|
+
getOptions: () => [
|
|
574
|
+
{ label: "English", value: "en" },
|
|
575
|
+
{ label: "Español", value: "es" },
|
|
576
|
+
],
|
|
577
|
+
defaultInitial: "en",
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: "readme",
|
|
581
|
+
message: "Would you like to generate a README.md and LICENSE?",
|
|
582
|
+
getOptions: () => [
|
|
583
|
+
{ label: "Yes", value: true },
|
|
584
|
+
{ label: "No", value: false },
|
|
585
|
+
],
|
|
586
|
+
defaultInitial: true,
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "finalConfirm",
|
|
590
|
+
render: async (ans, canGoBack, { selectFn: sFn, noteFn: nFn }) => {
|
|
591
|
+
nFn(`\n${formatConfig(ans)}\n`, "Project Configuration Summary");
|
|
592
|
+
return await sFn({
|
|
593
|
+
message: "Proceed with project generation?",
|
|
594
|
+
options: [
|
|
595
|
+
{ label: "Confirm and scaffold project", value: true },
|
|
596
|
+
...(canGoBack
|
|
597
|
+
? [{ label: chalk.dim("← Back to edit options"), value: BACK }]
|
|
598
|
+
: []),
|
|
599
|
+
],
|
|
600
|
+
initialValue: true,
|
|
601
|
+
});
|
|
602
|
+
},
|
|
603
|
+
}
|
|
604
|
+
);
|
|
282
605
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
606
|
+
const responses = await runPromptFlow({
|
|
607
|
+
steps,
|
|
608
|
+
initialAnswers: { projectName },
|
|
609
|
+
selectFn,
|
|
610
|
+
noteFn,
|
|
611
|
+
onCancelFn: onCancel,
|
|
286
612
|
});
|
|
287
|
-
if (isCancel(readme)) onCancel();
|
|
288
613
|
|
|
289
|
-
|
|
614
|
+
if (responses.useExisting) {
|
|
615
|
+
return { ...oldConfig, projectName };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const {
|
|
619
|
+
useExisting: _useExisting,
|
|
620
|
+
quickSetup: _quickSetup,
|
|
621
|
+
bundler,
|
|
622
|
+
adapter,
|
|
623
|
+
reactCompiler,
|
|
624
|
+
agentDocs,
|
|
625
|
+
...picked
|
|
626
|
+
} = responses;
|
|
627
|
+
// Final safety net: BACK-navigation can reselect the framework after other
|
|
628
|
+
// answers were collected, so re-validate everything before persisting.
|
|
629
|
+
const finalResponses = sanitizeResponsesForFramework(framework, {
|
|
290
630
|
projectName,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
631
|
+
frameworkName: framework.name,
|
|
632
|
+
frameworkVariant: framework.variant,
|
|
633
|
+
...picked,
|
|
634
|
+
});
|
|
635
|
+
if (bundler !== undefined) {
|
|
636
|
+
finalResponses.bundler = bundler;
|
|
637
|
+
}
|
|
638
|
+
if (adapter !== undefined && adapter !== "none") {
|
|
639
|
+
finalResponses.adapter = adapter;
|
|
640
|
+
}
|
|
641
|
+
if (reactCompiler !== undefined) {
|
|
642
|
+
finalResponses.reactCompiler = reactCompiler;
|
|
643
|
+
}
|
|
644
|
+
if (agentDocs !== undefined) {
|
|
645
|
+
finalResponses.agentDocs = agentDocs;
|
|
646
|
+
}
|
|
305
647
|
|
|
306
|
-
|
|
307
|
-
|
|
648
|
+
// No config cache write until final confirm!
|
|
649
|
+
await saveConfigFn(finalResponses);
|
|
650
|
+
return finalResponses;
|
|
308
651
|
}
|