create-lumen 1.2.0-beta.1 → 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 -10
- package/bin/cli.js +18 -4
- package/package.json +18 -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 +27 -12
- 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
|
@@ -25,12 +25,30 @@ 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);
|
|
29
|
+
|
|
30
|
+
if (deps.length > 0) {
|
|
31
|
+
await installDeps(pkg, false, deps, cwd);
|
|
32
|
+
}
|
|
33
|
+
if (devDeps.length > 0) {
|
|
34
|
+
await installDeps(pkg, true, devDeps, cwd);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pure dependency computation — the package list for a given matrix cell,
|
|
39
|
+
// independent of any package manager or filesystem. Unit-tested directly so
|
|
40
|
+
// the conditional wiring stays verifiable without a network install.
|
|
41
|
+
export function computeDeps(responses) {
|
|
28
42
|
const deps = [];
|
|
29
43
|
const devDeps = [];
|
|
30
44
|
|
|
31
45
|
// CSS framework
|
|
32
46
|
if (responses.cssFramework === "tailwind") {
|
|
33
|
-
|
|
47
|
+
if (responses.framework === "next") {
|
|
48
|
+
devDeps.push("tailwindcss", "@tailwindcss/postcss", "postcss");
|
|
49
|
+
} else {
|
|
50
|
+
devDeps.push("tailwindcss", "@tailwindcss/vite");
|
|
51
|
+
}
|
|
34
52
|
deps.push("clsx", "tailwind-merge");
|
|
35
53
|
} else if (responses.cssFramework === "bootstrap") {
|
|
36
54
|
deps.push("bootstrap", "react-bootstrap");
|
|
@@ -84,11 +102,13 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
84
102
|
|
|
85
103
|
// Linter
|
|
86
104
|
if (responses.linter === "eslint") {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
}
|
|
92
112
|
}
|
|
93
113
|
} else if (responses.linter === "oxlint") {
|
|
94
114
|
devDeps.push("oxlint");
|
|
@@ -107,10 +127,5 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
107
127
|
// Always re-install react + react-dom for consistency
|
|
108
128
|
deps.push("react", "react-dom");
|
|
109
129
|
|
|
110
|
-
|
|
111
|
-
await installDeps(pkg, false, deps, cwd);
|
|
112
|
-
}
|
|
113
|
-
if (devDeps.length > 0) {
|
|
114
|
-
await installDeps(pkg, true, devDeps, cwd);
|
|
115
|
-
}
|
|
130
|
+
return { deps, devDeps };
|
|
116
131
|
}
|
|
@@ -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
|
+
}
|