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.
- 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 +30 -2
- package/src/agent-context.js +71 -47
- package/src/cleanup.js +34 -1
- package/src/cli-args.js +79 -2
- package/src/configure.js +36 -8
- package/src/css.js +3 -4
- package/src/dependencies.js +42 -15
- package/src/engine/capabilities.js +51 -2
- package/src/engine/composition.js +282 -23
- package/src/env.js +159 -4
- package/src/format.js +14 -6
- package/src/injector.js +66 -14
- package/src/main.js +62 -17
- package/src/manifest/emit.js +35 -6
- package/src/manifest/paths.js +1 -25
- package/src/manifest/schema.js +32 -2
- package/src/prompts.js +101 -11
- package/src/readme.js +0 -16
- package/src/utils/dev-server.js +6 -1
- package/templates/.env.example +1 -1
- package/templates/architectures/feature-based/scripts/create-feature.mjs +2 -2
- package/templates/conditional/database/prisma/db.js +14 -0
- package/templates/conditional/database/prisma/db.ts +16 -0
- package/templates/conditional/database/prisma/schema.prisma +9 -0
- package/templates/conditional/database/prisma/seed.js +16 -0
- package/templates/conditional/database/prisma/seed.ts +16 -0
- package/templates/conditional/docker/.dockerignore +10 -0
- package/templates/conditional/docker/Dockerfile +57 -0
- package/templates/conditional/docker/docker-compose.yml +35 -0
- package/templates/conditional/linting/biome/biome.json +17 -11
- package/templates/conditional/linting/eslint/next/eslint.config.mjs +2 -11
- package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
- package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
- package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
- package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
- package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
- package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
- package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
- package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
- package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
- package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
- package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
- package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
- package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
- package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
- package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
- package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
- package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
- package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
- package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
- package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
- package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
- package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
- package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
- package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
- 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.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"
|
|
@@ -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
|
},
|
package/src/agent-context.js
CHANGED
|
@@ -19,17 +19,19 @@ function resolveManifest(manifestOrResponses) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Render the
|
|
23
|
-
*
|
|
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
|
|
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
|
-
`#
|
|
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
|
-
|
|
70
|
+
`${nextSection}` +
|
|
71
|
+
`## Architecture & Conventions\n` +
|
|
48
72
|
`\n` +
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
69
|
-
*
|
|
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 {{
|
|
113
|
+
* @returns {{ agentsMd: string, conventionsMd: string }}
|
|
72
114
|
*/
|
|
73
115
|
export function buildAgentContext(manifest) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
97
|
-
* Accepts either a validated manifest v2 or scaffolder responses
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
|
|
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
|
|
108
|
-
const
|
|
109
|
-
await fsp.
|
|
110
|
-
|
|
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
|
|
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
|
-
|
|
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";
|