create-lumen 2.0.0-alpha.2 → 2.0.0-beta.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.
Files changed (174) hide show
  1. package/README.md +24 -6
  2. package/bin/cli.js +2 -1
  3. package/package.json +2 -2
  4. package/schema/lumen.config.v2.json +29 -0
  5. package/src/agent-context.js +71 -47
  6. package/src/cleanup.js +19 -1
  7. package/src/cli-args.js +131 -5
  8. package/src/configure.js +8 -4
  9. package/src/dependencies.js +46 -13
  10. package/src/engine/capabilities.js +71 -1
  11. package/src/engine/composition.js +334 -17
  12. package/src/engine/providers/base-provider.js +4 -4
  13. package/src/env.js +24 -6
  14. package/src/main.js +38 -15
  15. package/src/manifest/emit.js +14 -1
  16. package/src/manifest/schema.js +30 -0
  17. package/src/prompts.js +227 -23
  18. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  19. package/templates/bases/next/app-router/js/README.md +36 -0
  20. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  21. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  22. package/templates/bases/next/app-router/js/package.json +15 -0
  23. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  24. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  25. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  26. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  27. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  28. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  29. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  30. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  31. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  32. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  33. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  34. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  35. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  36. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  37. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  38. package/templates/bases/next/app-router/ts/README.md +36 -0
  39. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  40. package/templates/bases/next/app-router/ts/package.json +21 -0
  41. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  42. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  43. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  44. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  45. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  46. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  47. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  48. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  49. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  50. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  51. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  52. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  53. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  54. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  55. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  56. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  57. package/templates/bases/next/pages-router/js/README.md +40 -0
  58. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  59. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  60. package/templates/bases/next/pages-router/js/package.json +15 -0
  61. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  62. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  63. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  64. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  65. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  66. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  67. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  68. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  69. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  70. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  71. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  72. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  73. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  74. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  75. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  76. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  77. package/templates/bases/next/pages-router/ts/README.md +40 -0
  78. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  79. package/templates/bases/next/pages-router/ts/package.json +21 -0
  80. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  81. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  82. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  83. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  84. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  85. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  86. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  87. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  88. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  89. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  90. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  91. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  92. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  93. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  94. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  95. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  96. package/templates/conditional/database/prisma/db.js +14 -0
  97. package/templates/conditional/database/prisma/db.ts +16 -0
  98. package/templates/conditional/database/prisma/schema.prisma +9 -0
  99. package/templates/conditional/database/prisma/seed.js +16 -0
  100. package/templates/conditional/database/prisma/seed.ts +16 -0
  101. package/templates/conditional/docker/.dockerignore +10 -0
  102. package/templates/conditional/docker/Dockerfile +57 -0
  103. package/templates/conditional/docker/docker-compose.yml +35 -0
  104. package/templates/conditional/linting/biome/biome.json +31 -0
  105. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  106. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  107. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  108. package/templates/conditional/next/config/node/next.config.ts +7 -0
  109. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  110. package/templates/conditional/next/config/static/next.config.ts +7 -0
  111. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  112. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  113. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  114. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  115. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  116. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  117. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  118. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  119. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  120. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  121. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  122. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  123. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  124. package/templates/conditional/next/font/src/app/layout.js +25 -0
  125. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  126. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  127. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  128. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  129. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  130. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  131. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  132. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  133. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  134. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  135. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  136. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  137. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  138. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  139. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  140. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  141. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  142. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  143. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  144. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  145. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  146. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  147. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  148. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  149. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  150. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  151. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  152. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  153. package/templates/conditional/next/server-components/README.md +13 -0
  154. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  155. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  156. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  157. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  158. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  159. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  160. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  161. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  162. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
  163. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  164. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  165. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  166. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  167. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  168. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  169. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  170. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  171. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  172. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  173. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  174. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
@@ -23,6 +23,8 @@ export const DOCS_LANGUAGES = ["en", "es"];
23
23
  export const TOOLING_LANGUAGES = ["ts", "js"];
24
24
  export const TOOLING_LINTERS = ["eslint", "oxlint", "biome", "none"];
25
25
  export const TOOLING_FORMATTERS = ["prettier", "oxfmt", "none"];
26
+ export const DATABASE_ORMS = ["prisma", "none"];
27
+ export const DATABASE_PROVIDERS = ["postgresql"];
26
28
 
27
29
  // ---------------------------------------------------------------------------
28
30
  // Sub-schemas
@@ -121,6 +123,13 @@ const toolingSchema = z
121
123
  })
122
124
  .strict();
123
125
 
126
+ const databaseSchema = z
127
+ .object({
128
+ orm: z.enum(DATABASE_ORMS),
129
+ provider: z.enum(DATABASE_PROVIDERS).optional().default("postgresql"),
130
+ })
131
+ .strict();
132
+
124
133
  // ---------------------------------------------------------------------------
125
134
  // Top-level manifest v2 - strict, no extra keys except $schema
126
135
  // ---------------------------------------------------------------------------
@@ -136,6 +145,9 @@ export const manifestSchemaV2 = z
136
145
  docs: docsSchema,
137
146
  paths: pathsSchema,
138
147
  tooling: toolingSchema,
148
+ database: databaseSchema.optional(),
149
+ docker: z.boolean().optional(),
150
+ routeGroups: z.boolean().optional(),
139
151
  reactCompiler: z.boolean().optional(),
140
152
  agentDocs: z.boolean().optional(),
141
153
  })
@@ -177,6 +189,24 @@ export const manifestSchemaV2 = z
177
189
  });
178
190
  }
179
191
 
192
+ // database only for Next.js (#62)
193
+ if (val.database !== undefined && val.database.orm !== "none" && framework.name !== "next") {
194
+ ctx.addIssue({
195
+ code: "custom",
196
+ path: ["database", "orm"],
197
+ message: `database is only valid when framework.name is "next"`,
198
+ });
199
+ }
200
+
201
+ // routeGroups only for Next.js App Router (#64)
202
+ if (val.routeGroups === true && (framework.name !== "next" || framework.variant !== "app-router")) {
203
+ ctx.addIssue({
204
+ code: "custom",
205
+ path: ["routeGroups"],
206
+ message: `routeGroups is only valid when framework is "next:app-router"`,
207
+ });
208
+ }
209
+
180
210
  // architecture scoping per framework (#34) — canonical `preset`, legacy `type` fallback
181
211
  const archValue = architecture.preset ?? architecture.type;
182
212
  const allowedByFramework =
package/src/prompts.js CHANGED
@@ -46,32 +46,53 @@ export function formatConfig(responses) {
46
46
  ? "Redux Toolkit"
47
47
  : "Zustand";
48
48
 
49
- const docsLang = responses.docsLanguage === "es" ? "ES" : responses.docsLanguage === "en" ? "EN" : "—";
49
+ const docsLang = responses.docsLanguage === "es" ? "ES" : responses.docsLanguage === "en" ? "EN" : "—";
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";
50
56
  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)}`,
57
+ `• ${chalk.bold("Framework:")} ${chalk.green(fwLabel)}`,
58
+ `• ${chalk.bold("Architecture:")} ${chalk.green(arch)}`,
59
+ `• ${chalk.bold("Language:")} ${chalk.green(lang)}`,
60
+ `• ${chalk.bold("CSS:")} ${chalk.yellow(css)}`,
61
+ `• ${chalk.bold("UI Kit:")} ${responses.uiKit === "shadcn" ? chalk.cyan("shadcn/ui") : chalk.gray("None")}`,
62
+ `• ${chalk.bold("Testing:")} ${chalk.magenta(testing)}`,
55
63
  ];
56
64
 
57
65
  if (responses.router !== undefined) {
58
- lines.push(`• ${chalk.bold("Router:")} ${responses.router ? chalk.green("Yes") : chalk.red("No")}`);
66
+ lines.push(`• ${chalk.bold("Router:")} ${responses.router ? chalk.green("Yes") : chalk.red("No")}`);
59
67
  }
60
68
  if (responses.bundler) {
61
- lines.push(`• ${chalk.bold("Bundler:")} ${chalk.cyan(responses.bundler)}`);
69
+ lines.push(`• ${chalk.bold("Bundler:")} ${chalk.cyan(responses.bundler)}`);
62
70
  }
63
71
  if (responses.adapter && responses.adapter !== "none") {
64
- lines.push(`• ${chalk.bold("Adapter:")} ${chalk.cyan(responses.adapter)}`);
72
+ lines.push(`• ${chalk.bold("Adapter:")} ${chalk.cyan(responses.adapter)}`);
73
+ }
74
+ if (responses.database && responses.database !== "none") {
75
+ lines.push(`• ${chalk.bold("Database:")} ${chalk.cyan(responses.database === "prisma" ? "Prisma ORM" : responses.database)}`);
76
+ }
77
+ if (responses.reactCompiler !== undefined) {
78
+ lines.push(`• ${chalk.bold("React Compiler:")} ${responses.reactCompiler ? chalk.green("Yes") : chalk.red("No")}`);
79
+ }
80
+ if (responses.routeGroups !== undefined) {
81
+ lines.push(`• ${chalk.bold("Route Groups:")} ${responses.routeGroups ? chalk.green("Yes") : chalk.red("No")}`);
82
+ }
83
+ if (responses.docker !== undefined) {
84
+ lines.push(`• ${chalk.bold("Docker:")} ${responses.docker ? chalk.green("Yes") : chalk.red("No")}`);
65
85
  }
66
86
  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")}`
87
+ `• ${chalk.bold("State:")} ${chalk.blue(state)}`,
88
+ `• ${chalk.bold("Icons:")} ${chalk.blue(responses.iconLibrary === "none" ? "None" : responses.iconLibrary === "lucide" ? "Lucide" : "Huge")}`,
89
+ `• ${chalk.bold("API Client:")} ${responses.apiClient === "axios" ? chalk.green("Axios") : responses.apiClient === "fetch" ? chalk.yellow("Fetch") : chalk.red("None")}`,
90
+ `• ${chalk.bold("Linter:")} ${responses.linter === "oxlint" ? chalk.green("Oxlint") : responses.linter === "biome" ? chalk.cyan("Biome") : responses.linter === "eslint" ? chalk.yellow("ESLint") : chalk.red("None")}`,
91
+ `• ${chalk.bold("Formatter:")} ${responses.formatter === "oxfmt" ? chalk.green("Oxfmt") : responses.formatter === "prettier" ? chalk.yellow("Prettier") : chalk.red("None")}`,
92
+ `• ${chalk.bold("Docs:")} ${chalk.cyan(docsLang)}`,
93
+ `• ${chalk.bold("Agent Docs:")} ${responses.agentDocs ? chalk.green("Yes") : chalk.gray("No")}`,
94
+ `• ${chalk.bold("Git:")} ${responses.gitInit ? chalk.green("Yes") : chalk.red("No")}`,
95
+ `• ${chalk.bold("README:")} ${responses.readme ? chalk.green("Yes") : chalk.red("No")}`
75
96
  );
76
97
  return lines.join("\n");
77
98
  }
@@ -112,7 +133,7 @@ export async function runPromptFlow({
112
133
  const options = [
113
134
  ...stepOptions,
114
135
  ...(canGoBack
115
- ? [{ label: chalk.dim(" Back"), value: BACK, hint: "Return to previous question" }]
136
+ ? [{ label: chalk.dim("← Back"), value: BACK, hint: "Return to previous question" }]
116
137
  : []),
117
138
  ];
118
139
 
@@ -176,13 +197,20 @@ export async function getUserInputs(
176
197
  loadConfigFn = loadConfig,
177
198
  } = {}
178
199
  ) {
179
- const framework = normalizeFramework(rawFramework);
200
+ let framework = normalizeFramework(rawFramework);
201
+ // When no framework flag is given, ask interactively as the first step so all
202
+ // downstream prompts gate on the user's selection instead of the default.
203
+ const frameworkLocked =
204
+ rawFramework !== undefined && rawFramework !== null && String(rawFramework).trim() !== "";
205
+ // Variant is locked only when the flag names it explicitly (next:app-router).
206
+ // A bare `-f next` still asks App vs Pages interactively.
207
+ const variantLocked = frameworkLocked && String(rawFramework).includes(":");
180
208
  // Non-interactive quick setup: capability-aware defaults without prompting.
181
209
  if (quickSetup) {
182
210
  const responses = getDefaultResponses(framework, { projectName });
183
211
  if (initialArch) responses.architecture = initialArch;
184
- await saveConfigFn(responses);
185
- return responses;
212
+ await saveConfigFn(sanitizeResponsesForFramework(framework, responses));
213
+ return sanitizeResponsesForFramework(framework, responses);
186
214
  }
187
215
 
188
216
  let oldConfig = null;
@@ -233,6 +261,68 @@ export async function getUserInputs(
233
261
  }
234
262
 
235
263
  steps.push(
264
+ {
265
+ id: "framework",
266
+ skip: () => frameworkLocked,
267
+ onSkip: (ans) => {
268
+ ans.frameworkName = framework.name;
269
+ ans.frameworkVariant = framework.variant;
270
+ },
271
+ message: "Which framework do you want to scaffold?",
272
+ getOptions: () => [
273
+ {
274
+ label: "React + Vite",
275
+ value: "react:vite",
276
+ hint: "Client-side SPA with React Router",
277
+ },
278
+ {
279
+ label: "Next.js",
280
+ value: "next",
281
+ hint: "Filesystem routing, server components, route handlers",
282
+ },
283
+ ],
284
+ defaultInitial: "react:vite",
285
+ onAnswer: (val, ans, { steps: allSteps }) => {
286
+ const [name, ...rest] = String(val).split(":");
287
+ framework = normalizeFramework({ name, variant: rest.join(":") });
288
+ ans.frameworkName = framework.name;
289
+ ans.frameworkVariant = framework.variant;
290
+ const qs = allSteps.find((s) => s.id === "quickSetup");
291
+ if (qs) {
292
+ qs.message = isPromptVisible(framework, "router")
293
+ ? "Quick Setup? (TypeScript + Tailwind + Feature-based + Router + ESLint + Vitest)"
294
+ : "Quick Setup? (TypeScript + Tailwind + Feature-based + ESLint + Vitest)";
295
+ }
296
+ return null;
297
+ },
298
+ },
299
+ {
300
+ id: "nextRouter",
301
+ skip: () => framework.name !== "next" || variantLocked,
302
+ onSkip: (ans) => {
303
+ ans.frameworkVariant = framework.variant;
304
+ },
305
+ message: "Which Next.js router do you want to use?",
306
+ getOptions: () => [
307
+ {
308
+ label: "App Router",
309
+ value: "app-router",
310
+ hint: "Recommended: layouts, server components, route handlers",
311
+ },
312
+ {
313
+ label: "Pages Router",
314
+ value: "pages-router",
315
+ hint: "Classic pages directory with API routes",
316
+ },
317
+ ],
318
+ defaultInitial: "app-router",
319
+ onAnswer: (val, ans) => {
320
+ framework = normalizeFramework({ name: "next", variant: String(val) });
321
+ ans.frameworkName = framework.name;
322
+ ans.frameworkVariant = framework.variant;
323
+ return null;
324
+ },
325
+ },
236
326
  {
237
327
  id: "quickSetup",
238
328
  message: isPromptVisible(framework, "router")
@@ -303,6 +393,24 @@ export async function getUserInputs(
303
393
  ]),
304
394
  defaultInitial: "tailwind",
305
395
  },
396
+ {
397
+ id: "uiKit",
398
+ skip: (ans) => ans.cssFramework !== "tailwind",
399
+ onSkip: (ans) => {
400
+ ans.uiKit = "none";
401
+ },
402
+ message: "Which UI kit do you want to use?",
403
+ getOptions: () =>
404
+ filterCompatibleChoices(framework, "uiKit", [
405
+ { label: "None", value: "none" },
406
+ {
407
+ label: "shadcn/ui",
408
+ value: "shadcn",
409
+ hint: "Requires Tailwind CSS — primitives land in paths.ui",
410
+ },
411
+ ]),
412
+ defaultInitial: "none",
413
+ },
306
414
  {
307
415
  id: "testing",
308
416
  message: "Which testing framework do you want to set up?",
@@ -311,7 +419,15 @@ export async function getUserInputs(
311
419
  { label: "Jest", value: "jest" },
312
420
  { label: "None", value: "none" },
313
421
  ],
314
- defaultInitial: "vitest",
422
+ defaultInitial: (ans) => {
423
+ const fw = normalizeFramework(
424
+ ans.framework || {
425
+ name: ans.frameworkName,
426
+ variant: ans.frameworkVariant,
427
+ }
428
+ );
429
+ return fw.name === "next" ? "none" : "vitest";
430
+ },
315
431
  },
316
432
  {
317
433
  id: "router",
@@ -357,6 +473,63 @@ export async function getUserInputs(
357
473
  ]),
358
474
  defaultInitial: "none",
359
475
  },
476
+ {
477
+ id: "database",
478
+ skip: () => !isPromptVisible(framework, "database"),
479
+ onSkip: (ans) => {
480
+ ans.database = "none";
481
+ },
482
+ message: "Which database ORM do you want to use?",
483
+ getOptions: () =>
484
+ filterCompatibleChoices(framework, "database", [
485
+ {
486
+ label: "Prisma ORM",
487
+ value: "prisma",
488
+ hint: "PostgreSQL schema, client singleton, seed script",
489
+ },
490
+ { label: "None", value: "none" },
491
+ ]),
492
+ defaultInitial: "none",
493
+ },
494
+ {
495
+ id: "reactCompiler",
496
+ skip: () => !isPromptVisible(framework, "reactCompiler"),
497
+ onSkip: (ans) => {
498
+ ans.reactCompiler = false;
499
+ },
500
+ message: "Would you like to enable the React Compiler (experimental)?",
501
+ getOptions: () => [
502
+ { label: "No (default)", value: false },
503
+ { label: "Yes", value: true },
504
+ ],
505
+ defaultInitial: false,
506
+ },
507
+ {
508
+ id: "routeGroups",
509
+ skip: () => !isPromptVisible(framework, "routeGroups"),
510
+ onSkip: (ans) => {
511
+ ans.routeGroups = false;
512
+ },
513
+ message: "Include route groups preset (public, auth, private, admin)?",
514
+ getOptions: () => [
515
+ { label: "No (default)", value: false },
516
+ { label: "Yes", value: true },
517
+ ],
518
+ defaultInitial: false,
519
+ },
520
+ {
521
+ id: "docker",
522
+ skip: () => !isPromptVisible(framework, "docker"),
523
+ onSkip: (ans) => {
524
+ ans.docker = false;
525
+ },
526
+ message: "Include Docker setup?",
527
+ getOptions: () => [
528
+ { label: "No (default)", value: false },
529
+ { label: "Yes", value: true, hint: "Multi-stage Dockerfile and docker-compose" },
530
+ ],
531
+ defaultInitial: false,
532
+ },
360
533
  {
361
534
  id: "stateManagement",
362
535
  message: "Which state management library do you want to use?",
@@ -446,6 +619,15 @@ export async function getUserInputs(
446
619
  return compatible.includes(preferred) ? preferred : compatible[0];
447
620
  },
448
621
  },
622
+ {
623
+ id: "agentDocs",
624
+ message: "Generate AGENTS.md for AI assistants?",
625
+ getOptions: () => [
626
+ { label: "No (default)", value: false },
627
+ { label: "Yes", value: true, hint: "Emits AGENTS.md referencing lumen.config.json (SSOT)" },
628
+ ],
629
+ defaultInitial: false,
630
+ },
449
631
  {
450
632
  id: "docsLanguage",
451
633
  message: "Which docs language do you want?",
@@ -499,20 +681,42 @@ export async function getUserInputs(
499
681
  quickSetup: _quickSetup,
500
682
  bundler,
501
683
  adapter,
684
+ database,
685
+ reactCompiler,
686
+ routeGroups,
687
+ agentDocs,
688
+ docker,
502
689
  ...picked
503
690
  } = responses;
504
- const finalResponses = {
691
+ // Final safety net: BACK-navigation can reselect the framework after other
692
+ // answers were collected, so re-validate everything before persisting.
693
+ const finalResponses = sanitizeResponsesForFramework(framework, {
505
694
  projectName,
506
695
  frameworkName: framework.name,
507
696
  frameworkVariant: framework.variant,
508
697
  ...picked,
509
- };
698
+ });
510
699
  if (bundler !== undefined) {
511
700
  finalResponses.bundler = bundler;
512
701
  }
513
702
  if (adapter !== undefined && adapter !== "none") {
514
703
  finalResponses.adapter = adapter;
515
704
  }
705
+ if (database !== undefined && database !== "none") {
706
+ finalResponses.database = database;
707
+ }
708
+ if (reactCompiler !== undefined) {
709
+ finalResponses.reactCompiler = reactCompiler;
710
+ }
711
+ if (routeGroups !== undefined) {
712
+ finalResponses.routeGroups = routeGroups;
713
+ }
714
+ if (agentDocs !== undefined) {
715
+ finalResponses.agentDocs = agentDocs;
716
+ }
717
+ if (docker !== undefined) {
718
+ finalResponses.docker = docker;
719
+ }
516
720
 
517
721
  // No config cache write until final confirm!
518
722
  await saveConfigFn(finalResponses);
@@ -0,0 +1,19 @@
1
+ {
2
+ "cli": "create-next-app",
3
+ "source": "create-next-app",
4
+ "version": "16.3.8",
5
+ "flags": [
6
+ "--js",
7
+ "--app",
8
+ "--src-dir",
9
+ "--import-alias",
10
+ "@/*",
11
+ "--no-tailwind",
12
+ "--no-eslint",
13
+ "--skip-install",
14
+ "--disable-git",
15
+ "--no-agents-md",
16
+ "--no-react-compiler"
17
+ ],
18
+ "date": "2026-10-06"
19
+ }
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,7 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "@/*": ["./src/*"]
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ /* config options here */
4
+ };
5
+
6
+ export default nextConfig;
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "next-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "next": "^16.0.0",
12
+ "react": "^19.0.0",
13
+ "react-dom": "^19.0.0"
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,5 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function GET() {
4
+ return NextResponse.json({ hello: "lumen" });
5
+ }
@@ -0,0 +1,33 @@
1
+ import Link from "next/link";
2
+
3
+ export async function generateMetadata({ params }) {
4
+ const resolvedParams = await params;
5
+ const slug = resolvedParams?.slug || "";
6
+ return {
7
+ title: slug,
8
+ };
9
+ }
10
+
11
+ export default async function BlogPost({ params }) {
12
+ const resolvedParams = await params;
13
+ const slug = resolvedParams?.slug || "";
14
+
15
+ return (
16
+ <main
17
+ style={{
18
+ padding: "2rem",
19
+ fontFamily: "var(--font-geist-sans, sans-serif)",
20
+ maxWidth: "800px",
21
+ margin: "0 auto",
22
+ }}
23
+ >
24
+ <h1>{slug}</h1>
25
+ <p style={{ marginTop: "1rem" }}>Post slug: {slug}</p>
26
+ <p style={{ marginTop: "1.5rem" }}>
27
+ <Link href="/" style={{ textDecoration: "underline" }}>
28
+ Back to home
29
+ </Link>
30
+ </p>
31
+ </main>
32
+ );
33
+ }
@@ -0,0 +1,49 @@
1
+ :root {
2
+ --background: #ffffff;
3
+ --foreground: #171717;
4
+ }
5
+
6
+ @media (prefers-color-scheme: dark) {
7
+ :root {
8
+ --background: #0a0a0a;
9
+ --foreground: #ededed;
10
+ }
11
+ }
12
+
13
+ html {
14
+ height: 100%;
15
+ }
16
+
17
+ html,
18
+ body {
19
+ max-width: 100vw;
20
+ overflow-x: hidden;
21
+ }
22
+
23
+ body {
24
+ min-height: 100%;
25
+ display: flex;
26
+ flex-direction: column;
27
+ color: var(--foreground);
28
+ background: var(--background);
29
+ font-family: Arial, Helvetica, sans-serif;
30
+ -webkit-font-smoothing: antialiased;
31
+ -moz-osx-font-smoothing: grayscale;
32
+ }
33
+
34
+ * {
35
+ box-sizing: border-box;
36
+ padding: 0;
37
+ margin: 0;
38
+ }
39
+
40
+ a {
41
+ color: inherit;
42
+ text-decoration: none;
43
+ }
44
+
45
+ @media (prefers-color-scheme: dark) {
46
+ html {
47
+ color-scheme: dark;
48
+ }
49
+ }
@@ -0,0 +1,25 @@
1
+ import { Geist, Geist_Mono } from "next/font/google";
2
+ import "./globals.css";
3
+
4
+ const geistSans = Geist({
5
+ variable: "--font-geist-sans",
6
+ subsets: ["latin"],
7
+ });
8
+
9
+ const geistMono = Geist_Mono({
10
+ variable: "--font-geist-mono",
11
+ subsets: ["latin"],
12
+ });
13
+
14
+ export const metadata = {
15
+ title: "Create Next App",
16
+ description: "Generated by create next app",
17
+ };
18
+
19
+ export default function RootLayout({ children }) {
20
+ return (
21
+ <html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
22
+ <body>{children}</body>
23
+ </html>
24
+ );
25
+ }
@@ -0,0 +1,15 @@
1
+ export default function Loading() {
2
+ return (
3
+ <div
4
+ style={{
5
+ display: "flex",
6
+ alignItems: "center",
7
+ justifyContent: "center",
8
+ minHeight: "50vh",
9
+ fontFamily: "var(--font-geist-sans, sans-serif)",
10
+ }}
11
+ >
12
+ <p>Loading...</p>
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,29 @@
1
+ import Link from "next/link";
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div
6
+ style={{
7
+ display: "flex",
8
+ flexDirection: "column",
9
+ alignItems: "center",
10
+ justifyContent: "center",
11
+ minHeight: "100vh",
12
+ fontFamily: "var(--font-geist-sans, sans-serif)",
13
+ padding: "2rem",
14
+ textAlign: "center",
15
+ }}
16
+ >
17
+ <h1>404 - Page Not Found</h1>
18
+ <p style={{ marginTop: "1rem" }}>
19
+ The page you are looking for does not exist.
20
+ </p>
21
+ <Link
22
+ href="/"
23
+ style={{ marginTop: "1.5rem", textDecoration: "underline" }}
24
+ >
25
+ Go back home
26
+ </Link>
27
+ </div>
28
+ );
29
+ }