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.
Files changed (56) hide show
  1. package/README.md +23 -22
  2. package/bin/cli.js +1 -6
  3. package/package.json +2 -2
  4. package/schema/fixtures/v2/valid/next-app-router-feature-based.json +1 -1
  5. package/schema/fixtures/v2/valid/next-app-router-none.json +1 -1
  6. package/schema/fixtures/v2/valid/next-pages-router-feature-based.json +1 -1
  7. package/schema/fixtures/v2/valid/next-pages-router-none.json +1 -1
  8. package/schema/lumen.config.v2.json +2 -3
  9. package/src/cleanup.js +21 -5
  10. package/src/cli-args.js +53 -2
  11. package/src/configure.js +36 -8
  12. package/src/css.js +17 -7
  13. package/src/dependencies.js +9 -2
  14. package/src/engine/capabilities.js +2 -2
  15. package/src/engine/composition.js +48 -10
  16. package/src/env.js +149 -12
  17. package/src/format.js +14 -6
  18. package/src/injector.js +66 -14
  19. package/src/main.js +57 -12
  20. package/src/manifest/emit.js +23 -7
  21. package/src/manifest/paths.js +1 -25
  22. package/src/manifest/schema.js +3 -3
  23. package/src/prompts.js +25 -8
  24. package/src/readme.js +0 -16
  25. package/src/utils/dev-server.js +40 -8
  26. package/templates/.env.example +1 -1
  27. package/templates/architectures/feature-based/scripts/create-feature.mjs +2 -2
  28. package/templates/conditional/linting/biome/biome.json +17 -11
  29. package/templates/conditional/linting/eslint/next/eslint.config.mjs +2 -11
  30. package/templates/conditional/testing/vitest/next/vitest.config.js +1 -1
  31. package/templates/conditional/testing/vitest/next/vitest.config.ts +1 -1
  32. package/templates/css/tailwind/postcss.config.mjs +3 -1
  33. package/schema/fixtures/v2/valid/next-app-router-hybrid.json +0 -35
  34. package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +0 -35
  35. package/templates/architectures/hybrid/main.jsx +0 -10
  36. package/templates/architectures/hybrid/main.tsx +0 -10
  37. package/templates/architectures/hybrid/src/App.jsx +0 -20
  38. package/templates/architectures/hybrid/src/App.tsx +0 -20
  39. package/templates/architectures/hybrid/src/features/.gitkeep +0 -1
  40. package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +0 -31
  41. package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +0 -36
  42. package/templates/architectures/hybrid/src/shared/components/ui/index.js +0 -1
  43. package/templates/architectures/hybrid/src/shared/components/ui/index.ts +0 -1
  44. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +0 -28
  45. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +0 -28
  46. package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
  47. package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
  48. package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
  49. package/templates/architectures/hybrid/src/shared/types/index.js +0 -20
  50. package/templates/architectures/hybrid/src/shared/types/index.ts +0 -16
  51. package/templates/architectures/hybrid/src/shared/utils/cn.jsx +0 -3
  52. package/templates/architectures/hybrid/src/shared/utils/cn.ts +0 -5
  53. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +0 -11
  54. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +0 -11
  55. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +0 -10
  56. 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, hybrid, or none.
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: feature-based, type-based, hybrid, or none (per-framework availability)
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 the mapped `paths.ui`)
88
- - Optional state management: Zustand or Redux Toolkit
89
- - Optional router (React Router — Vite only; Next.js uses filesystem routing)
90
- - Optional testing: Vitest or Jest (Next.js defaults to none until a Next-native overlay is verified)
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: ESLint (default), Oxlint, or Biome
94
- - Code formatter: Prettier or Oxfmt (chosen after the linter; `eslint-config-prettier` is auto-wired for the ESLint + Prettier combo)
95
- - Manifest v2: Emits a deterministic `lumen.config.json` validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration.
96
- - Explicit path mapping: Target directories declared for `lumen-cli` code generation
97
- - Optional Axios setup and icon libraries
98
- - Auto `git init`, generated `README` (with a project description reflecting the chosen tooling) and `LICENSE`
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:** optional `.lumen/` context directory (project, architecture, conventions) via prompt or `--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`, `hybrid`, `none`).
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 people and agents. When `agentDocs: true` is set in the manifest (or via the `--agent-docs` flag), `create-lumen` generates a `.lumen/` directory in the project root containing:
235
- - `project.json`: The resolved manifest.
236
- - `architecture.json`: The effective architecture preset and validation mode.
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
- quickSetup: cliArgs.quickSetup,
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.2",
4
- "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
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"
@@ -24,7 +24,7 @@
24
24
  "components": "src/shared/components",
25
25
  "services": "src/shared/services",
26
26
  "hooks": "src/shared/hooks",
27
- "pages": "app",
27
+ "pages": "src/app",
28
28
  "ui": "src/shared/components/ui"
29
29
  },
30
30
  "tooling": {
@@ -22,7 +22,7 @@
22
22
  "components": "src/components",
23
23
  "services": "src/services",
24
24
  "hooks": "src/hooks",
25
- "pages": "app",
25
+ "pages": "src/app",
26
26
  "ui": "src/components/ui"
27
27
  },
28
28
  "tooling": {
@@ -24,7 +24,7 @@
24
24
  "components": "src/shared/components",
25
25
  "services": "src/shared/services",
26
26
  "hooks": "src/shared/hooks",
27
- "pages": "pages",
27
+ "pages": "src/pages",
28
28
  "ui": "src/shared/components/ui"
29
29
  },
30
30
  "tooling": {
@@ -22,7 +22,7 @@
22
22
  "components": "src/components",
23
23
  "services": "src/services",
24
24
  "hooks": "src/hooks",
25
- "pages": "pages",
25
+ "pages": "src/pages",
26
26
  "ui": "src/components/ui"
27
27
  },
28
28
  "tooling": {
@@ -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/app/App.tsx`);
20
- await deleteIfExists(`${projectPath}/src/app/App.jsx`);
21
- await deleteIfExists(`${projectPath}/src/app/hooks`);
22
- await deleteIfExists(`${projectPath}/src/app/contexts`);
23
- await deleteIfExists(`${projectPath}/src/app/providers`);
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, hybrid)
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
- export async function configureProject(projectPath, language, cssFramework) {
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 (cssFramework !== "tailwind") {
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
- pkg.scripts.dev ??= "vite";
174
- pkg.scripts.build ??= "vite build";
175
- pkg.scripts.preview ??= "vite preview";
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
- const stylesDir =
29
- architecture === "feature-based" || architecture === "hybrid"
30
- ? path.join(projectPath, "src", "shared", "styles")
31
- : path.join(projectPath, "src", "styles");
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" || architecture === "hybrid"
113
+ architecture === "feature-based"
104
114
  ? `./shared/styles/${mainFileName}`
105
115
  : `./styles/${mainFileName}`;
106
116
  const wrong =
107
- architecture === "feature-based" || architecture === "hybrid"
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}'`);
@@ -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", "hybrid", "none"],
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", "hybrid", "none"],
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 / hybrid / none)
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 / Supabase)",
432
- 'DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"',
433
- 'DIRECT_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"',
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
- await writeFileRecursive(testDest, await fsp.readFile(testSrc, "utf8"));
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
  }