@prisma/cli 8.0.0-rc.1 → 8.0.0-rc.10-dev.80

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.
Files changed (110) hide show
  1. package/README.md +25 -29
  2. package/dist/cli.js +14551 -16
  3. package/dist/{v8/sender.js → sender.js} +1 -1
  4. package/package.json +17 -18
  5. package/dist/adapters/git.js +0 -54
  6. package/dist/adapters/local-state.js +0 -155
  7. package/dist/auth/client.js +0 -30
  8. package/dist/auth/errors.js +0 -50
  9. package/dist/auth/guard.js +0 -39
  10. package/dist/auth/login.js +0 -471
  11. package/dist/auth/operations.js +0 -208
  12. package/dist/auth/recipient.js +0 -42
  13. package/dist/auth/token-storage.js +0 -439
  14. package/dist/auth/workspaces.js +0 -171
  15. package/dist/cli-command.js +0 -14
  16. package/dist/cli-name.js +0 -12
  17. package/dist/cli2.js +0 -136
  18. package/dist/command-arguments.js +0 -12
  19. package/dist/commands/agent/index.js +0 -60
  20. package/dist/commands/app/index.js +0 -350
  21. package/dist/commands/auth/index.js +0 -95
  22. package/dist/commands/branch/index.js +0 -27
  23. package/dist/commands/bucket/index.js +0 -123
  24. package/dist/commands/build/index.js +0 -29
  25. package/dist/commands/database/index.js +0 -249
  26. package/dist/commands/env.js +0 -99
  27. package/dist/commands/feedback/index.js +0 -20
  28. package/dist/commands/git/index.js +0 -36
  29. package/dist/commands/init/index.js +0 -33
  30. package/dist/commands/project/index.js +0 -118
  31. package/dist/commands/version/index.js +0 -18
  32. package/dist/controllers/agent-setup.js +0 -52
  33. package/dist/controllers/agent.js +0 -228
  34. package/dist/controllers/app-env-api.js +0 -55
  35. package/dist/controllers/app-env-file.js +0 -181
  36. package/dist/controllers/app-env.js +0 -502
  37. package/dist/controllers/app.js +0 -2508
  38. package/dist/controllers/auth.js +0 -124
  39. package/dist/controllers/branch.js +0 -92
  40. package/dist/controllers/bucket.js +0 -184
  41. package/dist/controllers/build.js +0 -88
  42. package/dist/controllers/database.js +0 -463
  43. package/dist/controllers/feedback.js +0 -90
  44. package/dist/controllers/init.js +0 -814
  45. package/dist/controllers/project.js +0 -892
  46. package/dist/controllers/select-prompt-port.js +0 -13
  47. package/dist/controllers/version.js +0 -12
  48. package/dist/errors.js +0 -102
  49. package/dist/lib/agent/cli-command.js +0 -20
  50. package/dist/lib/agent/constants.js +0 -12
  51. package/dist/lib/agent/package-manager.js +0 -99
  52. package/dist/lib/agent/setup-status.js +0 -83
  53. package/dist/lib/app/app-provider.js +0 -617
  54. package/dist/lib/app/branch-database-api.js +0 -102
  55. package/dist/lib/app/branch-database-deploy.js +0 -353
  56. package/dist/lib/app/branch-database.js +0 -234
  57. package/dist/lib/app/build-settings.js +0 -98
  58. package/dist/lib/app/build.js +0 -82
  59. package/dist/lib/app/bun-project.js +0 -45
  60. package/dist/lib/app/compute-config.js +0 -145
  61. package/dist/lib/app/deploy-output.js +0 -24
  62. package/dist/lib/app/deploy-plan.js +0 -59
  63. package/dist/lib/app/deploy-progress.js +0 -100
  64. package/dist/lib/app/domain-guidance.js +0 -14
  65. package/dist/lib/app/env-config.js +0 -67
  66. package/dist/lib/app/env-file.js +0 -82
  67. package/dist/lib/app/env-vars.js +0 -50
  68. package/dist/lib/app/local-dev.js +0 -109
  69. package/dist/lib/app/production-deploy-gate.js +0 -162
  70. package/dist/lib/app/read-branch.js +0 -30
  71. package/dist/lib/bucket/provider.js +0 -139
  72. package/dist/lib/database/provider.js +0 -385
  73. package/dist/lib/diagnostics.js +0 -16
  74. package/dist/lib/feedback.js +0 -15
  75. package/dist/lib/fs/home-path.js +0 -24
  76. package/dist/lib/git/local-branch.js +0 -53
  77. package/dist/lib/git/local-status.js +0 -57
  78. package/dist/lib/project/interactive-setup.js +0 -57
  79. package/dist/lib/project/local-pin.js +0 -200
  80. package/dist/lib/project/provider.js +0 -93
  81. package/dist/lib/project/resolution.js +0 -389
  82. package/dist/lib/project/setup.js +0 -138
  83. package/dist/lib/version.js +0 -56
  84. package/dist/lib/workspace-id.js +0 -18
  85. package/dist/output/patterns.js +0 -90
  86. package/dist/presenters/agent.js +0 -74
  87. package/dist/presenters/app-env.js +0 -265
  88. package/dist/presenters/app.js +0 -672
  89. package/dist/presenters/auth.js +0 -184
  90. package/dist/presenters/branch.js +0 -46
  91. package/dist/presenters/bucket.js +0 -174
  92. package/dist/presenters/database.js +0 -462
  93. package/dist/presenters/feedback.js +0 -26
  94. package/dist/presenters/init.js +0 -30
  95. package/dist/presenters/project.js +0 -268
  96. package/dist/presenters/verbose-context.js +0 -64
  97. package/dist/presenters/version.js +0 -29
  98. package/dist/shell/cli-command.js +0 -2
  99. package/dist/shell/command-meta.js +0 -923
  100. package/dist/shell/command-runner.js +0 -119
  101. package/dist/shell/diagnostics-output.js +0 -57
  102. package/dist/shell/global-flags.js +0 -37
  103. package/dist/shell/help.js +0 -90
  104. package/dist/shell/output.js +0 -144
  105. package/dist/shell/prompt.js +0 -48
  106. package/dist/shell/runtime.js +0 -38
  107. package/dist/shell/ui.js +0 -116
  108. package/dist/state-dir.js +0 -12
  109. package/dist/update-check.js +0 -248
  110. package/dist/v8/cli.js +0 -14699
@@ -1,814 +0,0 @@
1
- import { detectPackageManagerSync } from "../lib/agent/package-manager.js";
2
- import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
3
- import { CliError, usageError } from "../errors.js";
4
- import { canPrompt } from "../shell/runtime.js";
5
- import { confirmPrompt, isPromptCancelError, selectPrompt, textPrompt } from "../shell/prompt.js";
6
- import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
7
- import { readLocalResolutionPin } from "../lib/project/local-pin.js";
8
- import { maybePromptForAgentSetup } from "./agent-setup.js";
9
- import { runProjectLink } from "./project.js";
10
- import { detectDeployFramework } from "./app.js";
11
- import { execa } from "execa";
12
- import path from "node:path";
13
- import { readFile, rm, writeFile } from "node:fs/promises";
14
- import { COMPUTE_CONFIG_FILENAME, COMPUTE_CONFIG_JSON_FILENAME, COMPUTE_REGIONS, FRAMEWORKS, defaultHttpPortForBuildType, findComputeConfigCandidates, findComputeConfigDir, frameworkByKey, frameworkFromAlias, normalizeComputeConfig, serializeComputeConfig, serializeComputeConfigJson } from "@prisma/compute-sdk/config";
15
- //#region src/controllers/init.ts
16
- async function runInit(context, flags) {
17
- const cwd = context.runtime.cwd;
18
- const signal = context.runtime.signal;
19
- const formatCommand = resolvePrismaCliPackageCommandFormatterSync(cwd);
20
- const format = parseInitFormat(flags.format, formatCommand);
21
- rejectInstallFlagForJsonFormat(format.value, flags, formatCommand);
22
- const route = await routeExistingComputeConfig(cwd, signal, format);
23
- if (route.kind === "convert") {
24
- rejectConversionResolutionFlags(flags, formatCommand);
25
- return runInitConversion(context, flags, route.jsonConfigPath, formatCommand);
26
- }
27
- const region = parseInitRegion(flags.region, formatCommand);
28
- let framework = await resolveInitFramework(context, flags, formatCommand);
29
- const name = await resolveInitAppName(cwd, flags.name, signal, formatCommand);
30
- let httpPort = parseInitHttpPort(flags.httpPort, formatCommand) ?? {
31
- value: defaultHttpPortForBuildType(frameworkByKey(framework.key).buildType),
32
- source: "framework default"
33
- };
34
- const adjusted = await maybeAdjustSettings(context, framework, httpPort, { portExplicit: flags.httpPort !== void 0 });
35
- framework = adjusted.framework;
36
- httpPort = adjusted.httpPort;
37
- rejectCustomFrameworkForJsonFormat(format.value, framework, formatCommand);
38
- const entry = await resolveInitEntry(cwd, framework, flags.entry, signal);
39
- const resolved = {
40
- name,
41
- framework,
42
- entry,
43
- httpPort,
44
- region
45
- };
46
- const settings = initSettingRows(resolved);
47
- renderInitSettingsPreview(context, settings);
48
- const app = initAppConfig(resolved);
49
- const configFile = initConfigFile({ app }, format.value, framework.key);
50
- const configPath = path.join(cwd, configFile.filename);
51
- signal.throwIfAborted();
52
- await writeInitConfigFile(configPath, configFile.source);
53
- const warnings = [];
54
- const types = format.value === "json" ? {
55
- status: "skipped",
56
- package: COMPUTE_SDK_PACKAGE,
57
- installCommand: null
58
- } : await resolveInitTypes(context, flags, { onWarning: (message) => warnings.push(message) });
59
- const link = await resolveInitLink(context, flags, {
60
- onWarning: (message) => warnings.push(message),
61
- formatCommand
62
- });
63
- warnings.push(...await maybePromptForAgentSetup(context, cwd));
64
- return {
65
- command: "init",
66
- result: {
67
- configPath: configFile.filename,
68
- format: format.value,
69
- converted: false,
70
- directory: formatInitDirectory(cwd),
71
- app,
72
- settings,
73
- types,
74
- link
75
- },
76
- warnings,
77
- nextSteps: initNextSteps(types, link, formatCommand)
78
- };
79
- }
80
- function rejectInstallFlagForJsonFormat(format, flags, formatCommand) {
81
- if (format !== "json" || flags.install !== true) return;
82
- throw usageError("--install does not apply to the JSON config format", `${COMPUTE_CONFIG_JSON_FILENAME} is a dependency-free static config; the ${COMPUTE_SDK_PACKAGE} devDependency exists only for ${COMPUTE_CONFIG_FILENAME} editor types.`, "Drop --install, or use the TypeScript format.", [formatCommand([
83
- "init",
84
- "--format",
85
- "json"
86
- ])], "app");
87
- }
88
- /**
89
- * The custom framework needs build.outputDirectory and build.entrypoint,
90
- * which init does not collect. The TypeScript format carries a commented
91
- * build stub to fill in; strict JSON cannot hold comments, so refuse here
92
- * instead of writing a config that deploy would reject.
93
- */
94
- function rejectCustomFrameworkForJsonFormat(format, framework, formatCommand) {
95
- if (format !== "json" || framework.key !== "custom") return;
96
- throw usageError("Custom framework requires the TypeScript config format", "The custom framework needs build.outputDirectory and build.entrypoint, which init does not collect; the TypeScript format includes a commented build stub to complete, and strict JSON cannot carry it.", `Rerun without --format json and fill in the build stub, or write ${COMPUTE_CONFIG_JSON_FILENAME} by hand with a build object.`, [formatCommand([
97
- "init",
98
- "--framework",
99
- "custom"
100
- ])], "app");
101
- }
102
- /** Preview rows for a fresh init, one per resolved setting. */
103
- function initSettingRows(resolved) {
104
- const { name, framework, entry, httpPort, region } = resolved;
105
- return [
106
- {
107
- key: "app",
108
- value: name.value,
109
- source: name.source
110
- },
111
- {
112
- key: "framework",
113
- value: framework.displayName,
114
- source: framework.source
115
- },
116
- ...entry ? [{
117
- key: "entry",
118
- value: entry.value,
119
- source: entry.source
120
- }] : [],
121
- {
122
- key: "http port",
123
- value: String(httpPort.value),
124
- source: httpPort.source
125
- },
126
- ...region ? [{
127
- key: "region",
128
- value: region,
129
- source: "flag"
130
- }] : []
131
- ];
132
- }
133
- /** App identity written to the config and reported in the result. */
134
- function initAppConfig(resolved) {
135
- const { name, framework, entry, httpPort, region } = resolved;
136
- return {
137
- name: name.value,
138
- framework: framework.key,
139
- httpPort: httpPort.value,
140
- ...entry ? { entry: entry.value } : {},
141
- ...region ? { region } : {}
142
- };
143
- }
144
- /** Name and contents of the config file for the chosen format. */
145
- function initConfigFile(config, format, framework) {
146
- if (format === "json") return {
147
- filename: COMPUTE_CONFIG_JSON_FILENAME,
148
- source: serializeComputeConfigJson(config)
149
- };
150
- const source = serializeComputeConfig(config);
151
- return {
152
- filename: COMPUTE_CONFIG_FILENAME,
153
- source: framework === "custom" ? source + CUSTOM_BUILD_STUB : source
154
- };
155
- }
156
- async function writeInitConfigFile(configPath, source) {
157
- try {
158
- await writeFile(configPath, source, {
159
- encoding: "utf8",
160
- flag: "wx"
161
- });
162
- } catch (error) {
163
- if (error.code === "EEXIST") throw initConfigExistsError(configPath);
164
- throw error;
165
- }
166
- }
167
- function initNextSteps(types, link, formatCommand) {
168
- const typesMissing = types.status !== "installed" && types.status !== "already-installed";
169
- const unlinked = link.status !== "linked" && link.status !== "already-linked";
170
- return [
171
- ...typesMissing && types.installCommand ? [types.installCommand] : [],
172
- formatCommand(["app", "deploy"]),
173
- ...unlinked ? [formatCommand(["project", "link"])] : []
174
- ];
175
- }
176
- /** Dev dependency that provides editor types for the generated config. */
177
- const COMPUTE_SDK_PACKAGE = "@prisma/compute-sdk";
178
- function packageAddCommand(packageManager) {
179
- switch (packageManager) {
180
- case "pnpm": return [
181
- "pnpm",
182
- "add",
183
- "-D",
184
- COMPUTE_SDK_PACKAGE
185
- ];
186
- case "bun": return [
187
- "bun",
188
- "add",
189
- "-d",
190
- COMPUTE_SDK_PACKAGE
191
- ];
192
- case "yarn": return [
193
- "yarn",
194
- "add",
195
- "-D",
196
- COMPUTE_SDK_PACKAGE
197
- ];
198
- case "npm": return [
199
- "npm",
200
- "install",
201
- "-D",
202
- COMPUTE_SDK_PACKAGE
203
- ];
204
- }
205
- }
206
- /**
207
- * Offers to install `@prisma/compute-sdk` as a devDependency so the generated
208
- * config's typed import resolves in the editor. Deploy resolves the import
209
- * without a local install, so every outcome short of success is a hint, never
210
- * a failure.
211
- */
212
- async function resolveInitTypes(context, flags, hooks) {
213
- const cwd = context.runtime.cwd;
214
- const read = await readPackageJsonForTypes(context, hooks);
215
- if (!read.readable) return {
216
- status: "skipped",
217
- package: COMPUTE_SDK_PACKAGE,
218
- installCommand: null
219
- };
220
- const packageJson = read.packageJson;
221
- if (hasComputeSdkDependency(packageJson)) return {
222
- status: "already-installed",
223
- package: COMPUTE_SDK_PACKAGE,
224
- installCommand: null
225
- };
226
- const installCommand = packageAddCommand(detectPackageManagerSync(cwd) ?? "npm");
227
- const installCommandText = installCommand.join(" ");
228
- const state = (status) => ({
229
- status,
230
- package: COMPUTE_SDK_PACKAGE,
231
- installCommand: installCommandText
232
- });
233
- if (!packageJson) return state("skipped");
234
- const decision = await resolveTypesInstallDecision(context, flags, installCommandText);
235
- if (decision !== "install") return state(decision);
236
- return state(await installComputeSdkTypes(context, installCommand, hooks));
237
- }
238
- /**
239
- * The types install runs after the config is written, so an unreadable
240
- * package.json (malformed JSON, permissions) must not turn the already
241
- * successful write into a command failure; it degrades to a skip.
242
- */
243
- async function readPackageJsonForTypes(context, hooks) {
244
- try {
245
- return {
246
- readable: true,
247
- packageJson: await readBunPackageJson(context.runtime.cwd, context.runtime.signal)
248
- };
249
- } catch (error) {
250
- if (context.runtime.signal.aborted) throw error;
251
- hooks.onWarning(`Skipped the ${COMPUTE_SDK_PACKAGE} types install: package.json could not be read (${firstErrorLine(error)}).`);
252
- return { readable: false };
253
- }
254
- }
255
- async function resolveTypesInstallDecision(context, flags, installCommandText) {
256
- if (flags.install === false) return "skipped";
257
- if (flags.install === true) return "install";
258
- if (!canPrompt(context) || context.flags.yes) return "skipped";
259
- try {
260
- return await confirmPrompt({
261
- input: context.runtime.stdin,
262
- output: context.output.stderr,
263
- signal: context.runtime.signal,
264
- message: `Install ${COMPUTE_SDK_PACKAGE} for config types? (${installCommandText})`,
265
- initialValue: true
266
- }) ? "install" : "declined";
267
- } catch (error) {
268
- if (isPromptCancelError(error)) return "declined";
269
- throw error;
270
- }
271
- }
272
- async function installComputeSdkTypes(context, installCommand, hooks) {
273
- const installCommandText = installCommand.join(" ");
274
- const command = resolveInitInstallCommandOverride(context) ?? installCommand;
275
- if (!context.flags.quiet && !context.flags.json) context.output.stderr.write(`Installing ${COMPUTE_SDK_PACKAGE}...\n`);
276
- try {
277
- const [executable, ...args] = command;
278
- await execa(executable, args, {
279
- cwd: context.runtime.cwd,
280
- env: context.runtime.env,
281
- cancelSignal: context.runtime.signal,
282
- stdin: "ignore"
283
- });
284
- return "installed";
285
- } catch (error) {
286
- if (context.runtime.signal.aborted) throw error;
287
- hooks.onWarning(`Installing ${COMPUTE_SDK_PACKAGE} failed: ${firstErrorLine(error)}. Install it later with ${installCommandText}.`);
288
- return "failed";
289
- }
290
- }
291
- function hasComputeSdkDependency(packageJson) {
292
- for (const group of [packageJson?.dependencies, packageJson?.devDependencies]) if (group && typeof group === "object" && COMPUTE_SDK_PACKAGE in group) return true;
293
- return false;
294
- }
295
- /** Test hook: JSON array command that replaces the real package-manager install. */
296
- function resolveInitInstallCommandOverride(context) {
297
- const raw = context.runtime.env.PRISMA_CLI_INIT_INSTALL_COMMAND;
298
- if (!raw) return null;
299
- try {
300
- const parsed = JSON.parse(raw);
301
- return Array.isArray(parsed) && parsed.every((p) => typeof p === "string") ? parsed : null;
302
- } catch {
303
- return null;
304
- }
305
- }
306
- const CUSTOM_BUILD_STUB = `
307
- // framework "custom" deploys a prebuilt artifact. Add its build settings:
308
- // build: {
309
- // command: "npm run build",
310
- // outputDirectory: "dist",
311
- // entrypoint: "server.js",
312
- // },
313
- `;
314
- /**
315
- * Nearest existing compute config, searching from `cwd` up to the source
316
- * root. Init routes on this: refuse, convert, or proceed fresh.
317
- */
318
- async function findExistingComputeConfig(cwd, signal) {
319
- const configDir = await findComputeConfigDir(cwd, signal);
320
- if (!configDir) return null;
321
- return {
322
- directory: configDir,
323
- candidates: await findComputeConfigCandidates(configDir, signal)
324
- };
325
- }
326
- /**
327
- * Init's route when a config already exists: convert a sole JSON config that
328
- * an explicit `--format ts` asked for, or refuse. A directory with no config
329
- * proceeds fresh.
330
- */
331
- async function routeExistingComputeConfig(cwd, signal, format) {
332
- const existingConfig = await findExistingComputeConfig(cwd, signal);
333
- if (!existingConfig) return { kind: "fresh" };
334
- const solePath = existingConfig.candidates.length === 1 ? existingConfig.candidates[0] : void 0;
335
- const soleIsJson = solePath !== void 0 && path.extname(solePath) === ".json";
336
- if (soleIsJson && format.value === "typescript" && format.explicit) return {
337
- kind: "convert",
338
- jsonConfigPath: solePath
339
- };
340
- if (solePath && !soleIsJson && format.value === "json") throw initConvertUnsupportedError(solePath);
341
- throw initConfigExistsError(existingConfig.candidates[0] ?? existingConfig.directory);
342
- }
343
- function parseInitFormat(value, formatCommand) {
344
- if (value === void 0) return {
345
- value: "typescript",
346
- explicit: false
347
- };
348
- const normalized = value.trim().toLowerCase();
349
- if (normalized === "ts" || normalized === "typescript") return {
350
- value: "typescript",
351
- explicit: true
352
- };
353
- if (normalized === "json") return {
354
- value: "json",
355
- explicit: true
356
- };
357
- throw usageError("Unknown config format", `"${value}" is not a supported config format.`, "Pass --format ts or --format json.", [formatCommand([
358
- "init",
359
- "--format",
360
- "json"
361
- ])], "app");
362
- }
363
- /**
364
- * Conversion transports the existing config's values; it never re-resolves
365
- * settings. Refusing resolution flags beats silently ignoring them.
366
- */
367
- function rejectConversionResolutionFlags(flags, formatCommand) {
368
- const passed = [
369
- flags.framework !== void 0 ? "--framework" : null,
370
- flags.entry !== void 0 ? "--entry" : null,
371
- flags.httpPort !== void 0 ? "--http-port" : null,
372
- flags.name !== void 0 ? "--name" : null,
373
- flags.region !== void 0 ? "--region" : null
374
- ].filter((flag) => flag !== null);
375
- if (passed.length === 0) return;
376
- throw usageError(`${passed.join(", ")} ${passed.length === 1 ? "does" : "do"} not apply when converting an existing config`, `--format ts with an existing ${COMPUTE_CONFIG_JSON_FILENAME} converts it as-is; settings are transported, never re-resolved.`, `Convert first, then edit ${COMPUTE_CONFIG_FILENAME} directly.`, [formatCommand([
377
- "init",
378
- "--format",
379
- "ts"
380
- ])], "app");
381
- }
382
- function initConvertIncompleteError(jsonConfigPath, tsConfigPath) {
383
- return new CliError({
384
- code: "INIT_CONVERT_INCOMPLETE",
385
- domain: "app",
386
- summary: "Conversion left two config files behind",
387
- why: `${path.basename(tsConfigPath)} was written but ${path.basename(jsonConfigPath)} could not be deleted, and rolling back the write also failed. Commands refuse to load a directory with two config files.`,
388
- fix: `Delete one file by hand: keep ${path.basename(tsConfigPath)} to finish the conversion, or keep ${path.basename(jsonConfigPath)} to undo it.`,
389
- exitCode: 1,
390
- nextSteps: [],
391
- meta: {
392
- jsonConfigPath,
393
- tsConfigPath
394
- }
395
- });
396
- }
397
- function initConvertUnsupportedError(existingPath) {
398
- return new CliError({
399
- code: "INIT_CONVERT_UNSUPPORTED",
400
- domain: "app",
401
- summary: "TypeScript configs do not convert to JSON",
402
- why: `${existingPath} may contain imports, expressions, or comments that the static ${COMPUTE_CONFIG_JSON_FILENAME} format cannot express, so an automatic conversion would be lossy.`,
403
- fix: `If the config is fully static, rewrite it by hand as ${COMPUTE_CONFIG_JSON_FILENAME} and delete ${path.basename(existingPath)}.`,
404
- exitCode: 1,
405
- nextSteps: [],
406
- meta: { existingConfigPath: existingPath }
407
- });
408
- }
409
- /**
410
- * The graduation path: an explicit `--format ts` with an existing
411
- * `prisma.compute.json` rewrites the same config as `prisma.compute.ts` and
412
- * deletes the JSON file, so a static config can grow into a programmatic one.
413
- * The values are transported, never re-resolved.
414
- */
415
- async function runInitConversion(context, flags, jsonConfigPath, formatCommand) {
416
- const cwd = context.runtime.cwd;
417
- const signal = context.runtime.signal;
418
- const { config, loaded } = await readConvertibleJsonConfig(jsonConfigPath, signal);
419
- const tsConfigPath = path.join(loaded.configDir, COMPUTE_CONFIG_FILENAME);
420
- const source = serializeComputeConfig(config);
421
- signal.throwIfAborted();
422
- await writeInitConfigFile(tsConfigPath, source);
423
- await removeConvertedJsonConfig(jsonConfigPath, tsConfigPath);
424
- const settings = conversionSettings(loaded);
425
- renderInitSettingsPreview(context, settings);
426
- const warnings = [];
427
- const stepContext = withRuntimeCwd(context, loaded.configDir);
428
- const types = await resolveInitTypes(stepContext, flags, { onWarning: (message) => warnings.push(message) });
429
- const link = await resolveInitLink(stepContext, flags, {
430
- onWarning: (message) => warnings.push(message),
431
- formatCommand
432
- });
433
- warnings.push(...await maybePromptForAgentSetup(stepContext, loaded.configDir));
434
- return {
435
- command: "init",
436
- result: {
437
- configPath: path.relative(cwd, tsConfigPath) || COMPUTE_CONFIG_FILENAME,
438
- format: "typescript",
439
- converted: true,
440
- directory: formatInitDirectory(loaded.configDir),
441
- app: conversionApp(loaded),
442
- settings,
443
- types,
444
- link
445
- },
446
- warnings,
447
- nextSteps: initNextSteps(types, link, formatCommand)
448
- };
449
- }
450
- /**
451
- * The JSON config a conversion transports: its parsed contents, plus the
452
- * normalized view the preview and result are built from.
453
- */
454
- async function readConvertibleJsonConfig(jsonConfigPath, signal) {
455
- let parsed;
456
- try {
457
- parsed = JSON.parse(await readFile(jsonConfigPath, "utf8"));
458
- } catch (error) {
459
- if (signal.aborted) throw error;
460
- throw initConvertInvalidError(jsonConfigPath, [firstErrorLine(error)]);
461
- }
462
- const config = stripJsonSchemaKey(parsed);
463
- const normalized = normalizeComputeConfig(config, jsonConfigPath);
464
- if (normalized.isErr()) throw initConvertInvalidError(jsonConfigPath, normalized.error.issues);
465
- return {
466
- config,
467
- loaded: normalized.value
468
- };
469
- }
470
- async function removeConvertedJsonConfig(jsonConfigPath, tsConfigPath) {
471
- try {
472
- await rm(jsonConfigPath);
473
- } catch (error) {
474
- try {
475
- await rm(tsConfigPath, { force: true });
476
- } catch {
477
- throw initConvertIncompleteError(jsonConfigPath, tsConfigPath);
478
- }
479
- throw error;
480
- }
481
- }
482
- /** The same command context, acting from `cwd` instead of the invocation directory. */
483
- function withRuntimeCwd(context, cwd) {
484
- if (path.resolve(context.runtime.cwd) === path.resolve(cwd)) return context;
485
- return {
486
- ...context,
487
- runtime: {
488
- ...context.runtime,
489
- cwd
490
- }
491
- };
492
- }
493
- function stripJsonSchemaKey(parsed) {
494
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
495
- const { $schema: _schema, ...config } = parsed;
496
- return config;
497
- }
498
- function initConvertInvalidError(jsonConfigPath, issues) {
499
- return new CliError({
500
- code: "COMPUTE_CONFIG_INVALID",
501
- domain: "app",
502
- summary: `Invalid ${path.basename(jsonConfigPath)}`,
503
- why: issues.join(" "),
504
- fix: `Fix ${path.basename(jsonConfigPath)} and rerun the conversion.`,
505
- where: jsonConfigPath,
506
- meta: {
507
- configPath: jsonConfigPath,
508
- issues
509
- },
510
- exitCode: 2,
511
- nextSteps: []
512
- });
513
- }
514
- /** Preview rows for a conversion; every value is sourced from the JSON file. */
515
- function conversionSettings(loaded) {
516
- const target = loaded.kind === "single" ? loaded.targets[0] : void 0;
517
- if (!target) return [];
518
- const source = COMPUTE_CONFIG_JSON_FILENAME;
519
- return [
520
- ...target.name ? [{
521
- key: "app",
522
- value: target.name,
523
- source
524
- }] : [],
525
- ...target.framework ? [{
526
- key: "framework",
527
- value: frameworkByKey(target.framework).displayName,
528
- source
529
- }] : [],
530
- ...target.entry ? [{
531
- key: "entry",
532
- value: target.entry,
533
- source
534
- }] : [],
535
- ...target.httpPort !== null ? [{
536
- key: "http port",
537
- value: String(target.httpPort),
538
- source
539
- }] : [],
540
- ...target.region ? [{
541
- key: "region",
542
- value: target.region,
543
- source
544
- }] : []
545
- ];
546
- }
547
- /**
548
- * App identity for the conversion result. Configs written by init pin all of
549
- * name, framework, and httpPort; hand-written configs that omit any of them
550
- * (or define multiple apps) report null instead of a partial identity.
551
- */
552
- function conversionApp(loaded) {
553
- const target = loaded.kind === "single" ? loaded.targets[0] : void 0;
554
- if (!target?.name || !target.framework || target.httpPort === null) return null;
555
- return {
556
- name: target.name,
557
- framework: target.framework,
558
- httpPort: target.httpPort,
559
- ...target.entry ? { entry: target.entry } : {},
560
- ...target.region ? { region: target.region } : {}
561
- };
562
- }
563
- function initConfigExistsError(existingPath) {
564
- return new CliError({
565
- code: "INIT_CONFIG_EXISTS",
566
- domain: "app",
567
- summary: "A compute config already exists",
568
- why: `${existingPath} already defines this repository's compute config, and init never overwrites or merges.`,
569
- fix: "Edit the existing config instead, or delete it first if you want init to regenerate it.",
570
- exitCode: 1,
571
- nextSteps: [],
572
- meta: { existingConfigPath: existingPath }
573
- });
574
- }
575
- async function resolveInitFramework(context, flags, formatCommand) {
576
- if (flags.framework) {
577
- const framework = frameworkFromAlias(flags.framework.trim());
578
- if (!framework) throw usageError("Unknown framework", `"${flags.framework}" is not a supported framework.`, `Pass one of: ${FRAMEWORKS.map((candidate) => candidate.key).join(", ")}.`, [formatCommand([
579
- "init",
580
- "--framework",
581
- "hono"
582
- ])], "app");
583
- return {
584
- key: framework.key,
585
- displayName: framework.displayName,
586
- source: "flag"
587
- };
588
- }
589
- const detected = await detectDeployFramework(context.runtime.cwd, context.runtime.signal);
590
- if (detected) return {
591
- key: detected.key,
592
- displayName: detected.displayName,
593
- source: detected.annotation
594
- };
595
- if (canPrompt(context) && !context.flags.yes) {
596
- const key = await selectPrompt({
597
- input: context.runtime.stdin,
598
- output: context.output.stderr,
599
- signal: context.runtime.signal,
600
- message: "Which framework does this app use?",
601
- choices: FRAMEWORKS.map((framework) => ({
602
- label: framework.displayName,
603
- value: framework.key
604
- }))
605
- });
606
- return {
607
- key,
608
- displayName: frameworkByKey(key).displayName,
609
- source: "selected"
610
- };
611
- }
612
- throw new CliError({
613
- code: "INIT_DETECTION_FAILED",
614
- domain: "app",
615
- summary: "No supported framework detected",
616
- why: "The directory has none of the framework signals init detects from, and no --framework was passed.",
617
- fix: `Pass --framework with one of: ${FRAMEWORKS.map((framework) => framework.key).join(", ")}.`,
618
- exitCode: 1,
619
- nextSteps: FRAMEWORKS.slice(0, 3).map((framework) => formatCommand([
620
- "init",
621
- "--framework",
622
- framework.key
623
- ])),
624
- meta: { frameworks: FRAMEWORKS.map((framework) => framework.key) }
625
- });
626
- }
627
- async function resolveInitAppName(cwd, explicitName, signal, formatCommand) {
628
- const trimmed = explicitName?.trim();
629
- if (explicitName !== void 0 && !trimmed) throw usageError("App name required", "--name needs a non-empty value.", "Pass a non-empty app name.", [formatCommand([
630
- "init",
631
- "--name",
632
- "api"
633
- ])], "app");
634
- if (trimmed) return {
635
- value: trimmed,
636
- source: "flag"
637
- };
638
- const packageJson = await readBunPackageJson(cwd, signal);
639
- const packageName = typeof packageJson?.name === "string" ? packageJson.name.trim() : "";
640
- if (packageName) return {
641
- value: packageName,
642
- source: "package.json"
643
- };
644
- return {
645
- value: path.basename(cwd),
646
- source: "directory name"
647
- };
648
- }
649
- function parseInitHttpPort(value, formatCommand) {
650
- if (value === void 0) return;
651
- const port = Number(value.trim());
652
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw usageError("Invalid HTTP port", "--http-port must be an integer between 1 and 65535.", "Pass a valid port.", [formatCommand([
653
- "init",
654
- "--http-port",
655
- "3000"
656
- ])], "app");
657
- return {
658
- value: port,
659
- source: "flag"
660
- };
661
- }
662
- function parseInitRegion(value, formatCommand) {
663
- if (value === void 0) return;
664
- const trimmed = value.trim();
665
- if (COMPUTE_REGIONS.includes(trimmed)) return trimmed;
666
- throw usageError("Unknown region", `"${value}" is not a supported Compute region.`, `Pass one of: ${COMPUTE_REGIONS.join(", ")}.`, [formatCommand([
667
- "init",
668
- "--region",
669
- "us-east-1"
670
- ])], "app");
671
- }
672
- async function resolveInitEntry(cwd, resolvedFramework, explicitEntry, signal) {
673
- const framework = frameworkByKey(resolvedFramework.key);
674
- const trimmed = explicitEntry?.trim();
675
- if (!framework.usesEntrypoint) {
676
- if (trimmed) throw usageError("--entry is not supported for this framework", `${resolvedFramework.displayName} derives its entrypoint from build output; --entry applies only to frameworks that run a source entrypoint (Bun, Hono).`, "Drop --entry, or pass an entrypoint framework with --framework.", [], "app");
677
- return;
678
- }
679
- if (trimmed) return {
680
- value: trimmed,
681
- source: "flag"
682
- };
683
- const packageEntrypoint = readBunPackageEntrypoint(await readBunPackageJson(cwd, signal));
684
- if (packageEntrypoint) return {
685
- value: packageEntrypoint,
686
- source: "package.json"
687
- };
688
- }
689
- async function maybeAdjustSettings(context, framework, httpPort, options) {
690
- if (!canPrompt(context) || context.flags.yes) return {
691
- framework,
692
- httpPort
693
- };
694
- if (!await confirmPrompt({
695
- input: context.runtime.stdin,
696
- output: context.output.stderr,
697
- signal: context.runtime.signal,
698
- message: `Adjust these settings? (${framework.displayName}, HTTP ${httpPort.value})`,
699
- initialValue: false
700
- })) return {
701
- framework,
702
- httpPort
703
- };
704
- const key = await selectPrompt({
705
- input: context.runtime.stdin,
706
- output: context.output.stderr,
707
- signal: context.runtime.signal,
708
- message: "Framework",
709
- choices: FRAMEWORKS.map((candidate) => ({
710
- label: candidate.key === framework.key ? `${candidate.displayName} (current)` : candidate.displayName,
711
- value: candidate.key
712
- }))
713
- });
714
- const nextFramework = key === framework.key ? framework : {
715
- key,
716
- displayName: frameworkByKey(key).displayName,
717
- source: "selected"
718
- };
719
- const defaultPort = options.portExplicit ? httpPort.value : defaultHttpPortForBuildType(frameworkByKey(key).buildType);
720
- const portText = await textPrompt({
721
- input: context.runtime.stdin,
722
- output: context.output.stderr,
723
- signal: context.runtime.signal,
724
- message: "HTTP port",
725
- placeholder: String(defaultPort),
726
- validate: (value) => {
727
- if (!value?.trim()) return;
728
- const port = Number(value.trim());
729
- return Number.isInteger(port) && port >= 1 && port <= 65535 ? void 0 : "Enter a port between 1 and 65535.";
730
- }
731
- });
732
- return {
733
- framework: nextFramework,
734
- httpPort: portText.trim() ? {
735
- value: Number(portText.trim()),
736
- source: "selected"
737
- } : {
738
- value: defaultPort,
739
- source: options.portExplicit ? httpPort.source : "framework default"
740
- }
741
- };
742
- }
743
- function renderInitSettingsPreview(context, settings) {
744
- if (context.flags.quiet || context.flags.json) return;
745
- const keyWidth = Math.max(...settings.map((row) => row.key.length));
746
- const valueWidth = Math.max(...settings.map((row) => row.value.length));
747
- const lines = settings.map((row) => ` ${row.key.padEnd(keyWidth)} ${row.value.padEnd(valueWidth)} ${context.ui.dim(row.source)}`);
748
- context.output.stderr.write(`${lines.join("\n")}\n\n`);
749
- }
750
- async function resolveInitLink(context, flags, hooks) {
751
- const pin = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
752
- if (pin.isOk() && pin.value.kind === "present") return {
753
- status: "already-linked",
754
- project: null
755
- };
756
- const explicitProject = flags.project?.trim();
757
- const decision = await resolveProjectLinkDecision(context, flags, explicitProject);
758
- if (decision !== "link") return {
759
- status: decision,
760
- project: null
761
- };
762
- return attemptProjectLink(context, explicitProject, hooks);
763
- }
764
- async function resolveProjectLinkDecision(context, flags, explicitProject) {
765
- if (flags.link === false) return "skipped";
766
- if (explicitProject || flags.link === true) return "link";
767
- if (!canPrompt(context) || context.flags.yes) return "skipped";
768
- try {
769
- return await confirmPrompt({
770
- input: context.runtime.stdin,
771
- output: context.output.stderr,
772
- signal: context.runtime.signal,
773
- message: "Link this directory to a Prisma Project now?",
774
- initialValue: true
775
- }) ? "link" : "declined";
776
- } catch (error) {
777
- if (isPromptCancelError(error)) return "declined";
778
- throw error;
779
- }
780
- }
781
- async function attemptProjectLink(context, explicitProject, hooks) {
782
- try {
783
- const linked = await runProjectLink(context, explicitProject || void 0);
784
- return {
785
- status: "linked",
786
- project: {
787
- id: linked.result.project.id,
788
- name: linked.result.project.name
789
- }
790
- };
791
- } catch (error) {
792
- if (error instanceof CliError) {
793
- if (isPromptCancelError(error)) return {
794
- status: "declined",
795
- project: null
796
- };
797
- hooks.onWarning(`Project link failed: ${error.summary}. Link later with ${hooks.formatCommand(["project", "link"])}.`);
798
- return {
799
- status: "failed",
800
- project: null
801
- };
802
- }
803
- throw error;
804
- }
805
- }
806
- function firstErrorLine(error) {
807
- return error instanceof Error ? error.message.split("\n")[0] : String(error);
808
- }
809
- function formatInitDirectory(cwd) {
810
- const basename = path.basename(cwd);
811
- return basename ? `./${basename}` : ".";
812
- }
813
- //#endregion
814
- export { runInit };