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.
- package/README.md +56 -9
- package/bin/cli.js +18 -4
- package/package.json +14 -4
- package/schema/fixtures/index.js +62 -0
- package/schema/fixtures/v2/invalid/invalid-manifest-version.json +33 -0
- package/schema/fixtures/v2/invalid/malformed-paths.json +33 -0
- package/schema/fixtures/v2/invalid/missing-required-fields.json +17 -0
- package/schema/fixtures/v2/invalid/next-type-based.json +33 -0
- package/schema/fixtures/v2/invalid/next-vite.json +33 -0
- package/schema/fixtures/v2/invalid/react-bundler.json +34 -0
- package/schema/fixtures/v2/invalid/react-hybrid.json +33 -0
- package/schema/fixtures/v2/invalid/react-variant-mismatch.json +33 -0
- package/schema/fixtures/v2/invalid/shadcn-without-tailwind.json +33 -0
- package/schema/fixtures/v2/invalid/v1-flat-config.json +11 -0
- package/schema/fixtures/v2/valid/full-kitchen-sink.json +35 -0
- package/schema/fixtures/v2/valid/next-app-router-feature-based.json +35 -0
- package/schema/fixtures/v2/valid/next-app-router-hybrid.json +35 -0
- package/schema/fixtures/v2/valid/next-app-router-none.json +33 -0
- package/schema/fixtures/v2/valid/next-pages-router-feature-based.json +35 -0
- package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +35 -0
- package/schema/fixtures/v2/valid/next-pages-router-none.json +33 -0
- package/schema/fixtures/v2/valid/react-vite-feature-based.json +33 -0
- package/schema/fixtures/v2/valid/react-vite-none.json +33 -0
- package/schema/fixtures/v2/valid/react-vite-type-based.json +33 -0
- package/schema/lumen.config.v2.json +216 -0
- package/src/cli-args.js +294 -0
- package/src/css.js +20 -9
- package/src/dependencies.js +12 -6
- package/src/engine/capabilities.js +218 -0
- package/src/engine/index.js +2 -0
- package/src/engine/providers/base-provider.js +219 -0
- package/src/injector.js +26 -13
- package/src/main.js +52 -10
- package/src/manifest/emit.js +120 -0
- package/src/manifest/index.js +11 -0
- package/src/manifest/paths.js +128 -0
- package/src/manifest/schema.js +212 -0
- package/src/prompts.js +26 -6
- package/templates/conditional/linting/eslint/next/eslint.config.mjs +16 -0
- package/templates/css/tailwind/postcss.config.mjs +5 -0
- /package/templates/architectures/{component-based → type-based}/main.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/main.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/App.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/App.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/components/common/Button.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/components/common/Button.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/components/common/index.js +0 -0
- /package/templates/architectures/{component-based → type-based}/src/components/common/index.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/components/form/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/config/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/hooks/useApp.js +0 -0
- /package/templates/architectures/{component-based → type-based}/src/hooks/useApp.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/hooks/useLocalStorage.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/hooks/useLocalStorage.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/pages/Home/index.js +0 -0
- /package/templates/architectures/{component-based → type-based}/src/pages/Home/index.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/providers/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/providers/AppProvider.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/providers/AppProvider.tsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/providers/appcontext.js +0 -0
- /package/templates/architectures/{component-based → type-based}/src/providers/appcontext.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/services/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/store/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/styles/.gitkeep +0 -0
- /package/templates/architectures/{component-based → type-based}/src/types/index.js +0 -0
- /package/templates/architectures/{component-based → type-based}/src/types/index.ts +0 -0
- /package/templates/architectures/{component-based → type-based}/src/utils/cn.jsx +0 -0
- /package/templates/architectures/{component-based → type-based}/src/utils/cn.ts +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/config/axios.config.js +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/config/axios.config.ts +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/services/axios.client.js +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/services/axios.client.ts +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/services/user.service.js +0 -0
- /package/templates/conditional/axios/{component-based → type-based}/src/services/user.service.ts +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/config/api.config.js +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/config/api.config.ts +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/services/api.client.js +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/services/api.client.ts +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/services/user.service.js +0 -0
- /package/templates/conditional/fetch/{component-based → type-based}/src/services/user.service.ts +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/bootstrap/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/bootstrap/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/tailwind/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/huge/{component-based → type-based}/tailwind/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/bootstrap/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/bootstrap/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/tailwind/src/pages/Home/Home.jsx +0 -0
- /package/templates/conditional/icons/lucide/{component-based → type-based}/tailwind/src/pages/Home/Home.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/bootstrap/src/layouts/MainLayout.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/bootstrap/src/layouts/MainLayout.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/App.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/App.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/guards/AuthGuard.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/guards/AuthGuard.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/guards/GuestGuard.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/guards/GuestGuard.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/index.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/src/router/index.tsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/tailwind/src/layouts/MainLayout.jsx +0 -0
- /package/templates/conditional/router/{component-based → type-based}/tailwind/src/layouts/MainLayout.tsx +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/pages/Home/counterSlice.jsx +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/pages/Home/counterSlice.ts +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/providers/StoreProvider.jsx +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/providers/StoreProvider.tsx +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/store/hooks.js +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/store/hooks.ts +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/store/index.js +0 -0
- /package/templates/conditional/state/redux/{component-based → type-based}/src/store/index.ts +0 -0
- /package/templates/conditional/state/zustand/{component-based → type-based}/src/store/useCounterStore.jsx +0 -0
- /package/templates/conditional/state/zustand/{component-based → type-based}/src/store/useCounterStore.ts +0 -0
- /package/templates/conditional/testing/jest/{component-based → type-based}/src/__tests__/App.test.jsx +0 -0
- /package/templates/conditional/testing/jest/{component-based → type-based}/src/__tests__/App.test.tsx +0 -0
- /package/templates/conditional/testing/vitest/{component-based → type-based}/src/__tests__/App.test.jsx +0 -0
- /package/templates/conditional/testing/vitest/{component-based → type-based}/src/__tests__/App.test.tsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/components/common/Button.jsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/components/common/Button.tsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
- /package/templates/css/component-styles/bootstrap/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/components/common/Button.jsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/components/common/Button.tsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/layouts/MainLayout.jsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/layouts/MainLayout.tsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/pages/Home/Home.jsx +0 -0
- /package/templates/css/component-styles/tailwind/{component-based → type-based}/src/pages/Home/Home.tsx +0 -0
package/src/cli-args.js
ADDED
|
@@ -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");
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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"),
|
package/src/dependencies.js
CHANGED
|
@@ -44,7 +44,11 @@ export function computeDeps(responses) {
|
|
|
44
44
|
|
|
45
45
|
// CSS framework
|
|
46
46
|
if (responses.cssFramework === "tailwind") {
|
|
47
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}
|