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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/schema/lumen.config.v2.json +29 -0
  3. package/src/agent-context.js +71 -47
  4. package/src/cleanup.js +19 -1
  5. package/src/cli-args.js +26 -0
  6. package/src/dependencies.js +42 -15
  7. package/src/engine/capabilities.js +49 -0
  8. package/src/engine/composition.js +242 -17
  9. package/src/env.js +24 -6
  10. package/src/main.js +5 -5
  11. package/src/manifest/emit.js +13 -0
  12. package/src/manifest/schema.js +30 -0
  13. package/src/prompts.js +77 -4
  14. package/templates/conditional/database/prisma/db.js +14 -0
  15. package/templates/conditional/database/prisma/db.ts +16 -0
  16. package/templates/conditional/database/prisma/schema.prisma +9 -0
  17. package/templates/conditional/database/prisma/seed.js +16 -0
  18. package/templates/conditional/database/prisma/seed.ts +16 -0
  19. package/templates/conditional/docker/.dockerignore +10 -0
  20. package/templates/conditional/docker/Dockerfile +57 -0
  21. package/templates/conditional/docker/docker-compose.yml +35 -0
  22. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  23. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  24. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  25. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  26. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  27. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  28. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  29. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  30. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  31. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  32. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  33. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  34. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  35. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  36. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  37. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  38. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  39. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  40. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  41. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  42. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  43. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  44. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  45. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lumen",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
  },
@@ -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,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
 
@@ -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 fwName =
44
- typeof responses.framework === "string"
45
- ? responses.framework
46
- : (responses.framework?.name ?? responses.frameworkName ?? "react");
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
- testingDevDeps.push(
96
- "jest",
97
- "jest-environment-jsdom",
98
- "@testing-library/react",
99
- "@testing-library/jest-dom",
100
- "jsdom",
101
- "babel-jest",
102
- "@babel/preset-env",
103
- "@babel/preset-react",
104
- "@babel/preset-typescript"
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;