create-lumen 2.0.0-beta.1 → 2.0.0-beta.3

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 (87) 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 +30 -2
  9. package/src/agent-context.js +71 -47
  10. package/src/cleanup.js +34 -1
  11. package/src/cli-args.js +79 -2
  12. package/src/configure.js +36 -8
  13. package/src/css.js +3 -4
  14. package/src/dependencies.js +42 -15
  15. package/src/engine/capabilities.js +51 -2
  16. package/src/engine/composition.js +282 -23
  17. package/src/env.js +159 -4
  18. package/src/format.js +14 -6
  19. package/src/injector.js +66 -14
  20. package/src/main.js +62 -17
  21. package/src/manifest/emit.js +35 -6
  22. package/src/manifest/paths.js +1 -25
  23. package/src/manifest/schema.js +32 -2
  24. package/src/prompts.js +101 -11
  25. package/src/readme.js +0 -16
  26. package/src/utils/dev-server.js +6 -1
  27. package/templates/.env.example +1 -1
  28. package/templates/architectures/feature-based/scripts/create-feature.mjs +2 -2
  29. package/templates/conditional/database/prisma/db.js +14 -0
  30. package/templates/conditional/database/prisma/db.ts +16 -0
  31. package/templates/conditional/database/prisma/schema.prisma +9 -0
  32. package/templates/conditional/database/prisma/seed.js +16 -0
  33. package/templates/conditional/database/prisma/seed.ts +16 -0
  34. package/templates/conditional/docker/.dockerignore +10 -0
  35. package/templates/conditional/docker/Dockerfile +57 -0
  36. package/templates/conditional/docker/docker-compose.yml +35 -0
  37. package/templates/conditional/linting/biome/biome.json +17 -11
  38. package/templates/conditional/linting/eslint/next/eslint.config.mjs +2 -11
  39. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  40. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  41. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  42. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  43. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  44. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  45. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  46. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  47. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  48. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  49. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  50. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  51. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  52. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  53. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  54. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  55. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  56. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  57. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  58. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  59. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  60. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  61. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  62. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
  63. package/templates/css/tailwind/postcss.config.mjs +3 -1
  64. package/schema/fixtures/v2/valid/next-app-router-hybrid.json +0 -35
  65. package/schema/fixtures/v2/valid/next-pages-router-hybrid.json +0 -35
  66. package/templates/architectures/hybrid/main.jsx +0 -10
  67. package/templates/architectures/hybrid/main.tsx +0 -10
  68. package/templates/architectures/hybrid/src/App.jsx +0 -20
  69. package/templates/architectures/hybrid/src/App.tsx +0 -20
  70. package/templates/architectures/hybrid/src/features/.gitkeep +0 -1
  71. package/templates/architectures/hybrid/src/shared/components/ui/Button.jsx +0 -31
  72. package/templates/architectures/hybrid/src/shared/components/ui/Button.tsx +0 -36
  73. package/templates/architectures/hybrid/src/shared/components/ui/index.js +0 -1
  74. package/templates/architectures/hybrid/src/shared/components/ui/index.ts +0 -1
  75. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.jsx +0 -28
  76. package/templates/architectures/hybrid/src/shared/hooks/useLocalStorage.ts +0 -28
  77. package/templates/architectures/hybrid/src/shared/layouts/.gitkeep +0 -0
  78. package/templates/architectures/hybrid/src/shared/stores/.gitkeep +0 -0
  79. package/templates/architectures/hybrid/src/shared/styles/.gitkeep +0 -0
  80. package/templates/architectures/hybrid/src/shared/types/index.js +0 -20
  81. package/templates/architectures/hybrid/src/shared/types/index.ts +0 -16
  82. package/templates/architectures/hybrid/src/shared/utils/cn.jsx +0 -3
  83. package/templates/architectures/hybrid/src/shared/utils/cn.ts +0 -5
  84. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.jsx +0 -11
  85. package/templates/conditional/testing/jest/hybrid/src/__tests__/App.test.tsx +0 -11
  86. package/templates/conditional/testing/vitest/hybrid/src/__tests__/App.test.jsx +0 -10
  87. 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.1",
4
- "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
3
+ "version": "2.0.0-beta.3",
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
  },
@@ -247,6 +245,36 @@
247
245
  ],
248
246
  "additionalProperties": false
249
247
  },
248
+ "database": {
249
+ "type": "object",
250
+ "properties": {
251
+ "orm": {
252
+ "type": "string",
253
+ "enum": [
254
+ "prisma",
255
+ "none"
256
+ ]
257
+ },
258
+ "provider": {
259
+ "default": "postgresql",
260
+ "type": "string",
261
+ "enum": [
262
+ "postgresql",
263
+ "supabase"
264
+ ]
265
+ }
266
+ },
267
+ "required": [
268
+ "orm"
269
+ ],
270
+ "additionalProperties": false
271
+ },
272
+ "docker": {
273
+ "type": "boolean"
274
+ },
275
+ "routeGroups": {
276
+ "type": "boolean"
277
+ },
250
278
  "reactCompiler": {
251
279
  "type": "boolean"
252
280
  },
@@ -19,17 +19,19 @@ function resolveManifest(manifestOrResponses) {
19
19
  }
20
20
 
21
21
  /**
22
- * Render the short agent conventions markdown from a manifest.
23
- * Deterministic: derived only from stable manifest fields.
22
+ * Render the root AGENTS.md document from a manifest.
23
+ * Single Source of Truth (SSOT): references `lumen.config.json` directly.
24
+ * Byte-deterministic: derived only from stable manifest fields (no timestamps).
24
25
  * @param {object} manifest - validated manifest v2
25
26
  * @returns {string}
26
27
  */
27
- export function renderConventions(manifest) {
28
+ export function renderAgentsMd(manifest) {
28
29
  const preset = manifest.architecture?.preset ?? manifest.architecture?.type ?? "feature-based";
29
30
  const validation = manifest.architecture?.validation ?? "strict";
30
31
  const paths = manifest.paths ?? {};
31
32
  const harness = manifest.harness ?? { commands: [] };
32
33
  const barrelFile = manifest.tooling?.language === "js" ? "index.js" : "index.ts";
34
+ const isNext = manifest.framework?.name === "next";
33
35
 
34
36
  const pathLines = Object.entries(paths)
35
37
  .map(([key, value]) => `- ${key}: \`${value}\``)
@@ -41,74 +43,96 @@ export function renderConventions(manifest) {
41
43
 
42
44
  const harnessSummary = (harness.commands ?? []).map((c) => `\`${c.command}\``).join(", ");
43
45
 
46
+ let nextSection = "";
47
+ if (isNext) {
48
+ nextSection =
49
+ `<!-- BEGIN:nextjs-agent-rules -->\n` +
50
+ `## This is NOT the Next.js you know\n` +
51
+ `This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory...) before writing any code. Heed deprecation notices.\n` +
52
+ `<!-- END:nextjs-agent-rules -->\n` +
53
+ `\n` +
54
+ `### Next.js Component Rules\n` +
55
+ `\n` +
56
+ `- By default, all components in the App Router are React Server Components (RSC).\n` +
57
+ `- Use the \`"use client"\` directive only at the top of files that require browser APIs, React state, or event handlers.\n` +
58
+ `- Never import server-only modules or secrets into client components.\n` +
59
+ `\n`;
60
+ }
61
+
62
+ const uiPath = paths.ui ?? "@/components/ui";
63
+ const componentsPath = paths.components ?? "@/components";
64
+
44
65
  return (
45
- `# Lumen Agent Conventions\n` +
66
+ `# AGENTS.md\n` +
67
+ `\n` +
68
+ `> **Single Source of Truth (SSOT)**: Project configuration, paths, and harness definitions are defined in \`lumen.config.json\`. Consult \`lumen.config.json\` for the authoritative settings.\n` +
46
69
  `\n` +
47
- `Generated from \`lumen.config.json\`. Resolve every file location via \`paths.*\` in \`.lumen/architecture.json\` (preset: \`${preset}\`, validation: \`${validation}\`).\n` +
70
+ `${nextSection}` +
71
+ `## Architecture & Conventions\n` +
48
72
  `\n` +
49
- `## Rules\n` +
73
+ `- **Architecture Preset**: \`${preset}\`\n` +
74
+ `- **Validation Mode**: \`${validation}\` (${VALIDATION_NOTES[validation] ?? VALIDATION_NOTES.strict})\n` +
75
+ `- **Paths Resolution**: Resolve file locations via \`paths.*\` defined in \`lumen.config.json\` — never hardcode arbitrary alternative locations.\n` +
76
+ `- **Feature Barrels**: Every feature directory (under \`${paths.features ?? "src/features"}/<feature>/\`) must re-export its public API via \`${barrelFile}\`.\n` +
77
+ `- **Shared Imports**: Import shared utilities via \`@/shared\` (plus path aliases configured in tsconfig/jsconfig).\n` +
50
78
  `\n` +
51
- `- Resolve all paths via \`paths.*\` — never hardcode alternative \`src/...\` locations.\n` +
52
- `- Feature barrel: every \`src/features/<feature>/\` must re-export its public API via \`${barrelFile}\`.\n` +
53
- `- Shared code: import shared utilities via \`@/shared\` (plus the \`@/...\` aliases from tsconfig/jsconfig).\n` +
54
- `- Harness: before finishing, run each command in \`.lumen/project.json\` \`harness.commands\` in order (${harnessSummary}).\n` +
55
- `- Validation mode \`${validation}\`: ${VALIDATION_NOTES[validation] ?? VALIDATION_NOTES.strict}\n` +
79
+ `## Component Taxonomy\n` +
56
80
  `\n` +
57
- `## Paths\n` +
81
+ `Components must be organized strictly according to \`lumen.config.json\` path mappings:\n` +
82
+ `- **Atomic UI Primitives**: Base atoms and UI primitives (e.g. shadcn components) belong in \`paths.ui\` (\`${uiPath}\`).\n` +
83
+ `- **Compound Components**: Shared composite components belong in \`paths.components\` (\`${componentsPath}\`).\n` +
84
+ `- **Feature Components**: Domain-specific components belong inside their respective feature folder (e.g. \`${paths.features ?? "src/features"}/<feature>/components\`).\n` +
85
+ `\n` +
86
+ `Lumen CLI and AI agents must place primitive UI atoms under \`paths.ui\`, global compound components under \`paths.components\`, and feature-scoped components in their respective feature folders.\n` +
87
+ `\n` +
88
+ `## Path Mappings\n` +
58
89
  `\n` +
59
90
  `${pathLines}\n` +
60
91
  `\n` +
61
- `## Harness\n` +
92
+ `## Validation Harness\n` +
93
+ `\n` +
94
+ `Before finishing work, run each harness command defined in \`lumen.config.json\` \`harness.commands\` in order (${harnessSummary}):\n` +
62
95
  `\n` +
63
96
  `${harnessLines}\n`
64
97
  );
65
98
  }
66
99
 
67
100
  /**
68
- * Build the deterministic agent-context payload from a validated manifest v2.
69
- * No timestamps — output must be byte-stable.
101
+ * Backward compatibility alias for renderAgentsMd.
102
+ * @param {object} manifest - validated manifest v2
103
+ * @returns {string}
104
+ */
105
+ export function renderConventions(manifest) {
106
+ return renderAgentsMd(manifest);
107
+ }
108
+
109
+ /**
110
+ * Build deterministic agent context metadata.
111
+ * Retained for backwards compatibility with tests and callers.
70
112
  * @param {object} manifest - validated manifest v2
71
- * @returns {{ project: object, architecture: object, conventionsMd: string }}
113
+ * @returns {{ agentsMd: string, conventionsMd: string }}
72
114
  */
73
115
  export function buildAgentContext(manifest) {
74
- const project = {
75
- manifestVersion: manifest.manifestVersion,
76
- framework: manifest.framework,
77
- styling: manifest.styling,
78
- tooling: manifest.tooling,
79
- harness: manifest.harness,
80
- docs: manifest.docs,
81
- };
82
- const architecture = {
83
- preset: manifest.architecture?.preset ?? manifest.architecture?.type,
84
- validation: manifest.architecture?.validation ?? "strict",
85
- paths: manifest.paths,
116
+ const agentsMd = renderAgentsMd(manifest);
117
+ return {
118
+ agentsMd,
119
+ conventionsMd: agentsMd,
86
120
  };
87
- const conventionsMd = renderConventions(manifest);
88
- return { project, architecture, conventionsMd };
89
- }
90
-
91
- function writeJson(filePath, value) {
92
- return fsp.writeFile(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
93
121
  }
94
122
 
95
123
  /**
96
- * Emit the .lumen agent context dir into a scaffolded project.
97
- * Accepts either a validated manifest v2 or scaffolder responses
98
- * (uses responses._manifestSource when present, else buildManifest).
99
- * Writes .lumen/project.json, .lumen/architecture.json, .lumen/conventions.md
100
- * with 2-space indent + trailing newline. Byte-deterministic (no timestamps).
124
+ * Emit AGENTS.md directly into project root.
125
+ * Accepts either a validated manifest v2 or scaffolder responses.
126
+ * Writes ${projectPath}/AGENTS.md with trailing newline. Byte-deterministic.
127
+ * Does not emit duplicate JSON files or .lumen/ directory.
101
128
  * @param {string} projectPath - absolute path to scaffolded project
102
129
  * @param {object} manifestOrResponses
103
- * @returns {Promise<string>} absolute path to the .lumen dir
130
+ * @returns {Promise<string>} absolute path to AGENTS.md
104
131
  */
105
132
  export async function emitAgentContext(projectPath, manifestOrResponses) {
106
133
  const manifest = resolveManifest(manifestOrResponses);
107
- const { project, architecture, conventionsMd } = buildAgentContext(manifest);
108
- const dir = path.join(projectPath, ".lumen");
109
- await fsp.mkdir(dir, { recursive: true });
110
- await writeJson(path.join(dir, "project.json"), project);
111
- await writeJson(path.join(dir, "architecture.json"), architecture);
112
- await fsp.writeFile(path.join(dir, "conventions.md"), conventionsMd, "utf8");
113
- return dir;
134
+ const content = renderAgentsMd(manifest);
135
+ const filePath = path.join(projectPath, "AGENTS.md");
136
+ await fsp.writeFile(filePath, content, "utf8");
137
+ return filePath;
114
138
  }
package/src/cleanup.js CHANGED
@@ -1,7 +1,40 @@
1
1
  import { deleteIfExists } from "@/utils/fs.js";
2
+ import { promises as fsp } from "fs";
3
+ import path from "path";
2
4
 
3
- export async function cleanupBoilerplate(projectPath) {
5
+ export async function cleanupBoilerplate(projectPath, responses = {}) {
4
6
  await deleteIfExists(`${projectPath}/public`);
5
7
  await deleteIfExists(`${projectPath}/src/assets`);
6
8
  await deleteIfExists(`${projectPath}/.oxlintrc.json`);
9
+ await deleteIfExists(`${projectPath}/.provenance.json`);
10
+
11
+ const isNext =
12
+ responses?.framework?.name === "next" ||
13
+ responses?.frameworkName === "next" ||
14
+ (await fsp.stat(path.join(projectPath, "next.config.ts")).catch(() => false)) ||
15
+ (await fsp.stat(path.join(projectPath, "next.config.mjs")).catch(() => false)) ||
16
+ (await fsp.stat(path.join(projectPath, "next.config.js")).catch(() => false));
17
+
18
+ if (isNext) {
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
+
25
+ const isPagesRouter =
26
+ responses?.framework?.variant === "pages-router" ||
27
+ responses?.frameworkVariant === "pages-router" ||
28
+ (await fsp.stat(path.join(projectPath, "src", "pages")).catch(() => false));
29
+
30
+ if (isPagesRouter) {
31
+ await deleteIfExists(`${projectPath}/src/app`);
32
+ } else {
33
+ await deleteIfExists(`${projectPath}/src/app/App.tsx`);
34
+ await deleteIfExists(`${projectPath}/src/app/App.jsx`);
35
+ await deleteIfExists(`${projectPath}/src/app/hooks`);
36
+ await deleteIfExists(`${projectPath}/src/app/contexts`);
37
+ await deleteIfExists(`${projectPath}/src/app/providers`);
38
+ }
39
+ }
7
40
  }
package/src/cli-args.js CHANGED
@@ -280,11 +280,31 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
280
280
  architecture: { type: "string" },
281
281
  bundler: { type: "string", short: "b" },
282
282
  adapter: { type: "string" },
283
+ database: { type: "string" },
284
+ db: { type: "string" },
285
+ "db-provider": { type: "string" },
286
+ dbProvider: { type: "string" },
287
+ "database-provider": { type: "string" },
288
+ databaseProvider: { type: "string" },
283
289
  "react-compiler": { type: "boolean" },
284
290
  reactCompiler: { type: "boolean" },
291
+ "route-groups": { type: "boolean" },
292
+ routeGroups: { type: "boolean" },
285
293
  "agent-docs": { type: "boolean" },
286
294
  agentDocs: { type: "boolean" },
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" },
287
306
  linter: { type: "string" },
307
+ formatter: { type: "string" },
288
308
  help: { type: "boolean", short: "h", default: false },
289
309
  version: { type: "boolean", short: "v", default: false },
290
310
  },
@@ -303,9 +323,34 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
303
323
 
304
324
  const bundler = values.bundler ? values.bundler.trim().toLowerCase() : null;
305
325
  const adapter = values.adapter ? values.adapter.trim().toLowerCase() : null;
326
+ let database = values.database || values.db || null;
327
+ if (database) {
328
+ database = database.trim().toLowerCase();
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
+ }
306
339
  const reactCompiler = values["react-compiler"] ?? values.reactCompiler ?? null;
340
+ const routeGroups = values["route-groups"] ?? values.routeGroups ?? null;
307
341
  const agentDocs = values["agent-docs"] ?? values.agentDocs ?? null;
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;
308
352
  const linter = values.linter ? values.linter.trim().toLowerCase() : null;
353
+ const formatter = values.formatter ? values.formatter.trim().toLowerCase() : null;
309
354
 
310
355
  return {
311
356
  projectName,
@@ -314,11 +359,20 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
314
359
  template: values.template || null,
315
360
  framework: values.framework ? values.framework.trim() : null,
316
361
  arch,
362
+ language,
363
+ cssFramework,
364
+ uiKit,
317
365
  bundler,
318
366
  adapter,
367
+ database,
368
+ databaseProvider,
319
369
  reactCompiler,
370
+ routeGroups,
320
371
  agentDocs,
372
+ docker,
373
+ testing,
321
374
  linter,
375
+ formatter,
322
376
  help: Boolean(values.help),
323
377
  version: Boolean(values.version),
324
378
  positionals,
@@ -368,7 +422,10 @@ export function manifestToResponses(manifest, projectName) {
368
422
  const canClientRoute = isPromptVisible(framework, "router");
369
423
  const supportsBundler = isPromptVisible(framework, "bundler");
370
424
  const supportsAdapter = isPromptVisible(framework, "adapter");
425
+ const supportsDatabase = isPromptVisible(framework, "database");
371
426
  const supportsCompiler = isPromptVisible(framework, "reactCompiler");
427
+ const supportsRouteGroups = isPromptVisible(framework, "routeGroups");
428
+ const supportsDocker = isPromptVisible(framework, "docker");
372
429
 
373
430
  return {
374
431
  projectName,
@@ -382,6 +439,14 @@ export function manifestToResponses(manifest, projectName) {
382
439
  supportsAdapter && manifest.framework?.adapter
383
440
  ? manifest.framework.adapter
384
441
  : undefined,
442
+ database:
443
+ supportsDatabase && manifest.database?.orm
444
+ ? manifest.database.orm
445
+ : "none",
446
+ databaseProvider:
447
+ supportsDatabase && manifest.database?.provider
448
+ ? manifest.database.provider
449
+ : undefined,
385
450
  architecture: manifest.architecture?.type || "feature-based",
386
451
  language: manifest.tooling?.language || "ts",
387
452
  cssFramework: manifest.styling?.engine || "tailwind",
@@ -390,7 +455,9 @@ export function manifestToResponses(manifest, projectName) {
390
455
  uiKit: manifest.ui?.kit || "none",
391
456
  docsLanguage: manifest.docs?.language || "en",
392
457
  reactCompiler: supportsCompiler ? manifest.reactCompiler : undefined,
458
+ routeGroups: supportsRouteGroups ? manifest.routeGroups : undefined,
393
459
  agentDocs: manifest.agentDocs,
460
+ docker: supportsDocker ? (manifest.docker ?? false) : false,
394
461
  // Sensible defaults for scaffolder conditional passes (Next.js ships no
395
462
  // verified test-runner overlay yet, so it defaults to none; explicit
396
463
  // vitest/jest selection still applies the standard overlays)
@@ -410,17 +477,27 @@ export function manifestToResponses(manifest, projectName) {
410
477
  */
411
478
  export function printHelp() {
412
479
  console.log(`
413
- ${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
414
481
 
415
482
  ${chalk.bold("USAGE")}
416
483
  ${chalk.green("$")} ${chalk.bold("create-lumen")} [project-name] [options]
417
484
 
418
485
  ${chalk.bold("OPTIONS")}
419
486
  ${chalk.yellow("-y, --yes")} Quick setup with recommended defaults (TS + Tailwind + Router + ESLint + Prettier)
420
- ${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)
421
490
  ${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
422
491
  ${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
423
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)
496
+ ${chalk.yellow("--db, --database")} <orm> Database ORM (prisma, none)
497
+ ${chalk.yellow("--db-provider")} <provider> Database provider (postgresql, supabase)
498
+ ${chalk.yellow(" --docker")} Include multi-stage Dockerfile & docker-compose for Next.js standalone
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
424
501
  ${chalk.yellow("-h, --help")} Show this help message
425
502
  ${chalk.yellow("-v, --version")} Show version number
426
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";