create-prisma 0.4.1 → 0.4.2-next.37.104.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -44
- package/dist/cli.mjs +1 -1
- package/dist/{create-H6Tk0JlE.mjs → create-BeFhatOc.mjs} +910 -993
- package/dist/index.d.mts +32 -39
- package/dist/index.mjs +3 -3
- package/package.json +7 -2
- package/templates/create/_shared/prisma/seed.ts.hbs +52 -0
- package/templates/create/astro/README.md.hbs +25 -15
- package/templates/create/astro/astro.config.mjs +6 -1
- package/templates/create/astro/deno.json.hbs +1 -1
- package/templates/create/astro/package.json.hbs +3 -2
- package/templates/create/astro/src/lib/prisma.ts.hbs +60 -40
- package/templates/create/astro/src/pages/api/users.ts.hbs +6 -22
- package/templates/create/astro/src/pages/index.astro.hbs +18 -55
- package/templates/create/elysia/README.md.hbs +27 -17
- package/templates/create/elysia/deno.json.hbs +1 -1
- package/templates/create/elysia/src/index.ts.hbs +15 -11
- package/templates/create/elysia/src/lib/prisma.ts.hbs +60 -43
- package/templates/create/elysia/tsconfig.json +1 -0
- package/templates/create/hono/README.md.hbs +27 -17
- package/templates/create/hono/deno.json.hbs +1 -1
- package/templates/create/hono/src/index.ts.hbs +9 -10
- package/templates/create/hono/src/lib/prisma.ts.hbs +60 -43
- package/templates/create/hono/tsconfig.json +1 -0
- package/templates/create/minimal/.yarnrc.yml.hbs +3 -0
- package/templates/create/minimal/README.md.hbs +39 -0
- package/templates/create/{turborepo → minimal}/deno.json.hbs +1 -1
- package/templates/create/minimal/package.json.hbs +13 -0
- package/templates/create/minimal/src/index.ts.hbs +43 -0
- package/templates/create/minimal/src/lib/prisma.ts.hbs +73 -0
- package/templates/create/{turborepo/apps/api → minimal}/tsconfig.json +3 -4
- package/templates/create/nest/README.md.hbs +28 -17
- package/templates/create/nest/deno.json.hbs +1 -1
- package/templates/create/nest/src/app.module.ts.hbs +5 -6
- package/templates/create/nest/src/lib/prisma.ts.hbs +60 -45
- package/templates/create/nest/src/prisma.service.ts.hbs +6 -5
- package/templates/create/nest/src/users.controller.ts.hbs +1 -2
- package/templates/create/nest/src/users.service.ts.hbs +2 -8
- package/templates/create/nest/tsconfig.json +1 -0
- package/templates/create/next/README.md.hbs +25 -13
- package/templates/create/next/deno.json.hbs +1 -1
- package/templates/create/next/src/app/page.tsx.hbs +16 -57
- package/templates/create/next/src/lib/prisma.ts.hbs +60 -40
- package/templates/create/next/tsconfig.json +1 -0
- package/templates/create/nuxt/README.md.hbs +26 -13
- package/templates/create/nuxt/app/pages/index.vue.hbs +12 -45
- package/templates/create/nuxt/deno.json.hbs +1 -1
- package/templates/create/nuxt/nuxt.config.ts +21 -0
- package/templates/create/nuxt/package.json.hbs +2 -1
- package/templates/create/nuxt/server/api/users.get.ts.hbs +4 -15
- package/templates/create/nuxt/server/utils/prisma.ts.hbs +60 -40
- package/templates/create/svelte/README.md.hbs +25 -15
- package/templates/create/svelte/deno.json.hbs +1 -1
- package/templates/create/svelte/package.json.hbs +1 -1
- package/templates/create/svelte/src/lib/server/prisma.ts.hbs +61 -41
- package/templates/create/svelte/src/routes/+page.server.ts.hbs +4 -15
- package/templates/create/svelte/src/routes/+page.svelte.hbs +13 -163
- package/templates/create/svelte/vite.config.ts +2 -1
- package/templates/create/tanstack-start/README.md.hbs +26 -13
- package/templates/create/tanstack-start/deno.json.hbs +1 -2
- package/templates/create/tanstack-start/package.json.hbs +1 -2
- package/templates/create/tanstack-start/src/lib/prisma.server.ts.hbs +60 -40
- package/templates/create/tanstack-start/src/routes/__root.tsx.hbs +2 -3
- package/templates/create/tanstack-start/src/routes/index.tsx.hbs +26 -77
- package/templates/create/tanstack-start/tsconfig.json +1 -0
- package/templates/create/tanstack-start/vite.config.ts +7 -1
- package/templates/create/astro/prisma/schema.prisma.hbs +0 -21
- package/templates/create/astro/prisma/seed.ts.hbs +0 -38
- package/templates/create/astro/prisma.config.ts +0 -13
- package/templates/create/elysia/prisma/schema.prisma.hbs +0 -25
- package/templates/create/elysia/prisma/seed.ts.hbs +0 -42
- package/templates/create/elysia/prisma.config.ts.hbs +0 -15
- package/templates/create/hono/prisma/schema.prisma.hbs +0 -25
- package/templates/create/hono/prisma/seed.ts.hbs +0 -42
- package/templates/create/hono/prisma.config.ts.hbs +0 -15
- package/templates/create/nest/prisma/schema.prisma.hbs +0 -25
- package/templates/create/nest/prisma/seed.ts.hbs +0 -44
- package/templates/create/nest/prisma.config.ts.hbs +0 -15
- package/templates/create/next/prisma/schema.prisma.hbs +0 -21
- package/templates/create/next/prisma/seed.ts.hbs +0 -38
- package/templates/create/next/prisma.config.ts +0 -13
- package/templates/create/nuxt/prisma/schema.prisma.hbs +0 -21
- package/templates/create/nuxt/prisma/seed.ts.hbs +0 -38
- package/templates/create/nuxt/prisma.config.ts +0 -13
- package/templates/create/svelte/prisma/schema.prisma.hbs +0 -21
- package/templates/create/svelte/prisma/seed.ts.hbs +0 -87
- package/templates/create/svelte/prisma.config.ts +0 -13
- package/templates/create/tanstack-start/prisma/schema.prisma.hbs +0 -21
- package/templates/create/tanstack-start/prisma/seed.ts.hbs +0 -37
- package/templates/create/tanstack-start/prisma.config.ts +0 -13
- package/templates/create/turborepo/README.md.hbs +0 -29
- package/templates/create/turborepo/apps/api/package.json.hbs +0 -19
- package/templates/create/turborepo/apps/api/src/index.ts.hbs +0 -51
- package/templates/create/turborepo/package.json.hbs +0 -24
- package/templates/create/turborepo/packages/db/package.json.hbs +0 -17
- package/templates/create/turborepo/packages/db/prisma/schema.prisma.hbs +0 -21
- package/templates/create/turborepo/packages/db/prisma/seed.ts.hbs +0 -38
- package/templates/create/turborepo/packages/db/prisma.config.ts +0 -13
- package/templates/create/turborepo/packages/db/src/client.ts.hbs +0 -58
- package/templates/create/turborepo/packages/db/src/index.ts +0 -2
- package/templates/create/turborepo/packages/db/tsconfig.json +0 -15
- package/templates/create/turborepo/turbo.json +0 -28
- /package/templates/create/{turborepo → elysia}/.yarnrc.yml.hbs +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { cancel, confirm, intro, isCancel, log,
|
|
2
|
+
import { cancel, confirm, intro, isCancel, log, note, outro, select, spinner, text } from "@clack/prompts";
|
|
3
3
|
import fs from "fs-extra";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import { PostHog } from "posthog-node";
|
|
5
8
|
import Handlebars from "handlebars";
|
|
6
9
|
import { existsSync } from "node:fs";
|
|
7
10
|
import { fileURLToPath } from "node:url";
|
|
8
11
|
import { z } from "zod";
|
|
9
12
|
import { execa } from "execa";
|
|
10
|
-
import { randomUUID } from "node:crypto";
|
|
11
|
-
import os from "node:os";
|
|
12
|
-
import { PostHog } from "posthog-node";
|
|
13
13
|
import { styleText } from "node:util";
|
|
14
14
|
|
|
15
15
|
//#region src/utils/runtime.ts
|
|
@@ -24,23 +24,60 @@ function requiresDotenvConfigImport(packageManager) {
|
|
|
24
24
|
//#region src/constants/dependencies.ts
|
|
25
25
|
const dependencyVersionMap = {
|
|
26
26
|
"@elysiajs/node": "^1.4.5",
|
|
27
|
-
"@
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"@prisma/adapter-better-sqlite3": "^7.4.0",
|
|
31
|
-
"@prisma/adapter-mssql": "^7.4.0",
|
|
32
|
-
"@types/node": "^24.3.0",
|
|
33
|
-
dotenv: "^17.2.3",
|
|
34
|
-
"node-gyp": "^11.5.0",
|
|
35
|
-
prisma: "^7.4.0",
|
|
27
|
+
"@types/node": "^25.6.2",
|
|
28
|
+
dotenv: "^17.4.2",
|
|
29
|
+
"mongodb-memory-server": "^11.1.0",
|
|
36
30
|
tsx: "^4.21.0"
|
|
37
31
|
};
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
const PRISMA_NEXT_DEFAULT_VERSION = "latest";
|
|
33
|
+
const PKG_PR_NEW_PREFIX = "pkg-pr-new:";
|
|
34
|
+
const PKG_PR_NEW_BASE_URL = "https://pkg.pr.new/prisma/prisma-next";
|
|
35
|
+
const DEFAULT_PRISMA_NEXT_SPEC = {
|
|
36
|
+
kind: "npm",
|
|
37
|
+
spec: PRISMA_NEXT_DEFAULT_VERSION
|
|
38
|
+
};
|
|
39
|
+
function parsePrismaNextVersionSpec(input) {
|
|
40
|
+
if (input === void 0) return DEFAULT_PRISMA_NEXT_SPEC;
|
|
41
|
+
const trimmed = input.trim();
|
|
42
|
+
if (trimmed.length === 0) return DEFAULT_PRISMA_NEXT_SPEC;
|
|
43
|
+
if (trimmed.startsWith(PKG_PR_NEW_PREFIX)) {
|
|
44
|
+
const ref = trimmed.slice(11).trim();
|
|
45
|
+
if (ref.length === 0) throw new Error(`Invalid --prisma-next-version value: '${input}'. Expected 'pkg-pr-new:<sha|branch|pr-number>'.`);
|
|
46
|
+
return {
|
|
47
|
+
kind: "pkg-pr-new",
|
|
48
|
+
ref
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: "npm",
|
|
53
|
+
spec: trimmed
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function isPrismaNextPackage(packageName) {
|
|
57
|
+
return packageName === "prisma-next" || packageName.startsWith("@prisma-next/");
|
|
58
|
+
}
|
|
59
|
+
function getPrismaNextPackageSpecifier(packageName, spec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
60
|
+
if (spec.kind === "pkg-pr-new") return `${PKG_PR_NEW_BASE_URL}/${packageName}@${spec.ref}`;
|
|
61
|
+
return `${packageName}@${spec.spec}`;
|
|
62
|
+
}
|
|
63
|
+
function getDependencyVersion(packageName, prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
64
|
+
if (isPrismaNextPackage(packageName)) {
|
|
65
|
+
if (prismaNextSpec.kind === "pkg-pr-new") return `${PKG_PR_NEW_BASE_URL}/${packageName}@${prismaNextSpec.ref}`;
|
|
66
|
+
return prismaNextSpec.spec;
|
|
67
|
+
}
|
|
68
|
+
return dependencyVersionMap[packageName];
|
|
69
|
+
}
|
|
70
|
+
function usesViteDevServer(template) {
|
|
71
|
+
return template === "astro" || template === "nuxt" || template === "svelte" || template === "tanstack-start";
|
|
40
72
|
}
|
|
41
73
|
function getCreateTemplateDependencies(template, packageManager) {
|
|
42
74
|
const targets = [];
|
|
43
|
-
if (template
|
|
75
|
+
if (usesViteDevServer(template)) targets.push({
|
|
76
|
+
packageJsonPath: "package.json",
|
|
77
|
+
dependencies: [],
|
|
78
|
+
devDependencies: ["@prisma-next/vite-plugin-contract-emit"]
|
|
79
|
+
});
|
|
80
|
+
if (template === "minimal" || template === "hono" || template === "elysia" || template === "nest") {
|
|
44
81
|
const runtimeDevDependencies = usesNodeStyleRuntime(packageManager) ? ["tsx"] : [];
|
|
45
82
|
if (template === "elysia" && packageManager !== "deno") targets.push({
|
|
46
83
|
packageJsonPath: "package.json",
|
|
@@ -53,23 +90,155 @@ function getCreateTemplateDependencies(template, packageManager) {
|
|
|
53
90
|
devDependencies: runtimeDevDependencies
|
|
54
91
|
});
|
|
55
92
|
}
|
|
56
|
-
if (template === "turborepo") targets.push({
|
|
57
|
-
packageJsonPath: "apps/api/package.json",
|
|
58
|
-
dependencies: [],
|
|
59
|
-
devDependencies: ["tsx"],
|
|
60
|
-
customDependencies: { "@repo/db": getWorkspaceDependencyVersion(packageManager) }
|
|
61
|
-
});
|
|
62
93
|
return targets;
|
|
63
94
|
}
|
|
64
95
|
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/telemetry/client.ts
|
|
98
|
+
const TELEMETRY_API_KEY = "phc_cmc85avbWyuJ2JyKdGPdv7dxXli8xLdWDBPbvIXWJfs";
|
|
99
|
+
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
100
|
+
const TELEMETRY_CONFIG_FILE = "telemetry.json";
|
|
101
|
+
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;
|
|
102
|
+
function isTruthyEnvValue(value) {
|
|
103
|
+
return [
|
|
104
|
+
"1",
|
|
105
|
+
"true",
|
|
106
|
+
"yes",
|
|
107
|
+
"on"
|
|
108
|
+
].includes(String(value ?? "").trim().toLowerCase());
|
|
109
|
+
}
|
|
110
|
+
function shouldDisableTelemetry() {
|
|
111
|
+
if (isTruthyEnvValue(process.env.CI) || isTruthyEnvValue(process.env.GITHUB_ACTIONS)) return true;
|
|
112
|
+
return process.env.CREATE_PRISMA_DISABLE_TELEMETRY !== void 0 || process.env.CREATE_PRISMA_TELEMETRY_DISABLED !== void 0 || process.env.DO_NOT_TRACK !== void 0;
|
|
113
|
+
}
|
|
114
|
+
function getTelemetryConfigDir() {
|
|
115
|
+
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "create-prisma");
|
|
116
|
+
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "create-prisma");
|
|
117
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "create-prisma");
|
|
118
|
+
}
|
|
119
|
+
async function getAnonymousId() {
|
|
120
|
+
const telemetryConfigPath = path.join(getTelemetryConfigDir(), TELEMETRY_CONFIG_FILE);
|
|
121
|
+
try {
|
|
122
|
+
const config = await fs.readJSON(telemetryConfigPath);
|
|
123
|
+
if (typeof config.anonymousId === "string" && UUID_V4_REGEX.test(config.anonymousId)) return config.anonymousId;
|
|
124
|
+
} catch {}
|
|
125
|
+
const anonymousId = randomUUID();
|
|
126
|
+
try {
|
|
127
|
+
await fs.ensureDir(path.dirname(telemetryConfigPath));
|
|
128
|
+
await fs.writeJSON(telemetryConfigPath, { anonymousId }, { spaces: 2 });
|
|
129
|
+
} catch {}
|
|
130
|
+
return anonymousId;
|
|
131
|
+
}
|
|
132
|
+
function getCommonProperties() {
|
|
133
|
+
return {
|
|
134
|
+
"cli-version": "0.4.2-next.37.104.1",
|
|
135
|
+
"node-version": process.version,
|
|
136
|
+
platform: process.platform,
|
|
137
|
+
arch: process.arch
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function sanitizeProperties(properties) {
|
|
141
|
+
return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0));
|
|
142
|
+
}
|
|
143
|
+
async function trackCliTelemetry(event, properties) {
|
|
144
|
+
if (shouldDisableTelemetry()) return;
|
|
145
|
+
let client;
|
|
146
|
+
try {
|
|
147
|
+
const distinctId = await getAnonymousId();
|
|
148
|
+
const sanitizedProperties = sanitizeProperties({
|
|
149
|
+
...getCommonProperties(),
|
|
150
|
+
...properties,
|
|
151
|
+
$process_person_profile: false
|
|
152
|
+
});
|
|
153
|
+
client = new PostHog(TELEMETRY_API_KEY, {
|
|
154
|
+
host: TELEMETRY_HOST,
|
|
155
|
+
disableGeoip: true,
|
|
156
|
+
flushAt: 1,
|
|
157
|
+
flushInterval: 0
|
|
158
|
+
});
|
|
159
|
+
await client.captureImmediate({
|
|
160
|
+
distinctId,
|
|
161
|
+
event,
|
|
162
|
+
properties: sanitizedProperties,
|
|
163
|
+
disableGeoip: true
|
|
164
|
+
});
|
|
165
|
+
} catch {} finally {
|
|
166
|
+
if (client) await client.shutdown().catch(() => {});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/telemetry/create.ts
|
|
172
|
+
function classifyPrismaNextSpec(spec) {
|
|
173
|
+
if (!spec || spec === DEFAULT_PRISMA_NEXT_SPEC) return "default";
|
|
174
|
+
if (spec.kind === "pkg-pr-new") return "pkg-pr-new";
|
|
175
|
+
if (spec.spec === PRISMA_NEXT_DEFAULT_VERSION) return "default";
|
|
176
|
+
return /^[0-9]/.test(spec.spec) ? "npm-version" : "npm-tag";
|
|
177
|
+
}
|
|
178
|
+
function getPrismaNextVersionSpecString(spec) {
|
|
179
|
+
if (!spec) return null;
|
|
180
|
+
return spec.kind === "pkg-pr-new" ? `pkg-pr-new:${spec.ref}` : spec.spec;
|
|
181
|
+
}
|
|
182
|
+
const CREATE_PRISMA_NEXT_COMPLETED_EVENT = "cli:create_prisma_next_command_completed";
|
|
183
|
+
const CREATE_PRISMA_NEXT_FAILED_EVENT = "cli:create_prisma_next_command_failed";
|
|
184
|
+
function getTargetDirectoryState(context) {
|
|
185
|
+
if (!context.targetPathState.exists) return "new";
|
|
186
|
+
if (context.targetPathState.isEmptyDirectory) return "empty_directory";
|
|
187
|
+
return "non_empty_directory";
|
|
188
|
+
}
|
|
189
|
+
function getBaseCreateProperties(input, context) {
|
|
190
|
+
const resolvedPrismaNextSpec = context?.prismaSetupContext.prismaNextSpec;
|
|
191
|
+
return {
|
|
192
|
+
command: "create",
|
|
193
|
+
"uses-defaults": input.yes === true,
|
|
194
|
+
verbose: input.verbose === true,
|
|
195
|
+
force: input.force === true,
|
|
196
|
+
template: context?.template ?? input.template ?? null,
|
|
197
|
+
"database-provider": context?.prismaSetupContext.databaseProvider ?? input.provider ?? null,
|
|
198
|
+
"authoring-style": context?.prismaSetupContext.authoring ?? input.authoring ?? null,
|
|
199
|
+
"package-manager": context?.prismaSetupContext.packageManager ?? input.packageManager ?? null,
|
|
200
|
+
"should-install": context?.prismaSetupContext.shouldInstall ?? input.install ?? null,
|
|
201
|
+
"should-emit": context?.prismaSetupContext.shouldEmit ?? input.emit ?? null,
|
|
202
|
+
"uses-prisma-postgres": context?.prismaSetupContext.shouldUsePrismaPostgres ?? input.prismaPostgres ?? null,
|
|
203
|
+
"target-directory-state": context ? getTargetDirectoryState(context) : null,
|
|
204
|
+
"prisma-next-version-kind": classifyPrismaNextSpec(resolvedPrismaNextSpec),
|
|
205
|
+
"prisma-next-version-spec": getPrismaNextVersionSpecString(resolvedPrismaNextSpec) ?? input.prismaNextVersion ?? null
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function getErrorName(error) {
|
|
209
|
+
if (error instanceof Error) return error.name;
|
|
210
|
+
return error === void 0 ? null : "UnknownError";
|
|
211
|
+
}
|
|
212
|
+
function getErrorCode(error) {
|
|
213
|
+
if (typeof error !== "object" || error === null) return null;
|
|
214
|
+
const exitCode = Reflect.get(error, "exitCode");
|
|
215
|
+
if (typeof exitCode === "number") return exitCode;
|
|
216
|
+
const code = Reflect.get(error, "code");
|
|
217
|
+
return typeof code === "number" || typeof code === "string" ? code : null;
|
|
218
|
+
}
|
|
219
|
+
async function trackCreateCompleted(params) {
|
|
220
|
+
await trackCliTelemetry(CREATE_PRISMA_NEXT_COMPLETED_EVENT, {
|
|
221
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
222
|
+
"duration-ms": params.durationMs
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async function trackCreateFailed(params) {
|
|
226
|
+
await trackCliTelemetry(CREATE_PRISMA_NEXT_FAILED_EVENT, {
|
|
227
|
+
...getBaseCreateProperties(params.input, params.context),
|
|
228
|
+
"duration-ms": params.durationMs,
|
|
229
|
+
"failure-stage": params.stage,
|
|
230
|
+
"error-name": getErrorName(params.error),
|
|
231
|
+
"error-code": getErrorCode(params.error)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
65
235
|
//#endregion
|
|
66
236
|
//#region src/types.ts
|
|
67
|
-
const
|
|
237
|
+
const databaseProviderInputs = [
|
|
238
|
+
"postgres",
|
|
68
239
|
"postgresql",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"sqlserver",
|
|
72
|
-
"cockroachdb"
|
|
240
|
+
"mongo",
|
|
241
|
+
"mongodb"
|
|
73
242
|
];
|
|
74
243
|
const packageManagers = [
|
|
75
244
|
"npm",
|
|
@@ -78,8 +247,9 @@ const packageManagers = [
|
|
|
78
247
|
"bun",
|
|
79
248
|
"deno"
|
|
80
249
|
];
|
|
81
|
-
const
|
|
250
|
+
const authoringStyles = ["psl", "typescript"];
|
|
82
251
|
const createTemplates = [
|
|
252
|
+
"minimal",
|
|
83
253
|
"hono",
|
|
84
254
|
"elysia",
|
|
85
255
|
"nest",
|
|
@@ -87,56 +257,36 @@ const createTemplates = [
|
|
|
87
257
|
"svelte",
|
|
88
258
|
"astro",
|
|
89
259
|
"nuxt",
|
|
90
|
-
"tanstack-start"
|
|
91
|
-
"turborepo"
|
|
260
|
+
"tanstack-start"
|
|
92
261
|
];
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const extensionTargets = [
|
|
100
|
-
"vscode",
|
|
101
|
-
"cursor",
|
|
102
|
-
"windsurf"
|
|
103
|
-
];
|
|
104
|
-
const prismaSkillNames = [
|
|
105
|
-
"prisma-cli",
|
|
106
|
-
"prisma-client-api",
|
|
107
|
-
"prisma-database-setup",
|
|
108
|
-
"prisma-upgrade-v7",
|
|
109
|
-
"prisma-postgres"
|
|
110
|
-
];
|
|
111
|
-
const DatabaseProviderSchema = z.enum(databaseProviders);
|
|
262
|
+
function normalizeDatabaseProvider(value) {
|
|
263
|
+
if (value === "postgresql") return "postgres";
|
|
264
|
+
if (value === "mongodb") return "mongo";
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
const DatabaseProviderSchema = z.enum(databaseProviderInputs).transform(normalizeDatabaseProvider);
|
|
112
268
|
const PackageManagerSchema = z.enum(packageManagers);
|
|
113
|
-
const
|
|
269
|
+
const AuthoringStyleSchema = z.enum(authoringStyles);
|
|
114
270
|
const CreateTemplateSchema = z.enum(createTemplates);
|
|
115
|
-
const CreateAddonSchema = z.enum(createAddons);
|
|
116
|
-
const AddonInstallScopeSchema = z.enum(addonInstallScopes);
|
|
117
|
-
const ExtensionTargetSchema = z.enum(extensionTargets);
|
|
118
|
-
const PrismaSkillNameSchema = z.enum(prismaSkillNames);
|
|
119
271
|
const DatabaseUrlSchema = z.string().trim().min(1, "Please enter a valid database URL");
|
|
120
272
|
const CommonCommandOptionsSchema = z.object({
|
|
121
273
|
yes: z.boolean().optional().describe("Skip prompts and accept default choices"),
|
|
122
274
|
verbose: z.boolean().optional().describe("Show verbose command output during setup")
|
|
123
275
|
});
|
|
124
276
|
const PrismaSetupOptionsSchema = z.object({
|
|
125
|
-
provider: DatabaseProviderSchema.optional().describe("
|
|
277
|
+
provider: DatabaseProviderSchema.optional().describe("Prisma Next database target: PostgreSQL relational models or MongoDB document models"),
|
|
278
|
+
authoring: AuthoringStyleSchema.optional().describe("Contract authoring style"),
|
|
126
279
|
packageManager: PackageManagerSchema.optional().describe("Package manager used for dependency installation"),
|
|
127
|
-
prismaPostgres: z.boolean().optional().describe("Provision Prisma Postgres with create-db when
|
|
280
|
+
prismaPostgres: z.boolean().optional().describe("Provision Prisma Postgres with create-db when target is postgres"),
|
|
128
281
|
databaseUrl: DatabaseUrlSchema.optional().describe("DATABASE_URL value"),
|
|
129
282
|
install: z.boolean().optional().describe("Install dependencies with selected package manager"),
|
|
130
|
-
|
|
131
|
-
|
|
283
|
+
emit: z.boolean().optional().describe("Emit Prisma Next contract artifacts after scaffolding"),
|
|
284
|
+
prismaNextVersion: z.string().trim().min(1).optional().describe("Prisma Next version, npm dist-tag, or 'pkg-pr-new:<sha|branch|pr>' (default: latest)")
|
|
132
285
|
});
|
|
133
286
|
const PrismaSetupCommandInputSchema = CommonCommandOptionsSchema.extend(PrismaSetupOptionsSchema.shape);
|
|
134
287
|
const CreateScaffoldOptionsSchema = z.object({
|
|
135
288
|
name: z.string().trim().min(1, "Please enter a valid project name").optional().describe("Project name / directory"),
|
|
136
289
|
template: CreateTemplateSchema.optional().describe("Project template"),
|
|
137
|
-
skills: z.boolean().optional().describe("Enable skills addon"),
|
|
138
|
-
mcp: z.boolean().optional().describe("Enable MCP addon"),
|
|
139
|
-
extension: z.boolean().optional().describe("Enable extension addon"),
|
|
140
290
|
force: z.boolean().optional().describe("Allow scaffolding into a non-empty target directory")
|
|
141
291
|
});
|
|
142
292
|
const CreateCommandInputSchema = PrismaSetupCommandInputSchema.extend(CreateScaffoldOptionsSchema.shape);
|
|
@@ -221,13 +371,17 @@ function getPackageManagerManifestValue(packageManager) {
|
|
|
221
371
|
return packageManagerManifestValues[packageManager];
|
|
222
372
|
}
|
|
223
373
|
function getDenoPrismaSpecifier() {
|
|
224
|
-
return
|
|
374
|
+
return "npm:prisma-next";
|
|
375
|
+
}
|
|
376
|
+
function getDenoNpmSpecifier(packageSpecifier) {
|
|
377
|
+
return `npm:${packageSpecifier.replace(/@latest$/, "")}`;
|
|
225
378
|
}
|
|
226
379
|
function getDenoAllowedScriptSpecifiers() {
|
|
227
380
|
return [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
381
|
+
"npm:prisma-next",
|
|
382
|
+
"npm:@prisma-next/postgres",
|
|
383
|
+
"npm:@prisma-next/mongo",
|
|
384
|
+
"npm:mongodb-memory-server"
|
|
231
385
|
].join(",");
|
|
232
386
|
}
|
|
233
387
|
function getInstallCommand(packageManager) {
|
|
@@ -311,7 +465,7 @@ function getPackageExecutionArgs(packageManager, commandArgs) {
|
|
|
311
465
|
args: [
|
|
312
466
|
"run",
|
|
313
467
|
"-A",
|
|
314
|
-
|
|
468
|
+
getDenoNpmSpecifier(packageName),
|
|
315
469
|
...args
|
|
316
470
|
]
|
|
317
471
|
};
|
|
@@ -326,26 +480,46 @@ function getPackageExecutionCommand(packageManager, commandArgs) {
|
|
|
326
480
|
const execution = getPackageExecutionArgs(packageManager, commandArgs);
|
|
327
481
|
return [execution.command, ...execution.args].join(" ");
|
|
328
482
|
}
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
"
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
483
|
+
function getLocalPackageBinaryArgs(packageManager, binaryName, binaryArgs) {
|
|
484
|
+
switch (packageManager) {
|
|
485
|
+
case "pnpm": return {
|
|
486
|
+
command: "pnpm",
|
|
487
|
+
args: [
|
|
488
|
+
"exec",
|
|
489
|
+
binaryName,
|
|
490
|
+
...binaryArgs
|
|
491
|
+
]
|
|
492
|
+
};
|
|
493
|
+
case "yarn": return {
|
|
494
|
+
command: "yarn",
|
|
495
|
+
args: [binaryName, ...binaryArgs]
|
|
496
|
+
};
|
|
497
|
+
case "bun": return {
|
|
498
|
+
command: "bun",
|
|
499
|
+
args: [binaryName, ...binaryArgs]
|
|
500
|
+
};
|
|
501
|
+
case "deno": return {
|
|
502
|
+
command: "deno",
|
|
503
|
+
args: [
|
|
504
|
+
"run",
|
|
505
|
+
"-A",
|
|
506
|
+
`npm:${binaryName}`,
|
|
507
|
+
...binaryArgs
|
|
508
|
+
]
|
|
509
|
+
};
|
|
510
|
+
default: return {
|
|
511
|
+
command: "npm",
|
|
512
|
+
args: [
|
|
513
|
+
"exec",
|
|
514
|
+
binaryName,
|
|
515
|
+
"--",
|
|
516
|
+
...binaryArgs
|
|
517
|
+
]
|
|
518
|
+
};
|
|
519
|
+
}
|
|
346
520
|
}
|
|
347
|
-
function
|
|
348
|
-
const execution =
|
|
521
|
+
function getLocalPackageBinaryCommand(packageManager, binaryName, binaryArgs) {
|
|
522
|
+
const execution = getLocalPackageBinaryArgs(packageManager, binaryName, binaryArgs);
|
|
349
523
|
return [execution.command, ...execution.args].join(" ");
|
|
350
524
|
}
|
|
351
525
|
|
|
@@ -431,97 +605,133 @@ async function renderTemplateTree(opts) {
|
|
|
431
605
|
function getCreateTemplateDir(template) {
|
|
432
606
|
return resolveTemplatesDir(`templates/create/${template}`);
|
|
433
607
|
}
|
|
434
|
-
function
|
|
608
|
+
function getCreateSharedTemplateDir() {
|
|
609
|
+
return resolveTemplatesDir("templates/create/_shared");
|
|
610
|
+
}
|
|
611
|
+
function createTemplateContext(projectName, template, provider, authoring, packageManager) {
|
|
435
612
|
return {
|
|
436
613
|
projectName,
|
|
614
|
+
template,
|
|
437
615
|
provider,
|
|
438
|
-
|
|
616
|
+
authoring,
|
|
439
617
|
packageManager
|
|
440
618
|
};
|
|
441
619
|
}
|
|
442
620
|
async function scaffoldCreateTemplate(opts) {
|
|
443
|
-
const { projectDir, projectName, template, provider,
|
|
621
|
+
const { projectDir, projectName, template, provider, authoring, packageManager } = opts;
|
|
622
|
+
const templateRoot = getCreateTemplateDir(template);
|
|
623
|
+
const sharedTemplateRoot = getCreateSharedTemplateDir();
|
|
624
|
+
const context = createTemplateContext(projectName, template, provider, authoring, packageManager);
|
|
625
|
+
await renderTemplateTree({
|
|
626
|
+
templateRoot: sharedTemplateRoot,
|
|
627
|
+
outputDir: projectDir,
|
|
628
|
+
context
|
|
629
|
+
});
|
|
444
630
|
await renderTemplateTree({
|
|
445
|
-
templateRoot
|
|
631
|
+
templateRoot,
|
|
446
632
|
outputDir: projectDir,
|
|
447
|
-
context
|
|
633
|
+
context
|
|
448
634
|
});
|
|
449
635
|
}
|
|
450
636
|
|
|
451
637
|
//#endregion
|
|
452
638
|
//#region src/constants/db-packages.ts
|
|
453
|
-
function getDbPackages(provider) {
|
|
639
|
+
function getDbPackages(provider, _packageManager) {
|
|
454
640
|
switch (provider) {
|
|
455
|
-
case "
|
|
456
|
-
case "
|
|
457
|
-
case "mysql": return "@prisma/adapter-mariadb";
|
|
458
|
-
case "sqlite": return "@prisma/adapter-better-sqlite3";
|
|
459
|
-
case "sqlserver": return "@prisma/adapter-mssql";
|
|
641
|
+
case "postgres": return "@prisma-next/postgres";
|
|
642
|
+
case "mongo": return "@prisma-next/mongo";
|
|
460
643
|
default: {
|
|
461
644
|
const exhaustiveCheck = provider;
|
|
462
|
-
throw new Error(`Unsupported
|
|
645
|
+
throw new Error(`Unsupported Prisma Next target: ${String(exhaustiveCheck)}`);
|
|
463
646
|
}
|
|
464
647
|
}
|
|
465
648
|
}
|
|
466
649
|
|
|
467
650
|
//#endregion
|
|
468
651
|
//#region src/tasks/install.ts
|
|
469
|
-
function
|
|
652
|
+
function getPrismaNextScriptMap(packageManager) {
|
|
470
653
|
if (packageManager === "deno") {
|
|
471
|
-
const
|
|
654
|
+
const prismaNextCli = `deno run -A --env-file=.env ${getDenoPrismaSpecifier()}`;
|
|
472
655
|
return {
|
|
473
|
-
"
|
|
474
|
-
"db:
|
|
475
|
-
"db:
|
|
476
|
-
"db:
|
|
656
|
+
"contract:emit": `${prismaNextCli} contract emit`,
|
|
657
|
+
"db:init": `${prismaNextCli} db init`,
|
|
658
|
+
"db:update": `${prismaNextCli} db update`,
|
|
659
|
+
"db:verify": `${prismaNextCli} db verify`,
|
|
660
|
+
"db:seed": "deno run -A --env-file=.env prisma/seed.ts",
|
|
661
|
+
"migration:plan": `${prismaNextCli} migration plan`,
|
|
662
|
+
migrate: `${prismaNextCli} migrate`,
|
|
663
|
+
"migration:status": `${prismaNextCli} migration status`,
|
|
664
|
+
"migration:show": `${prismaNextCli} migration show`
|
|
477
665
|
};
|
|
478
666
|
}
|
|
479
667
|
if (packageManager === "bun") {
|
|
480
|
-
const
|
|
668
|
+
const prismaNextCli = "bun prisma-next";
|
|
481
669
|
return {
|
|
482
|
-
"
|
|
483
|
-
"db:
|
|
484
|
-
"db:
|
|
485
|
-
"db:
|
|
670
|
+
"contract:emit": `${prismaNextCli} contract emit`,
|
|
671
|
+
"db:init": `${prismaNextCli} db init`,
|
|
672
|
+
"db:update": `${prismaNextCli} db update`,
|
|
673
|
+
"db:verify": `${prismaNextCli} db verify`,
|
|
674
|
+
"db:seed": "bun prisma/seed.ts",
|
|
675
|
+
"migration:plan": `${prismaNextCli} migration plan`,
|
|
676
|
+
migrate: `${prismaNextCli} migrate`,
|
|
677
|
+
"migration:status": `${prismaNextCli} migration status`,
|
|
678
|
+
"migration:show": `${prismaNextCli} migration show`
|
|
486
679
|
};
|
|
487
680
|
}
|
|
488
681
|
return {
|
|
489
|
-
"
|
|
490
|
-
"db:
|
|
491
|
-
"db:
|
|
492
|
-
"db:
|
|
682
|
+
"contract:emit": "prisma-next contract emit",
|
|
683
|
+
"db:init": "prisma-next db init",
|
|
684
|
+
"db:update": "prisma-next db update",
|
|
685
|
+
"db:verify": "prisma-next db verify",
|
|
686
|
+
"db:seed": "tsx prisma/seed.ts",
|
|
687
|
+
"migration:plan": "prisma-next migration plan",
|
|
688
|
+
migrate: "prisma-next migrate",
|
|
689
|
+
"migration:status": "prisma-next migration status",
|
|
690
|
+
"migration:show": "prisma-next migration show"
|
|
493
691
|
};
|
|
494
692
|
}
|
|
495
|
-
function getVersion(packageName) {
|
|
496
|
-
return dependencyVersionMap[packageName];
|
|
497
|
-
}
|
|
498
693
|
function unique(items) {
|
|
499
694
|
return [...new Set(items)];
|
|
500
695
|
}
|
|
501
696
|
function sortRecord(record) {
|
|
502
697
|
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
503
698
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
699
|
+
function getGeneratedContractTypePackages(provider) {
|
|
700
|
+
if (provider === "mongo") return [
|
|
701
|
+
"@prisma-next/adapter-mongo",
|
|
702
|
+
"@prisma-next/contract",
|
|
703
|
+
"@prisma-next/mongo-contract"
|
|
704
|
+
];
|
|
705
|
+
return [
|
|
706
|
+
"@prisma-next/adapter-postgres",
|
|
707
|
+
"@prisma-next/contract",
|
|
708
|
+
"@prisma-next/sql-contract",
|
|
709
|
+
"@prisma-next/target-postgres"
|
|
710
|
+
];
|
|
711
|
+
}
|
|
712
|
+
function getTypeScriptContractPackages(provider) {
|
|
713
|
+
if (provider === "mongo") return [
|
|
714
|
+
...getGeneratedContractTypePackages(provider),
|
|
715
|
+
"@prisma-next/family-mongo",
|
|
716
|
+
"@prisma-next/mongo-contract-ts",
|
|
717
|
+
"@prisma-next/target-mongo"
|
|
718
|
+
];
|
|
719
|
+
return [
|
|
720
|
+
...getGeneratedContractTypePackages(provider),
|
|
721
|
+
"@prisma-next/family-sql",
|
|
722
|
+
"@prisma-next/sql-contract-ts"
|
|
723
|
+
];
|
|
724
|
+
}
|
|
725
|
+
function getMigrationPackages(provider) {
|
|
726
|
+
if (provider === "mongo") return ["@prisma-next/family-mongo", "@prisma-next/target-mongo"];
|
|
727
|
+
return ["@prisma-next/target-postgres"];
|
|
728
|
+
}
|
|
729
|
+
function getOrmTypePackages(provider) {
|
|
730
|
+
if (provider === "mongo") return ["@prisma-next/mongo-orm"];
|
|
731
|
+
return ["@prisma-next/sql-orm-client"];
|
|
522
732
|
}
|
|
523
733
|
async function addPackageDependency(opts) {
|
|
524
|
-
const { dependencies = [], devDependencies = [], customDependencies = {}, scripts = {}, scriptMode, projectDir } = opts;
|
|
734
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, scripts = {}, scriptMode, projectDir, prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC } = opts;
|
|
525
735
|
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
526
736
|
if (!await fs.pathExists(pkgJsonPath)) throw new Error(`No package.json found in ${projectDir}. Run this command inside an existing JavaScript/TypeScript project.`);
|
|
527
737
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
@@ -529,12 +739,12 @@ async function addPackageDependency(opts) {
|
|
|
529
739
|
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
530
740
|
if (!pkgJson.scripts) pkgJson.scripts = {};
|
|
531
741
|
for (const pkgName of unique(dependencies)) {
|
|
532
|
-
const version =
|
|
742
|
+
const version = getDependencyVersion(pkgName, prismaNextSpec);
|
|
533
743
|
if (version) pkgJson.dependencies[pkgName] = version;
|
|
534
744
|
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
535
745
|
}
|
|
536
746
|
for (const pkgName of unique(devDependencies)) {
|
|
537
|
-
const version =
|
|
747
|
+
const version = getDependencyVersion(pkgName, prismaNextSpec);
|
|
538
748
|
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
539
749
|
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
540
750
|
}
|
|
@@ -550,22 +760,27 @@ async function addPackageDependency(opts) {
|
|
|
550
760
|
pkgJson.devDependencies = sortRecord(pkgJson.devDependencies);
|
|
551
761
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
552
762
|
}
|
|
553
|
-
async function writePrismaDependencies(provider, packageManager, projectDir = process.cwd()) {
|
|
554
|
-
const dependencies = ["
|
|
555
|
-
const devDependencies = [
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
763
|
+
async function writePrismaDependencies(provider, packageManager, authoring, projectDir = process.cwd(), prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
764
|
+
const dependencies = [getDbPackages(provider, packageManager), "dotenv"];
|
|
765
|
+
const devDependencies = [
|
|
766
|
+
"prisma-next",
|
|
767
|
+
"@prisma-next/cli",
|
|
768
|
+
"@types/node"
|
|
769
|
+
];
|
|
770
|
+
devDependencies.push(...getGeneratedContractTypePackages(provider));
|
|
771
|
+
devDependencies.push(...getMigrationPackages(provider));
|
|
772
|
+
devDependencies.push(...getOrmTypePackages(provider));
|
|
773
|
+
if (authoring === "typescript") devDependencies.push(...getTypeScriptContractPackages(provider));
|
|
559
774
|
await addPackageDependency({
|
|
560
775
|
dependencies,
|
|
561
776
|
devDependencies,
|
|
562
|
-
scripts:
|
|
563
|
-
|
|
564
|
-
|
|
777
|
+
scripts: getPrismaNextScriptMap(packageManager),
|
|
778
|
+
projectDir,
|
|
779
|
+
prismaNextSpec
|
|
565
780
|
});
|
|
566
781
|
}
|
|
567
782
|
async function writeCreateTemplateDependencies(opts) {
|
|
568
|
-
const { template, packageManager, projectDir = process.cwd() } = opts;
|
|
783
|
+
const { template, packageManager, projectDir = process.cwd(), prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC } = opts;
|
|
569
784
|
const targets = getCreateTemplateDependencies(template, packageManager);
|
|
570
785
|
for (const dependencyTarget of targets) {
|
|
571
786
|
const targetDirectory = path.join(projectDir, path.dirname(dependencyTarget.packageJsonPath));
|
|
@@ -573,15 +788,18 @@ async function writeCreateTemplateDependencies(opts) {
|
|
|
573
788
|
dependencies: dependencyTarget.dependencies,
|
|
574
789
|
devDependencies: dependencyTarget.devDependencies,
|
|
575
790
|
customDependencies: dependencyTarget.customDependencies,
|
|
576
|
-
projectDir: targetDirectory
|
|
791
|
+
projectDir: targetDirectory,
|
|
792
|
+
prismaNextSpec
|
|
577
793
|
});
|
|
578
794
|
}
|
|
579
795
|
}
|
|
580
796
|
async function installProjectDependencies(packageManager, projectDir = process.cwd(), options = {}) {
|
|
581
797
|
const verbose = options.verbose === true;
|
|
582
798
|
const installCommand = getInstallArgs(packageManager);
|
|
799
|
+
const env = packageManager === "yarn" ? { YARN_ENABLE_IMMUTABLE_INSTALLS: "false" } : void 0;
|
|
583
800
|
await execa(installCommand.command, installCommand.args, {
|
|
584
801
|
cwd: projectDir,
|
|
802
|
+
env,
|
|
585
803
|
stdio: verbose ? "inherit" : "pipe"
|
|
586
804
|
});
|
|
587
805
|
}
|
|
@@ -643,55 +861,233 @@ function getCreateDbCommand(packageManager) {
|
|
|
643
861
|
|
|
644
862
|
//#endregion
|
|
645
863
|
//#region src/tasks/setup-prisma.ts
|
|
646
|
-
const DEFAULT_DATABASE_PROVIDER = "
|
|
647
|
-
const
|
|
648
|
-
const DEFAULT_PRISMA_POSTGRES = true;
|
|
864
|
+
const DEFAULT_DATABASE_PROVIDER = "postgres";
|
|
865
|
+
const DEFAULT_AUTHORING = "psl";
|
|
649
866
|
const DEFAULT_INSTALL = true;
|
|
650
|
-
const
|
|
867
|
+
const DEFAULT_EMIT = true;
|
|
868
|
+
const DEFAULT_INTERACTIVE_PRISMA_POSTGRES = true;
|
|
869
|
+
const DEFAULT_AUTOMATED_PRISMA_POSTGRES = false;
|
|
870
|
+
const MONGO_MEMORY_SERVER_SCRIPT = `import { spawn } from "node:child_process";
|
|
871
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
872
|
+
import path from "node:path";
|
|
873
|
+
|
|
874
|
+
const defaultDatabaseUrl = "mongodb://localhost:27017/mydb?replicaSet=rs0&directConnection=true";
|
|
875
|
+
const dataRoot = path.resolve(process.env.MONGO_DB_PATH ?? ".mongo-data");
|
|
876
|
+
const dbPath = path.join(dataRoot, "db");
|
|
877
|
+
const pidFile = path.join(dataRoot, "mongo.pid");
|
|
878
|
+
const logFile = path.join(dataRoot, "mongo.log");
|
|
879
|
+
const readyTimeoutMs = Number(process.env.MONGO_READY_TIMEOUT_MS ?? 60_000);
|
|
880
|
+
|
|
881
|
+
function getMongoConfig() {
|
|
882
|
+
const databaseUrl = process.env.DATABASE_URL ?? defaultDatabaseUrl;
|
|
883
|
+
const url = new URL(databaseUrl);
|
|
884
|
+
if (url.protocol !== "mongodb:") {
|
|
885
|
+
throw new Error("DATABASE_URL must use the mongodb:// protocol.");
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const port = Number(url.port || "27017");
|
|
889
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
890
|
+
throw new Error(\`DATABASE_URL has an invalid MongoDB port: \${url.port}\`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
databaseUrl,
|
|
895
|
+
port,
|
|
896
|
+
replSetName: url.searchParams.get("replicaSet") || "rs0",
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function readPid() {
|
|
901
|
+
if (!existsSync(pidFile)) return null;
|
|
902
|
+
const raw = readFileSync(pidFile, "utf8").trim();
|
|
903
|
+
const pid = Number(raw);
|
|
904
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function isAlive(pid) {
|
|
908
|
+
try {
|
|
909
|
+
process.kill(pid, 0);
|
|
910
|
+
return true;
|
|
911
|
+
} catch {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function getChildCommand() {
|
|
917
|
+
const scriptPath = path.resolve(process.argv[1] ?? "");
|
|
918
|
+
const versions = process.versions;
|
|
919
|
+
if (versions.deno) return { command: process.execPath, args: ["run", "-A", scriptPath, "_run"] };
|
|
920
|
+
return { command: process.execPath, args: [scriptPath, "_run"] };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
async function runServer() {
|
|
924
|
+
mkdirSync(dbPath, { recursive: true });
|
|
925
|
+
const config = getMongoConfig();
|
|
926
|
+
const memoryServer = await import("mongodb-memory-server");
|
|
927
|
+
const { MongoMemoryReplSet } = memoryServer.default ?? memoryServer;
|
|
928
|
+
const replSet = await MongoMemoryReplSet.create({
|
|
929
|
+
replSet: { name: config.replSetName, count: 1 },
|
|
930
|
+
instanceOpts: [{ port: config.port, storageEngine: "wiredTiger", dbPath }],
|
|
931
|
+
});
|
|
932
|
+
console.log(\`MongoDB server ready for \${config.databaseUrl}\`);
|
|
933
|
+
console.log(\`Data directory: \${dbPath}\`);
|
|
934
|
+
const shutdown = async () => {
|
|
935
|
+
await replSet.stop();
|
|
936
|
+
process.exit(0);
|
|
937
|
+
};
|
|
938
|
+
process.on("SIGINT", shutdown);
|
|
939
|
+
process.on("SIGTERM", shutdown);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async function up() {
|
|
943
|
+
mkdirSync(dataRoot, { recursive: true });
|
|
944
|
+
const existing = readPid();
|
|
945
|
+
if (existing !== null && isAlive(existing)) {
|
|
946
|
+
console.log(\`MongoDB is already running (PID \${existing}). Use \\\`db:down\\\` to stop.\`);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (existing !== null) rmSync(pidFile, { force: true });
|
|
950
|
+
|
|
951
|
+
writeFileSync(logFile, "");
|
|
952
|
+
const logFd = openSync(logFile, "a");
|
|
953
|
+
const { command, args } = getChildCommand();
|
|
954
|
+
const child = spawn(
|
|
955
|
+
command,
|
|
956
|
+
args,
|
|
957
|
+
{
|
|
958
|
+
detached: true,
|
|
959
|
+
stdio: ["ignore", logFd, logFd],
|
|
960
|
+
env: process.env,
|
|
961
|
+
},
|
|
962
|
+
);
|
|
963
|
+
closeSync(logFd);
|
|
964
|
+
if (typeof child.pid !== "number") throw new Error("Failed to spawn MongoDB child process.");
|
|
965
|
+
writeFileSync(pidFile, String(child.pid));
|
|
966
|
+
child.unref();
|
|
967
|
+
|
|
968
|
+
const start = Date.now();
|
|
969
|
+
while (Date.now() - start < readyTimeoutMs) {
|
|
970
|
+
if (!isAlive(child.pid)) {
|
|
971
|
+
console.error("MongoDB failed to start:");
|
|
972
|
+
console.error(readFileSync(logFile, "utf8"));
|
|
973
|
+
rmSync(pidFile, { force: true });
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
const log = readFileSync(logFile, "utf8");
|
|
977
|
+
if (log.includes("MongoDB server ready")) {
|
|
978
|
+
for (const line of log.split("\\n")) {
|
|
979
|
+
if (line.trim().length > 0) console.log(line);
|
|
980
|
+
}
|
|
981
|
+
console.log(\`Detached (PID \${child.pid}). Logs: \${logFile}\`);
|
|
982
|
+
console.log("Stop with \`db:down\` or wipe with \`db:reset\`.");
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
986
|
+
}
|
|
987
|
+
console.error(\`Timed out waiting for MongoDB after \${readyTimeoutMs}ms.\`);
|
|
988
|
+
console.error(readFileSync(logFile, "utf8"));
|
|
989
|
+
try {
|
|
990
|
+
process.kill(child.pid, "SIGTERM");
|
|
991
|
+
} catch {
|
|
992
|
+
// ignore
|
|
993
|
+
}
|
|
994
|
+
rmSync(pidFile, { force: true });
|
|
995
|
+
process.exit(1);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
async function down(wipe) {
|
|
999
|
+
const pid = readPid();
|
|
1000
|
+
if (pid !== null && isAlive(pid)) {
|
|
1001
|
+
process.kill(pid, "SIGTERM");
|
|
1002
|
+
const deadline = Date.now() + 10_000;
|
|
1003
|
+
while (Date.now() < deadline && isAlive(pid)) {
|
|
1004
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1005
|
+
}
|
|
1006
|
+
if (isAlive(pid)) {
|
|
1007
|
+
console.warn(\`MongoDB (PID \${pid}) did not exit within 10s; sending SIGKILL.\`);
|
|
1008
|
+
try {
|
|
1009
|
+
process.kill(pid, "SIGKILL");
|
|
1010
|
+
} catch {
|
|
1011
|
+
// ignore
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
console.log(\`Stopped MongoDB (PID \${pid}).\`);
|
|
1015
|
+
} else if (pid !== null) {
|
|
1016
|
+
console.log("MongoDB was not running (stale PID file).");
|
|
1017
|
+
} else {
|
|
1018
|
+
console.log("MongoDB is not running.");
|
|
1019
|
+
}
|
|
1020
|
+
rmSync(pidFile, { force: true });
|
|
1021
|
+
if (wipe) {
|
|
1022
|
+
rmSync(dataRoot, { recursive: true, force: true });
|
|
1023
|
+
console.log(\`Removed \${dataRoot}.\`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const cmd = process.argv[2] ?? "up";
|
|
1028
|
+
switch (cmd) {
|
|
1029
|
+
case "up":
|
|
1030
|
+
await up();
|
|
1031
|
+
break;
|
|
1032
|
+
case "down":
|
|
1033
|
+
await down(false);
|
|
1034
|
+
break;
|
|
1035
|
+
case "reset":
|
|
1036
|
+
await down(true);
|
|
1037
|
+
break;
|
|
1038
|
+
case "_run":
|
|
1039
|
+
await runServer();
|
|
1040
|
+
break;
|
|
1041
|
+
default:
|
|
1042
|
+
console.error(\`Unknown command: \${cmd}. Use: up | down | reset\`);
|
|
1043
|
+
process.exit(2);
|
|
1044
|
+
}
|
|
1045
|
+
`;
|
|
1046
|
+
function getMongoMemoryScripts(packageManager) {
|
|
1047
|
+
switch (packageManager) {
|
|
1048
|
+
case "bun": return {
|
|
1049
|
+
"db:up": "bun --env-file=.env scripts/mongo.mjs up",
|
|
1050
|
+
"db:down": "bun --env-file=.env scripts/mongo.mjs down",
|
|
1051
|
+
"db:reset": "bun --env-file=.env scripts/mongo.mjs reset"
|
|
1052
|
+
};
|
|
1053
|
+
case "deno": return {
|
|
1054
|
+
"db:up": "deno run -A --env-file=.env scripts/mongo.mjs up",
|
|
1055
|
+
"db:down": "deno run -A --env-file=.env scripts/mongo.mjs down",
|
|
1056
|
+
"db:reset": "deno run -A --env-file=.env scripts/mongo.mjs reset"
|
|
1057
|
+
};
|
|
1058
|
+
default: return {
|
|
1059
|
+
"db:up": "node --env-file=.env scripts/mongo.mjs up",
|
|
1060
|
+
"db:down": "node --env-file=.env scripts/mongo.mjs down",
|
|
1061
|
+
"db:reset": "node --env-file=.env scripts/mongo.mjs reset"
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
651
1065
|
const requiredPrismaFileGroups = [
|
|
652
|
-
["prisma/
|
|
653
|
-
["prisma
|
|
654
|
-
["prisma.config.ts", "packages/db/prisma.config.ts"],
|
|
1066
|
+
["prisma/contract.prisma", "prisma/contract.ts"],
|
|
1067
|
+
["prisma-next.config.ts"],
|
|
655
1068
|
[
|
|
656
1069
|
"src/lib/prisma.ts",
|
|
657
1070
|
"src/lib/prisma.server.ts",
|
|
658
1071
|
"src/lib/server/prisma.ts",
|
|
659
|
-
"server/utils/prisma.ts"
|
|
660
|
-
"packages/db/src/client.ts"
|
|
1072
|
+
"server/utils/prisma.ts"
|
|
661
1073
|
]
|
|
662
1074
|
];
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if (await fs.pathExists(path.join(monorepoDbDir, "prisma/schema.prisma"))) return monorepoDbDir;
|
|
666
|
-
return projectDir;
|
|
1075
|
+
function getContractPath(authoring) {
|
|
1076
|
+
return `prisma/contract${authoring === "typescript" ? ".ts" : ".prisma"}`;
|
|
667
1077
|
}
|
|
668
1078
|
async function promptForDatabaseProvider() {
|
|
669
1079
|
const databaseProvider = await select({
|
|
670
1080
|
message: "Select your database",
|
|
671
1081
|
initialValue: DEFAULT_DATABASE_PROVIDER,
|
|
672
|
-
options: [
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
value: "sqlite",
|
|
684
|
-
label: "SQLite"
|
|
685
|
-
},
|
|
686
|
-
{
|
|
687
|
-
value: "sqlserver",
|
|
688
|
-
label: "SQL Server"
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
value: "cockroachdb",
|
|
692
|
-
label: "CockroachDB"
|
|
693
|
-
}
|
|
694
|
-
]
|
|
1082
|
+
options: [{
|
|
1083
|
+
value: "postgres",
|
|
1084
|
+
label: "PostgreSQL",
|
|
1085
|
+
hint: "Relational models with typed ORM, relations, indexes, raw SQL"
|
|
1086
|
+
}, {
|
|
1087
|
+
value: "mongo",
|
|
1088
|
+
label: "MongoDB",
|
|
1089
|
+
hint: "Document models with typed ORM, indexes, aggregations"
|
|
1090
|
+
}]
|
|
695
1091
|
});
|
|
696
1092
|
if (isCancel(databaseProvider)) {
|
|
697
1093
|
cancel("Operation cancelled.");
|
|
@@ -699,10 +1095,48 @@ async function promptForDatabaseProvider() {
|
|
|
699
1095
|
}
|
|
700
1096
|
return DatabaseProviderSchema.parse(databaseProvider);
|
|
701
1097
|
}
|
|
1098
|
+
async function promptForAuthoringStyle() {
|
|
1099
|
+
const authoring = await select({
|
|
1100
|
+
message: "Choose contract authoring style",
|
|
1101
|
+
initialValue: DEFAULT_AUTHORING,
|
|
1102
|
+
options: [{
|
|
1103
|
+
value: "psl",
|
|
1104
|
+
label: "PSL",
|
|
1105
|
+
hint: "Schema syntax emits contract.json + types"
|
|
1106
|
+
}, {
|
|
1107
|
+
value: "typescript",
|
|
1108
|
+
label: "TypeScript",
|
|
1109
|
+
hint: "Builder API emits the same contract artifacts"
|
|
1110
|
+
}]
|
|
1111
|
+
});
|
|
1112
|
+
if (isCancel(authoring)) {
|
|
1113
|
+
cancel("Operation cancelled.");
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
return AuthoringStyleSchema.parse(authoring);
|
|
1117
|
+
}
|
|
1118
|
+
async function promptForPrismaPostgres() {
|
|
1119
|
+
const shouldUsePrismaPostgres = await confirm({
|
|
1120
|
+
message: "Provision a Prisma Postgres database?",
|
|
1121
|
+
active: "Provision Prisma Postgres",
|
|
1122
|
+
inactive: "Use my own database",
|
|
1123
|
+
initialValue: DEFAULT_INTERACTIVE_PRISMA_POSTGRES
|
|
1124
|
+
});
|
|
1125
|
+
if (isCancel(shouldUsePrismaPostgres)) {
|
|
1126
|
+
cancel("Operation cancelled.");
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
return Boolean(shouldUsePrismaPostgres);
|
|
1130
|
+
}
|
|
702
1131
|
function getPackageManagerHint(option, detected) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1132
|
+
const hint = {
|
|
1133
|
+
npm: "Node.js default",
|
|
1134
|
+
pnpm: "Fast, disk-efficient Node.js package manager",
|
|
1135
|
+
yarn: "Yarn package manager",
|
|
1136
|
+
bun: "Fast runtime + package manager",
|
|
1137
|
+
deno: "Deno runtime + task runner"
|
|
1138
|
+
}[option];
|
|
1139
|
+
return option === detected ? `Detected; ${hint}` : hint;
|
|
706
1140
|
}
|
|
707
1141
|
async function promptForPackageManager(detectedPackageManager) {
|
|
708
1142
|
const packageManager = await select({
|
|
@@ -744,7 +1178,9 @@ async function promptForPackageManager(detectedPackageManager) {
|
|
|
744
1178
|
}
|
|
745
1179
|
async function promptForDependencyInstall(packageManager) {
|
|
746
1180
|
const shouldInstall = await confirm({
|
|
747
|
-
message: `Install dependencies now with ${getInstallCommand(packageManager)}
|
|
1181
|
+
message: `Install dependencies now with ${getInstallCommand(packageManager)}? You can run it later.`,
|
|
1182
|
+
active: "Install now",
|
|
1183
|
+
inactive: "Skip for now",
|
|
748
1184
|
initialValue: true
|
|
749
1185
|
});
|
|
750
1186
|
if (isCancel(shouldInstall)) {
|
|
@@ -753,17 +1189,6 @@ async function promptForDependencyInstall(packageManager) {
|
|
|
753
1189
|
}
|
|
754
1190
|
return Boolean(shouldInstall);
|
|
755
1191
|
}
|
|
756
|
-
async function promptForPrismaPostgres() {
|
|
757
|
-
const shouldUsePrismaPostgres = await confirm({
|
|
758
|
-
message: "Use Prisma Postgres and auto-generate DATABASE_URL with create-db?",
|
|
759
|
-
initialValue: true
|
|
760
|
-
});
|
|
761
|
-
if (isCancel(shouldUsePrismaPostgres)) {
|
|
762
|
-
cancel("Operation cancelled.");
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
return Boolean(shouldUsePrismaPostgres);
|
|
766
|
-
}
|
|
767
1192
|
function getCommandErrorMessage(error) {
|
|
768
1193
|
if (error instanceof Error && "stderr" in error) {
|
|
769
1194
|
const stderr = String(error.stderr ?? "").trim();
|
|
@@ -775,17 +1200,29 @@ async function collectPrismaSetupContext(input, options = {}) {
|
|
|
775
1200
|
const projectDir = path.resolve(options.projectDir ?? process.cwd());
|
|
776
1201
|
const useDefaults = input.yes === true;
|
|
777
1202
|
const verbose = input.verbose === true;
|
|
778
|
-
const
|
|
1203
|
+
const shouldEmit = input.emit ?? DEFAULT_EMIT;
|
|
1204
|
+
let prismaNextSpec;
|
|
1205
|
+
try {
|
|
1206
|
+
prismaNextSpec = parsePrismaNextVersionSpec(input.prismaNextVersion);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
cancel(error instanceof Error ? error.message : String(error));
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
779
1211
|
const databaseProvider = input.provider ?? (useDefaults ? DEFAULT_DATABASE_PROVIDER : await promptForDatabaseProvider());
|
|
780
1212
|
if (!databaseProvider) return;
|
|
781
|
-
const schemaPreset = input.schemaPreset ?? options.defaultSchemaPreset ?? DEFAULT_SCHEMA_PRESET$1;
|
|
782
1213
|
const databaseUrl = input.databaseUrl;
|
|
783
|
-
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1214
|
+
const shouldUsePrismaPostgres = input.prismaPostgres ?? (databaseProvider === "postgres" && !databaseUrl && !useDefaults ? await promptForPrismaPostgres() : DEFAULT_AUTOMATED_PRISMA_POSTGRES);
|
|
1215
|
+
if (shouldUsePrismaPostgres === void 0) return;
|
|
1216
|
+
if (shouldUsePrismaPostgres && databaseProvider !== "postgres") {
|
|
1217
|
+
cancel("--prisma-postgres is only supported with --provider postgres.");
|
|
1218
|
+
return;
|
|
788
1219
|
}
|
|
1220
|
+
if (shouldUsePrismaPostgres && databaseUrl) {
|
|
1221
|
+
cancel("Use either --database-url or --prisma-postgres, not both.");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const authoring = input.authoring ?? (useDefaults ? DEFAULT_AUTHORING : await promptForAuthoringStyle());
|
|
1225
|
+
if (!authoring) return;
|
|
789
1226
|
const detectedPackageManager = await detectPackageManager(projectDir);
|
|
790
1227
|
const packageManager = input.packageManager ?? (useDefaults ? detectedPackageManager : await promptForPackageManager(detectedPackageManager));
|
|
791
1228
|
if (!packageManager) return;
|
|
@@ -794,25 +1231,23 @@ async function collectPrismaSetupContext(input, options = {}) {
|
|
|
794
1231
|
return {
|
|
795
1232
|
projectDir,
|
|
796
1233
|
verbose,
|
|
797
|
-
|
|
1234
|
+
shouldEmit,
|
|
798
1235
|
databaseProvider,
|
|
799
|
-
|
|
1236
|
+
authoring,
|
|
800
1237
|
databaseUrl,
|
|
801
1238
|
shouldUsePrismaPostgres,
|
|
802
1239
|
packageManager,
|
|
803
|
-
shouldInstall
|
|
1240
|
+
shouldInstall,
|
|
1241
|
+
prismaNextSpec
|
|
804
1242
|
};
|
|
805
1243
|
}
|
|
806
1244
|
function getDefaultDatabaseUrl(provider) {
|
|
807
1245
|
switch (provider) {
|
|
808
|
-
case "
|
|
809
|
-
case "
|
|
810
|
-
case "mysql": return "mysql://johndoe:randompassword@localhost:3306/mydb";
|
|
811
|
-
case "sqlite": return "file:./dev.db";
|
|
812
|
-
case "sqlserver": return "sqlserver://localhost:1433;database=mydb;user=SA;password=randompassword;";
|
|
1246
|
+
case "postgres": return "postgresql://user:password@localhost:5432/mydb";
|
|
1247
|
+
case "mongo": return "mongodb://localhost:27017/mydb?replicaSet=rs0&directConnection=true";
|
|
813
1248
|
default: {
|
|
814
1249
|
const exhaustiveCheck = provider;
|
|
815
|
-
throw new Error(`Unsupported
|
|
1250
|
+
throw new Error(`Unsupported Prisma Next target: ${String(exhaustiveCheck)}`);
|
|
816
1251
|
}
|
|
817
1252
|
}
|
|
818
1253
|
}
|
|
@@ -882,6 +1317,48 @@ async function ensureGitignoreEntry(projectDir, entry) {
|
|
|
882
1317
|
const separator = existingContent.endsWith("\n") ? "" : "\n";
|
|
883
1318
|
await fs.appendFile(gitignorePath, `${separator}${entry}\n`, "utf8");
|
|
884
1319
|
}
|
|
1320
|
+
async function ensurePackageScripts(projectDir, scripts) {
|
|
1321
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
1322
|
+
if (!await fs.pathExists(packageJsonPath)) return;
|
|
1323
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1324
|
+
if (!packageJson.scripts) packageJson.scripts = {};
|
|
1325
|
+
let didChange = false;
|
|
1326
|
+
for (const [scriptName, command] of Object.entries(scripts)) if (typeof packageJson.scripts[scriptName] !== "string" || packageJson.scripts[scriptName].trim().length === 0) {
|
|
1327
|
+
packageJson.scripts[scriptName] = command;
|
|
1328
|
+
didChange = true;
|
|
1329
|
+
}
|
|
1330
|
+
if (didChange) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1331
|
+
}
|
|
1332
|
+
async function ensureMongoMemoryServerScript(projectDir) {
|
|
1333
|
+
const scriptPath = path.join(projectDir, "scripts", "mongo.mjs");
|
|
1334
|
+
if (await fs.pathExists(scriptPath)) return;
|
|
1335
|
+
await fs.ensureDir(path.dirname(scriptPath));
|
|
1336
|
+
await fs.writeFile(scriptPath, MONGO_MEMORY_SERVER_SCRIPT, "utf8");
|
|
1337
|
+
}
|
|
1338
|
+
async function ensureMongoMemoryServerDevDependency(projectDir) {
|
|
1339
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
1340
|
+
if (!await fs.pathExists(packageJsonPath)) return;
|
|
1341
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1342
|
+
if (!packageJson.devDependencies) packageJson.devDependencies = {};
|
|
1343
|
+
const memoryServerVersion = getDependencyVersion("mongodb-memory-server");
|
|
1344
|
+
if (packageJson.devDependencies["mongodb-memory-server"] === memoryServerVersion) return;
|
|
1345
|
+
packageJson.devDependencies["mongodb-memory-server"] = memoryServerVersion;
|
|
1346
|
+
packageJson.devDependencies = Object.fromEntries(Object.entries(packageJson.devDependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
1347
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1348
|
+
}
|
|
1349
|
+
async function writeMongoLocalHelpersForContext(context, projectDir) {
|
|
1350
|
+
if (context.databaseProvider !== "mongo" || context.databaseUrl) return true;
|
|
1351
|
+
try {
|
|
1352
|
+
await ensureMongoMemoryServerScript(projectDir);
|
|
1353
|
+
await ensureMongoMemoryServerDevDependency(projectDir);
|
|
1354
|
+
await ensurePackageScripts(projectDir, getMongoMemoryScripts(context.packageManager));
|
|
1355
|
+
await ensureGitignoreEntry(projectDir, ".mongo-data");
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
cancel(getCommandErrorMessage(error));
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
885
1362
|
async function ensureRequiredPrismaFiles(projectDir) {
|
|
886
1363
|
const missingFiles = [];
|
|
887
1364
|
for (const candidates of requiredPrismaFileGroups) {
|
|
@@ -895,41 +1372,37 @@ async function ensureRequiredPrismaFiles(projectDir) {
|
|
|
895
1372
|
}
|
|
896
1373
|
if (!foundCandidate) missingFiles.push(candidates.join(" or "));
|
|
897
1374
|
}
|
|
898
|
-
if (missingFiles.length > 0) throw new Error(`Template is missing required Prisma files: ${missingFiles.join(", ")}`);
|
|
1375
|
+
if (missingFiles.length > 0) throw new Error(`Template is missing required Prisma Next files: ${missingFiles.join(", ")}`);
|
|
899
1376
|
}
|
|
900
1377
|
async function finalizePrismaFiles(options) {
|
|
901
1378
|
const projectDir = options.projectDir ?? process.cwd();
|
|
902
|
-
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
903
1379
|
await ensureRequiredPrismaFiles(projectDir);
|
|
904
|
-
|
|
905
|
-
await ensureEnvVarInEnv(prismaProjectDir, "DATABASE_URL", options.databaseUrl ?? getDefaultDatabaseUrl(options.provider), {
|
|
1380
|
+
await ensureEnvVarInEnv(projectDir, "DATABASE_URL", options.databaseUrl ?? getDefaultDatabaseUrl(options.provider), {
|
|
906
1381
|
mode: options.databaseUrl ? "upsert" : "keep-existing",
|
|
907
1382
|
comment: "Added by create-prisma"
|
|
908
1383
|
});
|
|
909
1384
|
if (options.claimUrl) {
|
|
910
|
-
await ensureEnvVarInEnv(
|
|
1385
|
+
await ensureEnvVarInEnv(projectDir, "CLAIM_URL", options.claimUrl, {
|
|
911
1386
|
mode: "upsert",
|
|
912
1387
|
comment: PRISMA_POSTGRES_TEMPORARY_NOTICE
|
|
913
1388
|
});
|
|
914
|
-
await ensureEnvComment(
|
|
1389
|
+
await ensureEnvComment(projectDir, PRISMA_POSTGRES_TEMPORARY_NOTICE);
|
|
915
1390
|
}
|
|
916
|
-
await ensureGitignoreEntry(
|
|
1391
|
+
await ensureGitignoreEntry(projectDir, ".env");
|
|
917
1392
|
}
|
|
918
1393
|
async function provisionPrismaPostgresIfNeeded(context, projectDir) {
|
|
919
1394
|
if (!context.shouldUsePrismaPostgres) return { databaseUrl: context.databaseUrl };
|
|
920
1395
|
const createDbCommand = getCreateDbCommand(context.packageManager);
|
|
921
|
-
|
|
922
|
-
prismaPostgresSpinner.start(`Provisioning Prisma Postgres with ${createDbCommand}...`);
|
|
1396
|
+
if (context.verbose) log.step(`Running ${createDbCommand}`);
|
|
923
1397
|
try {
|
|
924
1398
|
const prismaPostgresResult = await provisionPrismaPostgres(context.packageManager, projectDir);
|
|
925
|
-
|
|
1399
|
+
if (context.verbose) log.success("Prisma Postgres database provisioned.");
|
|
926
1400
|
return {
|
|
927
1401
|
databaseUrl: prismaPostgresResult.databaseUrl,
|
|
928
1402
|
claimUrl: prismaPostgresResult.claimUrl
|
|
929
1403
|
};
|
|
930
1404
|
} catch (error) {
|
|
931
1405
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
932
|
-
prismaPostgresSpinner.stop("Could not provision Prisma Postgres.");
|
|
933
1406
|
return {
|
|
934
1407
|
databaseUrl: context.databaseUrl,
|
|
935
1408
|
warning: `Prisma Postgres provisioning failed: ${errorMessage}`
|
|
@@ -937,44 +1410,86 @@ async function provisionPrismaPostgresIfNeeded(context, projectDir) {
|
|
|
937
1410
|
}
|
|
938
1411
|
}
|
|
939
1412
|
async function writeDependenciesForContext(context, projectDir) {
|
|
940
|
-
const prismaProjectDir = await resolvePrismaProjectDir(projectDir);
|
|
941
1413
|
try {
|
|
942
|
-
await writePrismaDependencies(context.databaseProvider, context.packageManager,
|
|
1414
|
+
await writePrismaDependencies(context.databaseProvider, context.packageManager, context.authoring, projectDir, context.prismaNextSpec);
|
|
943
1415
|
return true;
|
|
944
1416
|
} catch (error) {
|
|
945
1417
|
cancel(getCommandErrorMessage(error));
|
|
946
1418
|
return false;
|
|
947
1419
|
}
|
|
948
1420
|
}
|
|
1421
|
+
function getPrismaNextCliPackageSpecifier(prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
1422
|
+
return getPrismaNextPackageSpecifier("prisma-next", prismaNextSpec);
|
|
1423
|
+
}
|
|
1424
|
+
function getPrismaNextInitTarget(provider) {
|
|
1425
|
+
return provider === "mongo" ? "mongodb" : "postgres";
|
|
1426
|
+
}
|
|
1427
|
+
function getPrismaNextInitCliArgs(packageManager, prismaNextArgs, prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
1428
|
+
if (packageManager === "npm") return {
|
|
1429
|
+
command: "npx",
|
|
1430
|
+
args: [
|
|
1431
|
+
"--yes",
|
|
1432
|
+
getPrismaNextCliPackageSpecifier(prismaNextSpec),
|
|
1433
|
+
"init",
|
|
1434
|
+
...prismaNextArgs
|
|
1435
|
+
]
|
|
1436
|
+
};
|
|
1437
|
+
return getPackageExecutionArgs(packageManager, [
|
|
1438
|
+
getPrismaNextCliPackageSpecifier(prismaNextSpec),
|
|
1439
|
+
"init",
|
|
1440
|
+
...prismaNextArgs
|
|
1441
|
+
]);
|
|
1442
|
+
}
|
|
1443
|
+
function getPrismaNextInitCliCommand(packageManager, prismaNextArgs, prismaNextSpec = DEFAULT_PRISMA_NEXT_SPEC) {
|
|
1444
|
+
const execution = getPrismaNextInitCliArgs(packageManager, prismaNextArgs, prismaNextSpec);
|
|
1445
|
+
return [execution.command, ...execution.args].join(" ");
|
|
1446
|
+
}
|
|
1447
|
+
async function runPrismaNextInitForContext(context, projectDir) {
|
|
1448
|
+
const initArgs = [
|
|
1449
|
+
"--yes",
|
|
1450
|
+
"--force",
|
|
1451
|
+
"--target",
|
|
1452
|
+
getPrismaNextInitTarget(context.databaseProvider),
|
|
1453
|
+
"--authoring",
|
|
1454
|
+
context.authoring,
|
|
1455
|
+
"--schema-path",
|
|
1456
|
+
getContractPath(context.authoring),
|
|
1457
|
+
"--no-install"
|
|
1458
|
+
];
|
|
1459
|
+
const initCommand = getPrismaNextInitCliCommand(context.packageManager, initArgs, context.prismaNextSpec);
|
|
1460
|
+
if (context.verbose) log.step(`Running ${initCommand}`);
|
|
1461
|
+
try {
|
|
1462
|
+
const initExecution = getPrismaNextInitCliArgs(context.packageManager, initArgs, context.prismaNextSpec);
|
|
1463
|
+
await execa(initExecution.command, initExecution.args, {
|
|
1464
|
+
cwd: projectDir,
|
|
1465
|
+
stdio: context.verbose ? "inherit" : "pipe",
|
|
1466
|
+
env: {
|
|
1467
|
+
...process.env,
|
|
1468
|
+
CI: "1"
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
if (context.verbose) log.success("Prisma Next project files ready.");
|
|
1472
|
+
return true;
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
if (context.verbose) log.warn("Could not run Prisma Next init.");
|
|
1475
|
+
cancel(`Failed to run ${initCommand}: ${getCommandErrorMessage(error)}`);
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
949
1479
|
async function installDependenciesForContext(context, projectDir) {
|
|
950
1480
|
if (!context.shouldInstall) return true;
|
|
951
1481
|
const installCommand = getInstallCommand(context.packageManager);
|
|
952
|
-
if (context.verbose) {
|
|
953
|
-
log.step(`Running ${installCommand}`);
|
|
954
|
-
try {
|
|
955
|
-
await installProjectDependencies(context.packageManager, projectDir, { verbose: context.verbose });
|
|
956
|
-
log.success("Dependencies installed.");
|
|
957
|
-
return true;
|
|
958
|
-
} catch (error) {
|
|
959
|
-
cancel(`Failed to run ${installCommand}: ${getCommandErrorMessage(error)}`);
|
|
960
|
-
return false;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
const installSpinner = spinner();
|
|
964
|
-
installSpinner.start(`Running ${installCommand}...`);
|
|
1482
|
+
if (context.verbose) log.step(`Running ${installCommand}`);
|
|
965
1483
|
try {
|
|
966
1484
|
await installProjectDependencies(context.packageManager, projectDir, { verbose: context.verbose });
|
|
967
|
-
|
|
1485
|
+
if (context.verbose) log.success("Dependencies installed.");
|
|
968
1486
|
return true;
|
|
969
1487
|
} catch (error) {
|
|
970
|
-
installSpinner.stop("Could not install dependencies.");
|
|
971
1488
|
cancel(`Failed to run ${installCommand}: ${getCommandErrorMessage(error)}`);
|
|
972
1489
|
return false;
|
|
973
1490
|
}
|
|
974
1491
|
}
|
|
975
1492
|
async function finalizePrismaFilesForContext(context, projectDir, provisionResult) {
|
|
976
|
-
const initSpinner = spinner();
|
|
977
|
-
initSpinner.start("Preparing Prisma files...");
|
|
978
1493
|
try {
|
|
979
1494
|
await finalizePrismaFiles({
|
|
980
1495
|
provider: context.databaseProvider,
|
|
@@ -982,746 +1497,165 @@ async function finalizePrismaFilesForContext(context, projectDir, provisionResul
|
|
|
982
1497
|
claimUrl: provisionResult.claimUrl,
|
|
983
1498
|
projectDir
|
|
984
1499
|
});
|
|
985
|
-
|
|
1500
|
+
if (context.verbose) log.success("Prisma Next environment configured.");
|
|
986
1501
|
return true;
|
|
987
1502
|
} catch (error) {
|
|
988
|
-
initSpinner.stop("Could not prepare Prisma files.");
|
|
989
1503
|
cancel(getCommandErrorMessage(error));
|
|
990
1504
|
return false;
|
|
991
1505
|
}
|
|
992
1506
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1507
|
+
function getPrismaNextCliCommand(packageManager, prismaNextArgs) {
|
|
1508
|
+
if (packageManager === "deno") return `deno run -A --env-file=.env ${getDenoPrismaSpecifier()} ${prismaNextArgs.join(" ")}`;
|
|
1509
|
+
return getLocalPackageBinaryCommand(packageManager, "prisma-next", prismaNextArgs);
|
|
1510
|
+
}
|
|
1511
|
+
function getPrismaNextCliArgs(packageManager, prismaNextArgs) {
|
|
1512
|
+
if (packageManager === "deno") return {
|
|
1513
|
+
command: "deno",
|
|
1514
|
+
args: [
|
|
1515
|
+
"run",
|
|
1516
|
+
"-A",
|
|
1517
|
+
"--env-file=.env",
|
|
1518
|
+
getDenoPrismaSpecifier(),
|
|
1519
|
+
...prismaNextArgs
|
|
1520
|
+
]
|
|
1521
|
+
};
|
|
1522
|
+
return getLocalPackageBinaryArgs(packageManager, "prisma-next", prismaNextArgs);
|
|
1523
|
+
}
|
|
1524
|
+
async function emitPrismaNextContractForContext(context, projectDir) {
|
|
1525
|
+
if (!context.shouldEmit) return { didEmitContract: false };
|
|
1526
|
+
if (!context.shouldInstall) return {
|
|
1527
|
+
didEmitContract: false,
|
|
1528
|
+
warning: "Skipped contract emit because dependencies were not installed."
|
|
1529
|
+
};
|
|
1530
|
+
const emitCommand = getPrismaNextCliCommand(context.packageManager, ["contract", "emit"]);
|
|
1531
|
+
if (context.verbose) log.step(`Running ${emitCommand}`);
|
|
1000
1532
|
try {
|
|
1001
|
-
const
|
|
1002
|
-
await execa(
|
|
1003
|
-
cwd:
|
|
1533
|
+
const emitArgs = getPrismaNextCliArgs(context.packageManager, ["contract", "emit"]);
|
|
1534
|
+
await execa(emitArgs.command, emitArgs.args, {
|
|
1535
|
+
cwd: projectDir,
|
|
1004
1536
|
stdio: context.verbose ? "inherit" : "pipe"
|
|
1005
1537
|
});
|
|
1006
|
-
if (context.verbose) log.success("Prisma
|
|
1007
|
-
|
|
1008
|
-
return { didGenerateClient: true };
|
|
1538
|
+
if (context.verbose) log.success("Prisma Next contract artifacts emitted.");
|
|
1539
|
+
return { didEmitContract: true };
|
|
1009
1540
|
} catch (error) {
|
|
1010
|
-
if (context.verbose) log.warn("Could not
|
|
1011
|
-
else generateSpinner?.stop("Could not generate Prisma Client.");
|
|
1541
|
+
if (context.verbose) log.warn("Could not emit Prisma Next contract.");
|
|
1012
1542
|
return {
|
|
1013
|
-
|
|
1014
|
-
warning: `
|
|
1543
|
+
didEmitContract: false,
|
|
1544
|
+
warning: `Contract emit failed: ${getCommandErrorMessage(error)}`
|
|
1015
1545
|
};
|
|
1016
1546
|
}
|
|
1017
1547
|
}
|
|
1018
|
-
function buildWarningLines(provisionWarning,
|
|
1548
|
+
function buildWarningLines(provisionWarning, emitWarning) {
|
|
1019
1549
|
const warningLines = [];
|
|
1020
1550
|
if (provisionWarning) warningLines.push(`- ${provisionWarning}`);
|
|
1021
|
-
if (
|
|
1551
|
+
if (emitWarning) warningLines.push(`- ${emitWarning}`);
|
|
1022
1552
|
return warningLines;
|
|
1023
1553
|
}
|
|
1024
1554
|
function buildNextStepsForContext(opts) {
|
|
1025
|
-
const { context, options,
|
|
1555
|
+
const { context, options, didEmitContract } = opts;
|
|
1026
1556
|
const nextSteps = [...options.prependNextSteps ?? []];
|
|
1027
|
-
if (!context.shouldInstall) nextSteps.push(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "db:seed")}`);
|
|
1031
|
-
if (options.includeDevNextStep) nextSteps.push(`- ${getRunScriptCommand(context.packageManager, "dev")}`);
|
|
1032
|
-
return nextSteps;
|
|
1033
|
-
}
|
|
1034
|
-
async function executePrismaSetupContext(context, options = {}) {
|
|
1035
|
-
const projectDir = path.resolve(options.projectDir ?? context.projectDir);
|
|
1036
|
-
const provisionResult = await provisionPrismaPostgresIfNeeded(context, projectDir);
|
|
1037
|
-
if (!provisionResult) return false;
|
|
1038
|
-
if (!await writeDependenciesForContext(context, projectDir)) return false;
|
|
1039
|
-
if (!await installDependenciesForContext(context, projectDir)) return false;
|
|
1040
|
-
if (!await finalizePrismaFilesForContext(context, projectDir, provisionResult)) return false;
|
|
1041
|
-
const generateResult = await generatePrismaClientForContext(context, projectDir);
|
|
1042
|
-
const warningLines = buildWarningLines(provisionResult.warning, generateResult.warning);
|
|
1043
|
-
const nextSteps = buildNextStepsForContext({
|
|
1044
|
-
context,
|
|
1045
|
-
options,
|
|
1046
|
-
didGenerateClient: generateResult.didGenerateClient
|
|
1557
|
+
if (!context.shouldInstall) nextSteps.push({
|
|
1558
|
+
command: getInstallCommand(context.packageManager),
|
|
1559
|
+
description: "Install the project dependencies."
|
|
1047
1560
|
});
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
Next
|
|
1051
|
-
${nextSteps.join("\n")}`);
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
//#endregion
|
|
1056
|
-
//#region src/tasks/setup-addons.ts
|
|
1057
|
-
const DEFAULT_ADDON_SCOPE = "project";
|
|
1058
|
-
const DEFAULT_SKILLS_AGENTS = [
|
|
1059
|
-
"claude-code",
|
|
1060
|
-
"codex",
|
|
1061
|
-
"cursor"
|
|
1062
|
-
];
|
|
1063
|
-
const DEFAULT_MCP_AGENTS = [
|
|
1064
|
-
"claude-code",
|
|
1065
|
-
"codex",
|
|
1066
|
-
"cursor"
|
|
1067
|
-
];
|
|
1068
|
-
const DEFAULT_EXTENSION_TARGETS = ["vscode", "cursor"];
|
|
1069
|
-
const PRISMA_MCP_SERVER = "https://mcp.prisma.io/mcp";
|
|
1070
|
-
const ADDON_OPTIONS = [
|
|
1071
|
-
{
|
|
1072
|
-
value: "skills",
|
|
1073
|
-
label: "Skills",
|
|
1074
|
-
hint: "Install curated Prisma skills to your selected coding agents"
|
|
1075
|
-
},
|
|
1076
|
-
{
|
|
1077
|
-
value: "mcp",
|
|
1078
|
-
label: "MCP",
|
|
1079
|
-
hint: "Configure Prisma MCP server in agent MCP config files"
|
|
1080
|
-
},
|
|
1081
|
-
{
|
|
1082
|
-
value: "extension",
|
|
1083
|
-
label: "IDE Extension",
|
|
1084
|
-
hint: "Install Prisma extension in selected IDEs (VS Code, Cursor, Windsurf)"
|
|
1085
|
-
}
|
|
1086
|
-
];
|
|
1087
|
-
const SKILLS_AGENT_OPTIONS = [
|
|
1088
|
-
{
|
|
1089
|
-
value: "cursor",
|
|
1090
|
-
label: "Cursor"
|
|
1091
|
-
},
|
|
1092
|
-
{
|
|
1093
|
-
value: "claude-code",
|
|
1094
|
-
label: "Claude Code"
|
|
1095
|
-
},
|
|
1096
|
-
{
|
|
1097
|
-
value: "cline",
|
|
1098
|
-
label: "Cline"
|
|
1099
|
-
},
|
|
1100
|
-
{
|
|
1101
|
-
value: "github-copilot",
|
|
1102
|
-
label: "GitHub Copilot"
|
|
1103
|
-
},
|
|
1104
|
-
{
|
|
1105
|
-
value: "codex",
|
|
1106
|
-
label: "Codex"
|
|
1107
|
-
},
|
|
1108
|
-
{
|
|
1109
|
-
value: "opencode",
|
|
1110
|
-
label: "OpenCode"
|
|
1111
|
-
},
|
|
1112
|
-
{
|
|
1113
|
-
value: "windsurf",
|
|
1114
|
-
label: "Windsurf"
|
|
1115
|
-
},
|
|
1116
|
-
{
|
|
1117
|
-
value: "goose",
|
|
1118
|
-
label: "Goose"
|
|
1119
|
-
},
|
|
1120
|
-
{
|
|
1121
|
-
value: "roo",
|
|
1122
|
-
label: "Roo Code"
|
|
1123
|
-
},
|
|
1124
|
-
{
|
|
1125
|
-
value: "kilo",
|
|
1126
|
-
label: "Kilo Code"
|
|
1127
|
-
},
|
|
1128
|
-
{
|
|
1129
|
-
value: "gemini-cli",
|
|
1130
|
-
label: "Gemini CLI"
|
|
1131
|
-
},
|
|
1132
|
-
{
|
|
1133
|
-
value: "antigravity",
|
|
1134
|
-
label: "Antigravity"
|
|
1135
|
-
},
|
|
1136
|
-
{
|
|
1137
|
-
value: "openhands",
|
|
1138
|
-
label: "OpenHands"
|
|
1139
|
-
},
|
|
1140
|
-
{
|
|
1141
|
-
value: "trae",
|
|
1142
|
-
label: "Trae"
|
|
1143
|
-
},
|
|
1144
|
-
{
|
|
1145
|
-
value: "amp",
|
|
1146
|
-
label: "Amp"
|
|
1147
|
-
},
|
|
1148
|
-
{
|
|
1149
|
-
value: "pi",
|
|
1150
|
-
label: "Pi"
|
|
1151
|
-
},
|
|
1152
|
-
{
|
|
1153
|
-
value: "qoder",
|
|
1154
|
-
label: "Qoder"
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
value: "qwen-code",
|
|
1158
|
-
label: "Qwen Code"
|
|
1159
|
-
},
|
|
1160
|
-
{
|
|
1161
|
-
value: "kiro-cli",
|
|
1162
|
-
label: "Kiro CLI"
|
|
1163
|
-
},
|
|
1164
|
-
{
|
|
1165
|
-
value: "droid",
|
|
1166
|
-
label: "Droid"
|
|
1167
|
-
},
|
|
1168
|
-
{
|
|
1169
|
-
value: "command-code",
|
|
1170
|
-
label: "Command Code"
|
|
1171
|
-
},
|
|
1172
|
-
{
|
|
1173
|
-
value: "clawdbot",
|
|
1174
|
-
label: "Clawdbot"
|
|
1175
|
-
},
|
|
1176
|
-
{
|
|
1177
|
-
value: "zencoder",
|
|
1178
|
-
label: "Zencoder"
|
|
1179
|
-
},
|
|
1180
|
-
{
|
|
1181
|
-
value: "neovate",
|
|
1182
|
-
label: "Neovate"
|
|
1183
|
-
},
|
|
1184
|
-
{
|
|
1185
|
-
value: "mcpjam",
|
|
1186
|
-
label: "MCPJam"
|
|
1187
|
-
}
|
|
1188
|
-
];
|
|
1189
|
-
const MCP_AGENT_OPTIONS = [
|
|
1190
|
-
{
|
|
1191
|
-
value: "claude-code",
|
|
1192
|
-
label: "Claude Code"
|
|
1193
|
-
},
|
|
1194
|
-
{
|
|
1195
|
-
value: "codex",
|
|
1196
|
-
label: "Codex"
|
|
1197
|
-
},
|
|
1198
|
-
{
|
|
1199
|
-
value: "cursor",
|
|
1200
|
-
label: "Cursor"
|
|
1201
|
-
},
|
|
1202
|
-
{
|
|
1203
|
-
value: "vscode",
|
|
1204
|
-
label: "VS Code"
|
|
1205
|
-
},
|
|
1206
|
-
{
|
|
1207
|
-
value: "github-copilot-cli",
|
|
1208
|
-
label: "GitHub Copilot CLI"
|
|
1209
|
-
},
|
|
1210
|
-
{
|
|
1211
|
-
value: "opencode",
|
|
1212
|
-
label: "OpenCode"
|
|
1213
|
-
},
|
|
1214
|
-
{
|
|
1215
|
-
value: "gemini-cli",
|
|
1216
|
-
label: "Gemini CLI"
|
|
1217
|
-
},
|
|
1218
|
-
{
|
|
1219
|
-
value: "goose",
|
|
1220
|
-
label: "Goose"
|
|
1221
|
-
},
|
|
1222
|
-
{
|
|
1223
|
-
value: "zed",
|
|
1224
|
-
label: "Zed"
|
|
1225
|
-
},
|
|
1226
|
-
{
|
|
1227
|
-
value: "antigravity",
|
|
1228
|
-
label: "Antigravity"
|
|
1229
|
-
},
|
|
1230
|
-
{
|
|
1231
|
-
value: "cline",
|
|
1232
|
-
label: "Cline VS Code Extension"
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
value: "cline-cli",
|
|
1236
|
-
label: "Cline CLI"
|
|
1237
|
-
},
|
|
1238
|
-
{
|
|
1239
|
-
value: "claude-desktop",
|
|
1240
|
-
label: "Claude Desktop"
|
|
1241
|
-
},
|
|
1242
|
-
{
|
|
1243
|
-
value: "mcporter",
|
|
1244
|
-
label: "MCPorter"
|
|
1245
|
-
}
|
|
1246
|
-
];
|
|
1247
|
-
const SHARED_PRISMA_SKILLS = [
|
|
1248
|
-
"prisma-cli",
|
|
1249
|
-
"prisma-client-api",
|
|
1250
|
-
"prisma-database-setup",
|
|
1251
|
-
"prisma-upgrade-v7"
|
|
1252
|
-
];
|
|
1253
|
-
function getAvailablePrismaSkills(provider) {
|
|
1254
|
-
if (provider === "postgresql") return [...SHARED_PRISMA_SKILLS, "prisma-postgres"];
|
|
1255
|
-
return [...SHARED_PRISMA_SKILLS];
|
|
1256
|
-
}
|
|
1257
|
-
function getSkillOptions(provider) {
|
|
1258
|
-
const available = getAvailablePrismaSkills(provider);
|
|
1259
|
-
const options = {
|
|
1260
|
-
"prisma-cli": {
|
|
1261
|
-
value: "prisma-cli",
|
|
1262
|
-
label: "prisma-cli",
|
|
1263
|
-
hint: "Prisma CLI reference"
|
|
1264
|
-
},
|
|
1265
|
-
"prisma-client-api": {
|
|
1266
|
-
value: "prisma-client-api",
|
|
1267
|
-
label: "prisma-client-api",
|
|
1268
|
-
hint: "Prisma Client query patterns"
|
|
1269
|
-
},
|
|
1270
|
-
"prisma-database-setup": {
|
|
1271
|
-
value: "prisma-database-setup",
|
|
1272
|
-
label: "prisma-database-setup",
|
|
1273
|
-
hint: "Database provider setup guides"
|
|
1274
|
-
},
|
|
1275
|
-
"prisma-upgrade-v7": {
|
|
1276
|
-
value: "prisma-upgrade-v7",
|
|
1277
|
-
label: "prisma-upgrade-v7",
|
|
1278
|
-
hint: "v6 to v7 migration guide"
|
|
1279
|
-
},
|
|
1280
|
-
"prisma-postgres": {
|
|
1281
|
-
value: "prisma-postgres",
|
|
1282
|
-
label: "prisma-postgres",
|
|
1283
|
-
hint: "Prisma Postgres workflows"
|
|
1284
|
-
}
|
|
1285
|
-
};
|
|
1286
|
-
return available.map((skill) => options[skill]);
|
|
1287
|
-
}
|
|
1288
|
-
function collectAddonsFromInput(input) {
|
|
1289
|
-
const addons = [];
|
|
1290
|
-
if (input.skills === true) addons.push("skills");
|
|
1291
|
-
if (input.mcp === true) addons.push("mcp");
|
|
1292
|
-
if (input.extension === true) addons.push("extension");
|
|
1293
|
-
return uniqueValues(addons);
|
|
1294
|
-
}
|
|
1295
|
-
const EXTENSION_TARGET_OPTIONS = [
|
|
1296
|
-
{
|
|
1297
|
-
value: "vscode",
|
|
1298
|
-
label: "VS Code",
|
|
1299
|
-
hint: "Uses the `code` CLI"
|
|
1300
|
-
},
|
|
1301
|
-
{
|
|
1302
|
-
value: "cursor",
|
|
1303
|
-
label: "Cursor",
|
|
1304
|
-
hint: "Uses the `cursor` CLI"
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
value: "windsurf",
|
|
1308
|
-
label: "Windsurf",
|
|
1309
|
-
hint: "Uses the `windsurf` CLI"
|
|
1310
|
-
}
|
|
1311
|
-
];
|
|
1312
|
-
function uniqueValues(values) {
|
|
1313
|
-
return Array.from(new Set(values));
|
|
1314
|
-
}
|
|
1315
|
-
function getRecommendedPrismaSkills(provider, shouldUsePrismaPostgres) {
|
|
1316
|
-
const skills = [...getAvailablePrismaSkills(provider)];
|
|
1317
|
-
if (!shouldUsePrismaPostgres) return skills.filter((skill) => skill !== "prisma-postgres");
|
|
1318
|
-
return uniqueValues(skills);
|
|
1319
|
-
}
|
|
1320
|
-
async function promptForAddons() {
|
|
1321
|
-
const selectedAddons = await multiselect({
|
|
1322
|
-
message: "Select add-ons (optional)",
|
|
1323
|
-
options: ADDON_OPTIONS,
|
|
1324
|
-
required: false
|
|
1561
|
+
if (!didEmitContract || !context.shouldEmit) nextSteps.push({
|
|
1562
|
+
command: getRunScriptCommand(context.packageManager, "contract:emit"),
|
|
1563
|
+
description: "Emit contract.json and TypeScript types from your Prisma Next contract."
|
|
1325
1564
|
});
|
|
1326
|
-
if (
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1330
|
-
return uniqueValues(selectedAddons);
|
|
1331
|
-
}
|
|
1332
|
-
async function promptForAddonScope() {
|
|
1333
|
-
const selectedScope = await select({
|
|
1334
|
-
message: "Where should add-ons write config?",
|
|
1335
|
-
initialValue: DEFAULT_ADDON_SCOPE,
|
|
1336
|
-
options: [{
|
|
1337
|
-
value: "project",
|
|
1338
|
-
label: "Project",
|
|
1339
|
-
hint: "Recommended for teams (checked into the project when applicable)"
|
|
1340
|
-
}, {
|
|
1341
|
-
value: "global",
|
|
1342
|
-
label: "Global",
|
|
1343
|
-
hint: "Personal machine-level setup"
|
|
1344
|
-
}]
|
|
1565
|
+
if (context.databaseProvider === "postgres") nextSteps.push({
|
|
1566
|
+
command: getRunScriptCommand(context.packageManager, "db:init"),
|
|
1567
|
+
description: "Create the initial PostgreSQL database objects and sign the database."
|
|
1345
1568
|
});
|
|
1346
|
-
if (
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
}
|
|
1350
|
-
return selectedScope;
|
|
1351
|
-
}
|
|
1352
|
-
async function promptForPrismaSkills(provider, recommendedSkills) {
|
|
1353
|
-
const options = getSkillOptions(provider);
|
|
1354
|
-
const optionValues = new Set(options.map((option) => option.value));
|
|
1355
|
-
const selectedSkills = await multiselect({
|
|
1356
|
-
message: "Select Prisma skills",
|
|
1357
|
-
options,
|
|
1358
|
-
required: false,
|
|
1359
|
-
initialValues: recommendedSkills.filter((skill) => optionValues.has(skill))
|
|
1569
|
+
if (context.databaseProvider === "mongo" && !context.databaseUrl) nextSteps.push({
|
|
1570
|
+
command: getRunScriptCommand(context.packageManager, "db:up"),
|
|
1571
|
+
description: "Start the local MongoDB replica set with mongodb-memory-server. Stop with `db:down`, wipe with `db:reset`."
|
|
1360
1572
|
});
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
return uniqueValues(selectedSkills);
|
|
1366
|
-
}
|
|
1367
|
-
async function promptForSkillsAgents() {
|
|
1368
|
-
const selectedAgents = await multiselect({
|
|
1369
|
-
message: "Select agents for skills",
|
|
1370
|
-
options: SKILLS_AGENT_OPTIONS,
|
|
1371
|
-
required: false,
|
|
1372
|
-
initialValues: [...DEFAULT_SKILLS_AGENTS]
|
|
1573
|
+
nextSteps.push({
|
|
1574
|
+
command: getRunScriptCommand(context.packageManager, "migration:plan"),
|
|
1575
|
+
description: "Compare the contract to the database and write a migration plan."
|
|
1373
1576
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
return uniqueValues(selectedAgents);
|
|
1379
|
-
}
|
|
1380
|
-
async function promptForMcpAgents() {
|
|
1381
|
-
const selectedAgents = await multiselect({
|
|
1382
|
-
message: "Select agents for MCP",
|
|
1383
|
-
options: MCP_AGENT_OPTIONS,
|
|
1384
|
-
required: false,
|
|
1385
|
-
initialValues: [...DEFAULT_MCP_AGENTS]
|
|
1577
|
+
nextSteps.push({
|
|
1578
|
+
command: getRunScriptCommand(context.packageManager, "migrate"),
|
|
1579
|
+
description: "Apply the planned migration to the database."
|
|
1386
1580
|
});
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
}
|
|
1391
|
-
return uniqueValues(selectedAgents);
|
|
1392
|
-
}
|
|
1393
|
-
async function promptForExtensionTargets() {
|
|
1394
|
-
const selectedTargets = await multiselect({
|
|
1395
|
-
message: "Select IDEs for extension install",
|
|
1396
|
-
options: EXTENSION_TARGET_OPTIONS,
|
|
1397
|
-
required: false,
|
|
1398
|
-
initialValues: DEFAULT_EXTENSION_TARGETS
|
|
1581
|
+
nextSteps.push({
|
|
1582
|
+
command: getRunScriptCommand(context.packageManager, "db:seed"),
|
|
1583
|
+
description: "Insert the sample users from prisma/seed.ts."
|
|
1399
1584
|
});
|
|
1400
|
-
if (
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
return uniqueValues(selectedTargets);
|
|
1405
|
-
}
|
|
1406
|
-
async function collectCreateAddonSetupContext(input, options) {
|
|
1407
|
-
const hasExplicitAddonSelection = input.skills !== void 0 || input.mcp !== void 0 || input.extension !== void 0;
|
|
1408
|
-
const selectedFromInput = collectAddonsFromInput(input);
|
|
1409
|
-
const selectedAddons = selectedFromInput.length > 0 ? selectedFromInput : hasExplicitAddonSelection ? [] : options.useDefaults ? [] : await promptForAddons();
|
|
1410
|
-
if (!selectedAddons) return;
|
|
1411
|
-
const addons = uniqueValues(selectedAddons);
|
|
1412
|
-
if (addons.length === 0) return null;
|
|
1413
|
-
const scope = addons.includes("skills") || addons.includes("mcp") ? options.useDefaults ? DEFAULT_ADDON_SCOPE : await promptForAddonScope() : DEFAULT_ADDON_SCOPE;
|
|
1414
|
-
if (!scope) return;
|
|
1415
|
-
const recommendedSkills = getRecommendedPrismaSkills(options.provider, options.shouldUsePrismaPostgres);
|
|
1416
|
-
const skills = !addons.includes("skills") ? [] : options.useDefaults ? recommendedSkills : await promptForPrismaSkills(options.provider, recommendedSkills);
|
|
1417
|
-
if (!skills) return;
|
|
1418
|
-
const skillsAgents = !addons.includes("skills") ? [] : options.useDefaults ? [...DEFAULT_SKILLS_AGENTS] : await promptForSkillsAgents();
|
|
1419
|
-
if (!skillsAgents) return;
|
|
1420
|
-
const mcpAgents = !addons.includes("mcp") ? [] : options.useDefaults ? [...DEFAULT_MCP_AGENTS] : await promptForMcpAgents();
|
|
1421
|
-
if (!mcpAgents) return;
|
|
1422
|
-
const extensionTargets = !addons.includes("extension") ? [] : options.useDefaults ? [...DEFAULT_EXTENSION_TARGETS] : await promptForExtensionTargets();
|
|
1423
|
-
if (!extensionTargets) return;
|
|
1424
|
-
return {
|
|
1425
|
-
addons,
|
|
1426
|
-
scope,
|
|
1427
|
-
skills,
|
|
1428
|
-
skillsAgents: uniqueValues(skillsAgents),
|
|
1429
|
-
mcpAgents: uniqueValues(mcpAgents),
|
|
1430
|
-
extensionTargets: uniqueValues(extensionTargets)
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
async function executeExternalCommand(params) {
|
|
1434
|
-
await execa(params.command, params.args, {
|
|
1435
|
-
cwd: params.cwd,
|
|
1436
|
-
stdio: params.verbose ? "inherit" : "pipe",
|
|
1437
|
-
env: {
|
|
1438
|
-
...process.env,
|
|
1439
|
-
CI: "true"
|
|
1440
|
-
}
|
|
1585
|
+
if (options.includeDevNextStep) nextSteps.push({
|
|
1586
|
+
command: getRunScriptCommand(context.packageManager, "dev"),
|
|
1587
|
+
description: "Start the development server."
|
|
1441
1588
|
});
|
|
1589
|
+
return nextSteps;
|
|
1442
1590
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const scopeArgs = params.scope === "global" ? ["-g"] : [];
|
|
1446
|
-
const skillArgs = params.skills.flatMap((skill) => ["-s", skill]);
|
|
1447
|
-
const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
|
|
1448
|
-
const commandArgs = [
|
|
1449
|
-
"skills@latest",
|
|
1450
|
-
"add",
|
|
1451
|
-
"prisma/skills",
|
|
1452
|
-
...scopeArgs,
|
|
1453
|
-
...skillArgs,
|
|
1454
|
-
...agentArgs,
|
|
1455
|
-
"-y"
|
|
1456
|
-
];
|
|
1457
|
-
const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
|
|
1458
|
-
try {
|
|
1459
|
-
await executeExternalCommand({
|
|
1460
|
-
command: execution.command,
|
|
1461
|
-
args: execution.args,
|
|
1462
|
-
cwd: params.projectDir,
|
|
1463
|
-
verbose: params.verbose
|
|
1464
|
-
});
|
|
1465
|
-
return;
|
|
1466
|
-
} catch (error) {
|
|
1467
|
-
return `Skills addon failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
async function installMcpAddon(params) {
|
|
1471
|
-
if (params.agents.length === 0) return "Skipped MCP addon because no agents were selected.";
|
|
1472
|
-
const scopeArgs = params.scope === "global" ? ["-g"] : [];
|
|
1473
|
-
const agentArgs = params.agents.flatMap((agent) => ["-a", agent]);
|
|
1474
|
-
const commandArgs = [
|
|
1475
|
-
"add-mcp@latest",
|
|
1476
|
-
PRISMA_MCP_SERVER,
|
|
1477
|
-
...scopeArgs,
|
|
1478
|
-
...agentArgs,
|
|
1479
|
-
"--name",
|
|
1480
|
-
"prisma",
|
|
1481
|
-
"--gitignore",
|
|
1482
|
-
"-y"
|
|
1483
|
-
];
|
|
1484
|
-
const execution = getPackageExecutionArgs(params.packageManager, commandArgs);
|
|
1485
|
-
try {
|
|
1486
|
-
await executeExternalCommand({
|
|
1487
|
-
command: execution.command,
|
|
1488
|
-
args: execution.args,
|
|
1489
|
-
cwd: params.projectDir,
|
|
1490
|
-
verbose: params.verbose
|
|
1491
|
-
});
|
|
1492
|
-
return;
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
return `MCP addon failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1495
|
-
}
|
|
1591
|
+
function formatNextSteps(nextSteps) {
|
|
1592
|
+
return nextSteps.map((step) => `${step.command}\n ${step.description}`).join("\n\n");
|
|
1496
1593
|
}
|
|
1497
|
-
function
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1594
|
+
function formatAgentPrompt() {
|
|
1595
|
+
return [
|
|
1596
|
+
"Ask your agent:",
|
|
1597
|
+
"What can I do with Prisma Next?",
|
|
1598
|
+
"",
|
|
1599
|
+
"Learn more:",
|
|
1600
|
+
`Docs: prisma-next.md`,
|
|
1601
|
+
"Skills: https://github.com/prisma/prisma-next/tree/main/skills"
|
|
1602
|
+
].join("\n");
|
|
1507
1603
|
}
|
|
1508
|
-
async function
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
} catch {
|
|
1521
|
-
warnings.push(`Skipped ${target} extension install because the \`${binary}\` CLI is not available.`);
|
|
1522
|
-
continue;
|
|
1523
|
-
}
|
|
1524
|
-
try {
|
|
1525
|
-
await executeExternalCommand({
|
|
1526
|
-
command: binary,
|
|
1527
|
-
args: [
|
|
1528
|
-
"--install-extension",
|
|
1529
|
-
"Prisma.prisma",
|
|
1530
|
-
"--force"
|
|
1531
|
-
],
|
|
1532
|
-
cwd: params.projectDir,
|
|
1533
|
-
verbose: params.verbose
|
|
1534
|
-
});
|
|
1535
|
-
} catch (error) {
|
|
1536
|
-
warnings.push(`${target} extension install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1537
|
-
}
|
|
1604
|
+
async function executePrismaSetupContext(context, options = {}) {
|
|
1605
|
+
const projectDir = path.resolve(options.projectDir ?? context.projectDir);
|
|
1606
|
+
const progressSpinner = context.verbose ? void 0 : options.progressSpinner ?? spinner();
|
|
1607
|
+
if (progressSpinner !== void 0 && !options.progressSpinner) progressSpinner.start("Creating Prisma Next project...");
|
|
1608
|
+
const stopProgressOnFailure = () => {
|
|
1609
|
+
progressSpinner?.stop("Could not create Prisma Next project.");
|
|
1610
|
+
};
|
|
1611
|
+
if (context.shouldUsePrismaPostgres) progressSpinner?.message("Provisioning Prisma Postgres...");
|
|
1612
|
+
const provisionResult = await provisionPrismaPostgresIfNeeded(context, projectDir);
|
|
1613
|
+
if (!provisionResult) {
|
|
1614
|
+
stopProgressOnFailure();
|
|
1615
|
+
return false;
|
|
1538
1616
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
const addonSpinner = spinner();
|
|
1544
|
-
addonSpinner.start("Applying selected add-ons...");
|
|
1545
|
-
const warnings = [];
|
|
1546
|
-
if (context.addons.includes("skills")) {
|
|
1547
|
-
const warning = await installSkillsAddon({
|
|
1548
|
-
packageManager,
|
|
1549
|
-
projectDir,
|
|
1550
|
-
scope: context.scope,
|
|
1551
|
-
skills: context.skills,
|
|
1552
|
-
agents: context.skillsAgents,
|
|
1553
|
-
verbose
|
|
1554
|
-
});
|
|
1555
|
-
if (warning) warnings.push(warning);
|
|
1617
|
+
progressSpinner?.message("Preparing Prisma Next project files...");
|
|
1618
|
+
if (!await runPrismaNextInitForContext(context, projectDir)) {
|
|
1619
|
+
stopProgressOnFailure();
|
|
1620
|
+
return false;
|
|
1556
1621
|
}
|
|
1557
|
-
if (context
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
projectDir,
|
|
1561
|
-
scope: context.scope,
|
|
1562
|
-
agents: context.mcpAgents,
|
|
1563
|
-
verbose
|
|
1564
|
-
});
|
|
1565
|
-
if (warning) warnings.push(warning);
|
|
1622
|
+
if (!await writeDependenciesForContext(context, projectDir)) {
|
|
1623
|
+
stopProgressOnFailure();
|
|
1624
|
+
return false;
|
|
1566
1625
|
}
|
|
1567
|
-
if (context
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
verbose,
|
|
1571
|
-
targets: context.extensionTargets
|
|
1572
|
-
});
|
|
1573
|
-
warnings.push(...extensionWarnings);
|
|
1626
|
+
if (!await writeMongoLocalHelpersForContext(context, projectDir)) {
|
|
1627
|
+
stopProgressOnFailure();
|
|
1628
|
+
return false;
|
|
1574
1629
|
}
|
|
1575
|
-
if (
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
return;
|
|
1630
|
+
if (context.shouldInstall) progressSpinner?.message("Installing dependencies...");
|
|
1631
|
+
if (!await installDependenciesForContext(context, projectDir)) {
|
|
1632
|
+
stopProgressOnFailure();
|
|
1633
|
+
return false;
|
|
1579
1634
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
//#region src/telemetry/client.ts
|
|
1585
|
-
const TELEMETRY_API_KEY = "phc_cmc85avbWyuJ2JyKdGPdv7dxXli8xLdWDBPbvIXWJfs";
|
|
1586
|
-
const TELEMETRY_HOST = "https://us.i.posthog.com";
|
|
1587
|
-
const TELEMETRY_CONFIG_FILE = "telemetry.json";
|
|
1588
|
-
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;
|
|
1589
|
-
function isTruthyEnvValue(value) {
|
|
1590
|
-
return [
|
|
1591
|
-
"1",
|
|
1592
|
-
"true",
|
|
1593
|
-
"yes",
|
|
1594
|
-
"on"
|
|
1595
|
-
].includes(String(value ?? "").trim().toLowerCase());
|
|
1596
|
-
}
|
|
1597
|
-
function shouldDisableTelemetry() {
|
|
1598
|
-
if (isTruthyEnvValue(process.env.CI) || isTruthyEnvValue(process.env.GITHUB_ACTIONS)) return true;
|
|
1599
|
-
return process.env.CREATE_PRISMA_DISABLE_TELEMETRY !== void 0 || process.env.CREATE_PRISMA_TELEMETRY_DISABLED !== void 0 || process.env.DO_NOT_TRACK !== void 0;
|
|
1600
|
-
}
|
|
1601
|
-
function getTelemetryConfigDir() {
|
|
1602
|
-
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Application Support", "create-prisma");
|
|
1603
|
-
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "create-prisma");
|
|
1604
|
-
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "create-prisma");
|
|
1605
|
-
}
|
|
1606
|
-
async function getAnonymousId() {
|
|
1607
|
-
const telemetryConfigPath = path.join(getTelemetryConfigDir(), TELEMETRY_CONFIG_FILE);
|
|
1608
|
-
try {
|
|
1609
|
-
const config = await fs.readJSON(telemetryConfigPath);
|
|
1610
|
-
if (typeof config.anonymousId === "string" && UUID_V4_REGEX.test(config.anonymousId)) return config.anonymousId;
|
|
1611
|
-
} catch {}
|
|
1612
|
-
const anonymousId = randomUUID();
|
|
1613
|
-
try {
|
|
1614
|
-
await fs.ensureDir(path.dirname(telemetryConfigPath));
|
|
1615
|
-
await fs.writeJSON(telemetryConfigPath, { anonymousId }, { spaces: 2 });
|
|
1616
|
-
} catch {}
|
|
1617
|
-
return anonymousId;
|
|
1618
|
-
}
|
|
1619
|
-
function getCommonProperties() {
|
|
1620
|
-
return {
|
|
1621
|
-
"cli-version": "0.4.1",
|
|
1622
|
-
"node-version": process.version,
|
|
1623
|
-
platform: process.platform,
|
|
1624
|
-
arch: process.arch
|
|
1625
|
-
};
|
|
1626
|
-
}
|
|
1627
|
-
function sanitizeProperties(properties) {
|
|
1628
|
-
return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0));
|
|
1629
|
-
}
|
|
1630
|
-
async function trackCliTelemetry(event, properties) {
|
|
1631
|
-
if (shouldDisableTelemetry()) return;
|
|
1632
|
-
let client;
|
|
1633
|
-
try {
|
|
1634
|
-
const distinctId = await getAnonymousId();
|
|
1635
|
-
const sanitizedProperties = sanitizeProperties({
|
|
1636
|
-
...getCommonProperties(),
|
|
1637
|
-
...properties,
|
|
1638
|
-
$process_person_profile: false
|
|
1639
|
-
});
|
|
1640
|
-
client = new PostHog(TELEMETRY_API_KEY, {
|
|
1641
|
-
host: TELEMETRY_HOST,
|
|
1642
|
-
disableGeoip: true,
|
|
1643
|
-
flushAt: 1,
|
|
1644
|
-
flushInterval: 0
|
|
1645
|
-
});
|
|
1646
|
-
await client.captureImmediate({
|
|
1647
|
-
distinctId,
|
|
1648
|
-
event,
|
|
1649
|
-
properties: sanitizedProperties,
|
|
1650
|
-
disableGeoip: true
|
|
1651
|
-
});
|
|
1652
|
-
} catch {} finally {
|
|
1653
|
-
if (client) await client.shutdown().catch(() => {});
|
|
1635
|
+
progressSpinner?.message("Configuring Prisma Next...");
|
|
1636
|
+
if (!await finalizePrismaFilesForContext(context, projectDir, provisionResult)) {
|
|
1637
|
+
stopProgressOnFailure();
|
|
1638
|
+
return false;
|
|
1654
1639
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
if (input.mcp === true) addons.push("mcp");
|
|
1663
|
-
if (input.extension === true) addons.push("extension");
|
|
1664
|
-
return addons;
|
|
1665
|
-
}
|
|
1666
|
-
function getTargetDirectoryState(context) {
|
|
1667
|
-
if (!context.targetPathState.exists) return "new";
|
|
1668
|
-
if (context.targetPathState.isEmptyDirectory) return "empty_directory";
|
|
1669
|
-
return "non_empty_directory";
|
|
1670
|
-
}
|
|
1671
|
-
function getBaseCreateProperties(input, context) {
|
|
1672
|
-
const resolvedAddons = context?.addonSetupContext?.addons ?? getRequestedAddons(input);
|
|
1673
|
-
return {
|
|
1674
|
-
command: "create",
|
|
1675
|
-
"uses-defaults": input.yes === true,
|
|
1676
|
-
verbose: input.verbose === true,
|
|
1677
|
-
force: input.force === true,
|
|
1678
|
-
template: context?.template ?? input.template ?? null,
|
|
1679
|
-
"database-provider": context?.prismaSetupContext.databaseProvider ?? input.provider ?? null,
|
|
1680
|
-
"package-manager": context?.prismaSetupContext.packageManager ?? input.packageManager ?? null,
|
|
1681
|
-
"schema-preset": context?.prismaSetupContext.schemaPreset ?? input.schemaPreset ?? null,
|
|
1682
|
-
"should-install": context?.prismaSetupContext.shouldInstall ?? input.install ?? null,
|
|
1683
|
-
"should-generate": context?.prismaSetupContext.shouldGenerate ?? input.generate ?? null,
|
|
1684
|
-
"uses-prisma-postgres": context?.prismaSetupContext.shouldUsePrismaPostgres ?? input.prismaPostgres ?? null,
|
|
1685
|
-
addons: resolvedAddons,
|
|
1686
|
-
"addon-count": resolvedAddons.length,
|
|
1687
|
-
"addon-scope": context?.addonSetupContext?.scope ?? null,
|
|
1688
|
-
"skills-count": context?.addonSetupContext?.skills.length ?? null,
|
|
1689
|
-
"skills-agents-count": context?.addonSetupContext?.skillsAgents.length ?? null,
|
|
1690
|
-
"mcp-agents-count": context?.addonSetupContext?.mcpAgents.length ?? null,
|
|
1691
|
-
"extension-target-count": context?.addonSetupContext?.extensionTargets.length ?? null,
|
|
1692
|
-
"target-directory-state": context ? getTargetDirectoryState(context) : null
|
|
1693
|
-
};
|
|
1694
|
-
}
|
|
1695
|
-
function getErrorName(error) {
|
|
1696
|
-
if (error instanceof Error) return error.name;
|
|
1697
|
-
return error === void 0 ? null : "UnknownError";
|
|
1698
|
-
}
|
|
1699
|
-
function getErrorCode(error) {
|
|
1700
|
-
if (typeof error !== "object" || error === null) return null;
|
|
1701
|
-
const exitCode = Reflect.get(error, "exitCode");
|
|
1702
|
-
if (typeof exitCode === "number") return exitCode;
|
|
1703
|
-
const code = Reflect.get(error, "code");
|
|
1704
|
-
return typeof code === "number" || typeof code === "string" ? code : null;
|
|
1705
|
-
}
|
|
1706
|
-
async function trackCreateCompleted(params) {
|
|
1707
|
-
await trackCliTelemetry("cli:create_command_completed", {
|
|
1708
|
-
...getBaseCreateProperties(params.input, params.context),
|
|
1709
|
-
"duration-ms": params.durationMs
|
|
1710
|
-
});
|
|
1711
|
-
}
|
|
1712
|
-
async function trackCreateFailed(params) {
|
|
1713
|
-
await trackCliTelemetry("cli:create_command_failed", {
|
|
1714
|
-
...getBaseCreateProperties(params.input, params.context),
|
|
1715
|
-
"duration-ms": params.durationMs,
|
|
1716
|
-
"failure-stage": params.stage,
|
|
1717
|
-
"error-name": getErrorName(params.error),
|
|
1718
|
-
"error-code": getErrorCode(params.error)
|
|
1640
|
+
if (context.shouldEmit && context.shouldInstall) progressSpinner?.message("Emitting Prisma Next contract artifacts...");
|
|
1641
|
+
const emitResult = await emitPrismaNextContractForContext(context, projectDir);
|
|
1642
|
+
const warningLines = buildWarningLines(provisionResult.warning, emitResult.warning);
|
|
1643
|
+
const nextSteps = buildNextStepsForContext({
|
|
1644
|
+
context,
|
|
1645
|
+
options,
|
|
1646
|
+
didEmitContract: emitResult.didEmitContract
|
|
1719
1647
|
});
|
|
1648
|
+
progressSpinner?.stop("Prisma Next project ready.");
|
|
1649
|
+
if (warningLines.length > 0) note(warningLines.map((line) => line.replace(/^- /, "")).join("\n"), "Heads up");
|
|
1650
|
+
note(formatAgentPrompt(), "Agent prompt");
|
|
1651
|
+
if (context.verbose) note(formatNextSteps(nextSteps), "Next steps for Prisma Next");
|
|
1652
|
+
outro("Prisma Next setup complete.");
|
|
1653
|
+
return true;
|
|
1720
1654
|
}
|
|
1721
1655
|
|
|
1722
1656
|
//#endregion
|
|
1723
1657
|
//#region src/ui/branding.ts
|
|
1724
|
-
const prismaTitle = `${styleText(["bold", "
|
|
1658
|
+
const prismaTitle = `${styleText(["bold", "cyanBright"], "◭")} ${styleText(["bold", "cyanBright"], "Create")} ${styleText(["bold", "magentaBright"], "Prisma")} ${styleText(["bold", "blueBright"], "Next")}`;
|
|
1725
1659
|
function getCreatePrismaIntro() {
|
|
1726
1660
|
return prismaTitle;
|
|
1727
1661
|
}
|
|
@@ -1729,8 +1663,7 @@ function getCreatePrismaIntro() {
|
|
|
1729
1663
|
//#endregion
|
|
1730
1664
|
//#region src/commands/create.ts
|
|
1731
1665
|
const DEFAULT_PROJECT_NAME = "my-app";
|
|
1732
|
-
const DEFAULT_TEMPLATE = "
|
|
1733
|
-
const DEFAULT_SCHEMA_PRESET = "basic";
|
|
1666
|
+
const DEFAULT_TEMPLATE = "minimal";
|
|
1734
1667
|
function toPackageName(projectName) {
|
|
1735
1668
|
return projectName.toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^-+/, "").replace(/-+$/, "") || "app";
|
|
1736
1669
|
}
|
|
@@ -1761,50 +1694,50 @@ async function promptForCreateTemplate() {
|
|
|
1761
1694
|
message: "Select template",
|
|
1762
1695
|
initialValue: DEFAULT_TEMPLATE,
|
|
1763
1696
|
options: [
|
|
1697
|
+
{
|
|
1698
|
+
value: "minimal",
|
|
1699
|
+
label: "Minimal",
|
|
1700
|
+
hint: "Script-first Prisma Next starter with no web framework"
|
|
1701
|
+
},
|
|
1764
1702
|
{
|
|
1765
1703
|
value: "hono",
|
|
1766
1704
|
label: "Hono",
|
|
1767
|
-
hint: "TypeScript API
|
|
1705
|
+
hint: "Lightweight TypeScript API server"
|
|
1768
1706
|
},
|
|
1769
1707
|
{
|
|
1770
1708
|
value: "elysia",
|
|
1771
1709
|
label: "Elysia",
|
|
1772
|
-
hint: "TypeScript API
|
|
1710
|
+
hint: "Bun-friendly TypeScript API server"
|
|
1773
1711
|
},
|
|
1774
1712
|
{
|
|
1775
1713
|
value: "nest",
|
|
1776
1714
|
label: "NestJS",
|
|
1777
|
-
hint: "
|
|
1715
|
+
hint: "Structured Node API with controllers and services"
|
|
1778
1716
|
},
|
|
1779
1717
|
{
|
|
1780
1718
|
value: "next",
|
|
1781
1719
|
label: "Next.js",
|
|
1782
|
-
hint: "
|
|
1720
|
+
hint: "Full-stack React app with App Router"
|
|
1783
1721
|
},
|
|
1784
1722
|
{
|
|
1785
1723
|
value: "svelte",
|
|
1786
1724
|
label: "SvelteKit",
|
|
1787
|
-
hint: "
|
|
1725
|
+
hint: "Full-stack Svelte 5 app with Vite"
|
|
1788
1726
|
},
|
|
1789
1727
|
{
|
|
1790
1728
|
value: "astro",
|
|
1791
1729
|
label: "Astro",
|
|
1792
|
-
hint: "
|
|
1730
|
+
hint: "Content-oriented web app with server routes"
|
|
1793
1731
|
},
|
|
1794
1732
|
{
|
|
1795
1733
|
value: "nuxt",
|
|
1796
1734
|
label: "Nuxt",
|
|
1797
|
-
hint: "
|
|
1735
|
+
hint: "Full-stack Vue app with Nitro server routes"
|
|
1798
1736
|
},
|
|
1799
1737
|
{
|
|
1800
1738
|
value: "tanstack-start",
|
|
1801
1739
|
label: "TanStack Start",
|
|
1802
|
-
hint: "
|
|
1803
|
-
},
|
|
1804
|
-
{
|
|
1805
|
-
value: "turborepo",
|
|
1806
|
-
label: "Turborepo",
|
|
1807
|
-
hint: "Monorepo starter with apps + packages/db Prisma package"
|
|
1740
|
+
hint: "React app with file routes and server functions"
|
|
1808
1741
|
}
|
|
1809
1742
|
]
|
|
1810
1743
|
});
|
|
@@ -1872,8 +1805,8 @@ async function runCreateCommand(rawInput = {}) {
|
|
|
1872
1805
|
}
|
|
1873
1806
|
}
|
|
1874
1807
|
async function collectCreateContext(input) {
|
|
1875
|
-
const useDefaults = input.yes === true;
|
|
1876
1808
|
const force = input.force === true;
|
|
1809
|
+
const useDefaults = input.yes === true;
|
|
1877
1810
|
const projectNameInput = input.name ?? (useDefaults ? DEFAULT_PROJECT_NAME : await promptForProjectName());
|
|
1878
1811
|
if (projectNameInput === void 0) return;
|
|
1879
1812
|
const projectName = String(projectNameInput).trim();
|
|
@@ -1894,42 +1827,33 @@ async function collectCreateContext(input) {
|
|
|
1894
1827
|
cancel(`Target directory ${formatPathForDisplay(targetDirectory)} is not empty. Use --force to continue.`);
|
|
1895
1828
|
return;
|
|
1896
1829
|
}
|
|
1897
|
-
const prismaSetupContext = await collectPrismaSetupContext(input, {
|
|
1898
|
-
projectDir: targetDirectory,
|
|
1899
|
-
defaultSchemaPreset: DEFAULT_SCHEMA_PRESET
|
|
1900
|
-
});
|
|
1830
|
+
const prismaSetupContext = await collectPrismaSetupContext(input, { projectDir: targetDirectory });
|
|
1901
1831
|
if (!prismaSetupContext) return;
|
|
1902
|
-
const addonSetupContext = await collectCreateAddonSetupContext(input, {
|
|
1903
|
-
useDefaults,
|
|
1904
|
-
provider: prismaSetupContext.databaseProvider,
|
|
1905
|
-
shouldUsePrismaPostgres: prismaSetupContext.shouldUsePrismaPostgres
|
|
1906
|
-
});
|
|
1907
|
-
if (addonSetupContext === void 0) return;
|
|
1908
1832
|
return {
|
|
1909
1833
|
targetDirectory,
|
|
1910
1834
|
targetPathState,
|
|
1911
1835
|
force,
|
|
1912
1836
|
template,
|
|
1913
1837
|
projectPackageName: toPackageName(path.basename(targetDirectory)),
|
|
1914
|
-
prismaSetupContext
|
|
1915
|
-
addonSetupContext: addonSetupContext ?? void 0
|
|
1838
|
+
prismaSetupContext
|
|
1916
1839
|
};
|
|
1917
1840
|
}
|
|
1918
1841
|
async function executeCreateContext(context) {
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1842
|
+
const createSpinner = context.prismaSetupContext.verbose ? void 0 : spinner();
|
|
1843
|
+
createSpinner?.start("Creating Prisma Next project...");
|
|
1921
1844
|
try {
|
|
1845
|
+
if (context.prismaSetupContext.verbose) log.step(`Scaffolding ${context.template} starter.`);
|
|
1922
1846
|
await scaffoldCreateTemplate({
|
|
1923
1847
|
projectDir: context.targetDirectory,
|
|
1924
1848
|
projectName: context.projectPackageName,
|
|
1925
1849
|
template: context.template,
|
|
1926
|
-
schemaPreset: context.prismaSetupContext.schemaPreset,
|
|
1927
1850
|
provider: context.prismaSetupContext.databaseProvider,
|
|
1851
|
+
authoring: context.prismaSetupContext.authoring,
|
|
1928
1852
|
packageManager: context.prismaSetupContext.packageManager
|
|
1929
1853
|
});
|
|
1930
|
-
|
|
1854
|
+
if (context.prismaSetupContext.verbose) log.success("Starter files scaffolded.");
|
|
1931
1855
|
} catch (error) {
|
|
1932
|
-
|
|
1856
|
+
createSpinner?.stop("Could not create Prisma Next project.");
|
|
1933
1857
|
return {
|
|
1934
1858
|
ok: false,
|
|
1935
1859
|
stage: "scaffold_template",
|
|
@@ -1940,9 +1864,11 @@ async function executeCreateContext(context) {
|
|
|
1940
1864
|
await writeCreateTemplateDependencies({
|
|
1941
1865
|
template: context.template,
|
|
1942
1866
|
packageManager: context.prismaSetupContext.packageManager,
|
|
1943
|
-
projectDir: context.targetDirectory
|
|
1867
|
+
projectDir: context.targetDirectory,
|
|
1868
|
+
prismaNextSpec: context.prismaSetupContext.prismaNextSpec
|
|
1944
1869
|
});
|
|
1945
1870
|
} catch (error) {
|
|
1871
|
+
createSpinner?.stop("Could not create Prisma Next project.");
|
|
1946
1872
|
return {
|
|
1947
1873
|
ok: false,
|
|
1948
1874
|
stage: "scaffold_template",
|
|
@@ -1950,31 +1876,22 @@ async function executeCreateContext(context) {
|
|
|
1950
1876
|
};
|
|
1951
1877
|
}
|
|
1952
1878
|
if (context.targetPathState.exists && !context.targetPathState.isEmptyDirectory && context.force) log.warn(`Used --force in non-empty directory ${formatPathForDisplay(context.targetDirectory)}.`);
|
|
1953
|
-
const nextSteps = formatPathForDisplay(context.targetDirectory) === "." ? [] : [
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
packageManager: context.prismaSetupContext.packageManager,
|
|
1958
|
-
projectDir: context.targetDirectory,
|
|
1959
|
-
verbose: context.prismaSetupContext.verbose
|
|
1960
|
-
});
|
|
1961
|
-
} catch (error) {
|
|
1962
|
-
return {
|
|
1963
|
-
ok: false,
|
|
1964
|
-
stage: "addons",
|
|
1965
|
-
error
|
|
1966
|
-
};
|
|
1967
|
-
}
|
|
1879
|
+
const nextSteps = formatPathForDisplay(context.targetDirectory) === "." ? [] : [{
|
|
1880
|
+
command: `cd ${formatPathForDisplay(context.targetDirectory)}`,
|
|
1881
|
+
description: "Enter your new project directory."
|
|
1882
|
+
}];
|
|
1968
1883
|
try {
|
|
1969
1884
|
if (!await executePrismaSetupContext(context.prismaSetupContext, {
|
|
1970
1885
|
prependNextSteps: nextSteps,
|
|
1971
1886
|
projectDir: context.targetDirectory,
|
|
1972
|
-
includeDevNextStep: true
|
|
1887
|
+
includeDevNextStep: true,
|
|
1888
|
+
progressSpinner: createSpinner
|
|
1973
1889
|
})) return {
|
|
1974
1890
|
ok: false,
|
|
1975
1891
|
stage: "prisma_setup"
|
|
1976
1892
|
};
|
|
1977
1893
|
} catch (error) {
|
|
1894
|
+
createSpinner?.stop("Could not create Prisma Next project.");
|
|
1978
1895
|
return {
|
|
1979
1896
|
ok: false,
|
|
1980
1897
|
stage: "prisma_setup",
|
|
@@ -1985,4 +1902,4 @@ async function executeCreateContext(context) {
|
|
|
1985
1902
|
}
|
|
1986
1903
|
|
|
1987
1904
|
//#endregion
|
|
1988
|
-
export {
|
|
1905
|
+
export { DatabaseProviderSchema as a, CreateTemplateSchema as i, AuthoringStyleSchema as n, DatabaseUrlSchema as o, CreateCommandInputSchema as r, PackageManagerSchema as s, runCreateCommand as t };
|