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