create-prisma 0.1.4 → 0.2.0

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 (92) hide show
  1. package/README.md +55 -16
  2. package/dist/cli.mjs +1 -1
  3. package/dist/{create-Dz9GFGFQ.mjs → create-DgN5mAMV.mjs} +1031 -444
  4. package/dist/index.d.mts +31 -57
  5. package/dist/index.mjs +10 -21
  6. package/package.json +2 -2
  7. package/templates/create/astro/.yarnrc.yml.hbs +3 -0
  8. package/templates/create/astro/README.md.hbs +35 -0
  9. package/templates/create/astro/astro.config.mjs +5 -0
  10. package/templates/create/astro/deno.json.hbs +5 -0
  11. package/templates/create/astro/package.json.hbs +22 -0
  12. package/templates/{init → create/astro}/prisma/schema.prisma.hbs +4 -1
  13. package/templates/create/astro/prisma/seed.ts.hbs +38 -0
  14. package/templates/{init/prisma.config.ts.hbs → create/astro/prisma.config.ts} +1 -0
  15. package/templates/create/astro/public/favicon.svg +14 -0
  16. package/templates/create/astro/src/env.d.ts +9 -0
  17. package/templates/create/astro/src/lib/prisma.ts.hbs +53 -0
  18. package/templates/create/astro/src/pages/api/users.ts.hbs +42 -0
  19. package/templates/create/astro/src/pages/index.astro.hbs +236 -0
  20. package/templates/create/astro/tsconfig.json +5 -0
  21. package/templates/create/hono/.yarnrc.yml.hbs +3 -0
  22. package/templates/create/hono/README.md.hbs +13 -48
  23. package/templates/create/hono/deno.json.hbs +5 -0
  24. package/templates/create/hono/package.json.hbs +3 -0
  25. package/templates/create/hono/prisma/schema.prisma.hbs +21 -0
  26. package/templates/create/hono/prisma/seed.ts.hbs +38 -0
  27. package/templates/create/hono/prisma.config.ts +13 -0
  28. package/templates/create/hono/src/index.ts.hbs +1 -1
  29. package/templates/{init/prisma/index.ts.hbs → create/hono/src/lib/prisma.ts.hbs} +14 -13
  30. package/templates/create/hono/tsconfig.json +1 -2
  31. package/templates/create/next/.yarnrc.yml.hbs +3 -0
  32. package/templates/create/next/README.md.hbs +8 -23
  33. package/templates/create/next/deno.json.hbs +12 -0
  34. package/templates/create/next/package.json.hbs +4 -0
  35. package/templates/create/next/prisma/schema.prisma.hbs +21 -0
  36. package/templates/create/next/prisma/seed.ts.hbs +38 -0
  37. package/templates/create/next/prisma.config.ts +13 -0
  38. package/templates/create/next/src/app/globals.css +136 -0
  39. package/templates/create/next/src/app/page.tsx.hbs +104 -0
  40. package/templates/create/next/src/lib/prisma.ts.hbs +53 -0
  41. package/templates/create/nuxt/.yarnrc.yml.hbs +3 -0
  42. package/templates/create/nuxt/README.md.hbs +32 -0
  43. package/templates/create/nuxt/app/app.vue +4 -0
  44. package/templates/create/nuxt/app/pages/index.vue.hbs +230 -0
  45. package/templates/create/nuxt/deno.json.hbs +5 -0
  46. package/templates/create/nuxt/nuxt.config.ts +5 -0
  47. package/templates/create/nuxt/package.json.hbs +27 -0
  48. package/templates/create/nuxt/prisma/schema.prisma.hbs +21 -0
  49. package/templates/create/nuxt/prisma/seed.ts.hbs +38 -0
  50. package/templates/create/nuxt/prisma.config.ts +13 -0
  51. package/templates/create/nuxt/public/robots.txt +2 -0
  52. package/templates/create/nuxt/server/api/users.get.ts.hbs +36 -0
  53. package/templates/create/nuxt/server/utils/prisma.ts.hbs +53 -0
  54. package/templates/create/nuxt/tsconfig.json +17 -0
  55. package/templates/create/svelte/.vscode/extensions.json +3 -0
  56. package/templates/create/svelte/.yarnrc.yml.hbs +3 -0
  57. package/templates/create/svelte/README.md.hbs +34 -0
  58. package/templates/create/svelte/deno.json.hbs +5 -0
  59. package/templates/create/svelte/package.json.hbs +28 -0
  60. package/templates/create/svelte/prisma/schema.prisma.hbs +21 -0
  61. package/templates/create/svelte/prisma/seed.ts.hbs +87 -0
  62. package/templates/create/svelte/prisma.config.ts +13 -0
  63. package/templates/create/svelte/src/app.d.ts +13 -0
  64. package/templates/create/svelte/src/app.html +11 -0
  65. package/templates/create/svelte/src/lib/assets/favicon.svg +1 -0
  66. package/templates/create/svelte/src/lib/index.ts +1 -0
  67. package/templates/create/svelte/src/lib/server/prisma.ts.hbs +53 -0
  68. package/templates/create/svelte/src/routes/+layout.svelte +11 -0
  69. package/templates/create/svelte/src/routes/+page.server.ts.hbs +28 -0
  70. package/templates/create/svelte/src/routes/+page.svelte.hbs +350 -0
  71. package/templates/create/svelte/static/robots.txt +3 -0
  72. package/templates/create/svelte/svelte.config.js +13 -0
  73. package/templates/create/svelte/tsconfig.json +20 -0
  74. package/templates/create/svelte/vite.config.ts +6 -0
  75. package/templates/create/turborepo/.yarnrc.yml.hbs +3 -0
  76. package/templates/create/turborepo/README.md.hbs +29 -0
  77. package/templates/create/turborepo/apps/api/package.json.hbs +21 -0
  78. package/templates/create/turborepo/apps/api/src/index.ts.hbs +45 -0
  79. package/templates/create/turborepo/apps/api/tsconfig.json +17 -0
  80. package/templates/create/turborepo/deno.json.hbs +5 -0
  81. package/templates/create/turborepo/package.json.hbs +24 -0
  82. package/templates/create/turborepo/packages/db/package.json.hbs +17 -0
  83. package/templates/create/turborepo/packages/db/prisma/schema.prisma.hbs +21 -0
  84. package/templates/create/turborepo/packages/db/prisma/seed.ts.hbs +38 -0
  85. package/templates/create/turborepo/packages/db/prisma.config.ts +13 -0
  86. package/templates/create/turborepo/packages/db/src/client.ts.hbs +58 -0
  87. package/templates/create/turborepo/packages/db/src/index.ts +2 -0
  88. package/templates/create/turborepo/packages/db/tsconfig.json +19 -0
  89. package/templates/create/turborepo/turbo.json +34 -0
  90. package/templates/create/next/app/globals.css +0 -47
  91. package/templates/create/next/app/page.tsx.hbs +0 -46
  92. /package/templates/create/next/{app → src/app}/layout.tsx.hbs +0 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
2
+ import { cancel, confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
3
3
  import fs from "fs-extra";
4
4
  import path from "node:path";
5
5
  import Handlebars from "handlebars";
@@ -9,82 +9,17 @@ import { z } from "zod";
9
9
  import { execa } from "execa";
10
10
  import { styleText } from "node:util";
11
11
 
12
- //#region src/templates/shared.ts
13
- Handlebars.registerHelper("eq", (left, right) => left === right);
14
- function findPackageRoot(startDir) {
15
- let currentDir = startDir;
16
- while (true) {
17
- if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
18
- const parentDir = path.dirname(currentDir);
19
- if (parentDir === currentDir) break;
20
- currentDir = parentDir;
21
- }
22
- throw new Error(`Unable to locate package root from: ${startDir}`);
23
- }
24
- function resolveTemplatesDir(relativeTemplatesDir) {
25
- const currentFilePath = fileURLToPath(import.meta.url);
26
- const packageRoot = findPackageRoot(path.dirname(currentFilePath));
27
- const templatePath = path.join(packageRoot, relativeTemplatesDir);
28
- if (!existsSync(templatePath)) throw new Error(`Template directory not found at: ${templatePath}`);
29
- return templatePath;
30
- }
31
- async function getTemplateFilesRecursively(dir) {
32
- const entries = await fs.readdir(dir, { withFileTypes: true });
33
- return (await Promise.all(entries.map(async (entry) => {
34
- const entryPath = path.join(dir, entry.name);
35
- if (entry.isDirectory()) return getTemplateFilesRecursively(entryPath);
36
- if (!entry.isFile()) return [];
37
- return [entryPath];
38
- }))).flat();
39
- }
40
- function stripHbsExtension(filePath) {
41
- return filePath.endsWith(".hbs") ? filePath.slice(0, -4) : filePath;
42
- }
43
- function ensureTrailingNewline(content) {
44
- return content.endsWith("\n") ? content : `${content}\n`;
45
- }
46
- async function renderTemplateFile(opts) {
47
- const { templateFilePath, outputPath, context } = opts;
48
- const templateContent = await fs.readFile(templateFilePath, "utf8");
49
- const outputContent = templateFilePath.endsWith(".hbs") ? Handlebars.compile(templateContent, {
50
- noEscape: true,
51
- strict: true
52
- })(context) : templateContent;
53
- await fs.outputFile(outputPath, ensureTrailingNewline(outputContent), "utf8");
54
- }
55
- async function renderTemplateTree(opts) {
56
- const { templateRoot, outputDir, context } = opts;
57
- const templateFiles = await getTemplateFilesRecursively(templateRoot);
58
- for (const templateFilePath of templateFiles) {
59
- const relativeOutputPath = stripHbsExtension(path.relative(templateRoot, templateFilePath));
60
- await renderTemplateFile({
61
- templateFilePath,
62
- outputPath: path.join(outputDir, relativeOutputPath),
63
- context
64
- });
65
- }
66
- }
67
-
68
- //#endregion
69
- //#region src/templates/render-create-template.ts
70
- function getCreateTemplateDir(template) {
71
- return resolveTemplatesDir(`templates/create/${template}`);
72
- }
73
- function createTemplateContext$1(projectName, schemaPreset, packageManager) {
74
- return {
75
- projectName,
76
- schemaPreset,
77
- packageManager
78
- };
79
- }
80
- async function scaffoldCreateTemplate(opts) {
81
- const { projectDir, projectName, template, schemaPreset, packageManager } = opts;
82
- await renderTemplateTree({
83
- templateRoot: getCreateTemplateDir(template),
84
- outputDir: projectDir,
85
- context: createTemplateContext$1(projectName, schemaPreset, packageManager)
86
- });
87
- }
12
+ //#region src/constants/dependencies.ts
13
+ const dependencyVersionMap = {
14
+ "@prisma/client": "^7.4.0",
15
+ "@prisma/adapter-pg": "^7.4.0",
16
+ "@prisma/adapter-mariadb": "^7.4.0",
17
+ "@prisma/adapter-better-sqlite3": "^7.4.0",
18
+ "@prisma/adapter-mssql": "^7.4.0",
19
+ dotenv: "^17.2.3",
20
+ "node-gyp": "^11.5.0",
21
+ prisma: "^7.4.0"
22
+ };
88
23
 
89
24
  //#endregion
90
25
  //#region src/types.ts
@@ -98,14 +33,45 @@ const databaseProviders = [
98
33
  const packageManagers = [
99
34
  "npm",
100
35
  "pnpm",
101
- "bun"
36
+ "yarn",
37
+ "bun",
38
+ "deno"
102
39
  ];
103
40
  const schemaPresets = ["empty", "basic"];
104
- const createTemplates = ["hono", "next"];
41
+ const createTemplates = [
42
+ "hono",
43
+ "next",
44
+ "svelte",
45
+ "astro",
46
+ "nuxt",
47
+ "turborepo"
48
+ ];
49
+ const createAddons = [
50
+ "skills",
51
+ "mcp",
52
+ "extension"
53
+ ];
54
+ const addonInstallScopes = ["project", "global"];
55
+ const extensionTargets = [
56
+ "vscode",
57
+ "cursor",
58
+ "windsurf"
59
+ ];
60
+ const prismaSkillNames = [
61
+ "prisma-cli",
62
+ "prisma-client-api",
63
+ "prisma-database-setup",
64
+ "prisma-upgrade-v7",
65
+ "prisma-postgres"
66
+ ];
105
67
  const DatabaseProviderSchema = z.enum(databaseProviders);
106
68
  const PackageManagerSchema = z.enum(packageManagers);
107
69
  const SchemaPresetSchema = z.enum(schemaPresets);
108
70
  const CreateTemplateSchema = z.enum(createTemplates);
71
+ const CreateAddonSchema = z.enum(createAddons);
72
+ const AddonInstallScopeSchema = z.enum(addonInstallScopes);
73
+ const ExtensionTargetSchema = z.enum(extensionTargets);
74
+ const PrismaSkillNameSchema = z.enum(prismaSkillNames);
109
75
  const DatabaseUrlSchema = z.string().trim().min(1, "Please enter a valid database URL");
110
76
  const CommonCommandOptionsSchema = z.object({
111
77
  yes: z.boolean().optional().describe("Skip prompts and accept default choices"),
@@ -120,19 +86,30 @@ const PrismaSetupOptionsSchema = z.object({
120
86
  generate: z.boolean().optional().describe("Generate Prisma Client after scaffolding"),
121
87
  schemaPreset: SchemaPresetSchema.optional().describe("Schema preset to scaffold in prisma/schema.prisma")
122
88
  });
123
- const InitCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
89
+ const PrismaSetupCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
124
90
  const CreateScaffoldOptionsSchema = z.object({
125
91
  name: z.string().trim().min(1, "Please enter a valid project name").optional().describe("Project name / directory"),
126
92
  template: CreateTemplateSchema.optional().describe("Project template"),
93
+ skills: z.boolean().optional().describe("Enable skills addon"),
94
+ mcp: z.boolean().optional().describe("Enable MCP addon"),
95
+ extension: z.boolean().optional().describe("Enable extension addon"),
127
96
  force: z.boolean().optional().describe("Allow scaffolding into a non-empty target directory")
128
97
  });
129
- const CreateCommandInputSchema = CommonCommandOptionsSchema.extend(CreateScaffoldOptionsSchema.shape).extend(PrismaSetupOptionsSchema.shape);
98
+ const CreateCommandInputSchema = PrismaSetupCommandInputSchema.extend(CreateScaffoldOptionsSchema.shape);
130
99
 
131
100
  //#endregion
132
101
  //#region src/utils/package-manager.ts
102
+ const packageManagerManifestValues = {
103
+ npm: "npm@10.9.0",
104
+ pnpm: "pnpm@10.16.1",
105
+ yarn: "yarn@4.13.0",
106
+ bun: "bun@1.3.9"
107
+ };
133
108
  function parseUserAgent(userAgent) {
134
109
  if (userAgent?.startsWith("pnpm")) return "pnpm";
110
+ if (userAgent?.startsWith("yarn")) return "yarn";
135
111
  if (userAgent?.startsWith("bun")) return "bun";
112
+ if (userAgent?.startsWith("deno")) return "deno";
136
113
  if (userAgent?.startsWith("npm")) return "npm";
137
114
  return null;
138
115
  }
@@ -147,12 +124,20 @@ async function detectFromPackageJson(projectDir) {
147
124
  if (!await fs.pathExists(packageJsonPath)) return null;
148
125
  return parsePackageManagerField((await fs.readJson(packageJsonPath)).packageManager);
149
126
  }
127
+ async function detectFromDenoConfig(projectDir) {
128
+ for (const configFile of ["deno.json", "deno.jsonc"]) if (await fs.pathExists(path.join(projectDir, configFile))) return "deno";
129
+ return null;
130
+ }
150
131
  async function detectFromLockfile(projectDir) {
151
132
  for (const check of [
152
133
  {
153
134
  manager: "pnpm",
154
135
  lockfile: "pnpm-lock.yaml"
155
136
  },
137
+ {
138
+ manager: "yarn",
139
+ lockfile: "yarn.lock"
140
+ },
156
141
  {
157
142
  manager: "bun",
158
143
  lockfile: "bun.lockb"
@@ -168,6 +153,10 @@ async function detectFromLockfile(projectDir) {
168
153
  {
169
154
  manager: "npm",
170
155
  lockfile: "npm-shrinkwrap.json"
156
+ },
157
+ {
158
+ manager: "deno",
159
+ lockfile: "deno.lock"
171
160
  }
172
161
  ]) if (await fs.pathExists(path.join(projectDir, check.lockfile))) return check.manager;
173
162
  return null;
@@ -177,49 +166,75 @@ async function detectPackageManager(projectDir = process.cwd()) {
177
166
  if (fromPackageJson) return fromPackageJson;
178
167
  const fromLockfile = await detectFromLockfile(projectDir);
179
168
  if (fromLockfile) return fromLockfile;
169
+ const fromDenoConfig = await detectFromDenoConfig(projectDir);
170
+ if (fromDenoConfig) return fromDenoConfig;
180
171
  const fromUserAgent = parseUserAgent(process.env.npm_config_user_agent);
181
172
  if (fromUserAgent) return fromUserAgent;
182
173
  return "npm";
183
174
  }
175
+ function getPackageManagerManifestValue(packageManager) {
176
+ if (!packageManager || packageManager === "deno") return;
177
+ return packageManagerManifestValues[packageManager];
178
+ }
179
+ function getDenoPrismaSpecifier() {
180
+ return `npm:prisma@${dependencyVersionMap.prisma.replace(/^[^0-9]*/, "")}`;
181
+ }
184
182
  function getInstallCommand(packageManager) {
183
+ if (packageManager === "deno") return "deno install --allow-scripts";
185
184
  return `${packageManager} install`;
186
185
  }
187
186
  function getRunScriptCommand(packageManager, scriptName) {
188
187
  switch (packageManager) {
188
+ case "deno": return `deno task ${scriptName}`;
189
189
  case "bun": return `bun run ${scriptName}`;
190
190
  case "pnpm": return `pnpm run ${scriptName}`;
191
+ case "yarn": return `yarn run ${scriptName}`;
191
192
  default: return `npm run ${scriptName}`;
192
193
  }
193
194
  }
194
195
  function getInstallArgs(packageManager) {
196
+ if (packageManager === "deno") return {
197
+ command: "deno",
198
+ args: ["install", "--allow-scripts"]
199
+ };
195
200
  return {
196
201
  command: packageManager,
197
202
  args: ["install"]
198
203
  };
199
204
  }
200
- function getPackageExecutor(packageManager) {
205
+ function getPackageExecutionArgs(packageManager, commandArgs) {
201
206
  switch (packageManager) {
202
207
  case "pnpm": return {
203
208
  command: "pnpm",
204
- args: ["dlx"]
209
+ args: ["dlx", ...commandArgs]
210
+ };
211
+ case "yarn": return {
212
+ command: "yarn",
213
+ args: ["dlx", ...commandArgs]
205
214
  };
206
215
  case "bun": return {
207
216
  command: "bunx",
208
- args: []
217
+ args: [...commandArgs]
209
218
  };
219
+ case "deno": {
220
+ const [packageName, ...args] = commandArgs;
221
+ if (!packageName) throw new Error("Package execution requires a package name.");
222
+ return {
223
+ command: "deno",
224
+ args: [
225
+ "run",
226
+ "-A",
227
+ `npm:${packageName}`,
228
+ ...args
229
+ ]
230
+ };
231
+ }
210
232
  default: return {
211
233
  command: "npx",
212
- args: []
234
+ args: [...commandArgs]
213
235
  };
214
236
  }
215
237
  }
216
- function getPackageExecutionArgs(packageManager, commandArgs) {
217
- const executor = getPackageExecutor(packageManager);
218
- return {
219
- command: executor.command,
220
- args: [...executor.args, ...commandArgs]
221
- };
222
- }
223
238
  function getPackageExecutionCommand(packageManager, commandArgs) {
224
239
  const execution = getPackageExecutionArgs(packageManager, commandArgs);
225
240
  return [execution.command, ...execution.args].join(" ");
@@ -230,6 +245,15 @@ function getPrismaCliArgs(packageManager, prismaArgs) {
230
245
  "prisma",
231
246
  ...prismaArgs
232
247
  ]);
248
+ if (packageManager === "deno") return {
249
+ command: "deno",
250
+ args: [
251
+ "run",
252
+ "-A",
253
+ getDenoPrismaSpecifier(),
254
+ ...prismaArgs
255
+ ]
256
+ };
233
257
  return getPackageExecutionArgs(packageManager, ["prisma", ...prismaArgs]);
234
258
  }
235
259
  function getPrismaCliCommand(packageManager, prismaArgs) {
@@ -238,259 +262,88 @@ function getPrismaCliCommand(packageManager, prismaArgs) {
238
262
  }
239
263
 
240
264
  //#endregion
241
- //#region src/tasks/prisma-postgres.ts
242
- const PRISMA_POSTGRES_TEMPORARY_NOTICE = "Prisma Postgres is temporary for 24 hours. Claim this database before it expires using CLAIM_URL.";
243
- const CREATE_DB_COMMAND_ARGS = ["create-db@latest", "--json"];
244
- function parseCreateDbJson(rawOutput) {
245
- const trimmed = rawOutput.trim();
246
- if (!trimmed) throw new Error("create-db returned empty output.");
247
- const jsonCandidates = [trimmed];
248
- const firstBrace = trimmed.indexOf("{");
249
- const lastBrace = trimmed.lastIndexOf("}");
250
- if (firstBrace !== -1 && lastBrace > firstBrace) jsonCandidates.push(trimmed.slice(firstBrace, lastBrace + 1));
251
- for (const candidate of jsonCandidates) try {
252
- return JSON.parse(candidate);
253
- } catch {}
254
- throw new Error(`Unable to parse create-db JSON output: ${trimmed}`);
265
+ //#region src/templates/shared.ts
266
+ Handlebars.registerHelper("eq", (left, right) => left === right);
267
+ Handlebars.registerHelper("installCommand", (packageManager) => packageManager ? getInstallCommand(packageManager) : "");
268
+ Handlebars.registerHelper("runScriptCommand", (packageManager, scriptName) => packageManager ? getRunScriptCommand(packageManager, scriptName) : "");
269
+ Handlebars.registerHelper("packageManagerManifestValue", (packageManager) => getPackageManagerManifestValue(packageManager) ?? "");
270
+ function findPackageRoot(startDir) {
271
+ let currentDir = startDir;
272
+ while (true) {
273
+ if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
274
+ const parentDir = path.dirname(currentDir);
275
+ if (parentDir === currentDir) break;
276
+ currentDir = parentDir;
277
+ }
278
+ throw new Error(`Unable to locate package root from: ${startDir}`);
255
279
  }
256
- function pickConnectionString(payload) {
257
- if (typeof payload.connectionString === "string" && payload.connectionString.length > 0) return payload.connectionString;
258
- if (typeof payload.databaseUrl === "string" && payload.databaseUrl.length > 0) return payload.databaseUrl;
280
+ function resolveTemplatesDir(relativeTemplatesDir) {
281
+ const currentFilePath = fileURLToPath(import.meta.url);
282
+ const packageRoot = findPackageRoot(path.dirname(currentFilePath));
283
+ const templatePath = path.join(packageRoot, relativeTemplatesDir);
284
+ if (!existsSync(templatePath)) throw new Error(`Template directory not found at: ${templatePath}`);
285
+ return templatePath;
259
286
  }
260
- function extractErrorMessage(payload, fallback) {
261
- if (typeof payload.message === "string" && payload.message.length > 0) return payload.message;
262
- if (typeof payload.error === "string" && payload.error.length > 0) return payload.error;
263
- return fallback;
287
+ async function getTemplateFilesRecursively(dir) {
288
+ const entries = await fs.readdir(dir, { withFileTypes: true });
289
+ return (await Promise.all(entries.map(async (entry) => {
290
+ const entryPath = path.join(dir, entry.name);
291
+ if (entry.isDirectory()) return getTemplateFilesRecursively(entryPath);
292
+ if (!entry.isFile()) return [];
293
+ return [entryPath];
294
+ }))).flat();
264
295
  }
265
- async function provisionPrismaPostgres(packageManager, projectDir = process.cwd()) {
266
- const command = getPackageExecutionArgs(packageManager, [...CREATE_DB_COMMAND_ARGS]);
267
- const commandString = getCreateDbCommand(packageManager);
268
- let stdout;
269
- try {
270
- stdout = (await execa(command.command, command.args, {
271
- cwd: projectDir,
272
- stdio: "pipe"
273
- })).stdout;
274
- } catch (error) {
275
- if (error instanceof Error && "stderr" in error) {
276
- const stderr = String(error.stderr ?? "").trim();
277
- const message = stderr.length > 0 ? stderr : error.message;
278
- throw new Error(`Failed to run ${commandString}: ${message}`);
279
- }
280
- throw error;
281
- }
282
- const payload = parseCreateDbJson(stdout);
283
- if (payload.success === false) throw new Error(extractErrorMessage(payload, "create-db reported failure."));
284
- const databaseUrl = pickConnectionString(payload);
285
- if (!databaseUrl) throw new Error("create-db did not return a connection string.");
286
- return {
287
- databaseUrl,
288
- claimUrl: typeof payload.claimUrl === "string" && payload.claimUrl.length > 0 ? payload.claimUrl : typeof payload.claimURL === "string" && payload.claimURL.length > 0 ? payload.claimURL : void 0
289
- };
296
+ function stripHbsExtension(filePath) {
297
+ return filePath.endsWith(".hbs") ? filePath.slice(0, -4) : filePath;
290
298
  }
291
- function getCreateDbCommand(packageManager) {
292
- return getPackageExecutionCommand(packageManager, [...CREATE_DB_COMMAND_ARGS]);
299
+ function ensureTrailingNewline(content) {
300
+ return content.endsWith("\n") ? content : `${content}\n`;
301
+ }
302
+ async function renderTemplateFile(opts) {
303
+ const { templateFilePath, outputPath, context } = opts;
304
+ const templateContent = await fs.readFile(templateFilePath, "utf8");
305
+ const outputContent = templateFilePath.endsWith(".hbs") ? Handlebars.compile(templateContent, {
306
+ noEscape: true,
307
+ strict: true
308
+ })(context) : templateContent;
309
+ if (templateFilePath.endsWith(".hbs") && outputContent.trim().length === 0) return;
310
+ await fs.outputFile(outputPath, ensureTrailingNewline(outputContent), "utf8");
311
+ }
312
+ async function renderTemplateTree(opts) {
313
+ const { templateRoot, outputDir, context } = opts;
314
+ const templateFiles = await getTemplateFilesRecursively(templateRoot);
315
+ for (const templateFilePath of templateFiles) {
316
+ const relativeOutputPath = stripHbsExtension(path.relative(templateRoot, templateFilePath));
317
+ await renderTemplateFile({
318
+ templateFilePath,
319
+ outputPath: path.join(outputDir, relativeOutputPath),
320
+ context
321
+ });
322
+ }
293
323
  }
294
324
 
295
325
  //#endregion
296
- //#region src/templates/render-init-templates.ts
297
- function getInitTemplatesDir() {
298
- return resolveTemplatesDir("templates/init");
326
+ //#region src/templates/render-create-template.ts
327
+ function getCreateTemplateDir(template) {
328
+ return resolveTemplatesDir(`templates/create/${template}`);
299
329
  }
300
- function createTemplateContext(provider, envVar, schemaPreset) {
330
+ function createTemplateContext(projectName, provider, schemaPreset, packageManager) {
301
331
  return {
302
- envVar,
332
+ projectName,
303
333
  provider,
304
- schemaPreset
334
+ schemaPreset,
335
+ packageManager
305
336
  };
306
337
  }
307
- async function scaffoldInitTemplates(projectDir, provider, envVar = "DATABASE_URL", schemaPreset = "empty") {
338
+ async function scaffoldCreateTemplate(opts) {
339
+ const { projectDir, projectName, template, provider, schemaPreset, packageManager } = opts;
308
340
  await renderTemplateTree({
309
- templateRoot: getInitTemplatesDir(),
341
+ templateRoot: getCreateTemplateDir(template),
310
342
  outputDir: projectDir,
311
- context: createTemplateContext(provider, envVar, schemaPreset)
343
+ context: createTemplateContext(projectName, provider, schemaPreset, packageManager)
312
344
  });
313
- return {
314
- schemaPath: path.join(projectDir, "prisma/schema.prisma"),
315
- configPath: path.join(projectDir, "prisma.config.ts"),
316
- singletonPath: path.join(projectDir, "prisma/index.ts")
317
- };
318
345
  }
319
346
 
320
- //#endregion
321
- //#region src/tasks/init-prisma.ts
322
- const prismaManagedFiles = [
323
- "schema.prisma",
324
- "prisma/schema.prisma",
325
- "prisma/index.ts",
326
- "prisma.config.ts"
327
- ];
328
- const prismaTemplateFiles = [
329
- "prisma/schema.prisma",
330
- "prisma/index.ts",
331
- "prisma.config.ts"
332
- ];
333
- var PrismaAlreadyInitializedError = class extends Error {
334
- existingFiles;
335
- constructor(existingFiles) {
336
- super(`This project already appears to be Prisma-initialized: ${existingFiles.join(", ")}`);
337
- this.name = "PrismaAlreadyInitializedError";
338
- this.existingFiles = existingFiles;
339
- }
340
- };
341
- function findExistingPrismaFiles(projectDir = process.cwd()) {
342
- return prismaManagedFiles.map((relativePath) => path.join(projectDir, relativePath)).filter((absolutePath) => fs.existsSync(absolutePath));
343
- }
344
- function canReusePrismaFiles(projectDir = process.cwd()) {
345
- return prismaTemplateFiles.every((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
346
- }
347
- function getDefaultDatabaseUrl(provider) {
348
- switch (provider) {
349
- case "postgresql": return "postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public";
350
- case "cockroachdb": return "postgresql://johndoe:randompassword@localhost:26257/mydb?schema=public";
351
- case "mysql": return "mysql://johndoe:randompassword@localhost:3306/mydb";
352
- case "sqlite": return "file:./dev.db";
353
- case "sqlserver": return "sqlserver://localhost:1433;database=mydb;user=SA;password=randompassword;";
354
- default: {
355
- const exhaustiveCheck = provider;
356
- throw new Error(`Unsupported provider: ${String(exhaustiveCheck)}`);
357
- }
358
- }
359
- }
360
- function escapeRegExp(value) {
361
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
362
- }
363
- function hasEnvVar(content, envVarName) {
364
- const escapedName = escapeRegExp(envVarName);
365
- return new RegExp(`(^|\\n)\\s*${escapedName}\\s*=`).test(content);
366
- }
367
- function hasEnvComment(content, comment) {
368
- const escapedComment = escapeRegExp(comment);
369
- return new RegExp(`(^|\\n)\\s*#\\s*${escapedComment}\\s*(?=\\n|$)`).test(content);
370
- }
371
- async function ensureEnvVarInEnv(projectDir, envVarName, envVarValue, opts) {
372
- const envPath = path.join(projectDir, ".env");
373
- const envLine = `${envVarName}="${envVarValue}"`;
374
- if (!await fs.pathExists(envPath)) {
375
- await fs.writeFile(envPath, `${envLine}\n`, "utf8");
376
- return {
377
- envPath,
378
- status: "created"
379
- };
380
- }
381
- const existingContent = await fs.readFile(envPath, "utf8");
382
- if (hasEnvVar(existingContent, envVarName)) {
383
- if (opts.mode === "keep-existing") return {
384
- envPath,
385
- status: "existing"
386
- };
387
- const escapedName = escapeRegExp(envVarName);
388
- const lineRegex = new RegExp(`(^|\\n)\\s*${escapedName}\\s*=.*(?=\\n|$)`, "m");
389
- const updatedContent = existingContent.replace(lineRegex, `$1${envLine}`);
390
- if (updatedContent === existingContent) return {
391
- envPath,
392
- status: "existing"
393
- };
394
- await fs.writeFile(envPath, updatedContent, "utf8");
395
- return {
396
- envPath,
397
- status: "updated"
398
- };
399
- }
400
- const insertion = `${existingContent.endsWith("\n") ? "" : "\n"}${opts.comment ? `\n# ${opts.comment}\n` : "\n"}${envLine}\n`;
401
- await fs.appendFile(envPath, insertion, "utf8");
402
- return {
403
- envPath,
404
- status: "appended"
405
- };
406
- }
407
- async function ensureEnvComment(projectDir, comment) {
408
- const envPath = path.join(projectDir, ".env");
409
- const commentLine = `# ${comment}`;
410
- if (!await fs.pathExists(envPath)) {
411
- await fs.writeFile(envPath, `${commentLine}\n`, "utf8");
412
- return;
413
- }
414
- const existingContent = await fs.readFile(envPath, "utf8");
415
- if (hasEnvComment(existingContent, comment)) return;
416
- const separator = existingContent.endsWith("\n") ? "" : "\n";
417
- await fs.appendFile(envPath, `${separator}${commentLine}\n`, "utf8");
418
- }
419
- function hasGitignoreEntry(content, entry) {
420
- const escapedEntry = escapeRegExp(entry);
421
- const escapedWithLeadingSlash = escapeRegExp(`/${entry}`);
422
- return new RegExp(`(^|\\n)\\s*(?:${escapedEntry}|${escapedWithLeadingSlash})\\s*(?=\\n|$)`).test(content);
423
- }
424
- async function ensureGitignoreEntry(projectDir, entry) {
425
- const gitignorePath = path.join(projectDir, ".gitignore");
426
- if (!await fs.pathExists(gitignorePath)) {
427
- await fs.writeFile(gitignorePath, `${entry}\n`, "utf8");
428
- return {
429
- gitignorePath,
430
- status: "created"
431
- };
432
- }
433
- const existingContent = await fs.readFile(gitignorePath, "utf8");
434
- if (hasGitignoreEntry(existingContent, entry)) return {
435
- gitignorePath,
436
- status: "existing"
437
- };
438
- const separator = existingContent.endsWith("\n") ? "" : "\n";
439
- await fs.appendFile(gitignorePath, `${separator}${entry}\n`, "utf8");
440
- return {
441
- gitignorePath,
442
- status: "appended"
443
- };
444
- }
445
- async function initializePrismaFiles(options) {
446
- const projectDir = options.projectDir ?? process.cwd();
447
- const prismaFilesMode = options.prismaFilesMode ?? "create";
448
- const existingPrismaFiles = findExistingPrismaFiles(projectDir);
449
- if (prismaFilesMode === "create" && existingPrismaFiles.length > 0) throw new PrismaAlreadyInitializedError(existingPrismaFiles);
450
- if (prismaFilesMode === "reuse" && existingPrismaFiles.length === 0) throw new Error("Cannot reuse Prisma files because no existing Prisma files were found.");
451
- if (prismaFilesMode === "reuse" && !canReusePrismaFiles(projectDir)) throw new Error("Cannot reuse Prisma files because required files are missing.");
452
- const schemaPath = path.join(projectDir, "prisma/schema.prisma");
453
- const configPath = path.join(projectDir, "prisma.config.ts");
454
- const singletonPath = path.join(projectDir, "prisma/index.ts");
455
- if (prismaFilesMode !== "reuse") await scaffoldInitTemplates(projectDir, options.provider, "DATABASE_URL", options.schemaPreset ?? "empty");
456
- const envResult = await ensureEnvVarInEnv(projectDir, "DATABASE_URL", options.databaseUrl ?? getDefaultDatabaseUrl(options.provider), {
457
- mode: options.databaseUrl ? "upsert" : "keep-existing",
458
- comment: "Added by create-prisma init"
459
- });
460
- let claimEnvStatus;
461
- if (options.claimUrl) {
462
- claimEnvStatus = (await ensureEnvVarInEnv(projectDir, "CLAIM_URL", options.claimUrl, {
463
- mode: "upsert",
464
- comment: PRISMA_POSTGRES_TEMPORARY_NOTICE
465
- })).status;
466
- await ensureEnvComment(projectDir, PRISMA_POSTGRES_TEMPORARY_NOTICE);
467
- }
468
- const gitignoreResult = await ensureGitignoreEntry(projectDir, "prisma/generated");
469
- return {
470
- schemaPath,
471
- configPath,
472
- singletonPath,
473
- prismaFilesMode,
474
- envPath: envResult.envPath,
475
- envStatus: envResult.status,
476
- gitignorePath: gitignoreResult.gitignorePath,
477
- gitignoreStatus: gitignoreResult.status,
478
- claimEnvStatus
479
- };
480
- }
481
-
482
- //#endregion
483
- //#region src/constants/dependencies.ts
484
- const dependencyVersionMap = {
485
- "@prisma/client": "^7.4.0",
486
- "@prisma/adapter-pg": "^7.4.0",
487
- "@prisma/adapter-mariadb": "^7.4.0",
488
- "@prisma/adapter-better-sqlite3": "^7.4.0",
489
- "@prisma/adapter-mssql": "^7.4.0",
490
- dotenv: "^17.2.3",
491
- prisma: "^7.4.0"
492
- };
493
-
494
347
  //#endregion
495
348
  //#region src/constants/db-packages.ts
496
349
  function getDbPackages(provider) {
@@ -509,11 +362,23 @@ function getDbPackages(provider) {
509
362
 
510
363
  //#endregion
511
364
  //#region src/tasks/install.ts
512
- const prismaScriptMap = {
513
- "db:generate": "prisma generate",
514
- "db:push": "prisma db push",
515
- "db:migrate": "prisma migrate dev"
516
- };
365
+ function getPrismaScriptMap(packageManager) {
366
+ if (packageManager === "deno") {
367
+ const prismaSpecifier = getDenoPrismaSpecifier();
368
+ return {
369
+ "db:generate": `deno run -A ${prismaSpecifier} generate`,
370
+ "db:push": `deno run -A ${prismaSpecifier} db push`,
371
+ "db:migrate": `deno run -A ${prismaSpecifier} migrate dev`,
372
+ "db:seed": `deno run -A ${prismaSpecifier} db seed`
373
+ };
374
+ }
375
+ return {
376
+ "db:generate": "prisma generate",
377
+ "db:push": "prisma db push",
378
+ "db:migrate": "prisma migrate dev",
379
+ "db:seed": "prisma db seed"
380
+ };
381
+ }
517
382
  function getVersion(packageName) {
518
383
  return dependencyVersionMap[packageName];
519
384
  }
@@ -565,11 +430,13 @@ async function addPackageDependency(opts) {
565
430
  existingScripts
566
431
  };
567
432
  }
568
- async function writePrismaDependencies(provider, projectDir = process.cwd()) {
433
+ async function writePrismaDependencies(provider, packageManager, projectDir = process.cwd()) {
569
434
  const dependencies = ["@prisma/client", "dotenv"];
570
435
  const devDependencies = ["prisma"];
571
436
  const { adapterPackage } = getDbPackages(provider);
572
437
  dependencies.push(adapterPackage);
438
+ if (provider === "sqlite" && packageManager === "deno") devDependencies.push("node-gyp");
439
+ const prismaScriptMap = getPrismaScriptMap(packageManager);
573
440
  const scriptWriteResult = await addPackageDependency({
574
441
  dependencies,
575
442
  devDependencies,
@@ -595,19 +462,83 @@ async function installProjectDependencies(packageManager, projectDir = process.c
595
462
  }
596
463
 
597
464
  //#endregion
598
- //#region src/ui/branding.ts
599
- const prismaTitle = `${styleText(["bold", "cyan"], "Create")} ${styleText(["bold", "magenta"], "Prisma")}`;
600
- function getCreatePrismaIntro() {
601
- return prismaTitle;
465
+ //#region src/tasks/prisma-postgres.ts
466
+ const PRISMA_POSTGRES_TEMPORARY_NOTICE = "Prisma Postgres is temporary for 24 hours. Claim this database before it expires using CLAIM_URL.";
467
+ const CREATE_DB_COMMAND_ARGS = ["create-db@latest", "--json"];
468
+ function parseCreateDbJson(rawOutput) {
469
+ const trimmed = rawOutput.trim();
470
+ if (!trimmed) throw new Error("create-db returned empty output.");
471
+ const jsonCandidates = [trimmed];
472
+ const firstBrace = trimmed.indexOf("{");
473
+ const lastBrace = trimmed.lastIndexOf("}");
474
+ if (firstBrace !== -1 && lastBrace > firstBrace) jsonCandidates.push(trimmed.slice(firstBrace, lastBrace + 1));
475
+ for (const candidate of jsonCandidates) try {
476
+ return JSON.parse(candidate);
477
+ } catch {}
478
+ throw new Error(`Unable to parse create-db JSON output: ${trimmed}`);
479
+ }
480
+ function pickConnectionString(payload) {
481
+ if (typeof payload.connectionString === "string" && payload.connectionString.length > 0) return payload.connectionString;
482
+ if (typeof payload.databaseUrl === "string" && payload.databaseUrl.length > 0) return payload.databaseUrl;
483
+ }
484
+ function extractErrorMessage(payload, fallback) {
485
+ if (typeof payload.message === "string" && payload.message.length > 0) return payload.message;
486
+ if (typeof payload.error === "string" && payload.error.length > 0) return payload.error;
487
+ return fallback;
488
+ }
489
+ async function provisionPrismaPostgres(packageManager, projectDir = process.cwd()) {
490
+ const command = getPackageExecutionArgs(packageManager, [...CREATE_DB_COMMAND_ARGS]);
491
+ const commandString = getCreateDbCommand(packageManager);
492
+ let stdout;
493
+ try {
494
+ stdout = (await execa(command.command, command.args, {
495
+ cwd: projectDir,
496
+ stdio: "pipe"
497
+ })).stdout;
498
+ } catch (error) {
499
+ if (error instanceof Error && "stderr" in error) {
500
+ const stderr = String(error.stderr ?? "").trim();
501
+ const message = stderr.length > 0 ? stderr : error.message;
502
+ throw new Error(`Failed to run ${commandString}: ${message}`);
503
+ }
504
+ throw error;
505
+ }
506
+ const payload = parseCreateDbJson(stdout);
507
+ if (payload.success === false) throw new Error(extractErrorMessage(payload, "create-db reported failure."));
508
+ const databaseUrl = pickConnectionString(payload);
509
+ if (!databaseUrl) throw new Error("create-db did not return a connection string.");
510
+ return {
511
+ databaseUrl,
512
+ claimUrl: typeof payload.claimUrl === "string" && payload.claimUrl.length > 0 ? payload.claimUrl : typeof payload.claimURL === "string" && payload.claimURL.length > 0 ? payload.claimURL : void 0
513
+ };
514
+ }
515
+ function getCreateDbCommand(packageManager) {
516
+ return getPackageExecutionCommand(packageManager, [...CREATE_DB_COMMAND_ARGS]);
602
517
  }
603
518
 
604
519
  //#endregion
605
- //#region src/commands/init.ts
520
+ //#region src/tasks/setup-prisma.ts
606
521
  const DEFAULT_DATABASE_PROVIDER = "postgresql";
607
522
  const DEFAULT_SCHEMA_PRESET$1 = "empty";
608
523
  const DEFAULT_PRISMA_POSTGRES = true;
609
524
  const DEFAULT_INSTALL = true;
610
525
  const DEFAULT_GENERATE = true;
526
+ const requiredPrismaFileGroups = [
527
+ ["prisma/schema.prisma", "packages/db/prisma/schema.prisma"],
528
+ ["prisma/seed.ts", "packages/db/prisma/seed.ts"],
529
+ ["prisma.config.ts", "packages/db/prisma.config.ts"],
530
+ [
531
+ "src/lib/prisma.ts",
532
+ "src/lib/server/prisma.ts",
533
+ "server/utils/prisma.ts",
534
+ "packages/db/src/client.ts"
535
+ ]
536
+ ];
537
+ async function resolvePrismaProjectDir(projectDir) {
538
+ const monorepoDbDir = path.join(projectDir, "packages/db");
539
+ if (await fs.pathExists(path.join(monorepoDbDir, "prisma/schema.prisma"))) return monorepoDbDir;
540
+ return projectDir;
541
+ }
611
542
  async function promptForDatabaseProvider() {
612
543
  const databaseProvider = await select({
613
544
  message: "Select your database",
@@ -645,6 +576,7 @@ async function promptForDatabaseProvider() {
645
576
  function getPackageManagerHint(option, detected) {
646
577
  if (option === detected) return "Detected";
647
578
  if (option === "bun") return "Fast runtime + package manager";
579
+ if (option === "deno") return "Runtime + package manager";
648
580
  }
649
581
  async function promptForPackageManager(detectedPackageManager) {
650
582
  const packageManager = await select({
@@ -661,10 +593,20 @@ async function promptForPackageManager(detectedPackageManager) {
661
593
  label: "pnpm",
662
594
  hint: getPackageManagerHint("pnpm", detectedPackageManager)
663
595
  },
596
+ {
597
+ value: "yarn",
598
+ label: "yarn",
599
+ hint: getPackageManagerHint("yarn", detectedPackageManager)
600
+ },
664
601
  {
665
602
  value: "bun",
666
603
  label: "bun",
667
604
  hint: getPackageManagerHint("bun", detectedPackageManager)
605
+ },
606
+ {
607
+ value: "deno",
608
+ label: "deno",
609
+ hint: getPackageManagerHint("deno", detectedPackageManager)
668
610
  }
669
611
  ]
670
612
  });
@@ -696,50 +638,6 @@ async function promptForPrismaPostgres() {
696
638
  }
697
639
  return Boolean(shouldUsePrismaPostgres);
698
640
  }
699
- async function promptForPrismaFilesMode(existingFiles, canReuseExistingPrismaFiles, baseDir) {
700
- const mode = await select({
701
- message: `Prisma already exists (${existingFiles.map((filePath) => formatCreatedPath(filePath, baseDir)).join(", ")}). How should we continue?`,
702
- initialValue: canReuseExistingPrismaFiles ? "reuse" : "overwrite",
703
- options: canReuseExistingPrismaFiles ? [
704
- {
705
- value: "reuse",
706
- label: "Keep existing Prisma files",
707
- hint: "Recommended"
708
- },
709
- {
710
- value: "overwrite",
711
- label: "Overwrite Prisma files"
712
- },
713
- {
714
- value: "cancel",
715
- label: "Cancel"
716
- }
717
- ] : [{
718
- value: "overwrite",
719
- label: "Repair and overwrite Prisma files",
720
- hint: "Recommended"
721
- }, {
722
- value: "cancel",
723
- label: "Cancel"
724
- }]
725
- });
726
- if (isCancel(mode)) {
727
- cancel("Operation cancelled.");
728
- return;
729
- }
730
- if (mode === "cancel") {
731
- cancel("Operation cancelled.");
732
- return;
733
- }
734
- if (mode !== "reuse" && mode !== "overwrite") {
735
- cancel("Operation cancelled.");
736
- return;
737
- }
738
- return mode;
739
- }
740
- function formatCreatedPath(filePath, baseDir) {
741
- return path.relative(baseDir, filePath);
742
- }
743
641
  function getCommandErrorMessage(error) {
744
642
  if (error instanceof Error && "stderr" in error) {
745
643
  const stderr = String(error.stderr ?? "").trim();
@@ -747,25 +645,14 @@ function getCommandErrorMessage(error) {
747
645
  }
748
646
  return error instanceof Error ? error.message : String(error);
749
647
  }
750
- async function collectInitContext(rawInput = {}, options = {}) {
648
+ async function collectPrismaSetupContext(input, options = {}) {
751
649
  const projectDir = path.resolve(options.projectDir ?? process.cwd());
752
- const input = InitCommandInputSchema.parse(rawInput);
753
650
  const useDefaults = input.yes === true;
754
651
  const verbose = input.verbose === true;
755
652
  const shouldGenerate = input.generate ?? DEFAULT_GENERATE;
756
- if (!options.skipIntro) intro(getCreatePrismaIntro());
757
- let prismaFilesMode = "create";
758
- const existingPrismaFiles = findExistingPrismaFiles(projectDir);
759
- const canReuseExistingPrismaFiles = canReusePrismaFiles(projectDir);
760
- if (existingPrismaFiles.length > 0) if (useDefaults) prismaFilesMode = canReuseExistingPrismaFiles ? "reuse" : "overwrite";
761
- else {
762
- const selectedMode = await promptForPrismaFilesMode(existingPrismaFiles, canReuseExistingPrismaFiles, projectDir);
763
- if (!selectedMode) return;
764
- prismaFilesMode = selectedMode;
765
- }
766
653
  const databaseProvider = input.provider ?? (useDefaults ? DEFAULT_DATABASE_PROVIDER : await promptForDatabaseProvider());
767
654
  if (!databaseProvider) return;
768
- const schemaPreset = input.schemaPreset ?? DEFAULT_SCHEMA_PRESET$1;
655
+ const schemaPreset = input.schemaPreset ?? options.defaultSchemaPreset ?? DEFAULT_SCHEMA_PRESET$1;
769
656
  const databaseUrl = input.databaseUrl;
770
657
  let shouldUsePrismaPostgres = false;
771
658
  if (databaseProvider === "postgresql" && !databaseUrl) {
@@ -782,7 +669,6 @@ async function collectInitContext(rawInput = {}, options = {}) {
782
669
  projectDir,
783
670
  verbose,
784
671
  shouldGenerate,
785
- prismaFilesMode,
786
672
  databaseProvider,
787
673
  schemaPreset,
788
674
  databaseUrl,
@@ -791,6 +677,158 @@ async function collectInitContext(rawInput = {}, options = {}) {
791
677
  shouldInstall
792
678
  };
793
679
  }
680
+ function getDefaultDatabaseUrl(provider) {
681
+ switch (provider) {
682
+ case "postgresql": return "postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public";
683
+ case "cockroachdb": return "postgresql://johndoe:randompassword@localhost:26257/mydb?schema=public";
684
+ case "mysql": return "mysql://johndoe:randompassword@localhost:3306/mydb";
685
+ case "sqlite": return "file:./dev.db";
686
+ case "sqlserver": return "sqlserver://localhost:1433;database=mydb;user=SA;password=randompassword;";
687
+ default: {
688
+ const exhaustiveCheck = provider;
689
+ throw new Error(`Unsupported provider: ${String(exhaustiveCheck)}`);
690
+ }
691
+ }
692
+ }
693
+ function escapeRegExp(value) {
694
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
695
+ }
696
+ function escapeEnvValue(value) {
697
+ if (/[\r\n]/.test(value)) throw new Error("Environment variable values must be single-line.");
698
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
699
+ }
700
+ function hasEnvVar(content, envVarName) {
701
+ const escapedName = escapeRegExp(envVarName);
702
+ return new RegExp(`(^|\\n)\\s*${escapedName}\\s*=`).test(content);
703
+ }
704
+ function hasEnvComment(content, comment) {
705
+ const escapedComment = escapeRegExp(comment);
706
+ return new RegExp(`(^|\\n)\\s*#\\s*${escapedComment}\\s*(?=\\n|$)`).test(content);
707
+ }
708
+ async function ensureEnvVarInEnv(projectDir, envVarName, envVarValue, opts) {
709
+ const envPath = path.join(projectDir, ".env");
710
+ const envLine = `${envVarName}="${escapeEnvValue(envVarValue)}"`;
711
+ if (!await fs.pathExists(envPath)) {
712
+ const content = opts.comment ? `# ${opts.comment}\n${envLine}\n` : `${envLine}\n`;
713
+ await fs.writeFile(envPath, content, "utf8");
714
+ return {
715
+ envPath,
716
+ status: "created"
717
+ };
718
+ }
719
+ const existingContent = await fs.readFile(envPath, "utf8");
720
+ if (hasEnvVar(existingContent, envVarName)) {
721
+ if (opts.mode === "keep-existing") return {
722
+ envPath,
723
+ status: "existing"
724
+ };
725
+ const escapedName = escapeRegExp(envVarName);
726
+ const lineRegex = new RegExp(`(^|\\n)\\s*${escapedName}\\s*=.*(?=\\n|$)`, "gm");
727
+ const updatedContent = existingContent.replace(lineRegex, `$1${envLine}`);
728
+ if (updatedContent === existingContent) return {
729
+ envPath,
730
+ status: "existing"
731
+ };
732
+ await fs.writeFile(envPath, updatedContent, "utf8");
733
+ return {
734
+ envPath,
735
+ status: "updated"
736
+ };
737
+ }
738
+ const insertion = `${existingContent.endsWith("\n") ? "" : "\n"}${opts.comment ? `\n# ${opts.comment}\n` : "\n"}${envLine}\n`;
739
+ await fs.appendFile(envPath, insertion, "utf8");
740
+ return {
741
+ envPath,
742
+ status: "appended"
743
+ };
744
+ }
745
+ async function ensureEnvComment(projectDir, comment) {
746
+ const envPath = path.join(projectDir, ".env");
747
+ const commentLine = `# ${comment}`;
748
+ if (!await fs.pathExists(envPath)) {
749
+ await fs.writeFile(envPath, `${commentLine}\n`, "utf8");
750
+ return;
751
+ }
752
+ const existingContent = await fs.readFile(envPath, "utf8");
753
+ if (hasEnvComment(existingContent, comment)) return;
754
+ const separator = existingContent.endsWith("\n") ? "" : "\n";
755
+ await fs.appendFile(envPath, `${separator}${commentLine}\n`, "utf8");
756
+ }
757
+ function hasGitignoreEntry(content, entry) {
758
+ const escapedEntry = escapeRegExp(entry);
759
+ const escapedWithLeadingSlash = escapeRegExp(`/${entry}`);
760
+ const escapedWithTrailingSlash = escapeRegExp(`${entry}/`);
761
+ const escapedWithLeadingAndTrailingSlash = escapeRegExp(`/${entry}/`);
762
+ return new RegExp(`(^|\\n)\\s*(?:${escapedEntry}|${escapedWithLeadingSlash}|${escapedWithTrailingSlash}|${escapedWithLeadingAndTrailingSlash})\\s*(?=\\n|$)`).test(content);
763
+ }
764
+ async function ensureGitignoreEntry(projectDir, entry) {
765
+ const gitignorePath = path.join(projectDir, ".gitignore");
766
+ if (!await fs.pathExists(gitignorePath)) {
767
+ await fs.writeFile(gitignorePath, `${entry}\n`, "utf8");
768
+ return {
769
+ gitignorePath,
770
+ status: "created"
771
+ };
772
+ }
773
+ const existingContent = await fs.readFile(gitignorePath, "utf8");
774
+ if (hasGitignoreEntry(existingContent, entry)) return {
775
+ gitignorePath,
776
+ status: "existing"
777
+ };
778
+ const separator = existingContent.endsWith("\n") ? "" : "\n";
779
+ await fs.appendFile(gitignorePath, `${separator}${entry}\n`, "utf8");
780
+ return {
781
+ gitignorePath,
782
+ status: "appended"
783
+ };
784
+ }
785
+ async function ensureRequiredPrismaFiles(projectDir) {
786
+ const missingFiles = [];
787
+ for (const candidates of requiredPrismaFileGroups) {
788
+ let foundCandidate = false;
789
+ for (const relativePath of candidates) {
790
+ const absolutePath = path.join(projectDir, relativePath);
791
+ if (await fs.pathExists(absolutePath)) {
792
+ foundCandidate = true;
793
+ break;
794
+ }
795
+ }
796
+ if (!foundCandidate) missingFiles.push(candidates.join(" or "));
797
+ }
798
+ if (missingFiles.length > 0) throw new Error(`Template is missing required Prisma files: ${missingFiles.join(", ")}`);
799
+ }
800
+ async function finalizePrismaFiles(options) {
801
+ const projectDir = options.projectDir ?? process.cwd();
802
+ const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
803
+ const schemaPath = path.join(prismaProjectDir, "prisma/schema.prisma");
804
+ const configPath = path.join(prismaProjectDir, "prisma.config.ts");
805
+ await ensureRequiredPrismaFiles(projectDir);
806
+ const singletonPath = await fs.pathExists(path.join(prismaProjectDir, "src/lib/prisma.ts")) ? path.join(prismaProjectDir, "src/lib/prisma.ts") : await fs.pathExists(path.join(prismaProjectDir, "src/lib/server/prisma.ts")) ? path.join(prismaProjectDir, "src/lib/server/prisma.ts") : await fs.pathExists(path.join(prismaProjectDir, "server/utils/prisma.ts")) ? path.join(prismaProjectDir, "server/utils/prisma.ts") : path.join(prismaProjectDir, "src/client.ts");
807
+ const generatedDir = await fs.pathExists(path.join(prismaProjectDir, "server/utils/prisma.ts")) ? "server/generated" : "src/generated";
808
+ const envResult = await ensureEnvVarInEnv(prismaProjectDir, "DATABASE_URL", options.databaseUrl ?? getDefaultDatabaseUrl(options.provider), {
809
+ mode: options.databaseUrl ? "upsert" : "keep-existing",
810
+ comment: "Added by create-prisma"
811
+ });
812
+ let claimEnvStatus;
813
+ if (options.claimUrl) {
814
+ claimEnvStatus = (await ensureEnvVarInEnv(prismaProjectDir, "CLAIM_URL", options.claimUrl, {
815
+ mode: "upsert",
816
+ comment: PRISMA_POSTGRES_TEMPORARY_NOTICE
817
+ })).status;
818
+ await ensureEnvComment(prismaProjectDir, PRISMA_POSTGRES_TEMPORARY_NOTICE);
819
+ }
820
+ const gitignoreResult = await ensureGitignoreEntry(prismaProjectDir, generatedDir);
821
+ return {
822
+ schemaPath,
823
+ configPath,
824
+ singletonPath,
825
+ envPath: envResult.envPath,
826
+ envStatus: envResult.status,
827
+ gitignorePath: gitignoreResult.gitignorePath,
828
+ gitignoreStatus: gitignoreResult.status,
829
+ claimEnvStatus
830
+ };
831
+ }
794
832
  async function provisionPrismaPostgresIfNeeded(context, projectDir) {
795
833
  if (!context.shouldUsePrismaPostgres) return { databaseUrl: context.databaseUrl };
796
834
  const createDbCommand = getCreateDbCommand(context.packageManager);
@@ -813,8 +851,9 @@ async function provisionPrismaPostgresIfNeeded(context, projectDir) {
813
851
  }
814
852
  }
815
853
  async function writeDependenciesForContext(context, projectDir) {
854
+ const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
816
855
  try {
817
- return await writePrismaDependencies(context.databaseProvider, projectDir);
856
+ return await writePrismaDependencies(context.databaseProvider, context.packageManager, prismaProjectDir);
818
857
  } catch (error) {
819
858
  cancel(getCommandErrorMessage(error));
820
859
  return;
@@ -846,22 +885,18 @@ async function installDependenciesForContext(context, projectDir) {
846
885
  return false;
847
886
  }
848
887
  }
849
- async function initializePrismaFilesForContext(context, projectDir, provisionResult) {
888
+ async function finalizePrismaFilesForContext(context, projectDir, provisionResult) {
850
889
  const initSpinner = spinner();
851
890
  initSpinner.start("Preparing Prisma files...");
852
891
  try {
853
- const initResult = await initializePrismaFiles({
892
+ const finalizeResult = await finalizePrismaFiles({
854
893
  provider: context.databaseProvider,
855
894
  databaseUrl: provisionResult.databaseUrl,
856
895
  claimUrl: provisionResult.claimUrl,
857
- schemaPreset: context.schemaPreset,
858
- prismaFilesMode: context.prismaFilesMode,
859
896
  projectDir
860
897
  });
861
- if (initResult.prismaFilesMode === "overwrite") initSpinner.stop("Prisma files updated.");
862
- else if (initResult.prismaFilesMode === "reuse") initSpinner.stop("Using existing Prisma files.");
863
- else initSpinner.stop("Prisma files ready.");
864
- return initResult;
898
+ initSpinner.stop("Prisma files ready.");
899
+ return finalizeResult;
865
900
  } catch (error) {
866
901
  initSpinner.stop("Could not prepare Prisma files.");
867
902
  cancel(getCommandErrorMessage(error));
@@ -869,6 +904,7 @@ async function initializePrismaFilesForContext(context, projectDir, provisionRes
869
904
  }
870
905
  }
871
906
  async function generatePrismaClientForContext(context, projectDir) {
907
+ const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
872
908
  if (!context.shouldGenerate) return { didGenerateClient: false };
873
909
  const generateCommand = getPrismaCliCommand(context.packageManager, ["generate"]);
874
910
  if (context.verbose) log.step(`Running ${generateCommand}`);
@@ -877,7 +913,7 @@ async function generatePrismaClientForContext(context, projectDir) {
877
913
  try {
878
914
  const generateArgs = getPrismaCliArgs(context.packageManager, ["generate"]);
879
915
  await execa(generateArgs.command, generateArgs.args, {
880
- cwd: projectDir,
916
+ cwd: prismaProjectDir,
881
917
  stdio: context.verbose ? "inherit" : "pipe"
882
918
  });
883
919
  if (context.verbose) log.success("Prisma Client generated.");
@@ -904,16 +940,17 @@ function buildNextStepsForContext(opts) {
904
940
  if (!context.shouldInstall) nextSteps.push(`- ${getInstallCommand(context.packageManager)}`);
905
941
  if (!didGenerateClient || !context.shouldGenerate) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:generate")}`);
906
942
  nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:migrate")}`);
943
+ nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:seed")}`);
907
944
  if (options.includeDevNextStep) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "dev")}`);
908
945
  return nextSteps;
909
946
  }
910
- async function executeInitContext(context, options = {}) {
947
+ async function executePrismaSetupContext(context, options = {}) {
911
948
  const projectDir = path.resolve(options.projectDir ?? context.projectDir);
912
949
  const provisionResult = await provisionPrismaPostgresIfNeeded(context, projectDir);
913
950
  if (!provisionResult) return;
914
951
  if (!await writeDependenciesForContext(context, projectDir)) return;
915
952
  if (!await installDependenciesForContext(context, projectDir)) return;
916
- if (!await initializePrismaFilesForContext(context, projectDir, provisionResult)) return;
953
+ if (!await finalizePrismaFilesForContext(context, projectDir, provisionResult)) return;
917
954
  const generateResult = await generatePrismaClientForContext(context, projectDir);
918
955
  const warningLines = buildWarningLines(provisionResult.warning, generateResult.warning);
919
956
  const nextSteps = buildNextStepsForContext({
@@ -927,15 +964,540 @@ Next steps:
927
964
  ${nextSteps.join("\n")}`);
928
965
  return { packageManager: context.packageManager };
929
966
  }
930
- async function runInitCommand(rawInput = {}, options = {}) {
967
+
968
+ //#endregion
969
+ //#region src/tasks/setup-addons.ts
970
+ const DEFAULT_ADDON_SCOPE = "project";
971
+ const DEFAULT_SKILLS_AGENTS = [
972
+ "claude-code",
973
+ "codex",
974
+ "cursor"
975
+ ];
976
+ const DEFAULT_MCP_AGENTS = [
977
+ "claude-code",
978
+ "codex",
979
+ "cursor"
980
+ ];
981
+ const DEFAULT_EXTENSION_TARGETS = ["vscode", "cursor"];
982
+ const PRISMA_MCP_SERVER = "https://mcp.prisma.io/mcp";
983
+ const ADDON_OPTIONS = [
984
+ {
985
+ value: "skills",
986
+ label: "Skills",
987
+ hint: "Install curated Prisma skills to your selected coding agents"
988
+ },
989
+ {
990
+ value: "mcp",
991
+ label: "MCP",
992
+ hint: "Configure Prisma MCP server in agent MCP config files"
993
+ },
994
+ {
995
+ value: "extension",
996
+ label: "IDE Extension",
997
+ hint: "Install Prisma extension in selected IDEs (VS Code, Cursor, Windsurf)"
998
+ }
999
+ ];
1000
+ const SKILLS_AGENT_OPTIONS = [
1001
+ {
1002
+ value: "cursor",
1003
+ label: "Cursor"
1004
+ },
1005
+ {
1006
+ value: "claude-code",
1007
+ label: "Claude Code"
1008
+ },
1009
+ {
1010
+ value: "cline",
1011
+ label: "Cline"
1012
+ },
1013
+ {
1014
+ value: "github-copilot",
1015
+ label: "GitHub Copilot"
1016
+ },
1017
+ {
1018
+ value: "codex",
1019
+ label: "Codex"
1020
+ },
1021
+ {
1022
+ value: "opencode",
1023
+ label: "OpenCode"
1024
+ },
1025
+ {
1026
+ value: "windsurf",
1027
+ label: "Windsurf"
1028
+ },
1029
+ {
1030
+ value: "goose",
1031
+ label: "Goose"
1032
+ },
1033
+ {
1034
+ value: "roo",
1035
+ label: "Roo Code"
1036
+ },
1037
+ {
1038
+ value: "kilo",
1039
+ label: "Kilo Code"
1040
+ },
1041
+ {
1042
+ value: "gemini-cli",
1043
+ label: "Gemini CLI"
1044
+ },
1045
+ {
1046
+ value: "antigravity",
1047
+ label: "Antigravity"
1048
+ },
1049
+ {
1050
+ value: "openhands",
1051
+ label: "OpenHands"
1052
+ },
1053
+ {
1054
+ value: "trae",
1055
+ label: "Trae"
1056
+ },
1057
+ {
1058
+ value: "amp",
1059
+ label: "Amp"
1060
+ },
1061
+ {
1062
+ value: "pi",
1063
+ label: "Pi"
1064
+ },
1065
+ {
1066
+ value: "qoder",
1067
+ label: "Qoder"
1068
+ },
1069
+ {
1070
+ value: "qwen-code",
1071
+ label: "Qwen Code"
1072
+ },
1073
+ {
1074
+ value: "kiro-cli",
1075
+ label: "Kiro CLI"
1076
+ },
1077
+ {
1078
+ value: "droid",
1079
+ label: "Droid"
1080
+ },
1081
+ {
1082
+ value: "command-code",
1083
+ label: "Command Code"
1084
+ },
1085
+ {
1086
+ value: "clawdbot",
1087
+ label: "Clawdbot"
1088
+ },
1089
+ {
1090
+ value: "zencoder",
1091
+ label: "Zencoder"
1092
+ },
1093
+ {
1094
+ value: "neovate",
1095
+ label: "Neovate"
1096
+ },
1097
+ {
1098
+ value: "mcpjam",
1099
+ label: "MCPJam"
1100
+ }
1101
+ ];
1102
+ const MCP_AGENT_OPTIONS = [
1103
+ {
1104
+ value: "claude-code",
1105
+ label: "Claude Code"
1106
+ },
1107
+ {
1108
+ value: "codex",
1109
+ label: "Codex"
1110
+ },
1111
+ {
1112
+ value: "cursor",
1113
+ label: "Cursor"
1114
+ },
1115
+ {
1116
+ value: "vscode",
1117
+ label: "VS Code"
1118
+ },
1119
+ {
1120
+ value: "github-copilot-cli",
1121
+ label: "GitHub Copilot CLI"
1122
+ },
1123
+ {
1124
+ value: "opencode",
1125
+ label: "OpenCode"
1126
+ },
1127
+ {
1128
+ value: "gemini-cli",
1129
+ label: "Gemini CLI"
1130
+ },
1131
+ {
1132
+ value: "goose",
1133
+ label: "Goose"
1134
+ },
1135
+ {
1136
+ value: "zed",
1137
+ label: "Zed"
1138
+ },
1139
+ {
1140
+ value: "antigravity",
1141
+ label: "Antigravity"
1142
+ },
1143
+ {
1144
+ value: "cline",
1145
+ label: "Cline VS Code Extension"
1146
+ },
1147
+ {
1148
+ value: "cline-cli",
1149
+ label: "Cline CLI"
1150
+ },
1151
+ {
1152
+ value: "claude-desktop",
1153
+ label: "Claude Desktop"
1154
+ },
1155
+ {
1156
+ value: "mcporter",
1157
+ label: "MCPorter"
1158
+ }
1159
+ ];
1160
+ const SHARED_PRISMA_SKILLS = [
1161
+ "prisma-cli",
1162
+ "prisma-client-api",
1163
+ "prisma-database-setup",
1164
+ "prisma-upgrade-v7"
1165
+ ];
1166
+ function getAvailablePrismaSkills(provider) {
1167
+ if (provider === "postgresql") return [...SHARED_PRISMA_SKILLS, "prisma-postgres"];
1168
+ return [...SHARED_PRISMA_SKILLS];
1169
+ }
1170
+ function getSkillOptions(provider) {
1171
+ const available = getAvailablePrismaSkills(provider);
1172
+ const options = {
1173
+ "prisma-cli": {
1174
+ value: "prisma-cli",
1175
+ label: "prisma-cli",
1176
+ hint: "Prisma CLI reference"
1177
+ },
1178
+ "prisma-client-api": {
1179
+ value: "prisma-client-api",
1180
+ label: "prisma-client-api",
1181
+ hint: "Prisma Client query patterns"
1182
+ },
1183
+ "prisma-database-setup": {
1184
+ value: "prisma-database-setup",
1185
+ label: "prisma-database-setup",
1186
+ hint: "Database provider setup guides"
1187
+ },
1188
+ "prisma-upgrade-v7": {
1189
+ value: "prisma-upgrade-v7",
1190
+ label: "prisma-upgrade-v7",
1191
+ hint: "v6 to v7 migration guide"
1192
+ },
1193
+ "prisma-postgres": {
1194
+ value: "prisma-postgres",
1195
+ label: "prisma-postgres",
1196
+ hint: "Prisma Postgres workflows"
1197
+ }
1198
+ };
1199
+ return available.map((skill) => options[skill]);
1200
+ }
1201
+ function collectAddonsFromInput(input) {
1202
+ const addons = [];
1203
+ if (input.skills === true) addons.push("skills");
1204
+ if (input.mcp === true) addons.push("mcp");
1205
+ if (input.extension === true) addons.push("extension");
1206
+ return uniqueValues(addons);
1207
+ }
1208
+ const EXTENSION_TARGET_OPTIONS = [
1209
+ {
1210
+ value: "vscode",
1211
+ label: "VS Code",
1212
+ hint: "Uses the `code` CLI"
1213
+ },
1214
+ {
1215
+ value: "cursor",
1216
+ label: "Cursor",
1217
+ hint: "Uses the `cursor` CLI"
1218
+ },
1219
+ {
1220
+ value: "windsurf",
1221
+ label: "Windsurf",
1222
+ hint: "Uses the `windsurf` CLI"
1223
+ }
1224
+ ];
1225
+ function uniqueValues(values) {
1226
+ return Array.from(new Set(values));
1227
+ }
1228
+ function getRecommendedPrismaSkills(provider, shouldUsePrismaPostgres) {
1229
+ const skills = [...getAvailablePrismaSkills(provider)];
1230
+ if (!shouldUsePrismaPostgres) return skills.filter((skill) => skill !== "prisma-postgres");
1231
+ return uniqueValues(skills);
1232
+ }
1233
+ async function promptForAddons() {
1234
+ const selectedAddons = await multiselect({
1235
+ message: "Select add-ons (optional)",
1236
+ options: ADDON_OPTIONS,
1237
+ required: false
1238
+ });
1239
+ if (isCancel(selectedAddons)) {
1240
+ cancel("Operation cancelled.");
1241
+ return;
1242
+ }
1243
+ return uniqueValues(selectedAddons);
1244
+ }
1245
+ async function promptForAddonScope() {
1246
+ const selectedScope = await select({
1247
+ message: "Where should add-ons write config?",
1248
+ initialValue: DEFAULT_ADDON_SCOPE,
1249
+ options: [{
1250
+ value: "project",
1251
+ label: "Project",
1252
+ hint: "Recommended for teams (checked into the project when applicable)"
1253
+ }, {
1254
+ value: "global",
1255
+ label: "Global",
1256
+ hint: "Personal machine-level setup"
1257
+ }]
1258
+ });
1259
+ if (isCancel(selectedScope)) {
1260
+ cancel("Operation cancelled.");
1261
+ return;
1262
+ }
1263
+ return selectedScope;
1264
+ }
1265
+ async function promptForPrismaSkills(provider, recommendedSkills) {
1266
+ const options = getSkillOptions(provider);
1267
+ const optionValues = new Set(options.map((option) => option.value));
1268
+ const selectedSkills = await multiselect({
1269
+ message: "Select Prisma skills",
1270
+ options,
1271
+ required: false,
1272
+ initialValues: recommendedSkills.filter((skill) => optionValues.has(skill))
1273
+ });
1274
+ if (isCancel(selectedSkills)) {
1275
+ cancel("Operation cancelled.");
1276
+ return;
1277
+ }
1278
+ return uniqueValues(selectedSkills);
1279
+ }
1280
+ async function promptForSkillsAgents() {
1281
+ const selectedAgents = await multiselect({
1282
+ message: "Select agents for skills",
1283
+ options: SKILLS_AGENT_OPTIONS,
1284
+ required: false,
1285
+ initialValues: [...DEFAULT_SKILLS_AGENTS]
1286
+ });
1287
+ if (isCancel(selectedAgents)) {
1288
+ cancel("Operation cancelled.");
1289
+ return;
1290
+ }
1291
+ return uniqueValues(selectedAgents);
1292
+ }
1293
+ async function promptForMcpAgents() {
1294
+ const selectedAgents = await multiselect({
1295
+ message: "Select agents for MCP",
1296
+ options: MCP_AGENT_OPTIONS,
1297
+ required: false,
1298
+ initialValues: [...DEFAULT_MCP_AGENTS]
1299
+ });
1300
+ if (isCancel(selectedAgents)) {
1301
+ cancel("Operation cancelled.");
1302
+ return;
1303
+ }
1304
+ return uniqueValues(selectedAgents);
1305
+ }
1306
+ async function promptForExtensionTargets() {
1307
+ const selectedTargets = await multiselect({
1308
+ message: "Select IDEs for extension install",
1309
+ options: EXTENSION_TARGET_OPTIONS,
1310
+ required: false,
1311
+ initialValues: DEFAULT_EXTENSION_TARGETS
1312
+ });
1313
+ if (isCancel(selectedTargets)) {
1314
+ cancel("Operation cancelled.");
1315
+ return;
1316
+ }
1317
+ return uniqueValues(selectedTargets);
1318
+ }
1319
+ async function collectCreateAddonSetupContext(input, options) {
1320
+ const hasExplicitAddonSelection = input.skills !== void 0 || input.mcp !== void 0 || input.extension !== void 0;
1321
+ const selectedFromInput = collectAddonsFromInput(input);
1322
+ const selectedAddons = selectedFromInput.length > 0 ? selectedFromInput : hasExplicitAddonSelection ? [] : options.useDefaults ? [] : await promptForAddons();
1323
+ if (!selectedAddons) return;
1324
+ const addons = uniqueValues(selectedAddons);
1325
+ if (addons.length === 0) return null;
1326
+ const scope = addons.includes("skills") || addons.includes("mcp") ? options.useDefaults ? DEFAULT_ADDON_SCOPE : await promptForAddonScope() : DEFAULT_ADDON_SCOPE;
1327
+ if (!scope) return;
1328
+ const recommendedSkills = getRecommendedPrismaSkills(options.provider, options.shouldUsePrismaPostgres);
1329
+ const skills = !addons.includes("skills") ? [] : options.useDefaults ? recommendedSkills : await promptForPrismaSkills(options.provider, recommendedSkills);
1330
+ if (!skills) return;
1331
+ const skillsAgents = !addons.includes("skills") ? [] : options.useDefaults ? [...DEFAULT_SKILLS_AGENTS] : await promptForSkillsAgents();
1332
+ if (!skillsAgents) return;
1333
+ const mcpAgents = !addons.includes("mcp") ? [] : options.useDefaults ? [...DEFAULT_MCP_AGENTS] : await promptForMcpAgents();
1334
+ if (!mcpAgents) return;
1335
+ const extensionTargets = !addons.includes("extension") ? [] : options.useDefaults ? [...DEFAULT_EXTENSION_TARGETS] : await promptForExtensionTargets();
1336
+ if (!extensionTargets) return;
1337
+ return {
1338
+ addons,
1339
+ scope,
1340
+ skills,
1341
+ skillsAgents: uniqueValues(skillsAgents),
1342
+ mcpAgents: uniqueValues(mcpAgents),
1343
+ extensionTargets: uniqueValues(extensionTargets)
1344
+ };
1345
+ }
1346
+ async function executeExternalCommand(params) {
1347
+ await execa(params.command, params.args, {
1348
+ cwd: params.cwd,
1349
+ stdio: params.verbose ? "inherit" : "pipe",
1350
+ env: {
1351
+ ...process.env,
1352
+ CI: "true"
1353
+ }
1354
+ });
1355
+ }
1356
+ async function installSkillsAddon(params) {
1357
+ if (params.agents.length === 0 || params.skills.length === 0) return "Skipped skills addon because no skills or agents were selected.";
1358
+ const scopeArgs = params.scope === "global" ? ["-g"] : [];
1359
+ const skillArgs = params.skills.flatMap((skill) => ["-s", skill]);
1360
+ const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
1361
+ const commandArgs = [
1362
+ "skills@latest",
1363
+ "add",
1364
+ "prisma/skills",
1365
+ ...scopeArgs,
1366
+ ...skillArgs,
1367
+ ...agentArgs,
1368
+ "-y"
1369
+ ];
1370
+ const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
931
1371
  try {
932
- const context = await collectInitContext(rawInput, options);
933
- if (!context) return;
934
- return executeInitContext(context, options);
1372
+ await executeExternalCommand({
1373
+ command: execution.command,
1374
+ args: execution.args,
1375
+ cwd: params.projectDir,
1376
+ verbose: params.verbose
1377
+ });
1378
+ return;
1379
+ } catch (error) {
1380
+ return `Skills addon failed: ${error instanceof Error ? error.message : String(error)}`;
1381
+ }
1382
+ }
1383
+ async function installMcpAddon(params) {
1384
+ if (params.agents.length === 0) return "Skipped MCP addon because no agents were selected.";
1385
+ const scopeArgs = params.scope === "global" ? ["-g"] : [];
1386
+ const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
1387
+ const commandArgs = [
1388
+ "add-mcp@latest",
1389
+ PRISMA_MCP_SERVER,
1390
+ ...scopeArgs,
1391
+ ...agentArgs,
1392
+ "--name",
1393
+ "prisma",
1394
+ "--gitignore",
1395
+ "-y"
1396
+ ];
1397
+ const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
1398
+ try {
1399
+ await executeExternalCommand({
1400
+ command: execution.command,
1401
+ args: execution.args,
1402
+ cwd: params.projectDir,
1403
+ verbose: params.verbose
1404
+ });
1405
+ return;
935
1406
  } catch (error) {
936
- cancel(`Init command failed: ${error instanceof Error ? error.message : String(error)}`);
1407
+ return `MCP addon failed: ${error instanceof Error ? error.message : String(error)}`;
1408
+ }
1409
+ }
1410
+ function getExtensionInstallBinary(target) {
1411
+ switch (target) {
1412
+ case "vscode": return "code";
1413
+ case "cursor": return "cursor";
1414
+ case "windsurf": return "windsurf";
1415
+ default: {
1416
+ const exhaustiveCheck = target;
1417
+ throw new Error(`Unsupported extension target: ${String(exhaustiveCheck)}`);
1418
+ }
1419
+ }
1420
+ }
1421
+ async function installExtensionAddon(params) {
1422
+ if (params.targets.length === 0) return ["Skipped extension addon because no IDE targets were selected."];
1423
+ const warnings = [];
1424
+ for (const target of params.targets) {
1425
+ const binary = getExtensionInstallBinary(target);
1426
+ try {
1427
+ await executeExternalCommand({
1428
+ command: binary,
1429
+ args: ["--version"],
1430
+ cwd: params.projectDir,
1431
+ verbose: false
1432
+ });
1433
+ } catch {
1434
+ warnings.push(`Skipped ${target} extension install because the \`${binary}\` CLI is not available.`);
1435
+ continue;
1436
+ }
1437
+ try {
1438
+ await executeExternalCommand({
1439
+ command: binary,
1440
+ args: [
1441
+ "--install-extension",
1442
+ "Prisma.prisma",
1443
+ "--force"
1444
+ ],
1445
+ cwd: params.projectDir,
1446
+ verbose: params.verbose
1447
+ });
1448
+ } catch (error) {
1449
+ warnings.push(`${target} extension install failed: ${error instanceof Error ? error.message : String(error)}`);
1450
+ }
1451
+ }
1452
+ return warnings;
1453
+ }
1454
+ async function executeCreateAddonSetupContext(params) {
1455
+ const { context, packageManager, projectDir, verbose } = params;
1456
+ const addonSpinner = spinner();
1457
+ addonSpinner.start("Applying selected add-ons...");
1458
+ const warnings = [];
1459
+ if (context.addons.includes("skills")) {
1460
+ const warning = await installSkillsAddon({
1461
+ packageManager,
1462
+ projectDir,
1463
+ scope: context.scope,
1464
+ skills: context.skills,
1465
+ agents: context.skillsAgents,
1466
+ verbose
1467
+ });
1468
+ if (warning) warnings.push(warning);
1469
+ }
1470
+ if (context.addons.includes("mcp")) {
1471
+ const warning = await installMcpAddon({
1472
+ packageManager,
1473
+ projectDir,
1474
+ scope: context.scope,
1475
+ agents: context.mcpAgents,
1476
+ verbose
1477
+ });
1478
+ if (warning) warnings.push(warning);
1479
+ }
1480
+ if (context.addons.includes("extension")) {
1481
+ const extensionWarnings = await installExtensionAddon({
1482
+ projectDir,
1483
+ verbose,
1484
+ targets: context.extensionTargets
1485
+ });
1486
+ warnings.push(...extensionWarnings);
1487
+ }
1488
+ if (warnings.length > 0) {
1489
+ addonSpinner.stop("Add-ons applied with warnings.");
1490
+ for (const warning of warnings) log.warn(warning);
937
1491
  return;
938
1492
  }
1493
+ addonSpinner.stop("Add-ons applied.");
1494
+ }
1495
+
1496
+ //#endregion
1497
+ //#region src/ui/branding.ts
1498
+ const prismaTitle = `${styleText(["bold", "cyan"], "Create")} ${styleText(["bold", "magenta"], "Prisma")}`;
1499
+ function getCreatePrismaIntro() {
1500
+ return prismaTitle;
939
1501
  }
940
1502
 
941
1503
  //#endregion
@@ -972,15 +1534,38 @@ async function promptForCreateTemplate() {
972
1534
  const template = await select({
973
1535
  message: "Select template",
974
1536
  initialValue: DEFAULT_TEMPLATE,
975
- options: [{
976
- value: "hono",
977
- label: "Hono",
978
- hint: "Bun + TypeScript API starter"
979
- }, {
980
- value: "next",
981
- label: "Next.js",
982
- hint: "App Router + TypeScript starter"
983
- }]
1537
+ options: [
1538
+ {
1539
+ value: "hono",
1540
+ label: "Hono",
1541
+ hint: "TypeScript API starter"
1542
+ },
1543
+ {
1544
+ value: "next",
1545
+ label: "Next.js",
1546
+ hint: "App Router + TypeScript starter"
1547
+ },
1548
+ {
1549
+ value: "svelte",
1550
+ label: "SvelteKit",
1551
+ hint: "Official minimal SvelteKit + TypeScript starter"
1552
+ },
1553
+ {
1554
+ value: "astro",
1555
+ label: "Astro",
1556
+ hint: "Official minimal Astro starter with API route example"
1557
+ },
1558
+ {
1559
+ value: "nuxt",
1560
+ label: "Nuxt",
1561
+ hint: "Official minimal Nuxt starter with Nitro API route example"
1562
+ },
1563
+ {
1564
+ value: "turborepo",
1565
+ label: "Turborepo",
1566
+ hint: "Monorepo starter with apps + packages/db Prisma package"
1567
+ }
1568
+ ]
984
1569
  });
985
1570
  if (isCancel(template)) {
986
1571
  cancel("Operation cancelled.");
@@ -1023,7 +1608,6 @@ async function collectCreateContext(input) {
1023
1608
  if (!projectName) return;
1024
1609
  const template = input.template ?? (useDefaults ? DEFAULT_TEMPLATE : await promptForCreateTemplate());
1025
1610
  if (!template) return;
1026
- const schemaPreset = input.schemaPreset ?? DEFAULT_SCHEMA_PRESET;
1027
1611
  const targetDirectory = path.resolve(process.cwd(), projectName);
1028
1612
  const targetPathState = await inspectTargetPath(targetDirectory);
1029
1613
  if (targetPathState.exists && !targetPathState.isDirectory) {
@@ -1034,29 +1618,26 @@ async function collectCreateContext(input) {
1034
1618
  cancel(`Target directory ${formatPathForDisplay(targetDirectory)} is not empty. Use --force to continue.`);
1035
1619
  return;
1036
1620
  }
1037
- const initContext = await collectInitContext({
1038
- yes: input.yes,
1039
- verbose: input.verbose,
1040
- provider: input.provider,
1041
- packageManager: input.packageManager,
1042
- prismaPostgres: input.prismaPostgres,
1043
- databaseUrl: input.databaseUrl,
1044
- install: input.install,
1045
- generate: input.generate,
1046
- schemaPreset
1047
- }, {
1048
- skipIntro: true,
1049
- projectDir: targetDirectory
1621
+ const prismaSetupContext = await collectPrismaSetupContext(input, {
1622
+ projectDir: targetDirectory,
1623
+ defaultSchemaPreset: DEFAULT_SCHEMA_PRESET
1050
1624
  });
1051
- if (!initContext) return;
1625
+ if (!prismaSetupContext) return;
1626
+ const addonSetupContext = await collectCreateAddonSetupContext(input, {
1627
+ useDefaults,
1628
+ provider: prismaSetupContext.databaseProvider,
1629
+ shouldUsePrismaPostgres: prismaSetupContext.shouldUsePrismaPostgres
1630
+ });
1631
+ if (addonSetupContext === void 0) return;
1052
1632
  return {
1053
1633
  targetDirectory,
1054
1634
  targetPathState,
1055
1635
  force,
1056
1636
  template,
1057
- schemaPreset,
1637
+ schemaPreset: prismaSetupContext.schemaPreset,
1058
1638
  projectPackageName: toPackageName(path.basename(targetDirectory)),
1059
- initContext
1639
+ prismaSetupContext,
1640
+ addonSetupContext: addonSetupContext ?? void 0
1060
1641
  };
1061
1642
  }
1062
1643
  async function executeCreateContext(context) {
@@ -1068,7 +1649,8 @@ async function executeCreateContext(context) {
1068
1649
  projectName: context.projectPackageName,
1069
1650
  template: context.template,
1070
1651
  schemaPreset: context.schemaPreset,
1071
- packageManager: context.initContext.packageManager
1652
+ provider: context.prismaSetupContext.databaseProvider,
1653
+ packageManager: context.prismaSetupContext.packageManager
1072
1654
  });
1073
1655
  scaffoldSpinner.stop("Project files scaffolded.");
1074
1656
  } catch (error) {
@@ -1078,8 +1660,13 @@ async function executeCreateContext(context) {
1078
1660
  }
1079
1661
  if (context.targetPathState.exists && !context.targetPathState.isEmptyDirectory && context.force) log.warn(`Used --force in non-empty directory ${formatPathForDisplay(context.targetDirectory)}.`);
1080
1662
  const cdStep = `- cd ${formatPathForDisplay(context.targetDirectory)}`;
1081
- await executeInitContext(context.initContext, {
1082
- skipIntro: true,
1663
+ if (context.addonSetupContext) await executeCreateAddonSetupContext({
1664
+ context: context.addonSetupContext,
1665
+ packageManager: context.prismaSetupContext.packageManager,
1666
+ projectDir: context.targetDirectory,
1667
+ verbose: context.prismaSetupContext.verbose
1668
+ });
1669
+ await executePrismaSetupContext(context.prismaSetupContext, {
1083
1670
  prependNextSteps: [cdStep],
1084
1671
  projectDir: context.targetDirectory,
1085
1672
  includeDevNextStep: true
@@ -1087,4 +1674,4 @@ async function executeCreateContext(context) {
1087
1674
  }
1088
1675
 
1089
1676
  //#endregion
1090
- export { DatabaseProviderSchema as a, PackageManagerSchema as c, CreateTemplateSchema as i, SchemaPresetSchema as l, runInitCommand as n, DatabaseUrlSchema as o, CreateCommandInputSchema as r, InitCommandInputSchema as s, runCreateCommand as t };
1677
+ export { DatabaseUrlSchema as a, DatabaseProviderSchema as i, CreateCommandInputSchema as n, PackageManagerSchema as o, CreateTemplateSchema as r, SchemaPresetSchema as s, runCreateCommand as t };