create-prisma 0.7.2 → 0.8.0-pr.56.216.1
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 +35 -159
- package/dist/cli.mjs +9 -3
- package/dist/create-BeMLn0T7.mjs +1409 -0
- package/dist/index.d.mts +43 -62
- package/dist/index.mjs +35 -2
- package/package.json +24 -19
- package/templates/create/_shared/.gitattributes.hbs +11 -0
- package/templates/create/_shared/README.md.hbs +39 -0
- package/templates/create/_shared/module.ts.hbs +28 -0
- package/templates/create/_shared/pnpm-workspace.yaml.hbs +16 -0
- package/templates/create/_shared/prisma-composer.config.ts.hbs +11 -0
- package/templates/create/_shared/prisma.config.ts.hbs +17 -0
- package/templates/create/_shared/service.ts.hbs +42 -0
- package/templates/create/_shared/src/prisma/composer.ts.hbs +8 -0
- package/templates/create/_shared/src/prisma/db.ts.hbs +51 -0
- package/templates/create/_shared/src/prisma/seed.ts.hbs +36 -0
- package/templates/create/_shared/src/prisma/users.ts.hbs +35 -0
- package/templates/create/astro/{astro.config.mjs.hbs → astro.config.mjs} +1 -2
- package/templates/create/astro/package.json.hbs +5 -5
- package/templates/create/astro/src/pages/api/users.ts.hbs +6 -23
- package/templates/create/astro/src/pages/index.astro.hbs +19 -56
- package/templates/create/astro/tsconfig.json +3 -0
- package/templates/create/elysia/package.json.hbs +6 -7
- package/templates/create/elysia/src/index.ts.hbs +18 -27
- package/templates/create/elysia/tsconfig.json +3 -1
- package/templates/create/hono/package.json.hbs +6 -7
- package/templates/create/hono/src/index.ts.hbs +11 -15
- package/templates/create/hono/tsconfig.json +3 -1
- package/templates/create/minimal/.yarnrc.yml.hbs +3 -0
- package/templates/create/minimal/package.json.hbs +15 -0
- package/templates/create/minimal/src/index.ts.hbs +19 -0
- package/templates/create/{turborepo/apps/api → minimal}/tsconfig.json +2 -2
- package/templates/create/nest/package.json.hbs +7 -7
- package/templates/create/nest/src/app.module.ts.hbs +9 -10
- package/templates/create/nest/src/main.ts.hbs +3 -10
- package/templates/create/nest/src/prisma.service.ts.hbs +6 -5
- package/templates/create/nest/src/users.controller.ts.hbs +3 -5
- package/templates/create/nest/src/users.service.ts.hbs +4 -11
- package/templates/create/nest/tsconfig.json +3 -2
- package/templates/create/next/eslint.config.mjs +8 -1
- package/templates/create/next/package.json.hbs +10 -10
- package/templates/create/next/src/app/page.tsx.hbs +17 -56
- package/templates/create/next/tsconfig.json +1 -0
- package/templates/create/nuxt/app/pages/index.vue.hbs +12 -45
- package/templates/create/nuxt/package.json.hbs +8 -7
- package/templates/create/nuxt/server/api/users.get.ts.hbs +3 -15
- package/templates/create/nuxt/tsconfig.json +3 -0
- package/templates/create/svelte/package.json.hbs +9 -11
- package/templates/create/svelte/src/routes/+page.server.ts.hbs +4 -16
- package/templates/create/svelte/src/routes/+page.svelte.hbs +14 -164
- package/templates/create/svelte/svelte.config.js +1 -4
- package/templates/create/svelte/tsconfig.json +2 -1
- package/templates/create/svelte/vite.config.ts +3 -0
- package/templates/create/tanstack-start/package.json.hbs +14 -14
- package/templates/create/tanstack-start/src/routes/__root.tsx.hbs +2 -3
- package/templates/create/tanstack-start/src/routes/index.tsx.hbs +27 -78
- package/templates/create/tanstack-start/tsconfig.json +4 -2
- package/templates/create/tanstack-start/vite.config.ts +2 -4
- package/dist/src-B3hRLZ0U.mjs +0 -2527
- package/templates/create/astro/README.md.hbs +0 -47
- package/templates/create/astro/deno.json.hbs +0 -5
- package/templates/create/astro/prisma/schema.prisma.hbs +0 -21
- package/templates/create/astro/prisma/seed.ts.hbs +0 -38
- package/templates/create/astro/prisma.compute.ts.hbs +0 -10
- package/templates/create/astro/prisma.config.ts.hbs +0 -15
- package/templates/create/astro/src/lib/prisma.ts.hbs +0 -55
- package/templates/create/elysia/README.md.hbs +0 -43
- package/templates/create/elysia/deno.json.hbs +0 -5
- package/templates/create/elysia/prisma/schema.prisma.hbs +0 -25
- package/templates/create/elysia/prisma/seed.ts.hbs +0 -38
- package/templates/create/elysia/prisma.compute.ts.hbs +0 -11
- package/templates/create/elysia/prisma.config.ts.hbs +0 -15
- package/templates/create/elysia/src/lib/prisma.ts.hbs +0 -56
- package/templates/create/hono/README.md.hbs +0 -43
- package/templates/create/hono/deno.json.hbs +0 -5
- package/templates/create/hono/prisma/schema.prisma.hbs +0 -25
- package/templates/create/hono/prisma/seed.ts.hbs +0 -38
- package/templates/create/hono/prisma.compute.ts.hbs +0 -10
- package/templates/create/hono/prisma.config.ts.hbs +0 -15
- package/templates/create/hono/src/lib/prisma.ts.hbs +0 -56
- package/templates/create/nest/README.md.hbs +0 -43
- package/templates/create/nest/deno.json.hbs +0 -5
- package/templates/create/nest/prisma/schema.prisma.hbs +0 -25
- package/templates/create/nest/prisma/seed.ts.hbs +0 -40
- package/templates/create/nest/prisma.compute.ts.hbs +0 -10
- package/templates/create/nest/prisma.config.ts.hbs +0 -15
- package/templates/create/nest/src/lib/prisma.ts.hbs +0 -58
- package/templates/create/next/README.md.hbs +0 -42
- package/templates/create/next/deno.json.hbs +0 -12
- package/templates/create/next/prisma/schema.prisma.hbs +0 -21
- package/templates/create/next/prisma/seed.ts.hbs +0 -38
- package/templates/create/next/prisma.compute.ts.hbs +0 -9
- package/templates/create/next/prisma.config.ts.hbs +0 -15
- package/templates/create/next/src/lib/prisma.ts.hbs +0 -56
- package/templates/create/nuxt/README.md.hbs +0 -44
- package/templates/create/nuxt/deno.json.hbs +0 -5
- package/templates/create/nuxt/prisma/schema.prisma.hbs +0 -21
- package/templates/create/nuxt/prisma/seed.ts.hbs +0 -38
- package/templates/create/nuxt/prisma.compute.ts.hbs +0 -9
- package/templates/create/nuxt/prisma.config.ts.hbs +0 -15
- package/templates/create/nuxt/server/utils/prisma.ts.hbs +0 -56
- package/templates/create/svelte/README.md.hbs +0 -45
- package/templates/create/svelte/deno.json.hbs +0 -5
- package/templates/create/svelte/prisma/schema.prisma.hbs +0 -21
- package/templates/create/svelte/prisma/seed.ts.hbs +0 -38
- package/templates/create/svelte/prisma.config.ts.hbs +0 -15
- package/templates/create/svelte/src/lib/server/prisma.ts.hbs +0 -53
- package/templates/create/tanstack-start/README.md.hbs +0 -43
- package/templates/create/tanstack-start/deno.json.hbs +0 -13
- package/templates/create/tanstack-start/prisma/schema.prisma.hbs +0 -21
- package/templates/create/tanstack-start/prisma/seed.ts.hbs +0 -38
- package/templates/create/tanstack-start/prisma.compute.ts.hbs +0 -9
- package/templates/create/tanstack-start/prisma.config.ts.hbs +0 -15
- package/templates/create/tanstack-start/src/lib/prisma.server.ts.hbs +0 -56
- package/templates/create/turborepo/README.md.hbs +0 -42
- package/templates/create/turborepo/apps/api/package.json.hbs +0 -26
- package/templates/create/turborepo/apps/api/src/index.ts.hbs +0 -55
- package/templates/create/turborepo/deno.json.hbs +0 -5
- package/templates/create/turborepo/package.json.hbs +0 -30
- package/templates/create/turborepo/packages/db/package.json.hbs +0 -22
- package/templates/create/turborepo/packages/db/prisma/schema.prisma.hbs +0 -21
- package/templates/create/turborepo/packages/db/prisma/seed.ts.hbs +0 -38
- package/templates/create/turborepo/packages/db/prisma.config.ts.hbs +0 -15
- package/templates/create/turborepo/packages/db/src/client.ts.hbs +0 -63
- package/templates/create/turborepo/packages/db/src/index.ts +0 -2
- package/templates/create/turborepo/packages/db/tsconfig.json +0 -15
- package/templates/create/turborepo/pnpm-workspace.yaml +0 -3
- package/templates/create/turborepo/prisma.compute.ts.hbs +0 -14
- package/templates/create/turborepo/turbo.json +0 -28
- /package/templates/create/{turborepo → elysia}/.yarnrc.yml.hbs +0 -0
- /package/templates/create/next/{next.config.ts.hbs → next.config.ts} +0 -0
|
@@ -0,0 +1,1409 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, text } from "@clack/prompts";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { PostHog } from "posthog-node";
|
|
9
|
+
import Handlebars from "handlebars";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { execa } from "execa";
|
|
13
|
+
import { styleText } from "node:util";
|
|
14
|
+
|
|
15
|
+
//#region src/telemetry/client.ts
|
|
16
|
+
const TELEMETRY_API_KEY = "";
|
|
17
|
+
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
18
|
+
const TELEMETRY_CONFIG_FILE = "telemetry.json";
|
|
19
|
+
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;
|
|
20
|
+
function shouldDisableTelemetry() {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function getTelemetryConfigDir() {
|
|
24
|
+
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "create-prisma");
|
|
25
|
+
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "create-prisma");
|
|
26
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "create-prisma");
|
|
27
|
+
}
|
|
28
|
+
async function getAnonymousId() {
|
|
29
|
+
const telemetryConfigPath = path.join(getTelemetryConfigDir(), TELEMETRY_CONFIG_FILE);
|
|
30
|
+
try {
|
|
31
|
+
const config = await fs.readJSON(telemetryConfigPath);
|
|
32
|
+
if (typeof config.anonymousId === "string" && UUID_V4_REGEX.test(config.anonymousId)) return config.anonymousId;
|
|
33
|
+
} catch {}
|
|
34
|
+
const anonymousId = randomUUID();
|
|
35
|
+
try {
|
|
36
|
+
await fs.ensureDir(path.dirname(telemetryConfigPath));
|
|
37
|
+
await fs.writeJSON(telemetryConfigPath, { anonymousId }, { spaces: 2 });
|
|
38
|
+
} catch {}
|
|
39
|
+
return anonymousId;
|
|
40
|
+
}
|
|
41
|
+
function getCommonProperties() {
|
|
42
|
+
return {
|
|
43
|
+
"cli-version": "0.8.0-pr.56.216.1",
|
|
44
|
+
"node-version": process.version,
|
|
45
|
+
platform: process.platform,
|
|
46
|
+
arch: process.arch
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function sanitizeProperties(properties) {
|
|
50
|
+
return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0));
|
|
51
|
+
}
|
|
52
|
+
async function trackCliTelemetry(event, properties) {
|
|
53
|
+
if (shouldDisableTelemetry()) return;
|
|
54
|
+
let client;
|
|
55
|
+
try {
|
|
56
|
+
const distinctId = await getAnonymousId();
|
|
57
|
+
const sanitizedProperties = sanitizeProperties({
|
|
58
|
+
...getCommonProperties(),
|
|
59
|
+
...properties,
|
|
60
|
+
$process_person_profile: false
|
|
61
|
+
});
|
|
62
|
+
client = new PostHog(TELEMETRY_API_KEY, {
|
|
63
|
+
host: TELEMETRY_HOST,
|
|
64
|
+
disableGeoip: true,
|
|
65
|
+
flushAt: 1,
|
|
66
|
+
flushInterval: 0
|
|
67
|
+
});
|
|
68
|
+
await client.captureImmediate({
|
|
69
|
+
distinctId,
|
|
70
|
+
event,
|
|
71
|
+
properties: sanitizedProperties,
|
|
72
|
+
disableGeoip: true
|
|
73
|
+
});
|
|
74
|
+
} catch {} finally {
|
|
75
|
+
if (client) await client.shutdown().catch(() => {});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/telemetry/create.ts
|
|
81
|
+
const CREATE_PRISMA_NEXT_COMPLETED_EVENT = "cli:create_prisma_next_command_completed";
|
|
82
|
+
const CREATE_PRISMA_NEXT_FAILED_EVENT = "cli:create_prisma_next_command_failed";
|
|
83
|
+
function getTargetDirectoryState(context) {
|
|
84
|
+
if (!context.targetPathState.exists) return "new";
|
|
85
|
+
if (context.targetPathState.isEmptyDirectory) return "empty_directory";
|
|
86
|
+
return "non_empty_directory";
|
|
87
|
+
}
|
|
88
|
+
function getBaseCreateProperties(input, context) {
|
|
89
|
+
return {
|
|
90
|
+
command: "create",
|
|
91
|
+
"uses-defaults": input.yes === true,
|
|
92
|
+
verbose: input.verbose === true,
|
|
93
|
+
force: input.force === true,
|
|
94
|
+
template: context?.template ?? input.template ?? null,
|
|
95
|
+
"database-provider": context?.prismaSetupContext.databaseProvider ?? input.provider ?? null,
|
|
96
|
+
"authoring-style": context?.prismaSetupContext.authoring ?? input.authoring ?? null,
|
|
97
|
+
"package-manager": context?.prismaSetupContext.packageManager ?? input.packageManager ?? null,
|
|
98
|
+
"should-deploy": context?.prismaSetupContext.shouldDeploy ?? input.deploy ?? null,
|
|
99
|
+
"target-directory-state": context ? getTargetDirectoryState(context) : null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function getErrorName(error) {
|
|
103
|
+
if (error instanceof Error) return error.name;
|
|
104
|
+
return error === void 0 ? null : "UnknownError";
|
|
105
|
+
}
|
|
106
|
+
function getErrorCode(error) {
|
|
107
|
+
if (typeof error !== "object" || error === null) return null;
|
|
108
|
+
const exitCode = Reflect.get(error, "exitCode");
|
|
109
|
+
if (typeof exitCode === "number") return exitCode;
|
|
110
|
+
const code = Reflect.get(error, "code");
|
|
111
|
+
return typeof code === "number" || typeof code === "string" ? code : null;
|
|
112
|
+
}
|
|
113
|
+
async function trackCreateCompleted(params) {
|
|
114
|
+
await trackCliTelemetry(CREATE_PRISMA_NEXT_COMPLETED_EVENT, {
|
|
115
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
116
|
+
"duration-ms": params.durationMs
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function trackCreateFailed(params) {
|
|
120
|
+
await trackCliTelemetry(CREATE_PRISMA_NEXT_FAILED_EVENT, {
|
|
121
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
122
|
+
"duration-ms": params.durationMs,
|
|
123
|
+
"failure-stage": params.stage,
|
|
124
|
+
"error-name": getErrorName(params.error),
|
|
125
|
+
"error-code": getErrorCode(params.error)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/types.ts
|
|
131
|
+
const databaseProviderInputs = [
|
|
132
|
+
"postgres",
|
|
133
|
+
"postgresql",
|
|
134
|
+
"mongo",
|
|
135
|
+
"mongodb"
|
|
136
|
+
];
|
|
137
|
+
const packageManagers = [
|
|
138
|
+
"npm",
|
|
139
|
+
"pnpm",
|
|
140
|
+
"yarn",
|
|
141
|
+
"bun"
|
|
142
|
+
];
|
|
143
|
+
const authoringStyles = ["psl", "typescript"];
|
|
144
|
+
const createTemplates = [
|
|
145
|
+
"minimal",
|
|
146
|
+
"hono",
|
|
147
|
+
"elysia",
|
|
148
|
+
"nest",
|
|
149
|
+
"next",
|
|
150
|
+
"svelte",
|
|
151
|
+
"astro",
|
|
152
|
+
"nuxt",
|
|
153
|
+
"tanstack-start"
|
|
154
|
+
];
|
|
155
|
+
function normalizeDatabaseProvider(value) {
|
|
156
|
+
if (value === "postgresql") return "postgres";
|
|
157
|
+
if (value === "mongodb") return "mongo";
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
const DatabaseProviderSchema = z.enum(databaseProviderInputs).transform(normalizeDatabaseProvider);
|
|
161
|
+
const PackageManagerSchema = z.enum(packageManagers);
|
|
162
|
+
const AuthoringStyleSchema = z.enum(authoringStyles);
|
|
163
|
+
const CreateTemplateSchema = z.enum(createTemplates);
|
|
164
|
+
const CommonCommandOptionsSchema = z.object({
|
|
165
|
+
yes: z.boolean().optional().describe("Skip prompts and accept default choices"),
|
|
166
|
+
verbose: z.boolean().optional().describe("Show verbose command output during setup")
|
|
167
|
+
});
|
|
168
|
+
const PrismaSetupOptionsSchema = z.object({
|
|
169
|
+
provider: DatabaseProviderSchema.optional().describe("Prisma 8 database target: PostgreSQL relational models or MongoDB document models"),
|
|
170
|
+
authoring: AuthoringStyleSchema.optional().describe("Contract authoring style"),
|
|
171
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager used for dependency installation"),
|
|
172
|
+
deploy: z.boolean().optional().describe("Deploy the generated app to Prisma immediately"),
|
|
173
|
+
workspace: z.string().trim().min(1, "Please enter a valid workspace id or name").optional().describe("Prisma workspace id or name to deploy into")
|
|
174
|
+
});
|
|
175
|
+
const PrismaSetupCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
|
|
176
|
+
const CreateScaffoldOptionsSchema = z.object({
|
|
177
|
+
name: z.string().trim().min(1, "Please enter a valid project name").optional().describe("Project name / directory"),
|
|
178
|
+
template: CreateTemplateSchema.optional().describe("Project template"),
|
|
179
|
+
force: z.boolean().optional().describe("Allow scaffolding into a non-empty target directory")
|
|
180
|
+
});
|
|
181
|
+
const CreateCommandInputSchema = PrismaSetupCommandInputSchema.extend(CreateScaffoldOptionsSchema.shape);
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/utils/package-manager.ts
|
|
185
|
+
const packageManagerManifestValues = {
|
|
186
|
+
npm: "npm@10.9.0",
|
|
187
|
+
pnpm: "pnpm@11.21.0",
|
|
188
|
+
yarn: "yarn@4.13.0",
|
|
189
|
+
bun: "bun@1.3.9"
|
|
190
|
+
};
|
|
191
|
+
function parseUserAgent(userAgent) {
|
|
192
|
+
if (userAgent?.startsWith("pnpm")) return "pnpm";
|
|
193
|
+
if (userAgent?.startsWith("yarn")) return "yarn";
|
|
194
|
+
if (userAgent?.startsWith("bun")) return "bun";
|
|
195
|
+
if (userAgent?.startsWith("npm")) return "npm";
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function parsePackageManagerField(packageManagerField) {
|
|
199
|
+
if (typeof packageManagerField !== "string" || packageManagerField.length === 0) return null;
|
|
200
|
+
const managerName = packageManagerField.split("@")[0];
|
|
201
|
+
const parsed = PackageManagerSchema.safeParse(managerName);
|
|
202
|
+
return parsed.success ? parsed.data : null;
|
|
203
|
+
}
|
|
204
|
+
async function detectFromPackageJson(projectDir) {
|
|
205
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
206
|
+
if (!await fs.pathExists(packageJsonPath)) return null;
|
|
207
|
+
return parsePackageManagerField((await fs.readJson(packageJsonPath)).packageManager);
|
|
208
|
+
}
|
|
209
|
+
async function detectFromLockfile(projectDir) {
|
|
210
|
+
for (const check of [
|
|
211
|
+
{
|
|
212
|
+
manager: "pnpm",
|
|
213
|
+
lockfile: "pnpm-lock.yaml"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
manager: "yarn",
|
|
217
|
+
lockfile: "yarn.lock"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
manager: "bun",
|
|
221
|
+
lockfile: "bun.lockb"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
manager: "bun",
|
|
225
|
+
lockfile: "bun.lock"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
manager: "npm",
|
|
229
|
+
lockfile: "package-lock.json"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
manager: "npm",
|
|
233
|
+
lockfile: "npm-shrinkwrap.json"
|
|
234
|
+
}
|
|
235
|
+
]) if (await fs.pathExists(path.join(projectDir, check.lockfile))) return check.manager;
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
async function detectPackageManager(projectDir = process.cwd()) {
|
|
239
|
+
const fromPackageJson = await detectFromPackageJson(projectDir);
|
|
240
|
+
if (fromPackageJson) return fromPackageJson;
|
|
241
|
+
const fromLockfile = await detectFromLockfile(projectDir);
|
|
242
|
+
if (fromLockfile) return fromLockfile;
|
|
243
|
+
const fromUserAgent = parseUserAgent(process.env.npm_config_user_agent);
|
|
244
|
+
if (fromUserAgent) return fromUserAgent;
|
|
245
|
+
return "npm";
|
|
246
|
+
}
|
|
247
|
+
function getPackageManagerManifestValue(packageManager) {
|
|
248
|
+
if (!packageManager) return;
|
|
249
|
+
return packageManagerManifestValues[packageManager];
|
|
250
|
+
}
|
|
251
|
+
function getInstallCommand(packageManager) {
|
|
252
|
+
return `${packageManager} install`;
|
|
253
|
+
}
|
|
254
|
+
function getRunScriptCommand(packageManager, scriptName) {
|
|
255
|
+
switch (packageManager) {
|
|
256
|
+
case "bun": return `bun run ${scriptName}`;
|
|
257
|
+
case "pnpm": return `pnpm run ${scriptName}`;
|
|
258
|
+
case "yarn": return `yarn run ${scriptName}`;
|
|
259
|
+
default: return `npm run ${scriptName}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function getRuntimeScriptCommand(packageManager, kind, options) {
|
|
263
|
+
const { sourceEntrypoint, builtEntrypoint } = options;
|
|
264
|
+
if (packageManager === "bun") switch (kind) {
|
|
265
|
+
case "dev": return `bun --watch ${sourceEntrypoint}`;
|
|
266
|
+
case "build": return "tsc --noEmit";
|
|
267
|
+
case "start": return `bun ${sourceEntrypoint}`;
|
|
268
|
+
}
|
|
269
|
+
switch (kind) {
|
|
270
|
+
case "dev": return `tsx watch ${sourceEntrypoint}`;
|
|
271
|
+
case "build": return "tsc";
|
|
272
|
+
case "start": return builtEntrypoint ? `node ${builtEntrypoint}` : `tsx ${sourceEntrypoint}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function getInstallArgs(packageManager) {
|
|
276
|
+
return {
|
|
277
|
+
command: packageManager,
|
|
278
|
+
args: ["install"]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function getPackageExecutionArgs(packageManager, commandArgs) {
|
|
282
|
+
switch (packageManager) {
|
|
283
|
+
case "pnpm": return {
|
|
284
|
+
command: "pnpm",
|
|
285
|
+
args: ["dlx", ...commandArgs]
|
|
286
|
+
};
|
|
287
|
+
case "yarn": return {
|
|
288
|
+
command: "yarn",
|
|
289
|
+
args: ["dlx", ...commandArgs]
|
|
290
|
+
};
|
|
291
|
+
case "bun": return {
|
|
292
|
+
command: "bunx",
|
|
293
|
+
args: [...commandArgs]
|
|
294
|
+
};
|
|
295
|
+
default: return {
|
|
296
|
+
command: "npx",
|
|
297
|
+
args: ["--yes", ...commandArgs]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function getPackageExecutionCommand(packageManager, commandArgs) {
|
|
302
|
+
const execution = getPackageExecutionArgs(packageManager, commandArgs);
|
|
303
|
+
return [execution.command, ...execution.args].join(" ");
|
|
304
|
+
}
|
|
305
|
+
function getRunScriptArgs(packageManager, scriptName) {
|
|
306
|
+
switch (packageManager) {
|
|
307
|
+
case "bun": return {
|
|
308
|
+
command: "bun",
|
|
309
|
+
args: ["run", scriptName]
|
|
310
|
+
};
|
|
311
|
+
case "pnpm": return {
|
|
312
|
+
command: "pnpm",
|
|
313
|
+
args: ["run", scriptName]
|
|
314
|
+
};
|
|
315
|
+
case "yarn": return {
|
|
316
|
+
command: "yarn",
|
|
317
|
+
args: ["run", scriptName]
|
|
318
|
+
};
|
|
319
|
+
default: return {
|
|
320
|
+
command: "npm",
|
|
321
|
+
args: ["run", scriptName]
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/templates/shared.ts
|
|
328
|
+
Handlebars.registerHelper("eq", (left, right) => left === right);
|
|
329
|
+
Handlebars.registerHelper("runScriptCommand", (packageManager, scriptName) => packageManager ? getRunScriptCommand(packageManager, scriptName) : "");
|
|
330
|
+
Handlebars.registerHelper("packageManagerManifestValue", (packageManager) => getPackageManagerManifestValue(packageManager) ?? "");
|
|
331
|
+
Handlebars.registerHelper("runtimeScript", (packageManager, kind, sourceEntrypoint, builtEntrypoint, _options) => {
|
|
332
|
+
if (!packageManager) return "";
|
|
333
|
+
return getRuntimeScriptCommand(packageManager, kind, {
|
|
334
|
+
sourceEntrypoint,
|
|
335
|
+
builtEntrypoint
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
function findPackageRoot(startDir) {
|
|
339
|
+
let currentDir = startDir;
|
|
340
|
+
while (true) {
|
|
341
|
+
if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
|
|
342
|
+
const parentDir = path.dirname(currentDir);
|
|
343
|
+
if (parentDir === currentDir) break;
|
|
344
|
+
currentDir = parentDir;
|
|
345
|
+
}
|
|
346
|
+
throw new Error(`Unable to locate package root from: ${startDir}`);
|
|
347
|
+
}
|
|
348
|
+
function resolveTemplatesDir(relativeTemplatesDir) {
|
|
349
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
350
|
+
const packageRoot = findPackageRoot(path.dirname(currentFilePath));
|
|
351
|
+
const templatePath = path.join(packageRoot, relativeTemplatesDir);
|
|
352
|
+
if (!existsSync(templatePath)) throw new Error(`Template directory not found at: ${templatePath}`);
|
|
353
|
+
return templatePath;
|
|
354
|
+
}
|
|
355
|
+
async function getTemplateFilesRecursively(dir) {
|
|
356
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
357
|
+
return (await Promise.all(entries.map(async (entry) => {
|
|
358
|
+
const entryPath = path.join(dir, entry.name);
|
|
359
|
+
if (entry.isDirectory()) return getTemplateFilesRecursively(entryPath);
|
|
360
|
+
if (!entry.isFile()) return [];
|
|
361
|
+
return [entryPath];
|
|
362
|
+
}))).flat();
|
|
363
|
+
}
|
|
364
|
+
function stripHbsExtension(filePath) {
|
|
365
|
+
return filePath.endsWith(".hbs") ? filePath.slice(0, -4) : filePath;
|
|
366
|
+
}
|
|
367
|
+
function ensureTrailingNewline(content) {
|
|
368
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
369
|
+
}
|
|
370
|
+
async function renderTemplateFile(opts) {
|
|
371
|
+
const { templateFilePath, outputPath, context } = opts;
|
|
372
|
+
const templateContent = await fs.readFile(templateFilePath, "utf8");
|
|
373
|
+
const outputContent = templateFilePath.endsWith(".hbs") ? Handlebars.compile(templateContent, {
|
|
374
|
+
noEscape: true,
|
|
375
|
+
strict: true
|
|
376
|
+
})(context) : templateContent;
|
|
377
|
+
if (templateFilePath.endsWith(".hbs") && outputContent.trim().length === 0) return;
|
|
378
|
+
await fs.outputFile(outputPath, ensureTrailingNewline(outputContent), "utf8");
|
|
379
|
+
}
|
|
380
|
+
async function renderTemplateTree(opts) {
|
|
381
|
+
const { templateRoot, outputDir, context } = opts;
|
|
382
|
+
const templateFiles = await getTemplateFilesRecursively(templateRoot);
|
|
383
|
+
for (const templateFilePath of templateFiles) {
|
|
384
|
+
const relativeOutputPath = stripHbsExtension(path.relative(templateRoot, templateFilePath));
|
|
385
|
+
await renderTemplateFile({
|
|
386
|
+
templateFilePath,
|
|
387
|
+
outputPath: path.join(outputDir, relativeOutputPath),
|
|
388
|
+
context
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/templates/render-create-template.ts
|
|
395
|
+
function getCreateTemplateDir(template) {
|
|
396
|
+
return resolveTemplatesDir(`templates/create/${template}`);
|
|
397
|
+
}
|
|
398
|
+
function getCreateSharedTemplateDir() {
|
|
399
|
+
return resolveTemplatesDir("templates/create/_shared");
|
|
400
|
+
}
|
|
401
|
+
function createTemplateContext(projectName, template, provider, authoring, packageManager) {
|
|
402
|
+
return {
|
|
403
|
+
projectName,
|
|
404
|
+
template,
|
|
405
|
+
provider,
|
|
406
|
+
authoring,
|
|
407
|
+
packageManager
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async function scaffoldCreateSharedTemplates(opts) {
|
|
411
|
+
const { projectDir, projectName, template, provider, authoring, packageManager } = opts;
|
|
412
|
+
await renderTemplateTree({
|
|
413
|
+
templateRoot: getCreateSharedTemplateDir(),
|
|
414
|
+
outputDir: projectDir,
|
|
415
|
+
context: createTemplateContext(projectName, template, provider, authoring, packageManager)
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
async function scaffoldCreateFrameworkTemplate(opts) {
|
|
419
|
+
const { projectDir, projectName, template, provider, authoring, packageManager } = opts;
|
|
420
|
+
await renderTemplateTree({
|
|
421
|
+
templateRoot: getCreateTemplateDir(template),
|
|
422
|
+
outputDir: projectDir,
|
|
423
|
+
context: createTemplateContext(projectName, template, provider, authoring, packageManager)
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/constants/dependencies.ts
|
|
429
|
+
const dependencyVersionMap = {
|
|
430
|
+
"@astrojs/node": "^10.0.2",
|
|
431
|
+
"@elysiajs/node": "^1.4.5",
|
|
432
|
+
"@prisma/cli-engine": "0.2.0",
|
|
433
|
+
"@prisma/composer": "0.10.0",
|
|
434
|
+
"@prisma/composer-prisma-cloud": "0.10.0",
|
|
435
|
+
"@prisma/orm-mongo": "8.0.0-rc.4",
|
|
436
|
+
"@prisma/orm-postgres": "8.0.0-rc.4",
|
|
437
|
+
"@sveltejs/adapter-node": "^5.3.2",
|
|
438
|
+
"@types/node": "^25.6.2",
|
|
439
|
+
alchemy: "2.0.0-beta.67",
|
|
440
|
+
arktype: "^2.2.3",
|
|
441
|
+
esbuild: "^0.28.1",
|
|
442
|
+
effect: "4.0.0-beta.103",
|
|
443
|
+
mongodb: "^7.1.0",
|
|
444
|
+
"mongodb-memory-server": "^11.1.0",
|
|
445
|
+
nitro: "^3.0.260610-beta",
|
|
446
|
+
tsx: "^4.21.0",
|
|
447
|
+
typescript: "^5.9.3"
|
|
448
|
+
};
|
|
449
|
+
const PRISMA_PLATFORM_CLI_PACKAGE = "prisma@next";
|
|
450
|
+
function getDependencyVersion(packageName) {
|
|
451
|
+
return dependencyVersionMap[packageName];
|
|
452
|
+
}
|
|
453
|
+
function usesEsbuild(template) {
|
|
454
|
+
return template === "minimal" || template === "hono" || template === "elysia" || template === "nest";
|
|
455
|
+
}
|
|
456
|
+
function getCreateTemplateDependencies(template, _packageManager) {
|
|
457
|
+
const dependencies = [
|
|
458
|
+
"@prisma/composer",
|
|
459
|
+
"@prisma/composer-prisma-cloud",
|
|
460
|
+
"alchemy"
|
|
461
|
+
];
|
|
462
|
+
const devDependencies = ["@prisma/cli-engine"];
|
|
463
|
+
if (usesEsbuild(template)) devDependencies.push("esbuild");
|
|
464
|
+
if (template === "minimal" || usesEsbuild(template)) devDependencies.push("tsx");
|
|
465
|
+
if (template === "minimal") devDependencies.push("typescript");
|
|
466
|
+
if (template === "elysia") {
|
|
467
|
+
dependencies.push("@elysiajs/node");
|
|
468
|
+
devDependencies.push("@types/node");
|
|
469
|
+
}
|
|
470
|
+
if (template === "svelte") devDependencies.push("@sveltejs/adapter-node");
|
|
471
|
+
if (template === "astro") dependencies.push("@astrojs/node");
|
|
472
|
+
if (template === "tanstack-start") devDependencies.push("nitro");
|
|
473
|
+
return [{
|
|
474
|
+
packageJsonPath: "package.json",
|
|
475
|
+
dependencies,
|
|
476
|
+
devDependencies
|
|
477
|
+
}];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/constants/db-packages.ts
|
|
482
|
+
function getDbPackages(provider) {
|
|
483
|
+
switch (provider) {
|
|
484
|
+
case "postgres": return "@prisma/orm-postgres";
|
|
485
|
+
case "mongo": return "@prisma/orm-mongo";
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/tasks/install.ts
|
|
491
|
+
function getPrismaScriptMap(packageManager) {
|
|
492
|
+
const prismaCommand = (...args) => getPackageExecutionCommand(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]);
|
|
493
|
+
return {
|
|
494
|
+
"contract:emit": prismaCommand("contract", "emit"),
|
|
495
|
+
"db:init": prismaCommand("db", "init"),
|
|
496
|
+
"db:update": prismaCommand("db", "update"),
|
|
497
|
+
"db:verify": prismaCommand("db", "verify"),
|
|
498
|
+
"migration:plan": prismaCommand("migration", "plan"),
|
|
499
|
+
migrate: prismaCommand("migrate"),
|
|
500
|
+
"migration:status": prismaCommand("migration", "status"),
|
|
501
|
+
"migration:show": prismaCommand("migration", "show")
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function getComposerScriptMap(packageManager) {
|
|
505
|
+
const composerCommand = (subcommand, extraArgs = []) => getPackageExecutionCommand(packageManager, [
|
|
506
|
+
PRISMA_PLATFORM_CLI_PACKAGE,
|
|
507
|
+
"composer",
|
|
508
|
+
subcommand,
|
|
509
|
+
"module.ts",
|
|
510
|
+
...extraArgs
|
|
511
|
+
]);
|
|
512
|
+
return {
|
|
513
|
+
"composer:dev": composerCommand("dev"),
|
|
514
|
+
"composer:deploy": composerCommand("deploy"),
|
|
515
|
+
"composer:destroy": composerCommand("destroy", ["--production"]),
|
|
516
|
+
deploy: `${getRunScriptCommand(packageManager, "build")} && ${getRunScriptCommand(packageManager, "composer:deploy")}`,
|
|
517
|
+
"dev:composer": `${getRunScriptCommand(packageManager, "build")} && ${getRunScriptCommand(packageManager, "composer:dev")}`
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function unique(items) {
|
|
521
|
+
return [...new Set(items)];
|
|
522
|
+
}
|
|
523
|
+
function sortRecord(record) {
|
|
524
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
525
|
+
}
|
|
526
|
+
async function addPackageDependency(opts) {
|
|
527
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, scripts = {}, scriptMode, projectDir } = opts;
|
|
528
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
529
|
+
if (!await fs.pathExists(pkgJsonPath)) throw new Error(`No package.json found in ${projectDir}. Run this command inside an existing JavaScript/TypeScript project.`);
|
|
530
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
531
|
+
pkgJson.dependencies ??= {};
|
|
532
|
+
pkgJson.devDependencies ??= {};
|
|
533
|
+
pkgJson.scripts ??= {};
|
|
534
|
+
for (const packageName of unique(dependencies)) {
|
|
535
|
+
const version = getDependencyVersion(packageName);
|
|
536
|
+
if (!version) throw new Error(`Dependency ${packageName} is missing from the version map.`);
|
|
537
|
+
pkgJson.dependencies[packageName] = version;
|
|
538
|
+
}
|
|
539
|
+
for (const packageName of unique(devDependencies)) {
|
|
540
|
+
const version = getDependencyVersion(packageName);
|
|
541
|
+
if (!version) throw new Error(`Dependency ${packageName} is missing from the version map.`);
|
|
542
|
+
pkgJson.devDependencies[packageName] = version;
|
|
543
|
+
}
|
|
544
|
+
for (const [packageName, version] of Object.entries(customDependencies)) pkgJson.dependencies[packageName] = version;
|
|
545
|
+
for (const [scriptName, command] of Object.entries(scripts)) {
|
|
546
|
+
if (scriptMode === "if-missing" && typeof pkgJson.scripts[scriptName] === "string" && pkgJson.scripts[scriptName].trim().length > 0) continue;
|
|
547
|
+
pkgJson.scripts[scriptName] = command;
|
|
548
|
+
}
|
|
549
|
+
pkgJson.dependencies = sortRecord(pkgJson.dependencies);
|
|
550
|
+
pkgJson.devDependencies = sortRecord(pkgJson.devDependencies);
|
|
551
|
+
pkgJson.scripts = sortRecord(pkgJson.scripts);
|
|
552
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
553
|
+
}
|
|
554
|
+
async function writePrismaDependencies(provider, packageManager, _authoring, projectDir = process.cwd()) {
|
|
555
|
+
const dependencies = [getDbPackages(provider)];
|
|
556
|
+
if (provider === "mongo") dependencies.push("arktype", "mongodb");
|
|
557
|
+
await addPackageDependency({
|
|
558
|
+
dependencies,
|
|
559
|
+
devDependencies: ["@prisma/cli-engine", "@types/node"],
|
|
560
|
+
scripts: getPrismaScriptMap(packageManager),
|
|
561
|
+
projectDir
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
async function writeCreateTemplateDependencies(opts) {
|
|
565
|
+
const { template, packageManager, projectDir = process.cwd() } = opts;
|
|
566
|
+
for (const target of getCreateTemplateDependencies(template, packageManager)) await addPackageDependency({
|
|
567
|
+
dependencies: target.dependencies,
|
|
568
|
+
devDependencies: target.devDependencies,
|
|
569
|
+
customDependencies: target.customDependencies,
|
|
570
|
+
scripts: getComposerScriptMap(packageManager),
|
|
571
|
+
projectDir: path.join(projectDir, path.dirname(target.packageJsonPath))
|
|
572
|
+
});
|
|
573
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
574
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
575
|
+
const effectVersion = getDependencyVersion("effect");
|
|
576
|
+
if (!effectVersion) throw new Error("Dependency effect is missing from the version map.");
|
|
577
|
+
if (packageManager === "yarn") packageJson.resolutions = {
|
|
578
|
+
...packageJson.resolutions,
|
|
579
|
+
effect: effectVersion
|
|
580
|
+
};
|
|
581
|
+
else if (packageManager !== "pnpm") packageJson.overrides = {
|
|
582
|
+
...packageJson.overrides,
|
|
583
|
+
effect: effectVersion
|
|
584
|
+
};
|
|
585
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
586
|
+
}
|
|
587
|
+
async function installProjectDependencies(packageManager, projectDir = process.cwd(), options = {}) {
|
|
588
|
+
const installCommand = getInstallArgs(packageManager);
|
|
589
|
+
const env = packageManager === "yarn" ? { YARN_ENABLE_IMMUTABLE_INSTALLS: "false" } : void 0;
|
|
590
|
+
await execa(installCommand.command, installCommand.args, {
|
|
591
|
+
cwd: projectDir,
|
|
592
|
+
env,
|
|
593
|
+
stdio: options.verbose === true ? "inherit" : "pipe"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/tasks/deploy-with-composer.ts
|
|
599
|
+
function redactSecrets(message) {
|
|
600
|
+
return message.replace(/\b((?:prisma\+)?postgres(?:ql)?:\/\/)[^\s'"]+/gi, "$1<redacted>").replace(/\b([A-Z0-9_]*(?:DATABASE_URL|TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)[A-Z0-9_]*=)[^\s]+/g, "$1<redacted>");
|
|
601
|
+
}
|
|
602
|
+
function getErrorMessage(error) {
|
|
603
|
+
if (error instanceof Error) return redactSecrets(error.message);
|
|
604
|
+
return redactSecrets(String(error));
|
|
605
|
+
}
|
|
606
|
+
function stripResourcePrefix(id, prefix) {
|
|
607
|
+
const marker = `${prefix}_`;
|
|
608
|
+
return id.startsWith(marker) ? id.slice(marker.length) : id;
|
|
609
|
+
}
|
|
610
|
+
function getConsoleProjectUrl(workspaceId, projectId) {
|
|
611
|
+
const consoleWorkspaceId = stripResourcePrefix(workspaceId, "wksp");
|
|
612
|
+
const consoleProjectId = stripResourcePrefix(projectId, "proj");
|
|
613
|
+
return `https://console.prisma.io/${encodeURIComponent(consoleWorkspaceId)}/${encodeURIComponent(consoleProjectId)}`;
|
|
614
|
+
}
|
|
615
|
+
function parsePrismaCliEnvelope(output) {
|
|
616
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reverse();
|
|
617
|
+
for (const line of lines) try {
|
|
618
|
+
const parsed = JSON.parse(line);
|
|
619
|
+
const candidate = parsed.kind === "result" ? parsed.envelope : parsed;
|
|
620
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
621
|
+
if (typeof Reflect.get(candidate, "ok") !== "boolean") continue;
|
|
622
|
+
return candidate;
|
|
623
|
+
} catch {}
|
|
624
|
+
throw new Error("Prisma CLI returned output that is not a valid result envelope.");
|
|
625
|
+
}
|
|
626
|
+
function getPrismaCliArgs(packageManager, args) {
|
|
627
|
+
return getPackageExecutionArgs(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]);
|
|
628
|
+
}
|
|
629
|
+
async function runPrismaJsonCommand(options) {
|
|
630
|
+
const invocation = getPrismaCliArgs(options.packageManager, [
|
|
631
|
+
...options.args,
|
|
632
|
+
"--json",
|
|
633
|
+
"--no-interactive"
|
|
634
|
+
]);
|
|
635
|
+
const result = await execa(invocation.command, invocation.args, {
|
|
636
|
+
cwd: options.projectDir,
|
|
637
|
+
env: process.env,
|
|
638
|
+
reject: false
|
|
639
|
+
});
|
|
640
|
+
if (options.forwardStderr && result.stderr) process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
|
|
641
|
+
let envelope;
|
|
642
|
+
try {
|
|
643
|
+
envelope = parsePrismaCliEnvelope(result.stdout);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
if (result.exitCode !== 0 && result.stderr.trim()) throw new Error(result.stderr.trim());
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
if (result.exitCode !== 0 || !envelope.ok || envelope.result === void 0) {
|
|
649
|
+
const summary = envelope.error?.summary ?? envelope.error?.message;
|
|
650
|
+
throw new Error([summary, envelope.error?.why].filter(Boolean).join(": ") || result.stderr.trim() || "Prisma CLI command failed.");
|
|
651
|
+
}
|
|
652
|
+
return envelope.result;
|
|
653
|
+
}
|
|
654
|
+
async function ensureAuthentication(packageManager, projectDir, beforeInteractiveLogin) {
|
|
655
|
+
const whoami = () => runPrismaJsonCommand({
|
|
656
|
+
packageManager,
|
|
657
|
+
projectDir,
|
|
658
|
+
args: ["auth", "whoami"]
|
|
659
|
+
});
|
|
660
|
+
const authState = await whoami();
|
|
661
|
+
if (authState.authenticated) return authState;
|
|
662
|
+
const loginCommand = getPackageExecutionCommand(packageManager, [
|
|
663
|
+
PRISMA_PLATFORM_CLI_PACKAGE,
|
|
664
|
+
"auth",
|
|
665
|
+
"login"
|
|
666
|
+
]);
|
|
667
|
+
if (process.stdin.isTTY !== true) throw new Error(`Sign in first with ${loginCommand}, then run ${getRunScriptCommand(packageManager, "deploy")}.`);
|
|
668
|
+
beforeInteractiveLogin?.();
|
|
669
|
+
log.info("Sign in to Prisma to deploy.");
|
|
670
|
+
const login = getPrismaCliArgs(packageManager, ["auth", "login"]);
|
|
671
|
+
await execa(login.command, login.args, {
|
|
672
|
+
cwd: projectDir,
|
|
673
|
+
env: process.env,
|
|
674
|
+
stdio: "inherit"
|
|
675
|
+
});
|
|
676
|
+
const authenticatedState = await whoami();
|
|
677
|
+
if (!authenticatedState.authenticated) throw new Error("Prisma sign-in completed without an active workspace session.");
|
|
678
|
+
return authenticatedState;
|
|
679
|
+
}
|
|
680
|
+
function workspaceLabel(workspace) {
|
|
681
|
+
return workspace.name ?? workspace.id;
|
|
682
|
+
}
|
|
683
|
+
async function useWorkspace(options) {
|
|
684
|
+
return (await runPrismaJsonCommand({
|
|
685
|
+
packageManager: options.packageManager,
|
|
686
|
+
projectDir: options.projectDir,
|
|
687
|
+
args: [
|
|
688
|
+
"auth",
|
|
689
|
+
"workspace",
|
|
690
|
+
"use",
|
|
691
|
+
options.workspace
|
|
692
|
+
]
|
|
693
|
+
})).workspace;
|
|
694
|
+
}
|
|
695
|
+
async function selectDeploymentWorkspace(options) {
|
|
696
|
+
const activeWorkspace = options.authState.workspace;
|
|
697
|
+
if (!activeWorkspace) throw new Error("The active Prisma credential does not specify a workspace.");
|
|
698
|
+
if (options.workspace) {
|
|
699
|
+
if (options.authState.source === "environment") {
|
|
700
|
+
if (options.workspace === activeWorkspace.id || options.workspace === activeWorkspace.name) return activeWorkspace;
|
|
701
|
+
throw new Error(`The environment credential is fixed to workspace ${workspaceLabel(activeWorkspace)}. Unset it before using --workspace ${options.workspace}.`);
|
|
702
|
+
}
|
|
703
|
+
return useWorkspace({
|
|
704
|
+
packageManager: options.packageManager,
|
|
705
|
+
projectDir: options.projectDir,
|
|
706
|
+
workspace: options.workspace
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
if (!options.shouldPrompt || process.stdin.isTTY !== true) return activeWorkspace;
|
|
710
|
+
const available = await runPrismaJsonCommand({
|
|
711
|
+
packageManager: options.packageManager,
|
|
712
|
+
projectDir: options.projectDir,
|
|
713
|
+
args: [
|
|
714
|
+
"auth",
|
|
715
|
+
"workspace",
|
|
716
|
+
"list"
|
|
717
|
+
]
|
|
718
|
+
});
|
|
719
|
+
if (available.items.length <= 1) return activeWorkspace;
|
|
720
|
+
options.beforePrompt?.();
|
|
721
|
+
const selectedWorkspaceId = await select({
|
|
722
|
+
message: "Select Prisma workspace for deployment",
|
|
723
|
+
initialValue: activeWorkspace.id,
|
|
724
|
+
options: available.items.map((workspace) => ({
|
|
725
|
+
value: workspace.workspaceId,
|
|
726
|
+
label: workspace.workspaceName ?? workspace.workspaceId,
|
|
727
|
+
hint: workspace.current ? `${workspace.workspaceId}, current` : workspace.workspaceId
|
|
728
|
+
}))
|
|
729
|
+
});
|
|
730
|
+
if (isCancel(selectedWorkspaceId)) {
|
|
731
|
+
cancel("Operation cancelled.");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
options.afterPrompt?.();
|
|
735
|
+
if (selectedWorkspaceId === activeWorkspace.id) return activeWorkspace;
|
|
736
|
+
return useWorkspace({
|
|
737
|
+
packageManager: options.packageManager,
|
|
738
|
+
projectDir: options.projectDir,
|
|
739
|
+
workspace: selectedWorkspaceId
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
function parseComposerDeployResult(result) {
|
|
743
|
+
const summary = result.summary;
|
|
744
|
+
if (!summary) return;
|
|
745
|
+
const computeService = summary.nodes.flatMap((node) => node.entities).find((entity) => entity.kind === "compute-service");
|
|
746
|
+
return {
|
|
747
|
+
appName: summary.app,
|
|
748
|
+
...computeService?.url ? { appUrl: computeService.url.replace(/\/$/, "") } : {}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
async function getProjectDetails(options) {
|
|
752
|
+
try {
|
|
753
|
+
const result = await runPrismaJsonCommand({
|
|
754
|
+
packageManager: options.packageManager,
|
|
755
|
+
projectDir: options.projectDir,
|
|
756
|
+
args: [
|
|
757
|
+
"project",
|
|
758
|
+
"show",
|
|
759
|
+
"--project",
|
|
760
|
+
options.appName
|
|
761
|
+
]
|
|
762
|
+
});
|
|
763
|
+
if (!result.project) return;
|
|
764
|
+
return {
|
|
765
|
+
workspace: result.workspace,
|
|
766
|
+
project: {
|
|
767
|
+
id: result.project.id,
|
|
768
|
+
name: result.project.name,
|
|
769
|
+
consoleUrl: getConsoleProjectUrl(result.workspace.id, result.project.id)
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
} catch {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async function deployWithComposer(options) {
|
|
777
|
+
const progress = options.verbose ? void 0 : spinner();
|
|
778
|
+
let progressRunning = false;
|
|
779
|
+
const showProgress = (message) => {
|
|
780
|
+
if (!progress) return;
|
|
781
|
+
if (progressRunning) progress.message(message);
|
|
782
|
+
else {
|
|
783
|
+
progress.start(message);
|
|
784
|
+
progressRunning = true;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
const clearProgress = () => {
|
|
788
|
+
if (!progress || !progressRunning) return;
|
|
789
|
+
progress.clear();
|
|
790
|
+
progressRunning = false;
|
|
791
|
+
};
|
|
792
|
+
try {
|
|
793
|
+
showProgress("Checking Prisma account...");
|
|
794
|
+
if (options.verbose) log.step("Checking Prisma account.");
|
|
795
|
+
const authState = await ensureAuthentication(options.packageManager, options.projectDir, clearProgress);
|
|
796
|
+
showProgress("Checking Prisma workspace...");
|
|
797
|
+
if (options.verbose) log.step("Checking Prisma workspace.");
|
|
798
|
+
const selectedWorkspace = await selectDeploymentWorkspace({
|
|
799
|
+
packageManager: options.packageManager,
|
|
800
|
+
projectDir: options.projectDir,
|
|
801
|
+
shouldPrompt: options.shouldPromptForWorkspace,
|
|
802
|
+
authState,
|
|
803
|
+
beforePrompt: clearProgress,
|
|
804
|
+
afterPrompt: () => showProgress("Selecting Prisma workspace..."),
|
|
805
|
+
...options.workspace ? { workspace: options.workspace } : {}
|
|
806
|
+
});
|
|
807
|
+
if (!selectedWorkspace) return;
|
|
808
|
+
showProgress("Building for deployment...");
|
|
809
|
+
if (options.verbose) log.step("Building for deployment.");
|
|
810
|
+
const build = getRunScriptArgs(options.packageManager, "build");
|
|
811
|
+
await execa(build.command, build.args, {
|
|
812
|
+
cwd: options.projectDir,
|
|
813
|
+
env: process.env,
|
|
814
|
+
stdio: options.verbose ? "inherit" : "pipe"
|
|
815
|
+
});
|
|
816
|
+
showProgress("Deploying to Prisma...");
|
|
817
|
+
if (options.verbose) log.step("Deploying to Prisma.");
|
|
818
|
+
const deployment = parseComposerDeployResult(await runPrismaJsonCommand({
|
|
819
|
+
packageManager: options.packageManager,
|
|
820
|
+
projectDir: options.projectDir,
|
|
821
|
+
args: [
|
|
822
|
+
"composer",
|
|
823
|
+
"deploy",
|
|
824
|
+
"module.ts"
|
|
825
|
+
],
|
|
826
|
+
forwardStderr: options.verbose
|
|
827
|
+
}));
|
|
828
|
+
const appName = deployment?.appName ?? options.appName;
|
|
829
|
+
showProgress("Loading deployment details...");
|
|
830
|
+
const details = await getProjectDetails({
|
|
831
|
+
packageManager: options.packageManager,
|
|
832
|
+
projectDir: options.projectDir,
|
|
833
|
+
appName
|
|
834
|
+
});
|
|
835
|
+
progress?.stop("Deployed to Prisma.");
|
|
836
|
+
progressRunning = false;
|
|
837
|
+
if (options.verbose) log.success("Deployed to Prisma.");
|
|
838
|
+
const workspace = details?.workspace ?? selectedWorkspace;
|
|
839
|
+
return {
|
|
840
|
+
appName,
|
|
841
|
+
...deployment?.appUrl ? { appUrl: deployment.appUrl } : {},
|
|
842
|
+
...workspace ? { workspace } : {},
|
|
843
|
+
project: details?.project ?? { name: appName }
|
|
844
|
+
};
|
|
845
|
+
} catch (error) {
|
|
846
|
+
progress?.error("Deployment failed.");
|
|
847
|
+
progressRunning = false;
|
|
848
|
+
log.error(`Deploy failed: ${getErrorMessage(error)}`);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/tasks/setup-prisma.ts
|
|
855
|
+
const DEFAULT_DATABASE_PROVIDER = "postgres";
|
|
856
|
+
const DEFAULT_AUTHORING = "psl";
|
|
857
|
+
async function promptForDatabaseProvider() {
|
|
858
|
+
const databaseProvider = await select({
|
|
859
|
+
message: "Select your database",
|
|
860
|
+
initialValue: DEFAULT_DATABASE_PROVIDER,
|
|
861
|
+
options: [{
|
|
862
|
+
value: "postgres",
|
|
863
|
+
label: "PostgreSQL",
|
|
864
|
+
hint: "Prisma Postgres with Composer"
|
|
865
|
+
}, {
|
|
866
|
+
value: "mongo",
|
|
867
|
+
label: "MongoDB",
|
|
868
|
+
hint: "Connect an existing MongoDB database"
|
|
869
|
+
}]
|
|
870
|
+
});
|
|
871
|
+
if (isCancel(databaseProvider)) {
|
|
872
|
+
cancel("Operation cancelled.");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
return DatabaseProviderSchema.parse(databaseProvider);
|
|
876
|
+
}
|
|
877
|
+
async function promptForAuthoringStyle() {
|
|
878
|
+
const authoring = await select({
|
|
879
|
+
message: "Choose contract authoring style",
|
|
880
|
+
initialValue: DEFAULT_AUTHORING,
|
|
881
|
+
options: [{
|
|
882
|
+
value: "psl",
|
|
883
|
+
label: "PSL",
|
|
884
|
+
hint: "Prisma schema syntax"
|
|
885
|
+
}, {
|
|
886
|
+
value: "typescript",
|
|
887
|
+
label: "TypeScript",
|
|
888
|
+
hint: "TypeScript contract builder"
|
|
889
|
+
}]
|
|
890
|
+
});
|
|
891
|
+
if (isCancel(authoring)) {
|
|
892
|
+
cancel("Operation cancelled.");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
return AuthoringStyleSchema.parse(authoring);
|
|
896
|
+
}
|
|
897
|
+
function getPackageManagerHint(option, detected) {
|
|
898
|
+
const hints = {
|
|
899
|
+
npm: "Node.js default",
|
|
900
|
+
pnpm: "Fast, disk-efficient package manager",
|
|
901
|
+
yarn: "Yarn package manager",
|
|
902
|
+
bun: "Fast runtime and package manager"
|
|
903
|
+
};
|
|
904
|
+
return option === detected ? `Detected; ${hints[option]}` : hints[option];
|
|
905
|
+
}
|
|
906
|
+
async function promptForPackageManager(detected) {
|
|
907
|
+
const packageManager = await select({
|
|
908
|
+
message: "Choose package manager",
|
|
909
|
+
initialValue: detected,
|
|
910
|
+
options: [
|
|
911
|
+
"npm",
|
|
912
|
+
"pnpm",
|
|
913
|
+
"yarn",
|
|
914
|
+
"bun"
|
|
915
|
+
].map((value) => ({
|
|
916
|
+
value,
|
|
917
|
+
label: value,
|
|
918
|
+
hint: getPackageManagerHint(value, detected)
|
|
919
|
+
}))
|
|
920
|
+
});
|
|
921
|
+
if (isCancel(packageManager)) {
|
|
922
|
+
cancel("Operation cancelled.");
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
return PackageManagerSchema.parse(packageManager);
|
|
926
|
+
}
|
|
927
|
+
async function promptForDeployment() {
|
|
928
|
+
const shouldDeploy = await confirm({
|
|
929
|
+
message: "Deploy to Prisma now?",
|
|
930
|
+
initialValue: true
|
|
931
|
+
});
|
|
932
|
+
if (isCancel(shouldDeploy)) {
|
|
933
|
+
cancel("Operation cancelled.");
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
return Boolean(shouldDeploy);
|
|
937
|
+
}
|
|
938
|
+
async function collectPrismaSetupContext(input, options = {}) {
|
|
939
|
+
const projectDir = path.resolve(options.projectDir ?? process.cwd());
|
|
940
|
+
const useDefaults = input.yes === true;
|
|
941
|
+
const databaseProvider = input.provider ?? (useDefaults ? DEFAULT_DATABASE_PROVIDER : await promptForDatabaseProvider());
|
|
942
|
+
if (!databaseProvider) return;
|
|
943
|
+
const authoring = input.authoring ?? (useDefaults ? DEFAULT_AUTHORING : await promptForAuthoringStyle());
|
|
944
|
+
if (!authoring) return;
|
|
945
|
+
const detectedPackageManager = await detectPackageManager(projectDir);
|
|
946
|
+
const packageManager = input.packageManager ?? (useDefaults ? detectedPackageManager : await promptForPackageManager(detectedPackageManager));
|
|
947
|
+
if (!packageManager) return;
|
|
948
|
+
const shouldDeploy = input.deploy ?? (useDefaults ? false : await promptForDeployment());
|
|
949
|
+
if (shouldDeploy === void 0) return;
|
|
950
|
+
return {
|
|
951
|
+
projectDir,
|
|
952
|
+
verbose: input.verbose === true,
|
|
953
|
+
databaseProvider,
|
|
954
|
+
authoring,
|
|
955
|
+
packageManager,
|
|
956
|
+
shouldDeploy,
|
|
957
|
+
shouldPromptForWorkspace: !useDefaults,
|
|
958
|
+
...input.workspace ? { workspace: input.workspace } : {}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function getCommandErrorMessage(error) {
|
|
962
|
+
if (error instanceof Error && "stderr" in error) {
|
|
963
|
+
const stderr = String(error.stderr ?? "").trim();
|
|
964
|
+
if (stderr) return stderr;
|
|
965
|
+
}
|
|
966
|
+
return error instanceof Error ? error.message : String(error);
|
|
967
|
+
}
|
|
968
|
+
function getContractPath(authoring) {
|
|
969
|
+
return `src/prisma/contract${authoring === "typescript" ? ".ts" : ".prisma"}`;
|
|
970
|
+
}
|
|
971
|
+
function getInitTarget(provider) {
|
|
972
|
+
return provider === "mongo" ? "mongodb" : "postgres";
|
|
973
|
+
}
|
|
974
|
+
function getPrismaCliInvocation(packageManager, args) {
|
|
975
|
+
return getPackageExecutionArgs(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]);
|
|
976
|
+
}
|
|
977
|
+
async function runPrismaInit(context, projectDir) {
|
|
978
|
+
const args = [
|
|
979
|
+
"orm",
|
|
980
|
+
"init",
|
|
981
|
+
"--yes",
|
|
982
|
+
"--no-interactive",
|
|
983
|
+
"--target",
|
|
984
|
+
getInitTarget(context.databaseProvider),
|
|
985
|
+
"--authoring",
|
|
986
|
+
context.authoring,
|
|
987
|
+
"--schema-path",
|
|
988
|
+
getContractPath(context.authoring),
|
|
989
|
+
"--skip-install",
|
|
990
|
+
"--skip-skills"
|
|
991
|
+
];
|
|
992
|
+
const invocation = getPrismaCliInvocation(context.packageManager, args);
|
|
993
|
+
if (context.verbose) log.step(`Running ${[invocation.command, ...invocation.args].join(" ")}`);
|
|
994
|
+
await execa(invocation.command, invocation.args, {
|
|
995
|
+
cwd: projectDir,
|
|
996
|
+
stdio: context.verbose ? "inherit" : "pipe",
|
|
997
|
+
env: {
|
|
998
|
+
...process.env,
|
|
999
|
+
CI: "1"
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
async function ensureGitignoreEntry(projectDir, entry) {
|
|
1004
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
1005
|
+
const existing = await fs.pathExists(gitignorePath) ? await fs.readFile(gitignorePath, "utf8") : "";
|
|
1006
|
+
const lines = existing.split(/\r?\n/).map((line) => line.trim());
|
|
1007
|
+
if (lines.includes(entry) || lines.includes(`/${entry}`)) return;
|
|
1008
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
1009
|
+
await fs.writeFile(gitignorePath, `${existing}${separator}${entry}\n`, "utf8");
|
|
1010
|
+
}
|
|
1011
|
+
async function ensureMongoEnvironment(projectDir) {
|
|
1012
|
+
const envPath = path.join(projectDir, ".env");
|
|
1013
|
+
if (!await fs.pathExists(envPath)) await fs.writeFile(envPath, "DATABASE_URL=\"mongodb://localhost:27017/mydb?replicaSet=rs0&directConnection=true\"\n", "utf8");
|
|
1014
|
+
await ensureGitignoreEntry(projectDir, ".env");
|
|
1015
|
+
}
|
|
1016
|
+
async function ensureComposerTypeScriptOptions(projectDir) {
|
|
1017
|
+
const tsconfigPath = path.join(projectDir, "tsconfig.json");
|
|
1018
|
+
const tsconfig = await fs.readFile(tsconfigPath, "utf8");
|
|
1019
|
+
const additions = [];
|
|
1020
|
+
if (!/"allowImportingTsExtensions"\s*:/.test(tsconfig)) additions.push(" \"allowImportingTsExtensions\": true,");
|
|
1021
|
+
if (!/"noEmit"\s*:/.test(tsconfig)) additions.push(" \"noEmit\": true,");
|
|
1022
|
+
if (additions.length === 0) return;
|
|
1023
|
+
const updated = tsconfig.replace(/"compilerOptions"\s*:\s*\{/, (match) => `${match}\n${additions.join("\n")}`);
|
|
1024
|
+
if (updated === tsconfig) throw new Error("tsconfig.json is missing compilerOptions.");
|
|
1025
|
+
await fs.writeFile(tsconfigPath, updated, "utf8");
|
|
1026
|
+
}
|
|
1027
|
+
async function emitContract(context, projectDir) {
|
|
1028
|
+
const invocation = getPrismaCliInvocation(context.packageManager, ["contract", "emit"]);
|
|
1029
|
+
if (context.verbose) log.step([invocation.command, ...invocation.args].join(" "));
|
|
1030
|
+
await execa(invocation.command, invocation.args, {
|
|
1031
|
+
cwd: projectDir,
|
|
1032
|
+
stdio: context.verbose ? "inherit" : "pipe"
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
function formatNextSteps(steps) {
|
|
1036
|
+
return steps.map((step) => `${step.command}\n ${step.description}`).join("\n\n");
|
|
1037
|
+
}
|
|
1038
|
+
function formatPlatformTarget(name, id) {
|
|
1039
|
+
return name ? `${name} (${id})` : id;
|
|
1040
|
+
}
|
|
1041
|
+
function formatProjectSummary(options) {
|
|
1042
|
+
const lines = [];
|
|
1043
|
+
if (options.createdProjectPath) lines.push(`Path: ${path.resolve(options.createdProjectPath)}`);
|
|
1044
|
+
if (options.deployment?.workspace) lines.push(`Workspace: ${formatPlatformTarget(options.deployment.workspace.name, options.deployment.workspace.id)}`);
|
|
1045
|
+
if (options.deployment) {
|
|
1046
|
+
lines.push(`Project: ${options.deployment.project.id ? formatPlatformTarget(options.deployment.project.name, options.deployment.project.id) : options.deployment.project.name}`);
|
|
1047
|
+
lines.push(`App: ${options.deployment.appUrl ?? options.deployment.appName}`);
|
|
1048
|
+
if (options.deployment.project.consoleUrl) lines.push(`Console: ${options.deployment.project.consoleUrl}`);
|
|
1049
|
+
}
|
|
1050
|
+
return lines.join("\n");
|
|
1051
|
+
}
|
|
1052
|
+
function buildNextSteps(context, options) {
|
|
1053
|
+
const nextSteps = [...options.prependNextSteps ?? []];
|
|
1054
|
+
if (context.databaseProvider === "mongo") nextSteps.push({
|
|
1055
|
+
command: "Set MONGODB_URL in your environment",
|
|
1056
|
+
description: "Composer uses this secret when deploying the MongoDB template."
|
|
1057
|
+
});
|
|
1058
|
+
if (options.includeDevNextStep) nextSteps.push({
|
|
1059
|
+
command: getRunScriptCommand(context.packageManager, "dev:composer"),
|
|
1060
|
+
description: "Build and start the app with Prisma Composer locally."
|
|
1061
|
+
});
|
|
1062
|
+
nextSteps.push({
|
|
1063
|
+
command: getRunScriptCommand(context.packageManager, "deploy"),
|
|
1064
|
+
description: "Build and deploy the app with Prisma Composer."
|
|
1065
|
+
});
|
|
1066
|
+
return nextSteps;
|
|
1067
|
+
}
|
|
1068
|
+
async function executePrismaSetupContext(context, options = {}) {
|
|
1069
|
+
const projectDir = path.resolve(options.projectDir ?? context.projectDir);
|
|
1070
|
+
const projectName = options.projectName ?? path.basename(projectDir);
|
|
1071
|
+
const template = options.template ?? "minimal";
|
|
1072
|
+
const progress = context.verbose ? void 0 : options.progressSpinner ?? spinner();
|
|
1073
|
+
if (progress !== void 0 && !options.progressSpinner) progress.start("Creating Prisma 8 project...");
|
|
1074
|
+
try {
|
|
1075
|
+
progress?.message("Preparing Prisma 8 project files...");
|
|
1076
|
+
await runPrismaInit(context, projectDir);
|
|
1077
|
+
await scaffoldCreateSharedTemplates({
|
|
1078
|
+
projectDir,
|
|
1079
|
+
projectName,
|
|
1080
|
+
template,
|
|
1081
|
+
provider: context.databaseProvider,
|
|
1082
|
+
authoring: context.authoring,
|
|
1083
|
+
packageManager: context.packageManager
|
|
1084
|
+
});
|
|
1085
|
+
await writePrismaDependencies(context.databaseProvider, context.packageManager, context.authoring, projectDir);
|
|
1086
|
+
await ensureComposerTypeScriptOptions(projectDir);
|
|
1087
|
+
if (context.databaseProvider === "mongo") await ensureMongoEnvironment(projectDir);
|
|
1088
|
+
progress?.message(`Installing dependencies with ${getInstallCommand(context.packageManager)}...`);
|
|
1089
|
+
await installProjectDependencies(context.packageManager, projectDir, { verbose: context.verbose });
|
|
1090
|
+
progress?.message("Generating Prisma 8 contract artifacts...");
|
|
1091
|
+
await emitContract(context, projectDir);
|
|
1092
|
+
progress?.stop("Prisma 8 project ready.");
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
progress?.error("Could not create Prisma 8 project.");
|
|
1095
|
+
cancel(getCommandErrorMessage(error));
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
let deployment;
|
|
1099
|
+
if (context.shouldDeploy) {
|
|
1100
|
+
deployment = await deployWithComposer({
|
|
1101
|
+
appName: projectName,
|
|
1102
|
+
packageManager: context.packageManager,
|
|
1103
|
+
projectDir,
|
|
1104
|
+
shouldPromptForWorkspace: context.shouldPromptForWorkspace,
|
|
1105
|
+
verbose: context.verbose,
|
|
1106
|
+
...context.workspace ? { workspace: context.workspace } : {}
|
|
1107
|
+
});
|
|
1108
|
+
if (!deployment) return false;
|
|
1109
|
+
}
|
|
1110
|
+
const projectSummary = formatProjectSummary({
|
|
1111
|
+
createdProjectPath: options.createdProjectPath,
|
|
1112
|
+
deployment
|
|
1113
|
+
});
|
|
1114
|
+
if (projectSummary) note(projectSummary, context.shouldDeploy ? "Deployment" : "Project");
|
|
1115
|
+
note(formatNextSteps(buildNextSteps(context, options)), "Next steps");
|
|
1116
|
+
outro(context.shouldDeploy ? "Prisma 8 app deployed." : "Prisma 8 project ready.");
|
|
1117
|
+
return true;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
//#endregion
|
|
1121
|
+
//#region src/ui/branding.ts
|
|
1122
|
+
const prismaTitle = `${styleText(["bold", "cyanBright"], "◭")} ${styleText(["bold", "cyanBright"], "Create")} ${styleText(["bold", "magentaBright"], "Prisma")} ${styleText(["bold", "blueBright"], "8")}`;
|
|
1123
|
+
function getCreatePrismaIntro() {
|
|
1124
|
+
return prismaTitle;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/utils/node-version.ts
|
|
1129
|
+
const MINIMUM_NODE_VERSION = [
|
|
1130
|
+
22,
|
|
1131
|
+
18,
|
|
1132
|
+
0
|
|
1133
|
+
];
|
|
1134
|
+
function parseVersion(version) {
|
|
1135
|
+
const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split(".");
|
|
1136
|
+
return [
|
|
1137
|
+
Number(major),
|
|
1138
|
+
Number(minor),
|
|
1139
|
+
Number.parseInt(patch, 10)
|
|
1140
|
+
];
|
|
1141
|
+
}
|
|
1142
|
+
function supportsPrismaNext(nodeVersion = process.versions.node) {
|
|
1143
|
+
const current = parseVersion(nodeVersion);
|
|
1144
|
+
for (let index = 0; index < MINIMUM_NODE_VERSION.length; index += 1) {
|
|
1145
|
+
if (current[index] > MINIMUM_NODE_VERSION[index]) return true;
|
|
1146
|
+
if (current[index] < MINIMUM_NODE_VERSION[index]) return false;
|
|
1147
|
+
}
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
function getUnsupportedNodeMessage(nodeVersion = process.versions.node) {
|
|
1151
|
+
return [
|
|
1152
|
+
`Node.js ${nodeVersion} is unsupported by create-prisma@next.`,
|
|
1153
|
+
"Required: Node.js 22.18 or newer.",
|
|
1154
|
+
"Update Node.js and run the command again."
|
|
1155
|
+
].join("\n");
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/commands/create.ts
|
|
1160
|
+
const DEFAULT_PROJECT_NAME = "my-app";
|
|
1161
|
+
const DEFAULT_TEMPLATE = "minimal";
|
|
1162
|
+
function toPackageName(projectName) {
|
|
1163
|
+
return projectName.toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+/, "").replace(/-+$/, "") || "app";
|
|
1164
|
+
}
|
|
1165
|
+
function formatPathForDisplay(filePath) {
|
|
1166
|
+
return path.relative(process.cwd(), filePath) || ".";
|
|
1167
|
+
}
|
|
1168
|
+
function validateProjectName(value) {
|
|
1169
|
+
const trimmed = String(value ?? "").trim();
|
|
1170
|
+
if (trimmed.length === 0) return "Please enter a project name.";
|
|
1171
|
+
if (trimmed === "..") return "Project name cannot be '..'.";
|
|
1172
|
+
if (path.isAbsolute(trimmed)) return "Use a relative project name instead of an absolute path.";
|
|
1173
|
+
}
|
|
1174
|
+
async function promptForProjectName() {
|
|
1175
|
+
const projectName = await text({
|
|
1176
|
+
message: "Project name",
|
|
1177
|
+
placeholder: DEFAULT_PROJECT_NAME,
|
|
1178
|
+
initialValue: DEFAULT_PROJECT_NAME,
|
|
1179
|
+
validate: validateProjectName
|
|
1180
|
+
});
|
|
1181
|
+
if (isCancel(projectName)) {
|
|
1182
|
+
cancel("Operation cancelled.");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
return String(projectName).trim();
|
|
1186
|
+
}
|
|
1187
|
+
async function promptForCreateTemplate() {
|
|
1188
|
+
const template = await select({
|
|
1189
|
+
message: "Select template",
|
|
1190
|
+
initialValue: DEFAULT_TEMPLATE,
|
|
1191
|
+
options: [
|
|
1192
|
+
{
|
|
1193
|
+
value: "minimal",
|
|
1194
|
+
label: "Minimal",
|
|
1195
|
+
hint: "Script-first Prisma 8 starter with no web framework"
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
value: "hono",
|
|
1199
|
+
label: "Hono",
|
|
1200
|
+
hint: "Lightweight TypeScript API server"
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
value: "elysia",
|
|
1204
|
+
label: "Elysia",
|
|
1205
|
+
hint: "Bun-friendly TypeScript API server"
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
value: "nest",
|
|
1209
|
+
label: "NestJS",
|
|
1210
|
+
hint: "Structured Node API with controllers and services"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
value: "next",
|
|
1214
|
+
label: "Next.js",
|
|
1215
|
+
hint: "Full-stack React app with App Router"
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
value: "svelte",
|
|
1219
|
+
label: "SvelteKit",
|
|
1220
|
+
hint: "Full-stack Svelte 5 app with Vite"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
value: "astro",
|
|
1224
|
+
label: "Astro",
|
|
1225
|
+
hint: "Content-oriented web app with server routes"
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
value: "nuxt",
|
|
1229
|
+
label: "Nuxt",
|
|
1230
|
+
hint: "Full-stack Vue app with Nitro server routes"
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
value: "tanstack-start",
|
|
1234
|
+
label: "TanStack Start",
|
|
1235
|
+
hint: "React app with file routes and server functions"
|
|
1236
|
+
}
|
|
1237
|
+
]
|
|
1238
|
+
});
|
|
1239
|
+
if (isCancel(template)) {
|
|
1240
|
+
cancel("Operation cancelled.");
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
return CreateTemplateSchema.parse(template);
|
|
1244
|
+
}
|
|
1245
|
+
async function inspectTargetPath(targetPath) {
|
|
1246
|
+
if (!await fs.pathExists(targetPath)) return {
|
|
1247
|
+
exists: false,
|
|
1248
|
+
isDirectory: true,
|
|
1249
|
+
isEmptyDirectory: true
|
|
1250
|
+
};
|
|
1251
|
+
if (!(await fs.stat(targetPath)).isDirectory()) return {
|
|
1252
|
+
exists: true,
|
|
1253
|
+
isDirectory: false,
|
|
1254
|
+
isEmptyDirectory: false
|
|
1255
|
+
};
|
|
1256
|
+
return {
|
|
1257
|
+
exists: true,
|
|
1258
|
+
isDirectory: true,
|
|
1259
|
+
isEmptyDirectory: (await fs.readdir(targetPath)).length === 0
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
async function runCreateCommand(rawInput = {}) {
|
|
1263
|
+
const startedAt = Date.now();
|
|
1264
|
+
let input = {};
|
|
1265
|
+
let context;
|
|
1266
|
+
let failureStage = "validate_input";
|
|
1267
|
+
try {
|
|
1268
|
+
input = CreateCommandInputSchema.parse(rawInput);
|
|
1269
|
+
if (!supportsPrismaNext()) {
|
|
1270
|
+
cancel(getUnsupportedNodeMessage());
|
|
1271
|
+
process.exitCode = 1;
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
intro(getCreatePrismaIntro());
|
|
1275
|
+
failureStage = "collect_context";
|
|
1276
|
+
context = await collectCreateContext(input);
|
|
1277
|
+
if (!context) return;
|
|
1278
|
+
failureStage = "unknown";
|
|
1279
|
+
const executionResult = await executeCreateContext(context);
|
|
1280
|
+
if (!executionResult.ok) {
|
|
1281
|
+
process.exitCode = 1;
|
|
1282
|
+
if (executionResult.error) cancel(`Create command failed: ${executionResult.error instanceof Error ? executionResult.error.message : String(executionResult.error)}`);
|
|
1283
|
+
await trackCreateFailed({
|
|
1284
|
+
input,
|
|
1285
|
+
context,
|
|
1286
|
+
durationMs: Date.now() - startedAt,
|
|
1287
|
+
error: executionResult.error,
|
|
1288
|
+
stage: executionResult.stage
|
|
1289
|
+
});
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
await trackCreateCompleted({
|
|
1293
|
+
input,
|
|
1294
|
+
context,
|
|
1295
|
+
durationMs: Date.now() - startedAt
|
|
1296
|
+
});
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
process.exitCode = 1;
|
|
1299
|
+
cancel(`Create command failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1300
|
+
await trackCreateFailed({
|
|
1301
|
+
input,
|
|
1302
|
+
context,
|
|
1303
|
+
durationMs: Date.now() - startedAt,
|
|
1304
|
+
error,
|
|
1305
|
+
stage: failureStage
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async function collectCreateContext(input) {
|
|
1310
|
+
const force = input.force === true;
|
|
1311
|
+
const useDefaults = input.yes === true;
|
|
1312
|
+
const projectNameInput = input.name ?? (useDefaults ? DEFAULT_PROJECT_NAME : await promptForProjectName());
|
|
1313
|
+
if (projectNameInput === void 0) return;
|
|
1314
|
+
const projectName = String(projectNameInput).trim();
|
|
1315
|
+
const projectNameValidationError = validateProjectName(projectName);
|
|
1316
|
+
if (projectNameValidationError) {
|
|
1317
|
+
cancel(projectNameValidationError);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
const template = input.template ?? (useDefaults ? DEFAULT_TEMPLATE : await promptForCreateTemplate());
|
|
1321
|
+
if (!template) return;
|
|
1322
|
+
const targetDirectory = path.resolve(process.cwd(), projectName);
|
|
1323
|
+
const targetPathState = await inspectTargetPath(targetDirectory);
|
|
1324
|
+
if (targetPathState.exists && !targetPathState.isDirectory) {
|
|
1325
|
+
cancel(`Target path ${formatPathForDisplay(targetDirectory)} already exists and is not a directory. Choose a different project name.`);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (targetPathState.exists && !targetPathState.isEmptyDirectory && !force) {
|
|
1329
|
+
cancel(`Target directory ${formatPathForDisplay(targetDirectory)} is not empty. Use --force to continue.`);
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const prismaSetupContext = await collectPrismaSetupContext(input, { projectDir: targetDirectory });
|
|
1333
|
+
if (!prismaSetupContext) return;
|
|
1334
|
+
return {
|
|
1335
|
+
targetDirectory,
|
|
1336
|
+
targetPathState,
|
|
1337
|
+
force,
|
|
1338
|
+
template,
|
|
1339
|
+
projectPackageName: toPackageName(path.basename(targetDirectory)),
|
|
1340
|
+
prismaSetupContext
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
async function executeCreateContext(context) {
|
|
1344
|
+
const createSpinner = context.prismaSetupContext.verbose ? void 0 : spinner();
|
|
1345
|
+
createSpinner?.start("Creating Prisma 8 project...");
|
|
1346
|
+
try {
|
|
1347
|
+
if (context.prismaSetupContext.verbose) log.step(`Scaffolding ${context.template} starter.`);
|
|
1348
|
+
await scaffoldCreateFrameworkTemplate({
|
|
1349
|
+
projectDir: context.targetDirectory,
|
|
1350
|
+
projectName: context.projectPackageName,
|
|
1351
|
+
template: context.template,
|
|
1352
|
+
provider: context.prismaSetupContext.databaseProvider,
|
|
1353
|
+
authoring: context.prismaSetupContext.authoring,
|
|
1354
|
+
packageManager: context.prismaSetupContext.packageManager
|
|
1355
|
+
});
|
|
1356
|
+
if (context.prismaSetupContext.verbose) log.success("Starter files scaffolded.");
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1359
|
+
return {
|
|
1360
|
+
ok: false,
|
|
1361
|
+
stage: "scaffold_template",
|
|
1362
|
+
error
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
await writeCreateTemplateDependencies({
|
|
1367
|
+
template: context.template,
|
|
1368
|
+
packageManager: context.prismaSetupContext.packageManager,
|
|
1369
|
+
projectDir: context.targetDirectory
|
|
1370
|
+
});
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1373
|
+
return {
|
|
1374
|
+
ok: false,
|
|
1375
|
+
stage: "scaffold_template",
|
|
1376
|
+
error
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
if (context.targetPathState.exists && !context.targetPathState.isEmptyDirectory && context.force) log.warn(`Used --force in non-empty directory ${formatPathForDisplay(context.targetDirectory)}.`);
|
|
1380
|
+
const nextSteps = formatPathForDisplay(context.targetDirectory) === "." ? [] : [{
|
|
1381
|
+
command: `cd ${formatPathForDisplay(context.targetDirectory)}`,
|
|
1382
|
+
description: "Enter your new project directory."
|
|
1383
|
+
}];
|
|
1384
|
+
try {
|
|
1385
|
+
if (!await executePrismaSetupContext(context.prismaSetupContext, {
|
|
1386
|
+
prependNextSteps: nextSteps,
|
|
1387
|
+
projectDir: context.targetDirectory,
|
|
1388
|
+
projectName: context.projectPackageName,
|
|
1389
|
+
template: context.template,
|
|
1390
|
+
createdProjectPath: context.targetDirectory,
|
|
1391
|
+
includeDevNextStep: true,
|
|
1392
|
+
progressSpinner: createSpinner
|
|
1393
|
+
})) return {
|
|
1394
|
+
ok: false,
|
|
1395
|
+
stage: "prisma_setup"
|
|
1396
|
+
};
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1399
|
+
return {
|
|
1400
|
+
ok: false,
|
|
1401
|
+
stage: "prisma_setup",
|
|
1402
|
+
error
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
return { ok: true };
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
//#endregion
|
|
1409
|
+
export { DatabaseProviderSchema as a, CreateTemplateSchema as i, AuthoringStyleSchema as n, PackageManagerSchema as o, CreateCommandInputSchema as r, runCreateCommand as t };
|