create-lumen 2.0.0-alpha.2 → 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.
Files changed (137) hide show
  1. package/README.md +24 -6
  2. package/bin/cli.js +2 -1
  3. package/package.json +2 -2
  4. package/src/cli-args.js +105 -5
  5. package/src/configure.js +8 -4
  6. package/src/dependencies.js +8 -2
  7. package/src/engine/capabilities.js +22 -1
  8. package/src/engine/composition.js +92 -0
  9. package/src/engine/providers/base-provider.js +4 -4
  10. package/src/main.js +33 -10
  11. package/src/manifest/emit.js +1 -1
  12. package/src/prompts.js +154 -23
  13. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  14. package/templates/bases/next/app-router/js/README.md +36 -0
  15. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  16. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  17. package/templates/bases/next/app-router/js/package.json +15 -0
  18. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  19. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  20. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  21. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  22. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  23. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  24. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  25. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  26. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  27. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  28. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  29. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  30. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  31. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  32. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  33. package/templates/bases/next/app-router/ts/README.md +36 -0
  34. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  35. package/templates/bases/next/app-router/ts/package.json +21 -0
  36. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  37. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  38. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  39. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  40. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  41. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  42. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  43. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  44. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  45. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  46. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  47. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  48. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  49. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  50. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  51. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  52. package/templates/bases/next/pages-router/js/README.md +40 -0
  53. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  54. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  55. package/templates/bases/next/pages-router/js/package.json +15 -0
  56. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  57. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  58. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  59. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  60. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  61. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  62. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  63. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  64. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  65. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  66. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  67. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  68. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  69. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  70. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  71. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  72. package/templates/bases/next/pages-router/ts/README.md +40 -0
  73. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  74. package/templates/bases/next/pages-router/ts/package.json +21 -0
  75. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  76. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  77. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  78. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  79. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  80. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  81. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  82. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  83. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  84. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  85. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  86. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  87. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  88. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  89. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  90. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  91. package/templates/conditional/linting/biome/biome.json +31 -0
  92. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  93. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  94. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  95. package/templates/conditional/next/config/node/next.config.ts +7 -0
  96. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  97. package/templates/conditional/next/config/static/next.config.ts +7 -0
  98. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  99. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  100. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  101. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  102. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  103. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  104. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  105. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  106. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  107. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  108. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  109. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  110. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  111. package/templates/conditional/next/font/src/app/layout.js +25 -0
  112. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  113. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  114. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  115. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  116. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  117. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  118. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  119. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  120. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  121. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  122. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  123. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  124. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  125. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  126. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  127. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  128. package/templates/conditional/next/server-components/README.md +13 -0
  129. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  130. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  131. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  132. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  133. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  134. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  135. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  136. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  137. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-lumen
2
2
 
3
- A scaffolder that generates production-ready React + Vite projects (feature-based, type-based, hybrid, or none).
3
+ A scaffolder that generates production-ready **React + Vite** or **Next.js** projects (App Router / Pages Router), with a choice of architecture: feature-based, type-based, hybrid, or none.
4
4
 
5
5
  Note: this repository is the CLI scaffolder itself, not a generated app. Generated projects live in the target folder you create.
6
6
 
@@ -14,6 +14,8 @@ cd my-app
14
14
  npm run dev
15
15
  ```
16
16
 
17
+ > The v2 line (React + Vite + Next.js, manifest v2) is available on the `beta` dist-tag while v2 is in prerelease: `npm create lumen@beta my-app`.
18
+
17
19
  ### Non-interactive flags
18
20
 
19
21
  Quick Setup (TypeScript + Tailwind + Feature-based + Router + ESLint + Prettier + Vitest), no prompts:
@@ -26,20 +28,28 @@ npm create lumen -- -y my-app
26
28
  npm create lumen my-app -- --yes
27
29
  ```
28
30
 
29
- Scaffold using a template preset (`react-ts`, `react-js`, `react-type-ts`, `react-type-js`):
31
+ Scaffold using a template preset (`react-ts`, `react-js`, `react-type-ts`, `react-type-js`, `next-ts`, `next-js`, `next-pages-ts`, `next-pages-js`):
30
32
 
31
33
  ```bash
32
34
  npm create lumen my-app -- --template react-ts
33
35
  # or
34
- npm create lumen my-app -- -t react-type-js
36
+ npm create lumen my-app -- -t next-ts
35
37
  ```
36
38
 
37
39
  Choose framework and architecture directly:
38
40
 
39
41
  ```bash
40
42
  npm create lumen my-app -- --framework react --arch none
43
+
44
+ # Next.js — the router is asked interactively (App Router recommended):
45
+ npm create lumen my-app -- -f next
46
+ # ...or pin the variant outright:
47
+ npm create lumen my-app -- -f next:app-router
48
+ npm create lumen my-app -- -f next:pages-router
41
49
  ```
42
50
 
51
+ Interactively, the framework is the first prompt (React + Vite / Next.js), followed by a router step when Next.js is selected; every later option gates on that choice.
52
+
43
53
  Drive scaffolding directly from a manifest file or inline JSON (manifest v2):
44
54
 
45
55
  ```bash
@@ -70,20 +80,25 @@ create-lumen my-app # after npm link
70
80
 
71
81
  ## Features
72
82
 
83
+ - **Two frameworks:** React + Vite (SPA) or Next.js (App Router / Pages Router) — selected interactively or via `-f`
73
84
  - Architecture choice: feature-based, type-based, hybrid, or none (per-framework availability)
74
85
  - TypeScript or JavaScript output
75
86
  - CSS frameworks: Tailwind CSS v4 (CSS-first `@theme`, v3 dropped) or none
87
+ - **UI kit: shadcn/ui** (gated to Tailwind; primitives + `cn` helper land in the mapped `paths.ui`)
76
88
  - Optional state management: Zustand or Redux Toolkit
77
- - Optional router (React Router)
78
- - Optional testing: Vitest or Jest
89
+ - Optional router (React Router — Vite only; Next.js uses filesystem routing)
90
+ - Optional testing: Vitest or Jest (Next.js defaults to none until a Next-native overlay is verified)
91
+ - **Next.js-native overlays:** server/client boundary demos + `no-server-imports-in-client` ESLint rule, `next/font` (Geist), `next/image`, filesystem routing examples, route handlers (`route.ts`) / API routes (`pages/api`)
92
+ - **Next.js options:** `bundler: turbopack|webpack`, `adapter: node|vercel|cloudflare|static` (`next.config` overlays), `reactCompiler`, `linter: biome` — all validated against the capability model
79
93
  - Linting: ESLint (default), Oxlint, or Biome
80
94
  - Code formatter: Prettier or Oxfmt (chosen after the linter; `eslint-config-prettier` is auto-wired for the ESLint + Prettier combo)
81
- - Manifest v2: Emits a deterministic `lumen.config.json` (optional but recommended) validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration.
95
+ - Manifest v2: Emits a deterministic `lumen.config.json` validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration.
82
96
  - Explicit path mapping: Target directories declared for `lumen-cli` code generation
83
97
  - Optional Axios setup and icon libraries
84
98
  - Auto `git init`, generated `README` (with a project description reflecting the chosen tooling) and `LICENSE`
85
99
  - `.env.example` scaffolded, and the generated `.gitignore` excludes env files (`.env`, `.env.*`) while keeping `.env.example`
86
100
  - `@/` path alias configured (`tsconfig`/`jsconfig` + Vite)
101
+ - **Agent docs:** optional `.lumen/` context directory (project, architecture, conventions) via prompt or `--agent-docs`
87
102
 
88
103
  Your selected options are cached and offered as defaults on subsequent runs.
89
104
 
@@ -206,6 +221,9 @@ The manifest now includes:
206
221
  ### Ecosystem Documentation & Contracts
207
222
 
208
223
  - **[Documentation Index](./docs/README.md):** Complete overview of specifications, ADRs, and developer guides.
224
+ - **[Next.js App Router Guide](./docs/next-app-router.md):** App Router scaffold, filesystem routing, route handlers, server/client boundaries, `next/font` and `next/image`.
225
+ - **[Next.js Pages Router Guide](./docs/next-pages-router.md):** Pages Router scaffold, API routes, `_app`/`_document` wiring.
226
+ - **[Deployment Adapters Guide](./docs/next-adapters.md):** Adapter matrix (Node, Vercel, Cloudflare, static), bundler choice and Biome setup for Next.js.
209
227
  - **[Generator Engine & Capabilities Guide (M2)](./docs/engine-capabilities.md):** Capability model, template composition, shadcn/ui on Tailwind v4, architecture presets, and Bootstrap removal.
210
228
  - **[Manifest v2 Overview](./docs/manifest-v2.md):** Specification of the nested schema, validation behavior, and the new harness and architecture validation fields.
211
229
  - **[Shared Contract: `create-lumen` ↔ `lumen-cli`](./docs/contracts/manifest-v2-contract.md):** The agreed contract for path mapping, barrel conventions, version negotiation, and the harness/configuration interface.
package/bin/cli.js CHANGED
@@ -25,7 +25,7 @@ console.log(
25
25
  chalk.bold.cyan("\n ✦ LUMEN") +
26
26
  chalk.gray(` v${version}`) +
27
27
  chalk.gray(
28
- "\n Scaffold a React + Vite project with architecture choice\n"
28
+ "\n Scaffold a React, Vite, or Next.js project with architecture choice\n"
29
29
  )
30
30
  );
31
31
 
@@ -36,6 +36,7 @@ main({
36
36
  projectName: cliArgs.projectName,
37
37
  manifest: cliArgs.manifest,
38
38
  template: cliArgs.template,
39
+ framework: cliArgs.framework,
39
40
  arch: cliArgs.arch,
40
41
  }).catch((e) => {
41
42
  console.error(chalk.red("\nError:"), e.message || e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lumen",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/LUMEN-SH/create-lumen"
11
+ "url": "https://github.com/strophix-software/create-lumen"
12
12
  },
13
13
  "files": [
14
14
  "bin/",
package/src/cli-args.js CHANGED
@@ -6,7 +6,6 @@ import { parseManifest } from "./manifest/schema.js";
6
6
  import {
7
7
  normalizeFramework,
8
8
  isPromptVisible,
9
- getDefaultResponses,
10
9
  } from "./engine/capabilities.js";
11
10
 
12
11
  export const PRESETS = {
@@ -180,6 +179,78 @@ export const PRESETS = {
180
179
  gitInit: true,
181
180
  readme: true,
182
181
  },
182
+ "next-ts": {
183
+ frameworkName: "next",
184
+ frameworkVariant: "app-router",
185
+ architecture: "feature-based",
186
+ language: "ts",
187
+ cssFramework: "tailwind",
188
+ testing: "vitest",
189
+ router: false,
190
+ bundler: "turbopack",
191
+ stateManagement: "none",
192
+ iconLibrary: "none",
193
+ apiClient: "none",
194
+ linter: "eslint",
195
+ formatter: "prettier",
196
+ docsLanguage: "en",
197
+ gitInit: true,
198
+ readme: true,
199
+ },
200
+ "next-js": {
201
+ frameworkName: "next",
202
+ frameworkVariant: "app-router",
203
+ architecture: "feature-based",
204
+ language: "js",
205
+ cssFramework: "tailwind",
206
+ testing: "vitest",
207
+ router: false,
208
+ bundler: "turbopack",
209
+ stateManagement: "none",
210
+ iconLibrary: "none",
211
+ apiClient: "none",
212
+ linter: "eslint",
213
+ formatter: "prettier",
214
+ docsLanguage: "en",
215
+ gitInit: true,
216
+ readme: true,
217
+ },
218
+ "next-pages-ts": {
219
+ frameworkName: "next",
220
+ frameworkVariant: "pages-router",
221
+ architecture: "feature-based",
222
+ language: "ts",
223
+ cssFramework: "tailwind",
224
+ testing: "vitest",
225
+ router: false,
226
+ bundler: "turbopack",
227
+ stateManagement: "none",
228
+ iconLibrary: "none",
229
+ apiClient: "none",
230
+ linter: "eslint",
231
+ formatter: "prettier",
232
+ docsLanguage: "en",
233
+ gitInit: true,
234
+ readme: true,
235
+ },
236
+ "next-pages-js": {
237
+ frameworkName: "next",
238
+ frameworkVariant: "pages-router",
239
+ architecture: "feature-based",
240
+ language: "js",
241
+ cssFramework: "tailwind",
242
+ testing: "vitest",
243
+ router: false,
244
+ bundler: "turbopack",
245
+ stateManagement: "none",
246
+ iconLibrary: "none",
247
+ apiClient: "none",
248
+ linter: "eslint",
249
+ formatter: "prettier",
250
+ docsLanguage: "en",
251
+ gitInit: true,
252
+ readme: true,
253
+ },
183
254
  };
184
255
 
185
256
  /**
@@ -207,6 +278,13 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
207
278
  framework: { type: "string", short: "f" },
208
279
  arch: { type: "string", short: "a" },
209
280
  architecture: { type: "string" },
281
+ bundler: { type: "string", short: "b" },
282
+ adapter: { type: "string" },
283
+ "react-compiler": { type: "boolean" },
284
+ reactCompiler: { type: "boolean" },
285
+ "agent-docs": { type: "boolean" },
286
+ agentDocs: { type: "boolean" },
287
+ linter: { type: "string" },
210
288
  help: { type: "boolean", short: "h", default: false },
211
289
  version: { type: "boolean", short: "v", default: false },
212
290
  },
@@ -223,6 +301,12 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
223
301
  else if (arch === "component" || arch === "component-based" || arch === "type") arch = "type-based";
224
302
  }
225
303
 
304
+ const bundler = values.bundler ? values.bundler.trim().toLowerCase() : null;
305
+ const adapter = values.adapter ? values.adapter.trim().toLowerCase() : null;
306
+ const reactCompiler = values["react-compiler"] ?? values.reactCompiler ?? null;
307
+ const agentDocs = values["agent-docs"] ?? values.agentDocs ?? null;
308
+ const linter = values.linter ? values.linter.trim().toLowerCase() : null;
309
+
226
310
  return {
227
311
  projectName,
228
312
  quickSetup: Boolean(values.yes),
@@ -230,6 +314,11 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
230
314
  template: values.template || null,
231
315
  framework: values.framework ? values.framework.trim() : null,
232
316
  arch,
317
+ bundler,
318
+ adapter,
319
+ reactCompiler,
320
+ agentDocs,
321
+ linter,
233
322
  help: Boolean(values.help),
234
323
  version: Boolean(values.version),
235
324
  positionals,
@@ -278,6 +367,8 @@ export function manifestToResponses(manifest, projectName) {
278
367
  const framework = normalizeFramework({ name: frameworkName, variant: frameworkVariant });
279
368
  const canClientRoute = isPromptVisible(framework, "router");
280
369
  const supportsBundler = isPromptVisible(framework, "bundler");
370
+ const supportsAdapter = isPromptVisible(framework, "adapter");
371
+ const supportsCompiler = isPromptVisible(framework, "reactCompiler");
281
372
 
282
373
  return {
283
374
  projectName,
@@ -287,7 +378,10 @@ export function manifestToResponses(manifest, projectName) {
287
378
  supportsBundler && manifest.framework?.bundler
288
379
  ? manifest.framework.bundler
289
380
  : undefined,
290
- adapter: manifest.framework?.adapter,
381
+ adapter:
382
+ supportsAdapter && manifest.framework?.adapter
383
+ ? manifest.framework.adapter
384
+ : undefined,
291
385
  architecture: manifest.architecture?.type || "feature-based",
292
386
  language: manifest.tooling?.language || "ts",
293
387
  cssFramework: manifest.styling?.engine || "tailwind",
@@ -295,10 +389,12 @@ export function manifestToResponses(manifest, projectName) {
295
389
  formatter: manifest.tooling?.formatter || "prettier",
296
390
  uiKit: manifest.ui?.kit || "none",
297
391
  docsLanguage: manifest.docs?.language || "en",
298
- reactCompiler: manifest.reactCompiler,
392
+ reactCompiler: supportsCompiler ? manifest.reactCompiler : undefined,
299
393
  agentDocs: manifest.agentDocs,
300
- // Sensible defaults for scaffolder conditional passes
301
- testing: "vitest",
394
+ // Sensible defaults for scaffolder conditional passes (Next.js ships no
395
+ // verified test-runner overlay yet, so it defaults to none; explicit
396
+ // vitest/jest selection still applies the standard overlays)
397
+ testing: frameworkName === "next" ? "none" : "vitest",
302
398
  router: canClientRoute,
303
399
  stateManagement: "none",
304
400
  iconLibrary: "none",
@@ -335,6 +431,10 @@ ${chalk.bold("PRESETS")}
335
431
  ${chalk.cyan("react-type-js")} React + Vite + JavaScript (type-based)
336
432
  ${chalk.cyan("react-none-ts")} React + Vite + TypeScript (none)
337
433
  ${chalk.cyan("react-none-js")} React + Vite + JavaScript (none)
434
+ ${chalk.cyan("next-ts")} Next.js App Router + TypeScript (feature-based)
435
+ ${chalk.cyan("next-js")} Next.js App Router + JavaScript (feature-based)
436
+ ${chalk.cyan("next-pages-ts")} Next.js Pages Router + TypeScript (feature-based)
437
+ ${chalk.cyan("next-pages-js")} Next.js Pages Router + JavaScript (feature-based)
338
438
 
339
439
  ${chalk.bold("EXAMPLES")}
340
440
  ${chalk.gray("# Interactive setup")}
package/src/configure.js CHANGED
@@ -166,12 +166,16 @@ async function configurePackageJson(projectPath, language) {
166
166
 
167
167
  if (!pkg.scripts) pkg.scripts = {};
168
168
 
169
- pkg.scripts.dev = "vite";
170
- pkg.scripts.build = "vite build";
171
- pkg.scripts.preview = "vite preview";
169
+ // Fill defaults only — never overwrite scripts owned by the base template
170
+ // (Next.js bases ship next dev/build/start) or merged overlay fragments
171
+ // (e.g. the bundler-aware dev script). React/Vite bases already carry the
172
+ // vite commands, so this is a no-op there too.
173
+ pkg.scripts.dev ??= "vite";
174
+ pkg.scripts.build ??= "vite build";
175
+ pkg.scripts.preview ??= "vite preview";
172
176
 
173
177
  if (language === "ts") {
174
- pkg.scripts.typecheck = "tsc -b";
178
+ pkg.scripts.typecheck ??= "tsc -b";
175
179
  }
176
180
 
177
181
  await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
@@ -40,6 +40,10 @@ export async function installAllDeps(responses, pkg, cwd) {
40
40
  // the conditional wiring stays verifiable without a network install.
41
41
  export function computeDeps(responses) {
42
42
  const deps = [];
43
+ const fwName =
44
+ typeof responses.framework === "string"
45
+ ? responses.framework
46
+ : (responses.framework?.name ?? responses.frameworkName ?? "react");
43
47
  const cssDevDeps = [];
44
48
  const testingDevDeps = [];
45
49
  const linterDevDeps = [];
@@ -47,7 +51,7 @@ export function computeDeps(responses) {
47
51
 
48
52
  // CSS framework
49
53
  if (responses.cssFramework === "tailwind") {
50
- if (responses.framework === "next") {
54
+ if (fwName === "next") {
51
55
  cssDevDeps.push("tailwindcss", "@tailwindcss/postcss", "postcss");
52
56
  } else {
53
57
  cssDevDeps.push("tailwindcss", "@tailwindcss/vite");
@@ -103,7 +107,7 @@ export function computeDeps(responses) {
103
107
 
104
108
  // Linter
105
109
  if (responses.linter === "eslint") {
106
- if (responses.framework === "next") {
110
+ if (fwName === "next") {
107
111
  linterDevDeps.push("eslint", "eslint-config-next");
108
112
  } else {
109
113
  linterDevDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
@@ -113,6 +117,8 @@ export function computeDeps(responses) {
113
117
  }
114
118
  } else if (responses.linter === "oxlint") {
115
119
  linterDevDeps.push("oxlint");
120
+ } else if (responses.linter === "biome") {
121
+ linterDevDeps.push("@biomejs/biome");
116
122
  }
117
123
 
118
124
  // Formatter
@@ -153,6 +153,9 @@ export function isOptionCompatible(framework, optionKey, optionValue) {
153
153
  case "bundler":
154
154
  return desc.allowedBundlers.includes(optionValue);
155
155
  case "adapter":
156
+ if (optionValue === "none") {
157
+ return desc.allowedAdapters.length > 0;
158
+ }
156
159
  return desc.allowedAdapters.includes(optionValue);
157
160
  case "router":
158
161
  // If framework has filesystem routing, external router options (true) are incompatible
@@ -160,6 +163,11 @@ export function isOptionCompatible(framework, optionKey, optionValue) {
160
163
  return optionValue === false || optionValue === "none";
161
164
  }
162
165
  return true;
166
+ case "reactCompiler":
167
+ if (optionValue === true) {
168
+ return hasCapability(framework, CAPABILITIES.REACT_COMPILER);
169
+ }
170
+ return true;
163
171
  default:
164
172
  return true;
165
173
  }
@@ -318,10 +326,11 @@ export function getDefaultResponses(framework, { projectName = "my-app" } = {})
318
326
  architecture: "feature-based",
319
327
  language: "ts",
320
328
  cssFramework: "tailwind",
321
- testing: "vitest",
329
+ testing: "none",
322
330
  router: false,
323
331
  bundler: "turbopack",
324
332
  adapter: "none",
333
+ uiKit: "none",
325
334
  stateManagement: "none",
326
335
  iconLibrary: "none",
327
336
  apiClient: "none",
@@ -342,6 +351,7 @@ export function getDefaultResponses(framework, { projectName = "my-app" } = {})
342
351
  cssFramework: "tailwind",
343
352
  testing: "vitest",
344
353
  router: true,
354
+ uiKit: "none",
345
355
  stateManagement: "none",
346
356
  iconLibrary: "none",
347
357
  apiClient: "none",
@@ -369,6 +379,17 @@ export function sanitizeResponsesForFramework(framework, responses = {}) {
369
379
  if (!isPromptVisible(fw, "router")) {
370
380
  sanitized.router = false;
371
381
  }
382
+ if (!isPromptVisible(fw, "bundler")) {
383
+ delete sanitized.bundler;
384
+ }
385
+ if (!isPromptVisible(fw, "adapter")) {
386
+ delete sanitized.adapter;
387
+ }
388
+ if (!isPromptVisible(fw, "reactCompiler")) {
389
+ if (sanitized.reactCompiler !== undefined) {
390
+ sanitized.reactCompiler = false;
391
+ }
392
+ }
372
393
 
373
394
  for (const [key, val] of Object.entries(sanitized)) {
374
395
  if (val === undefined || val === null) continue;
@@ -44,6 +44,7 @@ export const TOOLING_ORDER = [
44
44
  "testing",
45
45
  "linter",
46
46
  "feature-script",
47
+ "next-config",
47
48
  "formatter",
48
49
  ];
49
50
 
@@ -149,6 +150,15 @@ export function resolveFragments(responses = {}, templatesDir) {
149
150
  try { await fsp.rm(path.join(projectPath, "src", "App.jsx"), { force: true }); } catch {}
150
151
  try { await fsp.rm(path.join(projectPath, "src", "index.css"), { force: true }); } catch {}
151
152
  try { await fsp.rm(path.join(projectPath, "src", "App.css"), { force: true }); } catch {}
153
+ if (fw.name === "next") {
154
+ // Next cleanup: remove Vite defaults without touching app/
155
+ try { await fsp.rm(path.join(projectPath, "src", "main.tsx"), { force: true }); } catch {}
156
+ try { await fsp.rm(path.join(projectPath, "src", "main.jsx"), { force: true }); } catch {}
157
+ try { await fsp.rm(path.join(projectPath, "index.html"), { force: true }); } catch {}
158
+ try { await fsp.rm(path.join(projectPath, "vite.config.ts"), { force: true }); } catch {}
159
+ try { await fsp.rm(path.join(projectPath, "vite.config.js"), { force: true }); } catch {}
160
+ try { await fsp.rm(path.join(projectPath, "public", "vite.svg"), { force: true }); } catch {}
161
+ }
152
162
  },
153
163
  });
154
164
 
@@ -406,6 +416,23 @@ export function resolveFragments(responses = {}, templatesDir) {
406
416
  } catch {}
407
417
  },
408
418
  });
419
+ } else if (responses.linter === "biome") {
420
+ fragments.push({
421
+ scope: FRAGMENT_SCOPES.TOOLING,
422
+ category: "linter",
423
+ id: "tooling:linter:biome",
424
+ scripts: {
425
+ lint: "biome check .",
426
+ "lint:fix": "biome check --write .",
427
+ },
428
+ action: async ({ projectPath }) => {
429
+ const configSrc = path.join(templatesDir, "conditional", "linting", "biome", "biome.json");
430
+ try {
431
+ await fsp.access(configSrc);
432
+ await fsp.copyFile(configSrc, path.join(projectPath, "biome.json"));
433
+ } catch {}
434
+ },
435
+ });
409
436
  }
410
437
 
411
438
  // 5.7 Feature Script (Feature-based only)
@@ -437,6 +464,71 @@ export function resolveFragments(responses = {}, templatesDir) {
437
464
  });
438
465
  }
439
466
 
467
+ // 5.75 Next.js Configuration & Adapter Overlays
468
+ if (fw.name === "next") {
469
+ const nextScripts = {};
470
+ if (responses.bundler === "turbopack") {
471
+ nextScripts.dev = "next dev --turbopack";
472
+ } else if (responses.bundler === "webpack") {
473
+ nextScripts.dev = "next dev --webpack";
474
+ }
475
+
476
+ fragments.push({
477
+ scope: FRAGMENT_SCOPES.TOOLING,
478
+ category: "next-config",
479
+ id: "tooling:next:config",
480
+ scripts: nextScripts,
481
+ action: async ({ projectPath }) => {
482
+ const configExt = language === "ts" ? "ts" : "mjs";
483
+
484
+ // Apply adapter overlay if specified and not "none"
485
+ if (responses.adapter && responses.adapter !== "none") {
486
+ const adapterConfigSrc = path.join(
487
+ templatesDir,
488
+ "conditional",
489
+ "next",
490
+ "config",
491
+ responses.adapter,
492
+ `next.config.${configExt}`
493
+ );
494
+ try {
495
+ await fsp.access(adapterConfigSrc);
496
+ await fsp.copyFile(adapterConfigSrc, path.join(projectPath, `next.config.${configExt}`));
497
+ } catch {}
498
+ } else if (responses.bundler === "turbopack" || responses.bundler === "webpack") {
499
+ // If no adapter specified, but bundler config overlay exists, apply bundler overlay
500
+ const bundlerConfigSrc = path.join(
501
+ templatesDir,
502
+ "conditional",
503
+ "next",
504
+ "config",
505
+ responses.bundler,
506
+ `next.config.${configExt}`
507
+ );
508
+ try {
509
+ await fsp.access(bundlerConfigSrc);
510
+ await fsp.copyFile(bundlerConfigSrc, path.join(projectPath, `next.config.${configExt}`));
511
+ } catch {}
512
+ }
513
+
514
+ // Apply reactCompiler flag to next.config if enabled
515
+ if (responses.reactCompiler === true) {
516
+ const targetConfigPath = path.join(projectPath, `next.config.${configExt}`);
517
+ try {
518
+ let content = await fsp.readFile(targetConfigPath, "utf8");
519
+ if (!content.includes("reactCompiler")) {
520
+ content = content.replace(
521
+ /const nextConfig(?::\s*NextConfig)?\s*=\s*\{/,
522
+ (match) => `${match}\n reactCompiler: true,`
523
+ );
524
+ await fsp.writeFile(targetConfigPath, content, "utf8");
525
+ }
526
+ } catch {}
527
+ }
528
+ },
529
+ });
530
+ }
531
+
440
532
  // 5.8 Formatter
441
533
  if (responses.formatter && responses.formatter !== "none") {
442
534
  const isPrettier = responses.formatter === "prettier";
@@ -125,7 +125,7 @@ export class NextAppRouterProvider extends BaseProvider {
125
125
  getPackageSet({ options = {} } = {}) {
126
126
  const isTs = options.language === "ts";
127
127
  const deps = {
128
- next: "^15.0.0",
128
+ next: "^16.0.0",
129
129
  react: "^19.0.0",
130
130
  "react-dom": "^19.0.0",
131
131
  };
@@ -134,7 +134,7 @@ export class NextAppRouterProvider extends BaseProvider {
134
134
  devDeps.typescript = "^5.7.0";
135
135
  devDeps["@types/react"] = "^19.0.0";
136
136
  devDeps["@types/react-dom"] = "^19.0.0";
137
- devDeps["@types/node"] = "^20.0.0";
137
+ devDeps["@types/node"] = "^22.0.0";
138
138
  }
139
139
  return {
140
140
  dependencies: deps,
@@ -159,7 +159,7 @@ export class NextPagesRouterProvider extends BaseProvider {
159
159
  getPackageSet({ options = {} } = {}) {
160
160
  const isTs = options.language === "ts";
161
161
  const deps = {
162
- next: "^15.0.0",
162
+ next: "^16.0.0",
163
163
  react: "^19.0.0",
164
164
  "react-dom": "^19.0.0",
165
165
  };
@@ -168,7 +168,7 @@ export class NextPagesRouterProvider extends BaseProvider {
168
168
  devDeps.typescript = "^5.7.0";
169
169
  devDeps["@types/react"] = "^19.0.0";
170
170
  devDeps["@types/react-dom"] = "^19.0.0";
171
- devDeps["@types/node"] = "^20.0.0";
171
+ devDeps["@types/node"] = "^22.0.0";
172
172
  }
173
173
  return {
174
174
  dependencies: deps,
package/src/main.js CHANGED
@@ -12,7 +12,7 @@ import { injectArchitecture, injectConditionals, injectFormatter } from "@/injec
12
12
  import { getPkgManager } from "@/utils/pkg-manager.js";
13
13
  import { handleDevServer } from "@/utils/dev-server.js";
14
14
  import { getUserInputs, onCancel } from "@/prompts.js";
15
- import { runBaseInstall, runGitInit, runViteCreate } from "@/scaffold.js";
15
+ import { runBaseInstall, runGitInit, runViteCreate, scaffoldBase } from "@/scaffold.js";
16
16
  import { installAllDeps } from "@/dependencies.js";
17
17
  import { generateReadme } from "@/readme.js";
18
18
  import { runProjectFormat } from "@/format.js";
@@ -20,6 +20,8 @@ import { emitManifest } from "@/manifest/emit.js";
20
20
  import { emitAgentContext } from "@/agent-context.js";
21
21
 
22
22
  import { parseCliArgs, loadManifestSource, manifestToResponses, PRESETS } from "@/cli-args.js";
23
+ import { getProvider } from "@/engine/providers/base-provider.js";
24
+ import { normalizeFramework } from "@/engine/capabilities.js";
23
25
 
24
26
  const __filename = fileURLToPath(import.meta.url);
25
27
  const __dirname = path.dirname(__filename);
@@ -48,6 +50,7 @@ export async function main(options = {}) {
48
50
  projectName: nameArg,
49
51
  manifest: manifestSource,
50
52
  template: templateName,
53
+ framework: frameworkArg,
51
54
  arch: archArg,
52
55
  } = options;
53
56
  const pkg = getPkgManager();
@@ -94,20 +97,40 @@ export async function main(options = {}) {
94
97
  ...preset,
95
98
  };
96
99
  } else {
97
- responses = await getUserInputs(projectName, { quickSetup, initialArch: archArg });
100
+ responses = await getUserInputs(projectName, { quickSetup, initialArch: archArg, framework: frameworkArg });
98
101
  }
99
102
 
100
103
  const projectPath = path.resolve(CURRENT_DIR, projectName);
101
104
 
102
- // 4. Scaffold via vite create
105
+ // 4. Scaffold the base project (provider-owned bases; Vite shells out, Next copies)
106
+ const fw = normalizeFramework(
107
+ responses.framework || { name: responses.frameworkName, variant: responses.frameworkVariant }
108
+ );
103
109
  const createSpin = spinner();
104
- createSpin.start("Creating your Vite + React project...");
105
- try {
106
- await runViteCreate(projectPath, projectName, pkg, responses.language);
107
- createSpin.stop(chalk.green("Project created successfully!"));
108
- } catch (err) {
109
- createSpin.stop(chalk.red("Failed to create project."));
110
- throw err;
110
+ if (fw.name === "next") {
111
+ createSpin.start("Creating your Next.js project...");
112
+ try {
113
+ const provider = getProvider(fw.name, fw.variant);
114
+ const based = await scaffoldBase(projectPath, provider, responses.language, TEMPLATES_DIR);
115
+ if (!based) {
116
+ throw new Error(
117
+ `Next.js base template not found for variant "${fw.variant}" (${responses.language}).`
118
+ );
119
+ }
120
+ createSpin.stop(chalk.green("Project created successfully!"));
121
+ } catch (err) {
122
+ createSpin.stop(chalk.red("Failed to create project."));
123
+ throw err;
124
+ }
125
+ } else {
126
+ createSpin.start("Creating your Vite + React project...");
127
+ try {
128
+ await runViteCreate(projectPath, projectName, pkg, responses.language);
129
+ createSpin.stop(chalk.green("Project created successfully!"));
130
+ } catch (err) {
131
+ createSpin.stop(chalk.red("Failed to create project."));
132
+ throw err;
133
+ }
111
134
  }
112
135
 
113
136
  // 5. Install base dependencies
@@ -64,7 +64,7 @@ export function buildManifest(responses, opts = {}) {
64
64
  };
65
65
  // Include optional axes only if present in responses (future overlays)
66
66
  if (responses.bundler) framework.bundler = responses.bundler;
67
- if (responses.adapter) framework.adapter = responses.adapter;
67
+ if (responses.adapter && responses.adapter !== "none") framework.adapter = responses.adapter;
68
68
 
69
69
  const styling = {
70
70
  engine: responses.cssFramework || "tailwind",