create-lumen 1.2.0 → 2.0.0-alpha.0

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 (136) hide show
  1. package/README.md +56 -9
  2. package/bin/cli.js +18 -4
  3. package/package.json +14 -4
  4. package/schema/fixtures/index.js +62 -0
  5. package/schema/fixtures/v2/invalid/invalid-manifest-version.json +33 -0
  6. package/schema/fixtures/v2/invalid/malformed-paths.json +33 -0
  7. package/schema/fixtures/v2/invalid/missing-required-fields.json +17 -0
  8. package/schema/fixtures/v2/invalid/next-type-based.json +33 -0
  9. package/schema/fixtures/v2/invalid/next-vite.json +33 -0
  10. package/schema/fixtures/v2/invalid/react-bundler.json +34 -0
  11. package/schema/fixtures/v2/invalid/react-hybrid.json +33 -0
  12. package/schema/fixtures/v2/invalid/react-variant-mismatch.json +33 -0
  13. package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +33 -0
  14. package/schema/fixtures/v2/invalid/v1-flat-config.json +11 -0
  15. package/schema/fixtures/v2/valid/full-kitchen-sink.json +35 -0
  16. package/schema/fixtures/v2/valid/next-app-router-feature-based.json +35 -0
  17. package/schema/fixtures/v2/valid/next-app-router-hybrid.json +35 -0
  18. package/schema/fixtures/v2/valid/next-app-router-none.json +33 -0
  19. package/schema/fixtures/v2/valid/next-pages-router-feature-based.json +35 -0
  20. package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +35 -0
  21. package/schema/fixtures/v2/valid/next-pages-router-none.json +33 -0
  22. package/schema/fixtures/v2/valid/react-vite-feature-based.json +33 -0
  23. package/schema/fixtures/v2/valid/react-vite-none.json +33 -0
  24. package/schema/fixtures/v2/valid/react-vite-type-based.json +33 -0
  25. package/schema/lumen.config.v2.json +216 -0
  26. package/src/cli-args.js +294 -0
  27. package/src/css.js +20 -9
  28. package/src/dependencies.js +12 -6
  29. package/src/engine/capabilities.js +218 -0
  30. package/src/engine/index.js +2 -0
  31. package/src/engine/providers/base-provider.js +219 -0
  32. package/src/injector.js +26 -13
  33. package/src/main.js +52 -10
  34. package/src/manifest/emit.js +120 -0
  35. package/src/manifest/index.js +11 -0
  36. package/src/manifest/paths.js +128 -0
  37. package/src/manifest/schema.js +212 -0
  38. package/src/prompts.js +26 -6
  39. package/templates/conditional/linting/eslint/next/eslint.config.mjs +16 -0
  40. package/templates/css/tailwind/postcss.config.mjs +5 -0
  41. /package/templates/architectures/{component-based → type-based}/main.jsx +0 -0
  42. /package/templates/architectures/{component-based → type-based}/main.tsx +0 -0
  43. /package/templates/architectures/{component-based → type-based}/src/App.jsx +0 -0
  44. /package/templates/architectures/{component-based → type-based}/src/App.tsx +0 -0
  45. /package/templates/architectures/{component-based → type-based}/src/components/common/Button.jsx +0 -0
  46. /package/templates/architectures/{component-based → type-based}/src/components/common/Button.tsx +0 -0
  47. /package/templates/architectures/{component-based → type-based}/src/components/common/index.js +0 -0
  48. /package/templates/architectures/{component-based → type-based}/src/components/common/index.ts +0 -0
  49. /package/templates/architectures/{component-based → type-based}/src/components/form/.gitkeep +0 -0
  50. /package/templates/architectures/{component-based → type-based}/src/config/.gitkeep +0 -0
  51. /package/templates/architectures/{component-based → type-based}/src/hooks/useApp.js +0 -0
  52. /package/templates/architectures/{component-based → type-based}/src/hooks/useApp.ts +0 -0
  53. /package/templates/architectures/{component-based → type-based}/src/hooks/useLocalStorage.jsx +0 -0
  54. /package/templates/architectures/{component-based → type-based}/src/hooks/useLocalStorage.ts +0 -0
  55. /package/templates/architectures/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
  56. /package/templates/architectures/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
  57. /package/templates/architectures/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
  58. /package/templates/architectures/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
  59. /package/templates/architectures/{component-based → type-based}/src/pages/Home/index.js +0 -0
  60. /package/templates/architectures/{component-based → type-based}/src/pages/Home/index.ts +0 -0
  61. /package/templates/architectures/{component-based → type-based}/src/providers/.gitkeep +0 -0
  62. /package/templates/architectures/{component-based → type-based}/src/providers/AppProvider.jsx +0 -0
  63. /package/templates/architectures/{component-based → type-based}/src/providers/AppProvider.tsx +0 -0
  64. /package/templates/architectures/{component-based → type-based}/src/providers/appcontext.js +0 -0
  65. /package/templates/architectures/{component-based → type-based}/src/providers/appcontext.ts +0 -0
  66. /package/templates/architectures/{component-based → type-based}/src/services/.gitkeep +0 -0
  67. /package/templates/architectures/{component-based → type-based}/src/store/.gitkeep +0 -0
  68. /package/templates/architectures/{component-based → type-based}/src/styles/.gitkeep +0 -0
  69. /package/templates/architectures/{component-based → type-based}/src/types/index.js +0 -0
  70. /package/templates/architectures/{component-based → type-based}/src/types/index.ts +0 -0
  71. /package/templates/architectures/{component-based → type-based}/src/utils/cn.jsx +0 -0
  72. /package/templates/architectures/{component-based → type-based}/src/utils/cn.ts +0 -0
  73. /package/templates/conditional/axios/{component-based → type-based}/src/config/axios.config.js +0 -0
  74. /package/templates/conditional/axios/{component-based → type-based}/src/config/axios.config.ts +0 -0
  75. /package/templates/conditional/axios/{component-based → type-based}/src/services/axios.client.js +0 -0
  76. /package/templates/conditional/axios/{component-based → type-based}/src/services/axios.client.ts +0 -0
  77. /package/templates/conditional/axios/{component-based → type-based}/src/services/user.service.js +0 -0
  78. /package/templates/conditional/axios/{component-based → type-based}/src/services/user.service.ts +0 -0
  79. /package/templates/conditional/fetch/{component-based → type-based}/src/config/api.config.js +0 -0
  80. /package/templates/conditional/fetch/{component-based → type-based}/src/config/api.config.ts +0 -0
  81. /package/templates/conditional/fetch/{component-based → type-based}/src/services/api.client.js +0 -0
  82. /package/templates/conditional/fetch/{component-based → type-based}/src/services/api.client.ts +0 -0
  83. /package/templates/conditional/fetch/{component-based → type-based}/src/services/user.service.js +0 -0
  84. /package/templates/conditional/fetch/{component-based → type-based}/src/services/user.service.ts +0 -0
  85. /package/templates/conditional/icons/huge/{component-based → type-based}/bootstrap/src/pages/Home/Home.jsx +0 -0
  86. /package/templates/conditional/icons/huge/{component-based → type-based}/bootstrap/src/pages/Home/Home.tsx +0 -0
  87. /package/templates/conditional/icons/huge/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
  88. /package/templates/conditional/icons/huge/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
  89. /package/templates/conditional/icons/huge/{component-based → type-based}/tailwind/src/pages/Home/Home.jsx +0 -0
  90. /package/templates/conditional/icons/huge/{component-based → type-based}/tailwind/src/pages/Home/Home.tsx +0 -0
  91. /package/templates/conditional/icons/lucide/{component-based → type-based}/bootstrap/src/pages/Home/Home.jsx +0 -0
  92. /package/templates/conditional/icons/lucide/{component-based → type-based}/bootstrap/src/pages/Home/Home.tsx +0 -0
  93. /package/templates/conditional/icons/lucide/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
  94. /package/templates/conditional/icons/lucide/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
  95. /package/templates/conditional/icons/lucide/{component-based → type-based}/tailwind/src/pages/Home/Home.jsx +0 -0
  96. /package/templates/conditional/icons/lucide/{component-based → type-based}/tailwind/src/pages/Home/Home.tsx +0 -0
  97. /package/templates/conditional/router/{component-based → type-based}/bootstrap/src/layouts/MainLayout.jsx +0 -0
  98. /package/templates/conditional/router/{component-based → type-based}/bootstrap/src/layouts/MainLayout.tsx +0 -0
  99. /package/templates/conditional/router/{component-based → type-based}/src/App.jsx +0 -0
  100. /package/templates/conditional/router/{component-based → type-based}/src/App.tsx +0 -0
  101. /package/templates/conditional/router/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
  102. /package/templates/conditional/router/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
  103. /package/templates/conditional/router/{component-based → type-based}/src/router/guards/AuthGuard.jsx +0 -0
  104. /package/templates/conditional/router/{component-based → type-based}/src/router/guards/AuthGuard.tsx +0 -0
  105. /package/templates/conditional/router/{component-based → type-based}/src/router/guards/GuestGuard.jsx +0 -0
  106. /package/templates/conditional/router/{component-based → type-based}/src/router/guards/GuestGuard.tsx +0 -0
  107. /package/templates/conditional/router/{component-based → type-based}/src/router/index.jsx +0 -0
  108. /package/templates/conditional/router/{component-based → type-based}/src/router/index.tsx +0 -0
  109. /package/templates/conditional/router/{component-based → type-based}/tailwind/src/layouts/MainLayout.jsx +0 -0
  110. /package/templates/conditional/router/{component-based → type-based}/tailwind/src/layouts/MainLayout.tsx +0 -0
  111. /package/templates/conditional/state/redux/{component-based → type-based}/src/pages/Home/counterSlice.jsx +0 -0
  112. /package/templates/conditional/state/redux/{component-based → type-based}/src/pages/Home/counterSlice.ts +0 -0
  113. /package/templates/conditional/state/redux/{component-based → type-based}/src/providers/StoreProvider.jsx +0 -0
  114. /package/templates/conditional/state/redux/{component-based → type-based}/src/providers/StoreProvider.tsx +0 -0
  115. /package/templates/conditional/state/redux/{component-based → type-based}/src/store/hooks.js +0 -0
  116. /package/templates/conditional/state/redux/{component-based → type-based}/src/store/hooks.ts +0 -0
  117. /package/templates/conditional/state/redux/{component-based → type-based}/src/store/index.js +0 -0
  118. /package/templates/conditional/state/redux/{component-based → type-based}/src/store/index.ts +0 -0
  119. /package/templates/conditional/state/zustand/{component-based → type-based}/src/store/useCounterStore.jsx +0 -0
  120. /package/templates/conditional/state/zustand/{component-based → type-based}/src/store/useCounterStore.ts +0 -0
  121. /package/templates/conditional/testing/jest/{component-based → type-based}/src/__tests__/App.test.jsx +0 -0
  122. /package/templates/conditional/testing/jest/{component-based → type-based}/src/__tests__/App.test.tsx +0 -0
  123. /package/templates/conditional/testing/vitest/{component-based → type-based}/src/__tests__/App.test.jsx +0 -0
  124. /package/templates/conditional/testing/vitest/{component-based → type-based}/src/__tests__/App.test.tsx +0 -0
  125. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/components/common/Button.jsx +0 -0
  126. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/components/common/Button.tsx +0 -0
  127. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
  128. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
  129. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
  130. /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
  131. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/components/common/Button.jsx +0 -0
  132. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/components/common/Button.tsx +0 -0
  133. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
  134. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
  135. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
  136. /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
@@ -0,0 +1,294 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import chalk from "chalk";
5
+ import { parseManifest } from "./manifest/schema.js";
6
+
7
+ export const PRESETS = {
8
+ "react-ts": {
9
+ frameworkName: "react",
10
+ frameworkVariant: "vite",
11
+ architecture: "feature-based",
12
+ language: "ts",
13
+ cssFramework: "tailwind",
14
+ testing: "vitest",
15
+ router: true,
16
+ stateManagement: "none",
17
+ iconLibrary: "none",
18
+ apiClient: "none",
19
+ linter: "eslint",
20
+ formatter: "prettier",
21
+ docsLanguage: "en",
22
+ gitInit: true,
23
+ readme: true,
24
+ },
25
+ "react-vite-ts": {
26
+ frameworkName: "react",
27
+ frameworkVariant: "vite",
28
+ architecture: "feature-based",
29
+ language: "ts",
30
+ cssFramework: "tailwind",
31
+ testing: "vitest",
32
+ router: true,
33
+ stateManagement: "none",
34
+ iconLibrary: "none",
35
+ apiClient: "none",
36
+ linter: "eslint",
37
+ formatter: "prettier",
38
+ docsLanguage: "en",
39
+ gitInit: true,
40
+ readme: true,
41
+ },
42
+ "react-js": {
43
+ frameworkName: "react",
44
+ frameworkVariant: "vite",
45
+ architecture: "feature-based",
46
+ language: "js",
47
+ cssFramework: "tailwind",
48
+ testing: "vitest",
49
+ router: true,
50
+ stateManagement: "none",
51
+ iconLibrary: "none",
52
+ apiClient: "none",
53
+ linter: "eslint",
54
+ formatter: "prettier",
55
+ docsLanguage: "en",
56
+ gitInit: true,
57
+ readme: true,
58
+ },
59
+ "react-vite-js": {
60
+ frameworkName: "react",
61
+ frameworkVariant: "vite",
62
+ architecture: "feature-based",
63
+ language: "js",
64
+ cssFramework: "tailwind",
65
+ testing: "vitest",
66
+ router: true,
67
+ stateManagement: "none",
68
+ iconLibrary: "none",
69
+ apiClient: "none",
70
+ linter: "eslint",
71
+ formatter: "prettier",
72
+ docsLanguage: "en",
73
+ gitInit: true,
74
+ readme: true,
75
+ },
76
+ "react-type-ts": {
77
+ frameworkName: "react",
78
+ frameworkVariant: "vite",
79
+ architecture: "type-based",
80
+ language: "ts",
81
+ cssFramework: "tailwind",
82
+ testing: "vitest",
83
+ router: true,
84
+ stateManagement: "none",
85
+ iconLibrary: "none",
86
+ apiClient: "none",
87
+ linter: "eslint",
88
+ formatter: "prettier",
89
+ docsLanguage: "en",
90
+ gitInit: true,
91
+ readme: true,
92
+ },
93
+ "react-type-js": {
94
+ frameworkName: "react",
95
+ frameworkVariant: "vite",
96
+ architecture: "type-based",
97
+ language: "js",
98
+ cssFramework: "tailwind",
99
+ testing: "vitest",
100
+ router: true,
101
+ stateManagement: "none",
102
+ iconLibrary: "none",
103
+ apiClient: "none",
104
+ linter: "eslint",
105
+ formatter: "prettier",
106
+ docsLanguage: "en",
107
+ gitInit: true,
108
+ readme: true,
109
+ },
110
+ "react-component-ts": {
111
+ frameworkName: "react",
112
+ frameworkVariant: "vite",
113
+ architecture: "type-based",
114
+ language: "ts",
115
+ cssFramework: "tailwind",
116
+ testing: "vitest",
117
+ router: true,
118
+ stateManagement: "none",
119
+ iconLibrary: "none",
120
+ apiClient: "none",
121
+ linter: "eslint",
122
+ formatter: "prettier",
123
+ docsLanguage: "en",
124
+ gitInit: true,
125
+ readme: true,
126
+ },
127
+ "react-component-js": {
128
+ frameworkName: "react",
129
+ frameworkVariant: "vite",
130
+ architecture: "type-based",
131
+ language: "js",
132
+ cssFramework: "tailwind",
133
+ testing: "vitest",
134
+ router: true,
135
+ stateManagement: "none",
136
+ iconLibrary: "none",
137
+ apiClient: "none",
138
+ linter: "eslint",
139
+ formatter: "prettier",
140
+ docsLanguage: "en",
141
+ gitInit: true,
142
+ readme: true,
143
+ },
144
+ };
145
+
146
+ /**
147
+ * Parse CLI arguments using standard node:util parseArgs.
148
+ * @param {string[]} [argv]
149
+ * @returns {{
150
+ * projectName: string | null,
151
+ * quickSetup: boolean,
152
+ * manifest: string | null,
153
+ * template: string | null,
154
+ * help: boolean,
155
+ * version: boolean,
156
+ * positionals: string[]
157
+ * }}
158
+ */
159
+ export function parseCliArgs(argv = process.argv.slice(2)) {
160
+ const { values, positionals } = parseArgs({
161
+ args: argv,
162
+ options: {
163
+ yes: { type: "boolean", short: "y", default: false },
164
+ manifest: { type: "string", short: "m" },
165
+ template: { type: "string", short: "t" },
166
+ help: { type: "boolean", short: "h", default: false },
167
+ version: { type: "boolean", short: "v", default: false },
168
+ },
169
+ allowPositionals: true,
170
+ strict: false,
171
+ });
172
+
173
+ const projectName = positionals[0] ? positionals[0].trim() : null;
174
+
175
+ return {
176
+ projectName,
177
+ quickSetup: Boolean(values.yes),
178
+ manifest: values.manifest || null,
179
+ template: values.template || null,
180
+ help: Boolean(values.help),
181
+ version: Boolean(values.version),
182
+ positionals,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Load and validate a manifest from a file path or raw inline JSON string.
188
+ * @param {string} source - file path or inline JSON
189
+ * @param {string} [cwd=process.cwd()]
190
+ * @returns {object} validated manifest v2 object
191
+ */
192
+ export function loadManifestSource(source, cwd = process.cwd()) {
193
+ const trimmed = source.trim();
194
+ let parsed;
195
+
196
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
197
+ try {
198
+ parsed = JSON.parse(trimmed);
199
+ } catch (err) {
200
+ throw new Error(`Failed to parse inline manifest JSON: ${err.message}`, { cause: err });
201
+ }
202
+ } else {
203
+ const filePath = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
204
+ try {
205
+ const content = readFileSync(filePath, "utf8");
206
+ parsed = JSON.parse(content);
207
+ } catch (err) {
208
+ throw new Error(`Failed to read manifest file "${filePath}": ${err.message}`, { cause: err });
209
+ }
210
+ }
211
+
212
+ // Validate strictly against schema v2; throws if v1 or invalid
213
+ return parseManifest(parsed);
214
+ }
215
+
216
+ /**
217
+ * Map a validated v2 manifest into scaffolder response options.
218
+ * @param {object} manifest - validated manifest v2
219
+ * @param {string} projectName
220
+ * @returns {object} scaffolder response set
221
+ */
222
+ export function manifestToResponses(manifest, projectName) {
223
+ const frameworkName = manifest.framework?.name || "react";
224
+ const frameworkVariant = manifest.framework?.variant || "vite";
225
+ const isNext = frameworkName === "next";
226
+
227
+ return {
228
+ projectName,
229
+ frameworkName,
230
+ frameworkVariant,
231
+ bundler: manifest.framework?.bundler,
232
+ adapter: manifest.framework?.adapter,
233
+ architecture: manifest.architecture?.type || "feature-based",
234
+ language: manifest.tooling?.language || "ts",
235
+ cssFramework: manifest.styling?.engine || "tailwind",
236
+ linter: manifest.tooling?.linter || "eslint",
237
+ formatter: manifest.tooling?.formatter || "prettier",
238
+ uiKit: manifest.ui?.kit || "none",
239
+ docsLanguage: manifest.docs?.language || "en",
240
+ reactCompiler: manifest.reactCompiler,
241
+ agentDocs: manifest.agentDocs,
242
+ // Sensible defaults for scaffolder conditional passes
243
+ testing: "vitest",
244
+ router: isNext ? false : true,
245
+ stateManagement: "none",
246
+ iconLibrary: "none",
247
+ apiClient: "none",
248
+ gitInit: true,
249
+ readme: true,
250
+ _manifestSource: manifest,
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Print styled CLI help message.
256
+ */
257
+ export function printHelp() {
258
+ console.log(`
259
+ ${chalk.bold.cyan(" create-lumen")} — Scaffold a React + Vite project with architecture choice
260
+
261
+ ${chalk.bold("USAGE")}
262
+ ${chalk.green("$")} ${chalk.bold("create-lumen")} [project-name] [options]
263
+
264
+ ${chalk.bold("OPTIONS")}
265
+ ${chalk.yellow("-y, --yes")} Quick setup with recommended defaults (TS + Tailwind + Router + ESLint + Prettier)
266
+ ${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
267
+ ${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
268
+ ${chalk.yellow("-h, --help")} Show this help message
269
+ ${chalk.yellow("-v, --version")} Show version number
270
+
271
+ ${chalk.bold("PRESETS")}
272
+ ${chalk.cyan("react-ts")} React + Vite + TypeScript (feature-based)
273
+ ${chalk.cyan("react-js")} React + Vite + JavaScript (feature-based)
274
+ ${chalk.cyan("react-type-ts")} React + Vite + TypeScript (type-based)
275
+ ${chalk.cyan("react-type-js")} React + Vite + JavaScript (type-based)
276
+
277
+ ${chalk.bold("EXAMPLES")}
278
+ ${chalk.gray("# Interactive setup")}
279
+ ${chalk.green("$")} create-lumen my-app
280
+
281
+ ${chalk.gray("# Non-interactive quick setup")}
282
+ ${chalk.green("$")} create-lumen my-app -y
283
+ ${chalk.green("$")} create-lumen -y my-app
284
+
285
+ ${chalk.gray("# Scaffold from a template preset")}
286
+ ${chalk.green("$")} create-lumen my-app --template react-ts
287
+
288
+ ${chalk.gray("# Scaffold from a manifest file")}
289
+ ${chalk.green("$")} create-lumen my-app --manifest ./lumen.config.json
290
+
291
+ ${chalk.gray("# Scaffold from inline JSON manifest")}
292
+ ${chalk.green("$")} create-lumen my-app -m '{"manifestVersion":2,"framework":{"name":"react","variant":"vite"},...}'
293
+ `);
294
+ }
package/src/css.js CHANGED
@@ -9,12 +9,13 @@ export async function setupCssFramework({
9
9
  cssFramework,
10
10
  architecture,
11
11
  ext,
12
- pkg,
12
+ pkg: _pkg,
13
+ framework = "vite",
13
14
  }) {
14
15
  process.chdir(projectPath);
15
16
  const cssDir = path.join(templatesDir, "css");
16
17
  // Feature-based main.* imports ./shared/styles/globals.css (or main.css when
17
- // framework is "none"); component-based imports ./styles/globals.css (or
18
+ // framework is "none"); type-based imports ./styles/globals.css (or
18
19
  // main.css). The chosen framework's styles land in the file the architecture
19
20
  // actually imports.
20
21
  const stylesDir =
@@ -31,13 +32,23 @@ export async function setupCssFramework({
31
32
  }
32
33
 
33
34
  if (cssFramework === "tailwind") {
34
- const viteConfigFile = `vite.config.${language === "ts" ? "ts" : "js"}`;
35
- const viteConfigTarget = path.join(projectPath, viteConfigFile);
36
- const viteConfigContent = await fsp.readFile(
37
- path.join(cssDir, "tailwind", viteConfigFile),
38
- "utf8"
39
- );
40
- await fsp.writeFile(viteConfigTarget, viteConfigContent, "utf8");
35
+ if (framework === "next") {
36
+ const postcssConfigFile = "postcss.config.mjs";
37
+ const postcssConfigTarget = path.join(projectPath, postcssConfigFile);
38
+ const postcssConfigContent = await fsp.readFile(
39
+ path.join(cssDir, "tailwind", postcssConfigFile),
40
+ "utf8"
41
+ );
42
+ await fsp.writeFile(postcssConfigTarget, postcssConfigContent, "utf8");
43
+ } else {
44
+ const viteConfigFile = `vite.config.${language === "ts" ? "ts" : "js"}`;
45
+ const viteConfigTarget = path.join(projectPath, viteConfigFile);
46
+ const viteConfigContent = await fsp.readFile(
47
+ path.join(cssDir, "tailwind", viteConfigFile),
48
+ "utf8"
49
+ );
50
+ await fsp.writeFile(viteConfigTarget, viteConfigContent, "utf8");
51
+ }
41
52
 
42
53
  const globalsContent = await fsp.readFile(
43
54
  path.join(cssDir, "tailwind", "src", "globals.css"),
@@ -44,7 +44,11 @@ export function computeDeps(responses) {
44
44
 
45
45
  // CSS framework
46
46
  if (responses.cssFramework === "tailwind") {
47
- devDeps.push("tailwindcss", "@tailwindcss/vite");
47
+ if (responses.framework === "next") {
48
+ devDeps.push("tailwindcss", "@tailwindcss/postcss", "postcss");
49
+ } else {
50
+ devDeps.push("tailwindcss", "@tailwindcss/vite");
51
+ }
48
52
  deps.push("clsx", "tailwind-merge");
49
53
  } else if (responses.cssFramework === "bootstrap") {
50
54
  deps.push("bootstrap", "react-bootstrap");
@@ -98,11 +102,13 @@ export function computeDeps(responses) {
98
102
 
99
103
  // Linter
100
104
  if (responses.linter === "eslint") {
101
- devDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
102
- if (responses.language === "ts") {
103
- devDeps.push("typescript-eslint");
104
- // ESLint >= 10 loads TS config files (eslint.config.ts) via jiti.
105
- devDeps.push("jiti");
105
+ if (responses.framework === "next") {
106
+ devDeps.push("eslint", "eslint-config-next");
107
+ } else {
108
+ devDeps.push("eslint", "@eslint/js", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", "globals");
109
+ if (responses.language === "ts") {
110
+ devDeps.push("typescript-eslint", "jiti");
111
+ }
106
112
  }
107
113
  } else if (responses.linter === "oxlint") {
108
114
  devDeps.push("oxlint");
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Capability declaration model for create-lumen v2 generator engine.
3
+ * Decouples option compatibility from hardcoded generator checks.
4
+ * Covered under create-lumen#25 (ADR 0001).
5
+ */
6
+
7
+ export const CAPABILITIES = {
8
+ CLIENT_ROUTING: "client-routing",
9
+ FILESYSTEM_ROUTING: "filesystem-routing",
10
+ SERVER_COMPONENTS: "server-components",
11
+ CLIENT_COMPONENTS: "client-components",
12
+ ROUTE_HANDLERS: "route-handlers",
13
+ API_ROUTES: "api-routes",
14
+ ADAPTERS: "adapters",
15
+ BUNDLER_SELECTION: "bundler-selection",
16
+ REACT_COMPILER: "react-compiler",
17
+ NEXT_FONT: "next-font",
18
+ NEXT_IMAGE: "next-image",
19
+ SPA_FALLBACK: "spa-fallback",
20
+ AGENT_DOCS: "agent-docs",
21
+ };
22
+
23
+ export const FRAMEWORK_CAPABILITIES = {
24
+ "react:vite": {
25
+ framework: { name: "react", variant: "vite" },
26
+ capabilities: [
27
+ CAPABILITIES.CLIENT_ROUTING,
28
+ CAPABILITIES.CLIENT_COMPONENTS,
29
+ CAPABILITIES.SPA_FALLBACK,
30
+ CAPABILITIES.AGENT_DOCS,
31
+ ],
32
+ allowedArchitectures: ["feature-based", "type-based", "none"],
33
+ allowedStyling: ["tailwind", "bootstrap", "none"],
34
+ allowedUiKits: ["shadcn", "none"],
35
+ allowedLanguages: ["ts", "js"],
36
+ allowedLinters: ["eslint", "oxlint", "biome", "none"],
37
+ allowedFormatters: ["prettier", "oxfmt", "none"],
38
+ allowedBundlers: [],
39
+ allowedAdapters: [],
40
+ supportsRouterPrompt: true,
41
+ },
42
+ "next:app-router": {
43
+ framework: { name: "next", variant: "app-router" },
44
+ capabilities: [
45
+ CAPABILITIES.FILESYSTEM_ROUTING,
46
+ CAPABILITIES.SERVER_COMPONENTS,
47
+ CAPABILITIES.CLIENT_COMPONENTS,
48
+ CAPABILITIES.ROUTE_HANDLERS,
49
+ CAPABILITIES.ADAPTERS,
50
+ CAPABILITIES.BUNDLER_SELECTION,
51
+ CAPABILITIES.REACT_COMPILER,
52
+ CAPABILITIES.NEXT_FONT,
53
+ CAPABILITIES.NEXT_IMAGE,
54
+ CAPABILITIES.AGENT_DOCS,
55
+ ],
56
+ allowedArchitectures: ["feature-based", "hybrid", "none"],
57
+ allowedStyling: ["tailwind", "none"],
58
+ allowedUiKits: ["shadcn", "none"],
59
+ allowedLanguages: ["ts", "js"],
60
+ allowedLinters: ["eslint", "biome", "none"],
61
+ allowedFormatters: ["prettier", "none"],
62
+ allowedBundlers: ["turbopack", "webpack"],
63
+ allowedAdapters: ["node", "vercel", "cloudflare", "static"],
64
+ supportsRouterPrompt: false, // filesystem routing is built-in
65
+ },
66
+ "next:pages-router": {
67
+ framework: { name: "next", variant: "pages-router" },
68
+ capabilities: [
69
+ CAPABILITIES.FILESYSTEM_ROUTING,
70
+ CAPABILITIES.CLIENT_COMPONENTS,
71
+ CAPABILITIES.API_ROUTES,
72
+ CAPABILITIES.ADAPTERS,
73
+ CAPABILITIES.BUNDLER_SELECTION,
74
+ CAPABILITIES.NEXT_FONT,
75
+ CAPABILITIES.NEXT_IMAGE,
76
+ CAPABILITIES.AGENT_DOCS,
77
+ ],
78
+ allowedArchitectures: ["feature-based", "hybrid", "none"],
79
+ allowedStyling: ["tailwind", "bootstrap", "none"],
80
+ allowedUiKits: ["shadcn", "none"],
81
+ allowedLanguages: ["ts", "js"],
82
+ allowedLinters: ["eslint", "biome", "none"],
83
+ allowedFormatters: ["prettier", "none"],
84
+ allowedBundlers: ["turbopack", "webpack"],
85
+ allowedAdapters: ["node", "vercel", "cloudflare", "static"],
86
+ supportsRouterPrompt: false, // filesystem routing is built-in
87
+ },
88
+ };
89
+
90
+ /**
91
+ * Get canonical framework lookup key.
92
+ * @param {string} name
93
+ * @param {string} variant
94
+ * @returns {string}
95
+ */
96
+ export function getFrameworkKey(name, variant) {
97
+ return `${name}:${variant}`;
98
+ }
99
+
100
+ /**
101
+ * Retrieve capability descriptor for a framework/variant pair.
102
+ * @param {{ name: string, variant: string }} framework
103
+ * @returns {object | null}
104
+ */
105
+ export function getFrameworkDescriptor(framework) {
106
+ if (!framework || !framework.name || !framework.variant) return null;
107
+ const key = getFrameworkKey(framework.name, framework.variant);
108
+ return FRAMEWORK_CAPABILITIES[key] || null;
109
+ }
110
+
111
+ /**
112
+ * Check if a framework has a declared capability.
113
+ * @param {{ name: string, variant: string }} framework
114
+ * @param {string} capability
115
+ * @returns {boolean}
116
+ */
117
+ export function hasCapability(framework, capability) {
118
+ const desc = getFrameworkDescriptor(framework);
119
+ if (!desc) return false;
120
+ return desc.capabilities.includes(capability);
121
+ }
122
+
123
+ /**
124
+ * Check whether an option value is compatible with the framework's capabilities.
125
+ * @param {{ name: string, variant: string }} framework
126
+ * @param {string} optionKey
127
+ * @param {*} optionValue
128
+ * @returns {boolean}
129
+ */
130
+ export function isOptionCompatible(framework, optionKey, optionValue) {
131
+ const desc = getFrameworkDescriptor(framework);
132
+ if (!desc) return false;
133
+
134
+ switch (optionKey) {
135
+ case "architecture":
136
+ return desc.allowedArchitectures.includes(optionValue);
137
+ case "styling":
138
+ case "cssFramework":
139
+ return desc.allowedStyling.includes(optionValue);
140
+ case "uiKit":
141
+ case "ui":
142
+ return desc.allowedUiKits.includes(optionValue);
143
+ case "language":
144
+ return desc.allowedLanguages.includes(optionValue);
145
+ case "linter":
146
+ return desc.allowedLinters.includes(optionValue);
147
+ case "formatter":
148
+ return desc.allowedFormatters.includes(optionValue);
149
+ case "bundler":
150
+ return desc.allowedBundlers.includes(optionValue);
151
+ case "adapter":
152
+ return desc.allowedAdapters.includes(optionValue);
153
+ case "router":
154
+ // If framework has filesystem routing, external router options (true) are incompatible
155
+ if (hasCapability(framework, CAPABILITIES.FILESYSTEM_ROUTING)) {
156
+ return optionValue === false || optionValue === "none";
157
+ }
158
+ return true;
159
+ default:
160
+ return true;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Return all compatible option domains for a framework.
166
+ * @param {{ name: string, variant: string }} framework
167
+ * @returns {object | null}
168
+ */
169
+ export function getCompatibleOptions(framework) {
170
+ const desc = getFrameworkDescriptor(framework);
171
+ if (!desc) return null;
172
+
173
+ return {
174
+ capabilities: [...desc.capabilities],
175
+ architectures: [...desc.allowedArchitectures],
176
+ styling: [...desc.allowedStyling],
177
+ uiKits: [...desc.allowedUiKits],
178
+ languages: [...desc.allowedLanguages],
179
+ linters: [...desc.allowedLinters],
180
+ formatters: [...desc.allowedFormatters],
181
+ bundlers: [...desc.allowedBundlers],
182
+ adapters: [...desc.allowedAdapters],
183
+ supportsRouterPrompt: desc.supportsRouterPrompt,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Validate a set of options against framework capabilities.
189
+ * Returns { valid: true } or { valid: false, errors: string[] }
190
+ * @param {{ name: string, variant: string }} framework
191
+ * @param {object} options
192
+ * @returns {{ valid: boolean, errors: string[] }}
193
+ */
194
+ export function validateCompatibility(framework, options = {}) {
195
+ const desc = getFrameworkDescriptor(framework);
196
+ if (!desc) {
197
+ return {
198
+ valid: false,
199
+ errors: [`Unsupported framework "${framework?.name}:${framework?.variant}"`],
200
+ };
201
+ }
202
+
203
+ const errors = [];
204
+
205
+ for (const [key, val] of Object.entries(options)) {
206
+ if (val === undefined || val === null) continue;
207
+ if (!isOptionCompatible(framework, key, val)) {
208
+ errors.push(
209
+ `Option "${key}" with value "${val}" is incompatible with framework "${framework.name}:${framework.variant}"`
210
+ );
211
+ }
212
+ }
213
+
214
+ return {
215
+ valid: errors.length === 0,
216
+ errors,
217
+ };
218
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./capabilities.js";
2
+ export * from "./providers/base-provider.js";