@prisma/cli 3.0.0-beta.10 → 3.0.0-beta.11

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.
@@ -1,6 +1,6 @@
1
1
  import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
2
- import { requireComputeAuth } from "../lib/auth/guard.js";
3
2
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
4
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
5
5
  import { requireAuthenticatedAuthState } from "./auth.js";
6
6
  import { listRealWorkspaceProjects } from "./project.js";
@@ -1,6 +1,6 @@
1
1
  import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
- import { requireComputeAuth } from "../lib/auth/guard.js";
3
2
  import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
3
+ import { requireComputeAuth } from "../lib/auth/guard.js";
4
4
  import { requireAuthenticatedAuthState } from "./auth.js";
5
5
  import { listFixtureWorkspaceProjects, listRealWorkspaceProjects } from "./project.js";
6
6
  import { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase } from "../lib/database/provider.js";
@@ -1,19 +1,19 @@
1
1
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
2
  import { renderSummaryLine } from "../shell/ui.js";
3
3
  import { canPrompt } from "../shell/runtime.js";
4
- import { requireComputeAuth } from "../lib/auth/guard.js";
5
- import { formatCommandArgument } from "../shell/command-arguments.js";
6
4
  import { readLocalResolutionPin } from "../lib/project/local-pin.js";
5
+ import { formatCommandArgument } from "../shell/command-arguments.js";
7
6
  import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
7
  import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
9
- import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
10
8
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
9
+ import { requireComputeAuth } from "../lib/auth/guard.js";
10
+ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
11
11
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
12
12
  import { requireAuthenticatedAuthState } from "./auth.js";
13
13
  import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
14
14
  import { createProjectUseCases } from "../use-cases/project.js";
15
- import open from "open";
16
15
  import { matchError } from "better-result";
16
+ import open from "open";
17
17
  //#region src/controllers/project.ts
18
18
  const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
19
19
  const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
@@ -1,10 +1,10 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
+ import { confirmPrompt } from "../../shell/prompt.js";
2
3
  import { renderSummaryLine } from "../../shell/ui.js";
3
4
  import { canPrompt } from "../../shell/runtime.js";
4
- import { confirmPrompt } from "../../shell/prompt.js";
5
5
  import { formatCommandArgument } from "../../shell/command-arguments.js";
6
6
  import "../project/setup.js";
7
- import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
7
+ import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal } from "./branch-database.js";
8
8
  import path from "node:path";
9
9
  //#region src/lib/app/branch-database-deploy.ts
10
10
  async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
@@ -26,13 +26,12 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
26
26
  result: options.db === true ? {
27
27
  status: "skipped",
28
28
  reason: existingDatabaseEnvReason(branch),
29
- envVars: targetEnvVars,
30
- schema: null
29
+ envVars: targetEnvVars
31
30
  } : void 0,
32
31
  warnings: warning ? [warning] : []
33
32
  };
34
33
  }
35
- const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
34
+ const localSignal = await inspectBranchDatabaseSignal(options.projectDir, context.runtime.signal);
36
35
  if (localSignal.unsupportedSchema) {
37
36
  if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
38
37
  return emptyBranchDatabaseSetupOutcome();
@@ -56,9 +55,9 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
56
55
  initialValue: false
57
56
  })) return emptyBranchDatabaseSetupOutcome();
58
57
  } else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
59
- return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
58
+ return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState, options.projectDir);
60
59
  }
61
- async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
60
+ async function setupBranchDatabase(context, provider, projectId, branch, signal, envState, projectDir) {
62
61
  emitBranchDatabaseProgress(context, "pending", "Creating database");
63
62
  const database = await provider.createBranchDatabase({
64
63
  projectId,
@@ -70,27 +69,11 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
70
69
  });
71
70
  emitBranchDatabaseProgress(context, "success", "Created database");
72
71
  try {
73
- let schemaSetup = null;
74
- const warnings = [];
75
- let skippedSchemaWarning = null;
76
- if (signal.schema) {
77
- emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(signal.schema.command)}`);
78
- schemaSetup = await runBranchDatabaseSchemaSetup({
79
- context,
80
- schema: signal.schema,
81
- databaseUrl: database.databaseUrl,
82
- directUrl: database.directUrl
83
- }).catch((error) => {
84
- throw schemaSetupFailedError(error, signal.schema, branch, context.runtime.cwd);
85
- });
86
- emitBranchDatabaseProgress(context, "success", "Applied database schema");
87
- } else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
88
72
  const envVars = await upsertBranchDatabaseEnvVars(context, provider, projectId, branch, database, envState);
89
- emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")}`);
90
- if (skippedSchemaWarning) {
91
- emitBranchDatabaseWarning(context, skippedSchemaWarning);
92
- warnings.push(skippedSchemaWarning);
93
- }
73
+ emitBranchDatabaseProgress(context, "success", `Added ${envScopeLabel(branch)} env var${envVars.length === 1 ? "" : "s"} ${envVars.join(", ")} — shared by every app on ${envScopeLabel(branch) === "production" ? "production" : `branch "${branch.name}"`}`);
74
+ const schemaCommand = signal.schema ? `DATABASE_URL=<url> npx ${formatSchemaSetupCommand(signal.schema.command)} (detected ${path.relative(projectDir, signal.schema.path) || signal.schema.path})` : "your own migration tooling";
75
+ const schemaSuggestion = `The new database is empty. Get a connection URL with \`prisma-cli database connection create ${database.id}\`, then apply your schema with ${schemaCommand}.`;
76
+ emitBranchDatabaseWarning(context, schemaSuggestion);
94
77
  return {
95
78
  result: {
96
79
  status: "created",
@@ -98,14 +81,9 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
98
81
  id: database.id,
99
82
  name: database.name
100
83
  },
101
- envVars,
102
- schema: schemaSetup ? {
103
- command: schemaSetup.command,
104
- source: schemaSetup.source,
105
- path: schemaSetup.schemaPath
106
- } : null
84
+ envVars
107
85
  },
108
- warnings
86
+ warnings: [schemaSuggestion]
109
87
  };
110
88
  } catch (error) {
111
89
  throw await cleanupCreatedBranchDatabaseAfterFailure(context, provider, database, branch, error);
@@ -307,24 +285,6 @@ function branchDatabaseCleanupFailedError(setupError, cleanupError, database, br
307
285
  nextSteps: []
308
286
  });
309
287
  }
310
- function schemaSetupFailedError(error, schema, branch, cwd) {
311
- return new CliError({
312
- code: "SCHEMA_SETUP_FAILED",
313
- domain: "app",
314
- summary: "Database schema setup failed",
315
- why: error instanceof Error ? error.message : String(error),
316
- fix: "Fix the Prisma schema or migrations, then rerun deploy with --db.",
317
- debug: formatDebugDetails(error),
318
- meta: {
319
- branch: branch.name,
320
- schemaPath: schema.path,
321
- source: schema.kind,
322
- command: schema.command
323
- },
324
- exitCode: 1,
325
- nextSteps: [...formatSchemaSetupNextSteps(schema, cwd), formatAppDeployWithDbNextStep(branch)]
326
- });
327
- }
328
288
  function unsupportedBranchDatabaseSchemaError(schema, branch, context) {
329
289
  return usageError("Database setup is not available for this Prisma schema", `${path.relative(context.runtime.cwd, schema.path) || defaultUnsupportedSchemaSourcePath(schema)} targets ${formatUnsupportedSchemaTarget(schema.target)}, but --db creates Prisma Postgres databases.`, "Use project env commands to provide a database URL, or switch the Prisma schema source to PostgreSQL before using --db.", [formatProjectEnvAddNextStep(branch), `prisma-cli app deploy --branch ${formatCommandArgument(branch.name)}`], "app");
330
290
  }
@@ -337,14 +297,6 @@ function formatProjectEnvListNextStep(branch) {
337
297
  function formatProjectEnvAddNextStep(branch) {
338
298
  return branch.kind === "production" ? "prisma-cli project env add DATABASE_URL=<value> --role production" : `prisma-cli project env add DATABASE_URL=<value> --branch ${formatCommandArgument(branch.name)}`;
339
299
  }
340
- function formatSchemaSetupNextSteps(schema, cwd) {
341
- const sourcePath = path.relative(cwd, schema.path) || defaultSchemaSourcePath(schema);
342
- switch (schema.command) {
343
- case "migrate-deploy": return [`npx --no-install prisma migrate deploy --schema ${formatCommandArgument(sourcePath)}`];
344
- case "db-push": return [`npx --no-install prisma db push --schema ${formatCommandArgument(sourcePath)}`];
345
- case "prisma-next-db-init": return [`npx --no-install prisma-next contract emit --config ${formatCommandArgument(sourcePath)}`, `npx --no-install prisma-next db init --config ${formatCommandArgument(sourcePath)} --db <DATABASE_URL>`];
346
- }
347
- }
348
300
  function defaultSchemaSourcePath(schema) {
349
301
  return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
350
302
  }
@@ -1,6 +1,5 @@
1
1
  import { access, readFile, readdir, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { spawn } from "node:child_process";
4
3
  //#region src/lib/app/branch-database.ts
5
4
  const SKIPPED_DIRECTORIES = new Set([
6
5
  ".git",
@@ -67,23 +66,6 @@ function hasBranchDatabaseSignal(signal) {
67
66
  if (signal.unsupportedSchema) return false;
68
67
  return Boolean(signal.schema || signal.databaseUrlReferences.length > 0);
69
68
  }
70
- async function runBranchDatabaseSchemaSetup(options) {
71
- const schemaPath = path.relative(options.context.runtime.cwd, options.schema.path) || defaultSchemaSourcePath(options.schema);
72
- const commands = buildSchemaSetupCommands(options.schema, schemaPath, options.databaseUrl);
73
- for (const command of commands) await runPrismaCommand({
74
- context: options.context,
75
- ...command,
76
- env: {
77
- DATABASE_URL: options.databaseUrl,
78
- ...options.directUrl ? { DIRECT_URL: options.directUrl } : {}
79
- }
80
- });
81
- return {
82
- command: options.schema.command,
83
- source: options.schema.kind,
84
- schemaPath
85
- };
86
- }
87
69
  async function scanDirectory(cwd, directory, depth, state, signal) {
88
70
  signal.throwIfAborted();
89
71
  if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
@@ -229,88 +211,5 @@ async function readTextFileIfSmall(filePath, signal) {
229
211
  signal
230
212
  });
231
213
  }
232
- function buildSchemaSetupCommands(schema, schemaPath, databaseUrl) {
233
- if (schema.command === "migrate-deploy") return [{
234
- args: [
235
- "--no-install",
236
- "prisma",
237
- "migrate",
238
- "deploy",
239
- "--schema",
240
- schemaPath
241
- ],
242
- displayCommand: "npx --no-install prisma migrate deploy"
243
- }];
244
- if (schema.command === "db-push") return [{
245
- args: [
246
- "--no-install",
247
- "prisma",
248
- "db",
249
- "push",
250
- "--schema",
251
- schemaPath
252
- ],
253
- displayCommand: "npx --no-install prisma db push"
254
- }];
255
- return [{
256
- args: [
257
- "--no-install",
258
- "prisma-next",
259
- "contract",
260
- "emit",
261
- "--config",
262
- schemaPath
263
- ],
264
- displayCommand: "npx --no-install prisma-next contract emit"
265
- }, {
266
- args: [
267
- "--no-install",
268
- "prisma-next",
269
- "db",
270
- "init",
271
- "--config",
272
- schemaPath,
273
- "--db",
274
- databaseUrl
275
- ],
276
- displayCommand: "npx --no-install prisma-next db init"
277
- }];
278
- }
279
- function defaultSchemaSourcePath(schema) {
280
- return schema.kind === "prisma-next" ? "prisma-next.config.ts" : "schema.prisma";
281
- }
282
- async function runPrismaCommand(options) {
283
- const shouldPipeOutput = !options.context.flags.json && !options.context.flags.quiet;
284
- const child = spawn("npx", options.args, {
285
- cwd: options.context.runtime.cwd,
286
- env: {
287
- ...options.context.runtime.env,
288
- ...options.env
289
- },
290
- signal: options.context.runtime.signal,
291
- stdio: shouldPipeOutput ? [
292
- "ignore",
293
- "pipe",
294
- "pipe"
295
- ] : [
296
- "ignore",
297
- "ignore",
298
- "ignore"
299
- ]
300
- });
301
- if (shouldPipeOutput) {
302
- child.stdout?.pipe(options.context.output.stderr, { end: false });
303
- child.stderr?.pipe(options.context.output.stderr, { end: false });
304
- }
305
- const exit = await new Promise((resolve, reject) => {
306
- child.once("error", reject);
307
- child.once("close", (code, signal) => resolve({
308
- code,
309
- signal
310
- }));
311
- });
312
- if (exit.signal) throw new Error(`${options.displayCommand} was terminated by ${exit.signal}.`);
313
- if (exit.code !== 0) throw new Error(`${options.displayCommand} exited with code ${exit.code ?? 1}.`);
314
- }
315
214
  //#endregion
316
- export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup };
215
+ export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal };
@@ -0,0 +1,147 @@
1
+ import { CliError } from "../../shell/errors.js";
2
+ import { COMPUTE_CONFIG_FILENAME, COMPUTE_CONFIG_FILENAME as COMPUTE_CONFIG_FILENAME$1, ComputeConfigTargetRequiredError, computeTargetAppDir, frameworkByKey, inferComputeTargetFromCwd, loadComputeConfig, selectComputeDeployTarget } from "@prisma/compute-sdk/config";
3
+ import path from "node:path";
4
+ import { matchError } from "better-result";
5
+ //#region src/lib/app/compute-config.ts
6
+ /**
7
+ * Loads the nearest compute config, searching from `cwd` up to the source
8
+ * root (repository or workspace boundary). Thin adapter over the SDK loader
9
+ * keeping the CLI's positional-signal call shape.
10
+ */
11
+ async function loadComputeConfig$1(cwd, signal) {
12
+ return loadComputeConfig(cwd, { signal });
13
+ }
14
+ /** Local build/run strategy implied by a configured framework. */
15
+ function computeFrameworkToBuildType(framework) {
16
+ return frameworkByKey(framework).buildType;
17
+ }
18
+ function mergeComputeDeployInputs(options) {
19
+ const { cli, target, configFilename } = options;
20
+ const configAnnotation = `set by ${configFilename}`;
21
+ const framework = cli.framework ? {
22
+ value: cli.framework,
23
+ annotation: "set by --framework"
24
+ } : target?.framework ? {
25
+ value: target.framework,
26
+ annotation: configAnnotation
27
+ } : void 0;
28
+ const entrypoint = cli.entrypoint ? {
29
+ value: cli.entrypoint,
30
+ annotation: "set by --entry"
31
+ } : target?.entry ? {
32
+ value: target.entry,
33
+ annotation: configAnnotation
34
+ } : void 0;
35
+ const httpPort = cli.httpPort ? {
36
+ value: cli.httpPort,
37
+ annotation: "set by --http-port"
38
+ } : target?.httpPort ? {
39
+ value: String(target.httpPort),
40
+ annotation: configAnnotation
41
+ } : void 0;
42
+ const cliEnvInputs = cli.envInputs && cli.envInputs.length > 0 ? cli.envInputs : void 0;
43
+ const configEnvInputs = target && target.envInputs.length > 0 ? target.envInputs : void 0;
44
+ const envInputs = cliEnvInputs ?? configEnvInputs;
45
+ const configAppName = target?.name ? {
46
+ value: target.name,
47
+ annotation: configAnnotation
48
+ } : target?.key ? {
49
+ value: target.key,
50
+ annotation: configAnnotation
51
+ } : void 0;
52
+ return {
53
+ framework,
54
+ entrypoint,
55
+ httpPort,
56
+ envInputs,
57
+ envInputsFromConfig: !cliEnvInputs && configEnvInputs !== void 0,
58
+ configAppName,
59
+ appRoot: target?.root ?? void 0
60
+ };
61
+ }
62
+ /**
63
+ * Merges CLI inputs for the local `app build` and `app run` commands with a
64
+ * selected config target. Explicit flags win; `--build-type auto` is the
65
+ * flag default and defers to the configured framework.
66
+ */
67
+ function mergeComputeLocalInputs(options) {
68
+ const { cli, target } = options;
69
+ const cliBuildType = cli.buildType && cli.buildType !== "auto" ? cli.buildType : void 0;
70
+ const configBuildType = target?.framework ? computeFrameworkToBuildType(target.framework) : void 0;
71
+ return {
72
+ entrypoint: cli.entrypoint ?? target?.entry ?? void 0,
73
+ buildType: cliBuildType ?? configBuildType,
74
+ buildTypeFromConfig: !cliBuildType && configBuildType !== void 0,
75
+ port: cli.port ?? (target?.httpPort ? String(target.httpPort) : void 0),
76
+ appRoot: target?.root ?? void 0
77
+ };
78
+ }
79
+ function computeConfigErrorToCliError(error, commandName = "deploy") {
80
+ const command = `prisma-cli app ${commandName}`;
81
+ return matchError(error, {
82
+ ComputeConfigAmbiguousError: (ambiguous) => new CliError({
83
+ code: "COMPUTE_CONFIG_INVALID",
84
+ domain: "app",
85
+ summary: "Multiple compute config files found",
86
+ why: ambiguous.message,
87
+ fix: `Keep exactly one compute config file, preferably ${COMPUTE_CONFIG_FILENAME}.`,
88
+ meta: { configPaths: ambiguous.configPaths },
89
+ exitCode: 2,
90
+ nextSteps: [command]
91
+ }),
92
+ ComputeConfigLoadError: (load) => new CliError({
93
+ code: "COMPUTE_CONFIG_INVALID",
94
+ domain: "app",
95
+ summary: `Could not load ${path.basename(load.configPath)}`,
96
+ why: load.message,
97
+ fix: `Fix the error in ${path.basename(load.configPath)} and rerun the command.`,
98
+ where: load.configPath,
99
+ meta: { configPath: load.configPath },
100
+ exitCode: 2,
101
+ nextSteps: [command]
102
+ }),
103
+ ComputeConfigInvalidError: (invalid) => new CliError({
104
+ code: "COMPUTE_CONFIG_INVALID",
105
+ domain: "app",
106
+ summary: `Invalid ${path.basename(invalid.configPath)}`,
107
+ why: invalid.issues.join(" "),
108
+ fix: `Edit ${path.basename(invalid.configPath)} so it default-exports defineComputeConfig({ app }) or defineComputeConfig({ apps }).`,
109
+ where: invalid.configPath,
110
+ meta: {
111
+ configPath: invalid.configPath,
112
+ issues: invalid.issues
113
+ },
114
+ exitCode: 2,
115
+ nextSteps: [command]
116
+ }),
117
+ ComputeConfigTargetRequiredError: (required) => new CliError({
118
+ code: "COMPUTE_CONFIG_TARGET_REQUIRED",
119
+ domain: "app",
120
+ summary: "App target required",
121
+ why: required.message,
122
+ fix: `Pass the app target, for example ${command} <target>.`,
123
+ meta: {
124
+ configPath: required.configPath,
125
+ availableTargets: required.availableTargets
126
+ },
127
+ exitCode: 2,
128
+ nextSteps: required.availableTargets.map((target) => `${command} ${target}`)
129
+ }),
130
+ ComputeConfigTargetUnknownError: (unknown) => new CliError({
131
+ code: "COMPUTE_CONFIG_TARGET_UNKNOWN",
132
+ domain: "app",
133
+ summary: `Unknown app target "${unknown.requestedTarget}"`,
134
+ why: unknown.message,
135
+ fix: unknown.availableTargets.length > 0 ? `Pass one of the configured targets: ${unknown.availableTargets.join(", ")}.` : "Remove the target argument; this config defines a single app.",
136
+ meta: {
137
+ configPath: unknown.configPath,
138
+ requestedTarget: unknown.requestedTarget,
139
+ availableTargets: unknown.availableTargets
140
+ },
141
+ exitCode: 2,
142
+ nextSteps: unknown.availableTargets.length > 0 ? unknown.availableTargets.map((target) => `${command} ${target}`) : [command]
143
+ })
144
+ });
145
+ }
146
+ //#endregion
147
+ export { COMPUTE_CONFIG_FILENAME$1 as COMPUTE_CONFIG_FILENAME, ComputeConfigTargetRequiredError, computeConfigErrorToCliError, computeFrameworkToBuildType, computeTargetAppDir, inferComputeTargetFromCwd, loadComputeConfig$1 as loadComputeConfig, mergeComputeDeployInputs, mergeComputeLocalInputs, selectComputeDeployTarget };
@@ -0,0 +1,58 @@
1
+ //#region src/lib/app/deploy-plan.ts
2
+ /**
3
+ * Decides whether a deploy covers one app or the whole system. A multi-app
4
+ * config with no target named or inferred deploys every target in declaration
5
+ * order; everything else (single-app config, an explicit/inferred target, or
6
+ * no config at all) is a single deploy.
7
+ */
8
+ function planAppDeploy(options) {
9
+ const { config, requestedTarget, hasCreateProject } = options;
10
+ if (!(config !== null && config.kind === "multi" && !requestedTarget && config.targets.length > 1)) return { mode: "single" };
11
+ const total = config.targets.length;
12
+ return {
13
+ mode: "all",
14
+ targets: config.targets.map((target, index) => ({
15
+ targetKey: target.key,
16
+ index,
17
+ total,
18
+ bindsCreateProject: index === 0 && hasCreateProject
19
+ }))
20
+ };
21
+ }
22
+ /**
23
+ * Names the per-app inputs present in a deploy-all invocation, in the order
24
+ * they should be reported. An empty list means the run is unambiguous; the
25
+ * caller renders the usage error when it is not.
26
+ */
27
+ function perAppInputsForDeployAll(inputs) {
28
+ return [
29
+ ["--app", inputs.appName],
30
+ ["--framework", inputs.framework],
31
+ ["--entry", inputs.entrypoint],
32
+ ["--http-port", inputs.httpPort],
33
+ ["--env", inputs.envAssignments?.length ? inputs.envAssignments : void 0],
34
+ [inputs.appIdEnvVar.name, inputs.appIdEnvVar.value]
35
+ ].filter(([, value]) => value !== void 0).map(([flag]) => flag);
36
+ }
37
+ /**
38
+ * Describes a deploy-all run that stopped at the first failing target: which
39
+ * targets already went live and which were never attempted, so the caller can
40
+ * surface a converge path. Pure; the caller folds this into the CliError.
41
+ */
42
+ function describeDeployAllFailure(options) {
43
+ const { targetKeys, failedIndex, completed } = options;
44
+ const failedTarget = targetKeys[failedIndex];
45
+ const notAttempted = targetKeys.slice(failedIndex + 1);
46
+ return {
47
+ failedTarget,
48
+ completed,
49
+ notAttempted,
50
+ contextLines: [
51
+ `Deploying all apps stopped at "${failedTarget}" (${failedIndex + 1}/${targetKeys.length}).`,
52
+ ...completed.length > 0 ? [`Already live: ${completed.map((entry) => entry.target).join(", ")}.`] : [],
53
+ ...notAttempted.length > 0 ? [`Not attempted: ${notAttempted.join(", ")}.`] : []
54
+ ]
55
+ };
56
+ }
57
+ //#endregion
58
+ export { describeDeployAllFailure, perAppInputsForDeployAll, planAppDeploy };
@@ -1,25 +1,9 @@
1
- import { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
2
- import { access } from "node:fs/promises";
1
+ import { resolveBunEntrypoint } from "./bun-project.js";
2
+ import "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
  //#region src/lib/app/local-dev.ts
6
- const NEXT_CONFIG_FILENAMES = [
7
- "next.config.js",
8
- "next.config.mjs",
9
- "next.config.ts",
10
- "next.config.mts"
11
- ];
12
6
  const DEFAULT_LOCAL_DEV_PORT = 3e3;
13
- async function resolveLocalBuildType(appPath, buildType, signal) {
14
- if (buildType === "bun" || buildType === "nextjs") return buildType;
15
- if (buildType !== "auto") return null;
16
- return detectLocalBuildType(appPath, signal);
17
- }
18
- async function detectLocalBuildType(appPath, signal) {
19
- if (await isNextProject(appPath, signal)) return "nextjs";
20
- if (await isBunProject(appPath, signal)) return "bun";
21
- return null;
22
- }
23
7
  async function runLocalApp(options) {
24
8
  const spawnImpl = options.spawnImpl ?? spawn;
25
9
  if (options.buildType === "nextjs") {
@@ -92,47 +76,6 @@ async function runLocalApp(options) {
92
76
  signal: command.signal
93
77
  };
94
78
  }
95
- async function isNextProject(appPath, signal) {
96
- for (const fileName of NEXT_CONFIG_FILENAMES) {
97
- signal?.throwIfAborted();
98
- try {
99
- await access(path.join(appPath, fileName));
100
- signal?.throwIfAborted();
101
- return true;
102
- } catch (error) {
103
- if (signal?.aborted) throw error;
104
- }
105
- }
106
- return hasDependency(await readBunPackageJson(appPath, signal), "next");
107
- }
108
- async function isBunProject(appPath, signal) {
109
- signal?.throwIfAborted();
110
- try {
111
- await access(path.join(appPath, "bun.lock"));
112
- signal?.throwIfAborted();
113
- return true;
114
- } catch (error) {
115
- if (signal?.aborted) throw error;
116
- }
117
- signal?.throwIfAborted();
118
- try {
119
- await access(path.join(appPath, "bun.lockb"));
120
- signal?.throwIfAborted();
121
- return true;
122
- } catch (error) {
123
- if (signal?.aborted) throw error;
124
- }
125
- const packageJson = await readBunPackageJson(appPath, signal);
126
- if (!packageJson) return false;
127
- const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
128
- const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");
129
- const usesBunScripts = (typeof packageJson.scripts === "object" && packageJson.scripts !== null ? Object.values(packageJson.scripts) : []).some((value) => typeof value === "string" && /\bbun\b/.test(value));
130
- return hasEntrypoint && (hasBunDependency || usesBunScripts);
131
- }
132
- function hasDependency(packageJson, dependencyName) {
133
- if (!packageJson) return false;
134
- return [packageJson.dependencies, packageJson.devDependencies].some((group) => typeof group === "object" && group !== null && dependencyName in group);
135
- }
136
79
  async function runWithFallback(candidates, options, spawnImpl, missingCommandMessage) {
137
80
  for (const candidate of candidates) try {
138
81
  const result = await spawnCommand(candidate, options, spawnImpl);
@@ -163,4 +106,4 @@ function spawnCommand(candidate, options, spawnImpl) {
163
106
  });
164
107
  }
165
108
  //#endregion
166
- export { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp };
109
+ export { DEFAULT_LOCAL_DEV_PORT, runLocalApp };