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.
Files changed (190) hide show
  1. package/README.md +38 -12
  2. package/bin/cli.js +3 -1
  3. package/package.json +3 -3
  4. package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +1 -1
  5. package/schema/fixtures/v2/valid/react-vite-type-based.json +1 -1
  6. package/schema/lumen.config.v2.json +0 -1
  7. package/src/cli-args.js +169 -7
  8. package/src/configure.js +12 -4
  9. package/src/css.js +14 -30
  10. package/src/dependencies.js +36 -20
  11. package/src/engine/capabilities.js +195 -4
  12. package/src/engine/composition.js +736 -0
  13. package/src/engine/index.js +1 -0
  14. package/src/engine/providers/base-provider.js +4 -4
  15. package/src/injector.js +294 -446
  16. package/src/main.js +38 -16
  17. package/src/manifest/emit.js +1 -1
  18. package/src/manifest/schema.js +1 -1
  19. package/src/prompts.js +581 -238
  20. package/src/readme.js +23 -2
  21. package/src/scaffold.js +17 -0
  22. package/templates/architectures/hybrid/main.jsx +10 -0
  23. package/templates/architectures/hybrid/main.tsx +10 -0
  24. package/templates/architectures/hybrid/src/App.jsx +20 -0
  25. package/templates/architectures/hybrid/src/App.tsx +20 -0
  26. package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +31 -0
  27. package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +36 -0
  28. package/templates/architectures/hybrid/src/shared/components/ui/index.js +1 -0
  29. package/templates/architectures/hybrid/src/shared/components/ui/index.ts +1 -0
  30. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +28 -0
  31. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +28 -0
  32. package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
  33. package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
  34. package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
  35. package/templates/architectures/hybrid/src/shared/types/index.js +20 -0
  36. package/templates/architectures/hybrid/src/shared/types/index.ts +16 -0
  37. package/templates/architectures/hybrid/src/shared/utils/cn.jsx +3 -0
  38. package/templates/architectures/hybrid/src/shared/utils/cn.ts +5 -0
  39. package/templates/architectures/none/main.jsx +10 -0
  40. package/templates/architectures/none/main.tsx +10 -0
  41. package/templates/architectures/none/src/App.jsx +15 -0
  42. package/templates/architectures/none/src/App.tsx +15 -0
  43. package/templates/architectures/none/src/styles/.gitkeep +1 -0
  44. package/templates/architectures/none/src/types/index.js +20 -0
  45. package/templates/architectures/none/src/types/index.ts +16 -0
  46. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  47. package/templates/bases/next/app-router/js/README.md +36 -0
  48. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  49. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  50. package/templates/bases/next/app-router/js/package.json +15 -0
  51. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  52. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  53. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  54. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  55. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  56. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  57. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  58. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  59. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  60. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  61. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  62. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  63. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  64. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  65. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  66. package/templates/bases/next/app-router/ts/README.md +36 -0
  67. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  68. package/templates/bases/next/app-router/ts/package.json +21 -0
  69. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  70. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  71. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  72. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  73. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  74. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  75. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  76. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  77. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  78. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  79. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  80. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  81. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  82. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  83. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  84. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  85. package/templates/bases/next/pages-router/js/README.md +40 -0
  86. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  87. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  88. package/templates/bases/next/pages-router/js/package.json +15 -0
  89. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  90. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  91. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  92. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  93. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  94. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  95. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  96. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  97. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  98. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  99. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  100. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  101. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  102. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  103. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  104. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  105. package/templates/bases/next/pages-router/ts/README.md +40 -0
  106. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  107. package/templates/bases/next/pages-router/ts/package.json +21 -0
  108. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  109. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  110. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  111. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  112. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  113. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  114. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  115. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  116. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  117. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  118. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  119. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  120. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  121. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  122. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  123. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  124. package/templates/conditional/linting/biome/biome.json +31 -0
  125. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  126. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  127. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  128. package/templates/conditional/next/config/node/next.config.ts +7 -0
  129. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  130. package/templates/conditional/next/config/static/next.config.ts +7 -0
  131. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  132. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  133. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  134. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  135. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  136. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  137. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  138. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  139. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  140. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  141. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  142. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  143. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  144. package/templates/conditional/next/font/src/app/layout.js +25 -0
  145. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  146. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  147. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  148. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  149. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  150. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  151. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  152. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  153. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  154. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  155. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  156. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  157. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  158. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  159. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  160. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  161. package/templates/conditional/next/server-components/README.md +13 -0
  162. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  163. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  164. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  165. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  166. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  167. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  168. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  169. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  170. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
  171. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +11 -0
  172. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +11 -0
  173. package/templates/conditional/testing/jest/none/src/__tests__/App.test.jsx +11 -0
  174. package/templates/conditional/testing/jest/none/src/__tests__/App.test.tsx +11 -0
  175. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +10 -0
  176. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +10 -0
  177. package/templates/conditional/testing/vitest/none/src/__tests__/App.test.jsx +10 -0
  178. package/templates/conditional/testing/vitest/none/src/__tests__/App.test.tsx +10 -0
  179. package/templates/css/tailwind/src/globals.css +10 -0
  180. package/templates/css/tailwind/src/themes.css +86 -4
  181. package/templates/ui/shadcn/lib/utils.js +6 -0
  182. package/templates/ui/shadcn/lib/utils.ts +6 -0
  183. package/templates/ui/shadcn/primitives/Button.jsx +48 -0
  184. package/templates/ui/shadcn/primitives/Button.tsx +59 -0
  185. package/templates/ui/shadcn/primitives/Card.jsx +55 -0
  186. package/templates/ui/shadcn/primitives/Card.tsx +73 -0
  187. package/templates/ui/shadcn/primitives/index.js +9 -0
  188. package/templates/ui/shadcn/primitives/index.ts +9 -0
  189. package/templates/css/bootstrap/src/themes.css +0 -14
  190. /package/templates/{css/bootstrap/src/globals.css → architectures/hybrid/src/features/.gitkeep} +0 -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 or type-based).
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,14 +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
 
39
+ Choose framework and architecture directly:
40
+
41
+ ```bash
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
49
+ ```
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
+
37
53
  Drive scaffolding directly from a manifest file or inline JSON (manifest v2):
38
54
 
39
55
  ```bash
@@ -64,20 +80,25 @@ create-lumen my-app # after npm link
64
80
 
65
81
  ## Features
66
82
 
67
- - Architecture choice: feature-based or type-based (with Next.js hybrid/none in v2)
83
+ - **Two frameworks:** React + Vite (SPA) or Next.js (App Router / Pages Router) — selected interactively or via `-f`
84
+ - Architecture choice: feature-based, type-based, hybrid, or none (per-framework availability)
68
85
  - TypeScript or JavaScript output
69
- - CSS frameworks: Tailwind CSS v4 (CSS-first `@theme`, v3 dropped), Bootstrap 5.3+, or none
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`)
70
88
  - Optional state management: Zustand or Redux Toolkit
71
- - Optional router (React Router)
72
- - 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
73
93
  - Linting: ESLint (default), Oxlint, or Biome
74
94
  - Code formatter: Prettier or Oxfmt (chosen after the linter; `eslint-config-prettier` is auto-wired for the ESLint + Prettier combo)
75
- - 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.
76
96
  - Explicit path mapping: Target directories declared for `lumen-cli` code generation
77
97
  - Optional Axios setup and icon libraries
78
98
  - Auto `git init`, generated `README` (with a project description reflecting the chosen tooling) and `LICENSE`
79
99
  - `.env.example` scaffolded, and the generated `.gitignore` excludes env files (`.env`, `.env.*`) while keeping `.env.example`
80
100
  - `@/` path alias configured (`tsconfig`/`jsconfig` + Vite)
101
+ - **Agent docs:** optional `.lumen/` context directory (project, architecture, conventions) via prompt or `--agent-docs`
81
102
 
82
103
  Your selected options are cached and offered as defaults on subsequent runs.
83
104
 
@@ -148,8 +169,8 @@ src/
148
169
  ├── layouts
149
170
  ├── stores
150
171
  ├── styles
151
- │   ├── globals.css # Tailwind/Bootstrap directives (or main.css for vanilla)
152
- │   └── themes.css # Theme tokens (Tailwind @theme / Bootstrap data-bs-theme / CSS vars)
172
+ │   ├── globals.css # Tailwind directives (or main.css for vanilla)
173
+ │   └── themes.css # Theme tokens (Tailwind @theme / CSS vars)
153
174
  ├── types
154
175
  └── utils
155
176
  ```
@@ -177,8 +198,8 @@ src/
177
198
  ├── services
178
199
  ├── store
179
200
  ├── styles
180
- │   ├── globals.css # Tailwind/Bootstrap directives (or main.css for vanilla)
181
- │   └── themes.css # Theme tokens (Tailwind @theme / Bootstrap data-bs-theme / CSS vars)
201
+ │   ├── globals.css # Tailwind directives (or main.css for vanilla)
202
+ │   └── themes.css # Theme tokens (Tailwind @theme / CSS vars)
182
203
  ├── test
183
204
  └── utils
184
205
  ```
@@ -199,6 +220,11 @@ The manifest now includes:
199
220
 
200
221
  ### Ecosystem Documentation & Contracts
201
222
 
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.
227
+ - **[Generator Engine & Capabilities Guide (M2)](./docs/engine-capabilities.md):** Capability model, template composition, shadcn/ui on Tailwind v4, architecture presets, and Bootstrap removal.
202
228
  - **[Manifest v2 Overview](./docs/manifest-v2.md):** Specification of the nested schema, validation behavior, and the new harness and architecture validation fields.
203
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.
204
230
  - **[v1 to v2 Migration Guide](./docs/migration/v1-to-v2.md):** Complete guide for migrating generated projects and legacy flat configs to v2.
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,8 @@ main({
36
36
  projectName: cliArgs.projectName,
37
37
  manifest: cliArgs.manifest,
38
38
  template: cliArgs.template,
39
+ framework: cliArgs.framework,
40
+ arch: cliArgs.arch,
39
41
  }).catch((e) => {
40
42
  console.error(chalk.red("\nError:"), e.message || e);
41
43
  process.exit(1);
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "create-lumen",
3
- "version": "2.0.0-alpha.1",
4
- "description": "Scaffold a React + Vite project with architecture choice: feature-based or type-based.",
3
+ "version": "2.0.0-beta.1",
4
+ "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-lumen": "bin/cli.js"
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/",
@@ -6,7 +6,7 @@
6
6
  "variant": "vite"
7
7
  },
8
8
  "styling": {
9
- "engine": "bootstrap"
9
+ "engine": "none"
10
10
  },
11
11
  "architecture": {
12
12
  "type": "feature-based"
@@ -6,7 +6,7 @@
6
6
  "variant": "vite"
7
7
  },
8
8
  "styling": {
9
- "engine": "bootstrap"
9
+ "engine": "none"
10
10
  },
11
11
  "architecture": {
12
12
  "type": "type-based"
@@ -62,7 +62,6 @@
62
62
  "type": "string",
63
63
  "enum": [
64
64
  "tailwind",
65
- "bootstrap",
66
65
  "none"
67
66
  ]
68
67
  }
package/src/cli-args.js CHANGED
@@ -3,6 +3,10 @@ import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { parseManifest } from "./manifest/schema.js";
6
+ import {
7
+ normalizeFramework,
8
+ isPromptVisible,
9
+ } from "./engine/capabilities.js";
6
10
 
7
11
  export const PRESETS = {
8
12
  "react-ts": {
@@ -141,6 +145,112 @@ export const PRESETS = {
141
145
  gitInit: true,
142
146
  readme: true,
143
147
  },
148
+ "react-none-ts": {
149
+ frameworkName: "react",
150
+ frameworkVariant: "vite",
151
+ architecture: "none",
152
+ language: "ts",
153
+ cssFramework: "tailwind",
154
+ testing: "vitest",
155
+ router: false,
156
+ stateManagement: "none",
157
+ iconLibrary: "none",
158
+ apiClient: "none",
159
+ linter: "eslint",
160
+ formatter: "prettier",
161
+ docsLanguage: "en",
162
+ gitInit: true,
163
+ readme: true,
164
+ },
165
+ "react-none-js": {
166
+ frameworkName: "react",
167
+ frameworkVariant: "vite",
168
+ architecture: "none",
169
+ language: "js",
170
+ cssFramework: "tailwind",
171
+ testing: "vitest",
172
+ router: false,
173
+ stateManagement: "none",
174
+ iconLibrary: "none",
175
+ apiClient: "none",
176
+ linter: "eslint",
177
+ formatter: "prettier",
178
+ docsLanguage: "en",
179
+ gitInit: true,
180
+ readme: true,
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
+ },
144
254
  };
145
255
 
146
256
  /**
@@ -151,6 +261,8 @@ export const PRESETS = {
151
261
  * quickSetup: boolean,
152
262
  * manifest: string | null,
153
263
  * template: string | null,
264
+ * framework: string | null,
265
+ * arch: string | null,
154
266
  * help: boolean,
155
267
  * version: boolean,
156
268
  * positionals: string[]
@@ -163,6 +275,16 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
163
275
  yes: { type: "boolean", short: "y", default: false },
164
276
  manifest: { type: "string", short: "m" },
165
277
  template: { type: "string", short: "t" },
278
+ framework: { type: "string", short: "f" },
279
+ arch: { type: "string", short: "a" },
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" },
166
288
  help: { type: "boolean", short: "h", default: false },
167
289
  version: { type: "boolean", short: "v", default: false },
168
290
  },
@@ -172,11 +294,31 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
172
294
 
173
295
  const projectName = positionals[0] ? positionals[0].trim() : null;
174
296
 
297
+ let arch = values.arch || values.architecture || null;
298
+ if (arch) {
299
+ arch = arch.trim().toLowerCase();
300
+ if (arch === "feature") arch = "feature-based";
301
+ else if (arch === "component" || arch === "component-based" || arch === "type") arch = "type-based";
302
+ }
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
+
175
310
  return {
176
311
  projectName,
177
312
  quickSetup: Boolean(values.yes),
178
313
  manifest: values.manifest || null,
179
314
  template: values.template || null,
315
+ framework: values.framework ? values.framework.trim() : null,
316
+ arch,
317
+ bundler,
318
+ adapter,
319
+ reactCompiler,
320
+ agentDocs,
321
+ linter,
180
322
  help: Boolean(values.help),
181
323
  version: Boolean(values.version),
182
324
  positionals,
@@ -222,14 +364,24 @@ export function loadManifestSource(source, cwd = process.cwd()) {
222
364
  export function manifestToResponses(manifest, projectName) {
223
365
  const frameworkName = manifest.framework?.name || "react";
224
366
  const frameworkVariant = manifest.framework?.variant || "vite";
225
- const isNext = frameworkName === "next";
367
+ const framework = normalizeFramework({ name: frameworkName, variant: frameworkVariant });
368
+ const canClientRoute = isPromptVisible(framework, "router");
369
+ const supportsBundler = isPromptVisible(framework, "bundler");
370
+ const supportsAdapter = isPromptVisible(framework, "adapter");
371
+ const supportsCompiler = isPromptVisible(framework, "reactCompiler");
226
372
 
227
373
  return {
228
374
  projectName,
229
375
  frameworkName,
230
376
  frameworkVariant,
231
- bundler: manifest.framework?.bundler,
232
- adapter: manifest.framework?.adapter,
377
+ bundler:
378
+ supportsBundler && manifest.framework?.bundler
379
+ ? manifest.framework.bundler
380
+ : undefined,
381
+ adapter:
382
+ supportsAdapter && manifest.framework?.adapter
383
+ ? manifest.framework.adapter
384
+ : undefined,
233
385
  architecture: manifest.architecture?.type || "feature-based",
234
386
  language: manifest.tooling?.language || "ts",
235
387
  cssFramework: manifest.styling?.engine || "tailwind",
@@ -237,11 +389,13 @@ export function manifestToResponses(manifest, projectName) {
237
389
  formatter: manifest.tooling?.formatter || "prettier",
238
390
  uiKit: manifest.ui?.kit || "none",
239
391
  docsLanguage: manifest.docs?.language || "en",
240
- reactCompiler: manifest.reactCompiler,
392
+ reactCompiler: supportsCompiler ? manifest.reactCompiler : undefined,
241
393
  agentDocs: manifest.agentDocs,
242
- // Sensible defaults for scaffolder conditional passes
243
- testing: "vitest",
244
- router: isNext ? false : true,
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",
398
+ router: canClientRoute,
245
399
  stateManagement: "none",
246
400
  iconLibrary: "none",
247
401
  apiClient: "none",
@@ -263,8 +417,10 @@ ${chalk.bold("USAGE")}
263
417
 
264
418
  ${chalk.bold("OPTIONS")}
265
419
  ${chalk.yellow("-y, --yes")} Quick setup with recommended defaults (TS + Tailwind + Router + ESLint + Prettier)
420
+ ${chalk.yellow("-a, --arch")} <name> Architecture preset (feature-based, type-based, none, hybrid)
266
421
  ${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
267
422
  ${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
423
+ ${chalk.yellow("-f, --framework")} <name> Target framework (react, next, react:vite, next:app-router, etc.)
268
424
  ${chalk.yellow("-h, --help")} Show this help message
269
425
  ${chalk.yellow("-v, --version")} Show version number
270
426
 
@@ -273,6 +429,12 @@ ${chalk.bold("PRESETS")}
273
429
  ${chalk.cyan("react-js")} React + Vite + JavaScript (feature-based)
274
430
  ${chalk.cyan("react-type-ts")} React + Vite + TypeScript (type-based)
275
431
  ${chalk.cyan("react-type-js")} React + Vite + JavaScript (type-based)
432
+ ${chalk.cyan("react-none-ts")} React + Vite + TypeScript (none)
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)
276
438
 
277
439
  ${chalk.bold("EXAMPLES")}
278
440
  ${chalk.gray("# Interactive setup")}
package/src/configure.js CHANGED
@@ -88,6 +88,10 @@ async function configureTsconfig(projectPath) {
88
88
  const rootTsconfig = {
89
89
  files: [],
90
90
  references,
91
+ compilerOptions: {
92
+ baseUrl: ".",
93
+ paths,
94
+ },
91
95
  };
92
96
  await fsp.writeFile(
93
97
  rootTsconfigPath,
@@ -162,12 +166,16 @@ async function configurePackageJson(projectPath, language) {
162
166
 
163
167
  if (!pkg.scripts) pkg.scripts = {};
164
168
 
165
- pkg.scripts.dev = "vite";
166
- pkg.scripts.build = "vite build";
167
- 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";
168
176
 
169
177
  if (language === "ts") {
170
- pkg.scripts.typecheck = "tsc -b";
178
+ pkg.scripts.typecheck ??= "tsc -b";
171
179
  }
172
180
 
173
181
  await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
package/src/css.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import { promises as fsp } from "fs";
2
2
  import path from "path";
3
3
  import { deleteIfExists } from "@/utils/fs.js";
4
+ import {
5
+ normalizeFramework,
6
+ hasCapability,
7
+ CAPABILITIES,
8
+ } from "@/engine/capabilities.js";
4
9
 
5
10
  export async function setupCssFramework({
6
11
  projectPath,
@@ -13,18 +18,20 @@ export async function setupCssFramework({
13
18
  framework = "vite",
14
19
  }) {
15
20
  process.chdir(projectPath);
21
+ const fw = normalizeFramework(framework);
22
+ const isFilesystemRouted = hasCapability(fw, CAPABILITIES.FILESYSTEM_ROUTING);
16
23
  const cssDir = path.join(templatesDir, "css");
17
24
  // Feature-based main.* imports ./shared/styles/globals.css (or main.css when
18
25
  // framework is "none"); type-based imports ./styles/globals.css (or
19
26
  // main.css). The chosen framework's styles land in the file the architecture
20
27
  // actually imports.
21
28
  const stylesDir =
22
- architecture === "feature-based"
29
+ architecture === "feature-based" || architecture === "hybrid"
23
30
  ? path.join(projectPath, "src", "shared", "styles")
24
31
  : path.join(projectPath, "src", "styles");
25
32
 
26
- // Vanilla CSS gets a stylesheet named main.css; tailwind/bootstrap keep
27
- // globals.css (their idiomatic name — they inject framework directives).
33
+ // Vanilla CSS gets a stylesheet named main.css; tailwind keeps
34
+ // globals.css (its idiomatic name — it injects framework directives).
28
35
  const mainFileName = cssFramework === "none" ? "main.css" : "globals.css";
29
36
 
30
37
  async function writeCss(content, fileName) {
@@ -32,7 +39,7 @@ export async function setupCssFramework({
32
39
  }
33
40
 
34
41
  if (cssFramework === "tailwind") {
35
- if (framework === "next") {
42
+ if (isFilesystemRouted || fw.name === "next") {
36
43
  const postcssConfigFile = "postcss.config.mjs";
37
44
  const postcssConfigTarget = path.join(projectPath, postcssConfigFile);
38
45
  const postcssConfigContent = await fsp.readFile(
@@ -60,29 +67,6 @@ export async function setupCssFramework({
60
67
  );
61
68
  await writeCss(globalsContent, "globals.css");
62
69
  await writeCss(themesContent, "themes.css");
63
- } else if (cssFramework === "bootstrap") {
64
- const mainFile = path.join(projectPath, "src", `main.${ext}`);
65
- try {
66
- let mainContent = await fsp.readFile(mainFile, "utf8");
67
- if (
68
- !mainContent.includes("bootstrap/dist/css/bootstrap.min.css")
69
- ) {
70
- mainContent =
71
- "import 'bootstrap/dist/css/bootstrap.min.css';\n" + mainContent;
72
- await fsp.writeFile(mainFile, mainContent, "utf8");
73
- }
74
- } catch {}
75
-
76
- const globalsContent = await fsp.readFile(
77
- path.join(cssDir, "bootstrap", "src", "globals.css"),
78
- "utf8"
79
- );
80
- const themesContent = await fsp.readFile(
81
- path.join(cssDir, "bootstrap", "src", "themes.css"),
82
- "utf8"
83
- );
84
- await writeCss(globalsContent, "globals.css");
85
- await writeCss(themesContent, "themes.css");
86
70
  } else {
87
71
  const mainContent = await fsp.readFile(
88
72
  path.join(cssDir, "none", "src", "main.css"),
@@ -110,17 +94,17 @@ export async function setupCssFramework({
110
94
  }
111
95
 
112
96
  // Rewrite the CSS import in main.{ext} so it matches the file the framework
113
- // actually wrote (main.css for "none", globals.css for tailwind/bootstrap).
97
+ // actually wrote (main.css for "none", globals.css for tailwind).
114
98
  async function syncMainCssImport(projectPath, ext, architecture, mainFileName) {
115
99
  const mainPath = path.join(projectPath, "src", `main.${ext}`);
116
100
  try {
117
101
  let content = await fsp.readFile(mainPath, "utf8");
118
102
  const expected =
119
- architecture === "feature-based"
103
+ architecture === "feature-based" || architecture === "hybrid"
120
104
  ? `./shared/styles/${mainFileName}`
121
105
  : `./styles/${mainFileName}`;
122
106
  const wrong =
123
- architecture === "feature-based"
107
+ architecture === "feature-based" || architecture === "hybrid"
124
108
  ? /import ['"]\.\/shared\/styles\/(globals|main)\.css['"]/
125
109
  : /import ['"]\.\/styles\/(globals|main)\.css['"]/;
126
110
  content = content.replace(wrong, `import '${expected}'`);
@@ -25,13 +25,13 @@ export async function installDeps(pkgManager, dev, packages, cwd) {
25
25
  }
26
26
 
27
27
  export async function installAllDeps(responses, pkg, cwd) {
28
- const { deps, devDeps } = computeDeps(responses);
28
+ const { deps, devDepBatches } = computeDeps(responses);
29
29
 
30
30
  if (deps.length > 0) {
31
31
  await installDeps(pkg, false, deps, cwd);
32
32
  }
33
- if (devDeps.length > 0) {
34
- await installDeps(pkg, true, devDeps, cwd);
33
+ for (const batch of devDepBatches) {
34
+ await installDeps(pkg, true, batch, cwd);
35
35
  }
36
36
  }
37
37
 
@@ -40,18 +40,23 @@ 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 devDeps = [];
43
+ const fwName =
44
+ typeof responses.framework === "string"
45
+ ? responses.framework
46
+ : (responses.framework?.name ?? responses.frameworkName ?? "react");
47
+ const cssDevDeps = [];
48
+ const testingDevDeps = [];
49
+ const linterDevDeps = [];
50
+ const formatterDevDeps = [];
44
51
 
45
52
  // CSS framework
46
53
  if (responses.cssFramework === "tailwind") {
47
- if (responses.framework === "next") {
48
- devDeps.push("tailwindcss", "@tailwindcss/postcss", "postcss");
54
+ if (fwName === "next") {
55
+ cssDevDeps.push("tailwindcss", "@tailwindcss/postcss", "postcss");
49
56
  } else {
50
- devDeps.push("tailwindcss", "@tailwindcss/vite");
57
+ cssDevDeps.push("tailwindcss", "@tailwindcss/vite");
51
58
  }
52
59
  deps.push("clsx", "tailwind-merge");
53
- } else if (responses.cssFramework === "bootstrap") {
54
- deps.push("bootstrap", "react-bootstrap");
55
60
  }
56
61
 
57
62
  // State management
@@ -80,14 +85,14 @@ export function computeDeps(responses) {
80
85
 
81
86
  // Testing
82
87
  if (responses.testing === "vitest") {
83
- devDeps.push(
88
+ testingDevDeps.push(
84
89
  "vitest",
85
90
  "@testing-library/react",
86
91
  "@testing-library/jest-dom",
87
92
  "jsdom"
88
93
  );
89
94
  } else if (responses.testing === "jest") {
90
- devDeps.push(
95
+ testingDevDeps.push(
91
96
  "jest",
92
97
  "jest-environment-jsdom",
93
98
  "@testing-library/react",
@@ -102,30 +107,41 @@ export function computeDeps(responses) {
102
107
 
103
108
  // Linter
104
109
  if (responses.linter === "eslint") {
105
- if (responses.framework === "next") {
106
- devDeps.push("eslint", "eslint-config-next");
110
+ if (fwName === "next") {
111
+ linterDevDeps.push("eslint", "eslint-config-next");
107
112
  } else {
108
- devDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
113
+ linterDevDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
109
114
  if (responses.language === "ts") {
110
- devDeps.push("typescript-eslint", "jiti");
115
+ linterDevDeps.push("typescript-eslint", "jiti");
111
116
  }
112
117
  }
113
118
  } else if (responses.linter === "oxlint") {
114
- devDeps.push("oxlint");
119
+ linterDevDeps.push("oxlint");
120
+ } else if (responses.linter === "biome") {
121
+ linterDevDeps.push("@biomejs/biome");
115
122
  }
116
123
 
117
124
  // Formatter
118
125
  if (responses.formatter === "prettier") {
119
- devDeps.push("prettier");
126
+ formatterDevDeps.push("prettier");
120
127
  if (responses.linter === "eslint") {
121
- devDeps.push("eslint-config-prettier");
128
+ formatterDevDeps.push("eslint-config-prettier");
122
129
  }
123
130
  } else if (responses.formatter === "oxfmt") {
124
- devDeps.push("oxfmt");
131
+ formatterDevDeps.push("oxfmt");
125
132
  }
126
133
 
127
134
  // Always re-install react + react-dom for consistency
128
135
  deps.push("react", "react-dom");
129
136
 
130
- return { deps, devDeps };
137
+ const devDepBatches = [
138
+ cssDevDeps,
139
+ testingDevDeps,
140
+ linterDevDeps,
141
+ formatterDevDeps,
142
+ ].filter((batch) => batch.length > 0);
143
+
144
+ const devDeps = devDepBatches.flat();
145
+
146
+ return { deps, devDeps, devDepBatches };
131
147
  }