create-prisma 0.7.1-pr.52.181.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -19
- package/dist/cli.mjs +2 -32
- package/dist/index.d.mts +25 -1
- package/dist/index.mjs +2 -2290
- package/dist/src-B3hRLZ0U.mjs +2527 -0
- package/package.json +2 -4
- package/templates/create/astro/README.md.hbs +14 -7
- package/templates/create/astro/astro.config.mjs.hbs +0 -11
- package/templates/create/astro/deno.json.hbs +5 -0
- package/templates/create/astro/prisma/schema.prisma.hbs +5 -0
- package/templates/create/astro/prisma/seed.ts.hbs +5 -1
- package/templates/create/astro/prisma.compute.ts.hbs +10 -0
- package/templates/create/astro/src/lib/prisma.ts.hbs +4 -18
- package/templates/create/astro/src/pages/api/users.ts.hbs +11 -1
- package/templates/create/astro/src/pages/index.astro.hbs +176 -36
- package/templates/create/elysia/README.md.hbs +13 -7
- package/templates/create/elysia/deno.json.hbs +5 -0
- package/templates/create/elysia/package.json.hbs +3 -4
- package/templates/create/elysia/prisma/schema.prisma.hbs +6 -0
- package/templates/create/elysia/prisma/seed.ts.hbs +5 -1
- package/templates/create/elysia/prisma.compute.ts.hbs +11 -0
- package/templates/create/elysia/src/index.ts.hbs +17 -13
- package/templates/create/elysia/src/lib/prisma.ts.hbs +4 -18
- package/templates/create/elysia/tsconfig.json +0 -1
- package/templates/create/hono/README.md.hbs +13 -7
- package/templates/create/hono/deno.json.hbs +5 -0
- package/templates/create/hono/package.json.hbs +2 -2
- package/templates/create/hono/prisma/schema.prisma.hbs +6 -0
- package/templates/create/hono/prisma/seed.ts.hbs +5 -1
- package/templates/create/hono/prisma.compute.ts.hbs +10 -0
- package/templates/create/hono/src/index.ts.hbs +7 -9
- package/templates/create/hono/src/lib/prisma.ts.hbs +4 -18
- package/templates/create/hono/tsconfig.json +0 -1
- package/templates/create/nest/README.md.hbs +12 -6
- package/templates/create/nest/deno.json.hbs +5 -0
- package/templates/create/nest/package.json.hbs +2 -2
- package/templates/create/nest/prisma/schema.prisma.hbs +6 -0
- package/templates/create/nest/prisma/seed.ts.hbs +5 -1
- package/templates/create/nest/prisma.compute.ts.hbs +10 -0
- package/templates/create/nest/src/app.controller.ts.hbs +1 -6
- package/templates/create/nest/src/app.module.ts.hbs +14 -7
- package/templates/create/nest/src/lib/prisma.ts.hbs +4 -17
- package/templates/create/nest/src/main.ts.hbs +6 -2
- package/templates/create/nest/src/prisma.service.ts.hbs +2 -2
- package/templates/create/nest/src/users.controller.ts.hbs +3 -1
- package/templates/create/nest/src/users.service.ts.hbs +3 -1
- package/templates/create/nest/tsconfig.json +0 -1
- package/templates/create/next/README.md.hbs +11 -7
- package/templates/create/next/deno.json.hbs +12 -0
- package/templates/create/next/prisma/schema.prisma.hbs +5 -0
- package/templates/create/next/prisma/seed.ts.hbs +5 -1
- package/templates/create/next/prisma.compute.ts.hbs +9 -0
- package/templates/create/next/src/app/globals.css +122 -0
- package/templates/create/next/src/app/layout.tsx.hbs +2 -2
- package/templates/create/next/src/app/page.tsx.hbs +58 -34
- package/templates/create/next/src/lib/prisma.ts.hbs +4 -18
- package/templates/create/nuxt/README.md.hbs +11 -7
- package/templates/create/nuxt/app/pages/index.vue.hbs +175 -43
- package/templates/create/nuxt/deno.json.hbs +5 -0
- package/templates/create/nuxt/nuxt.config.ts +0 -1
- package/templates/create/nuxt/prisma/schema.prisma.hbs +5 -0
- package/templates/create/nuxt/prisma/seed.ts.hbs +5 -1
- package/templates/create/nuxt/prisma.compute.ts.hbs +9 -0
- package/templates/create/nuxt/server/api/users.get.ts.hbs +6 -0
- package/templates/create/nuxt/server/utils/prisma.ts.hbs +4 -18
- package/templates/create/svelte/README.md.hbs +13 -4
- package/templates/create/svelte/deno.json.hbs +5 -0
- package/templates/create/svelte/package.json.hbs +1 -2
- package/templates/create/svelte/prisma/schema.prisma.hbs +5 -0
- package/templates/create/svelte/prisma/seed.ts.hbs +5 -1
- package/templates/create/svelte/src/lib/server/prisma.ts.hbs +2 -18
- package/templates/create/svelte/src/routes/+layout.svelte +0 -1
- package/templates/create/svelte/src/routes/+page.server.ts.hbs +7 -1
- package/templates/create/svelte/src/routes/+page.svelte.hbs +297 -41
- package/templates/create/svelte/svelte.config.js +13 -0
- package/templates/create/svelte/vite.config.ts +0 -3
- package/templates/create/tanstack-start/README.md.hbs +11 -7
- package/templates/create/tanstack-start/deno.json.hbs +13 -0
- package/templates/create/tanstack-start/prisma/schema.prisma.hbs +5 -0
- package/templates/create/tanstack-start/prisma/seed.ts.hbs +5 -1
- package/templates/create/tanstack-start/prisma.compute.ts.hbs +9 -0
- package/templates/create/tanstack-start/src/lib/prisma.server.ts.hbs +4 -18
- package/templates/create/tanstack-start/src/routes/__root.tsx.hbs +29 -4
- package/templates/create/tanstack-start/src/routes/index.tsx.hbs +76 -35
- package/templates/create/tanstack-start/src/styles.css +146 -0
- package/templates/create/tanstack-start/tsconfig.json +1 -1
- package/templates/create/turborepo/README.md.hbs +9 -7
- package/templates/create/turborepo/apps/api/package.json.hbs +9 -2
- package/templates/create/turborepo/apps/api/src/index.ts.hbs +12 -28
- package/templates/create/turborepo/apps/api/tsconfig.json +0 -1
- package/templates/create/turborepo/deno.json.hbs +5 -0
- package/templates/create/turborepo/package.json.hbs +6 -0
- package/templates/create/turborepo/packages/db/package.json.hbs +5 -4
- package/templates/create/turborepo/packages/db/prisma/schema.prisma.hbs +5 -0
- package/templates/create/turborepo/packages/db/prisma/seed.ts.hbs +5 -1
- package/templates/create/turborepo/packages/db/src/client.ts.hbs +5 -2
- package/templates/create/turborepo/packages/db/tsconfig.json +0 -1
- package/templates/create/turborepo/prisma.compute.ts.hbs +14 -0
- package/templates/create/astro/module.ts.hbs +0 -30
- package/templates/create/astro/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/astro/service.ts.hbs +0 -19
- package/templates/create/elysia/.yarnrc.yml.hbs +0 -3
- package/templates/create/elysia/module.ts.hbs +0 -30
- package/templates/create/elysia/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/elysia/service.ts.hbs +0 -19
- package/templates/create/hono/module.ts.hbs +0 -30
- package/templates/create/hono/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/hono/service.ts.hbs +0 -19
- package/templates/create/nest/module.ts.hbs +0 -30
- package/templates/create/nest/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/nest/src/service.ts.hbs +0 -19
- package/templates/create/next/module.ts.hbs +0 -30
- package/templates/create/next/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/next/service.ts.hbs +0 -19
- package/templates/create/nuxt/module.ts.hbs +0 -30
- package/templates/create/nuxt/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/nuxt/service.ts.hbs +0 -19
- package/templates/create/svelte/module.ts.hbs +0 -30
- package/templates/create/svelte/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/svelte/service.ts.hbs +0 -19
- package/templates/create/svelte/svelte.config.js.hbs +0 -10
- package/templates/create/tanstack-start/module.ts.hbs +0 -30
- package/templates/create/tanstack-start/prisma-composer.config.ts.hbs +0 -8
- package/templates/create/tanstack-start/service.ts.hbs +0 -19
- package/templates/create/turborepo/apps/api/src/service.ts.hbs +0 -19
- package/templates/create/turborepo/module.ts.hbs +0 -30
- package/templates/create/turborepo/prisma-composer.config.ts.hbs +0 -8
- package/templates/shared/setup-composer-postgres.mjs.hbs +0 -179
- package/templates/shared/starter.css +0 -276
|
@@ -0,0 +1,2527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { os } from "@orpc/server";
|
|
3
|
+
import { createCli } from "trpc-cli";
|
|
4
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import Handlebars from "handlebars";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { execa } from "execa";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import os$1 from "node:os";
|
|
14
|
+
import { PostHog } from "posthog-node";
|
|
15
|
+
import { styleText } from "node:util";
|
|
16
|
+
//#region src/utils/runtime.ts
|
|
17
|
+
function usesNodeStyleRuntime(packageManager) {
|
|
18
|
+
return packageManager !== void 0 && packageManager !== "bun" && packageManager !== "deno";
|
|
19
|
+
}
|
|
20
|
+
function requiresDotenvConfigImport(packageManager) {
|
|
21
|
+
return usesNodeStyleRuntime(packageManager);
|
|
22
|
+
}
|
|
23
|
+
function requiresPrismaConfigDotenvImport(packageManager) {
|
|
24
|
+
return packageManager !== "deno";
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/constants/dependencies.ts
|
|
28
|
+
const dependencyVersionMap = {
|
|
29
|
+
"@elysiajs/node": "^1.4.5",
|
|
30
|
+
"@libsql/client": "^0.17.4",
|
|
31
|
+
"@prisma/client": "^7.8.0",
|
|
32
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
33
|
+
"@prisma/adapter-libsql": "^7.8.0",
|
|
34
|
+
"@prisma/adapter-mariadb": "^7.8.0",
|
|
35
|
+
"@prisma/adapter-mssql": "^7.8.0",
|
|
36
|
+
"@prisma/compute-sdk": "latest",
|
|
37
|
+
"@types/node": "^26.0.0",
|
|
38
|
+
dotenv: "^17.4.2",
|
|
39
|
+
prisma: "^7.8.0",
|
|
40
|
+
tsx: "^4.22.4"
|
|
41
|
+
};
|
|
42
|
+
const computeConfigTemplates = /* @__PURE__ */ new Set([
|
|
43
|
+
"hono",
|
|
44
|
+
"elysia",
|
|
45
|
+
"nest",
|
|
46
|
+
"next",
|
|
47
|
+
"astro",
|
|
48
|
+
"nuxt",
|
|
49
|
+
"tanstack-start",
|
|
50
|
+
"turborepo"
|
|
51
|
+
]);
|
|
52
|
+
function getWorkspaceDependencyVersion(packageManager) {
|
|
53
|
+
return packageManager === "npm" ? "*" : "workspace:*";
|
|
54
|
+
}
|
|
55
|
+
function getCreateTemplateDependencies(template, packageManager) {
|
|
56
|
+
const targets = [];
|
|
57
|
+
if (template === "hono" || template === "elysia" || template === "nest") {
|
|
58
|
+
const runtimeDevDependencies = usesNodeStyleRuntime(packageManager) ? ["tsx"] : [];
|
|
59
|
+
if (template === "elysia" && packageManager !== "deno") targets.push({
|
|
60
|
+
packageJsonPath: "package.json",
|
|
61
|
+
dependencies: ["@elysiajs/node"],
|
|
62
|
+
devDependencies: ["@types/node", ...runtimeDevDependencies]
|
|
63
|
+
});
|
|
64
|
+
else if (runtimeDevDependencies.length > 0) targets.push({
|
|
65
|
+
packageJsonPath: "package.json",
|
|
66
|
+
dependencies: [],
|
|
67
|
+
devDependencies: runtimeDevDependencies
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (template === "turborepo") targets.push({
|
|
71
|
+
packageJsonPath: "apps/api/package.json",
|
|
72
|
+
dependencies: ["dotenv"],
|
|
73
|
+
devDependencies: ["tsx"],
|
|
74
|
+
customDependencies: { "@repo/db": getWorkspaceDependencyVersion(packageManager) }
|
|
75
|
+
});
|
|
76
|
+
if (computeConfigTemplates.has(template)) targets.push({
|
|
77
|
+
packageJsonPath: "package.json",
|
|
78
|
+
dependencies: [],
|
|
79
|
+
devDependencies: ["@prisma/compute-sdk"]
|
|
80
|
+
});
|
|
81
|
+
return targets;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/types.ts
|
|
85
|
+
const databaseProviders = [
|
|
86
|
+
"postgresql",
|
|
87
|
+
"mysql",
|
|
88
|
+
"sqlite",
|
|
89
|
+
"sqlserver",
|
|
90
|
+
"cockroachdb"
|
|
91
|
+
];
|
|
92
|
+
const packageManagers = [
|
|
93
|
+
"npm",
|
|
94
|
+
"pnpm",
|
|
95
|
+
"yarn",
|
|
96
|
+
"bun",
|
|
97
|
+
"deno"
|
|
98
|
+
];
|
|
99
|
+
const schemaPresets = ["empty", "basic"];
|
|
100
|
+
const createTemplates = [
|
|
101
|
+
"hono",
|
|
102
|
+
"elysia",
|
|
103
|
+
"nest",
|
|
104
|
+
"next",
|
|
105
|
+
"svelte",
|
|
106
|
+
"astro",
|
|
107
|
+
"nuxt",
|
|
108
|
+
"tanstack-start",
|
|
109
|
+
"turborepo"
|
|
110
|
+
];
|
|
111
|
+
const createAddons = [
|
|
112
|
+
"skills",
|
|
113
|
+
"mcp",
|
|
114
|
+
"extension"
|
|
115
|
+
];
|
|
116
|
+
const addonInstallScopes = ["project", "global"];
|
|
117
|
+
const extensionTargets = [
|
|
118
|
+
"vscode",
|
|
119
|
+
"cursor",
|
|
120
|
+
"windsurf"
|
|
121
|
+
];
|
|
122
|
+
const prismaSkillNames = [
|
|
123
|
+
"prisma-cli",
|
|
124
|
+
"prisma-client-api",
|
|
125
|
+
"prisma-compute",
|
|
126
|
+
"prisma-database-setup",
|
|
127
|
+
"prisma-upgrade-v7",
|
|
128
|
+
"prisma-postgres"
|
|
129
|
+
];
|
|
130
|
+
const DatabaseProviderSchema = z.enum(databaseProviders);
|
|
131
|
+
const PackageManagerSchema = z.enum(packageManagers);
|
|
132
|
+
const SchemaPresetSchema = z.enum(schemaPresets);
|
|
133
|
+
const CreateTemplateSchema = z.enum(createTemplates);
|
|
134
|
+
z.enum(createAddons);
|
|
135
|
+
z.enum(addonInstallScopes);
|
|
136
|
+
z.enum(extensionTargets);
|
|
137
|
+
z.enum(prismaSkillNames);
|
|
138
|
+
const DatabaseUrlSchema = z.string().trim().min(1, "Please enter a valid database URL");
|
|
139
|
+
const CommonCommandOptionsSchema = z.object({
|
|
140
|
+
yes: z.boolean().optional().describe("Skip prompts and accept default choices"),
|
|
141
|
+
verbose: z.boolean().optional().describe("Show verbose command output during setup")
|
|
142
|
+
});
|
|
143
|
+
const PrismaSetupOptionsSchema = z.object({
|
|
144
|
+
provider: DatabaseProviderSchema.optional().describe("Database provider"),
|
|
145
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager used for dependency installation"),
|
|
146
|
+
prismaPostgres: z.boolean().optional().describe("Use Prisma Postgres when provider is postgresql"),
|
|
147
|
+
databaseUrl: DatabaseUrlSchema.optional().describe("DATABASE_URL value"),
|
|
148
|
+
install: z.boolean().optional().describe("Install dependencies with selected package manager"),
|
|
149
|
+
generate: z.boolean().optional().describe("Generate Prisma Client after scaffolding"),
|
|
150
|
+
migrateAndSeed: z.boolean().optional().describe("Run an initial migration and seed after Prisma Client generation"),
|
|
151
|
+
schemaPreset: SchemaPresetSchema.optional().describe("Schema preset to scaffold in prisma/schema.prisma")
|
|
152
|
+
});
|
|
153
|
+
const PrismaSetupCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
|
|
154
|
+
const CreateScaffoldOptionsSchema = z.object({
|
|
155
|
+
name: z.string().trim().min(1, "Please enter a valid project name").optional().describe("Project name / directory"),
|
|
156
|
+
template: CreateTemplateSchema.optional().describe("Project template"),
|
|
157
|
+
skills: z.boolean().optional().describe("Enable skills addon"),
|
|
158
|
+
mcp: z.boolean().optional().describe("Enable MCP addon"),
|
|
159
|
+
extension: z.boolean().optional().describe("Enable extension addon"),
|
|
160
|
+
deploy: z.boolean().optional().describe("Deploy the scaffolded project to Prisma Compute"),
|
|
161
|
+
force: z.boolean().optional().describe("Allow scaffolding into a non-empty target directory")
|
|
162
|
+
});
|
|
163
|
+
const COMPUTE_DEPLOYABLE_TEMPLATES = /* @__PURE__ */ new Set([
|
|
164
|
+
"hono",
|
|
165
|
+
"elysia",
|
|
166
|
+
"nest",
|
|
167
|
+
"next",
|
|
168
|
+
"astro",
|
|
169
|
+
"nuxt",
|
|
170
|
+
"tanstack-start",
|
|
171
|
+
"turborepo"
|
|
172
|
+
]);
|
|
173
|
+
function isComputeDeployableTemplate(template) {
|
|
174
|
+
return COMPUTE_DEPLOYABLE_TEMPLATES.has(template);
|
|
175
|
+
}
|
|
176
|
+
const CreateCommandInputSchema = PrismaSetupCommandInputSchema.extend(CreateScaffoldOptionsSchema.shape);
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/utils/package-manager.ts
|
|
179
|
+
const packageManagerManifestValues = {
|
|
180
|
+
npm: "npm@11.17.0",
|
|
181
|
+
pnpm: "pnpm@11.8.0",
|
|
182
|
+
yarn: "yarn@4.17.0",
|
|
183
|
+
bun: "bun@1.3.14"
|
|
184
|
+
};
|
|
185
|
+
function parseUserAgent(userAgent) {
|
|
186
|
+
if (userAgent?.startsWith("pnpm")) return "pnpm";
|
|
187
|
+
if (userAgent?.startsWith("yarn")) return "yarn";
|
|
188
|
+
if (userAgent?.startsWith("bun")) return "bun";
|
|
189
|
+
if (userAgent?.startsWith("deno")) return "deno";
|
|
190
|
+
if (userAgent?.startsWith("npm")) return "npm";
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
function parsePackageManagerField(packageManagerField) {
|
|
194
|
+
if (typeof packageManagerField !== "string" || packageManagerField.length === 0) return null;
|
|
195
|
+
const managerName = packageManagerField.split("@")[0];
|
|
196
|
+
const parsed = PackageManagerSchema.safeParse(managerName);
|
|
197
|
+
return parsed.success ? parsed.data : null;
|
|
198
|
+
}
|
|
199
|
+
async function detectFromPackageJson(projectDir) {
|
|
200
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
201
|
+
if (!await fs.pathExists(packageJsonPath)) return null;
|
|
202
|
+
return parsePackageManagerField((await fs.readJson(packageJsonPath)).packageManager);
|
|
203
|
+
}
|
|
204
|
+
async function detectFromDenoConfig(projectDir) {
|
|
205
|
+
for (const configFile of ["deno.json", "deno.jsonc"]) if (await fs.pathExists(path.join(projectDir, configFile))) return "deno";
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
async function detectFromLockfile(projectDir) {
|
|
209
|
+
for (const check of [
|
|
210
|
+
{
|
|
211
|
+
manager: "pnpm",
|
|
212
|
+
lockfile: "pnpm-lock.yaml"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
manager: "yarn",
|
|
216
|
+
lockfile: "yarn.lock"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
manager: "bun",
|
|
220
|
+
lockfile: "bun.lockb"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
manager: "bun",
|
|
224
|
+
lockfile: "bun.lock"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
manager: "npm",
|
|
228
|
+
lockfile: "package-lock.json"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
manager: "npm",
|
|
232
|
+
lockfile: "npm-shrinkwrap.json"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
manager: "deno",
|
|
236
|
+
lockfile: "deno.lock"
|
|
237
|
+
}
|
|
238
|
+
]) if (await fs.pathExists(path.join(projectDir, check.lockfile))) return check.manager;
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
async function detectPackageManager(projectDir = process.cwd()) {
|
|
242
|
+
const fromPackageJson = await detectFromPackageJson(projectDir);
|
|
243
|
+
if (fromPackageJson) return fromPackageJson;
|
|
244
|
+
const fromLockfile = await detectFromLockfile(projectDir);
|
|
245
|
+
if (fromLockfile) return fromLockfile;
|
|
246
|
+
const fromDenoConfig = await detectFromDenoConfig(projectDir);
|
|
247
|
+
if (fromDenoConfig) return fromDenoConfig;
|
|
248
|
+
const fromUserAgent = parseUserAgent(process.env.npm_config_user_agent);
|
|
249
|
+
if (fromUserAgent) return fromUserAgent;
|
|
250
|
+
return "npm";
|
|
251
|
+
}
|
|
252
|
+
function getPackageManagerManifestValue(packageManager) {
|
|
253
|
+
if (!packageManager || packageManager === "deno") return;
|
|
254
|
+
return packageManagerManifestValues[packageManager];
|
|
255
|
+
}
|
|
256
|
+
function getDenoPrismaSpecifier() {
|
|
257
|
+
return `npm:prisma@${dependencyVersionMap.prisma}`;
|
|
258
|
+
}
|
|
259
|
+
function getDenoAllowedScriptSpecifiers() {
|
|
260
|
+
return [
|
|
261
|
+
`npm:prisma@${dependencyVersionMap.prisma}`,
|
|
262
|
+
`npm:@prisma/client@${dependencyVersionMap["@prisma/client"]}`,
|
|
263
|
+
`npm:@prisma/engines@${dependencyVersionMap.prisma}`
|
|
264
|
+
].join(",");
|
|
265
|
+
}
|
|
266
|
+
function getInstallCommand(packageManager) {
|
|
267
|
+
if (packageManager === "deno") return `deno install --allow-scripts=${getDenoAllowedScriptSpecifiers()}`;
|
|
268
|
+
return `${packageManager} install`;
|
|
269
|
+
}
|
|
270
|
+
function getRunScriptCommand(packageManager, scriptName) {
|
|
271
|
+
switch (packageManager) {
|
|
272
|
+
case "deno": return `deno task ${scriptName}`;
|
|
273
|
+
case "bun": return `bun run ${scriptName}`;
|
|
274
|
+
case "pnpm": return `pnpm run ${scriptName}`;
|
|
275
|
+
case "yarn": return `yarn run ${scriptName}`;
|
|
276
|
+
default: return `npm run ${scriptName}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function getRunScriptInDirectoryCommand(packageManager, directory, scriptName) {
|
|
280
|
+
switch (packageManager) {
|
|
281
|
+
case "deno": return `deno task --cwd ${directory} ${scriptName}`;
|
|
282
|
+
case "bun": return `bun run --cwd ${directory} ${scriptName}`;
|
|
283
|
+
case "pnpm": return `pnpm --dir ${directory} run ${scriptName}`;
|
|
284
|
+
case "yarn": return `yarn --cwd ${directory} run ${scriptName}`;
|
|
285
|
+
default: return `npm --prefix ${directory} run ${scriptName}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function joinCommandParts(parts) {
|
|
289
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" ");
|
|
290
|
+
}
|
|
291
|
+
function getRuntimeScriptCommand(packageManager, kind, options) {
|
|
292
|
+
const { sourceEntrypoint, builtEntrypoint, denoFlags = [], emit = false } = options;
|
|
293
|
+
if (packageManager === "deno") switch (kind) {
|
|
294
|
+
case "dev": return joinCommandParts([
|
|
295
|
+
"deno",
|
|
296
|
+
"run",
|
|
297
|
+
"-A",
|
|
298
|
+
"--env-file=.env",
|
|
299
|
+
...denoFlags,
|
|
300
|
+
"--watch",
|
|
301
|
+
sourceEntrypoint
|
|
302
|
+
]);
|
|
303
|
+
case "build": return `deno check ${sourceEntrypoint}`;
|
|
304
|
+
case "start": return joinCommandParts([
|
|
305
|
+
"deno",
|
|
306
|
+
"run",
|
|
307
|
+
"-A",
|
|
308
|
+
"--env-file=.env",
|
|
309
|
+
...denoFlags,
|
|
310
|
+
sourceEntrypoint
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
if (packageManager === "bun") switch (kind) {
|
|
314
|
+
case "dev": return `bun --watch ${sourceEntrypoint}`;
|
|
315
|
+
case "build": return emit ? "tsc" : "tsc --noEmit";
|
|
316
|
+
case "start": return `bun ${sourceEntrypoint}`;
|
|
317
|
+
}
|
|
318
|
+
switch (kind) {
|
|
319
|
+
case "dev": return `tsx watch ${sourceEntrypoint}`;
|
|
320
|
+
case "build": return "tsc";
|
|
321
|
+
case "start": return builtEntrypoint ? `node ${builtEntrypoint}` : `tsx ${sourceEntrypoint}`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function getInstallArgs(packageManager) {
|
|
325
|
+
if (packageManager === "deno") return {
|
|
326
|
+
command: "deno",
|
|
327
|
+
args: ["install", `--allow-scripts=${getDenoAllowedScriptSpecifiers()}`]
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
command: packageManager,
|
|
331
|
+
args: ["install"]
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function getPackageExecutionArgs(packageManager, commandArgs, options = {}) {
|
|
335
|
+
switch (packageManager) {
|
|
336
|
+
case "pnpm": return {
|
|
337
|
+
command: "pnpm",
|
|
338
|
+
args: [
|
|
339
|
+
...options.silent ? ["--silent"] : [],
|
|
340
|
+
"dlx",
|
|
341
|
+
...commandArgs
|
|
342
|
+
]
|
|
343
|
+
};
|
|
344
|
+
case "yarn": return {
|
|
345
|
+
command: "yarn",
|
|
346
|
+
args: [
|
|
347
|
+
"dlx",
|
|
348
|
+
...options.silent ? ["--quiet"] : [],
|
|
349
|
+
...commandArgs
|
|
350
|
+
]
|
|
351
|
+
};
|
|
352
|
+
case "bun": return {
|
|
353
|
+
command: "bunx",
|
|
354
|
+
args: [...options.silent ? ["--silent"] : [], ...commandArgs]
|
|
355
|
+
};
|
|
356
|
+
case "deno": {
|
|
357
|
+
const [packageName, ...args] = commandArgs;
|
|
358
|
+
if (!packageName) throw new Error("Package execution requires a package name.");
|
|
359
|
+
return {
|
|
360
|
+
command: "deno",
|
|
361
|
+
args: [
|
|
362
|
+
"run",
|
|
363
|
+
"-A",
|
|
364
|
+
`npm:${packageName}`,
|
|
365
|
+
...args
|
|
366
|
+
]
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
default: return {
|
|
370
|
+
command: "npx",
|
|
371
|
+
args: [...options.silent ? ["--yes", "--no-update-notifier"] : [], ...commandArgs]
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function getPackageExecutionCommand(packageManager, commandArgs, options = {}) {
|
|
376
|
+
const execution = getPackageExecutionArgs(packageManager, commandArgs, options);
|
|
377
|
+
return [execution.command, ...execution.args].join(" ");
|
|
378
|
+
}
|
|
379
|
+
function getPrismaCliArgs(packageManager, prismaArgs) {
|
|
380
|
+
if (packageManager === "deno") return {
|
|
381
|
+
command: "deno",
|
|
382
|
+
args: [
|
|
383
|
+
"run",
|
|
384
|
+
"-A",
|
|
385
|
+
"--env-file=.env",
|
|
386
|
+
getDenoPrismaSpecifier(),
|
|
387
|
+
...prismaArgs
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
if (packageManager === "bun") return {
|
|
391
|
+
command: "bun",
|
|
392
|
+
args: [
|
|
393
|
+
"--env-file=.env",
|
|
394
|
+
"./node_modules/.bin/prisma",
|
|
395
|
+
...prismaArgs
|
|
396
|
+
]
|
|
397
|
+
};
|
|
398
|
+
if (packageManager === "pnpm") return {
|
|
399
|
+
command: "pnpm",
|
|
400
|
+
args: [
|
|
401
|
+
"exec",
|
|
402
|
+
"prisma",
|
|
403
|
+
...prismaArgs
|
|
404
|
+
]
|
|
405
|
+
};
|
|
406
|
+
if (packageManager === "yarn") return {
|
|
407
|
+
command: "yarn",
|
|
408
|
+
args: [
|
|
409
|
+
"exec",
|
|
410
|
+
"prisma",
|
|
411
|
+
...prismaArgs
|
|
412
|
+
]
|
|
413
|
+
};
|
|
414
|
+
return getPackageExecutionArgs(packageManager, ["prisma", ...prismaArgs]);
|
|
415
|
+
}
|
|
416
|
+
function getPrismaCliCommand$1(packageManager, prismaArgs) {
|
|
417
|
+
const execution = getPrismaCliArgs(packageManager, prismaArgs);
|
|
418
|
+
return [execution.command, ...execution.args].join(" ");
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region src/templates/shared.ts
|
|
422
|
+
function getOptionalHashString(hash, key) {
|
|
423
|
+
const value = hash[key];
|
|
424
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
425
|
+
}
|
|
426
|
+
function getOptionalHashStringList(hash, key) {
|
|
427
|
+
return getOptionalHashString(hash, key)?.split(" ") ?? [];
|
|
428
|
+
}
|
|
429
|
+
function getSeedCommand(packageManager) {
|
|
430
|
+
if (packageManager === "deno") return "deno run -A --env-file=.env ./prisma/seed.ts";
|
|
431
|
+
if (packageManager === "bun") return "bun ./prisma/seed.ts";
|
|
432
|
+
return "tsx ./prisma/seed.ts";
|
|
433
|
+
}
|
|
434
|
+
function getPrismaCommand(packageManager, subcommand) {
|
|
435
|
+
if (packageManager === "deno") return `deno run -A --env-file=.env npm:prisma@${dependencyVersionMap.prisma} ${subcommand}`;
|
|
436
|
+
if (packageManager === "bun") return `bun --env-file=.env ./node_modules/.bin/prisma ${subcommand}`;
|
|
437
|
+
return `prisma ${subcommand}`;
|
|
438
|
+
}
|
|
439
|
+
Handlebars.registerHelper("eq", (left, right) => left === right);
|
|
440
|
+
Handlebars.registerHelper("runScriptCommand", (packageManager, scriptName) => packageManager ? getRunScriptCommand(packageManager, scriptName) : "");
|
|
441
|
+
Handlebars.registerHelper("runScriptInDirectoryCommand", (packageManager, directory, scriptName) => packageManager ? getRunScriptInDirectoryCommand(packageManager, directory, scriptName) : "");
|
|
442
|
+
Handlebars.registerHelper("packageManagerManifestValue", (packageManager) => getPackageManagerManifestValue(packageManager) ?? "");
|
|
443
|
+
Handlebars.registerHelper("requiresDotenvConfigImport", (packageManager) => requiresDotenvConfigImport(packageManager));
|
|
444
|
+
Handlebars.registerHelper("requiresPrismaConfigDotenvImport", (packageManager) => requiresPrismaConfigDotenvImport(packageManager));
|
|
445
|
+
Handlebars.registerHelper("sqliteAdapterPackage", () => "@prisma/adapter-libsql");
|
|
446
|
+
Handlebars.registerHelper("sqliteAdapterClass", () => "PrismaLibSql");
|
|
447
|
+
Handlebars.registerHelper("seedCommand", (packageManager) => getSeedCommand(packageManager));
|
|
448
|
+
Handlebars.registerHelper("prismaCommand", (packageManager, subcommand) => getPrismaCommand(packageManager, subcommand));
|
|
449
|
+
Handlebars.registerHelper("runtimeScript", (packageManager, kind, sourceEntrypoint, builtEntrypoint, options) => {
|
|
450
|
+
if (!packageManager) return "";
|
|
451
|
+
const hash = options.hash;
|
|
452
|
+
return getRuntimeScriptCommand(packageManager, kind, {
|
|
453
|
+
sourceEntrypoint,
|
|
454
|
+
builtEntrypoint,
|
|
455
|
+
denoFlags: getOptionalHashStringList(hash, "denoFlags"),
|
|
456
|
+
emit: hash.emit === true
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
function findPackageRoot(startDir) {
|
|
460
|
+
let currentDir = startDir;
|
|
461
|
+
while (true) {
|
|
462
|
+
if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
|
|
463
|
+
const parentDir = path.dirname(currentDir);
|
|
464
|
+
if (parentDir === currentDir) break;
|
|
465
|
+
currentDir = parentDir;
|
|
466
|
+
}
|
|
467
|
+
throw new Error(`Unable to locate package root from: ${startDir}`);
|
|
468
|
+
}
|
|
469
|
+
function resolveTemplatesDir(relativeTemplatesDir) {
|
|
470
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
471
|
+
const packageRoot = findPackageRoot(path.dirname(currentFilePath));
|
|
472
|
+
const templatePath = path.join(packageRoot, relativeTemplatesDir);
|
|
473
|
+
if (!existsSync(templatePath)) throw new Error(`Template directory not found at: ${templatePath}`);
|
|
474
|
+
return templatePath;
|
|
475
|
+
}
|
|
476
|
+
async function getTemplateFilesRecursively(dir) {
|
|
477
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
478
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
479
|
+
const entryPath = path.join(dir, entry.name);
|
|
480
|
+
if (entry.isDirectory()) return getTemplateFilesRecursively(entryPath);
|
|
481
|
+
if (!entry.isFile()) return [];
|
|
482
|
+
return [entryPath];
|
|
483
|
+
}))).flat();
|
|
484
|
+
}
|
|
485
|
+
function stripHbsExtension(filePath) {
|
|
486
|
+
return filePath.endsWith(".hbs") ? filePath.slice(0, -4) : filePath;
|
|
487
|
+
}
|
|
488
|
+
function ensureTrailingNewline(content) {
|
|
489
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
490
|
+
}
|
|
491
|
+
async function renderTemplateFile(opts) {
|
|
492
|
+
const { templateFilePath, outputPath, context } = opts;
|
|
493
|
+
const templateContent = await fs.readFile(templateFilePath, "utf8");
|
|
494
|
+
const outputContent = templateFilePath.endsWith(".hbs") ? Handlebars.compile(templateContent, {
|
|
495
|
+
noEscape: true,
|
|
496
|
+
strict: true
|
|
497
|
+
})(context) : templateContent;
|
|
498
|
+
if (templateFilePath.endsWith(".hbs") && outputContent.trim().length === 0) return;
|
|
499
|
+
await fs.outputFile(outputPath, ensureTrailingNewline(outputContent), "utf8");
|
|
500
|
+
}
|
|
501
|
+
async function renderTemplateTree(opts) {
|
|
502
|
+
const { templateRoot, outputDir, context } = opts;
|
|
503
|
+
const templateFiles = await getTemplateFilesRecursively(templateRoot);
|
|
504
|
+
for (const templateFilePath of templateFiles) {
|
|
505
|
+
const relativeOutputPath = stripHbsExtension(path.relative(templateRoot, templateFilePath));
|
|
506
|
+
await renderTemplateFile({
|
|
507
|
+
templateFilePath,
|
|
508
|
+
outputPath: path.join(outputDir, relativeOutputPath),
|
|
509
|
+
context
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/templates/render-create-template.ts
|
|
515
|
+
function getCreateTemplateDir(template) {
|
|
516
|
+
return resolveTemplatesDir(`templates/create/${template}`);
|
|
517
|
+
}
|
|
518
|
+
function createTemplateContext(projectName, provider, schemaPreset, packageManager, compute) {
|
|
519
|
+
return {
|
|
520
|
+
projectName,
|
|
521
|
+
provider,
|
|
522
|
+
schemaPreset,
|
|
523
|
+
packageManager,
|
|
524
|
+
compute
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
async function scaffoldCreateTemplate(opts) {
|
|
528
|
+
const { projectDir, projectName, template, provider, schemaPreset, packageManager } = opts;
|
|
529
|
+
await renderTemplateTree({
|
|
530
|
+
templateRoot: getCreateTemplateDir(template),
|
|
531
|
+
outputDir: projectDir,
|
|
532
|
+
context: createTemplateContext(projectName, provider, schemaPreset, packageManager, opts.compute === true)
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region src/constants/db-packages.ts
|
|
537
|
+
function getDbPackages(provider, _packageManager) {
|
|
538
|
+
switch (provider) {
|
|
539
|
+
case "postgresql":
|
|
540
|
+
case "cockroachdb": return "@prisma/adapter-pg";
|
|
541
|
+
case "mysql": return "@prisma/adapter-mariadb";
|
|
542
|
+
case "sqlite": return "@prisma/adapter-libsql";
|
|
543
|
+
case "sqlserver": return "@prisma/adapter-mssql";
|
|
544
|
+
default: throw new Error(`Unsupported database provider: ${String(provider)}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/tasks/install.ts
|
|
549
|
+
function getPrismaScriptMap(packageManager) {
|
|
550
|
+
if (packageManager === "deno") {
|
|
551
|
+
const prismaCli = `deno run -A --env-file=.env ${getDenoPrismaSpecifier()}`;
|
|
552
|
+
return {
|
|
553
|
+
"db:generate": `${prismaCli} generate`,
|
|
554
|
+
"db:push": `${prismaCli} db push`,
|
|
555
|
+
"db:migrate": `${prismaCli} migrate dev`,
|
|
556
|
+
"db:seed": `${prismaCli} db seed`
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (packageManager === "bun") {
|
|
560
|
+
const prismaCli = "bun --env-file=.env ./node_modules/.bin/prisma";
|
|
561
|
+
return {
|
|
562
|
+
"db:generate": `${prismaCli} generate`,
|
|
563
|
+
"db:push": `${prismaCli} db push`,
|
|
564
|
+
"db:migrate": `${prismaCli} migrate dev`,
|
|
565
|
+
"db:seed": `${prismaCli} db seed`
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
"db:generate": "prisma generate",
|
|
570
|
+
"db:push": "prisma db push",
|
|
571
|
+
"db:migrate": "prisma migrate dev",
|
|
572
|
+
"db:seed": "prisma db seed"
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function getVersion(packageName) {
|
|
576
|
+
return dependencyVersionMap[packageName];
|
|
577
|
+
}
|
|
578
|
+
function unique(items) {
|
|
579
|
+
return [...new Set(items)];
|
|
580
|
+
}
|
|
581
|
+
function sortRecord(record) {
|
|
582
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
583
|
+
}
|
|
584
|
+
async function projectContainsText(projectDir, text) {
|
|
585
|
+
const directories = [projectDir];
|
|
586
|
+
while (directories.length > 0) {
|
|
587
|
+
const currentDirectory = directories.pop();
|
|
588
|
+
if (!currentDirectory) continue;
|
|
589
|
+
const entries = await fs.readdir(currentDirectory, { withFileTypes: true });
|
|
590
|
+
for (const entry of entries) {
|
|
591
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
592
|
+
const entryPath = path.join(currentDirectory, entry.name);
|
|
593
|
+
if (entry.isDirectory()) {
|
|
594
|
+
directories.push(entryPath);
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (!entry.isFile() || !/\.(c|m)?[jt]sx?$/.test(entry.name)) continue;
|
|
598
|
+
if ((await fs.readFile(entryPath, "utf8")).includes(text)) return true;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
async function addPackageDependency(opts) {
|
|
604
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, scripts = {}, scriptMode, projectDir } = opts;
|
|
605
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
606
|
+
if (!await fs.pathExists(pkgJsonPath)) throw new Error(`No package.json found in ${projectDir}. Run this command inside an existing JavaScript/TypeScript project.`);
|
|
607
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
608
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
609
|
+
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
610
|
+
if (!pkgJson.scripts) pkgJson.scripts = {};
|
|
611
|
+
for (const pkgName of unique(dependencies)) {
|
|
612
|
+
const version = getVersion(pkgName);
|
|
613
|
+
if (version) pkgJson.dependencies[pkgName] = version;
|
|
614
|
+
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
615
|
+
}
|
|
616
|
+
for (const pkgName of unique(devDependencies)) {
|
|
617
|
+
const version = getVersion(pkgName);
|
|
618
|
+
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
619
|
+
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
620
|
+
}
|
|
621
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
622
|
+
for (const [scriptName, command] of Object.entries(scripts)) {
|
|
623
|
+
if (scriptMode === "if-missing") {
|
|
624
|
+
if (typeof pkgJson.scripts[scriptName] !== "string" || pkgJson.scripts[scriptName].trim().length === 0) pkgJson.scripts[scriptName] = command;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
pkgJson.scripts[scriptName] = command;
|
|
628
|
+
}
|
|
629
|
+
pkgJson.dependencies = sortRecord(pkgJson.dependencies);
|
|
630
|
+
pkgJson.devDependencies = sortRecord(pkgJson.devDependencies);
|
|
631
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
632
|
+
}
|
|
633
|
+
async function writePrismaDependencies(provider, packageManager, projectDir = process.cwd()) {
|
|
634
|
+
const dependencies = ["@prisma/client"];
|
|
635
|
+
const devDependencies = ["prisma"];
|
|
636
|
+
dependencies.push(getDbPackages(provider, packageManager));
|
|
637
|
+
if (provider === "sqlite") dependencies.push("@libsql/client");
|
|
638
|
+
if (requiresDotenvConfigImport(packageManager) || await projectContainsText(projectDir, "dotenv/config")) dependencies.push("dotenv");
|
|
639
|
+
await addPackageDependency({
|
|
640
|
+
dependencies,
|
|
641
|
+
devDependencies,
|
|
642
|
+
scripts: getPrismaScriptMap(packageManager),
|
|
643
|
+
scriptMode: "if-missing",
|
|
644
|
+
projectDir
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
async function writeCreateTemplateDependencies(opts) {
|
|
648
|
+
const { template, packageManager, projectDir = process.cwd() } = opts;
|
|
649
|
+
const targets = getCreateTemplateDependencies(template, packageManager);
|
|
650
|
+
for (const dependencyTarget of targets) {
|
|
651
|
+
const targetDirectory = path.join(projectDir, path.dirname(dependencyTarget.packageJsonPath));
|
|
652
|
+
await addPackageDependency({
|
|
653
|
+
dependencies: dependencyTarget.dependencies,
|
|
654
|
+
devDependencies: dependencyTarget.devDependencies,
|
|
655
|
+
customDependencies: dependencyTarget.customDependencies,
|
|
656
|
+
projectDir: targetDirectory
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function installProjectDependencies(packageManager, projectDir = process.cwd(), options = {}) {
|
|
661
|
+
const verbose = options.verbose === true;
|
|
662
|
+
const installCommand = getInstallArgs(packageManager);
|
|
663
|
+
await execa(installCommand.command, installCommand.args, {
|
|
664
|
+
cwd: projectDir,
|
|
665
|
+
stdio: verbose ? "inherit" : "pipe"
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/tasks/prisma-postgres.ts
|
|
670
|
+
const PRISMA_POSTGRES_TEMPORARY_NOTICE = "Prisma Postgres is temporary for 24 hours. Claim this database before it expires using CLAIM_URL.";
|
|
671
|
+
const CREATE_DB_COMMAND_ARGS = ["create-db@latest", "--json"];
|
|
672
|
+
function parseCreateDbJson(rawOutput) {
|
|
673
|
+
const trimmed = rawOutput.trim();
|
|
674
|
+
if (!trimmed) throw new Error("create-db returned empty output.");
|
|
675
|
+
const jsonCandidates = [trimmed];
|
|
676
|
+
const firstBrace = trimmed.indexOf("{");
|
|
677
|
+
const lastBrace = trimmed.lastIndexOf("}");
|
|
678
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) jsonCandidates.push(trimmed.slice(firstBrace, lastBrace + 1));
|
|
679
|
+
for (const candidate of jsonCandidates) try {
|
|
680
|
+
return JSON.parse(candidate);
|
|
681
|
+
} catch {}
|
|
682
|
+
throw new Error(`Unable to parse create-db JSON output: ${trimmed}`);
|
|
683
|
+
}
|
|
684
|
+
function pickConnectionString(payload) {
|
|
685
|
+
if (typeof payload.connectionString === "string" && payload.connectionString.length > 0) return payload.connectionString;
|
|
686
|
+
if (typeof payload.databaseUrl === "string" && payload.databaseUrl.length > 0) return payload.databaseUrl;
|
|
687
|
+
}
|
|
688
|
+
function extractErrorMessage(payload, fallback) {
|
|
689
|
+
if (typeof payload.message === "string" && payload.message.length > 0) return payload.message;
|
|
690
|
+
if (typeof payload.error === "string" && payload.error.length > 0) return payload.error;
|
|
691
|
+
return fallback;
|
|
692
|
+
}
|
|
693
|
+
async function provisionPrismaPostgres(packageManager, projectDir = process.cwd()) {
|
|
694
|
+
const command = getPackageExecutionArgs(packageManager, [...CREATE_DB_COMMAND_ARGS]);
|
|
695
|
+
const commandString = getCreateDbCommand(packageManager);
|
|
696
|
+
let stdout;
|
|
697
|
+
try {
|
|
698
|
+
stdout = (await execa(command.command, command.args, {
|
|
699
|
+
cwd: projectDir,
|
|
700
|
+
stdio: "pipe"
|
|
701
|
+
})).stdout;
|
|
702
|
+
} catch (error) {
|
|
703
|
+
if (error instanceof Error && "stderr" in error) {
|
|
704
|
+
const stderr = String(error.stderr ?? "").trim();
|
|
705
|
+
const message = stderr.length > 0 ? stderr : error.message;
|
|
706
|
+
throw new Error(`Failed to run ${commandString}: ${message}`);
|
|
707
|
+
}
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
const payload = parseCreateDbJson(stdout);
|
|
711
|
+
if (payload.success === false) throw new Error(extractErrorMessage(payload, "create-db reported failure."));
|
|
712
|
+
const databaseUrl = pickConnectionString(payload);
|
|
713
|
+
if (!databaseUrl) throw new Error("create-db did not return a connection string.");
|
|
714
|
+
return {
|
|
715
|
+
databaseUrl,
|
|
716
|
+
claimUrl: typeof payload.claimUrl === "string" && payload.claimUrl.length > 0 ? payload.claimUrl : typeof payload.claimURL === "string" && payload.claimURL.length > 0 ? payload.claimURL : void 0
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function getCreateDbCommand(packageManager) {
|
|
720
|
+
return getPackageExecutionCommand(packageManager, [...CREATE_DB_COMMAND_ARGS]);
|
|
721
|
+
}
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/tasks/setup-prisma.ts
|
|
724
|
+
const DEFAULT_DATABASE_PROVIDER = "postgresql";
|
|
725
|
+
const DEFAULT_SCHEMA_PRESET$1 = "empty";
|
|
726
|
+
const DEFAULT_PRISMA_POSTGRES = true;
|
|
727
|
+
const DEFAULT_INSTALL = true;
|
|
728
|
+
const DEFAULT_GENERATE = true;
|
|
729
|
+
const DEFAULT_MIGRATE_AND_SEED = true;
|
|
730
|
+
const PRISMA_POSTGRES_MIGRATION_DELAY_MS = 2e3;
|
|
731
|
+
const requiredPrismaFileGroups = [
|
|
732
|
+
["prisma/schema.prisma", "packages/db/prisma/schema.prisma"],
|
|
733
|
+
["prisma/seed.ts", "packages/db/prisma/seed.ts"],
|
|
734
|
+
["prisma.config.ts", "packages/db/prisma.config.ts"],
|
|
735
|
+
[
|
|
736
|
+
"src/lib/prisma.ts",
|
|
737
|
+
"src/lib/prisma.server.ts",
|
|
738
|
+
"src/lib/server/prisma.ts",
|
|
739
|
+
"server/utils/prisma.ts",
|
|
740
|
+
"packages/db/src/client.ts"
|
|
741
|
+
]
|
|
742
|
+
];
|
|
743
|
+
async function resolvePrismaProjectDir(projectDir) {
|
|
744
|
+
const monorepoDbDir = path.join(projectDir, "packages/db");
|
|
745
|
+
if (await fs.pathExists(path.join(monorepoDbDir, "prisma/schema.prisma"))) return monorepoDbDir;
|
|
746
|
+
return projectDir;
|
|
747
|
+
}
|
|
748
|
+
async function promptForDatabaseProvider() {
|
|
749
|
+
const databaseProvider = await select({
|
|
750
|
+
message: "Select your database",
|
|
751
|
+
initialValue: DEFAULT_DATABASE_PROVIDER,
|
|
752
|
+
options: [
|
|
753
|
+
{
|
|
754
|
+
value: "postgresql",
|
|
755
|
+
label: "PostgreSQL",
|
|
756
|
+
hint: "Default"
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
value: "mysql",
|
|
760
|
+
label: "MySQL"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
value: "sqlite",
|
|
764
|
+
label: "SQLite"
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
value: "sqlserver",
|
|
768
|
+
label: "SQL Server"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
value: "cockroachdb",
|
|
772
|
+
label: "CockroachDB"
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
});
|
|
776
|
+
if (isCancel(databaseProvider)) {
|
|
777
|
+
cancel("Operation cancelled.");
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
return DatabaseProviderSchema.parse(databaseProvider);
|
|
781
|
+
}
|
|
782
|
+
function getPackageManagerHint(option, detected) {
|
|
783
|
+
if (option === detected) return "Detected";
|
|
784
|
+
if (option === "bun") return "Fast runtime + package manager";
|
|
785
|
+
if (option === "deno") return "Runtime + package manager";
|
|
786
|
+
}
|
|
787
|
+
async function promptForPackageManager(detectedPackageManager) {
|
|
788
|
+
const packageManager = await select({
|
|
789
|
+
message: "Choose package manager",
|
|
790
|
+
initialValue: detectedPackageManager,
|
|
791
|
+
options: [
|
|
792
|
+
{
|
|
793
|
+
value: "npm",
|
|
794
|
+
label: "npm",
|
|
795
|
+
hint: getPackageManagerHint("npm", detectedPackageManager)
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
value: "pnpm",
|
|
799
|
+
label: "pnpm",
|
|
800
|
+
hint: getPackageManagerHint("pnpm", detectedPackageManager)
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
value: "yarn",
|
|
804
|
+
label: "yarn",
|
|
805
|
+
hint: getPackageManagerHint("yarn", detectedPackageManager)
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
value: "bun",
|
|
809
|
+
label: "bun",
|
|
810
|
+
hint: getPackageManagerHint("bun", detectedPackageManager)
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
value: "deno",
|
|
814
|
+
label: "deno",
|
|
815
|
+
hint: getPackageManagerHint("deno", detectedPackageManager)
|
|
816
|
+
}
|
|
817
|
+
]
|
|
818
|
+
});
|
|
819
|
+
if (isCancel(packageManager)) {
|
|
820
|
+
cancel("Operation cancelled.");
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
return PackageManagerSchema.parse(packageManager);
|
|
824
|
+
}
|
|
825
|
+
async function promptForDependencyInstall(packageManager) {
|
|
826
|
+
const shouldInstall = await confirm({
|
|
827
|
+
message: `Install dependencies now with ${getInstallCommand(packageManager)}?`,
|
|
828
|
+
initialValue: true
|
|
829
|
+
});
|
|
830
|
+
if (isCancel(shouldInstall)) {
|
|
831
|
+
cancel("Operation cancelled.");
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
return Boolean(shouldInstall);
|
|
835
|
+
}
|
|
836
|
+
async function promptForMigrateAndSeed() {
|
|
837
|
+
const shouldMigrateAndSeed = await confirm({
|
|
838
|
+
message: "Run an initial migration and seed your database now?",
|
|
839
|
+
initialValue: DEFAULT_MIGRATE_AND_SEED
|
|
840
|
+
});
|
|
841
|
+
if (isCancel(shouldMigrateAndSeed)) {
|
|
842
|
+
cancel("Operation cancelled.");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
return Boolean(shouldMigrateAndSeed);
|
|
846
|
+
}
|
|
847
|
+
async function promptForPrismaPostgres() {
|
|
848
|
+
const shouldUsePrismaPostgres = await confirm({
|
|
849
|
+
message: "Use Prisma Postgres and write DATABASE_URL automatically?",
|
|
850
|
+
initialValue: true
|
|
851
|
+
});
|
|
852
|
+
if (isCancel(shouldUsePrismaPostgres)) {
|
|
853
|
+
cancel("Operation cancelled.");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
return Boolean(shouldUsePrismaPostgres);
|
|
857
|
+
}
|
|
858
|
+
function getCommandErrorMessage(error) {
|
|
859
|
+
if (error instanceof Error && "stderr" in error) {
|
|
860
|
+
const stderr = String(error.stderr ?? "").trim();
|
|
861
|
+
if (stderr.length > 0) return stderr;
|
|
862
|
+
}
|
|
863
|
+
return error instanceof Error ? error.message : String(error);
|
|
864
|
+
}
|
|
865
|
+
async function collectPrismaSetupInitialContext(input, options = {}) {
|
|
866
|
+
const projectDir = path.resolve(options.projectDir ?? process.cwd());
|
|
867
|
+
const useDefaults = input.yes === true;
|
|
868
|
+
const verbose = input.verbose === true;
|
|
869
|
+
const shouldGenerate = input.generate ?? DEFAULT_GENERATE;
|
|
870
|
+
const databaseProvider = input.provider ?? (useDefaults ? DEFAULT_DATABASE_PROVIDER : await promptForDatabaseProvider());
|
|
871
|
+
if (!databaseProvider) return;
|
|
872
|
+
const schemaPreset = input.schemaPreset ?? options.defaultSchemaPreset ?? DEFAULT_SCHEMA_PRESET$1;
|
|
873
|
+
const databaseUrl = input.databaseUrl;
|
|
874
|
+
const detectedPackageManager = await detectPackageManager(projectDir);
|
|
875
|
+
const packageManager = input.packageManager ?? (useDefaults ? detectedPackageManager : await promptForPackageManager(detectedPackageManager));
|
|
876
|
+
if (!packageManager) return;
|
|
877
|
+
const shouldInstall = input.install ?? (useDefaults ? DEFAULT_INSTALL : await promptForDependencyInstall(packageManager));
|
|
878
|
+
if (shouldInstall === void 0) return;
|
|
879
|
+
return {
|
|
880
|
+
projectDir,
|
|
881
|
+
verbose,
|
|
882
|
+
shouldGenerate,
|
|
883
|
+
databaseProvider,
|
|
884
|
+
schemaPreset,
|
|
885
|
+
databaseUrl,
|
|
886
|
+
packageManager,
|
|
887
|
+
shouldInstall
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
async function completePrismaSetupContext(input, context, options = {}) {
|
|
891
|
+
const useDefaults = input.yes === true;
|
|
892
|
+
let shouldUsePrismaPostgres = false;
|
|
893
|
+
const shouldUseComputePostgres = context.databaseProvider === "postgresql" && !context.databaseUrl && options.useComputePostgres === true;
|
|
894
|
+
if (context.databaseProvider === "postgresql" && !context.databaseUrl && !shouldUseComputePostgres) {
|
|
895
|
+
const prismaPostgresChoice = input.prismaPostgres ?? (useDefaults ? DEFAULT_PRISMA_POSTGRES : await promptForPrismaPostgres());
|
|
896
|
+
if (prismaPostgresChoice === void 0) return;
|
|
897
|
+
shouldUsePrismaPostgres = prismaPostgresChoice;
|
|
898
|
+
}
|
|
899
|
+
const shouldMigrateAndSeed = !(context.shouldInstall && context.shouldGenerate && !(shouldUseComputePostgres && options.skipMigrateAndSeedPrompt)) ? false : input.migrateAndSeed ?? (useDefaults ? DEFAULT_MIGRATE_AND_SEED : await promptForMigrateAndSeed());
|
|
900
|
+
if (shouldMigrateAndSeed === void 0) return;
|
|
901
|
+
return {
|
|
902
|
+
...context,
|
|
903
|
+
shouldUsePrismaPostgres,
|
|
904
|
+
shouldMigrateAndSeed
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
function getDefaultDatabaseUrl(provider) {
|
|
908
|
+
switch (provider) {
|
|
909
|
+
case "postgresql": return "postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public";
|
|
910
|
+
case "cockroachdb": return "postgresql://johndoe:randompassword@localhost:26257/mydb?schema=public";
|
|
911
|
+
case "mysql": return "mysql://johndoe:randompassword@localhost:3306/mydb";
|
|
912
|
+
case "sqlite": return "file:./dev.db";
|
|
913
|
+
case "sqlserver": return "sqlserver://localhost:1433;database=mydb;user=SA;password=randompassword;";
|
|
914
|
+
default: throw new Error(`Unsupported provider: ${String(provider)}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function escapeRegExp(value) {
|
|
918
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
919
|
+
}
|
|
920
|
+
function escapeEnvValue(value) {
|
|
921
|
+
if (/[\r\n]/.test(value)) throw new Error("Environment variable values must be single-line.");
|
|
922
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
923
|
+
}
|
|
924
|
+
function hasEnvVar(content, envVarName) {
|
|
925
|
+
const escapedName = escapeRegExp(envVarName);
|
|
926
|
+
return new RegExp(`(^|\\n)\\s*${escapedName}\\s*=`).test(content);
|
|
927
|
+
}
|
|
928
|
+
function hasEnvComment(content, comment) {
|
|
929
|
+
const escapedComment = escapeRegExp(comment);
|
|
930
|
+
return new RegExp(`(^|\\n)\\s*#\\s*${escapedComment}\\s*(?=\\n|$)`).test(content);
|
|
931
|
+
}
|
|
932
|
+
async function ensureEnvVarInEnv(projectDir, envVarName, envVarValue, opts) {
|
|
933
|
+
const envPath = path.join(projectDir, ".env");
|
|
934
|
+
const envLine = `${envVarName}="${escapeEnvValue(envVarValue)}"`;
|
|
935
|
+
if (!await fs.pathExists(envPath)) {
|
|
936
|
+
const content = opts.comment ? `# ${opts.comment}\n${envLine}\n` : `${envLine}\n`;
|
|
937
|
+
await fs.writeFile(envPath, content, "utf8");
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const existingContent = await fs.readFile(envPath, "utf8");
|
|
941
|
+
if (hasEnvVar(existingContent, envVarName)) {
|
|
942
|
+
if (opts.mode === "keep-existing") return;
|
|
943
|
+
const escapedName = escapeRegExp(envVarName);
|
|
944
|
+
const lineRegex = new RegExp(`(^|\\n)\\s*${escapedName}\\s*=.*(?=\\n|$)`, "gm");
|
|
945
|
+
const updatedContent = existingContent.replace(lineRegex, `$1${envLine}`);
|
|
946
|
+
if (updatedContent === existingContent) return;
|
|
947
|
+
await fs.writeFile(envPath, updatedContent, "utf8");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const insertion = `${existingContent.endsWith("\n") ? "" : "\n"}${opts.comment ? `\n# ${opts.comment}\n` : "\n"}${envLine}\n`;
|
|
951
|
+
await fs.appendFile(envPath, insertion, "utf8");
|
|
952
|
+
}
|
|
953
|
+
async function ensureEnvComment(projectDir, comment) {
|
|
954
|
+
const envPath = path.join(projectDir, ".env");
|
|
955
|
+
const commentLine = `# ${comment}`;
|
|
956
|
+
if (!await fs.pathExists(envPath)) {
|
|
957
|
+
await fs.writeFile(envPath, `${commentLine}\n`, "utf8");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const existingContent = await fs.readFile(envPath, "utf8");
|
|
961
|
+
if (hasEnvComment(existingContent, comment)) return;
|
|
962
|
+
const separator = existingContent.endsWith("\n") ? "" : "\n";
|
|
963
|
+
await fs.appendFile(envPath, `${separator}${commentLine}\n`, "utf8");
|
|
964
|
+
}
|
|
965
|
+
function hasGitignoreEntry(content, entry) {
|
|
966
|
+
const escapedEntry = escapeRegExp(entry);
|
|
967
|
+
const escapedWithLeadingSlash = escapeRegExp(`/${entry}`);
|
|
968
|
+
const escapedWithTrailingSlash = escapeRegExp(`${entry}/`);
|
|
969
|
+
const escapedWithLeadingAndTrailingSlash = escapeRegExp(`/${entry}/`);
|
|
970
|
+
return new RegExp(`(^|\\n)\\s*(?:${escapedEntry}|${escapedWithLeadingSlash}|${escapedWithTrailingSlash}|${escapedWithLeadingAndTrailingSlash})\\s*(?=\\n|$)`).test(content);
|
|
971
|
+
}
|
|
972
|
+
async function ensureGitignoreEntry(projectDir, entry) {
|
|
973
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
974
|
+
if (!await fs.pathExists(gitignorePath)) {
|
|
975
|
+
await fs.writeFile(gitignorePath, `${entry}\n`, "utf8");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const existingContent = await fs.readFile(gitignorePath, "utf8");
|
|
979
|
+
if (hasGitignoreEntry(existingContent, entry)) return;
|
|
980
|
+
const separator = existingContent.endsWith("\n") ? "" : "\n";
|
|
981
|
+
await fs.appendFile(gitignorePath, `${separator}${entry}\n`, "utf8");
|
|
982
|
+
}
|
|
983
|
+
async function ensureRequiredPrismaFiles(projectDir) {
|
|
984
|
+
const missingFiles = [];
|
|
985
|
+
for (const candidates of requiredPrismaFileGroups) {
|
|
986
|
+
let foundCandidate = false;
|
|
987
|
+
for (const relativePath of candidates) {
|
|
988
|
+
const absolutePath = path.join(projectDir, relativePath);
|
|
989
|
+
if (await fs.pathExists(absolutePath)) {
|
|
990
|
+
foundCandidate = true;
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (!foundCandidate) missingFiles.push(candidates.join(" or "));
|
|
995
|
+
}
|
|
996
|
+
if (missingFiles.length > 0) throw new Error(`Template is missing required Prisma files: ${missingFiles.join(", ")}`);
|
|
997
|
+
}
|
|
998
|
+
async function finalizePrismaFiles(options) {
|
|
999
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
1000
|
+
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
1001
|
+
await ensureRequiredPrismaFiles(projectDir);
|
|
1002
|
+
const generatedDir = await fs.pathExists(path.join(prismaProjectDir, "server/utils/prisma.ts")) ? "server/generated" : "src/generated";
|
|
1003
|
+
await ensureEnvVarInEnv(prismaProjectDir, "DATABASE_URL", options.databaseUrl ?? getDefaultDatabaseUrl(options.provider), {
|
|
1004
|
+
mode: options.databaseUrl ? "upsert" : "keep-existing",
|
|
1005
|
+
comment: "Added by create-prisma"
|
|
1006
|
+
});
|
|
1007
|
+
if (options.claimUrl) {
|
|
1008
|
+
await ensureEnvVarInEnv(prismaProjectDir, "CLAIM_URL", options.claimUrl, {
|
|
1009
|
+
mode: "upsert",
|
|
1010
|
+
comment: PRISMA_POSTGRES_TEMPORARY_NOTICE
|
|
1011
|
+
});
|
|
1012
|
+
await ensureEnvComment(prismaProjectDir, PRISMA_POSTGRES_TEMPORARY_NOTICE);
|
|
1013
|
+
}
|
|
1014
|
+
await ensureGitignoreEntry(prismaProjectDir, generatedDir);
|
|
1015
|
+
}
|
|
1016
|
+
async function provisionPrismaPostgresIfNeeded(context, projectDir) {
|
|
1017
|
+
if (!context.shouldUsePrismaPostgres) return { databaseUrl: context.databaseUrl };
|
|
1018
|
+
const createDbCommand = getCreateDbCommand(context.packageManager);
|
|
1019
|
+
const prismaPostgresSpinner = spinner();
|
|
1020
|
+
prismaPostgresSpinner.start(`Provisioning Prisma Postgres with ${createDbCommand}...`);
|
|
1021
|
+
try {
|
|
1022
|
+
const prismaPostgresResult = await provisionPrismaPostgres(context.packageManager, projectDir);
|
|
1023
|
+
prismaPostgresSpinner.stop("Prisma Postgres database provisioned.");
|
|
1024
|
+
return {
|
|
1025
|
+
databaseUrl: prismaPostgresResult.databaseUrl,
|
|
1026
|
+
claimUrl: prismaPostgresResult.claimUrl
|
|
1027
|
+
};
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1030
|
+
prismaPostgresSpinner.stop("Could not provision Prisma Postgres.");
|
|
1031
|
+
return {
|
|
1032
|
+
databaseUrl: context.databaseUrl,
|
|
1033
|
+
warning: `Prisma Postgres provisioning failed: ${errorMessage}`
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async function writeDependenciesForContext(context, projectDir) {
|
|
1038
|
+
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
1039
|
+
try {
|
|
1040
|
+
await writePrismaDependencies(context.databaseProvider, context.packageManager, prismaProjectDir);
|
|
1041
|
+
return true;
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
cancel(getCommandErrorMessage(error));
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async function installDependenciesForContext(context, projectDir) {
|
|
1048
|
+
if (!context.shouldInstall) return true;
|
|
1049
|
+
const installCommand = getInstallCommand(context.packageManager);
|
|
1050
|
+
if (context.verbose) {
|
|
1051
|
+
log.step(`Running ${installCommand}`);
|
|
1052
|
+
try {
|
|
1053
|
+
await installProjectDependencies(context.packageManager, projectDir, { verbose: context.verbose });
|
|
1054
|
+
log.success("Dependencies installed.");
|
|
1055
|
+
return true;
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
cancel(`Failed to run ${installCommand}: ${getCommandErrorMessage(error)}`);
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
const installSpinner = spinner();
|
|
1062
|
+
installSpinner.start(`Running ${installCommand}...`);
|
|
1063
|
+
try {
|
|
1064
|
+
await installProjectDependencies(context.packageManager, projectDir, { verbose: context.verbose });
|
|
1065
|
+
installSpinner.stop("Dependencies installed.");
|
|
1066
|
+
return true;
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
installSpinner.stop("Could not install dependencies.");
|
|
1069
|
+
cancel(`Failed to run ${installCommand}: ${getCommandErrorMessage(error)}`);
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async function finalizePrismaFilesForContext(context, projectDir, provisionResult) {
|
|
1074
|
+
const initSpinner = spinner();
|
|
1075
|
+
initSpinner.start("Preparing Prisma files...");
|
|
1076
|
+
try {
|
|
1077
|
+
await finalizePrismaFiles({
|
|
1078
|
+
provider: context.databaseProvider,
|
|
1079
|
+
databaseUrl: provisionResult.databaseUrl,
|
|
1080
|
+
claimUrl: provisionResult.claimUrl,
|
|
1081
|
+
projectDir
|
|
1082
|
+
});
|
|
1083
|
+
initSpinner.stop("Prisma files ready.");
|
|
1084
|
+
return true;
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
initSpinner.stop("Could not prepare Prisma files.");
|
|
1087
|
+
cancel(getCommandErrorMessage(error));
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function generatePrismaClientForContext(context, projectDir) {
|
|
1092
|
+
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
1093
|
+
if (!context.shouldGenerate) return { didGenerateClient: false };
|
|
1094
|
+
const generateCommand = getPrismaCliCommand$1(context.packageManager, ["generate"]);
|
|
1095
|
+
if (context.verbose) log.step(`Running ${generateCommand}`);
|
|
1096
|
+
const generateSpinner = context.verbose ? void 0 : spinner();
|
|
1097
|
+
generateSpinner?.start("Generating Prisma Client...");
|
|
1098
|
+
try {
|
|
1099
|
+
const generateArgs = getPrismaCliArgs(context.packageManager, ["generate"]);
|
|
1100
|
+
await execa(generateArgs.command, generateArgs.args, {
|
|
1101
|
+
cwd: prismaProjectDir,
|
|
1102
|
+
stdio: context.verbose ? "inherit" : "pipe"
|
|
1103
|
+
});
|
|
1104
|
+
if (context.verbose) log.success("Prisma Client generated.");
|
|
1105
|
+
else generateSpinner?.stop("Prisma Client generated.");
|
|
1106
|
+
return { didGenerateClient: true };
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
if (context.verbose) log.warn("Could not generate Prisma Client.");
|
|
1109
|
+
else generateSpinner?.stop("Could not generate Prisma Client.");
|
|
1110
|
+
return {
|
|
1111
|
+
didGenerateClient: false,
|
|
1112
|
+
warning: `Prisma generate failed: ${getCommandErrorMessage(error)}`
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function buildWarningLines(provisionWarning, generateWarning, migrateAndSeedWarning) {
|
|
1117
|
+
const warningLines = [];
|
|
1118
|
+
if (provisionWarning) warningLines.push(`- ${provisionWarning}`);
|
|
1119
|
+
if (generateWarning) warningLines.push(`- ${generateWarning}`);
|
|
1120
|
+
if (migrateAndSeedWarning) warningLines.push(`- ${migrateAndSeedWarning}`);
|
|
1121
|
+
return warningLines;
|
|
1122
|
+
}
|
|
1123
|
+
function buildNextStepsForContext(opts) {
|
|
1124
|
+
const { context, options, didGenerateClient, didMigrate, didSeed } = opts;
|
|
1125
|
+
const nextSteps = [...options.prependNextSteps ?? []];
|
|
1126
|
+
if (!context.shouldInstall) nextSteps.push(`- ${getInstallCommand(context.packageManager)}`);
|
|
1127
|
+
if (!didGenerateClient || !context.shouldGenerate) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:generate")}`);
|
|
1128
|
+
if (options.includeMigrationAndSeedNextSteps !== false && !didMigrate) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:migrate")}`);
|
|
1129
|
+
if (options.includeMigrationAndSeedNextSteps !== false && !didSeed) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:seed")}`);
|
|
1130
|
+
if (options.includeDevNextStep) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "dev")}`);
|
|
1131
|
+
return nextSteps;
|
|
1132
|
+
}
|
|
1133
|
+
async function executePrismaSetupContext(context, options = {}) {
|
|
1134
|
+
const projectDir = path.resolve(options.projectDir ?? context.projectDir);
|
|
1135
|
+
const provisionResult = await provisionPrismaPostgresIfNeeded(context, projectDir);
|
|
1136
|
+
if (!provisionResult) return { ok: false };
|
|
1137
|
+
if (!await writeDependenciesForContext(context, projectDir)) return { ok: false };
|
|
1138
|
+
if (!await installDependenciesForContext(context, projectDir)) return { ok: false };
|
|
1139
|
+
if (!await finalizePrismaFilesForContext(context, projectDir, provisionResult)) return { ok: false };
|
|
1140
|
+
const databaseUrl = provisionResult.databaseUrl ?? context.databaseUrl ?? getDefaultDatabaseUrl(context.databaseProvider);
|
|
1141
|
+
const generateResult = await generatePrismaClientForContext(context, projectDir);
|
|
1142
|
+
const migrateAndSeedResult = await migrateAndSeedIfRequested(context, projectDir, {
|
|
1143
|
+
databaseUrl,
|
|
1144
|
+
didGenerateClient: generateResult.didGenerateClient
|
|
1145
|
+
});
|
|
1146
|
+
const warningLines = buildWarningLines(provisionResult.warning, generateResult.warning, migrateAndSeedResult.warning);
|
|
1147
|
+
return {
|
|
1148
|
+
ok: true,
|
|
1149
|
+
nextSteps: buildNextStepsForContext({
|
|
1150
|
+
context,
|
|
1151
|
+
options,
|
|
1152
|
+
didGenerateClient: generateResult.didGenerateClient,
|
|
1153
|
+
didMigrate: migrateAndSeedResult.didMigrate,
|
|
1154
|
+
didSeed: migrateAndSeedResult.didSeed
|
|
1155
|
+
}),
|
|
1156
|
+
warningSection: warningLines.length > 0 ? `\n\n${warningLines.join("\n")}` : "",
|
|
1157
|
+
databaseUrl: provisionResult.databaseUrl ?? context.databaseUrl
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
async function migrateAndSeedIfRequested(context, projectDir, options) {
|
|
1161
|
+
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
1162
|
+
if (!context.shouldMigrateAndSeed) return {
|
|
1163
|
+
didMigrate: false,
|
|
1164
|
+
didSeed: false
|
|
1165
|
+
};
|
|
1166
|
+
if (!options.didGenerateClient) return {
|
|
1167
|
+
didMigrate: false,
|
|
1168
|
+
didSeed: false,
|
|
1169
|
+
warning: "Skipped migrate + seed because the Prisma Client was not generated."
|
|
1170
|
+
};
|
|
1171
|
+
if (!options.databaseUrl) return {
|
|
1172
|
+
didMigrate: false,
|
|
1173
|
+
didSeed: false,
|
|
1174
|
+
warning: "Skipped migrate + seed because no DATABASE_URL is available."
|
|
1175
|
+
};
|
|
1176
|
+
const migrateInvocation = getPrismaCliArgs(context.packageManager, [
|
|
1177
|
+
"migrate",
|
|
1178
|
+
"dev",
|
|
1179
|
+
"--name",
|
|
1180
|
+
"init"
|
|
1181
|
+
]);
|
|
1182
|
+
const seedInvocation = getPrismaCliArgs(context.packageManager, ["db", "seed"]);
|
|
1183
|
+
const migrateSpinner = spinner();
|
|
1184
|
+
migrateSpinner.start("Creating and applying initial migration...");
|
|
1185
|
+
let didMigrate = false;
|
|
1186
|
+
try {
|
|
1187
|
+
if (context.shouldUsePrismaPostgres) await new Promise((resolve) => setTimeout(resolve, PRISMA_POSTGRES_MIGRATION_DELAY_MS));
|
|
1188
|
+
await execa(migrateInvocation.command, migrateInvocation.args, {
|
|
1189
|
+
cwd: prismaProjectDir,
|
|
1190
|
+
stdio: context.verbose ? "inherit" : "pipe"
|
|
1191
|
+
});
|
|
1192
|
+
migrateSpinner.stop("Initial migration applied.");
|
|
1193
|
+
didMigrate = true;
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
migrateSpinner.stop(`Migration failed${error instanceof Error ? `: ${error.message}` : "."}`);
|
|
1196
|
+
return {
|
|
1197
|
+
didMigrate: false,
|
|
1198
|
+
didSeed: false,
|
|
1199
|
+
warning: `Migration failed; run \`${getRunScriptCommand(context.packageManager, "db:migrate")}\` manually.`
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
const seedSpinner = spinner();
|
|
1203
|
+
seedSpinner.start("Seeding database...");
|
|
1204
|
+
let didSeed = false;
|
|
1205
|
+
try {
|
|
1206
|
+
await execa(seedInvocation.command, seedInvocation.args, {
|
|
1207
|
+
cwd: prismaProjectDir,
|
|
1208
|
+
stdio: context.verbose ? "inherit" : "pipe"
|
|
1209
|
+
});
|
|
1210
|
+
seedSpinner.stop("Database seeded.");
|
|
1211
|
+
didSeed = true;
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
seedSpinner.stop(`Seed failed${error instanceof Error ? `: ${error.message}` : "."}`);
|
|
1214
|
+
return {
|
|
1215
|
+
didMigrate,
|
|
1216
|
+
didSeed: false,
|
|
1217
|
+
warning: `Seed failed; run \`${getRunScriptCommand(context.packageManager, "db:seed")}\` manually.`
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
didMigrate,
|
|
1222
|
+
didSeed
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
//#endregion
|
|
1226
|
+
//#region src/tasks/setup-addons.ts
|
|
1227
|
+
const DEFAULT_ADDON_SCOPE = "project";
|
|
1228
|
+
const DEFAULT_SKILLS_AGENTS = [
|
|
1229
|
+
"claude-code",
|
|
1230
|
+
"codex",
|
|
1231
|
+
"cursor"
|
|
1232
|
+
];
|
|
1233
|
+
const DEFAULT_MCP_AGENTS = [
|
|
1234
|
+
"claude-code",
|
|
1235
|
+
"codex",
|
|
1236
|
+
"cursor"
|
|
1237
|
+
];
|
|
1238
|
+
const DEFAULT_EXTENSION_TARGETS = ["vscode", "cursor"];
|
|
1239
|
+
const PRISMA_MCP_SERVER = "https://mcp.prisma.io/mcp";
|
|
1240
|
+
const ADDON_OPTIONS = [
|
|
1241
|
+
{
|
|
1242
|
+
value: "skills",
|
|
1243
|
+
label: "Skills",
|
|
1244
|
+
hint: "Install curated Prisma skills to your selected coding agents"
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
value: "mcp",
|
|
1248
|
+
label: "MCP",
|
|
1249
|
+
hint: "Configure Prisma MCP server in agent MCP config files"
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
value: "extension",
|
|
1253
|
+
label: "IDE Extension",
|
|
1254
|
+
hint: "Install Prisma extension in selected IDEs (VS Code, Cursor, Windsurf)"
|
|
1255
|
+
}
|
|
1256
|
+
];
|
|
1257
|
+
const SKILLS_AGENT_OPTIONS = [
|
|
1258
|
+
{
|
|
1259
|
+
value: "cursor",
|
|
1260
|
+
label: "Cursor"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
value: "claude-code",
|
|
1264
|
+
label: "Claude Code"
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
value: "cline",
|
|
1268
|
+
label: "Cline"
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
value: "github-copilot",
|
|
1272
|
+
label: "GitHub Copilot"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
value: "codex",
|
|
1276
|
+
label: "Codex"
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
value: "opencode",
|
|
1280
|
+
label: "OpenCode"
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
value: "windsurf",
|
|
1284
|
+
label: "Windsurf"
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
value: "goose",
|
|
1288
|
+
label: "Goose"
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
value: "roo",
|
|
1292
|
+
label: "Roo Code"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
value: "kilo",
|
|
1296
|
+
label: "Kilo Code"
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
value: "gemini-cli",
|
|
1300
|
+
label: "Gemini CLI"
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
value: "antigravity",
|
|
1304
|
+
label: "Antigravity"
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
value: "openhands",
|
|
1308
|
+
label: "OpenHands"
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
value: "trae",
|
|
1312
|
+
label: "Trae"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
value: "amp",
|
|
1316
|
+
label: "Amp"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
value: "pi",
|
|
1320
|
+
label: "Pi"
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
value: "qoder",
|
|
1324
|
+
label: "Qoder"
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
value: "qwen-code",
|
|
1328
|
+
label: "Qwen Code"
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
value: "kiro-cli",
|
|
1332
|
+
label: "Kiro CLI"
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
value: "droid",
|
|
1336
|
+
label: "Droid"
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
value: "command-code",
|
|
1340
|
+
label: "Command Code"
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
value: "clawdbot",
|
|
1344
|
+
label: "Clawdbot"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
value: "zencoder",
|
|
1348
|
+
label: "Zencoder"
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
value: "neovate",
|
|
1352
|
+
label: "Neovate"
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
value: "mcpjam",
|
|
1356
|
+
label: "MCPJam"
|
|
1357
|
+
}
|
|
1358
|
+
];
|
|
1359
|
+
const MCP_AGENT_OPTIONS = [
|
|
1360
|
+
{
|
|
1361
|
+
value: "claude-code",
|
|
1362
|
+
label: "Claude Code"
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
value: "codex",
|
|
1366
|
+
label: "Codex"
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
value: "cursor",
|
|
1370
|
+
label: "Cursor"
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
value: "vscode",
|
|
1374
|
+
label: "VS Code"
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
value: "github-copilot-cli",
|
|
1378
|
+
label: "GitHub Copilot CLI"
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
value: "opencode",
|
|
1382
|
+
label: "OpenCode"
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
value: "gemini-cli",
|
|
1386
|
+
label: "Gemini CLI"
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
value: "goose",
|
|
1390
|
+
label: "Goose"
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
value: "zed",
|
|
1394
|
+
label: "Zed"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
value: "antigravity",
|
|
1398
|
+
label: "Antigravity"
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
value: "cline",
|
|
1402
|
+
label: "Cline VS Code Extension"
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
value: "cline-cli",
|
|
1406
|
+
label: "Cline CLI"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
value: "claude-desktop",
|
|
1410
|
+
label: "Claude Desktop"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
value: "mcporter",
|
|
1414
|
+
label: "MCPorter"
|
|
1415
|
+
}
|
|
1416
|
+
];
|
|
1417
|
+
const SHARED_PRISMA_SKILLS = [
|
|
1418
|
+
"prisma-cli",
|
|
1419
|
+
"prisma-client-api",
|
|
1420
|
+
"prisma-database-setup",
|
|
1421
|
+
"prisma-upgrade-v7"
|
|
1422
|
+
];
|
|
1423
|
+
function getAvailablePrismaSkills(provider) {
|
|
1424
|
+
const skills = [...SHARED_PRISMA_SKILLS, "prisma-compute"];
|
|
1425
|
+
if (provider === "postgresql") return [...skills, "prisma-postgres"];
|
|
1426
|
+
return skills;
|
|
1427
|
+
}
|
|
1428
|
+
function getSkillOptions(provider) {
|
|
1429
|
+
const available = getAvailablePrismaSkills(provider);
|
|
1430
|
+
const options = {
|
|
1431
|
+
"prisma-cli": {
|
|
1432
|
+
value: "prisma-cli",
|
|
1433
|
+
label: "prisma-cli",
|
|
1434
|
+
hint: "Prisma CLI reference"
|
|
1435
|
+
},
|
|
1436
|
+
"prisma-client-api": {
|
|
1437
|
+
value: "prisma-client-api",
|
|
1438
|
+
label: "prisma-client-api",
|
|
1439
|
+
hint: "Prisma Client query patterns"
|
|
1440
|
+
},
|
|
1441
|
+
"prisma-compute": {
|
|
1442
|
+
value: "prisma-compute",
|
|
1443
|
+
label: "prisma-compute",
|
|
1444
|
+
hint: "Prisma Compute deploy and hosting workflows"
|
|
1445
|
+
},
|
|
1446
|
+
"prisma-database-setup": {
|
|
1447
|
+
value: "prisma-database-setup",
|
|
1448
|
+
label: "prisma-database-setup",
|
|
1449
|
+
hint: "Database provider setup guides"
|
|
1450
|
+
},
|
|
1451
|
+
"prisma-upgrade-v7": {
|
|
1452
|
+
value: "prisma-upgrade-v7",
|
|
1453
|
+
label: "prisma-upgrade-v7",
|
|
1454
|
+
hint: "v6 to v7 migration guide"
|
|
1455
|
+
},
|
|
1456
|
+
"prisma-postgres": {
|
|
1457
|
+
value: "prisma-postgres",
|
|
1458
|
+
label: "prisma-postgres",
|
|
1459
|
+
hint: "Prisma Postgres workflows"
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
return available.map((skill) => options[skill]);
|
|
1463
|
+
}
|
|
1464
|
+
function collectAddonsFromInput(input) {
|
|
1465
|
+
const addons = [];
|
|
1466
|
+
if (input.skills === true) addons.push("skills");
|
|
1467
|
+
if (input.mcp === true) addons.push("mcp");
|
|
1468
|
+
if (input.extension === true) addons.push("extension");
|
|
1469
|
+
return uniqueValues(addons);
|
|
1470
|
+
}
|
|
1471
|
+
const EXTENSION_TARGET_OPTIONS = [
|
|
1472
|
+
{
|
|
1473
|
+
value: "vscode",
|
|
1474
|
+
label: "VS Code",
|
|
1475
|
+
hint: "Uses the `code` CLI"
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
value: "cursor",
|
|
1479
|
+
label: "Cursor",
|
|
1480
|
+
hint: "Uses the `cursor` CLI"
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
value: "windsurf",
|
|
1484
|
+
label: "Windsurf",
|
|
1485
|
+
hint: "Uses the `windsurf` CLI"
|
|
1486
|
+
}
|
|
1487
|
+
];
|
|
1488
|
+
function uniqueValues(values) {
|
|
1489
|
+
return Array.from(new Set(values));
|
|
1490
|
+
}
|
|
1491
|
+
function getRecommendedPrismaSkills(provider, shouldUsePrismaPostgres, shouldUseComputeDeploy) {
|
|
1492
|
+
const skills = [...SHARED_PRISMA_SKILLS];
|
|
1493
|
+
if (provider === "postgresql" && shouldUsePrismaPostgres) skills.push("prisma-postgres");
|
|
1494
|
+
if (shouldUseComputeDeploy) skills.push("prisma-compute");
|
|
1495
|
+
return uniqueValues(skills);
|
|
1496
|
+
}
|
|
1497
|
+
async function promptForAddons() {
|
|
1498
|
+
const selectedAddons = await multiselect({
|
|
1499
|
+
message: "Select add-ons (optional)",
|
|
1500
|
+
options: ADDON_OPTIONS,
|
|
1501
|
+
required: false
|
|
1502
|
+
});
|
|
1503
|
+
if (isCancel(selectedAddons)) {
|
|
1504
|
+
cancel("Operation cancelled.");
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
return uniqueValues(selectedAddons);
|
|
1508
|
+
}
|
|
1509
|
+
async function promptForAddonScope() {
|
|
1510
|
+
const selectedScope = await select({
|
|
1511
|
+
message: "Where should add-ons write config?",
|
|
1512
|
+
initialValue: DEFAULT_ADDON_SCOPE,
|
|
1513
|
+
options: [{
|
|
1514
|
+
value: "project",
|
|
1515
|
+
label: "Project",
|
|
1516
|
+
hint: "Recommended for teams (checked into the project when applicable)"
|
|
1517
|
+
}, {
|
|
1518
|
+
value: "global",
|
|
1519
|
+
label: "Global",
|
|
1520
|
+
hint: "Personal machine-level setup"
|
|
1521
|
+
}]
|
|
1522
|
+
});
|
|
1523
|
+
if (isCancel(selectedScope)) {
|
|
1524
|
+
cancel("Operation cancelled.");
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
return selectedScope;
|
|
1528
|
+
}
|
|
1529
|
+
async function promptForPrismaSkills(provider, recommendedSkills) {
|
|
1530
|
+
const options = getSkillOptions(provider);
|
|
1531
|
+
const optionValues = new Set(options.map((option) => option.value));
|
|
1532
|
+
const selectedSkills = await multiselect({
|
|
1533
|
+
message: "Select Prisma skills",
|
|
1534
|
+
options,
|
|
1535
|
+
required: false,
|
|
1536
|
+
initialValues: recommendedSkills.filter((skill) => optionValues.has(skill))
|
|
1537
|
+
});
|
|
1538
|
+
if (isCancel(selectedSkills)) {
|
|
1539
|
+
cancel("Operation cancelled.");
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
return uniqueValues(selectedSkills);
|
|
1543
|
+
}
|
|
1544
|
+
async function promptForSkillsAgents() {
|
|
1545
|
+
const selectedAgents = await multiselect({
|
|
1546
|
+
message: "Select agents for skills",
|
|
1547
|
+
options: SKILLS_AGENT_OPTIONS,
|
|
1548
|
+
required: false,
|
|
1549
|
+
initialValues: [...DEFAULT_SKILLS_AGENTS]
|
|
1550
|
+
});
|
|
1551
|
+
if (isCancel(selectedAgents)) {
|
|
1552
|
+
cancel("Operation cancelled.");
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
return uniqueValues(selectedAgents);
|
|
1556
|
+
}
|
|
1557
|
+
async function promptForMcpAgents() {
|
|
1558
|
+
const selectedAgents = await multiselect({
|
|
1559
|
+
message: "Select agents for MCP",
|
|
1560
|
+
options: MCP_AGENT_OPTIONS,
|
|
1561
|
+
required: false,
|
|
1562
|
+
initialValues: [...DEFAULT_MCP_AGENTS]
|
|
1563
|
+
});
|
|
1564
|
+
if (isCancel(selectedAgents)) {
|
|
1565
|
+
cancel("Operation cancelled.");
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
return uniqueValues(selectedAgents);
|
|
1569
|
+
}
|
|
1570
|
+
async function promptForExtensionTargets() {
|
|
1571
|
+
const selectedTargets = await multiselect({
|
|
1572
|
+
message: "Select IDEs for extension install",
|
|
1573
|
+
options: EXTENSION_TARGET_OPTIONS,
|
|
1574
|
+
required: false,
|
|
1575
|
+
initialValues: DEFAULT_EXTENSION_TARGETS
|
|
1576
|
+
});
|
|
1577
|
+
if (isCancel(selectedTargets)) {
|
|
1578
|
+
cancel("Operation cancelled.");
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
return uniqueValues(selectedTargets);
|
|
1582
|
+
}
|
|
1583
|
+
async function collectCreateAddonSetupContext(input, options) {
|
|
1584
|
+
const hasExplicitAddonSelection = input.skills !== void 0 || input.mcp !== void 0 || input.extension !== void 0;
|
|
1585
|
+
const selectedFromInput = collectAddonsFromInput(input);
|
|
1586
|
+
const selectedAddons = selectedFromInput.length > 0 ? selectedFromInput : hasExplicitAddonSelection ? [] : options.useDefaults ? [] : await promptForAddons();
|
|
1587
|
+
if (!selectedAddons) return;
|
|
1588
|
+
const addons = uniqueValues(selectedAddons);
|
|
1589
|
+
if (addons.length === 0) return null;
|
|
1590
|
+
const scope = addons.includes("skills") || addons.includes("mcp") ? options.useDefaults ? DEFAULT_ADDON_SCOPE : await promptForAddonScope() : DEFAULT_ADDON_SCOPE;
|
|
1591
|
+
if (!scope) return;
|
|
1592
|
+
const recommendedSkills = getRecommendedPrismaSkills(options.provider, options.shouldUsePrismaPostgres, options.shouldUseComputeDeploy);
|
|
1593
|
+
const skills = !addons.includes("skills") ? [] : options.useDefaults ? recommendedSkills : await promptForPrismaSkills(options.provider, recommendedSkills);
|
|
1594
|
+
if (!skills) return;
|
|
1595
|
+
const skillsAgents = !addons.includes("skills") ? [] : options.useDefaults ? [...DEFAULT_SKILLS_AGENTS] : await promptForSkillsAgents();
|
|
1596
|
+
if (!skillsAgents) return;
|
|
1597
|
+
const mcpAgents = !addons.includes("mcp") ? [] : options.useDefaults ? [...DEFAULT_MCP_AGENTS] : await promptForMcpAgents();
|
|
1598
|
+
if (!mcpAgents) return;
|
|
1599
|
+
const extensionTargets = !addons.includes("extension") ? [] : options.useDefaults ? [...DEFAULT_EXTENSION_TARGETS] : await promptForExtensionTargets();
|
|
1600
|
+
if (!extensionTargets) return;
|
|
1601
|
+
return {
|
|
1602
|
+
addons,
|
|
1603
|
+
scope,
|
|
1604
|
+
skills,
|
|
1605
|
+
skillsAgents: uniqueValues(skillsAgents),
|
|
1606
|
+
mcpAgents: uniqueValues(mcpAgents),
|
|
1607
|
+
extensionTargets: uniqueValues(extensionTargets)
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
async function executeExternalCommand(params) {
|
|
1611
|
+
await execa(params.command, params.args, {
|
|
1612
|
+
cwd: params.cwd,
|
|
1613
|
+
stdio: params.verbose ? "inherit" : "pipe",
|
|
1614
|
+
env: {
|
|
1615
|
+
...process.env,
|
|
1616
|
+
CI: "true"
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
async function installSkillsAddon(params) {
|
|
1621
|
+
if (params.agents.length === 0 || params.skills.length === 0) return "Skipped skills addon because no skills or agents were selected.";
|
|
1622
|
+
const scopeArgs = params.scope === "global" ? ["-g"] : [];
|
|
1623
|
+
const skillArgs = params.skills.flatMap((skill) => ["-s", skill]);
|
|
1624
|
+
const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
|
|
1625
|
+
const commandArgs = [
|
|
1626
|
+
"skills@latest",
|
|
1627
|
+
"add",
|
|
1628
|
+
"prisma/skills",
|
|
1629
|
+
...scopeArgs,
|
|
1630
|
+
...skillArgs,
|
|
1631
|
+
...agentArgs,
|
|
1632
|
+
"-y"
|
|
1633
|
+
];
|
|
1634
|
+
const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
|
|
1635
|
+
try {
|
|
1636
|
+
await executeExternalCommand({
|
|
1637
|
+
command: execution.command,
|
|
1638
|
+
args: execution.args,
|
|
1639
|
+
cwd: params.projectDir,
|
|
1640
|
+
verbose: params.verbose
|
|
1641
|
+
});
|
|
1642
|
+
return;
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
return `Skills addon failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function installMcpAddon(params) {
|
|
1648
|
+
if (params.agents.length === 0) return "Skipped MCP addon because no agents were selected.";
|
|
1649
|
+
const scopeArgs = params.scope === "global" ? ["-g"] : [];
|
|
1650
|
+
const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
|
|
1651
|
+
const commandArgs = [
|
|
1652
|
+
"add-mcp@latest",
|
|
1653
|
+
PRISMA_MCP_SERVER,
|
|
1654
|
+
...scopeArgs,
|
|
1655
|
+
...agentArgs,
|
|
1656
|
+
"--name",
|
|
1657
|
+
"prisma",
|
|
1658
|
+
"--gitignore",
|
|
1659
|
+
"-y"
|
|
1660
|
+
];
|
|
1661
|
+
const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
|
|
1662
|
+
try {
|
|
1663
|
+
await executeExternalCommand({
|
|
1664
|
+
command: execution.command,
|
|
1665
|
+
args: execution.args,
|
|
1666
|
+
cwd: params.projectDir,
|
|
1667
|
+
verbose: params.verbose
|
|
1668
|
+
});
|
|
1669
|
+
return;
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
return `MCP addon failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function getExtensionInstallBinary(target) {
|
|
1675
|
+
switch (target) {
|
|
1676
|
+
case "vscode": return "code";
|
|
1677
|
+
case "cursor": return "cursor";
|
|
1678
|
+
case "windsurf": return "windsurf";
|
|
1679
|
+
default: throw new Error(`Unsupported extension target: ${String(target)}`);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
async function installExtensionAddon(params) {
|
|
1683
|
+
if (params.targets.length === 0) return ["Skipped extension addon because no IDE targets were selected."];
|
|
1684
|
+
const warnings = [];
|
|
1685
|
+
for (const target of params.targets) {
|
|
1686
|
+
const binary = getExtensionInstallBinary(target);
|
|
1687
|
+
try {
|
|
1688
|
+
await executeExternalCommand({
|
|
1689
|
+
command: binary,
|
|
1690
|
+
args: ["--version"],
|
|
1691
|
+
cwd: params.projectDir,
|
|
1692
|
+
verbose: false
|
|
1693
|
+
});
|
|
1694
|
+
} catch {
|
|
1695
|
+
warnings.push(`Skipped ${target} extension install because the \`${binary}\` CLI is not available.`);
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
try {
|
|
1699
|
+
await executeExternalCommand({
|
|
1700
|
+
command: binary,
|
|
1701
|
+
args: [
|
|
1702
|
+
"--install-extension",
|
|
1703
|
+
"Prisma.prisma",
|
|
1704
|
+
"--force"
|
|
1705
|
+
],
|
|
1706
|
+
cwd: params.projectDir,
|
|
1707
|
+
verbose: params.verbose
|
|
1708
|
+
});
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
warnings.push(`${target} extension install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return warnings;
|
|
1714
|
+
}
|
|
1715
|
+
async function executeCreateAddonSetupContext(params) {
|
|
1716
|
+
const { context, packageManager, projectDir, verbose } = params;
|
|
1717
|
+
const addonSpinner = spinner();
|
|
1718
|
+
addonSpinner.start("Applying selected add-ons...");
|
|
1719
|
+
const warnings = [];
|
|
1720
|
+
if (context.addons.includes("skills")) {
|
|
1721
|
+
const warning = await installSkillsAddon({
|
|
1722
|
+
packageManager,
|
|
1723
|
+
projectDir,
|
|
1724
|
+
scope: context.scope,
|
|
1725
|
+
skills: context.skills,
|
|
1726
|
+
agents: context.skillsAgents,
|
|
1727
|
+
verbose
|
|
1728
|
+
});
|
|
1729
|
+
if (warning) warnings.push(warning);
|
|
1730
|
+
}
|
|
1731
|
+
if (context.addons.includes("mcp")) {
|
|
1732
|
+
const warning = await installMcpAddon({
|
|
1733
|
+
packageManager,
|
|
1734
|
+
projectDir,
|
|
1735
|
+
scope: context.scope,
|
|
1736
|
+
agents: context.mcpAgents,
|
|
1737
|
+
verbose
|
|
1738
|
+
});
|
|
1739
|
+
if (warning) warnings.push(warning);
|
|
1740
|
+
}
|
|
1741
|
+
if (context.addons.includes("extension")) {
|
|
1742
|
+
const extensionWarnings = await installExtensionAddon({
|
|
1743
|
+
projectDir,
|
|
1744
|
+
verbose,
|
|
1745
|
+
targets: context.extensionTargets
|
|
1746
|
+
});
|
|
1747
|
+
warnings.push(...extensionWarnings);
|
|
1748
|
+
}
|
|
1749
|
+
if (warnings.length > 0) {
|
|
1750
|
+
addonSpinner.stop("Add-ons applied with warnings.");
|
|
1751
|
+
for (const warning of warnings) log.warn(warning);
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
addonSpinner.stop("Add-ons applied.");
|
|
1755
|
+
}
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/tasks/deploy-to-compute.ts
|
|
1758
|
+
const PRISMA_CLI_PACKAGE = "@prisma/cli@latest";
|
|
1759
|
+
const DEPLOY_OPTIONS_BY_TEMPLATE = {
|
|
1760
|
+
hono: {},
|
|
1761
|
+
elysia: {},
|
|
1762
|
+
nest: {},
|
|
1763
|
+
next: {},
|
|
1764
|
+
astro: {},
|
|
1765
|
+
nuxt: {},
|
|
1766
|
+
"tanstack-start": {},
|
|
1767
|
+
turborepo: { configTarget: "api" }
|
|
1768
|
+
};
|
|
1769
|
+
function getPrismaCliCommand(packageManager) {
|
|
1770
|
+
return getPackageExecutionCommand(getPrismaCliExecutionPackageManager(packageManager), [PRISMA_CLI_PACKAGE]);
|
|
1771
|
+
}
|
|
1772
|
+
function getPrismaCliAppDeployCommand(packageManager) {
|
|
1773
|
+
return getPackageExecutionCommand(getPrismaCliExecutionPackageManager(packageManager), [
|
|
1774
|
+
PRISMA_CLI_PACKAGE,
|
|
1775
|
+
"app",
|
|
1776
|
+
"deploy"
|
|
1777
|
+
], { silent: true });
|
|
1778
|
+
}
|
|
1779
|
+
function getComputeDeployScriptMap(context) {
|
|
1780
|
+
const deployArgs = [
|
|
1781
|
+
...getComputeDeployTargetArgs(context),
|
|
1782
|
+
"--prod",
|
|
1783
|
+
"--yes"
|
|
1784
|
+
];
|
|
1785
|
+
return { "compute:deploy": [getPrismaCliAppDeployCommand(context.packageManager), ...deployArgs].join(" ") };
|
|
1786
|
+
}
|
|
1787
|
+
function getComputeDeployTargetArgs(context) {
|
|
1788
|
+
return context.configTarget ? [context.configTarget] : [];
|
|
1789
|
+
}
|
|
1790
|
+
function runPrismaCli(packageManager, args, options = {}) {
|
|
1791
|
+
const { silentPackageRunner, ...execaOptions } = options;
|
|
1792
|
+
const execution = getPackageExecutionArgs(getPrismaCliExecutionPackageManager(packageManager), [PRISMA_CLI_PACKAGE, ...args], { silent: silentPackageRunner });
|
|
1793
|
+
return execa(execution.command, execution.args, execaOptions);
|
|
1794
|
+
}
|
|
1795
|
+
function getPrismaCliExecutionPackageManager(packageManager) {
|
|
1796
|
+
return packageManager === "deno" ? "npm" : packageManager;
|
|
1797
|
+
}
|
|
1798
|
+
async function isAuthenticated(packageManager) {
|
|
1799
|
+
try {
|
|
1800
|
+
await runPrismaCli(packageManager, [
|
|
1801
|
+
"project",
|
|
1802
|
+
"list",
|
|
1803
|
+
"--json"
|
|
1804
|
+
], { stdio: "pipe" });
|
|
1805
|
+
return true;
|
|
1806
|
+
} catch {
|
|
1807
|
+
return false;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
async function ensurePrismaCliAvailable(packageManager) {
|
|
1811
|
+
try {
|
|
1812
|
+
await runPrismaCli(packageManager, ["--help"], { stdio: "pipe" });
|
|
1813
|
+
return true;
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
const command = getPrismaCliCommand(packageManager);
|
|
1816
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1817
|
+
log.warn(`Could not find the selected package manager. Re-run ${command} manually.`);
|
|
1818
|
+
return false;
|
|
1819
|
+
}
|
|
1820
|
+
log.warn(`Could not run ${command}${error instanceof Error ? `: ${redactSecrets(error.message)}` : "."}`);
|
|
1821
|
+
return false;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
async function collectComputeDeployContext(input, options) {
|
|
1825
|
+
if (!isComputeDeployableTemplate(options.template)) return null;
|
|
1826
|
+
if (input.deploy === false) return null;
|
|
1827
|
+
let wantsDeploy;
|
|
1828
|
+
if (input.deploy === true) wantsDeploy = true;
|
|
1829
|
+
else if (options.useDefaults) return null;
|
|
1830
|
+
else {
|
|
1831
|
+
const confirmed = await confirm({
|
|
1832
|
+
message: "Deploy to Prisma Compute now?",
|
|
1833
|
+
initialValue: false
|
|
1834
|
+
});
|
|
1835
|
+
if (isCancel(confirmed)) {
|
|
1836
|
+
cancel("Operation cancelled.");
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
wantsDeploy = confirmed;
|
|
1840
|
+
}
|
|
1841
|
+
if (!wantsDeploy) return null;
|
|
1842
|
+
if (!await ensurePrismaCliAvailable(options.packageManager)) {
|
|
1843
|
+
if (input.deploy === true) throw createExplicitDeployError("the Prisma CLI is not available");
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
if (!await isAuthenticated(options.packageManager)) {
|
|
1847
|
+
log.info("Authenticating with Prisma...");
|
|
1848
|
+
try {
|
|
1849
|
+
await runPrismaCli(options.packageManager, ["auth", "login"], { stdio: "inherit" });
|
|
1850
|
+
} catch (error) {
|
|
1851
|
+
log.warn(`Prisma login was not completed${error instanceof Error ? `: ${redactSecrets(error.message)}` : "."}`);
|
|
1852
|
+
if (input.deploy === true) throw createExplicitDeployError("authentication failed", error);
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const deployOptions = DEPLOY_OPTIONS_BY_TEMPLATE[options.template];
|
|
1857
|
+
if (!deployOptions) {
|
|
1858
|
+
if (input.deploy === true) throw createExplicitDeployError(`${options.template} is not supported by prisma app deploy yet`);
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
template: options.template,
|
|
1863
|
+
packageManager: options.packageManager,
|
|
1864
|
+
createProjectName: options.defaultServiceName,
|
|
1865
|
+
configTarget: deployOptions.configTarget
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function redactSecrets(message) {
|
|
1869
|
+
return message.replace(/(['"])([A-Z0-9_]*(?:DATABASE_URL|DIRECT_URL|TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|ACCESS_KEY)[A-Z0-9_]*=)(.*?)\1/g, "$1$2<redacted>$1").replace(/\b([A-Z0-9_]*(?:DATABASE_URL|DIRECT_URL|TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|ACCESS_KEY)[A-Z0-9_]*=)[^\s]+/g, "$1<redacted>");
|
|
1870
|
+
}
|
|
1871
|
+
function parseJson(stdout) {
|
|
1872
|
+
if (typeof stdout !== "string" || stdout.trim().length === 0) return null;
|
|
1873
|
+
try {
|
|
1874
|
+
return JSON.parse(stdout);
|
|
1875
|
+
} catch {
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function getJsonErrorMessage(error, fallback) {
|
|
1880
|
+
return error?.summary ?? error?.message ?? error?.name ?? fallback;
|
|
1881
|
+
}
|
|
1882
|
+
function getErrorMessage(error) {
|
|
1883
|
+
return redactSecrets(error instanceof Error ? error.message : String(error));
|
|
1884
|
+
}
|
|
1885
|
+
function createDeployError(message) {
|
|
1886
|
+
return new Error(redactSecrets(message ?? "unknown error"));
|
|
1887
|
+
}
|
|
1888
|
+
function createExplicitDeployError(reason, error) {
|
|
1889
|
+
const detail = error instanceof Error ? `: ${redactSecrets(error.message)}` : "";
|
|
1890
|
+
return /* @__PURE__ */ new Error(`Deploy requested but ${reason}${detail}`);
|
|
1891
|
+
}
|
|
1892
|
+
async function runPrismaCliJson(params) {
|
|
1893
|
+
try {
|
|
1894
|
+
const { stdout, exitCode } = await runPrismaCli(params.packageManager, params.args, {
|
|
1895
|
+
cwd: params.cwd,
|
|
1896
|
+
reject: false,
|
|
1897
|
+
stdio: [
|
|
1898
|
+
"ignore",
|
|
1899
|
+
"pipe",
|
|
1900
|
+
"pipe"
|
|
1901
|
+
],
|
|
1902
|
+
silentPackageRunner: true
|
|
1903
|
+
});
|
|
1904
|
+
const parsed = parseJson(stdout);
|
|
1905
|
+
if (!parsed) return {
|
|
1906
|
+
ok: false,
|
|
1907
|
+
error: new Error(params.invalidOutputError)
|
|
1908
|
+
};
|
|
1909
|
+
if (exitCode !== 0 || !parsed.ok) return {
|
|
1910
|
+
ok: false,
|
|
1911
|
+
error: createDeployError(parsed.ok ? params.fallbackError : getJsonErrorMessage(parsed.error, params.fallbackError))
|
|
1912
|
+
};
|
|
1913
|
+
return {
|
|
1914
|
+
ok: true,
|
|
1915
|
+
result: parsed.result
|
|
1916
|
+
};
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
return {
|
|
1919
|
+
ok: false,
|
|
1920
|
+
error: new Error(getErrorMessage(error))
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function toComputeDeployResult(data) {
|
|
1925
|
+
return {
|
|
1926
|
+
appUrl: data.deployment.url,
|
|
1927
|
+
appId: data.app.id,
|
|
1928
|
+
appName: data.app.name,
|
|
1929
|
+
deploymentId: data.deployment.id,
|
|
1930
|
+
projectId: data.project.id,
|
|
1931
|
+
projectName: data.project.name,
|
|
1932
|
+
branchName: data.branch.name,
|
|
1933
|
+
database: data.branchDatabase?.database
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
async function executeComputeDatabaseSetup(params) {
|
|
1937
|
+
const projectSpinner = spinner();
|
|
1938
|
+
projectSpinner.start("Creating Prisma Compute project...");
|
|
1939
|
+
const projectResult = await runPrismaCliJson({
|
|
1940
|
+
packageManager: params.context.packageManager,
|
|
1941
|
+
args: [
|
|
1942
|
+
"project",
|
|
1943
|
+
"create",
|
|
1944
|
+
params.context.createProjectName,
|
|
1945
|
+
"--json"
|
|
1946
|
+
],
|
|
1947
|
+
cwd: params.projectDir,
|
|
1948
|
+
fallbackError: "Prisma project create failed.",
|
|
1949
|
+
invalidOutputError: "Invalid prisma project create output"
|
|
1950
|
+
});
|
|
1951
|
+
if (!projectResult.ok) {
|
|
1952
|
+
projectSpinner.error(`Project creation failed: ${projectResult.error.message}`);
|
|
1953
|
+
return {
|
|
1954
|
+
ok: false,
|
|
1955
|
+
cancelled: false,
|
|
1956
|
+
error: projectResult.error
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
projectSpinner.stop("Prisma Compute project created.");
|
|
1960
|
+
const databaseSpinner = spinner();
|
|
1961
|
+
databaseSpinner.start("Creating Prisma Postgres database...");
|
|
1962
|
+
const databaseResult = await runPrismaCliJson({
|
|
1963
|
+
packageManager: params.context.packageManager,
|
|
1964
|
+
args: [
|
|
1965
|
+
"database",
|
|
1966
|
+
"create",
|
|
1967
|
+
"main",
|
|
1968
|
+
"--branch",
|
|
1969
|
+
"main",
|
|
1970
|
+
"--json"
|
|
1971
|
+
],
|
|
1972
|
+
cwd: params.projectDir,
|
|
1973
|
+
fallbackError: "Prisma database create failed.",
|
|
1974
|
+
invalidOutputError: "Invalid prisma database create output"
|
|
1975
|
+
});
|
|
1976
|
+
if (!databaseResult.ok) {
|
|
1977
|
+
databaseSpinner.error(`Database creation failed: ${databaseResult.error.message}`);
|
|
1978
|
+
return {
|
|
1979
|
+
ok: false,
|
|
1980
|
+
cancelled: false,
|
|
1981
|
+
error: databaseResult.error
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
databaseSpinner.stop("Prisma Postgres database created.");
|
|
1985
|
+
return {
|
|
1986
|
+
ok: true,
|
|
1987
|
+
result: {
|
|
1988
|
+
databaseUrl: databaseResult.result.connectionString,
|
|
1989
|
+
projectId: databaseResult.result.projectId,
|
|
1990
|
+
projectName: databaseResult.result.projectName,
|
|
1991
|
+
database: databaseResult.result.database
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
async function executeComputeDeployContext(params) {
|
|
1996
|
+
const deploySpinner = spinner();
|
|
1997
|
+
deploySpinner.start("Deploying to Prisma Compute...");
|
|
1998
|
+
const args = [
|
|
1999
|
+
"app",
|
|
2000
|
+
"deploy",
|
|
2001
|
+
...getComputeDeployTargetArgs(params.context),
|
|
2002
|
+
"--json",
|
|
2003
|
+
"--yes",
|
|
2004
|
+
"--prod",
|
|
2005
|
+
...params.createProject === false ? [] : ["--create-project", params.context.createProjectName]
|
|
2006
|
+
];
|
|
2007
|
+
const deployResult = await runPrismaCliJson({
|
|
2008
|
+
packageManager: params.context.packageManager,
|
|
2009
|
+
args,
|
|
2010
|
+
cwd: params.projectDir,
|
|
2011
|
+
fallbackError: "Prisma app deploy failed.",
|
|
2012
|
+
invalidOutputError: "Invalid prisma app deploy output"
|
|
2013
|
+
});
|
|
2014
|
+
if (!deployResult.ok) {
|
|
2015
|
+
deploySpinner.error(`Deploy failed: ${deployResult.error.message}`);
|
|
2016
|
+
return {
|
|
2017
|
+
ok: false,
|
|
2018
|
+
cancelled: false,
|
|
2019
|
+
error: deployResult.error
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
deploySpinner.stop("Deployed to Prisma Compute.");
|
|
2023
|
+
return {
|
|
2024
|
+
ok: true,
|
|
2025
|
+
result: toComputeDeployResult(deployResult.result)
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
//#endregion
|
|
2029
|
+
//#region src/telemetry/client.ts
|
|
2030
|
+
const TELEMETRY_API_KEY = "phc_cmc85avbWyuJ2JyKdGPdv7dxXli8xLdWDBPbvIXWJfs";
|
|
2031
|
+
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
2032
|
+
const TELEMETRY_CONFIG_FILE = "telemetry.json";
|
|
2033
|
+
const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2034
|
+
function isTruthyEnvValue(value) {
|
|
2035
|
+
return [
|
|
2036
|
+
"1",
|
|
2037
|
+
"true",
|
|
2038
|
+
"yes",
|
|
2039
|
+
"on"
|
|
2040
|
+
].includes(String(value ?? "").trim().toLowerCase());
|
|
2041
|
+
}
|
|
2042
|
+
function shouldDisableTelemetry() {
|
|
2043
|
+
if (isTruthyEnvValue(process.env.CI) || isTruthyEnvValue(process.env.GITHUB_ACTIONS)) return true;
|
|
2044
|
+
return process.env.CREATE_PRISMA_DISABLE_TELEMETRY !== void 0 || process.env.CREATE_PRISMA_TELEMETRY_DISABLED !== void 0 || process.env.DO_NOT_TRACK !== void 0;
|
|
2045
|
+
}
|
|
2046
|
+
function getTelemetryConfigDir() {
|
|
2047
|
+
if (process.platform === "darwin") return path.join(os$1.homedir(), "Library", "Application Support", "create-prisma");
|
|
2048
|
+
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os$1.homedir(), "AppData", "Roaming"), "create-prisma");
|
|
2049
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os$1.homedir(), ".config"), "create-prisma");
|
|
2050
|
+
}
|
|
2051
|
+
async function getAnonymousId() {
|
|
2052
|
+
const telemetryConfigPath = path.join(getTelemetryConfigDir(), TELEMETRY_CONFIG_FILE);
|
|
2053
|
+
try {
|
|
2054
|
+
const config = await fs.readJSON(telemetryConfigPath);
|
|
2055
|
+
if (typeof config.anonymousId === "string" && UUID_V4_REGEX.test(config.anonymousId)) return config.anonymousId;
|
|
2056
|
+
} catch {}
|
|
2057
|
+
const anonymousId = randomUUID();
|
|
2058
|
+
try {
|
|
2059
|
+
await fs.ensureDir(path.dirname(telemetryConfigPath));
|
|
2060
|
+
await fs.writeJSON(telemetryConfigPath, { anonymousId }, { spaces: 2 });
|
|
2061
|
+
} catch {}
|
|
2062
|
+
return anonymousId;
|
|
2063
|
+
}
|
|
2064
|
+
function getCommonProperties() {
|
|
2065
|
+
return {
|
|
2066
|
+
"cli-version": "0.7.2",
|
|
2067
|
+
"node-version": process.version,
|
|
2068
|
+
platform: process.platform,
|
|
2069
|
+
arch: process.arch
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function sanitizeProperties(properties) {
|
|
2073
|
+
return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0));
|
|
2074
|
+
}
|
|
2075
|
+
async function trackCliTelemetry(event, properties) {
|
|
2076
|
+
if (shouldDisableTelemetry()) return;
|
|
2077
|
+
let client;
|
|
2078
|
+
try {
|
|
2079
|
+
const distinctId = await getAnonymousId();
|
|
2080
|
+
const sanitizedProperties = sanitizeProperties({
|
|
2081
|
+
...getCommonProperties(),
|
|
2082
|
+
...properties,
|
|
2083
|
+
$process_person_profile: false
|
|
2084
|
+
});
|
|
2085
|
+
client = new PostHog(TELEMETRY_API_KEY, {
|
|
2086
|
+
host: TELEMETRY_HOST,
|
|
2087
|
+
disableGeoip: true,
|
|
2088
|
+
flushAt: 1,
|
|
2089
|
+
flushInterval: 0
|
|
2090
|
+
});
|
|
2091
|
+
await client.captureImmediate({
|
|
2092
|
+
distinctId,
|
|
2093
|
+
event,
|
|
2094
|
+
properties: sanitizedProperties,
|
|
2095
|
+
disableGeoip: true
|
|
2096
|
+
});
|
|
2097
|
+
} catch {} finally {
|
|
2098
|
+
if (client) await client.shutdown().catch(() => {});
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
//#endregion
|
|
2102
|
+
//#region src/telemetry/create.ts
|
|
2103
|
+
function getRequestedAddons(input) {
|
|
2104
|
+
const addons = [];
|
|
2105
|
+
if (input.skills === true) addons.push("skills");
|
|
2106
|
+
if (input.mcp === true) addons.push("mcp");
|
|
2107
|
+
if (input.extension === true) addons.push("extension");
|
|
2108
|
+
return addons;
|
|
2109
|
+
}
|
|
2110
|
+
function getTargetDirectoryState(context) {
|
|
2111
|
+
if (!context.targetPathState.exists) return "new";
|
|
2112
|
+
if (context.targetPathState.isEmptyDirectory) return "empty_directory";
|
|
2113
|
+
return "non_empty_directory";
|
|
2114
|
+
}
|
|
2115
|
+
function getBaseCreateProperties(input, context) {
|
|
2116
|
+
const resolvedAddons = context?.addonSetupContext?.addons ?? getRequestedAddons(input);
|
|
2117
|
+
return {
|
|
2118
|
+
command: "create",
|
|
2119
|
+
"uses-defaults": input.yes === true,
|
|
2120
|
+
verbose: input.verbose === true,
|
|
2121
|
+
force: input.force === true,
|
|
2122
|
+
template: context?.template ?? input.template ?? null,
|
|
2123
|
+
"database-provider": context?.prismaSetupContext.databaseProvider ?? input.provider ?? null,
|
|
2124
|
+
"package-manager": context?.prismaSetupContext.packageManager ?? input.packageManager ?? null,
|
|
2125
|
+
"schema-preset": context?.prismaSetupContext.schemaPreset ?? input.schemaPreset ?? null,
|
|
2126
|
+
"should-install": context?.prismaSetupContext.shouldInstall ?? input.install ?? null,
|
|
2127
|
+
"should-generate": context?.prismaSetupContext.shouldGenerate ?? input.generate ?? null,
|
|
2128
|
+
"uses-prisma-postgres": context?.prismaSetupContext.shouldUsePrismaPostgres ?? input.prismaPostgres ?? null,
|
|
2129
|
+
addons: resolvedAddons,
|
|
2130
|
+
"addon-count": resolvedAddons.length,
|
|
2131
|
+
"addon-scope": context?.addonSetupContext?.scope ?? null,
|
|
2132
|
+
"skills-count": context?.addonSetupContext?.skills.length ?? null,
|
|
2133
|
+
"skills-agents-count": context?.addonSetupContext?.skillsAgents.length ?? null,
|
|
2134
|
+
"mcp-agents-count": context?.addonSetupContext?.mcpAgents.length ?? null,
|
|
2135
|
+
"extension-target-count": context?.addonSetupContext?.extensionTargets.length ?? null,
|
|
2136
|
+
"target-directory-state": context ? getTargetDirectoryState(context) : null
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function getErrorName(error) {
|
|
2140
|
+
if (error instanceof Error) return error.name;
|
|
2141
|
+
return error === void 0 ? null : "UnknownError";
|
|
2142
|
+
}
|
|
2143
|
+
function getErrorCode(error) {
|
|
2144
|
+
if (typeof error !== "object" || error === null) return null;
|
|
2145
|
+
const exitCode = Reflect.get(error, "exitCode");
|
|
2146
|
+
if (typeof exitCode === "number") return exitCode;
|
|
2147
|
+
const code = Reflect.get(error, "code");
|
|
2148
|
+
return typeof code === "number" || typeof code === "string" ? code : null;
|
|
2149
|
+
}
|
|
2150
|
+
async function trackCreateCompleted(params) {
|
|
2151
|
+
await trackCliTelemetry("cli:create_command_completed", {
|
|
2152
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
2153
|
+
"duration-ms": params.durationMs
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
async function trackCreateFailed(params) {
|
|
2157
|
+
await trackCliTelemetry("cli:create_command_failed", {
|
|
2158
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
2159
|
+
"duration-ms": params.durationMs,
|
|
2160
|
+
"failure-stage": params.stage,
|
|
2161
|
+
"error-name": getErrorName(params.error),
|
|
2162
|
+
"error-code": getErrorCode(params.error)
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
//#endregion
|
|
2166
|
+
//#region src/ui/branding.ts
|
|
2167
|
+
const prismaTitle = `${styleText(["bold", "cyan"], "Create")} ${styleText(["bold", "magenta"], "Prisma")}`;
|
|
2168
|
+
function getCreatePrismaIntro() {
|
|
2169
|
+
return prismaTitle;
|
|
2170
|
+
}
|
|
2171
|
+
//#endregion
|
|
2172
|
+
//#region src/commands/create.ts
|
|
2173
|
+
const DEFAULT_PROJECT_NAME = "my-app";
|
|
2174
|
+
const DEFAULT_TEMPLATE = "hono";
|
|
2175
|
+
const DEFAULT_SCHEMA_PRESET = "basic";
|
|
2176
|
+
function toPackageName(projectName) {
|
|
2177
|
+
return projectName.toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+/, "").replace(/-+$/, "") || "app";
|
|
2178
|
+
}
|
|
2179
|
+
function formatPathForDisplay(filePath) {
|
|
2180
|
+
return path.relative(process.cwd(), filePath) || ".";
|
|
2181
|
+
}
|
|
2182
|
+
function validateProjectName(value) {
|
|
2183
|
+
const trimmed = String(value ?? "").trim();
|
|
2184
|
+
if (trimmed.length === 0) return "Please enter a project name.";
|
|
2185
|
+
if (trimmed === "..") return "Project name cannot be '..'.";
|
|
2186
|
+
if (path.isAbsolute(trimmed)) return "Use a relative project name instead of an absolute path.";
|
|
2187
|
+
}
|
|
2188
|
+
async function promptForProjectName() {
|
|
2189
|
+
const projectName = await text({
|
|
2190
|
+
message: "Project name",
|
|
2191
|
+
placeholder: DEFAULT_PROJECT_NAME,
|
|
2192
|
+
initialValue: DEFAULT_PROJECT_NAME,
|
|
2193
|
+
validate: validateProjectName
|
|
2194
|
+
});
|
|
2195
|
+
if (isCancel(projectName)) {
|
|
2196
|
+
cancel("Operation cancelled.");
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
return String(projectName).trim();
|
|
2200
|
+
}
|
|
2201
|
+
async function promptForCreateTemplate() {
|
|
2202
|
+
const template = await select({
|
|
2203
|
+
message: "Select template",
|
|
2204
|
+
initialValue: DEFAULT_TEMPLATE,
|
|
2205
|
+
options: [
|
|
2206
|
+
{
|
|
2207
|
+
value: "hono",
|
|
2208
|
+
label: "Hono",
|
|
2209
|
+
hint: "TypeScript API starter"
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
value: "elysia",
|
|
2213
|
+
label: "Elysia",
|
|
2214
|
+
hint: "TypeScript API starter with Elysia's Node adapter"
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
value: "nest",
|
|
2218
|
+
label: "NestJS",
|
|
2219
|
+
hint: "Official Nest-style API starter with a Prisma service"
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
value: "next",
|
|
2223
|
+
label: "Next.js",
|
|
2224
|
+
hint: "App Router + TypeScript starter"
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
value: "svelte",
|
|
2228
|
+
label: "SvelteKit",
|
|
2229
|
+
hint: "Official minimal SvelteKit + TypeScript starter"
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
value: "astro",
|
|
2233
|
+
label: "Astro",
|
|
2234
|
+
hint: "Official minimal Astro starter with API route example"
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
value: "nuxt",
|
|
2238
|
+
label: "Nuxt",
|
|
2239
|
+
hint: "Official minimal Nuxt starter with Nitro API route example"
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
value: "tanstack-start",
|
|
2243
|
+
label: "TanStack Start",
|
|
2244
|
+
hint: "TanStack Start React app with file routes and server functions"
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
value: "turborepo",
|
|
2248
|
+
label: "Turborepo",
|
|
2249
|
+
hint: "Monorepo starter with apps + packages/db Prisma package"
|
|
2250
|
+
}
|
|
2251
|
+
]
|
|
2252
|
+
});
|
|
2253
|
+
if (isCancel(template)) {
|
|
2254
|
+
cancel("Operation cancelled.");
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
return CreateTemplateSchema.parse(template);
|
|
2258
|
+
}
|
|
2259
|
+
async function inspectTargetPath(targetPath) {
|
|
2260
|
+
if (!await fs.pathExists(targetPath)) return {
|
|
2261
|
+
exists: false,
|
|
2262
|
+
isDirectory: true,
|
|
2263
|
+
isEmptyDirectory: true
|
|
2264
|
+
};
|
|
2265
|
+
if (!(await fs.stat(targetPath)).isDirectory()) return {
|
|
2266
|
+
exists: true,
|
|
2267
|
+
isDirectory: false,
|
|
2268
|
+
isEmptyDirectory: false
|
|
2269
|
+
};
|
|
2270
|
+
return {
|
|
2271
|
+
exists: true,
|
|
2272
|
+
isDirectory: true,
|
|
2273
|
+
isEmptyDirectory: (await fs.readdir(targetPath)).length === 0
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
async function runCreateCommand(rawInput = {}) {
|
|
2277
|
+
const startedAt = Date.now();
|
|
2278
|
+
let input = {};
|
|
2279
|
+
let context;
|
|
2280
|
+
let failureStage = "validate_input";
|
|
2281
|
+
try {
|
|
2282
|
+
input = CreateCommandInputSchema.parse(rawInput);
|
|
2283
|
+
intro(getCreatePrismaIntro());
|
|
2284
|
+
failureStage = "collect_context";
|
|
2285
|
+
context = await collectCreateContext(input);
|
|
2286
|
+
if (!context) return;
|
|
2287
|
+
failureStage = "unknown";
|
|
2288
|
+
const executionResult = await executeCreateContext(context);
|
|
2289
|
+
if (!executionResult.ok) {
|
|
2290
|
+
if (executionResult.error) cancel(`Create command failed: ${executionResult.error instanceof Error ? executionResult.error.message : String(executionResult.error)}`);
|
|
2291
|
+
await trackCreateFailed({
|
|
2292
|
+
input,
|
|
2293
|
+
context,
|
|
2294
|
+
durationMs: Date.now() - startedAt,
|
|
2295
|
+
error: executionResult.error,
|
|
2296
|
+
stage: executionResult.stage
|
|
2297
|
+
});
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
await trackCreateCompleted({
|
|
2301
|
+
input,
|
|
2302
|
+
context,
|
|
2303
|
+
durationMs: Date.now() - startedAt
|
|
2304
|
+
});
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
cancel(`Create command failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2307
|
+
await trackCreateFailed({
|
|
2308
|
+
input,
|
|
2309
|
+
context,
|
|
2310
|
+
durationMs: Date.now() - startedAt,
|
|
2311
|
+
error,
|
|
2312
|
+
stage: failureStage
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
async function collectCreateContext(input) {
|
|
2317
|
+
const useDefaults = input.yes === true;
|
|
2318
|
+
const force = input.force === true;
|
|
2319
|
+
const projectNameInput = input.name ?? (useDefaults ? DEFAULT_PROJECT_NAME : await promptForProjectName());
|
|
2320
|
+
if (projectNameInput === void 0) return;
|
|
2321
|
+
const projectName = String(projectNameInput).trim();
|
|
2322
|
+
const projectNameValidationError = validateProjectName(projectName);
|
|
2323
|
+
if (projectNameValidationError) {
|
|
2324
|
+
cancel(projectNameValidationError);
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
const template = input.template ?? (useDefaults ? DEFAULT_TEMPLATE : await promptForCreateTemplate());
|
|
2328
|
+
if (!template) return;
|
|
2329
|
+
const targetDirectory = path.resolve(process.cwd(), projectName);
|
|
2330
|
+
const targetPathState = await inspectTargetPath(targetDirectory);
|
|
2331
|
+
if (targetPathState.exists && !targetPathState.isDirectory) {
|
|
2332
|
+
cancel(`Target path ${formatPathForDisplay(targetDirectory)} already exists and is not a directory. Choose a different project name.`);
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
if (targetPathState.exists && !targetPathState.isEmptyDirectory && !force) {
|
|
2336
|
+
cancel(`Target directory ${formatPathForDisplay(targetDirectory)} is not empty. Use --force to continue.`);
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
const prismaSetupInitialContext = await collectPrismaSetupInitialContext(input, {
|
|
2340
|
+
projectDir: targetDirectory,
|
|
2341
|
+
defaultSchemaPreset: DEFAULT_SCHEMA_PRESET
|
|
2342
|
+
});
|
|
2343
|
+
if (!prismaSetupInitialContext) return;
|
|
2344
|
+
const projectPackageName = toPackageName(path.basename(targetDirectory));
|
|
2345
|
+
const computeDeployContext = await collectComputeDeployContext(input, {
|
|
2346
|
+
template,
|
|
2347
|
+
packageManager: prismaSetupInitialContext.packageManager,
|
|
2348
|
+
useDefaults,
|
|
2349
|
+
defaultServiceName: projectPackageName
|
|
2350
|
+
});
|
|
2351
|
+
if (computeDeployContext === void 0) return;
|
|
2352
|
+
const prismaSetupContext = await completePrismaSetupContext(input, prismaSetupInitialContext);
|
|
2353
|
+
if (!prismaSetupContext) return;
|
|
2354
|
+
const useComputeDatabase = Boolean(computeDeployContext && prismaSetupContext.shouldUsePrismaPostgres);
|
|
2355
|
+
if (useComputeDatabase) log.info("Prisma Postgres selected: create-prisma will provision a database and write DATABASE_URL to the template env file before deploying.");
|
|
2356
|
+
const addonSetupContext = await collectCreateAddonSetupContext(input, {
|
|
2357
|
+
useDefaults,
|
|
2358
|
+
provider: prismaSetupContext.databaseProvider,
|
|
2359
|
+
shouldUsePrismaPostgres: prismaSetupContext.shouldUsePrismaPostgres,
|
|
2360
|
+
shouldUseComputeDeploy: Boolean(computeDeployContext)
|
|
2361
|
+
});
|
|
2362
|
+
if (addonSetupContext === void 0) return;
|
|
2363
|
+
return {
|
|
2364
|
+
targetDirectory,
|
|
2365
|
+
targetPathState,
|
|
2366
|
+
force,
|
|
2367
|
+
template,
|
|
2368
|
+
projectPackageName,
|
|
2369
|
+
prismaSetupContext,
|
|
2370
|
+
addonSetupContext: addonSetupContext ?? void 0,
|
|
2371
|
+
computeDeployContext: computeDeployContext ?? void 0,
|
|
2372
|
+
useComputeDatabase
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
async function executeCreateContext(context) {
|
|
2376
|
+
const scaffoldSpinner = spinner();
|
|
2377
|
+
scaffoldSpinner.start(`Scaffolding ${context.template} project...`);
|
|
2378
|
+
try {
|
|
2379
|
+
await scaffoldCreateTemplate({
|
|
2380
|
+
projectDir: context.targetDirectory,
|
|
2381
|
+
projectName: context.projectPackageName,
|
|
2382
|
+
template: context.template,
|
|
2383
|
+
schemaPreset: context.prismaSetupContext.schemaPreset,
|
|
2384
|
+
provider: context.prismaSetupContext.databaseProvider,
|
|
2385
|
+
packageManager: context.prismaSetupContext.packageManager,
|
|
2386
|
+
compute: Boolean(context.computeDeployContext)
|
|
2387
|
+
});
|
|
2388
|
+
scaffoldSpinner.stop("Project files scaffolded.");
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
scaffoldSpinner.stop("Could not scaffold project files.");
|
|
2391
|
+
return {
|
|
2392
|
+
ok: false,
|
|
2393
|
+
stage: "scaffold_template",
|
|
2394
|
+
error
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
try {
|
|
2398
|
+
await writeCreateTemplateDependencies({
|
|
2399
|
+
template: context.template,
|
|
2400
|
+
packageManager: context.prismaSetupContext.packageManager,
|
|
2401
|
+
projectDir: context.targetDirectory
|
|
2402
|
+
});
|
|
2403
|
+
if (context.computeDeployContext) await addPackageDependency({
|
|
2404
|
+
scripts: getComputeDeployScriptMap(context.computeDeployContext),
|
|
2405
|
+
scriptMode: "if-missing",
|
|
2406
|
+
projectDir: context.targetDirectory
|
|
2407
|
+
});
|
|
2408
|
+
} catch (error) {
|
|
2409
|
+
return {
|
|
2410
|
+
ok: false,
|
|
2411
|
+
stage: "scaffold_template",
|
|
2412
|
+
error
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
if (context.targetPathState.exists && !context.targetPathState.isEmptyDirectory && context.force) log.warn(`Used --force in non-empty directory ${formatPathForDisplay(context.targetDirectory)}.`);
|
|
2416
|
+
const nextSteps = formatPathForDisplay(context.targetDirectory) === "." ? [] : [`- cd ${formatPathForDisplay(context.targetDirectory)}`];
|
|
2417
|
+
if (context.addonSetupContext) try {
|
|
2418
|
+
await executeCreateAddonSetupContext({
|
|
2419
|
+
context: context.addonSetupContext,
|
|
2420
|
+
packageManager: context.prismaSetupContext.packageManager,
|
|
2421
|
+
projectDir: context.targetDirectory,
|
|
2422
|
+
verbose: context.prismaSetupContext.verbose
|
|
2423
|
+
});
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
return {
|
|
2426
|
+
ok: false,
|
|
2427
|
+
stage: "addons",
|
|
2428
|
+
error
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
let computeDatabaseResult;
|
|
2432
|
+
if (context.useComputeDatabase && context.computeDeployContext) try {
|
|
2433
|
+
const result = await executeComputeDatabaseSetup({
|
|
2434
|
+
context: context.computeDeployContext,
|
|
2435
|
+
projectDir: context.targetDirectory
|
|
2436
|
+
});
|
|
2437
|
+
if (!result.ok && !result.cancelled) return {
|
|
2438
|
+
ok: false,
|
|
2439
|
+
stage: "compute_deploy",
|
|
2440
|
+
error: result.error
|
|
2441
|
+
};
|
|
2442
|
+
if (result.ok) computeDatabaseResult = result.result;
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
return {
|
|
2445
|
+
ok: false,
|
|
2446
|
+
stage: "compute_deploy",
|
|
2447
|
+
error
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
const prismaSetupContext = computeDatabaseResult ? {
|
|
2451
|
+
...context.prismaSetupContext,
|
|
2452
|
+
databaseUrl: computeDatabaseResult.databaseUrl,
|
|
2453
|
+
shouldUsePrismaPostgres: false
|
|
2454
|
+
} : context.prismaSetupContext;
|
|
2455
|
+
let prismaResult;
|
|
2456
|
+
try {
|
|
2457
|
+
prismaResult = await executePrismaSetupContext(prismaSetupContext, {
|
|
2458
|
+
prependNextSteps: nextSteps,
|
|
2459
|
+
projectDir: context.targetDirectory,
|
|
2460
|
+
includeDevNextStep: !context.useComputeDatabase
|
|
2461
|
+
});
|
|
2462
|
+
if (!prismaResult.ok) return {
|
|
2463
|
+
ok: false,
|
|
2464
|
+
stage: "prisma_setup"
|
|
2465
|
+
};
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
return {
|
|
2468
|
+
ok: false,
|
|
2469
|
+
stage: "prisma_setup",
|
|
2470
|
+
error
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
let deployResult;
|
|
2474
|
+
if (context.computeDeployContext) try {
|
|
2475
|
+
const result = await executeComputeDeployContext({
|
|
2476
|
+
context: context.computeDeployContext,
|
|
2477
|
+
projectDir: context.targetDirectory,
|
|
2478
|
+
createProject: !computeDatabaseResult
|
|
2479
|
+
});
|
|
2480
|
+
if (!result.ok && !result.cancelled) return {
|
|
2481
|
+
ok: false,
|
|
2482
|
+
stage: "compute_deploy",
|
|
2483
|
+
error: result.error
|
|
2484
|
+
};
|
|
2485
|
+
if (result.ok) {
|
|
2486
|
+
deployResult = result.result;
|
|
2487
|
+
prismaResult.nextSteps.push(`- ${getRunScriptCommand(context.prismaSetupContext.packageManager, "compute:deploy")}`);
|
|
2488
|
+
}
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
return {
|
|
2491
|
+
ok: false,
|
|
2492
|
+
stage: "compute_deploy",
|
|
2493
|
+
error
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
const summaryLines = [];
|
|
2497
|
+
summaryLines.push(`Setup complete.${prismaResult.warningSection}`);
|
|
2498
|
+
if (deployResult) {
|
|
2499
|
+
const database = computeDatabaseResult?.database ?? deployResult.database;
|
|
2500
|
+
summaryLines.push("", "Deployed to Prisma Compute:", ...deployResult.appUrl ? [`- App URL: ${deployResult.appUrl}`] : [], `- App: ${deployResult.appName} (${deployResult.appId})`, `- Deployment: ${deployResult.deploymentId}`, ...database ? [`- Database: ${database.name} (${database.id})`] : []);
|
|
2501
|
+
}
|
|
2502
|
+
summaryLines.push("", "Next steps:", prismaResult.nextSteps.join("\n"));
|
|
2503
|
+
outro(summaryLines.join("\n"));
|
|
2504
|
+
return { ok: true };
|
|
2505
|
+
}
|
|
2506
|
+
//#endregion
|
|
2507
|
+
//#region src/index.ts
|
|
2508
|
+
const CLI_VERSION = "0.7.2";
|
|
2509
|
+
const router = os.router({ create: os.meta({
|
|
2510
|
+
description: "Create a new project with Prisma setup",
|
|
2511
|
+
default: true,
|
|
2512
|
+
negateBooleans: true
|
|
2513
|
+
}).input(CreateCommandInputSchema.optional()).handler(async ({ input }) => {
|
|
2514
|
+
await runCreateCommand(input ?? {});
|
|
2515
|
+
}) });
|
|
2516
|
+
function createCreatePrismaCli() {
|
|
2517
|
+
return createCli({
|
|
2518
|
+
router,
|
|
2519
|
+
name: "create-prisma",
|
|
2520
|
+
version: CLI_VERSION
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
async function create(input = {}) {
|
|
2524
|
+
await runCreateCommand(input);
|
|
2525
|
+
}
|
|
2526
|
+
//#endregion
|
|
2527
|
+
export { CreateTemplateSchema as a, PackageManagerSchema as c, CreateCommandInputSchema as i, SchemaPresetSchema as l, createCreatePrismaCli as n, DatabaseProviderSchema as o, router as r, DatabaseUrlSchema as s, create as t };
|