@prisma/cli 3.0.0-beta.9 → 3.0.0-dev.101.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -13
- package/dist/adapters/token-storage.js +311 -33
- package/dist/cli.js +7 -7
- package/dist/cli2.js +3 -3
- package/dist/commands/app/index.js +65 -52
- package/dist/commands/auth/index.js +55 -2
- package/dist/controllers/app-env.js +10 -7
- package/dist/controllers/app.js +536 -207
- package/dist/controllers/auth.js +211 -2
- package/dist/controllers/branch.js +5 -6
- package/dist/controllers/database.js +7 -5
- package/dist/controllers/project.js +33 -18
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
- package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
- package/dist/lib/app/branch-database-deploy.js +12 -60
- package/dist/lib/app/branch-database.js +1 -102
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +2 -3
- package/dist/lib/app/compute-config.js +147 -0
- package/dist/lib/app/deploy-plan.js +58 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +3 -3
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +10 -4
- package/dist/lib/auth/guard.js +4 -1
- package/dist/lib/auth/login.js +32 -25
- package/dist/lib/diagnostics.js +1 -1
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +15 -3
- package/dist/lib/project/local-pin.js +106 -26
- package/dist/lib/project/resolution.js +162 -71
- package/dist/lib/project/setup.js +65 -19
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app.js +44 -39
- package/dist/presenters/auth.js +98 -1
- package/dist/presenters/branch.js +1 -1
- package/dist/presenters/database.js +2 -2
- package/dist/presenters/project.js +3 -9
- package/dist/shell/command-meta.js +52 -2
- package/dist/shell/command-runner.js +39 -28
- package/dist/shell/diagnostics-output.js +2 -8
- package/dist/shell/errors.js +50 -1
- package/dist/shell/help.js +30 -20
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +19 -2
- package/dist/use-cases/auth.js +68 -1
- package/package.json +17 -3
- package/dist/lib/app/preview-build-settings.js +0 -385
- package/dist/lib/app/preview-build.js +0 -475
- package/dist/lib/app/preview-interaction.js +0 -5
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
215
|
+
export { hasBranchDatabaseSignal, inspectBranchDatabaseSignal };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveBuildSettings, resolveConfiguredBuildSettings } from "@prisma/compute-sdk";
|
|
4
|
+
//#region src/lib/app/build-settings.ts
|
|
5
|
+
/** Legacy build-settings file: no longer read or written, only detected for migration. */
|
|
6
|
+
const PRISMA_APP_CONFIG_FILENAME = "prisma.app.json";
|
|
7
|
+
/**
|
|
8
|
+
* Detects a leftover `prisma.app.json`. The file is no longer used: one that
|
|
9
|
+
* matches the effective settings is reported for deletion, one with custom
|
|
10
|
+
* values must be migrated to the compute config so builds never silently
|
|
11
|
+
* change.
|
|
12
|
+
*/
|
|
13
|
+
async function detectLegacyBuildSettings(options) {
|
|
14
|
+
const configPath = path.join(options.appPath, PRISMA_APP_CONFIG_FILENAME);
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
options.signal?.throwIfAborted();
|
|
18
|
+
content = await readFile(configPath, {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
signal: options.signal
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (options.signal?.aborted) throw error;
|
|
24
|
+
return { kind: "absent" };
|
|
25
|
+
}
|
|
26
|
+
let legacy;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(content);
|
|
29
|
+
const buildCommand = parsed.buildCommand === null || typeof parsed.buildCommand === "string" ? typeof parsed.buildCommand === "string" ? parsed.buildCommand.trim() || null : null : void 0;
|
|
30
|
+
const outputDirectory = typeof parsed.outputDirectory === "string" ? normalizeRelativePath(parsed.outputDirectory) : void 0;
|
|
31
|
+
if (buildCommand === void 0 || !outputDirectory) return {
|
|
32
|
+
kind: "invalid",
|
|
33
|
+
configPath
|
|
34
|
+
};
|
|
35
|
+
legacy = {
|
|
36
|
+
buildCommand,
|
|
37
|
+
outputDirectory
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
return {
|
|
41
|
+
kind: "invalid",
|
|
42
|
+
configPath
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return legacy.buildCommand === options.effective.buildCommand && legacy.outputDirectory === options.effective.outputDirectory ? {
|
|
46
|
+
kind: "matching",
|
|
47
|
+
configPath
|
|
48
|
+
} : {
|
|
49
|
+
kind: "custom",
|
|
50
|
+
configPath,
|
|
51
|
+
...legacy
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Resolves build settings purely from framework inference; nothing is read or written. */
|
|
55
|
+
async function resolveInferredAppBuildSettings(options) {
|
|
56
|
+
return {
|
|
57
|
+
status: "inferred",
|
|
58
|
+
configPath: null,
|
|
59
|
+
relativeConfigPath: null,
|
|
60
|
+
settings: await resolveBuildSettings(options)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolves build settings when the compute config owns them: configured
|
|
65
|
+
* fields win, omitted fields fall back to framework defaults.
|
|
66
|
+
*/
|
|
67
|
+
async function resolveConfiguredAppBuildSettings(options) {
|
|
68
|
+
const configFilename = path.basename(options.configPath);
|
|
69
|
+
const settings = await resolveConfiguredBuildSettings({
|
|
70
|
+
appPath: options.appPath,
|
|
71
|
+
buildType: options.buildType,
|
|
72
|
+
configured: options.configured,
|
|
73
|
+
source: `set by ${configFilename}`,
|
|
74
|
+
signal: options.signal
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
status: "config",
|
|
78
|
+
configPath: options.configPath,
|
|
79
|
+
relativeConfigPath: configFilename,
|
|
80
|
+
settings
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function normalizeRelativePath(value) {
|
|
84
|
+
const raw = value.trim().replace(/\\/g, "/");
|
|
85
|
+
if (raw.length === 0 || raw.split("/").includes("..")) return;
|
|
86
|
+
if (/^[A-Za-z]:/.test(raw)) return;
|
|
87
|
+
const normalized = path.posix.normalize(raw);
|
|
88
|
+
const segments = normalized.split("/");
|
|
89
|
+
if (path.win32.isAbsolute(value) || path.posix.isAbsolute(normalized) || segments.includes("..")) return;
|
|
90
|
+
return normalized === "." ? "." : normalized;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import "./build-settings.js";
|
|
2
|
+
import { FRAMEWORKS } from "@prisma/compute-sdk/config";
|
|
3
|
+
import "node:fs/promises";
|
|
4
|
+
import "node:path";
|
|
5
|
+
import { normalizeArtifactSymlinks, resolveBuildStrategy } from "@prisma/compute-sdk";
|
|
6
|
+
//#region src/lib/app/build.ts
|
|
7
|
+
const RESOLVED_APP_BUILD_TYPES = [...frameworkBuildTypesByLastOccurrence()];
|
|
8
|
+
const APP_BUILD_TYPES = ["auto", ...RESOLVED_APP_BUILD_TYPES];
|
|
9
|
+
const APP_BUILD_TYPE_LABELS = APP_BUILD_TYPES.join(", ");
|
|
10
|
+
function frameworkBuildTypesByLastOccurrence() {
|
|
11
|
+
const buildTypes = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const framework of FRAMEWORKS) {
|
|
13
|
+
if (buildTypes.has(framework.buildType)) buildTypes.delete(framework.buildType);
|
|
14
|
+
buildTypes.add(framework.buildType);
|
|
15
|
+
}
|
|
16
|
+
return buildTypes;
|
|
17
|
+
}
|
|
18
|
+
var AppBuildStrategy = class {
|
|
19
|
+
#appPath;
|
|
20
|
+
#entrypoint;
|
|
21
|
+
#buildType;
|
|
22
|
+
#signal;
|
|
23
|
+
#buildSettings;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#appPath = options.appPath;
|
|
26
|
+
this.#entrypoint = options.entrypoint;
|
|
27
|
+
this.#buildType = options.buildType ?? "auto";
|
|
28
|
+
this.#signal = options.signal;
|
|
29
|
+
this.#buildSettings = options.buildSettings;
|
|
30
|
+
}
|
|
31
|
+
async canBuild(signal = this.#signal) {
|
|
32
|
+
const { strategy } = await resolveAppBuildStrategy({
|
|
33
|
+
appPath: this.#appPath,
|
|
34
|
+
entrypoint: this.#entrypoint,
|
|
35
|
+
buildType: this.#buildType,
|
|
36
|
+
signal,
|
|
37
|
+
buildSettings: this.#buildSettings
|
|
38
|
+
});
|
|
39
|
+
return strategy.canBuild(signal);
|
|
40
|
+
}
|
|
41
|
+
async execute(signal = this.#signal) {
|
|
42
|
+
const { artifact } = await executeAppBuild({
|
|
43
|
+
appPath: this.#appPath,
|
|
44
|
+
entrypoint: this.#entrypoint,
|
|
45
|
+
buildType: this.#buildType,
|
|
46
|
+
signal,
|
|
47
|
+
buildSettings: this.#buildSettings
|
|
48
|
+
});
|
|
49
|
+
return artifact;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function executeAppBuild(options) {
|
|
53
|
+
const { strategy, buildType } = await resolveAppBuildStrategy({
|
|
54
|
+
appPath: options.appPath,
|
|
55
|
+
entrypoint: options.entrypoint,
|
|
56
|
+
buildType: options.buildType ?? "auto",
|
|
57
|
+
signal: options.signal,
|
|
58
|
+
buildSettings: options.buildSettings
|
|
59
|
+
});
|
|
60
|
+
const artifact = await strategy.execute(options.signal);
|
|
61
|
+
try {
|
|
62
|
+
await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
|
|
63
|
+
return {
|
|
64
|
+
artifact,
|
|
65
|
+
buildType
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await artifact.cleanup?.().catch(() => void 0);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function resolveAppBuildStrategy(options) {
|
|
73
|
+
return resolveBuildStrategy({
|
|
74
|
+
appPath: options.appPath,
|
|
75
|
+
buildType: options.buildType,
|
|
76
|
+
entrypoint: options.entrypoint,
|
|
77
|
+
buildSettings: options.buildSettings,
|
|
78
|
+
signal: options.signal
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { APP_BUILD_TYPES, APP_BUILD_TYPE_LABELS, AppBuildStrategy, RESOLVED_APP_BUILD_TYPES, executeAppBuild };
|
|
@@ -21,13 +21,12 @@ async function readBunPackageJson(appPath, signal) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
function readBunPackageEntrypoint(packageJson) {
|
|
24
|
-
|
|
25
|
-
if (typeof packageJson?.module === "string") return packageJson.module;
|
|
24
|
+
return typeof packageJson?.main === "string" ? packageJson.main : void 0;
|
|
26
25
|
}
|
|
27
26
|
async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
|
|
28
27
|
const packageJson = await readBunPackageJson(appPath, signal);
|
|
29
28
|
const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
|
|
30
|
-
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main
|
|
29
|
+
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main.");
|
|
31
30
|
if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
|
|
32
31
|
const normalized = path.normalize(candidate);
|
|
33
32
|
if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import { renderDeployOutputRows } from "./deploy-output.js";
|
|
2
|
-
//#region src/lib/app/
|
|
3
|
-
function
|
|
2
|
+
//#region src/lib/app/deploy-progress.ts
|
|
3
|
+
function createDeployProgressState() {
|
|
4
4
|
return {
|
|
5
5
|
buildStarted: false,
|
|
6
6
|
buildCompleted: false,
|
|
@@ -13,7 +13,7 @@ function createPreviewDeployProgressState() {
|
|
|
13
13
|
promotedUrl: null
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function createDeployProgress(output, ui, enabled, state = createDeployProgressState()) {
|
|
17
17
|
const write = (line) => {
|
|
18
18
|
if (!enabled) return;
|
|
19
19
|
output.write(`${line}\n`);
|
|
@@ -63,7 +63,7 @@ function createPreviewDeployProgress(output, ui, enabled, state = createPreviewD
|
|
|
63
63
|
function formatArtifactSize(byteLength) {
|
|
64
64
|
return `${(byteLength / 1024 / 1024).toFixed(1)} MB`;
|
|
65
65
|
}
|
|
66
|
-
function
|
|
66
|
+
function createPromoteProgress(output, enabled) {
|
|
67
67
|
if (!enabled) return;
|
|
68
68
|
const write = (line) => {
|
|
69
69
|
output.write(`${line}\n`);
|
|
@@ -97,4 +97,4 @@ function createPreviewPromoteProgress(output, enabled) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
|
-
export {
|
|
100
|
+
export { createDeployProgress, createDeployProgressState, createPromoteProgress };
|