create-lumen 2.0.0-beta.1 → 2.0.0-beta.2
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/package.json +1 -1
- package/schema/lumen.config.v2.json +29 -0
- package/src/agent-context.js +71 -47
- package/src/cleanup.js +19 -1
- package/src/cli-args.js +26 -0
- package/src/dependencies.js +42 -15
- package/src/engine/capabilities.js +49 -0
- package/src/engine/composition.js +242 -17
- package/src/env.js +24 -6
- package/src/main.js +5 -5
- package/src/manifest/emit.js +13 -0
- package/src/manifest/schema.js +30 -0
- package/src/prompts.js +77 -4
- 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/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/package.json
CHANGED
|
@@ -247,6 +247,35 @@
|
|
|
247
247
|
],
|
|
248
248
|
"additionalProperties": false
|
|
249
249
|
},
|
|
250
|
+
"database": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"orm": {
|
|
254
|
+
"type": "string",
|
|
255
|
+
"enum": [
|
|
256
|
+
"prisma",
|
|
257
|
+
"none"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
"provider": {
|
|
261
|
+
"default": "postgresql",
|
|
262
|
+
"type": "string",
|
|
263
|
+
"enum": [
|
|
264
|
+
"postgresql"
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"required": [
|
|
269
|
+
"orm"
|
|
270
|
+
],
|
|
271
|
+
"additionalProperties": false
|
|
272
|
+
},
|
|
273
|
+
"docker": {
|
|
274
|
+
"type": "boolean"
|
|
275
|
+
},
|
|
276
|
+
"routeGroups": {
|
|
277
|
+
"type": "boolean"
|
|
278
|
+
},
|
|
250
279
|
"reactCompiler": {
|
|
251
280
|
"type": "boolean"
|
|
252
281
|
},
|
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,25 @@
|
|
|
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/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`);
|
|
24
|
+
}
|
|
7
25
|
}
|
package/src/cli-args.js
CHANGED
|
@@ -280,10 +280,15 @@ 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" },
|
|
283
285
|
"react-compiler": { type: "boolean" },
|
|
284
286
|
reactCompiler: { type: "boolean" },
|
|
287
|
+
"route-groups": { type: "boolean" },
|
|
288
|
+
routeGroups: { type: "boolean" },
|
|
285
289
|
"agent-docs": { type: "boolean" },
|
|
286
290
|
agentDocs: { type: "boolean" },
|
|
291
|
+
docker: { type: "boolean" },
|
|
287
292
|
linter: { type: "string" },
|
|
288
293
|
help: { type: "boolean", short: "h", default: false },
|
|
289
294
|
version: { type: "boolean", short: "v", default: false },
|
|
@@ -303,8 +308,14 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
303
308
|
|
|
304
309
|
const bundler = values.bundler ? values.bundler.trim().toLowerCase() : null;
|
|
305
310
|
const adapter = values.adapter ? values.adapter.trim().toLowerCase() : null;
|
|
311
|
+
let database = values.database || values.db || null;
|
|
312
|
+
if (database) {
|
|
313
|
+
database = database.trim().toLowerCase();
|
|
314
|
+
}
|
|
306
315
|
const reactCompiler = values["react-compiler"] ?? values.reactCompiler ?? null;
|
|
316
|
+
const routeGroups = values["route-groups"] ?? values.routeGroups ?? null;
|
|
307
317
|
const agentDocs = values["agent-docs"] ?? values.agentDocs ?? null;
|
|
318
|
+
const docker = Boolean(values.docker);
|
|
308
319
|
const linter = values.linter ? values.linter.trim().toLowerCase() : null;
|
|
309
320
|
|
|
310
321
|
return {
|
|
@@ -316,8 +327,11 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
|
316
327
|
arch,
|
|
317
328
|
bundler,
|
|
318
329
|
adapter,
|
|
330
|
+
database,
|
|
319
331
|
reactCompiler,
|
|
332
|
+
routeGroups,
|
|
320
333
|
agentDocs,
|
|
334
|
+
docker,
|
|
321
335
|
linter,
|
|
322
336
|
help: Boolean(values.help),
|
|
323
337
|
version: Boolean(values.version),
|
|
@@ -368,7 +382,10 @@ export function manifestToResponses(manifest, projectName) {
|
|
|
368
382
|
const canClientRoute = isPromptVisible(framework, "router");
|
|
369
383
|
const supportsBundler = isPromptVisible(framework, "bundler");
|
|
370
384
|
const supportsAdapter = isPromptVisible(framework, "adapter");
|
|
385
|
+
const supportsDatabase = isPromptVisible(framework, "database");
|
|
371
386
|
const supportsCompiler = isPromptVisible(framework, "reactCompiler");
|
|
387
|
+
const supportsRouteGroups = isPromptVisible(framework, "routeGroups");
|
|
388
|
+
const supportsDocker = isPromptVisible(framework, "docker");
|
|
372
389
|
|
|
373
390
|
return {
|
|
374
391
|
projectName,
|
|
@@ -382,6 +399,10 @@ export function manifestToResponses(manifest, projectName) {
|
|
|
382
399
|
supportsAdapter && manifest.framework?.adapter
|
|
383
400
|
? manifest.framework.adapter
|
|
384
401
|
: undefined,
|
|
402
|
+
database:
|
|
403
|
+
supportsDatabase && manifest.database?.orm
|
|
404
|
+
? manifest.database.orm
|
|
405
|
+
: "none",
|
|
385
406
|
architecture: manifest.architecture?.type || "feature-based",
|
|
386
407
|
language: manifest.tooling?.language || "ts",
|
|
387
408
|
cssFramework: manifest.styling?.engine || "tailwind",
|
|
@@ -390,7 +411,9 @@ export function manifestToResponses(manifest, projectName) {
|
|
|
390
411
|
uiKit: manifest.ui?.kit || "none",
|
|
391
412
|
docsLanguage: manifest.docs?.language || "en",
|
|
392
413
|
reactCompiler: supportsCompiler ? manifest.reactCompiler : undefined,
|
|
414
|
+
routeGroups: supportsRouteGroups ? manifest.routeGroups : undefined,
|
|
393
415
|
agentDocs: manifest.agentDocs,
|
|
416
|
+
docker: supportsDocker ? (manifest.docker ?? false) : false,
|
|
394
417
|
// Sensible defaults for scaffolder conditional passes (Next.js ships no
|
|
395
418
|
// verified test-runner overlay yet, so it defaults to none; explicit
|
|
396
419
|
// vitest/jest selection still applies the standard overlays)
|
|
@@ -421,6 +444,9 @@ ${chalk.bold("OPTIONS")}
|
|
|
421
444
|
${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
|
|
422
445
|
${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
|
|
423
446
|
${chalk.yellow("-f, --framework")} <name> Target framework (react, next, react:vite, next:app-router, etc.)
|
|
447
|
+
${chalk.yellow("--db, --database")} <orm> Database ORM (prisma, none)
|
|
448
|
+
${chalk.yellow(" --docker")} Include multi-stage Dockerfile & docker-compose for Next.js standalone
|
|
449
|
+
${chalk.yellow("--route-groups")} Include Next.js route groups preset (public, auth, private, admin)
|
|
424
450
|
${chalk.yellow("-h, --help")} Show this help message
|
|
425
451
|
${chalk.yellow("-v, --version")} Show version number
|
|
426
452
|
|
package/src/dependencies.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
+
import { normalizeFramework } from "./engine/capabilities.js";
|
|
2
3
|
|
|
3
4
|
export async function installDeps(pkgManager, dev, packages, cwd) {
|
|
4
5
|
let cmd, args;
|
|
@@ -40,11 +41,15 @@ export async function installAllDeps(responses, pkg, cwd) {
|
|
|
40
41
|
// the conditional wiring stays verifiable without a network install.
|
|
41
42
|
export function computeDeps(responses) {
|
|
42
43
|
const deps = [];
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
:
|
|
44
|
+
const fw = normalizeFramework(
|
|
45
|
+
responses.framework || {
|
|
46
|
+
name: responses.frameworkName,
|
|
47
|
+
variant: responses.frameworkVariant,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
const fwName = fw.name;
|
|
47
51
|
const cssDevDeps = [];
|
|
52
|
+
const databaseDevDeps = [];
|
|
48
53
|
const testingDevDeps = [];
|
|
49
54
|
const linterDevDeps = [];
|
|
50
55
|
const formatterDevDeps = [];
|
|
@@ -83,6 +88,15 @@ export function computeDeps(responses) {
|
|
|
83
88
|
deps.push("axios");
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
// Database
|
|
92
|
+
if (responses.database === "prisma") {
|
|
93
|
+
deps.push("@prisma/client");
|
|
94
|
+
databaseDevDeps.push("prisma");
|
|
95
|
+
if (responses.language === "ts") {
|
|
96
|
+
databaseDevDeps.push("tsx");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
// Testing
|
|
87
101
|
if (responses.testing === "vitest") {
|
|
88
102
|
testingDevDeps.push(
|
|
@@ -92,17 +106,29 @@ export function computeDeps(responses) {
|
|
|
92
106
|
"jsdom"
|
|
93
107
|
);
|
|
94
108
|
} else if (responses.testing === "jest") {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
if (fwName === "next") {
|
|
110
|
+
testingDevDeps.push(
|
|
111
|
+
"jest",
|
|
112
|
+
"jest-environment-jsdom",
|
|
113
|
+
"@testing-library/react",
|
|
114
|
+
"@testing-library/jest-dom"
|
|
115
|
+
);
|
|
116
|
+
if (responses.language === "ts") {
|
|
117
|
+
testingDevDeps.push("@types/jest");
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
testingDevDeps.push(
|
|
121
|
+
"jest",
|
|
122
|
+
"jest-environment-jsdom",
|
|
123
|
+
"@testing-library/react",
|
|
124
|
+
"@testing-library/jest-dom",
|
|
125
|
+
"jsdom",
|
|
126
|
+
"babel-jest",
|
|
127
|
+
"@babel/preset-env",
|
|
128
|
+
"@babel/preset-react",
|
|
129
|
+
"@babel/preset-typescript"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
106
132
|
}
|
|
107
133
|
|
|
108
134
|
// Linter
|
|
@@ -136,6 +162,7 @@ export function computeDeps(responses) {
|
|
|
136
162
|
|
|
137
163
|
const devDepBatches = [
|
|
138
164
|
cssDevDeps,
|
|
165
|
+
databaseDevDeps,
|
|
139
166
|
testingDevDeps,
|
|
140
167
|
linterDevDeps,
|
|
141
168
|
formatterDevDeps,
|
|
@@ -18,6 +18,9 @@ export const CAPABILITIES = {
|
|
|
18
18
|
NEXT_IMAGE: "next-image",
|
|
19
19
|
SPA_FALLBACK: "spa-fallback",
|
|
20
20
|
AGENT_DOCS: "agent-docs",
|
|
21
|
+
DATABASE: "database",
|
|
22
|
+
DOCKER: "docker",
|
|
23
|
+
ROUTE_GROUPS: "route-groups",
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
export const FRAMEWORK_CAPABILITIES = {
|
|
@@ -37,6 +40,7 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
37
40
|
allowedFormatters: ["prettier", "oxfmt", "none"],
|
|
38
41
|
allowedBundlers: [],
|
|
39
42
|
allowedAdapters: [],
|
|
43
|
+
allowedDatabases: [],
|
|
40
44
|
supportsRouterPrompt: true,
|
|
41
45
|
},
|
|
42
46
|
"next:app-router": {
|
|
@@ -52,6 +56,9 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
52
56
|
CAPABILITIES.NEXT_FONT,
|
|
53
57
|
CAPABILITIES.NEXT_IMAGE,
|
|
54
58
|
CAPABILITIES.AGENT_DOCS,
|
|
59
|
+
CAPABILITIES.DATABASE,
|
|
60
|
+
CAPABILITIES.DOCKER,
|
|
61
|
+
CAPABILITIES.ROUTE_GROUPS,
|
|
55
62
|
],
|
|
56
63
|
allowedArchitectures: ["feature-based", "hybrid", "none"],
|
|
57
64
|
allowedStyling: ["tailwind", "none"],
|
|
@@ -61,6 +68,7 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
61
68
|
allowedFormatters: ["prettier", "none"],
|
|
62
69
|
allowedBundlers: ["turbopack", "webpack"],
|
|
63
70
|
allowedAdapters: ["node", "vercel", "cloudflare", "static"],
|
|
71
|
+
allowedDatabases: ["prisma", "none"],
|
|
64
72
|
supportsRouterPrompt: false, // filesystem routing is built-in
|
|
65
73
|
},
|
|
66
74
|
"next:pages-router": {
|
|
@@ -74,6 +82,8 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
74
82
|
CAPABILITIES.NEXT_FONT,
|
|
75
83
|
CAPABILITIES.NEXT_IMAGE,
|
|
76
84
|
CAPABILITIES.AGENT_DOCS,
|
|
85
|
+
CAPABILITIES.DATABASE,
|
|
86
|
+
CAPABILITIES.DOCKER,
|
|
77
87
|
],
|
|
78
88
|
allowedArchitectures: ["feature-based", "hybrid", "none"],
|
|
79
89
|
allowedStyling: ["tailwind", "none"],
|
|
@@ -83,6 +93,7 @@ export const FRAMEWORK_CAPABILITIES = {
|
|
|
83
93
|
allowedFormatters: ["prettier", "none"],
|
|
84
94
|
allowedBundlers: ["turbopack", "webpack"],
|
|
85
95
|
allowedAdapters: ["node", "vercel", "cloudflare", "static"],
|
|
96
|
+
allowedDatabases: ["prisma", "none"],
|
|
86
97
|
supportsRouterPrompt: false, // filesystem routing is built-in
|
|
87
98
|
},
|
|
88
99
|
};
|
|
@@ -168,6 +179,22 @@ export function isOptionCompatible(framework, optionKey, optionValue) {
|
|
|
168
179
|
return hasCapability(framework, CAPABILITIES.REACT_COMPILER);
|
|
169
180
|
}
|
|
170
181
|
return true;
|
|
182
|
+
case "database":
|
|
183
|
+
case "db":
|
|
184
|
+
if (!desc.allowedDatabases || desc.allowedDatabases.length === 0) {
|
|
185
|
+
return optionValue === "none" || !optionValue;
|
|
186
|
+
}
|
|
187
|
+
return desc.allowedDatabases.includes(optionValue);
|
|
188
|
+
case "docker":
|
|
189
|
+
if (optionValue === true) {
|
|
190
|
+
return hasCapability(framework, CAPABILITIES.DOCKER);
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
case "routeGroups":
|
|
194
|
+
if (optionValue === true) {
|
|
195
|
+
return hasCapability(framework, CAPABILITIES.ROUTE_GROUPS);
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
171
198
|
default:
|
|
172
199
|
return true;
|
|
173
200
|
}
|
|
@@ -192,6 +219,7 @@ export function getCompatibleOptions(framework) {
|
|
|
192
219
|
formatters: [...desc.allowedFormatters],
|
|
193
220
|
bundlers: [...desc.allowedBundlers],
|
|
194
221
|
adapters: [...desc.allowedAdapters],
|
|
222
|
+
databases: [...(desc.allowedDatabases || [])],
|
|
195
223
|
supportsRouterPrompt: desc.supportsRouterPrompt,
|
|
196
224
|
};
|
|
197
225
|
}
|
|
@@ -289,6 +317,12 @@ export function isPromptVisible(framework, promptName) {
|
|
|
289
317
|
return hasCapability(fw, CAPABILITIES.ADAPTERS);
|
|
290
318
|
case "reactCompiler":
|
|
291
319
|
return hasCapability(fw, CAPABILITIES.REACT_COMPILER);
|
|
320
|
+
case "database":
|
|
321
|
+
return hasCapability(fw, CAPABILITIES.DATABASE);
|
|
322
|
+
case "docker":
|
|
323
|
+
return hasCapability(fw, CAPABILITIES.DOCKER);
|
|
324
|
+
case "routeGroups":
|
|
325
|
+
return hasCapability(fw, CAPABILITIES.ROUTE_GROUPS);
|
|
292
326
|
default:
|
|
293
327
|
return true;
|
|
294
328
|
}
|
|
@@ -334,6 +368,9 @@ export function getDefaultResponses(framework, { projectName = "my-app" } = {})
|
|
|
334
368
|
stateManagement: "none",
|
|
335
369
|
iconLibrary: "none",
|
|
336
370
|
apiClient: "none",
|
|
371
|
+
database: "none",
|
|
372
|
+
docker: false,
|
|
373
|
+
routeGroups: false,
|
|
337
374
|
linter: "eslint",
|
|
338
375
|
formatter: "prettier",
|
|
339
376
|
docsLanguage: "en",
|
|
@@ -355,6 +392,9 @@ export function getDefaultResponses(framework, { projectName = "my-app" } = {})
|
|
|
355
392
|
stateManagement: "none",
|
|
356
393
|
iconLibrary: "none",
|
|
357
394
|
apiClient: "none",
|
|
395
|
+
database: "none",
|
|
396
|
+
docker: false,
|
|
397
|
+
routeGroups: false,
|
|
358
398
|
linter: "eslint",
|
|
359
399
|
formatter: "prettier",
|
|
360
400
|
docsLanguage: "en",
|
|
@@ -390,6 +430,15 @@ export function sanitizeResponsesForFramework(framework, responses = {}) {
|
|
|
390
430
|
sanitized.reactCompiler = false;
|
|
391
431
|
}
|
|
392
432
|
}
|
|
433
|
+
if (!isPromptVisible(fw, "database")) {
|
|
434
|
+
sanitized.database = "none";
|
|
435
|
+
}
|
|
436
|
+
if (!isPromptVisible(fw, "docker")) {
|
|
437
|
+
sanitized.docker = false;
|
|
438
|
+
}
|
|
439
|
+
if (!isPromptVisible(fw, "routeGroups")) {
|
|
440
|
+
sanitized.routeGroups = false;
|
|
441
|
+
}
|
|
393
442
|
|
|
394
443
|
for (const [key, val] of Object.entries(sanitized)) {
|
|
395
444
|
if (val === undefined || val === null) continue;
|