create-lumen 2.0.0-beta.2 → 2.0.0-beta.4
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 +23 -22
- package/bin/cli.js +1 -6
- package/package.json +2 -2
- package/schema/fixtures/v2/valid/next-app-router-feature-based.json +1 -1
- package/schema/fixtures/v2/valid/next-app-router-none.json +1 -1
- package/schema/fixtures/v2/valid/next-pages-router-feature-based.json +1 -1
- package/schema/fixtures/v2/valid/next-pages-router-none.json +1 -1
- package/schema/lumen.config.v2.json +2 -3
- package/src/cleanup.js +21 -5
- package/src/cli-args.js +53 -2
- package/src/configure.js +36 -8
- package/src/css.js +17 -7
- package/src/dependencies.js +9 -2
- package/src/engine/capabilities.js +2 -2
- package/src/engine/composition.js +48 -10
- package/src/env.js +149 -12
- package/src/format.js +14 -6
- package/src/injector.js +66 -14
- package/src/main.js +57 -12
- package/src/manifest/emit.js +23 -7
- package/src/manifest/paths.js +1 -25
- package/src/manifest/schema.js +3 -3
- package/src/prompts.js +25 -8
- package/src/readme.js +0 -16
- package/src/utils/dev-server.js +40 -8
- package/templates/.env.example +1 -1
- package/templates/architectures/feature-based/scripts/create-feature.mjs +2 -2
- package/templates/conditional/linting/biome/biome.json +17 -11
- package/templates/conditional/linting/eslint/next/eslint.config.mjs +2 -11
- package/templates/conditional/testing/vitest/next/vitest.config.js +1 -1
- package/templates/conditional/testing/vitest/next/vitest.config.ts +1 -1
- package/templates/css/tailwind/postcss.config.mjs +3 -1
- package/schema/fixtures/v2/valid/next-app-router-hybrid.json +0 -35
- package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +0 -35
- package/templates/architectures/hybrid/main.jsx +0 -10
- package/templates/architectures/hybrid/main.tsx +0 -10
- package/templates/architectures/hybrid/src/App.jsx +0 -20
- package/templates/architectures/hybrid/src/App.tsx +0 -20
- package/templates/architectures/hybrid/src/features/.gitkeep +0 -1
- package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +0 -31
- package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +0 -36
- package/templates/architectures/hybrid/src/shared/components/ui/index.js +0 -1
- package/templates/architectures/hybrid/src/shared/components/ui/index.ts +0 -1
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +0 -28
- package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +0 -28
- package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
- package/templates/architectures/hybrid/src/shared/types/index.js +0 -20
- package/templates/architectures/hybrid/src/shared/types/index.ts +0 -16
- package/templates/architectures/hybrid/src/shared/utils/cn.jsx +0 -3
- package/templates/architectures/hybrid/src/shared/utils/cn.ts +0 -5
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +0 -11
- package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +0 -11
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +0 -10
- package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.tsx +0 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-lumen
|
|
2
2
|
|
|
3
|
-
A scaffolder that generates production-ready **React + Vite** or **Next.js** projects (App Router / Pages Router), with a choice of architecture: feature-based, type-based,
|
|
3
|
+
A scaffolder that generates production-ready **React + Vite** or **Next.js** projects (App Router / Pages Router), with a choice of architecture: feature-based, type-based, or none.
|
|
4
4
|
|
|
5
5
|
Note: this repository is the CLI scaffolder itself, not a generated app. Generated projects live in the target folder you create.
|
|
6
6
|
|
|
@@ -81,24 +81,27 @@ create-lumen my-app # after npm link
|
|
|
81
81
|
## Features
|
|
82
82
|
|
|
83
83
|
- **Two frameworks:** React + Vite (SPA) or Next.js (App Router / Pages Router) — selected interactively or via `-f`
|
|
84
|
-
- Architecture choice
|
|
85
|
-
- TypeScript or JavaScript output
|
|
86
|
-
- CSS frameworks
|
|
87
|
-
- **UI kit: shadcn/ui** (gated to Tailwind; primitives + `cn` helper land in
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
84
|
+
- **Architecture choice:** feature-based, type-based, or none (scoped per framework: React + Vite supports `feature-based`, `type-based`, and `none`; Next.js supports `feature-based` and `none`)
|
|
85
|
+
- **TypeScript or JavaScript output**
|
|
86
|
+
- **CSS frameworks:** Tailwind CSS v4 (CSS-first `@theme`, v3 dropped) or none
|
|
87
|
+
- **UI kit: shadcn/ui** (gated to Tailwind; primitives + `cn` helper land in mapped `paths.ui`; official `@lumen` registry configured in `components.json` for Shadcn Registry v2)
|
|
88
|
+
- **Fullstack Next.js database scaffolding:** Prisma ORM with SQLite, PostgreSQL, and Supabase (connection pooler port 6543, direct session port 5432, `.env.example`, client singleton)
|
|
89
|
+
- **Next.js Docker containerization:** multi-stage production Dockerfile targeting Next.js `output: "standalone"`, `.dockerignore`, `docker-compose.yml`
|
|
90
|
+
- **Next.js Route Groups & Auth Proxy:** `--route-groups` preset scaffolds logical App Router directory segments (`(public)`, `(auth)`, `(dashboard)`) with edge auth middleware
|
|
91
|
+
- **Optional state management:** Zustand or Redux Toolkit
|
|
92
|
+
- **Optional router:** React Router (Vite only; Next.js uses filesystem routing)
|
|
93
|
+
- **Testing:** Vitest or Jest (Next.js defaults to none until explicitly selected; zero-babel Next SWC integration)
|
|
91
94
|
- **Next.js-native overlays:** server/client boundary demos + `no-server-imports-in-client` ESLint rule, `next/font` (Geist), `next/image`, filesystem routing examples, route handlers (`route.ts`) / API routes (`pages/api`)
|
|
92
95
|
- **Next.js options:** `bundler: turbopack|webpack`, `adapter: node|vercel|cloudflare|static` (`next.config` overlays), `reactCompiler`, `linter: biome` — all validated against the capability model
|
|
93
|
-
- Linting
|
|
94
|
-
- Code formatter
|
|
95
|
-
- Manifest v2
|
|
96
|
-
- Explicit path mapping
|
|
97
|
-
- Optional Axios setup and icon libraries
|
|
98
|
-
- Auto `git init
|
|
96
|
+
- **Linting:** ESLint (default flat config), Oxlint, or Biome
|
|
97
|
+
- **Code formatter & scripts:** Prettier, Oxfmt, or none (with Biome handling lint + format via `biome check --write .`); auto-wired `format`, `format:check`, and `test:watch` scripts in `package.json`
|
|
98
|
+
- **Manifest v2:** Emits a deterministic `lumen.config.json` validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration
|
|
99
|
+
- **Explicit path mapping:** Canonical target directories declared for `lumen-cli` code generation (`paths.pages` resolves to `src/app` or `src/pages` in Next.js)
|
|
100
|
+
- **Optional Axios setup and icon libraries**
|
|
101
|
+
- **Auto `git init`**, generated `README` (with a project description reflecting the chosen tooling) and `LICENSE`
|
|
99
102
|
- `.env.example` scaffolded, and the generated `.gitignore` excludes env files (`.env`, `.env.*`) while keeping `.env.example`
|
|
100
|
-
- `@/` path alias configured (`tsconfig`/`jsconfig` + Vite)
|
|
101
|
-
- **Agent docs:**
|
|
103
|
+
- `@/` path alias configured (`tsconfig`/`jsconfig` + Vite/Next)
|
|
104
|
+
- **Agent docs:** deterministic root `AGENTS.md` referencing `lumen.config.json` as the Single Source of Truth (SSOT), providing AI agents with architecture rules, 3-tier component taxonomy, and harness commands
|
|
102
105
|
|
|
103
106
|
Your selected options are cached and offered as defaults on subsequent runs.
|
|
104
107
|
|
|
@@ -214,7 +217,7 @@ src/
|
|
|
214
217
|
### Manifest v2 Schema (v2.0.0-alpha.1+)
|
|
215
218
|
|
|
216
219
|
The manifest now includes:
|
|
217
|
-
- **`architecture.preset`**: The base architecture (`feature-based`, `type-based`, `
|
|
220
|
+
- **`architecture.preset`**: The base architecture (`feature-based`, `type-based`, `none`).
|
|
218
221
|
- **`architecture.validation`**: Validation mode for `lumen doctor` (`strict`, `relaxed`, `none`).
|
|
219
222
|
- **`harness.commands`**: An array of commands to run with `lumen harness` (e.g., lint, format, types, test, build), each with a name, shell command, required flag, and description.
|
|
220
223
|
|
|
@@ -231,11 +234,9 @@ The manifest now includes:
|
|
|
231
234
|
|
|
232
235
|
### Agent Interoperability
|
|
233
236
|
|
|
234
|
-
Lumen is designed for both
|
|
235
|
-
- `
|
|
236
|
-
-
|
|
237
|
-
- `conventions.md`: A markdown file documenting project-specific conventions (e.g., component naming, file organization).
|
|
238
|
-
These files enable agents to understand the project without parsing the source code.
|
|
237
|
+
Lumen is designed for both human developers and autonomous AI agents:
|
|
238
|
+
- **`AGENTS.md`**: Scaffolds a deterministic root-level guide providing AI agents with architecture rules, 3-tier component taxonomy (`paths.ui`, `paths.components`, and feature components), harness commands, and Next.js guidelines (RSC vs `"use client"`).
|
|
239
|
+
- **`lumen.config.json`**: Functions as the single source of truth (SSOT) defining canonical target paths (`paths.features`, `paths.pages`, `paths.ui`, etc.), tooling configurations, and architecture constraints without duplicating metadata into legacy `.lumen/` directories.
|
|
239
240
|
|
|
240
241
|
## Roadmap & Planning
|
|
241
242
|
|
package/bin/cli.js
CHANGED
|
@@ -32,12 +32,7 @@ console.log(
|
|
|
32
32
|
const { main } = await import("@/main.js");
|
|
33
33
|
|
|
34
34
|
main({
|
|
35
|
-
|
|
36
|
-
projectName: cliArgs.projectName,
|
|
37
|
-
manifest: cliArgs.manifest,
|
|
38
|
-
template: cliArgs.template,
|
|
39
|
-
framework: cliArgs.framework,
|
|
40
|
-
arch: cliArgs.arch,
|
|
35
|
+
...cliArgs,
|
|
41
36
|
}).catch((e) => {
|
|
42
37
|
console.error(chalk.red("\nError:"), e.message || e);
|
|
43
38
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lumen",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
4
|
-
"description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based,
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
|
+
"description": "Scaffold a React + Vite or Next.js project with architecture choice: feature-based, type-based, or none.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-lumen": "bin/cli.js"
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
"enum": [
|
|
80
80
|
"feature-based",
|
|
81
81
|
"type-based",
|
|
82
|
-
"hybrid",
|
|
83
82
|
"none"
|
|
84
83
|
]
|
|
85
84
|
},
|
|
@@ -88,7 +87,6 @@
|
|
|
88
87
|
"enum": [
|
|
89
88
|
"feature-based",
|
|
90
89
|
"type-based",
|
|
91
|
-
"hybrid",
|
|
92
90
|
"none"
|
|
93
91
|
]
|
|
94
92
|
},
|
|
@@ -261,7 +259,8 @@
|
|
|
261
259
|
"default": "postgresql",
|
|
262
260
|
"type": "string",
|
|
263
261
|
"enum": [
|
|
264
|
-
"postgresql"
|
|
262
|
+
"postgresql",
|
|
263
|
+
"supabase"
|
|
265
264
|
]
|
|
266
265
|
}
|
|
267
266
|
},
|
package/src/cleanup.js
CHANGED
|
@@ -16,10 +16,26 @@ export async function cleanupBoilerplate(projectPath, responses = {}) {
|
|
|
16
16
|
(await fsp.stat(path.join(projectPath, "next.config.js")).catch(() => false));
|
|
17
17
|
|
|
18
18
|
if (isNext) {
|
|
19
|
-
await deleteIfExists(`${projectPath}/src/
|
|
20
|
-
await deleteIfExists(`${projectPath}/src/
|
|
21
|
-
await deleteIfExists(`${projectPath}/src/
|
|
22
|
-
await deleteIfExists(`${projectPath}/src/
|
|
23
|
-
await deleteIfExists(`${projectPath}/src/
|
|
19
|
+
await deleteIfExists(`${projectPath}/src/main.tsx`);
|
|
20
|
+
await deleteIfExists(`${projectPath}/src/main.jsx`);
|
|
21
|
+
await deleteIfExists(`${projectPath}/src/App.tsx`);
|
|
22
|
+
await deleteIfExists(`${projectPath}/src/App.jsx`);
|
|
23
|
+
await deleteIfExists(`${projectPath}/src/App.css`);
|
|
24
|
+
await deleteIfExists(`${projectPath}/src/shared/styles`);
|
|
25
|
+
|
|
26
|
+
const isPagesRouter =
|
|
27
|
+
responses?.framework?.variant === "pages-router" ||
|
|
28
|
+
responses?.frameworkVariant === "pages-router" ||
|
|
29
|
+
(await fsp.stat(path.join(projectPath, "src", "pages")).catch(() => false));
|
|
30
|
+
|
|
31
|
+
if (isPagesRouter) {
|
|
32
|
+
await deleteIfExists(`${projectPath}/src/app`);
|
|
33
|
+
} else {
|
|
34
|
+
await deleteIfExists(`${projectPath}/src/app/App.tsx`);
|
|
35
|
+
await deleteIfExists(`${projectPath}/src/app/App.jsx`);
|
|
36
|
+
await deleteIfExists(`${projectPath}/src/app/hooks`);
|
|
37
|
+
await deleteIfExists(`${projectPath}/src/app/contexts`);
|
|
38
|
+
await deleteIfExists(`${projectPath}/src/app/providers`);
|
|
39
|
+
}
|
|
24
40
|
}
|
|
25
41
|
}
|
package/src/cli-args.js
CHANGED
|
@@ -282,6 +282,10 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
282
282
|
adapter: { type: "string" },
|
|
283
283
|
database: { type: "string" },
|
|
284
284
|
db: { type: "string" },
|
|
285
|
+
"db-provider": { type: "string" },
|
|
286
|
+
dbProvider: { type: "string" },
|
|
287
|
+
"database-provider": { type: "string" },
|
|
288
|
+
databaseProvider: { type: "string" },
|
|
285
289
|
"react-compiler": { type: "boolean" },
|
|
286
290
|
reactCompiler: { type: "boolean" },
|
|
287
291
|
"route-groups": { type: "boolean" },
|
|
@@ -289,7 +293,18 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
289
293
|
"agent-docs": { type: "boolean" },
|
|
290
294
|
agentDocs: { type: "boolean" },
|
|
291
295
|
docker: { type: "boolean" },
|
|
296
|
+
language: { type: "string", short: "l" },
|
|
297
|
+
lang: { type: "string" },
|
|
298
|
+
css: { type: "string", short: "c" },
|
|
299
|
+
styling: { type: "string" },
|
|
300
|
+
"css-framework": { type: "string" },
|
|
301
|
+
cssFramework: { type: "string" },
|
|
302
|
+
ui: { type: "string", short: "u" },
|
|
303
|
+
uiKit: { type: "string" },
|
|
304
|
+
"ui-kit": { type: "string" },
|
|
305
|
+
testing: { type: "string" },
|
|
292
306
|
linter: { type: "string" },
|
|
307
|
+
formatter: { type: "string" },
|
|
293
308
|
help: { type: "boolean", short: "h", default: false },
|
|
294
309
|
version: { type: "boolean", short: "v", default: false },
|
|
295
310
|
},
|
|
@@ -312,11 +327,30 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
312
327
|
if (database) {
|
|
313
328
|
database = database.trim().toLowerCase();
|
|
314
329
|
}
|
|
330
|
+
let databaseProvider =
|
|
331
|
+
values["db-provider"] ||
|
|
332
|
+
values.dbProvider ||
|
|
333
|
+
values["database-provider"] ||
|
|
334
|
+
values.databaseProvider ||
|
|
335
|
+
null;
|
|
336
|
+
if (databaseProvider) {
|
|
337
|
+
databaseProvider = databaseProvider.trim().toLowerCase();
|
|
338
|
+
}
|
|
315
339
|
const reactCompiler = values["react-compiler"] ?? values.reactCompiler ?? null;
|
|
316
340
|
const routeGroups = values["route-groups"] ?? values.routeGroups ?? null;
|
|
317
341
|
const agentDocs = values["agent-docs"] ?? values.agentDocs ?? null;
|
|
318
342
|
const docker = Boolean(values.docker);
|
|
343
|
+
const language = values.language || values.lang || null;
|
|
344
|
+
const cssFramework =
|
|
345
|
+
values.css ||
|
|
346
|
+
values.styling ||
|
|
347
|
+
values["css-framework"] ||
|
|
348
|
+
values.cssFramework ||
|
|
349
|
+
null;
|
|
350
|
+
const uiKit = values.ui || values.uiKit || values["ui-kit"] || null;
|
|
351
|
+
const testing = values.testing ? values.testing.trim().toLowerCase() : null;
|
|
319
352
|
const linter = values.linter ? values.linter.trim().toLowerCase() : null;
|
|
353
|
+
const formatter = values.formatter ? values.formatter.trim().toLowerCase() : null;
|
|
320
354
|
|
|
321
355
|
return {
|
|
322
356
|
projectName,
|
|
@@ -325,14 +359,20 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
325
359
|
template: values.template || null,
|
|
326
360
|
framework: values.framework ? values.framework.trim() : null,
|
|
327
361
|
arch,
|
|
362
|
+
language,
|
|
363
|
+
cssFramework,
|
|
364
|
+
uiKit,
|
|
328
365
|
bundler,
|
|
329
366
|
adapter,
|
|
330
367
|
database,
|
|
368
|
+
databaseProvider,
|
|
331
369
|
reactCompiler,
|
|
332
370
|
routeGroups,
|
|
333
371
|
agentDocs,
|
|
334
372
|
docker,
|
|
373
|
+
testing,
|
|
335
374
|
linter,
|
|
375
|
+
formatter,
|
|
336
376
|
help: Boolean(values.help),
|
|
337
377
|
version: Boolean(values.version),
|
|
338
378
|
positionals,
|
|
@@ -403,6 +443,10 @@ export function manifestToResponses(manifest, projectName) {
|
|
|
403
443
|
supportsDatabase && manifest.database?.orm
|
|
404
444
|
? manifest.database.orm
|
|
405
445
|
: "none",
|
|
446
|
+
databaseProvider:
|
|
447
|
+
supportsDatabase && manifest.database?.provider
|
|
448
|
+
? manifest.database.provider
|
|
449
|
+
: undefined,
|
|
406
450
|
architecture: manifest.architecture?.type || "feature-based",
|
|
407
451
|
language: manifest.tooling?.language || "ts",
|
|
408
452
|
cssFramework: manifest.styling?.engine || "tailwind",
|
|
@@ -433,20 +477,27 @@ export function manifestToResponses(manifest, projectName) {
|
|
|
433
477
|
*/
|
|
434
478
|
export function printHelp() {
|
|
435
479
|
console.log(`
|
|
436
|
-
${chalk.bold.cyan(" create-lumen")} — Scaffold a React + Vite project with architecture choice
|
|
480
|
+
${chalk.bold.cyan(" create-lumen")} — Scaffold a React + Vite or Next.js project with architecture choice
|
|
437
481
|
|
|
438
482
|
${chalk.bold("USAGE")}
|
|
439
483
|
${chalk.green("$")} ${chalk.bold("create-lumen")} [project-name] [options]
|
|
440
484
|
|
|
441
485
|
${chalk.bold("OPTIONS")}
|
|
442
486
|
${chalk.yellow("-y, --yes")} Quick setup with recommended defaults (TS + Tailwind + Router + ESLint + Prettier)
|
|
443
|
-
${chalk.yellow("-a, --arch")} <name> Architecture preset (feature-based, type-based, none
|
|
487
|
+
${chalk.yellow("-a, --arch")} <name> Architecture preset (feature-based, type-based, none)
|
|
488
|
+
${chalk.yellow("-l, --language")} <lang> Language (ts, js)
|
|
489
|
+
${chalk.yellow("-c, --css")} <framework> Styling engine (tailwind, none)
|
|
444
490
|
${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
|
|
445
491
|
${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
|
|
446
492
|
${chalk.yellow("-f, --framework")} <name> Target framework (react, next, react:vite, next:app-router, etc.)
|
|
493
|
+
${chalk.yellow("--testing")} <framework> Testing framework (vitest, jest, none)
|
|
494
|
+
${chalk.yellow("--linter")} <linter> Linter (eslint, oxlint, biome, none)
|
|
495
|
+
${chalk.yellow("--formatter")} <fmt> Code formatter (prettier, oxfmt, none)
|
|
447
496
|
${chalk.yellow("--db, --database")} <orm> Database ORM (prisma, none)
|
|
497
|
+
${chalk.yellow("--db-provider")} <provider> Database provider (postgresql, supabase)
|
|
448
498
|
${chalk.yellow(" --docker")} Include multi-stage Dockerfile & docker-compose for Next.js standalone
|
|
449
499
|
${chalk.yellow("--route-groups")} Include Next.js route groups preset (public, auth, private, admin)
|
|
500
|
+
${chalk.yellow("--agent-docs")} Generate root AGENTS.md referencing lumen.config.json
|
|
450
501
|
${chalk.yellow("-h, --help")} Show this help message
|
|
451
502
|
${chalk.yellow("-v, --version")} Show version number
|
|
452
503
|
|
package/src/configure.js
CHANGED
|
@@ -2,10 +2,31 @@ import { promises as fsp } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { readFileContent } from "@/utils/fs.js";
|
|
4
4
|
import { getPathAliases, getViteAliases } from "@/aliases.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { normalizeFramework } from "@/engine/capabilities.js";
|
|
6
|
+
|
|
7
|
+
export async function configureProject(projectPath, languageOrResponses, cssFramework, framework = "vite") {
|
|
8
|
+
let language = languageOrResponses;
|
|
9
|
+
let css = cssFramework;
|
|
10
|
+
let fwArg = framework;
|
|
11
|
+
if (languageOrResponses && typeof languageOrResponses === "object") {
|
|
12
|
+
language = languageOrResponses.language;
|
|
13
|
+
css = languageOrResponses.cssFramework;
|
|
14
|
+
fwArg =
|
|
15
|
+
languageOrResponses.framework || {
|
|
16
|
+
name: languageOrResponses.frameworkName,
|
|
17
|
+
variant: languageOrResponses.frameworkVariant,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const fw = normalizeFramework(fwArg);
|
|
7
21
|
const ext = language === "ts" ? "ts" : "js";
|
|
8
22
|
|
|
23
|
+
if (fw.name === "next") {
|
|
24
|
+
// Next.js ships its own tsconfig.json with next plugins and paths.
|
|
25
|
+
// Skip Vite tsconfig.app.json and vite.config mutations.
|
|
26
|
+
await configurePackageJson(projectPath, language, fw);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
// Set up tsconfig/jsconfig with the @/* path alias
|
|
10
31
|
if (language === "ts") {
|
|
11
32
|
await configureTsconfig(projectPath);
|
|
@@ -14,12 +35,12 @@ export async function configureProject(projectPath, language, cssFramework) {
|
|
|
14
35
|
}
|
|
15
36
|
|
|
16
37
|
// Add path aliases to vite.config for non-tailwind setups
|
|
17
|
-
if (
|
|
38
|
+
if (css !== "tailwind") {
|
|
18
39
|
await addPathAliasesToViteConfig(projectPath, ext);
|
|
19
40
|
}
|
|
20
41
|
|
|
21
42
|
// Add scripts to package.json
|
|
22
|
-
await configurePackageJson(projectPath, language);
|
|
43
|
+
await configurePackageJson(projectPath, language, fw);
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
async function configureTsconfig(projectPath) {
|
|
@@ -158,7 +179,8 @@ async function addPathAliasesToViteConfig(projectPath, ext) {
|
|
|
158
179
|
}
|
|
159
180
|
}
|
|
160
181
|
|
|
161
|
-
async function configurePackageJson(projectPath, language) {
|
|
182
|
+
async function configurePackageJson(projectPath, language, framework = "vite") {
|
|
183
|
+
const fw = normalizeFramework(framework);
|
|
162
184
|
const pkgPath = path.join(projectPath, "package.json");
|
|
163
185
|
try {
|
|
164
186
|
const content = await readFileContent(pkgPath);
|
|
@@ -170,9 +192,15 @@ async function configurePackageJson(projectPath, language) {
|
|
|
170
192
|
// (Next.js bases ship next dev/build/start) or merged overlay fragments
|
|
171
193
|
// (e.g. the bundler-aware dev script). React/Vite bases already carry the
|
|
172
194
|
// vite commands, so this is a no-op there too.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
if (fw.name !== "next") {
|
|
196
|
+
pkg.scripts.dev ??= "vite";
|
|
197
|
+
pkg.scripts.build ??= "vite build";
|
|
198
|
+
pkg.scripts.preview ??= "vite preview";
|
|
199
|
+
} else {
|
|
200
|
+
if (pkg.scripts.preview && pkg.scripts.preview.includes("vite")) {
|
|
201
|
+
delete pkg.scripts.preview;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
176
204
|
|
|
177
205
|
if (language === "ts") {
|
|
178
206
|
pkg.scripts.typecheck ??= "tsc -b";
|
package/src/css.js
CHANGED
|
@@ -17,7 +17,6 @@ export async function setupCssFramework({
|
|
|
17
17
|
pkg: _pkg,
|
|
18
18
|
framework = "vite",
|
|
19
19
|
}) {
|
|
20
|
-
process.chdir(projectPath);
|
|
21
20
|
const fw = normalizeFramework(framework);
|
|
22
21
|
const isFilesystemRouted = hasCapability(fw, CAPABILITIES.FILESYSTEM_ROUTING);
|
|
23
22
|
const cssDir = path.join(templatesDir, "css");
|
|
@@ -25,15 +24,26 @@ export async function setupCssFramework({
|
|
|
25
24
|
// framework is "none"); type-based imports ./styles/globals.css (or
|
|
26
25
|
// main.css). The chosen framework's styles land in the file the architecture
|
|
27
26
|
// actually imports.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
let stylesDir;
|
|
28
|
+
if (fw.name === "next") {
|
|
29
|
+
stylesDir =
|
|
30
|
+
fw.variant === "pages-router"
|
|
31
|
+
? path.join(projectPath, "src", "styles")
|
|
32
|
+
: path.join(projectPath, "src", "app");
|
|
33
|
+
} else {
|
|
34
|
+
stylesDir =
|
|
35
|
+
architecture === "feature-based"
|
|
36
|
+
? path.join(projectPath, "src", "shared", "styles")
|
|
37
|
+
: path.join(projectPath, "src", "styles");
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
// Vanilla CSS gets a stylesheet named main.css; tailwind keeps
|
|
34
41
|
// globals.css (its idiomatic name — it injects framework directives).
|
|
35
42
|
const mainFileName = cssFramework === "none" ? "main.css" : "globals.css";
|
|
36
43
|
|
|
44
|
+
// Ensure the styles dir exists
|
|
45
|
+
await fsp.mkdir(stylesDir, { recursive: true });
|
|
46
|
+
|
|
37
47
|
async function writeCss(content, fileName) {
|
|
38
48
|
await fsp.writeFile(path.join(stylesDir, fileName), content, "utf8");
|
|
39
49
|
}
|
|
@@ -100,11 +110,11 @@ async function syncMainCssImport(projectPath, ext, architecture, mainFileName) {
|
|
|
100
110
|
try {
|
|
101
111
|
let content = await fsp.readFile(mainPath, "utf8");
|
|
102
112
|
const expected =
|
|
103
|
-
architecture === "feature-based"
|
|
113
|
+
architecture === "feature-based"
|
|
104
114
|
? `./shared/styles/${mainFileName}`
|
|
105
115
|
: `./styles/${mainFileName}`;
|
|
106
116
|
const wrong =
|
|
107
|
-
architecture === "feature-based"
|
|
117
|
+
architecture === "feature-based"
|
|
108
118
|
? /import ['"]\.\/shared\/styles\/(globals|main)\.css['"]/
|
|
109
119
|
: /import ['"]\.\/styles\/(globals|main)\.css['"]/;
|
|
110
120
|
content = content.replace(wrong, `import '${expected}'`);
|
package/src/dependencies.js
CHANGED
|
@@ -34,6 +34,13 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
34
34
|
for (const batch of devDepBatches) {
|
|
35
35
|
await installDeps(pkg, true, batch, cwd);
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
if (responses.database === "prisma") {
|
|
39
|
+
try {
|
|
40
|
+
const runner = pkg === "npm" ? "npx" : pkg;
|
|
41
|
+
await execa(runner, ["prisma", "generate"], { cwd, stdio: "pipe" });
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
// Pure dependency computation — the package list for a given matrix cell,
|
|
@@ -90,8 +97,8 @@ export function computeDeps(responses) {
|
|
|
90
97
|
|
|
91
98
|
// Database
|
|
92
99
|
if (responses.database === "prisma") {
|
|
93
|
-
deps.push("@prisma/client");
|
|
94
|
-
databaseDevDeps.push("prisma");
|
|
100
|
+
deps.push("@prisma/client@^6");
|
|
101
|
+
databaseDevDeps.push("prisma@^6");
|
|
95
102
|
if (responses.language === "ts") {
|
|
96
103
|
databaseDevDeps.push("tsx");
|
|
97
104
|
}
|
|
@@ -60,7 +60,7 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
60
60
|
CAPABILITIES.DOCKER,
|
|
61
61
|
CAPABILITIES.ROUTE_GROUPS,
|
|
62
62
|
],
|
|
63
|
-
allowedArchitectures: ["feature-based", "
|
|
63
|
+
allowedArchitectures: ["feature-based", "none"],
|
|
64
64
|
allowedStyling: ["tailwind", "none"],
|
|
65
65
|
allowedUiKits: ["shadcn", "none"],
|
|
66
66
|
allowedLanguages: ["ts", "js"],
|
|
@@ -85,7 +85,7 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
85
85
|
CAPABILITIES.DATABASE,
|
|
86
86
|
CAPABILITIES.DOCKER,
|
|
87
87
|
],
|
|
88
|
-
allowedArchitectures: ["feature-based", "
|
|
88
|
+
allowedArchitectures: ["feature-based", "none"],
|
|
89
89
|
allowedStyling: ["tailwind", "none"],
|
|
90
90
|
allowedUiKits: ["shadcn", "none"],
|
|
91
91
|
allowedLanguages: ["ts", "js"],
|
|
@@ -166,7 +166,7 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
// =========================================================================
|
|
169
|
-
// Layer 3: Architecture (feature-based / type-based /
|
|
169
|
+
// Layer 3: Architecture (feature-based / type-based / none)
|
|
170
170
|
// =========================================================================
|
|
171
171
|
const archSrcDir = path.join(templatesDir, "architectures", architecture, "src");
|
|
172
172
|
const archMainSrc = path.join(templatesDir, "architectures", architecture, `main.${ext}`);
|
|
@@ -178,6 +178,11 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
178
178
|
targetDir: "src",
|
|
179
179
|
action: async ({ projectPath }) => {
|
|
180
180
|
if (fw.name === "next") {
|
|
181
|
+
try { await fsp.rm(path.join(projectPath, "src", "main.tsx"), { force: true }); } catch {}
|
|
182
|
+
try { await fsp.rm(path.join(projectPath, "src", "main.jsx"), { force: true }); } catch {}
|
|
183
|
+
try { await fsp.rm(path.join(projectPath, "src", "App.tsx"), { force: true }); } catch {}
|
|
184
|
+
try { await fsp.rm(path.join(projectPath, "src", "App.jsx"), { force: true }); } catch {}
|
|
185
|
+
try { await fsp.rm(path.join(projectPath, "src", "App.css"), { force: true }); } catch {}
|
|
181
186
|
if (fw.variant === "pages-router") {
|
|
182
187
|
try { await fsp.rm(path.join(projectPath, "src", "app"), { recursive: true, force: true }); } catch {}
|
|
183
188
|
} else {
|
|
@@ -379,6 +384,7 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
379
384
|
"db:push": "prisma db push",
|
|
380
385
|
"db:seed": "prisma db seed",
|
|
381
386
|
"db:studio": "prisma studio",
|
|
387
|
+
postinstall: "prisma generate",
|
|
382
388
|
},
|
|
383
389
|
action: async ({ projectPath, templatesDir: tDir }) => {
|
|
384
390
|
const prismaTplDir = path.join(tDir, "conditional", "database", "prisma");
|
|
@@ -428,9 +434,14 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
428
434
|
const envExamplePath = path.join(projectPath, ".env.example");
|
|
429
435
|
const envLines = [
|
|
430
436
|
"",
|
|
431
|
-
"# Database (Prisma + PostgreSQL
|
|
432
|
-
|
|
433
|
-
'
|
|
437
|
+
"# Database (Prisma + PostgreSQL)",
|
|
438
|
+
"# Local development with PostgreSQL or Docker compose (docker compose up -d db):",
|
|
439
|
+
'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/app?schema=public"',
|
|
440
|
+
'DIRECT_URL="postgresql://postgres:postgres@localhost:5432/app?schema=public"',
|
|
441
|
+
"",
|
|
442
|
+
"# Supabase / cloud PostgreSQL with connection pooling (Transaction mode / port 6543):",
|
|
443
|
+
'# DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"',
|
|
444
|
+
'# DIRECT_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"',
|
|
434
445
|
].join("\n");
|
|
435
446
|
try {
|
|
436
447
|
let envContent = "";
|
|
@@ -452,7 +463,7 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
452
463
|
const testingDir = path.join(templatesDir, "conditional", "testing", testingFramework);
|
|
453
464
|
const testScripts =
|
|
454
465
|
testingFramework === "vitest"
|
|
455
|
-
? { test: "vitest", "test:run": "vitest run" }
|
|
466
|
+
? { test: "vitest", "test:run": "vitest run", "test:watch": "vitest" }
|
|
456
467
|
: { test: "jest", "test:watch": "jest --watch" };
|
|
457
468
|
|
|
458
469
|
fragments.push({
|
|
@@ -519,7 +530,11 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
519
530
|
try {
|
|
520
531
|
await fsp.access(testSrc);
|
|
521
532
|
const testDest = path.join(projectPath, "src", "__tests__", `${testFileName}.test.${ext}`);
|
|
522
|
-
|
|
533
|
+
let testContent = await fsp.readFile(testSrc, "utf8");
|
|
534
|
+
if (responses.routeGroups === true && fw.variant === "app-router") {
|
|
535
|
+
testContent = testContent.replace("@/app/page", "@/app/(public)/page");
|
|
536
|
+
}
|
|
537
|
+
await writeFileRecursive(testDest, testContent);
|
|
523
538
|
} catch {}
|
|
524
539
|
} else {
|
|
525
540
|
// Architecture-scoped test file
|
|
@@ -578,14 +593,24 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
578
593
|
},
|
|
579
594
|
});
|
|
580
595
|
} else if (responses.linter === "biome") {
|
|
596
|
+
const isBiomeFormatter =
|
|
597
|
+
!responses.formatter ||
|
|
598
|
+
responses.formatter === "none" ||
|
|
599
|
+
responses.formatter === "biome";
|
|
600
|
+
const biomeScripts = {
|
|
601
|
+
lint: "biome check .",
|
|
602
|
+
"lint:fix": "biome check --write .",
|
|
603
|
+
};
|
|
604
|
+
if (isBiomeFormatter) {
|
|
605
|
+
biomeScripts.format = "biome check --write .";
|
|
606
|
+
biomeScripts["format:check"] = "biome check .";
|
|
607
|
+
}
|
|
608
|
+
|
|
581
609
|
fragments.push({
|
|
582
610
|
scope: FRAGMENT_SCOPES.TOOLING,
|
|
583
611
|
category: "linter",
|
|
584
612
|
id: "tooling:linter:biome",
|
|
585
|
-
scripts:
|
|
586
|
-
lint: "biome check .",
|
|
587
|
-
"lint:fix": "biome check --write .",
|
|
588
|
-
},
|
|
613
|
+
scripts: biomeScripts,
|
|
589
614
|
action: async ({ projectPath }) => {
|
|
590
615
|
const configSrc = path.join(templatesDir, "conditional", "linting", "biome", "biome.json");
|
|
591
616
|
try {
|
|
@@ -729,6 +754,19 @@ export function resolveFragments(responses = {}, templatesDir) {
|
|
|
729
754
|
try {
|
|
730
755
|
await fsp.rm(path.join(projectPath, "src", "app", "page.tsx"), { force: true });
|
|
731
756
|
} catch {}
|
|
757
|
+
try {
|
|
758
|
+
await fsp.rm(path.join(projectPath, "src", "app", "page.module.css"), { force: true });
|
|
759
|
+
} catch {}
|
|
760
|
+
// Adjust existing test file if it imported @/app/page before route groups was applied
|
|
761
|
+
for (const testExt of ["tsx", "jsx", "ts", "js"]) {
|
|
762
|
+
const testPath = path.join(projectPath, "src", "__tests__", `page.test.${testExt}`);
|
|
763
|
+
try {
|
|
764
|
+
const content = await fsp.readFile(testPath, "utf8");
|
|
765
|
+
if (content.includes("@/app/page")) {
|
|
766
|
+
await fsp.writeFile(testPath, content.replace(/@\/app\/page/g, "@/app/(public)/page"), "utf8");
|
|
767
|
+
}
|
|
768
|
+
} catch {}
|
|
769
|
+
}
|
|
732
770
|
},
|
|
733
771
|
});
|
|
734
772
|
}
|