create-prisma 0.11.2 → 0.11.3
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/dist/cli.mjs +5 -57
- package/dist/index.d.mts +198 -334
- package/dist/index.mjs +65 -29
- package/dist/json-output-DzDD1nb9.mjs +2131 -0
- package/package.json +5 -8
- package/dist/create-TR_gtjND.mjs +0 -1987
package/dist/create-TR_gtjND.mjs
DELETED
|
@@ -1,1987 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, taskLog, 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 { Writable } from "node:stream";
|
|
14
|
-
import { createInterface } from "node:readline";
|
|
15
|
-
import { styleText } from "node:util";
|
|
16
|
-
|
|
17
|
-
//#region src/create-outcome.ts
|
|
18
|
-
var CreateCancellationError = class extends Error {
|
|
19
|
-
stage;
|
|
20
|
-
constructor(stage) {
|
|
21
|
-
super("Operation cancelled.");
|
|
22
|
-
this.name = "CreateCancellationError";
|
|
23
|
-
this.stage = stage;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var ClassifiedCreateError = class extends Error {
|
|
27
|
-
reason;
|
|
28
|
-
constructor(reason, message, options) {
|
|
29
|
-
super(message, options);
|
|
30
|
-
this.name = "ClassifiedCreateError";
|
|
31
|
-
this.reason = reason;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
function getCreateFailureReason(error, fallback) {
|
|
35
|
-
return error instanceof ClassifiedCreateError ? error.reason : fallback;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/result.ts
|
|
40
|
-
const CREATE_PRISMA_RESULT_SCHEMA_VERSION = 1;
|
|
41
|
-
function createCommandFailureResult(stage, message, project) {
|
|
42
|
-
return {
|
|
43
|
-
schemaVersion: CREATE_PRISMA_RESULT_SCHEMA_VERSION,
|
|
44
|
-
ok: false,
|
|
45
|
-
error: {
|
|
46
|
-
stage,
|
|
47
|
-
message
|
|
48
|
-
},
|
|
49
|
-
...project ? { project } : {}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
//#endregion
|
|
54
|
-
//#region src/telemetry/client.ts
|
|
55
|
-
const TELEMETRY_API_KEY = "phc_cmc85avbWyuJ2JyKdGPdv7dxXli8xLdWDBPbvIXWJfs";
|
|
56
|
-
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
57
|
-
const TELEMETRY_CONFIG_FILE = "telemetry.json";
|
|
58
|
-
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;
|
|
59
|
-
function isTruthyEnvValue(value) {
|
|
60
|
-
return [
|
|
61
|
-
"1",
|
|
62
|
-
"true",
|
|
63
|
-
"yes",
|
|
64
|
-
"on"
|
|
65
|
-
].includes(String(value ?? "").trim().toLowerCase());
|
|
66
|
-
}
|
|
67
|
-
function shouldDisableTelemetry() {
|
|
68
|
-
if (isTruthyEnvValue(process.env.CI) || isTruthyEnvValue(process.env.GITHUB_ACTIONS)) return true;
|
|
69
|
-
return process.env.CREATE_PRISMA_DISABLE_TELEMETRY !== void 0 || process.env.CREATE_PRISMA_TELEMETRY_DISABLED !== void 0 || process.env.DO_NOT_TRACK !== void 0;
|
|
70
|
-
}
|
|
71
|
-
function getTelemetryConfigDir() {
|
|
72
|
-
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "create-prisma");
|
|
73
|
-
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "create-prisma");
|
|
74
|
-
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "create-prisma");
|
|
75
|
-
}
|
|
76
|
-
async function getAnonymousId() {
|
|
77
|
-
const telemetryConfigPath = path.join(getTelemetryConfigDir(), TELEMETRY_CONFIG_FILE);
|
|
78
|
-
try {
|
|
79
|
-
const config = await fs.readJSON(telemetryConfigPath);
|
|
80
|
-
if (typeof config.anonymousId === "string" && UUID_V4_REGEX.test(config.anonymousId)) return config.anonymousId;
|
|
81
|
-
} catch {}
|
|
82
|
-
const anonymousId = randomUUID();
|
|
83
|
-
try {
|
|
84
|
-
await fs.ensureDir(path.dirname(telemetryConfigPath));
|
|
85
|
-
await fs.writeJSON(telemetryConfigPath, { anonymousId }, { spaces: 2 });
|
|
86
|
-
} catch {}
|
|
87
|
-
return anonymousId;
|
|
88
|
-
}
|
|
89
|
-
function getCommonProperties() {
|
|
90
|
-
return {
|
|
91
|
-
"cli-version": "0.11.2",
|
|
92
|
-
"node-version": process.version,
|
|
93
|
-
platform: process.platform,
|
|
94
|
-
arch: process.arch
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function sanitizeProperties(properties) {
|
|
98
|
-
return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0));
|
|
99
|
-
}
|
|
100
|
-
async function trackCliTelemetry(event, properties) {
|
|
101
|
-
if (shouldDisableTelemetry()) return;
|
|
102
|
-
let client;
|
|
103
|
-
try {
|
|
104
|
-
const distinctId = await getAnonymousId();
|
|
105
|
-
const sanitizedProperties = sanitizeProperties({
|
|
106
|
-
...getCommonProperties(),
|
|
107
|
-
...properties,
|
|
108
|
-
$process_person_profile: false
|
|
109
|
-
});
|
|
110
|
-
client = new PostHog(TELEMETRY_API_KEY, {
|
|
111
|
-
host: TELEMETRY_HOST,
|
|
112
|
-
disableGeoip: true,
|
|
113
|
-
flushAt: 1,
|
|
114
|
-
flushInterval: 0
|
|
115
|
-
});
|
|
116
|
-
await client.captureImmediate({
|
|
117
|
-
distinctId,
|
|
118
|
-
event,
|
|
119
|
-
properties: sanitizedProperties,
|
|
120
|
-
disableGeoip: true
|
|
121
|
-
});
|
|
122
|
-
} catch {} finally {
|
|
123
|
-
if (client) await client.shutdown().catch(() => {});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region src/telemetry/create.ts
|
|
129
|
-
const CREATE_PRISMA_NEXT_COMPLETED_EVENT = "cli:create_prisma_next_command_completed";
|
|
130
|
-
const CREATE_PRISMA_NEXT_FAILED_EVENT = "cli:create_prisma_next_command_failed";
|
|
131
|
-
const CREATE_PRISMA_NEXT_CANCELLED_EVENT = "cli:create_prisma_next_command_cancelled";
|
|
132
|
-
const expectedRejectionReasons = new Set([
|
|
133
|
-
"invalid_input",
|
|
134
|
-
"unsupported_node_version",
|
|
135
|
-
"invalid_project_name",
|
|
136
|
-
"target_path_not_directory",
|
|
137
|
-
"target_directory_not_empty",
|
|
138
|
-
"unsupported_configuration",
|
|
139
|
-
"not_authenticated",
|
|
140
|
-
"workspace_missing",
|
|
141
|
-
"workspace_mismatch",
|
|
142
|
-
"project_name_collision"
|
|
143
|
-
]);
|
|
144
|
-
function getFailureClass(reason) {
|
|
145
|
-
return expectedRejectionReasons.has(reason) ? "expected_rejection" : "technical_failure";
|
|
146
|
-
}
|
|
147
|
-
function getTargetDirectoryState(context) {
|
|
148
|
-
if (!context.targetPathState.exists) return "new";
|
|
149
|
-
if (context.targetPathState.isEmptyDirectory) return "empty_directory";
|
|
150
|
-
return "non_empty_directory";
|
|
151
|
-
}
|
|
152
|
-
function getBaseCreateProperties(input, context) {
|
|
153
|
-
return {
|
|
154
|
-
"telemetry-schema-version": 2,
|
|
155
|
-
command: "create",
|
|
156
|
-
"uses-defaults": input.yes === true || input.json === true,
|
|
157
|
-
json: input.json === true,
|
|
158
|
-
verbose: input.verbose === true,
|
|
159
|
-
force: input.force === true,
|
|
160
|
-
template: context?.template ?? input.template ?? null,
|
|
161
|
-
"database-provider": context?.prismaSetupContext.databaseProvider ?? input.provider ?? null,
|
|
162
|
-
"authoring-style": context?.prismaSetupContext.authoring ?? input.authoring ?? null,
|
|
163
|
-
"package-manager": context?.prismaSetupContext.packageManager ?? input.packageManager ?? null,
|
|
164
|
-
"should-deploy": context?.prismaSetupContext.shouldDeploy ?? input.deploy ?? null,
|
|
165
|
-
"target-directory-state": context ? getTargetDirectoryState(context) : null
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function getErrorName(error) {
|
|
169
|
-
if (error instanceof Error) return error.name;
|
|
170
|
-
return error === void 0 ? null : "UnknownError";
|
|
171
|
-
}
|
|
172
|
-
function getErrorCode(error) {
|
|
173
|
-
if (typeof error !== "object" || error === null) return null;
|
|
174
|
-
const exitCode = Reflect.get(error, "exitCode");
|
|
175
|
-
if (typeof exitCode === "number") return exitCode;
|
|
176
|
-
const code = Reflect.get(error, "code");
|
|
177
|
-
return typeof code === "number" || typeof code === "string" ? code : null;
|
|
178
|
-
}
|
|
179
|
-
function getPrismaCliFailureProperty(error, property) {
|
|
180
|
-
if (typeof error !== "object" || error === null) return null;
|
|
181
|
-
const value = Reflect.get(error, property);
|
|
182
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
183
|
-
}
|
|
184
|
-
async function trackCreateCompleted(params) {
|
|
185
|
-
await trackCliTelemetry(CREATE_PRISMA_NEXT_COMPLETED_EVENT, {
|
|
186
|
-
...getBaseCreateProperties(params.input, params.context),
|
|
187
|
-
"duration-ms": params.durationMs
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
async function trackCreateFailed(params) {
|
|
191
|
-
await trackCliTelemetry(CREATE_PRISMA_NEXT_FAILED_EVENT, {
|
|
192
|
-
...getBaseCreateProperties(params.input, params.context),
|
|
193
|
-
"duration-ms": params.durationMs,
|
|
194
|
-
"failure-class": getFailureClass(params.reason),
|
|
195
|
-
"failure-stage": params.stage,
|
|
196
|
-
"failure-reason": params.reason,
|
|
197
|
-
"error-name": getErrorName(params.error),
|
|
198
|
-
"error-code": getErrorCode(params.error),
|
|
199
|
-
"prisma-cli-command": getPrismaCliFailureProperty(params.error, "prismaCliCommand"),
|
|
200
|
-
"prisma-cli-error-code": getPrismaCliFailureProperty(params.error, "prismaCliErrorCode")
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
async function trackCreateCancelled(params) {
|
|
204
|
-
await trackCliTelemetry(CREATE_PRISMA_NEXT_CANCELLED_EVENT, {
|
|
205
|
-
...getBaseCreateProperties(params.input, params.context),
|
|
206
|
-
"duration-ms": params.durationMs,
|
|
207
|
-
"cancellation-stage": params.stage
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
//#endregion
|
|
212
|
-
//#region src/types.ts
|
|
213
|
-
const databaseProviderInputs = [
|
|
214
|
-
"postgres",
|
|
215
|
-
"postgresql",
|
|
216
|
-
"mongo",
|
|
217
|
-
"mongodb"
|
|
218
|
-
];
|
|
219
|
-
const packageManagers = [
|
|
220
|
-
"npm",
|
|
221
|
-
"pnpm",
|
|
222
|
-
"yarn",
|
|
223
|
-
"bun",
|
|
224
|
-
"deno"
|
|
225
|
-
];
|
|
226
|
-
const authoringStyles = ["psl", "typescript"];
|
|
227
|
-
const createTemplates = [
|
|
228
|
-
"minimal",
|
|
229
|
-
"hono",
|
|
230
|
-
"elysia",
|
|
231
|
-
"nest",
|
|
232
|
-
"next",
|
|
233
|
-
"svelte",
|
|
234
|
-
"astro",
|
|
235
|
-
"nuxt",
|
|
236
|
-
"tanstack-start"
|
|
237
|
-
];
|
|
238
|
-
function normalizeDatabaseProvider(value) {
|
|
239
|
-
if (value === "postgresql") return "postgres";
|
|
240
|
-
if (value === "mongodb") return "mongo";
|
|
241
|
-
return value;
|
|
242
|
-
}
|
|
243
|
-
const DatabaseProviderSchema = z.enum(databaseProviderInputs).transform(normalizeDatabaseProvider);
|
|
244
|
-
const PackageManagerSchema = z.enum(packageManagers);
|
|
245
|
-
const AuthoringStyleSchema = z.enum(authoringStyles);
|
|
246
|
-
const CreateTemplateSchema = z.enum(createTemplates);
|
|
247
|
-
const CommonCommandOptionsSchema = z.object({
|
|
248
|
-
yes: z.boolean().optional().describe("Skip prompts and accept default choices"),
|
|
249
|
-
verbose: z.boolean().optional().describe("Show verbose command output during setup"),
|
|
250
|
-
json: z.boolean().optional().describe("Emit one quiet JSON result for agents and automation (non-interactive; deploys unless --no-deploy)")
|
|
251
|
-
});
|
|
252
|
-
const PrismaSetupOptionsSchema = z.object({
|
|
253
|
-
provider: DatabaseProviderSchema.optional().describe("Prisma 8 database target: PostgreSQL relational models or MongoDB document models"),
|
|
254
|
-
authoring: AuthoringStyleSchema.optional().describe("Contract authoring style"),
|
|
255
|
-
packageManager: PackageManagerSchema.optional().describe("Package manager used for dependency installation"),
|
|
256
|
-
deploy: z.boolean().optional().describe("Deploy the generated app to Prisma immediately"),
|
|
257
|
-
workspace: z.string().trim().min(1, "Please enter a valid workspace id or name").optional().describe("Prisma workspace id or name to deploy into")
|
|
258
|
-
});
|
|
259
|
-
const PrismaSetupCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
|
|
260
|
-
const CreateScaffoldOptionsSchema = z.object({
|
|
261
|
-
name: z.string().trim().min(1, "Please enter a valid project name").optional().describe("Project name / directory"),
|
|
262
|
-
template: CreateTemplateSchema.optional().describe("Project template"),
|
|
263
|
-
force: z.boolean().optional().describe("Allow scaffolding into a non-empty target directory")
|
|
264
|
-
});
|
|
265
|
-
const CreateCommandInputSchema = PrismaSetupCommandInputSchema.extend(CreateScaffoldOptionsSchema.shape);
|
|
266
|
-
|
|
267
|
-
//#endregion
|
|
268
|
-
//#region src/utils/package-manager.ts
|
|
269
|
-
const DENO_ALLOW_FRESH_DEPENDENCIES = "--minimum-dependency-age=0";
|
|
270
|
-
const packageManagerManifestValues = {
|
|
271
|
-
npm: "npm@10.9.0",
|
|
272
|
-
pnpm: "pnpm@11.21.0",
|
|
273
|
-
yarn: "yarn@4.13.0",
|
|
274
|
-
bun: "bun@1.3.9"
|
|
275
|
-
};
|
|
276
|
-
function parseUserAgent(userAgent) {
|
|
277
|
-
if (userAgent?.startsWith("pnpm")) return "pnpm";
|
|
278
|
-
if (userAgent?.startsWith("yarn")) return "yarn";
|
|
279
|
-
if (userAgent?.startsWith("bun")) return "bun";
|
|
280
|
-
if (userAgent?.startsWith("deno")) return "deno";
|
|
281
|
-
if (userAgent?.startsWith("npm")) return "npm";
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
function parsePackageManagerField(packageManagerField) {
|
|
285
|
-
if (typeof packageManagerField !== "string" || packageManagerField.length === 0) return null;
|
|
286
|
-
const managerName = packageManagerField.split("@")[0];
|
|
287
|
-
const parsed = PackageManagerSchema.safeParse(managerName);
|
|
288
|
-
return parsed.success ? parsed.data : null;
|
|
289
|
-
}
|
|
290
|
-
async function detectFromPackageJson(projectDir) {
|
|
291
|
-
const packageJsonPath = path.join(projectDir, "package.json");
|
|
292
|
-
if (!await fs.pathExists(packageJsonPath)) return null;
|
|
293
|
-
return parsePackageManagerField((await fs.readJson(packageJsonPath)).packageManager);
|
|
294
|
-
}
|
|
295
|
-
async function detectFromDenoConfig(projectDir) {
|
|
296
|
-
for (const configFile of ["deno.json", "deno.jsonc"]) if (await fs.pathExists(path.join(projectDir, configFile))) return "deno";
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
async function detectFromLockfile(projectDir) {
|
|
300
|
-
for (const check of [
|
|
301
|
-
{
|
|
302
|
-
manager: "pnpm",
|
|
303
|
-
lockfile: "pnpm-lock.yaml"
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
manager: "yarn",
|
|
307
|
-
lockfile: "yarn.lock"
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
manager: "bun",
|
|
311
|
-
lockfile: "bun.lockb"
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
manager: "bun",
|
|
315
|
-
lockfile: "bun.lock"
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
manager: "npm",
|
|
319
|
-
lockfile: "package-lock.json"
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
manager: "npm",
|
|
323
|
-
lockfile: "npm-shrinkwrap.json"
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
manager: "deno",
|
|
327
|
-
lockfile: "deno.lock"
|
|
328
|
-
}
|
|
329
|
-
]) if (await fs.pathExists(path.join(projectDir, check.lockfile))) return check.manager;
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
async function detectPackageManager(projectDir = process.cwd()) {
|
|
333
|
-
const fromPackageJson = await detectFromPackageJson(projectDir);
|
|
334
|
-
if (fromPackageJson) return fromPackageJson;
|
|
335
|
-
const fromLockfile = await detectFromLockfile(projectDir);
|
|
336
|
-
if (fromLockfile) return fromLockfile;
|
|
337
|
-
const fromDenoConfig = await detectFromDenoConfig(projectDir);
|
|
338
|
-
if (fromDenoConfig) return fromDenoConfig;
|
|
339
|
-
const fromUserAgent = parseUserAgent(process.env.npm_config_user_agent);
|
|
340
|
-
if (fromUserAgent) return fromUserAgent;
|
|
341
|
-
return "npm";
|
|
342
|
-
}
|
|
343
|
-
function getPackageManagerManifestValue(packageManager) {
|
|
344
|
-
if (!packageManager) return;
|
|
345
|
-
if (packageManager === "deno") return;
|
|
346
|
-
return packageManagerManifestValues[packageManager];
|
|
347
|
-
}
|
|
348
|
-
function getInstallCommand(packageManager) {
|
|
349
|
-
if (packageManager === "deno") return "deno install";
|
|
350
|
-
return `${packageManager} install`;
|
|
351
|
-
}
|
|
352
|
-
function getRunScriptCommand(packageManager, scriptName) {
|
|
353
|
-
switch (packageManager) {
|
|
354
|
-
case "deno": return `deno task ${scriptName}`;
|
|
355
|
-
case "bun": return `bun run ${scriptName}`;
|
|
356
|
-
case "pnpm": return `pnpm run ${scriptName}`;
|
|
357
|
-
case "yarn": return `yarn run ${scriptName}`;
|
|
358
|
-
default: return `npm run ${scriptName}`;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function getRuntimeScriptCommand(packageManager, kind, options) {
|
|
362
|
-
const { sourceEntrypoint, builtEntrypoint } = options;
|
|
363
|
-
if (packageManager === "deno") switch (kind) {
|
|
364
|
-
case "dev": return `deno run -A --env-file=.env --watch ${sourceEntrypoint}`;
|
|
365
|
-
case "build": return `deno check ${sourceEntrypoint}`;
|
|
366
|
-
case "start": return `deno run -A --env-file=.env ${sourceEntrypoint}`;
|
|
367
|
-
}
|
|
368
|
-
if (packageManager === "bun") switch (kind) {
|
|
369
|
-
case "dev": return `bun --watch ${sourceEntrypoint}`;
|
|
370
|
-
case "build": return "tsc --noEmit";
|
|
371
|
-
case "start": return `bun ${sourceEntrypoint}`;
|
|
372
|
-
}
|
|
373
|
-
switch (kind) {
|
|
374
|
-
case "dev": return `tsx watch ${sourceEntrypoint}`;
|
|
375
|
-
case "build": return "tsc";
|
|
376
|
-
case "start": return builtEntrypoint ? `node ${builtEntrypoint}` : `tsx ${sourceEntrypoint}`;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
function getInstallArgs(packageManager) {
|
|
380
|
-
if (packageManager === "deno") return {
|
|
381
|
-
command: "deno",
|
|
382
|
-
args: ["install", DENO_ALLOW_FRESH_DEPENDENCIES]
|
|
383
|
-
};
|
|
384
|
-
return {
|
|
385
|
-
command: packageManager,
|
|
386
|
-
args: ["install"]
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function getPackageExecutionArgs(packageManager, commandArgs) {
|
|
390
|
-
switch (packageManager) {
|
|
391
|
-
case "deno": {
|
|
392
|
-
const [packageName, ...args] = commandArgs;
|
|
393
|
-
if (!packageName) throw new Error("Package execution requires a package name.");
|
|
394
|
-
return {
|
|
395
|
-
command: "deno",
|
|
396
|
-
args: [
|
|
397
|
-
"run",
|
|
398
|
-
"-A",
|
|
399
|
-
DENO_ALLOW_FRESH_DEPENDENCIES,
|
|
400
|
-
`npm:${packageName}`,
|
|
401
|
-
...args
|
|
402
|
-
]
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
case "pnpm": return {
|
|
406
|
-
command: "pnpm",
|
|
407
|
-
args: ["dlx", ...commandArgs]
|
|
408
|
-
};
|
|
409
|
-
case "yarn": return {
|
|
410
|
-
command: "yarn",
|
|
411
|
-
args: ["dlx", ...commandArgs]
|
|
412
|
-
};
|
|
413
|
-
case "bun": return {
|
|
414
|
-
command: "bunx",
|
|
415
|
-
args: [...commandArgs]
|
|
416
|
-
};
|
|
417
|
-
default: return {
|
|
418
|
-
command: "npx",
|
|
419
|
-
args: ["--yes", ...commandArgs]
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
function getPackageExecutionCommand(packageManager, commandArgs) {
|
|
424
|
-
const execution = getPackageExecutionArgs(packageManager, commandArgs);
|
|
425
|
-
return [execution.command, ...execution.args].join(" ");
|
|
426
|
-
}
|
|
427
|
-
function getRunScriptArgs(packageManager, scriptName) {
|
|
428
|
-
switch (packageManager) {
|
|
429
|
-
case "deno": return {
|
|
430
|
-
command: "deno",
|
|
431
|
-
args: ["task", scriptName]
|
|
432
|
-
};
|
|
433
|
-
case "bun": return {
|
|
434
|
-
command: "bun",
|
|
435
|
-
args: ["run", scriptName]
|
|
436
|
-
};
|
|
437
|
-
case "pnpm": return {
|
|
438
|
-
command: "pnpm",
|
|
439
|
-
args: ["run", scriptName]
|
|
440
|
-
};
|
|
441
|
-
case "yarn": return {
|
|
442
|
-
command: "yarn",
|
|
443
|
-
args: ["run", scriptName]
|
|
444
|
-
};
|
|
445
|
-
default: return {
|
|
446
|
-
command: "npm",
|
|
447
|
-
args: ["run", scriptName]
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
//#endregion
|
|
453
|
-
//#region src/templates/shared.ts
|
|
454
|
-
Handlebars.registerHelper("eq", (left, right) => left === right);
|
|
455
|
-
Handlebars.registerHelper("runScriptCommand", (packageManager, scriptName) => packageManager ? getRunScriptCommand(packageManager, scriptName) : "");
|
|
456
|
-
Handlebars.registerHelper("packageManagerManifestValue", (packageManager) => getPackageManagerManifestValue(packageManager) ?? "");
|
|
457
|
-
Handlebars.registerHelper("runtimeScript", (packageManager, kind, sourceEntrypoint, builtEntrypoint, _options) => {
|
|
458
|
-
if (!packageManager) return "";
|
|
459
|
-
return getRuntimeScriptCommand(packageManager, kind, {
|
|
460
|
-
sourceEntrypoint,
|
|
461
|
-
builtEntrypoint
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
function findPackageRoot(startDir) {
|
|
465
|
-
let currentDir = startDir;
|
|
466
|
-
while (true) {
|
|
467
|
-
if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
|
|
468
|
-
const parentDir = path.dirname(currentDir);
|
|
469
|
-
if (parentDir === currentDir) break;
|
|
470
|
-
currentDir = parentDir;
|
|
471
|
-
}
|
|
472
|
-
throw new Error(`Unable to locate package root from: ${startDir}`);
|
|
473
|
-
}
|
|
474
|
-
function resolveTemplatesDir(relativeTemplatesDir) {
|
|
475
|
-
const currentFilePath = fileURLToPath(import.meta.url);
|
|
476
|
-
const packageRoot = findPackageRoot(path.dirname(currentFilePath));
|
|
477
|
-
const templatePath = path.join(packageRoot, relativeTemplatesDir);
|
|
478
|
-
if (!existsSync(templatePath)) throw new Error(`Template directory not found at: ${templatePath}`);
|
|
479
|
-
return templatePath;
|
|
480
|
-
}
|
|
481
|
-
async function getTemplateFilesRecursively(dir) {
|
|
482
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
483
|
-
return (await Promise.all(entries.map(async (entry) => {
|
|
484
|
-
const entryPath = path.join(dir, entry.name);
|
|
485
|
-
if (entry.isDirectory()) return getTemplateFilesRecursively(entryPath);
|
|
486
|
-
if (!entry.isFile()) return [];
|
|
487
|
-
return [entryPath];
|
|
488
|
-
}))).flat();
|
|
489
|
-
}
|
|
490
|
-
function stripHbsExtension(filePath) {
|
|
491
|
-
return filePath.endsWith(".hbs") ? filePath.slice(0, -4) : filePath;
|
|
492
|
-
}
|
|
493
|
-
function ensureTrailingNewline(content) {
|
|
494
|
-
return content.endsWith("\n") ? content : `${content}\n`;
|
|
495
|
-
}
|
|
496
|
-
async function renderTemplateFile(opts) {
|
|
497
|
-
const { templateFilePath, outputPath, context } = opts;
|
|
498
|
-
const templateContent = await fs.readFile(templateFilePath, "utf8");
|
|
499
|
-
const outputContent = templateFilePath.endsWith(".hbs") ? Handlebars.compile(templateContent, {
|
|
500
|
-
noEscape: true,
|
|
501
|
-
strict: true
|
|
502
|
-
})(context) : templateContent;
|
|
503
|
-
if (templateFilePath.endsWith(".hbs") && outputContent.trim().length === 0) return;
|
|
504
|
-
await fs.outputFile(outputPath, ensureTrailingNewline(outputContent), "utf8");
|
|
505
|
-
}
|
|
506
|
-
async function renderTemplateTree(opts) {
|
|
507
|
-
const { templateRoot, outputDir, context } = opts;
|
|
508
|
-
const templateFiles = await getTemplateFilesRecursively(templateRoot);
|
|
509
|
-
for (const templateFilePath of templateFiles) {
|
|
510
|
-
const relativeOutputPath = stripHbsExtension(path.relative(templateRoot, templateFilePath));
|
|
511
|
-
await renderTemplateFile({
|
|
512
|
-
templateFilePath,
|
|
513
|
-
outputPath: path.join(outputDir, relativeOutputPath),
|
|
514
|
-
context
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
//#endregion
|
|
520
|
-
//#region src/templates/render-create-template.ts
|
|
521
|
-
const tsdownEntries = {
|
|
522
|
-
minimal: "src/index.ts",
|
|
523
|
-
hono: "src/index.ts",
|
|
524
|
-
elysia: "src/index.ts",
|
|
525
|
-
nest: "src/main.ts"
|
|
526
|
-
};
|
|
527
|
-
function getCreateTemplateDir(template) {
|
|
528
|
-
return resolveTemplatesDir(`templates/create/${template}`);
|
|
529
|
-
}
|
|
530
|
-
function getCreateSharedTemplateDir() {
|
|
531
|
-
return resolveTemplatesDir("templates/create/_shared");
|
|
532
|
-
}
|
|
533
|
-
function createTemplateContext(projectName, template, provider, authoring, packageManager) {
|
|
534
|
-
return {
|
|
535
|
-
projectName,
|
|
536
|
-
template,
|
|
537
|
-
provider,
|
|
538
|
-
authoring,
|
|
539
|
-
packageManager,
|
|
540
|
-
tsdownEntry: tsdownEntries[template] ?? null
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
async function scaffoldCreateSharedTemplates(opts) {
|
|
544
|
-
const { projectDir, projectName, template, provider, authoring, packageManager } = opts;
|
|
545
|
-
await renderTemplateTree({
|
|
546
|
-
templateRoot: getCreateSharedTemplateDir(),
|
|
547
|
-
outputDir: projectDir,
|
|
548
|
-
context: createTemplateContext(projectName, template, provider, authoring, packageManager)
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
async function scaffoldCreateFrameworkTemplate(opts) {
|
|
552
|
-
const { projectDir, projectName, template, provider, authoring, packageManager } = opts;
|
|
553
|
-
await renderTemplateTree({
|
|
554
|
-
templateRoot: getCreateTemplateDir(template),
|
|
555
|
-
outputDir: projectDir,
|
|
556
|
-
context: createTemplateContext(projectName, template, provider, authoring, packageManager)
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region src/constants/dependencies.ts
|
|
562
|
-
const dependencyVersionMap = {
|
|
563
|
-
"@astrojs/node": "^10.0.2",
|
|
564
|
-
"@elysiajs/node": "^1.4.5",
|
|
565
|
-
"@prisma/composer": "0.16.0",
|
|
566
|
-
"@prisma/composer-prisma-cloud": "0.16.0",
|
|
567
|
-
"@prisma/orm-mongo": "8.0.0-rc.8",
|
|
568
|
-
"@prisma/orm-postgres": "8.0.0-rc.8",
|
|
569
|
-
"@sveltejs/adapter-node": "^5.3.2",
|
|
570
|
-
"@types/node": "^25.6.2",
|
|
571
|
-
alchemy: "2.0.0-beta.74",
|
|
572
|
-
arktype: "^2.2.3",
|
|
573
|
-
dotenv: "^17.4.2",
|
|
574
|
-
effect: "4.0.0-rc.112",
|
|
575
|
-
mongodb: "^7.1.0",
|
|
576
|
-
"mongodb-memory-server": "^11.1.0",
|
|
577
|
-
nitro: "^3.0.260610-beta",
|
|
578
|
-
prisma: "latest",
|
|
579
|
-
"temporal-polyfill": "^1.0.4",
|
|
580
|
-
tsdown: "^0.22.14",
|
|
581
|
-
tsx: "^4.21.0",
|
|
582
|
-
typescript: "^5.9.3"
|
|
583
|
-
};
|
|
584
|
-
const PRISMA_PLATFORM_CLI_PACKAGE = "prisma@latest";
|
|
585
|
-
const PRISMA_DENO_CLI_PACKAGE = PRISMA_PLATFORM_CLI_PACKAGE;
|
|
586
|
-
function getDependencyVersion(packageName) {
|
|
587
|
-
return dependencyVersionMap[packageName];
|
|
588
|
-
}
|
|
589
|
-
function usesTsdown(template) {
|
|
590
|
-
return template === "minimal" || template === "hono" || template === "elysia" || template === "nest";
|
|
591
|
-
}
|
|
592
|
-
function getCreateTemplateDependencies(template, _packageManager) {
|
|
593
|
-
const dependencies = [
|
|
594
|
-
"@prisma/composer",
|
|
595
|
-
"@prisma/composer-prisma-cloud",
|
|
596
|
-
"alchemy"
|
|
597
|
-
];
|
|
598
|
-
const devDependencies = [];
|
|
599
|
-
if (usesTsdown(template)) {
|
|
600
|
-
devDependencies.push("tsdown");
|
|
601
|
-
devDependencies.push("tsx");
|
|
602
|
-
}
|
|
603
|
-
if (template === "minimal") devDependencies.push("typescript");
|
|
604
|
-
if (template === "elysia") {
|
|
605
|
-
dependencies.push("@elysiajs/node");
|
|
606
|
-
devDependencies.push("@types/node");
|
|
607
|
-
}
|
|
608
|
-
if (template === "svelte") devDependencies.push("@sveltejs/adapter-node");
|
|
609
|
-
if (template === "astro") dependencies.push("@astrojs/node");
|
|
610
|
-
if (template === "tanstack-start") devDependencies.push("nitro");
|
|
611
|
-
return [{
|
|
612
|
-
packageJsonPath: "package.json",
|
|
613
|
-
dependencies,
|
|
614
|
-
devDependencies
|
|
615
|
-
}];
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
//#endregion
|
|
619
|
-
//#region src/constants/db-packages.ts
|
|
620
|
-
function getDbPackages(provider) {
|
|
621
|
-
switch (provider) {
|
|
622
|
-
case "postgres": return "@prisma/orm-postgres";
|
|
623
|
-
case "mongo": return "@prisma/orm-mongo";
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
//#endregion
|
|
628
|
-
//#region src/utils/run-command.ts
|
|
629
|
-
/**
|
|
630
|
-
* Runs a setup command without allowing child output to corrupt structured CLI output.
|
|
631
|
-
* Human verbose mode keeps native streaming; JSON mode always captures child output.
|
|
632
|
-
*/
|
|
633
|
-
async function runSetupCommand(options) {
|
|
634
|
-
const shouldInheritOutput = options.verbose && !options.json;
|
|
635
|
-
await execa(options.command, options.args, {
|
|
636
|
-
cwd: options.cwd,
|
|
637
|
-
env: options.env,
|
|
638
|
-
stdio: shouldInheritOutput ? "inherit" : "pipe"
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
//#endregion
|
|
643
|
-
//#region src/tasks/install.ts
|
|
644
|
-
function getPrismaScriptMap(packageManager) {
|
|
645
|
-
if (packageManager === "deno") {
|
|
646
|
-
const prismaCommand = (needsDatabase, ...args) => [
|
|
647
|
-
"deno run -A",
|
|
648
|
-
...needsDatabase ? ["--env-file=.env"] : [],
|
|
649
|
-
`npm:${PRISMA_DENO_CLI_PACKAGE}`,
|
|
650
|
-
...args
|
|
651
|
-
].join(" ");
|
|
652
|
-
return {
|
|
653
|
-
"contract:emit": prismaCommand(false, "contract", "emit"),
|
|
654
|
-
"db:init": prismaCommand(true, "db", "init"),
|
|
655
|
-
"db:update": prismaCommand(true, "db", "update"),
|
|
656
|
-
"db:verify": prismaCommand(true, "db", "verify"),
|
|
657
|
-
"migration:plan": prismaCommand(true, "migration", "plan"),
|
|
658
|
-
migrate: prismaCommand(true, "db", "migrate"),
|
|
659
|
-
"migration:status": prismaCommand(true, "migration", "status"),
|
|
660
|
-
"migration:show": prismaCommand(true, "migration", "show")
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
const prismaCommand = (...args) => ["prisma", ...args].join(" ");
|
|
664
|
-
return {
|
|
665
|
-
"contract:emit": prismaCommand("contract", "emit"),
|
|
666
|
-
"db:init": prismaCommand("db", "init"),
|
|
667
|
-
"db:update": prismaCommand("db", "update"),
|
|
668
|
-
"db:verify": prismaCommand("db", "verify"),
|
|
669
|
-
"migration:plan": prismaCommand("migration", "plan"),
|
|
670
|
-
migrate: prismaCommand("db", "migrate"),
|
|
671
|
-
"migration:status": prismaCommand("migration", "status"),
|
|
672
|
-
"migration:show": prismaCommand("migration", "show"),
|
|
673
|
-
"skills:sync": `${prismaCommand("skills", "sync")} || exit 0`
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
function getComposerScriptMap(packageManager) {
|
|
677
|
-
if (packageManager === "deno") return {};
|
|
678
|
-
const composerCommand = (subcommand) => [
|
|
679
|
-
"prisma",
|
|
680
|
-
subcommand,
|
|
681
|
-
"module.ts"
|
|
682
|
-
].join(" ");
|
|
683
|
-
return {
|
|
684
|
-
"composer:dev": composerCommand("dev"),
|
|
685
|
-
"composer:deploy": composerCommand("deploy"),
|
|
686
|
-
deploy: `${getRunScriptCommand(packageManager, "build")} && ${getRunScriptCommand(packageManager, "composer:deploy")}`,
|
|
687
|
-
"dev:composer": `${getRunScriptCommand(packageManager, "build")} && ${getRunScriptCommand(packageManager, "composer:dev")}`
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
function unique(items) {
|
|
691
|
-
return [...new Set(items)];
|
|
692
|
-
}
|
|
693
|
-
function sortRecord(record) {
|
|
694
|
-
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
695
|
-
}
|
|
696
|
-
async function addPackageDependency(opts) {
|
|
697
|
-
const { dependencies = [], devDependencies = [], customDependencies = {}, scripts = {}, scriptMode, projectDir } = opts;
|
|
698
|
-
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
699
|
-
if (!await fs.pathExists(pkgJsonPath)) throw new Error(`No package.json found in ${projectDir}. Run this command inside an existing JavaScript/TypeScript project.`);
|
|
700
|
-
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
701
|
-
pkgJson.dependencies ??= {};
|
|
702
|
-
pkgJson.devDependencies ??= {};
|
|
703
|
-
pkgJson.scripts ??= {};
|
|
704
|
-
for (const packageName of unique(dependencies)) {
|
|
705
|
-
const version = getDependencyVersion(packageName);
|
|
706
|
-
if (!version) throw new Error(`Dependency ${packageName} is missing from the version map.`);
|
|
707
|
-
pkgJson.dependencies[packageName] = version;
|
|
708
|
-
}
|
|
709
|
-
for (const packageName of unique(devDependencies)) {
|
|
710
|
-
const version = getDependencyVersion(packageName);
|
|
711
|
-
if (!version) throw new Error(`Dependency ${packageName} is missing from the version map.`);
|
|
712
|
-
pkgJson.devDependencies[packageName] = version;
|
|
713
|
-
}
|
|
714
|
-
for (const [packageName, version] of Object.entries(customDependencies)) pkgJson.dependencies[packageName] = version;
|
|
715
|
-
for (const [scriptName, command] of Object.entries(scripts)) {
|
|
716
|
-
if (scriptMode === "if-missing" && typeof pkgJson.scripts[scriptName] === "string" && pkgJson.scripts[scriptName].trim().length > 0) continue;
|
|
717
|
-
pkgJson.scripts[scriptName] = command;
|
|
718
|
-
}
|
|
719
|
-
pkgJson.dependencies = sortRecord(pkgJson.dependencies);
|
|
720
|
-
pkgJson.devDependencies = sortRecord(pkgJson.devDependencies);
|
|
721
|
-
pkgJson.scripts = sortRecord(pkgJson.scripts);
|
|
722
|
-
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
723
|
-
}
|
|
724
|
-
async function writePrismaDependencies(provider, packageManager, _authoring, projectDir = process.cwd()) {
|
|
725
|
-
const dependencies = [getDbPackages(provider)];
|
|
726
|
-
if (provider === "postgres" && packageManager !== "deno") dependencies.push("temporal-polyfill");
|
|
727
|
-
if (provider === "mongo") dependencies.push("arktype", "mongodb");
|
|
728
|
-
if (packageManager === "deno") dependencies.push("dotenv");
|
|
729
|
-
await addPackageDependency({
|
|
730
|
-
dependencies,
|
|
731
|
-
devDependencies: ["@types/node", "prisma"],
|
|
732
|
-
scripts: getPrismaScriptMap(packageManager),
|
|
733
|
-
projectDir
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
async function writeCreateTemplateDependencies(opts) {
|
|
737
|
-
const { template, packageManager, projectDir = process.cwd() } = opts;
|
|
738
|
-
if (packageManager === "deno") return;
|
|
739
|
-
for (const target of getCreateTemplateDependencies(template, packageManager)) await addPackageDependency({
|
|
740
|
-
dependencies: target.dependencies,
|
|
741
|
-
devDependencies: target.devDependencies,
|
|
742
|
-
customDependencies: target.customDependencies,
|
|
743
|
-
scripts: getComposerScriptMap(packageManager),
|
|
744
|
-
projectDir: path.join(projectDir, path.dirname(target.packageJsonPath))
|
|
745
|
-
});
|
|
746
|
-
const packageJsonPath = path.join(projectDir, "package.json");
|
|
747
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
748
|
-
const effectVersion = getDependencyVersion("effect");
|
|
749
|
-
if (!effectVersion) throw new Error("Dependency effect is missing from the version map.");
|
|
750
|
-
if (packageManager === "yarn") packageJson.resolutions = {
|
|
751
|
-
...packageJson.resolutions,
|
|
752
|
-
effect: effectVersion
|
|
753
|
-
};
|
|
754
|
-
else if (packageManager !== "pnpm") packageJson.overrides = {
|
|
755
|
-
...packageJson.overrides,
|
|
756
|
-
effect: effectVersion
|
|
757
|
-
};
|
|
758
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
759
|
-
}
|
|
760
|
-
async function installProjectDependencies(packageManager, projectDir = process.cwd(), options = {}) {
|
|
761
|
-
const installCommand = getInstallArgs(packageManager);
|
|
762
|
-
const env = packageManager === "yarn" ? { YARN_ENABLE_IMMUTABLE_INSTALLS: "false" } : void 0;
|
|
763
|
-
await runSetupCommand({
|
|
764
|
-
command: installCommand.command,
|
|
765
|
-
args: installCommand.args,
|
|
766
|
-
cwd: projectDir,
|
|
767
|
-
env,
|
|
768
|
-
verbose: options.verbose === true,
|
|
769
|
-
json: options.json === true
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
//#endregion
|
|
774
|
-
//#region src/ui/output.ts
|
|
775
|
-
const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
776
|
-
callback();
|
|
777
|
-
} });
|
|
778
|
-
/** Resolves the shared interaction policy for human and machine-readable modes. */
|
|
779
|
-
function resolveExecutionSettings(options) {
|
|
780
|
-
const json = options.json === true;
|
|
781
|
-
return {
|
|
782
|
-
json,
|
|
783
|
-
output: json ? silentOutput : process.stdout,
|
|
784
|
-
useDefaults: options.yes === true || json
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
//#endregion
|
|
789
|
-
//#region src/utils/errors.ts
|
|
790
|
-
function redactSecrets(message) {
|
|
791
|
-
return message.replace(/\b((?:(?:prisma\+)?postgres(?:ql)?|mongodb(?:\+srv)?):\/\/)[^\s'"]+/gi, "$1<redacted>").replace(/\b([A-Z0-9_]*(?:MONGODB_(?:URL|URI)|DATABASE_URL|TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY)[A-Z0-9_]*\s*=\s*)(?:"[^"]*"|'[^']*'|[^\s]+)/gi, "$1<redacted>").replace(/(\bAuthorization\s*:\s*Bearer\s+)[^\s'"]+/gi, "$1<redacted>");
|
|
792
|
-
}
|
|
793
|
-
function getErrorMessage(error) {
|
|
794
|
-
if (error instanceof Error && "stderr" in error) {
|
|
795
|
-
const stderr = String(error.stderr ?? "").trim();
|
|
796
|
-
if (stderr) return redactSecrets(stderr);
|
|
797
|
-
}
|
|
798
|
-
return redactSecrets(error instanceof Error ? error.message : String(error));
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
//#endregion
|
|
802
|
-
//#region src/tasks/deploy-with-composer.ts
|
|
803
|
-
var PrismaCliCommandError = class extends Error {
|
|
804
|
-
prismaCliCommand;
|
|
805
|
-
prismaCliErrorCode;
|
|
806
|
-
constructor(options) {
|
|
807
|
-
super(options.message);
|
|
808
|
-
this.name = "PrismaCliCommandError";
|
|
809
|
-
this.prismaCliCommand = options.command;
|
|
810
|
-
this.prismaCliErrorCode = options.code;
|
|
811
|
-
}
|
|
812
|
-
};
|
|
813
|
-
function stripResourcePrefix(id, prefix) {
|
|
814
|
-
const marker = `${prefix}_`;
|
|
815
|
-
return id.startsWith(marker) ? id.slice(marker.length) : id;
|
|
816
|
-
}
|
|
817
|
-
function getConsoleProjectUrl(workspaceId, projectId) {
|
|
818
|
-
const consoleWorkspaceId = stripResourcePrefix(workspaceId, "wksp");
|
|
819
|
-
const consoleProjectId = stripResourcePrefix(projectId, "proj");
|
|
820
|
-
return `https://console.prisma.io/${encodeURIComponent(consoleWorkspaceId)}/${encodeURIComponent(consoleProjectId)}`;
|
|
821
|
-
}
|
|
822
|
-
function parsePrismaCliEnvelope(output) {
|
|
823
|
-
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reverse();
|
|
824
|
-
for (const line of lines) try {
|
|
825
|
-
const parsed = JSON.parse(line);
|
|
826
|
-
const candidate = parsed.kind === "result" ? parsed.envelope : parsed;
|
|
827
|
-
if (typeof candidate !== "object" || candidate === null) continue;
|
|
828
|
-
if (typeof Reflect.get(candidate, "ok") !== "boolean") continue;
|
|
829
|
-
return candidate;
|
|
830
|
-
} catch {}
|
|
831
|
-
throw new Error("Prisma CLI returned output that is not a valid result envelope.");
|
|
832
|
-
}
|
|
833
|
-
function getPrismaCliArgs(packageManager, args) {
|
|
834
|
-
return getPackageExecutionArgs(packageManager, [PRISMA_PLATFORM_CLI_PACKAGE, ...args]);
|
|
835
|
-
}
|
|
836
|
-
async function runPrismaJsonCommand(options) {
|
|
837
|
-
const invocation = getPrismaCliArgs(options.packageManager, [
|
|
838
|
-
...options.args,
|
|
839
|
-
"--json",
|
|
840
|
-
"--no-interactive"
|
|
841
|
-
]);
|
|
842
|
-
const subprocess = execa(invocation.command, invocation.args, {
|
|
843
|
-
cwd: options.projectDir,
|
|
844
|
-
env: process.env,
|
|
845
|
-
reject: false
|
|
846
|
-
});
|
|
847
|
-
const stderrLines = options.onStderrLine && subprocess.stderr ? (async () => {
|
|
848
|
-
const lines = createInterface({ input: subprocess.stderr });
|
|
849
|
-
for await (const line of lines) if (line.trim()) options.onStderrLine?.(line);
|
|
850
|
-
})() : Promise.resolve();
|
|
851
|
-
const [result] = await Promise.all([subprocess, stderrLines]);
|
|
852
|
-
let envelope;
|
|
853
|
-
try {
|
|
854
|
-
envelope = parsePrismaCliEnvelope(result.stdout);
|
|
855
|
-
} catch (error) {
|
|
856
|
-
if (result.exitCode !== 0 && result.stderr.trim()) throw new Error(result.stderr.trim());
|
|
857
|
-
throw error;
|
|
858
|
-
}
|
|
859
|
-
if (result.exitCode !== 0 || !envelope.ok || envelope.result === void 0) throw new PrismaCliCommandError({
|
|
860
|
-
message: [envelope.error?.summary ?? envelope.error?.message, envelope.error?.why].filter(Boolean).join(": ") || result.stderr.trim() || "Prisma CLI command failed.",
|
|
861
|
-
...envelope.commandId || envelope.command ? { command: envelope.commandId ?? envelope.command } : {},
|
|
862
|
-
...envelope.error?.code ? { code: envelope.error.code } : {}
|
|
863
|
-
});
|
|
864
|
-
return envelope.result;
|
|
865
|
-
}
|
|
866
|
-
function findProjectNameCollisions(projects, appName) {
|
|
867
|
-
return projects.filter((project) => project.name === appName);
|
|
868
|
-
}
|
|
869
|
-
async function ensureProjectNameAvailable(options) {
|
|
870
|
-
const collisions = findProjectNameCollisions((await runPrismaJsonCommand({
|
|
871
|
-
packageManager: options.packageManager,
|
|
872
|
-
projectDir: options.projectDir,
|
|
873
|
-
args: ["project", "list"]
|
|
874
|
-
})).items, options.appName);
|
|
875
|
-
if (collisions.length === 0) return;
|
|
876
|
-
const projectIds = collisions.map((project) => project.id).join(", ");
|
|
877
|
-
throw new ClassifiedCreateError("project_name_collision", `A Prisma project named "${options.appName}" already exists in workspace ${workspaceLabel(options.workspace)} (${options.workspace.id}). Choose a different project name or delete the existing project (${projectIds}) in Prisma Console, then retry.`);
|
|
878
|
-
}
|
|
879
|
-
async function ensureAuthentication(options) {
|
|
880
|
-
const whoami = () => runPrismaJsonCommand({
|
|
881
|
-
packageManager: options.packageManager,
|
|
882
|
-
projectDir: options.projectDir,
|
|
883
|
-
args: ["auth", "whoami"]
|
|
884
|
-
});
|
|
885
|
-
const authState = await whoami();
|
|
886
|
-
if (authState.authenticated) return authState;
|
|
887
|
-
const loginCommand = getPackageExecutionCommand(options.packageManager, [
|
|
888
|
-
PRISMA_PLATFORM_CLI_PACKAGE,
|
|
889
|
-
"auth",
|
|
890
|
-
"login"
|
|
891
|
-
]);
|
|
892
|
-
if (!options.allowInteractiveLogin || process.stdin.isTTY !== true) throw new ClassifiedCreateError("not_authenticated", `Sign in first with ${loginCommand}, then run ${getRunScriptCommand(options.packageManager, "deploy")}.`);
|
|
893
|
-
options.beforeInteractiveLogin?.();
|
|
894
|
-
log.info("Sign in to Prisma to deploy.", { output: options.output });
|
|
895
|
-
const login = getPrismaCliArgs(options.packageManager, ["auth", "login"]);
|
|
896
|
-
await execa(login.command, login.args, {
|
|
897
|
-
cwd: options.projectDir,
|
|
898
|
-
env: process.env,
|
|
899
|
-
stdio: "inherit"
|
|
900
|
-
});
|
|
901
|
-
const authenticatedState = await whoami();
|
|
902
|
-
if (!authenticatedState.authenticated) throw new ClassifiedCreateError("authentication_failed", "Prisma sign-in completed without an active workspace session.");
|
|
903
|
-
return authenticatedState;
|
|
904
|
-
}
|
|
905
|
-
function workspaceLabel(workspace) {
|
|
906
|
-
return workspace.name ?? workspace.id;
|
|
907
|
-
}
|
|
908
|
-
async function useWorkspace(options) {
|
|
909
|
-
return (await runPrismaJsonCommand({
|
|
910
|
-
packageManager: options.packageManager,
|
|
911
|
-
projectDir: options.projectDir,
|
|
912
|
-
args: [
|
|
913
|
-
"auth",
|
|
914
|
-
"workspace",
|
|
915
|
-
"use",
|
|
916
|
-
options.workspace
|
|
917
|
-
]
|
|
918
|
-
})).workspace;
|
|
919
|
-
}
|
|
920
|
-
async function selectDeploymentWorkspace(options) {
|
|
921
|
-
const activeWorkspace = options.authState.workspace;
|
|
922
|
-
if (!activeWorkspace) throw new ClassifiedCreateError("workspace_missing", "The active Prisma credential does not specify a workspace.");
|
|
923
|
-
if (options.workspace) {
|
|
924
|
-
if (options.authState.source === "environment") {
|
|
925
|
-
if (options.workspace === activeWorkspace.id || options.workspace === activeWorkspace.name) return {
|
|
926
|
-
ok: true,
|
|
927
|
-
workspace: activeWorkspace
|
|
928
|
-
};
|
|
929
|
-
throw new ClassifiedCreateError("workspace_mismatch", `The environment credential is fixed to workspace ${workspaceLabel(activeWorkspace)}. Unset it before using --workspace ${options.workspace}.`);
|
|
930
|
-
}
|
|
931
|
-
return {
|
|
932
|
-
ok: true,
|
|
933
|
-
workspace: await useWorkspace({
|
|
934
|
-
packageManager: options.packageManager,
|
|
935
|
-
projectDir: options.projectDir,
|
|
936
|
-
workspace: options.workspace
|
|
937
|
-
})
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
if (!options.shouldPrompt || process.stdin.isTTY !== true) return {
|
|
941
|
-
ok: true,
|
|
942
|
-
workspace: activeWorkspace
|
|
943
|
-
};
|
|
944
|
-
const available = await runPrismaJsonCommand({
|
|
945
|
-
packageManager: options.packageManager,
|
|
946
|
-
projectDir: options.projectDir,
|
|
947
|
-
args: [
|
|
948
|
-
"auth",
|
|
949
|
-
"workspace",
|
|
950
|
-
"list"
|
|
951
|
-
]
|
|
952
|
-
});
|
|
953
|
-
if (available.items.length <= 1) return {
|
|
954
|
-
ok: true,
|
|
955
|
-
workspace: activeWorkspace
|
|
956
|
-
};
|
|
957
|
-
options.beforePrompt?.();
|
|
958
|
-
const selectedWorkspaceId = await select({
|
|
959
|
-
message: "Select Prisma workspace for deployment",
|
|
960
|
-
initialValue: activeWorkspace.id,
|
|
961
|
-
options: available.items.map((workspace) => ({
|
|
962
|
-
value: workspace.workspaceId,
|
|
963
|
-
label: workspace.workspaceName ?? workspace.workspaceId,
|
|
964
|
-
hint: workspace.current ? `${workspace.workspaceId}, current` : workspace.workspaceId
|
|
965
|
-
})),
|
|
966
|
-
output: options.output
|
|
967
|
-
});
|
|
968
|
-
if (isCancel(selectedWorkspaceId)) {
|
|
969
|
-
cancel("Operation cancelled.", { output: options.output });
|
|
970
|
-
return {
|
|
971
|
-
ok: false,
|
|
972
|
-
cancelled: true
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
options.afterPrompt?.();
|
|
976
|
-
if (selectedWorkspaceId === activeWorkspace.id) return {
|
|
977
|
-
ok: true,
|
|
978
|
-
workspace: activeWorkspace
|
|
979
|
-
};
|
|
980
|
-
return {
|
|
981
|
-
ok: true,
|
|
982
|
-
workspace: await useWorkspace({
|
|
983
|
-
packageManager: options.packageManager,
|
|
984
|
-
projectDir: options.projectDir,
|
|
985
|
-
workspace: selectedWorkspaceId
|
|
986
|
-
})
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
function parseComposerDeployResult(result) {
|
|
990
|
-
const summary = result.summary;
|
|
991
|
-
if (!summary) return;
|
|
992
|
-
const computeService = summary.nodes.flatMap((node) => node.entities).find((entity) => entity.kind === "compute-service");
|
|
993
|
-
return {
|
|
994
|
-
appName: summary.app,
|
|
995
|
-
...computeService?.id ? { serviceId: computeService.id } : {},
|
|
996
|
-
...computeService?.url ? { appUrl: computeService.url.replace(/\/$/, "") } : {}
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
async function getProjectDetails(options) {
|
|
1000
|
-
try {
|
|
1001
|
-
const result = await runPrismaJsonCommand({
|
|
1002
|
-
packageManager: options.packageManager,
|
|
1003
|
-
projectDir: options.projectDir,
|
|
1004
|
-
args: [
|
|
1005
|
-
"project",
|
|
1006
|
-
"show",
|
|
1007
|
-
options.appName
|
|
1008
|
-
]
|
|
1009
|
-
});
|
|
1010
|
-
if (!result.project) return;
|
|
1011
|
-
return {
|
|
1012
|
-
workspace: result.workspace,
|
|
1013
|
-
project: {
|
|
1014
|
-
id: result.project.id,
|
|
1015
|
-
name: result.project.name,
|
|
1016
|
-
consoleUrl: getConsoleProjectUrl(result.workspace.id, result.project.id)
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
} catch {
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Performs the optional one-shot deployment at the end of a create-prisma scaffold.
|
|
1025
|
-
* Generated projects use their own `deploy` script for every subsequent deployment.
|
|
1026
|
-
*/
|
|
1027
|
-
async function deployNewProjectWithComposer(options) {
|
|
1028
|
-
const output = options.output ?? process.stdout;
|
|
1029
|
-
const progress = options.verbose ? void 0 : spinner({ output });
|
|
1030
|
-
let deploymentLog;
|
|
1031
|
-
let progressRunning = false;
|
|
1032
|
-
let failureStage = "authenticate";
|
|
1033
|
-
let failureReason = "prisma_auth_command_failed";
|
|
1034
|
-
const showProgress = (message) => {
|
|
1035
|
-
if (!progress) return;
|
|
1036
|
-
if (progressRunning) progress.message(message);
|
|
1037
|
-
else {
|
|
1038
|
-
progress.start(message);
|
|
1039
|
-
progressRunning = true;
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
const clearProgress = () => {
|
|
1043
|
-
if (!progress || !progressRunning) return;
|
|
1044
|
-
progress.clear();
|
|
1045
|
-
progressRunning = false;
|
|
1046
|
-
};
|
|
1047
|
-
try {
|
|
1048
|
-
showProgress("Checking Prisma account...");
|
|
1049
|
-
if (options.verbose) log.step("Checking Prisma account.", { output });
|
|
1050
|
-
const authState = await ensureAuthentication({
|
|
1051
|
-
packageManager: options.packageManager,
|
|
1052
|
-
projectDir: options.projectDir,
|
|
1053
|
-
output,
|
|
1054
|
-
allowInteractiveLogin: options.allowInteractiveLogin ?? true,
|
|
1055
|
-
beforeInteractiveLogin: clearProgress
|
|
1056
|
-
});
|
|
1057
|
-
showProgress("Checking Prisma workspace...");
|
|
1058
|
-
failureStage = "select_workspace";
|
|
1059
|
-
failureReason = "workspace_selection_failed";
|
|
1060
|
-
if (options.verbose) log.step("Checking Prisma workspace.", { output });
|
|
1061
|
-
const workspaceResult = await selectDeploymentWorkspace({
|
|
1062
|
-
packageManager: options.packageManager,
|
|
1063
|
-
projectDir: options.projectDir,
|
|
1064
|
-
shouldPrompt: options.shouldPromptForWorkspace,
|
|
1065
|
-
authState,
|
|
1066
|
-
output,
|
|
1067
|
-
beforePrompt: clearProgress,
|
|
1068
|
-
afterPrompt: () => showProgress("Selecting Prisma workspace..."),
|
|
1069
|
-
...options.workspace ? { workspace: options.workspace } : {}
|
|
1070
|
-
});
|
|
1071
|
-
if (!workspaceResult.ok) return {
|
|
1072
|
-
ok: false,
|
|
1073
|
-
cancelled: true,
|
|
1074
|
-
stage: "select_workspace"
|
|
1075
|
-
};
|
|
1076
|
-
const selectedWorkspace = workspaceResult.workspace;
|
|
1077
|
-
showProgress("Checking Prisma project name...");
|
|
1078
|
-
failureStage = "check_project_name";
|
|
1079
|
-
failureReason = "project_lookup_failed";
|
|
1080
|
-
if (options.verbose) log.step("Checking Prisma project name.", { output });
|
|
1081
|
-
await ensureProjectNameAvailable({
|
|
1082
|
-
appName: options.appName,
|
|
1083
|
-
packageManager: options.packageManager,
|
|
1084
|
-
projectDir: options.projectDir,
|
|
1085
|
-
workspace: selectedWorkspace
|
|
1086
|
-
});
|
|
1087
|
-
showProgress("Building for deployment...");
|
|
1088
|
-
failureStage = "build";
|
|
1089
|
-
failureReason = "build_failed";
|
|
1090
|
-
if (options.verbose) log.step("Building for deployment.", { output });
|
|
1091
|
-
const build = getRunScriptArgs(options.packageManager, "build");
|
|
1092
|
-
await runSetupCommand({
|
|
1093
|
-
command: build.command,
|
|
1094
|
-
args: build.args,
|
|
1095
|
-
cwd: options.projectDir,
|
|
1096
|
-
env: process.env,
|
|
1097
|
-
verbose: options.verbose,
|
|
1098
|
-
json: options.json === true
|
|
1099
|
-
});
|
|
1100
|
-
clearProgress();
|
|
1101
|
-
failureStage = "composer_deploy";
|
|
1102
|
-
failureReason = "composer_deploy_failed";
|
|
1103
|
-
const deployCommand = getPackageExecutionCommand(options.packageManager, [
|
|
1104
|
-
PRISMA_PLATFORM_CLI_PACKAGE,
|
|
1105
|
-
"deploy",
|
|
1106
|
-
"module.ts"
|
|
1107
|
-
]);
|
|
1108
|
-
if (options.verbose) log.step(`Deploying to Prisma with ${deployCommand}.`, { output });
|
|
1109
|
-
else {
|
|
1110
|
-
deploymentLog = taskLog({
|
|
1111
|
-
title: "Deploying to Prisma...",
|
|
1112
|
-
limit: 10,
|
|
1113
|
-
output
|
|
1114
|
-
});
|
|
1115
|
-
deploymentLog.message(`$ ${deployCommand}`);
|
|
1116
|
-
}
|
|
1117
|
-
const deployment = parseComposerDeployResult(await runPrismaJsonCommand({
|
|
1118
|
-
packageManager: options.packageManager,
|
|
1119
|
-
projectDir: options.projectDir,
|
|
1120
|
-
args: ["deploy", "module.ts"],
|
|
1121
|
-
onStderrLine: (line) => {
|
|
1122
|
-
const redactedLine = redactSecrets(line);
|
|
1123
|
-
if (options.verbose) output.write(`${redactedLine}\n`);
|
|
1124
|
-
else deploymentLog?.message(redactedLine);
|
|
1125
|
-
}
|
|
1126
|
-
}));
|
|
1127
|
-
const appName = deployment?.appName ?? options.appName;
|
|
1128
|
-
if (options.verbose) log.step("Loading deployment details.", { output });
|
|
1129
|
-
else deploymentLog?.message("Loading deployment details...");
|
|
1130
|
-
const details = await getProjectDetails({
|
|
1131
|
-
packageManager: options.packageManager,
|
|
1132
|
-
projectDir: options.projectDir,
|
|
1133
|
-
appName
|
|
1134
|
-
});
|
|
1135
|
-
deploymentLog?.success("Deployed to Prisma.");
|
|
1136
|
-
deploymentLog = void 0;
|
|
1137
|
-
progressRunning = false;
|
|
1138
|
-
if (options.verbose) log.success("Deployed to Prisma.", { output });
|
|
1139
|
-
const workspace = details?.workspace ?? selectedWorkspace;
|
|
1140
|
-
return {
|
|
1141
|
-
ok: true,
|
|
1142
|
-
deployment: {
|
|
1143
|
-
appName,
|
|
1144
|
-
...deployment?.appUrl ? { appUrl: deployment.appUrl } : {},
|
|
1145
|
-
...deployment?.serviceId ? { serviceId: deployment.serviceId } : {},
|
|
1146
|
-
...workspace ? { workspace } : {},
|
|
1147
|
-
project: details?.project ?? { name: appName }
|
|
1148
|
-
}
|
|
1149
|
-
};
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
if (deploymentLog) {
|
|
1152
|
-
deploymentLog.error("Deployment failed.");
|
|
1153
|
-
deploymentLog = void 0;
|
|
1154
|
-
} else progress?.error("Deployment failed.");
|
|
1155
|
-
progressRunning = false;
|
|
1156
|
-
log.error(`Deploy failed: ${getErrorMessage(error)}`, { output });
|
|
1157
|
-
return {
|
|
1158
|
-
ok: false,
|
|
1159
|
-
stage: failureStage,
|
|
1160
|
-
reason: getCreateFailureReason(error, failureReason),
|
|
1161
|
-
error
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
//#endregion
|
|
1167
|
-
//#region src/tasks/initialize-git.ts
|
|
1168
|
-
function errorMessage(error) {
|
|
1169
|
-
if (error instanceof Error && "stderr" in error) {
|
|
1170
|
-
const stderr = String(error.stderr ?? "").trim();
|
|
1171
|
-
if (stderr) return stderr;
|
|
1172
|
-
}
|
|
1173
|
-
return error instanceof Error ? error.message : String(error);
|
|
1174
|
-
}
|
|
1175
|
-
/**
|
|
1176
|
-
* Initializes a standalone scaffold as a Git repository and records its generated files.
|
|
1177
|
-
* Projects created inside an existing repository remain part of that repository.
|
|
1178
|
-
*/
|
|
1179
|
-
async function initializeGitRepository(projectDir, env = process.env) {
|
|
1180
|
-
try {
|
|
1181
|
-
const existing = await execa("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
1182
|
-
cwd: projectDir,
|
|
1183
|
-
env,
|
|
1184
|
-
reject: false
|
|
1185
|
-
});
|
|
1186
|
-
if (existing.exitCode === 0 && existing.stdout.trim() === "true") return { status: "already-in-repository" };
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
return {
|
|
1189
|
-
status: "skipped",
|
|
1190
|
-
reason: errorMessage(error)
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
let initialized = false;
|
|
1194
|
-
try {
|
|
1195
|
-
await execa("git", ["init"], {
|
|
1196
|
-
cwd: projectDir,
|
|
1197
|
-
env
|
|
1198
|
-
});
|
|
1199
|
-
initialized = true;
|
|
1200
|
-
await execa("git", ["add", "--all"], {
|
|
1201
|
-
cwd: projectDir,
|
|
1202
|
-
env
|
|
1203
|
-
});
|
|
1204
|
-
await execa("git", [
|
|
1205
|
-
"commit",
|
|
1206
|
-
"--no-verify",
|
|
1207
|
-
"-m",
|
|
1208
|
-
"Initial commit from create-prisma"
|
|
1209
|
-
], {
|
|
1210
|
-
cwd: projectDir,
|
|
1211
|
-
env
|
|
1212
|
-
});
|
|
1213
|
-
return { status: "initialized" };
|
|
1214
|
-
} catch (error) {
|
|
1215
|
-
if (initialized) await fs.remove(path.join(projectDir, ".git"));
|
|
1216
|
-
return {
|
|
1217
|
-
status: "skipped",
|
|
1218
|
-
reason: errorMessage(error)
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
//#endregion
|
|
1224
|
-
//#region src/tasks/setup-prisma.ts
|
|
1225
|
-
const DEFAULT_DATABASE_PROVIDER = "postgres";
|
|
1226
|
-
const DEFAULT_AUTHORING = "psl";
|
|
1227
|
-
async function promptForDatabaseProvider(output) {
|
|
1228
|
-
const databaseProvider = await select({
|
|
1229
|
-
message: "Select your database",
|
|
1230
|
-
initialValue: DEFAULT_DATABASE_PROVIDER,
|
|
1231
|
-
options: [{
|
|
1232
|
-
value: "postgres",
|
|
1233
|
-
label: "PostgreSQL",
|
|
1234
|
-
hint: "Prisma Postgres with Composer"
|
|
1235
|
-
}, {
|
|
1236
|
-
value: "mongo",
|
|
1237
|
-
label: "MongoDB",
|
|
1238
|
-
hint: "Connect an existing MongoDB database"
|
|
1239
|
-
}],
|
|
1240
|
-
output
|
|
1241
|
-
});
|
|
1242
|
-
if (isCancel(databaseProvider)) {
|
|
1243
|
-
cancel("Operation cancelled.", { output });
|
|
1244
|
-
throw new CreateCancellationError("database_provider");
|
|
1245
|
-
}
|
|
1246
|
-
return DatabaseProviderSchema.parse(databaseProvider);
|
|
1247
|
-
}
|
|
1248
|
-
async function promptForAuthoringStyle(output) {
|
|
1249
|
-
const authoring = await select({
|
|
1250
|
-
message: "Choose contract authoring style",
|
|
1251
|
-
initialValue: DEFAULT_AUTHORING,
|
|
1252
|
-
options: [{
|
|
1253
|
-
value: "psl",
|
|
1254
|
-
label: "PSL",
|
|
1255
|
-
hint: "Prisma schema syntax"
|
|
1256
|
-
}, {
|
|
1257
|
-
value: "typescript",
|
|
1258
|
-
label: "TypeScript",
|
|
1259
|
-
hint: "TypeScript contract builder"
|
|
1260
|
-
}],
|
|
1261
|
-
output
|
|
1262
|
-
});
|
|
1263
|
-
if (isCancel(authoring)) {
|
|
1264
|
-
cancel("Operation cancelled.", { output });
|
|
1265
|
-
throw new CreateCancellationError("authoring_style");
|
|
1266
|
-
}
|
|
1267
|
-
return AuthoringStyleSchema.parse(authoring);
|
|
1268
|
-
}
|
|
1269
|
-
function getPackageManagerHint(option, detected) {
|
|
1270
|
-
const hints = {
|
|
1271
|
-
npm: "Node.js default",
|
|
1272
|
-
pnpm: "Fast, disk-efficient package manager",
|
|
1273
|
-
yarn: "Yarn package manager",
|
|
1274
|
-
bun: "Fast runtime and package manager",
|
|
1275
|
-
deno: "Deno runtime (minimal PostgreSQL apps)"
|
|
1276
|
-
};
|
|
1277
|
-
return option === detected ? `Detected; ${hints[option]}` : hints[option];
|
|
1278
|
-
}
|
|
1279
|
-
async function promptForPackageManager(detected, output) {
|
|
1280
|
-
const packageManager = await select({
|
|
1281
|
-
message: "Choose package manager",
|
|
1282
|
-
initialValue: detected,
|
|
1283
|
-
options: packageManagers.map((value) => ({
|
|
1284
|
-
value,
|
|
1285
|
-
label: value,
|
|
1286
|
-
hint: getPackageManagerHint(value, detected)
|
|
1287
|
-
})),
|
|
1288
|
-
output
|
|
1289
|
-
});
|
|
1290
|
-
if (isCancel(packageManager)) {
|
|
1291
|
-
cancel("Operation cancelled.", { output });
|
|
1292
|
-
throw new CreateCancellationError("package_manager");
|
|
1293
|
-
}
|
|
1294
|
-
return PackageManagerSchema.parse(packageManager);
|
|
1295
|
-
}
|
|
1296
|
-
async function promptForDeployment(output) {
|
|
1297
|
-
const shouldDeploy = await confirm({
|
|
1298
|
-
message: "Deploy to Prisma now?",
|
|
1299
|
-
initialValue: true,
|
|
1300
|
-
output
|
|
1301
|
-
});
|
|
1302
|
-
if (isCancel(shouldDeploy)) {
|
|
1303
|
-
cancel("Operation cancelled.", { output });
|
|
1304
|
-
throw new CreateCancellationError("deployment_intent");
|
|
1305
|
-
}
|
|
1306
|
-
return Boolean(shouldDeploy);
|
|
1307
|
-
}
|
|
1308
|
-
async function collectPrismaSetupContext(input, options = {}) {
|
|
1309
|
-
const projectDir = path.resolve(options.projectDir ?? process.cwd());
|
|
1310
|
-
const { json, output, useDefaults } = resolveExecutionSettings(input);
|
|
1311
|
-
const databaseProvider = input.provider ?? (useDefaults ? DEFAULT_DATABASE_PROVIDER : await promptForDatabaseProvider(output));
|
|
1312
|
-
const authoring = input.authoring ?? (useDefaults ? DEFAULT_AUTHORING : await promptForAuthoringStyle(output));
|
|
1313
|
-
const detectedPackageManager = await detectPackageManager(projectDir);
|
|
1314
|
-
const packageManager = input.packageManager ?? (useDefaults ? detectedPackageManager : await promptForPackageManager(detectedPackageManager, output));
|
|
1315
|
-
if (packageManager === "deno" && databaseProvider !== "postgres") throw new ClassifiedCreateError("unsupported_configuration", "Deno support currently requires PostgreSQL.");
|
|
1316
|
-
if (packageManager === "deno" && options.template && options.template !== "minimal") throw new ClassifiedCreateError("unsupported_configuration", "Deno support currently requires the minimal template.");
|
|
1317
|
-
if (packageManager === "deno" && input.deploy === true) throw new ClassifiedCreateError("unsupported_configuration", "Prisma Compute does not support Deno deployments yet. Use --no-deploy.");
|
|
1318
|
-
const shouldDeploy = packageManager === "deno" ? false : input.deploy ?? (json ? true : useDefaults ? false : await promptForDeployment(output));
|
|
1319
|
-
return {
|
|
1320
|
-
projectDir,
|
|
1321
|
-
verbose: input.verbose === true,
|
|
1322
|
-
json,
|
|
1323
|
-
output,
|
|
1324
|
-
databaseProvider,
|
|
1325
|
-
authoring,
|
|
1326
|
-
packageManager,
|
|
1327
|
-
shouldDeploy,
|
|
1328
|
-
shouldPromptForWorkspace: !useDefaults,
|
|
1329
|
-
...input.workspace ? { workspace: input.workspace } : {}
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
function getContractPath(authoring) {
|
|
1333
|
-
return `src/prisma/contract${authoring === "typescript" ? ".ts" : ".prisma"}`;
|
|
1334
|
-
}
|
|
1335
|
-
function getInitTarget(provider) {
|
|
1336
|
-
return provider === "mongo" ? "mongodb" : "postgres";
|
|
1337
|
-
}
|
|
1338
|
-
function getPrismaCliInvocation(packageManager, args) {
|
|
1339
|
-
return getPackageExecutionArgs(packageManager, [packageManager === "deno" ? PRISMA_DENO_CLI_PACKAGE : PRISMA_PLATFORM_CLI_PACKAGE, ...args]);
|
|
1340
|
-
}
|
|
1341
|
-
async function runPrismaInit(context, projectDir) {
|
|
1342
|
-
const args = [
|
|
1343
|
-
"orm",
|
|
1344
|
-
"init",
|
|
1345
|
-
"--yes",
|
|
1346
|
-
"--no-interactive",
|
|
1347
|
-
"--target",
|
|
1348
|
-
getInitTarget(context.databaseProvider),
|
|
1349
|
-
"--authoring",
|
|
1350
|
-
context.authoring,
|
|
1351
|
-
"--schema-path",
|
|
1352
|
-
getContractPath(context.authoring),
|
|
1353
|
-
"--skip-install"
|
|
1354
|
-
];
|
|
1355
|
-
const invocation = getPrismaCliInvocation(context.packageManager, args);
|
|
1356
|
-
if (context.verbose) log.step(`Running ${[invocation.command, ...invocation.args].join(" ")}`, { output: context.output });
|
|
1357
|
-
await runSetupCommand({
|
|
1358
|
-
command: invocation.command,
|
|
1359
|
-
args: invocation.args,
|
|
1360
|
-
cwd: projectDir,
|
|
1361
|
-
env: {
|
|
1362
|
-
...process.env,
|
|
1363
|
-
CI: "1"
|
|
1364
|
-
},
|
|
1365
|
-
verbose: context.verbose,
|
|
1366
|
-
json: context.json
|
|
1367
|
-
});
|
|
1368
|
-
if (context.packageManager === "deno") await fs.remove(path.join(projectDir, "prisma-next.md"));
|
|
1369
|
-
}
|
|
1370
|
-
async function initializeAgentSkills(context, projectDir) {
|
|
1371
|
-
if (context.packageManager === "deno") return;
|
|
1372
|
-
const invocation = getPrismaCliInvocation(context.packageManager, [
|
|
1373
|
-
"init",
|
|
1374
|
-
"--yes",
|
|
1375
|
-
"--no-interactive"
|
|
1376
|
-
]);
|
|
1377
|
-
if (context.verbose) log.step(`Running ${[invocation.command, ...invocation.args].join(" ")}`, { output: context.output });
|
|
1378
|
-
await runSetupCommand({
|
|
1379
|
-
command: invocation.command,
|
|
1380
|
-
args: invocation.args,
|
|
1381
|
-
cwd: projectDir,
|
|
1382
|
-
env: {
|
|
1383
|
-
...process.env,
|
|
1384
|
-
CI: "1"
|
|
1385
|
-
},
|
|
1386
|
-
verbose: context.verbose,
|
|
1387
|
-
json: context.json
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
async function ensureGitignoreEntry(projectDir, entry) {
|
|
1391
|
-
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
1392
|
-
const existing = await fs.pathExists(gitignorePath) ? await fs.readFile(gitignorePath, "utf8") : "";
|
|
1393
|
-
const lines = existing.split(/\r?\n/).map((line) => line.trim());
|
|
1394
|
-
if (lines.includes(entry) || lines.includes(`/${entry}`)) return;
|
|
1395
|
-
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
1396
|
-
await fs.writeFile(gitignorePath, `${existing}${separator}${entry}\n`, "utf8");
|
|
1397
|
-
}
|
|
1398
|
-
async function ensureMongoEnvironment(projectDir) {
|
|
1399
|
-
const envPath = path.join(projectDir, ".env");
|
|
1400
|
-
if (!await fs.pathExists(envPath)) await fs.writeFile(envPath, "DATABASE_URL=\"mongodb://localhost:27017/mydb?replicaSet=rs0&directConnection=true\"\n", "utf8");
|
|
1401
|
-
await ensureGitignoreEntry(projectDir, ".env");
|
|
1402
|
-
}
|
|
1403
|
-
async function ensureComposerTypeScriptOptions(projectDir) {
|
|
1404
|
-
const tsconfigPath = path.join(projectDir, "tsconfig.json");
|
|
1405
|
-
const tsconfig = await fs.readFile(tsconfigPath, "utf8");
|
|
1406
|
-
const additions = [];
|
|
1407
|
-
if (!/"allowImportingTsExtensions"\s*:/.test(tsconfig)) additions.push(" \"allowImportingTsExtensions\": true,");
|
|
1408
|
-
if (!/"noEmit"\s*:/.test(tsconfig)) additions.push(" \"noEmit\": true,");
|
|
1409
|
-
if (additions.length === 0) return;
|
|
1410
|
-
const updated = tsconfig.replace(/"compilerOptions"\s*:\s*\{/, (match) => `${match}\n${additions.join("\n")}`);
|
|
1411
|
-
if (updated === tsconfig) throw new Error("tsconfig.json is missing compilerOptions.");
|
|
1412
|
-
await fs.writeFile(tsconfigPath, updated, "utf8");
|
|
1413
|
-
}
|
|
1414
|
-
async function runPrismaCli(context, projectDir, args) {
|
|
1415
|
-
const invocation = getPrismaCliInvocation(context.packageManager, args);
|
|
1416
|
-
if (context.verbose) log.step([invocation.command, ...invocation.args].join(" "), { output: context.output });
|
|
1417
|
-
await runSetupCommand({
|
|
1418
|
-
command: invocation.command,
|
|
1419
|
-
args: invocation.args,
|
|
1420
|
-
cwd: projectDir,
|
|
1421
|
-
env: {
|
|
1422
|
-
...process.env,
|
|
1423
|
-
CI: "1"
|
|
1424
|
-
},
|
|
1425
|
-
verbose: context.verbose,
|
|
1426
|
-
json: context.json
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
async function emitContract(context, projectDir) {
|
|
1430
|
-
await runPrismaCli(context, projectDir, ["contract", "emit"]);
|
|
1431
|
-
}
|
|
1432
|
-
async function planBaselineMigration(context, projectDir) {
|
|
1433
|
-
await runPrismaCli(context, projectDir, [
|
|
1434
|
-
"migration",
|
|
1435
|
-
"plan",
|
|
1436
|
-
"--name",
|
|
1437
|
-
"init"
|
|
1438
|
-
]);
|
|
1439
|
-
}
|
|
1440
|
-
function formatNextSteps(steps) {
|
|
1441
|
-
return steps.map((step) => `${step.command}\n ${step.description}`).join("\n\n");
|
|
1442
|
-
}
|
|
1443
|
-
function formatPlatformTarget(name, id) {
|
|
1444
|
-
return name ? `${name} (${id})` : id;
|
|
1445
|
-
}
|
|
1446
|
-
function formatProjectSummary(options) {
|
|
1447
|
-
const lines = [];
|
|
1448
|
-
if (options.createdProjectPath) lines.push(`Path: ${path.resolve(options.createdProjectPath)}`);
|
|
1449
|
-
if (options.deployment?.workspace) lines.push(`Workspace: ${formatPlatformTarget(options.deployment.workspace.name, options.deployment.workspace.id)}`);
|
|
1450
|
-
if (options.deployment) {
|
|
1451
|
-
lines.push(`Project: ${options.deployment.project.id ? formatPlatformTarget(options.deployment.project.name, options.deployment.project.id) : options.deployment.project.name}`);
|
|
1452
|
-
lines.push(`App: ${options.deployment.appUrl ?? options.deployment.appName}`);
|
|
1453
|
-
if (options.deployment.project.consoleUrl) lines.push(`Console: ${options.deployment.project.consoleUrl}`);
|
|
1454
|
-
}
|
|
1455
|
-
return lines.join("\n");
|
|
1456
|
-
}
|
|
1457
|
-
function buildNextSteps(context, options) {
|
|
1458
|
-
const nextSteps = [...options.prependNextSteps ?? []];
|
|
1459
|
-
if (context.databaseProvider === "mongo") nextSteps.push({
|
|
1460
|
-
command: "Set MONGODB_URL in your environment",
|
|
1461
|
-
description: "Composer uses this secret when deploying the MongoDB template."
|
|
1462
|
-
});
|
|
1463
|
-
if (options.includeDevNextStep) nextSteps.push({
|
|
1464
|
-
command: getRunScriptCommand(context.packageManager, context.packageManager === "deno" ? "dev" : "dev:composer"),
|
|
1465
|
-
description: context.packageManager === "deno" ? "Start the Deno app after setting DATABASE_URL in .env." : "Build and start the app with Prisma Composer locally."
|
|
1466
|
-
});
|
|
1467
|
-
if (context.packageManager === "deno") return nextSteps;
|
|
1468
|
-
nextSteps.push({
|
|
1469
|
-
command: getRunScriptCommand(context.packageManager, "deploy"),
|
|
1470
|
-
description: "Build and deploy the app with Prisma Composer."
|
|
1471
|
-
});
|
|
1472
|
-
return nextSteps;
|
|
1473
|
-
}
|
|
1474
|
-
async function executePrismaSetupContext(context, options = {}) {
|
|
1475
|
-
const projectDir = path.resolve(options.projectDir ?? context.projectDir);
|
|
1476
|
-
const projectName = options.projectName ?? path.basename(projectDir);
|
|
1477
|
-
const template = options.template ?? "minimal";
|
|
1478
|
-
const progress = context.verbose ? void 0 : options.progressSpinner ?? spinner({ output: context.output });
|
|
1479
|
-
const ownsProgress = progress !== void 0 && !options.progressSpinner;
|
|
1480
|
-
let gitInitialization;
|
|
1481
|
-
let setupStage = "initialize_prisma";
|
|
1482
|
-
let setupReason = "prisma_init_failed";
|
|
1483
|
-
if (ownsProgress) progress.start("Creating Prisma 8 project...");
|
|
1484
|
-
try {
|
|
1485
|
-
progress?.message("Preparing Prisma 8 project files...");
|
|
1486
|
-
await runPrismaInit(context, projectDir);
|
|
1487
|
-
setupStage = "configure_project";
|
|
1488
|
-
setupReason = "project_configuration_failed";
|
|
1489
|
-
await scaffoldCreateSharedTemplates({
|
|
1490
|
-
projectDir,
|
|
1491
|
-
projectName,
|
|
1492
|
-
template,
|
|
1493
|
-
provider: context.databaseProvider,
|
|
1494
|
-
authoring: context.authoring,
|
|
1495
|
-
packageManager: context.packageManager
|
|
1496
|
-
});
|
|
1497
|
-
await writePrismaDependencies(context.databaseProvider, context.packageManager, context.authoring, projectDir);
|
|
1498
|
-
await ensureComposerTypeScriptOptions(projectDir);
|
|
1499
|
-
if (context.databaseProvider === "mongo") await ensureMongoEnvironment(projectDir);
|
|
1500
|
-
if (context.packageManager !== "deno") {
|
|
1501
|
-
await ensureGitignoreEntry(projectDir, "/.alchemy");
|
|
1502
|
-
await ensureGitignoreEntry(projectDir, "/.prisma-composer");
|
|
1503
|
-
}
|
|
1504
|
-
progress?.message(`Installing dependencies with ${getInstallCommand(context.packageManager)}...`);
|
|
1505
|
-
setupStage = "install_dependencies";
|
|
1506
|
-
setupReason = "dependency_install_failed";
|
|
1507
|
-
await installProjectDependencies(context.packageManager, projectDir, {
|
|
1508
|
-
verbose: context.verbose,
|
|
1509
|
-
json: context.json
|
|
1510
|
-
});
|
|
1511
|
-
progress?.message("Installing Prisma agent skills...");
|
|
1512
|
-
setupStage = "initialize_agent_skills";
|
|
1513
|
-
setupReason = "agent_skills_init_failed";
|
|
1514
|
-
await initializeAgentSkills(context, projectDir);
|
|
1515
|
-
progress?.message("Generating Prisma 8 contract artifacts...");
|
|
1516
|
-
setupStage = "emit_contract";
|
|
1517
|
-
setupReason = "contract_emit_failed";
|
|
1518
|
-
await emitContract(context, projectDir);
|
|
1519
|
-
if (context.databaseProvider === "postgres") {
|
|
1520
|
-
progress?.message("Authoring the baseline migration...");
|
|
1521
|
-
setupStage = "plan_migration";
|
|
1522
|
-
setupReason = "migration_plan_failed";
|
|
1523
|
-
await planBaselineMigration(context, projectDir);
|
|
1524
|
-
}
|
|
1525
|
-
if (options.initializeGit) {
|
|
1526
|
-
progress?.message("Initializing Git repository...");
|
|
1527
|
-
setupStage = "initialize_git";
|
|
1528
|
-
setupReason = "git_initialization_failed";
|
|
1529
|
-
gitInitialization = await initializeGitRepository(projectDir);
|
|
1530
|
-
}
|
|
1531
|
-
progress?.stop("Prisma 8 project ready.");
|
|
1532
|
-
if (gitInitialization?.status === "initialized" && context.verbose) log.success("Initialized Git repository with an initial commit.", { output: context.output });
|
|
1533
|
-
else if (gitInitialization?.status === "skipped") log.warn(`Could not initialize Git repository: ${gitInitialization.reason}`, { output: context.output });
|
|
1534
|
-
} catch (error) {
|
|
1535
|
-
progress?.error("Could not create Prisma 8 project.");
|
|
1536
|
-
cancel(getErrorMessage(error), { output: context.output });
|
|
1537
|
-
return {
|
|
1538
|
-
ok: false,
|
|
1539
|
-
stage: setupStage,
|
|
1540
|
-
reason: getCreateFailureReason(error, setupReason),
|
|
1541
|
-
error,
|
|
1542
|
-
errorReported: true
|
|
1543
|
-
};
|
|
1544
|
-
}
|
|
1545
|
-
let deployment;
|
|
1546
|
-
if (context.shouldDeploy) {
|
|
1547
|
-
const deploymentResult = await deployNewProjectWithComposer({
|
|
1548
|
-
appName: projectName,
|
|
1549
|
-
packageManager: context.packageManager,
|
|
1550
|
-
projectDir,
|
|
1551
|
-
shouldPromptForWorkspace: context.shouldPromptForWorkspace,
|
|
1552
|
-
verbose: context.verbose,
|
|
1553
|
-
output: context.output,
|
|
1554
|
-
allowInteractiveLogin: !context.json,
|
|
1555
|
-
json: context.json,
|
|
1556
|
-
...context.workspace ? { workspace: context.workspace } : {}
|
|
1557
|
-
});
|
|
1558
|
-
if (!deploymentResult.ok) {
|
|
1559
|
-
if (deploymentResult.cancelled) return {
|
|
1560
|
-
ok: false,
|
|
1561
|
-
cancelled: true,
|
|
1562
|
-
stage: deploymentResult.stage,
|
|
1563
|
-
errorReported: true
|
|
1564
|
-
};
|
|
1565
|
-
return {
|
|
1566
|
-
ok: false,
|
|
1567
|
-
stage: deploymentResult.stage,
|
|
1568
|
-
reason: deploymentResult.reason,
|
|
1569
|
-
error: deploymentResult.error,
|
|
1570
|
-
errorReported: true
|
|
1571
|
-
};
|
|
1572
|
-
}
|
|
1573
|
-
deployment = deploymentResult.deployment;
|
|
1574
|
-
}
|
|
1575
|
-
const nextSteps = buildNextSteps(context, options);
|
|
1576
|
-
const warnings = gitInitialization?.status === "skipped" ? [`Could not initialize Git repository: ${gitInitialization.reason}`] : [];
|
|
1577
|
-
const projectSummary = formatProjectSummary({
|
|
1578
|
-
createdProjectPath: options.createdProjectPath,
|
|
1579
|
-
deployment
|
|
1580
|
-
});
|
|
1581
|
-
if (projectSummary) note(projectSummary, context.shouldDeploy ? "Deployment" : "Project", { output: context.output });
|
|
1582
|
-
note(formatNextSteps(nextSteps), "Next steps", { output: context.output });
|
|
1583
|
-
outro(context.shouldDeploy ? "Prisma 8 app deployed." : "Prisma 8 project ready.", { output: context.output });
|
|
1584
|
-
return {
|
|
1585
|
-
ok: true,
|
|
1586
|
-
deployment: deployment ?? null,
|
|
1587
|
-
nextSteps,
|
|
1588
|
-
...gitInitialization ? { gitInitialization } : {},
|
|
1589
|
-
warnings
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
//#endregion
|
|
1594
|
-
//#region src/ui/branding.ts
|
|
1595
|
-
const prismaTitle = `${styleText(["bold", "cyanBright"], "◭")} ${styleText(["bold", "cyanBright"], "Create")} ${styleText(["bold", "magentaBright"], "Prisma")} ${styleText(["bold", "blueBright"], "8")}`;
|
|
1596
|
-
function getCreatePrismaIntro() {
|
|
1597
|
-
return prismaTitle;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
//#endregion
|
|
1601
|
-
//#region src/utils/node-version.ts
|
|
1602
|
-
const MINIMUM_NODE_VERSION = [
|
|
1603
|
-
22,
|
|
1604
|
-
18,
|
|
1605
|
-
0
|
|
1606
|
-
];
|
|
1607
|
-
function parseVersion(version) {
|
|
1608
|
-
const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split(".");
|
|
1609
|
-
return [
|
|
1610
|
-
Number(major),
|
|
1611
|
-
Number(minor),
|
|
1612
|
-
Number.parseInt(patch, 10)
|
|
1613
|
-
];
|
|
1614
|
-
}
|
|
1615
|
-
function supportsPrisma(nodeVersion = process.versions.node) {
|
|
1616
|
-
const current = parseVersion(nodeVersion);
|
|
1617
|
-
for (let index = 0; index < MINIMUM_NODE_VERSION.length; index += 1) {
|
|
1618
|
-
if (current[index] > MINIMUM_NODE_VERSION[index]) return true;
|
|
1619
|
-
if (current[index] < MINIMUM_NODE_VERSION[index]) return false;
|
|
1620
|
-
}
|
|
1621
|
-
return true;
|
|
1622
|
-
}
|
|
1623
|
-
function getUnsupportedNodeMessage(nodeVersion = process.versions.node) {
|
|
1624
|
-
return [
|
|
1625
|
-
`Node.js ${nodeVersion} is unsupported by create-prisma@latest.`,
|
|
1626
|
-
"Required: Node.js 22.18 or newer.",
|
|
1627
|
-
"Update Node.js and run the command again."
|
|
1628
|
-
].join("\n");
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
//#endregion
|
|
1632
|
-
//#region src/commands/create.ts
|
|
1633
|
-
const DEFAULT_PROJECT_NAME = "my-app";
|
|
1634
|
-
const DEFAULT_TEMPLATE = "minimal";
|
|
1635
|
-
function toPackageName(projectName) {
|
|
1636
|
-
return projectName.toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+/, "").replace(/-+$/, "") || "app";
|
|
1637
|
-
}
|
|
1638
|
-
function formatPathForDisplay(filePath) {
|
|
1639
|
-
return path.relative(process.cwd(), filePath) || ".";
|
|
1640
|
-
}
|
|
1641
|
-
function validateProjectName(value) {
|
|
1642
|
-
const trimmed = String(value ?? "").trim();
|
|
1643
|
-
if (trimmed.length === 0) return "Please enter a project name.";
|
|
1644
|
-
if (trimmed === "..") return "Project name cannot be '..'.";
|
|
1645
|
-
if (path.isAbsolute(trimmed)) return "Use a relative project name instead of an absolute path.";
|
|
1646
|
-
}
|
|
1647
|
-
function getProjectResult(context) {
|
|
1648
|
-
return {
|
|
1649
|
-
name: context.projectPackageName,
|
|
1650
|
-
path: context.targetDirectory,
|
|
1651
|
-
template: context.template,
|
|
1652
|
-
databaseProvider: context.prismaSetupContext.databaseProvider,
|
|
1653
|
-
authoring: context.prismaSetupContext.authoring,
|
|
1654
|
-
packageManager: context.prismaSetupContext.packageManager
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
async function promptForProjectName(output) {
|
|
1658
|
-
const projectName = await text({
|
|
1659
|
-
message: "Project name",
|
|
1660
|
-
placeholder: DEFAULT_PROJECT_NAME,
|
|
1661
|
-
initialValue: DEFAULT_PROJECT_NAME,
|
|
1662
|
-
validate: validateProjectName,
|
|
1663
|
-
output
|
|
1664
|
-
});
|
|
1665
|
-
if (isCancel(projectName)) {
|
|
1666
|
-
cancel("Operation cancelled.", { output });
|
|
1667
|
-
throw new CreateCancellationError("project_name");
|
|
1668
|
-
}
|
|
1669
|
-
return String(projectName).trim();
|
|
1670
|
-
}
|
|
1671
|
-
async function promptForCreateTemplate(output) {
|
|
1672
|
-
const template = await select({
|
|
1673
|
-
message: "Select template",
|
|
1674
|
-
initialValue: DEFAULT_TEMPLATE,
|
|
1675
|
-
options: [
|
|
1676
|
-
{
|
|
1677
|
-
value: "minimal",
|
|
1678
|
-
label: "Minimal",
|
|
1679
|
-
hint: "Script-first Prisma 8 starter with no web framework"
|
|
1680
|
-
},
|
|
1681
|
-
{
|
|
1682
|
-
value: "hono",
|
|
1683
|
-
label: "Hono",
|
|
1684
|
-
hint: "Lightweight TypeScript API server"
|
|
1685
|
-
},
|
|
1686
|
-
{
|
|
1687
|
-
value: "elysia",
|
|
1688
|
-
label: "Elysia",
|
|
1689
|
-
hint: "Bun-friendly TypeScript API server"
|
|
1690
|
-
},
|
|
1691
|
-
{
|
|
1692
|
-
value: "nest",
|
|
1693
|
-
label: "NestJS",
|
|
1694
|
-
hint: "Structured Node API with controllers and services"
|
|
1695
|
-
},
|
|
1696
|
-
{
|
|
1697
|
-
value: "next",
|
|
1698
|
-
label: "Next.js",
|
|
1699
|
-
hint: "Full-stack React app with App Router"
|
|
1700
|
-
},
|
|
1701
|
-
{
|
|
1702
|
-
value: "svelte",
|
|
1703
|
-
label: "SvelteKit",
|
|
1704
|
-
hint: "Full-stack Svelte 5 app with Vite"
|
|
1705
|
-
},
|
|
1706
|
-
{
|
|
1707
|
-
value: "astro",
|
|
1708
|
-
label: "Astro",
|
|
1709
|
-
hint: "Content-oriented web app with server routes"
|
|
1710
|
-
},
|
|
1711
|
-
{
|
|
1712
|
-
value: "nuxt",
|
|
1713
|
-
label: "Nuxt",
|
|
1714
|
-
hint: "Full-stack Vue app with Nitro server routes"
|
|
1715
|
-
},
|
|
1716
|
-
{
|
|
1717
|
-
value: "tanstack-start",
|
|
1718
|
-
label: "TanStack Start",
|
|
1719
|
-
hint: "React app with file routes and server functions"
|
|
1720
|
-
}
|
|
1721
|
-
],
|
|
1722
|
-
output
|
|
1723
|
-
});
|
|
1724
|
-
if (isCancel(template)) {
|
|
1725
|
-
cancel("Operation cancelled.", { output });
|
|
1726
|
-
throw new CreateCancellationError("template");
|
|
1727
|
-
}
|
|
1728
|
-
return CreateTemplateSchema.parse(template);
|
|
1729
|
-
}
|
|
1730
|
-
async function inspectTargetPath(targetPath) {
|
|
1731
|
-
if (!await fs.pathExists(targetPath)) return {
|
|
1732
|
-
exists: false,
|
|
1733
|
-
isDirectory: true,
|
|
1734
|
-
isEmptyDirectory: true
|
|
1735
|
-
};
|
|
1736
|
-
if (!(await fs.stat(targetPath)).isDirectory()) return {
|
|
1737
|
-
exists: true,
|
|
1738
|
-
isDirectory: false,
|
|
1739
|
-
isEmptyDirectory: false
|
|
1740
|
-
};
|
|
1741
|
-
return {
|
|
1742
|
-
exists: true,
|
|
1743
|
-
isDirectory: true,
|
|
1744
|
-
isEmptyDirectory: (await fs.readdir(targetPath)).length === 0
|
|
1745
|
-
};
|
|
1746
|
-
}
|
|
1747
|
-
async function runCreateCommand(rawInput = {}) {
|
|
1748
|
-
const startedAt = Date.now();
|
|
1749
|
-
let input = {};
|
|
1750
|
-
let context;
|
|
1751
|
-
let failureStage = "validate_input";
|
|
1752
|
-
let failureReason = "invalid_input";
|
|
1753
|
-
const { output } = resolveExecutionSettings(rawInput);
|
|
1754
|
-
try {
|
|
1755
|
-
input = CreateCommandInputSchema.parse(rawInput);
|
|
1756
|
-
if (input.json && input.verbose) throw new Error("--verbose cannot be used with --json because JSON mode is output-only.");
|
|
1757
|
-
if (!supportsPrisma()) {
|
|
1758
|
-
const message = getUnsupportedNodeMessage();
|
|
1759
|
-
cancel(message, { output });
|
|
1760
|
-
process.exitCode = 1;
|
|
1761
|
-
await trackCreateFailed({
|
|
1762
|
-
input,
|
|
1763
|
-
durationMs: Date.now() - startedAt,
|
|
1764
|
-
stage: failureStage,
|
|
1765
|
-
reason: "unsupported_node_version"
|
|
1766
|
-
});
|
|
1767
|
-
return createCommandFailureResult(failureStage, message);
|
|
1768
|
-
}
|
|
1769
|
-
intro(getCreatePrismaIntro(), { output });
|
|
1770
|
-
failureStage = "collect_context";
|
|
1771
|
-
failureReason = "unexpected_error";
|
|
1772
|
-
const collected = await collectCreateContext(input);
|
|
1773
|
-
if (!collected.ok) {
|
|
1774
|
-
process.exitCode = 1;
|
|
1775
|
-
const result = createCommandFailureResult(failureStage, collected.message);
|
|
1776
|
-
await trackCreateFailed({
|
|
1777
|
-
input,
|
|
1778
|
-
durationMs: Date.now() - startedAt,
|
|
1779
|
-
stage: failureStage,
|
|
1780
|
-
reason: collected.reason
|
|
1781
|
-
});
|
|
1782
|
-
return result;
|
|
1783
|
-
}
|
|
1784
|
-
context = collected.context;
|
|
1785
|
-
failureStage = "unknown";
|
|
1786
|
-
const executionResult = await executeCreateContext(context);
|
|
1787
|
-
if (!executionResult.ok) {
|
|
1788
|
-
process.exitCode = 1;
|
|
1789
|
-
if (executionResult.cancelled) {
|
|
1790
|
-
await trackCreateCancelled({
|
|
1791
|
-
input,
|
|
1792
|
-
context,
|
|
1793
|
-
durationMs: Date.now() - startedAt,
|
|
1794
|
-
stage: executionResult.stage
|
|
1795
|
-
});
|
|
1796
|
-
return createCommandFailureResult(executionResult.stage, "Operation cancelled.", getProjectResult(context));
|
|
1797
|
-
}
|
|
1798
|
-
const message = executionResult.error ? getErrorMessage(executionResult.error) : "Project setup did not complete.";
|
|
1799
|
-
if (executionResult.error && !executionResult.errorReported) cancel(`Create command failed: ${message}`, { output });
|
|
1800
|
-
await trackCreateFailed({
|
|
1801
|
-
input,
|
|
1802
|
-
context,
|
|
1803
|
-
durationMs: Date.now() - startedAt,
|
|
1804
|
-
error: executionResult.error,
|
|
1805
|
-
stage: executionResult.stage,
|
|
1806
|
-
reason: executionResult.reason
|
|
1807
|
-
});
|
|
1808
|
-
return createCommandFailureResult(executionResult.stage, message, getProjectResult(context));
|
|
1809
|
-
}
|
|
1810
|
-
await trackCreateCompleted({
|
|
1811
|
-
input,
|
|
1812
|
-
context,
|
|
1813
|
-
durationMs: Date.now() - startedAt
|
|
1814
|
-
});
|
|
1815
|
-
return executionResult.result;
|
|
1816
|
-
} catch (error) {
|
|
1817
|
-
process.exitCode = 1;
|
|
1818
|
-
if (error instanceof CreateCancellationError) {
|
|
1819
|
-
await trackCreateCancelled({
|
|
1820
|
-
input,
|
|
1821
|
-
context,
|
|
1822
|
-
durationMs: Date.now() - startedAt,
|
|
1823
|
-
stage: error.stage
|
|
1824
|
-
});
|
|
1825
|
-
return createCommandFailureResult(error.stage, error.message, context ? getProjectResult(context) : void 0);
|
|
1826
|
-
}
|
|
1827
|
-
const message = getErrorMessage(error);
|
|
1828
|
-
cancel(`Create command failed: ${message}`, { output });
|
|
1829
|
-
await trackCreateFailed({
|
|
1830
|
-
input,
|
|
1831
|
-
context,
|
|
1832
|
-
durationMs: Date.now() - startedAt,
|
|
1833
|
-
error,
|
|
1834
|
-
stage: failureStage,
|
|
1835
|
-
reason: getCreateFailureReason(error, failureReason)
|
|
1836
|
-
});
|
|
1837
|
-
return createCommandFailureResult(failureStage, message, context ? getProjectResult(context) : void 0);
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
async function collectCreateContext(input) {
|
|
1841
|
-
const force = input.force === true;
|
|
1842
|
-
const { output, useDefaults } = resolveExecutionSettings(input);
|
|
1843
|
-
const projectNameInput = input.name ?? (useDefaults ? DEFAULT_PROJECT_NAME : await promptForProjectName(output));
|
|
1844
|
-
const projectName = String(projectNameInput).trim();
|
|
1845
|
-
const projectNameValidationError = validateProjectName(projectName);
|
|
1846
|
-
if (projectNameValidationError) {
|
|
1847
|
-
cancel(projectNameValidationError, { output });
|
|
1848
|
-
return {
|
|
1849
|
-
ok: false,
|
|
1850
|
-
message: projectNameValidationError,
|
|
1851
|
-
reason: "invalid_project_name"
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
const template = input.template ?? (useDefaults ? DEFAULT_TEMPLATE : await promptForCreateTemplate(output));
|
|
1855
|
-
const targetDirectory = path.resolve(process.cwd(), projectName);
|
|
1856
|
-
const targetPathState = await inspectTargetPath(targetDirectory);
|
|
1857
|
-
if (targetPathState.exists && !targetPathState.isDirectory) {
|
|
1858
|
-
const message = `Target path ${formatPathForDisplay(targetDirectory)} already exists and is not a directory. Choose a different project name.`;
|
|
1859
|
-
cancel(message, { output });
|
|
1860
|
-
return {
|
|
1861
|
-
ok: false,
|
|
1862
|
-
message,
|
|
1863
|
-
reason: "target_path_not_directory"
|
|
1864
|
-
};
|
|
1865
|
-
}
|
|
1866
|
-
if (targetPathState.exists && !targetPathState.isEmptyDirectory && !force) {
|
|
1867
|
-
const message = `Target directory ${formatPathForDisplay(targetDirectory)} is not empty. Use --force to continue.`;
|
|
1868
|
-
cancel(message, { output });
|
|
1869
|
-
return {
|
|
1870
|
-
ok: false,
|
|
1871
|
-
message,
|
|
1872
|
-
reason: "target_directory_not_empty"
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
const prismaSetupContext = await collectPrismaSetupContext(input, {
|
|
1876
|
-
projectDir: targetDirectory,
|
|
1877
|
-
template
|
|
1878
|
-
});
|
|
1879
|
-
return {
|
|
1880
|
-
ok: true,
|
|
1881
|
-
context: {
|
|
1882
|
-
targetDirectory,
|
|
1883
|
-
targetPathState,
|
|
1884
|
-
force,
|
|
1885
|
-
template,
|
|
1886
|
-
projectPackageName: toPackageName(path.basename(targetDirectory)),
|
|
1887
|
-
prismaSetupContext
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
async function executeCreateContext(context) {
|
|
1892
|
-
const output = context.prismaSetupContext.output;
|
|
1893
|
-
const createSpinner = context.prismaSetupContext.verbose ? void 0 : spinner({ output });
|
|
1894
|
-
createSpinner?.start("Creating Prisma 8 project...");
|
|
1895
|
-
try {
|
|
1896
|
-
if (context.prismaSetupContext.verbose) log.step(`Scaffolding ${context.template} starter.`, { output });
|
|
1897
|
-
await scaffoldCreateFrameworkTemplate({
|
|
1898
|
-
projectDir: context.targetDirectory,
|
|
1899
|
-
projectName: context.projectPackageName,
|
|
1900
|
-
template: context.template,
|
|
1901
|
-
provider: context.prismaSetupContext.databaseProvider,
|
|
1902
|
-
authoring: context.prismaSetupContext.authoring,
|
|
1903
|
-
packageManager: context.prismaSetupContext.packageManager
|
|
1904
|
-
});
|
|
1905
|
-
if (context.prismaSetupContext.verbose) log.success("Starter files scaffolded.", { output });
|
|
1906
|
-
} catch (error) {
|
|
1907
|
-
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1908
|
-
return {
|
|
1909
|
-
ok: false,
|
|
1910
|
-
stage: "scaffold_template",
|
|
1911
|
-
reason: "template_scaffold_failed",
|
|
1912
|
-
error
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
try {
|
|
1916
|
-
await writeCreateTemplateDependencies({
|
|
1917
|
-
template: context.template,
|
|
1918
|
-
packageManager: context.prismaSetupContext.packageManager,
|
|
1919
|
-
projectDir: context.targetDirectory
|
|
1920
|
-
});
|
|
1921
|
-
} catch (error) {
|
|
1922
|
-
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1923
|
-
return {
|
|
1924
|
-
ok: false,
|
|
1925
|
-
stage: "scaffold_template",
|
|
1926
|
-
reason: "template_scaffold_failed",
|
|
1927
|
-
error
|
|
1928
|
-
};
|
|
1929
|
-
}
|
|
1930
|
-
const forceWarning = context.targetPathState.exists && !context.targetPathState.isEmptyDirectory && context.force ? `Used --force in non-empty directory ${formatPathForDisplay(context.targetDirectory)}.` : void 0;
|
|
1931
|
-
if (forceWarning) log.warn(forceWarning, { output });
|
|
1932
|
-
const nextSteps = formatPathForDisplay(context.targetDirectory) === "." ? [] : [{
|
|
1933
|
-
command: `cd ${formatPathForDisplay(context.targetDirectory)}`,
|
|
1934
|
-
description: "Enter your new project directory."
|
|
1935
|
-
}];
|
|
1936
|
-
try {
|
|
1937
|
-
const setupResult = await executePrismaSetupContext(context.prismaSetupContext, {
|
|
1938
|
-
prependNextSteps: nextSteps,
|
|
1939
|
-
projectDir: context.targetDirectory,
|
|
1940
|
-
projectName: context.projectPackageName,
|
|
1941
|
-
template: context.template,
|
|
1942
|
-
createdProjectPath: context.targetDirectory,
|
|
1943
|
-
includeDevNextStep: true,
|
|
1944
|
-
initializeGit: !context.targetPathState.exists || context.targetPathState.isEmptyDirectory,
|
|
1945
|
-
progressSpinner: createSpinner
|
|
1946
|
-
});
|
|
1947
|
-
if (!setupResult.ok) {
|
|
1948
|
-
if (setupResult.cancelled) return {
|
|
1949
|
-
ok: false,
|
|
1950
|
-
cancelled: true,
|
|
1951
|
-
stage: setupResult.stage,
|
|
1952
|
-
errorReported: setupResult.errorReported
|
|
1953
|
-
};
|
|
1954
|
-
return {
|
|
1955
|
-
ok: false,
|
|
1956
|
-
stage: setupResult.stage,
|
|
1957
|
-
reason: setupResult.reason,
|
|
1958
|
-
error: setupResult.error,
|
|
1959
|
-
errorReported: setupResult.errorReported
|
|
1960
|
-
};
|
|
1961
|
-
}
|
|
1962
|
-
const warnings = [...setupResult.warnings];
|
|
1963
|
-
if (forceWarning) warnings.unshift(forceWarning);
|
|
1964
|
-
return {
|
|
1965
|
-
ok: true,
|
|
1966
|
-
result: {
|
|
1967
|
-
schemaVersion: CREATE_PRISMA_RESULT_SCHEMA_VERSION,
|
|
1968
|
-
ok: true,
|
|
1969
|
-
project: getProjectResult(context),
|
|
1970
|
-
deployment: setupResult.deployment,
|
|
1971
|
-
nextSteps: setupResult.nextSteps,
|
|
1972
|
-
warnings
|
|
1973
|
-
}
|
|
1974
|
-
};
|
|
1975
|
-
} catch (error) {
|
|
1976
|
-
createSpinner?.error("Could not create Prisma 8 project.");
|
|
1977
|
-
return {
|
|
1978
|
-
ok: false,
|
|
1979
|
-
stage: "unknown",
|
|
1980
|
-
reason: getCreateFailureReason(error, "unexpected_error"),
|
|
1981
|
-
error
|
|
1982
|
-
};
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
//#endregion
|
|
1987
|
-
export { DatabaseProviderSchema as a, createCommandFailureResult as c, CreateTemplateSchema as i, AuthoringStyleSchema as n, PackageManagerSchema as o, CreateCommandInputSchema as r, CREATE_PRISMA_RESULT_SCHEMA_VERSION as s, runCreateCommand as t };
|