@prisma/cli 3.0.0-alpha.7 → 3.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/app.js +114 -56
- package/dist/lib/app/deploy-output.js +15 -0
- package/dist/lib/app/preview-build.js +98 -1
- package/dist/lib/app/preview-progress.js +43 -58
- package/dist/presenters/app.js +14 -38
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +11 -0
- package/package.json +1 -1
package/dist/controllers/app.js
CHANGED
|
@@ -8,13 +8,14 @@ import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
|
8
8
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
9
|
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
10
|
import { parseEnvAssignments } from "../lib/app/env-vars.js";
|
|
11
|
+
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
11
12
|
import { readBunPackageJson } from "../lib/app/bun-project.js";
|
|
12
13
|
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
13
14
|
import { inferTargetName, projectNotFoundError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
14
15
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
15
16
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
16
17
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
17
|
-
import { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
|
|
18
|
+
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress } from "../lib/app/preview-progress.js";
|
|
18
19
|
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
19
20
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
20
21
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
@@ -123,7 +124,6 @@ async function runAppDeploy(context, appName, options) {
|
|
|
123
124
|
});
|
|
124
125
|
await maybeRenderDeploySetupBlock(context, {
|
|
125
126
|
firstDeploy: selectedApp.firstDeploy,
|
|
126
|
-
showSubsequentAnnotations: skipLocalPin,
|
|
127
127
|
workspaceName: target.workspace.name,
|
|
128
128
|
projectName: target.project.name,
|
|
129
129
|
projectAnnotation: annotationForProjectResolution(target.resolution),
|
|
@@ -147,6 +147,17 @@ async function runAppDeploy(context, appName, options) {
|
|
|
147
147
|
const buildType = framework.buildType;
|
|
148
148
|
assertSupportedEntrypoint(buildType, options?.entrypoint, "deploy");
|
|
149
149
|
const portMapping = parseDeployPortMapping(String(runtime.port));
|
|
150
|
+
const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
|
|
151
|
+
if (shouldWriteLocalPin) {
|
|
152
|
+
await writeLocalResolutionPin(context.runtime.cwd, {
|
|
153
|
+
workspaceId: target.workspace.id,
|
|
154
|
+
projectId: target.project.id
|
|
155
|
+
});
|
|
156
|
+
await ensureLocalResolutionPinGitignore(context.runtime.cwd);
|
|
157
|
+
maybeRenderLocalPinBound(context, target.project.name);
|
|
158
|
+
}
|
|
159
|
+
const progressState = createPreviewDeployProgressState();
|
|
160
|
+
const deployStartedAt = Date.now();
|
|
150
161
|
const deployResult = await provider.deployApp({
|
|
151
162
|
cwd: context.runtime.cwd,
|
|
152
163
|
projectId,
|
|
@@ -159,23 +170,16 @@ async function runAppDeploy(context, appName, options) {
|
|
|
159
170
|
portMapping,
|
|
160
171
|
envVars,
|
|
161
172
|
interaction: void 0,
|
|
162
|
-
progress: createPreviewDeployProgress(context.output.stderr, !context.flags.json && !context.flags.quiet)
|
|
173
|
+
progress: createPreviewDeployProgress(context.output.stderr, context.ui, !context.flags.json && !context.flags.quiet, progressState)
|
|
163
174
|
}).catch((error) => {
|
|
164
|
-
throw
|
|
175
|
+
throw appDeployFailedError(error, progressState);
|
|
165
176
|
});
|
|
177
|
+
const deployDurationMs = Date.now() - deployStartedAt;
|
|
166
178
|
await context.stateStore.setSelectedApp(projectId, {
|
|
167
179
|
id: deployResult.app.id,
|
|
168
180
|
name: deployResult.app.name
|
|
169
181
|
});
|
|
170
182
|
await context.stateStore.setKnownLiveDeployment(projectId, deployResult.app.id, deployResult.deployment.id);
|
|
171
|
-
const shouldWriteLocalPin = firstDeploy && !skipLocalPin;
|
|
172
|
-
if (shouldWriteLocalPin) {
|
|
173
|
-
await writeLocalResolutionPin(context.runtime.cwd, {
|
|
174
|
-
workspaceId: target.workspace.id,
|
|
175
|
-
projectId: target.project.id
|
|
176
|
-
});
|
|
177
|
-
await ensureLocalResolutionPinGitignore(context.runtime.cwd);
|
|
178
|
-
}
|
|
179
183
|
return {
|
|
180
184
|
command: "app.deploy",
|
|
181
185
|
result: {
|
|
@@ -188,6 +192,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
188
192
|
name: deployResult.app.name
|
|
189
193
|
},
|
|
190
194
|
deployment: deployResult.deployment,
|
|
195
|
+
durationMs: deployDurationMs,
|
|
191
196
|
localPin: shouldWriteLocalPin ? {
|
|
192
197
|
path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
193
198
|
written: true
|
|
@@ -1054,9 +1059,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1054
1059
|
}
|
|
1055
1060
|
}, branch);
|
|
1056
1061
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1062
|
+
const localPin = options.localPin;
|
|
1063
|
+
if (localPin.kind === "present") {
|
|
1064
|
+
if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
|
|
1065
|
+
const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
|
|
1060
1066
|
if (!project) throw localResolutionPinStaleError();
|
|
1061
1067
|
return withDeployBranch({
|
|
1062
1068
|
workspace,
|
|
@@ -1257,7 +1263,12 @@ function frameworkNotDetectedError(cwd, requestedFramework) {
|
|
|
1257
1263
|
}
|
|
1258
1264
|
async function maybeRenderDeploySetupBlock(context, details) {
|
|
1259
1265
|
if (context.flags.json || context.flags.quiet) return;
|
|
1260
|
-
const
|
|
1266
|
+
const directory = formatDeployDirectory(context.runtime.cwd);
|
|
1267
|
+
if (!details.firstDeploy) {
|
|
1268
|
+
context.output.stderr.write(`Deploying ${directory} to ${details.projectName} / ${details.branchName} / ${details.appName}\n\n`);
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const title = `Setting up your local directory ${formatLocalDirectory(context.runtime.cwd, context.runtime.env)}`;
|
|
1261
1272
|
const rows = details.firstDeploy ? [
|
|
1262
1273
|
{
|
|
1263
1274
|
label: "Workspace",
|
|
@@ -1266,61 +1277,41 @@ async function maybeRenderDeploySetupBlock(context, details) {
|
|
|
1266
1277
|
{
|
|
1267
1278
|
label: "Project",
|
|
1268
1279
|
value: details.projectName,
|
|
1269
|
-
|
|
1280
|
+
origin: details.projectAnnotation
|
|
1270
1281
|
},
|
|
1271
1282
|
{
|
|
1272
1283
|
label: "Branch",
|
|
1273
1284
|
value: details.branchName,
|
|
1274
|
-
|
|
1285
|
+
origin: details.branchAnnotation
|
|
1275
1286
|
},
|
|
1276
1287
|
{
|
|
1277
1288
|
label: "App",
|
|
1278
1289
|
value: details.appName,
|
|
1279
|
-
|
|
1290
|
+
origin: details.appAnnotation
|
|
1280
1291
|
},
|
|
1281
1292
|
{
|
|
1282
1293
|
label: "Framework",
|
|
1283
1294
|
value: details.framework.displayName,
|
|
1284
|
-
|
|
1295
|
+
origin: details.framework.annotation
|
|
1285
1296
|
},
|
|
1286
1297
|
{
|
|
1287
1298
|
label: "Runtime",
|
|
1288
1299
|
value: `HTTP ${details.runtime.port}`,
|
|
1289
|
-
|
|
1300
|
+
origin: details.runtime.annotation
|
|
1290
1301
|
}
|
|
1291
|
-
] : [
|
|
1292
|
-
|
|
1293
|
-
label: "Workspace",
|
|
1294
|
-
value: details.workspaceName
|
|
1295
|
-
},
|
|
1296
|
-
{
|
|
1297
|
-
label: "Project",
|
|
1298
|
-
value: details.projectName,
|
|
1299
|
-
annotation: details.showSubsequentAnnotations ? details.projectAnnotation : void 0
|
|
1300
|
-
},
|
|
1301
|
-
{
|
|
1302
|
-
label: "Branch",
|
|
1303
|
-
value: details.branchName,
|
|
1304
|
-
annotation: details.showSubsequentAnnotations ? details.branchAnnotation : void 0
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
label: "App",
|
|
1308
|
-
value: details.appName,
|
|
1309
|
-
annotation: details.showSubsequentAnnotations ? details.appAnnotation : void 0
|
|
1310
|
-
}
|
|
1311
|
-
];
|
|
1312
|
-
const lines = details.firstDeploy ? [
|
|
1302
|
+
] : [];
|
|
1303
|
+
const lines = [
|
|
1313
1304
|
title,
|
|
1314
1305
|
"",
|
|
1315
|
-
...
|
|
1316
|
-
""
|
|
1317
|
-
] : [
|
|
1318
|
-
title,
|
|
1319
|
-
...renderDeploySetupRows(context, rows),
|
|
1306
|
+
...renderDeployOutputRows(context.ui, rows),
|
|
1320
1307
|
""
|
|
1321
1308
|
];
|
|
1322
1309
|
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
1323
1310
|
}
|
|
1311
|
+
function maybeRenderLocalPinBound(context, projectName) {
|
|
1312
|
+
if (context.flags.json || context.flags.quiet) return;
|
|
1313
|
+
context.output.stderr.write(`This directory is now linked to project ${projectName}.\n\n`);
|
|
1314
|
+
}
|
|
1324
1315
|
async function maybeCustomizeDeploySettings(context, options) {
|
|
1325
1316
|
if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitBuildType || options.explicitHttpPort || !canPrompt(context)) return {
|
|
1326
1317
|
framework: options.framework,
|
|
@@ -1364,19 +1355,16 @@ async function maybeCustomizeDeploySettings(context, options) {
|
|
|
1364
1355
|
value: `HTTP ${runtime.port}`,
|
|
1365
1356
|
annotation: runtime.annotation
|
|
1366
1357
|
} : null].filter((row) => Boolean(row));
|
|
1367
|
-
if (changedRows.length > 0 && !context.flags.quiet && !context.flags.json) context.output.stderr.write(`${
|
|
1358
|
+
if (changedRows.length > 0 && !context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderDeployOutputRows(context.ui, changedRows.map((row) => ({
|
|
1359
|
+
label: row.label,
|
|
1360
|
+
value: row.value,
|
|
1361
|
+
origin: row.annotation
|
|
1362
|
+
}))).join("\n")}\n\n`);
|
|
1368
1363
|
return {
|
|
1369
1364
|
framework,
|
|
1370
1365
|
runtime
|
|
1371
1366
|
};
|
|
1372
1367
|
}
|
|
1373
|
-
function renderDeploySetupRows(context, rows) {
|
|
1374
|
-
const labelWidth = Math.max(...rows.map((row) => row.label.length));
|
|
1375
|
-
const valueWidth = Math.max(...rows.map((row) => row.value.length));
|
|
1376
|
-
return rows.map((row) => {
|
|
1377
|
-
return ` ${row.label.padEnd(labelWidth)} ${row.value.padEnd(valueWidth)}${row.annotation ? ` ${context.ui.dim(row.annotation)}` : ""}`.trimEnd();
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
1368
|
function annotationForProjectResolution(resolution) {
|
|
1381
1369
|
switch (resolution.projectSource) {
|
|
1382
1370
|
case "explicit": return "set by --project";
|
|
@@ -1410,6 +1398,15 @@ function formatDeployDirectory(cwd) {
|
|
|
1410
1398
|
const basename = path.basename(cwd);
|
|
1411
1399
|
return basename ? `./${basename}` : ".";
|
|
1412
1400
|
}
|
|
1401
|
+
function formatLocalDirectory(cwd, env) {
|
|
1402
|
+
const resolved = path.resolve(cwd);
|
|
1403
|
+
const home = env.HOME ? path.resolve(env.HOME) : null;
|
|
1404
|
+
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
|
|
1405
|
+
const relative = path.relative(home, resolved);
|
|
1406
|
+
return relative ? `~/${relative}` : "~";
|
|
1407
|
+
}
|
|
1408
|
+
return resolved;
|
|
1409
|
+
}
|
|
1413
1410
|
async function readCurrentWorkspaceId(context) {
|
|
1414
1411
|
const state = await context.stateStore.read();
|
|
1415
1412
|
if (state.auth?.workspaceId) return state.auth.workspaceId;
|
|
@@ -1467,6 +1464,67 @@ function deployFailedError(summary, error, nextSteps) {
|
|
|
1467
1464
|
nextSteps
|
|
1468
1465
|
});
|
|
1469
1466
|
}
|
|
1467
|
+
function appDeployFailedError(error, progress) {
|
|
1468
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
1469
|
+
const debug = formatDebugDetails(error);
|
|
1470
|
+
if (progress.buildStarted && !progress.buildCompleted) return new CliError({
|
|
1471
|
+
code: "BUILD_FAILED",
|
|
1472
|
+
domain: "app",
|
|
1473
|
+
summary: "Build failed locally.",
|
|
1474
|
+
why,
|
|
1475
|
+
fix: "Inspect the build output above, fix the error, and redeploy.",
|
|
1476
|
+
debug,
|
|
1477
|
+
meta: { phase: "build" },
|
|
1478
|
+
humanLines: [
|
|
1479
|
+
"Build failed locally.",
|
|
1480
|
+
"",
|
|
1481
|
+
`✗ Built ${why}`,
|
|
1482
|
+
"",
|
|
1483
|
+
"Fix: Inspect the build output above, fix the error, and redeploy."
|
|
1484
|
+
],
|
|
1485
|
+
exitCode: 1,
|
|
1486
|
+
nextSteps: []
|
|
1487
|
+
});
|
|
1488
|
+
if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
|
|
1489
|
+
const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
|
|
1490
|
+
const recoveryLines = progress.versionId ? ["See what happened", `prisma-cli app logs --deployment ${progress.versionId}`] : ["Fix", "Retry the command, or rerun with --trace for more detailed diagnostics."];
|
|
1491
|
+
const urlLines = progress.deploymentUrl ? [
|
|
1492
|
+
"",
|
|
1493
|
+
"URL",
|
|
1494
|
+
progress.deploymentUrl
|
|
1495
|
+
] : [];
|
|
1496
|
+
const humanLines = progress.containerLive ? [
|
|
1497
|
+
phaseHeadline,
|
|
1498
|
+
"",
|
|
1499
|
+
"This is usually a missing env var, a failed DB connection,",
|
|
1500
|
+
"or a crash on startup.",
|
|
1501
|
+
"",
|
|
1502
|
+
...recoveryLines,
|
|
1503
|
+
...urlLines
|
|
1504
|
+
] : [
|
|
1505
|
+
phaseHeadline,
|
|
1506
|
+
"",
|
|
1507
|
+
progress.uploadCompleted ? "The artifact uploaded, but the deployment did not start." : progress.archiveReady ? "The app built locally, but the artifact did not finish uploading." : "The app built locally, but the deployment did not start.",
|
|
1508
|
+
"",
|
|
1509
|
+
...recoveryLines
|
|
1510
|
+
];
|
|
1511
|
+
return new CliError({
|
|
1512
|
+
code: "DEPLOY_FAILED",
|
|
1513
|
+
domain: "app",
|
|
1514
|
+
summary: phaseHeadline,
|
|
1515
|
+
why,
|
|
1516
|
+
fix: progress.versionId ? `Inspect logs with prisma-cli app logs --deployment ${progress.versionId}.` : "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
1517
|
+
debug,
|
|
1518
|
+
meta: {
|
|
1519
|
+
phase: progress.containerLive ? "runtime_ready" : "deploy",
|
|
1520
|
+
deploymentId: progress.versionId,
|
|
1521
|
+
deploymentUrl: progress.deploymentUrl
|
|
1522
|
+
},
|
|
1523
|
+
humanLines,
|
|
1524
|
+
exitCode: 1,
|
|
1525
|
+
nextSteps: []
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1470
1528
|
function localResolutionPinStaleError() {
|
|
1471
1529
|
return new CliError({
|
|
1472
1530
|
code: "LOCAL_STATE_STALE",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { padDisplay } from "../../shell/ui.js";
|
|
2
|
+
//#region src/lib/app/deploy-output.ts
|
|
3
|
+
const DEPLOY_OUTPUT_MIN_LABEL_WIDTH = 9;
|
|
4
|
+
const DEPLOY_OUTPUT_MIN_VALUE_WIDTH = 9;
|
|
5
|
+
function renderDeployOutputRows(ui, rows) {
|
|
6
|
+
if (rows.length === 0) return [];
|
|
7
|
+
const labelWidth = Math.max(DEPLOY_OUTPUT_MIN_LABEL_WIDTH, ...rows.map((row) => row.label.length));
|
|
8
|
+
const valueWidth = Math.max(DEPLOY_OUTPUT_MIN_VALUE_WIDTH, ...rows.map((row) => row.value?.length ?? 0));
|
|
9
|
+
return rows.map((row) => {
|
|
10
|
+
if (!row.value) return ` ${row.label}`;
|
|
11
|
+
return ` ${padDisplay(row.label, labelWidth)} ${padDisplay(ui.strong(row.value), valueWidth)}${row.origin ? ` ${ui.dim(`· ${row.origin}`)}` : ""}`.trimEnd();
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { renderDeployOutputRows };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { chmod, copyFile, cp, lstat, mkdir, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { AstroBuild, BunBuild, NextjsBuild, NuxtBuild, TanstackStartBuild } from "@prisma/compute-sdk";
|
|
5
5
|
//#region src/lib/app/preview-build.ts
|
|
@@ -46,6 +46,7 @@ async function executePreviewBuild(options) {
|
|
|
46
46
|
});
|
|
47
47
|
const artifact = await strategy.execute();
|
|
48
48
|
try {
|
|
49
|
+
if (buildType === "nextjs") await restageNextjsArtifact(artifact.directory, options.appPath);
|
|
49
50
|
await normalizeArtifactSymlinks(artifact.directory, options.appPath);
|
|
50
51
|
return {
|
|
51
52
|
artifact,
|
|
@@ -104,6 +105,38 @@ async function createPreviewBuildStrategy(options) {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
async function stageNextjsStandaloneArtifact(options) {
|
|
109
|
+
const standaloneRoot = path.resolve(options.standaloneDir);
|
|
110
|
+
const artifactRoot = path.resolve(options.artifactDir);
|
|
111
|
+
const appRoot = path.resolve(options.appPath);
|
|
112
|
+
await copyPathMaterializingSymlinks(standaloneRoot, artifactRoot, {
|
|
113
|
+
standaloneRoot,
|
|
114
|
+
appRoot,
|
|
115
|
+
sourceRoot: await resolveSourceRoot(appRoot)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async function restageNextjsArtifact(artifactDir, appPath) {
|
|
119
|
+
const standaloneDir = path.join(appPath, ".next", "standalone");
|
|
120
|
+
await rm(artifactDir, {
|
|
121
|
+
recursive: true,
|
|
122
|
+
force: true
|
|
123
|
+
});
|
|
124
|
+
await stageNextjsStandaloneArtifact({
|
|
125
|
+
standaloneDir,
|
|
126
|
+
artifactDir,
|
|
127
|
+
appPath
|
|
128
|
+
});
|
|
129
|
+
const publicDir = path.join(appPath, "public");
|
|
130
|
+
if (await directoryExists(publicDir)) await cp(publicDir, path.join(artifactDir, "public"), {
|
|
131
|
+
recursive: true,
|
|
132
|
+
verbatimSymlinks: true
|
|
133
|
+
});
|
|
134
|
+
const staticDir = path.join(appPath, ".next", "static");
|
|
135
|
+
if (await directoryExists(staticDir)) await cp(staticDir, path.join(artifactDir, ".next", "static"), {
|
|
136
|
+
recursive: true,
|
|
137
|
+
verbatimSymlinks: true
|
|
138
|
+
});
|
|
139
|
+
}
|
|
107
140
|
async function normalizeArtifactSymlinks(artifactDir, appPath) {
|
|
108
141
|
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
109
142
|
const normalizedAppPath = path.resolve(appPath);
|
|
@@ -138,5 +171,69 @@ function isPathWithin(rootPath, candidatePath) {
|
|
|
138
171
|
const relativePath = path.relative(rootPath, candidatePath);
|
|
139
172
|
return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
|
|
140
173
|
}
|
|
174
|
+
async function copyPathMaterializingSymlinks(sourcePath, destinationPath, options) {
|
|
175
|
+
const sourceStat = await lstat(sourcePath);
|
|
176
|
+
if (sourceStat.isSymbolicLink()) {
|
|
177
|
+
await copyPathMaterializingSymlinks(await resolveSymlinkTarget(sourcePath, options), destinationPath, options);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (sourceStat.isDirectory()) {
|
|
181
|
+
await mkdir(destinationPath, { recursive: true });
|
|
182
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
183
|
+
for (const entry of entries) await copyPathMaterializingSymlinks(path.join(sourcePath, entry.name), path.join(destinationPath, entry.name), options);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (sourceStat.isFile()) {
|
|
187
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
188
|
+
await copyFile(sourcePath, destinationPath);
|
|
189
|
+
await chmod(destinationPath, sourceStat.mode);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function resolveSymlinkTarget(symlinkPath, options) {
|
|
193
|
+
const linkTarget = await readlink(symlinkPath);
|
|
194
|
+
const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
|
|
195
|
+
if (await pathExists(resolvedTarget)) {
|
|
196
|
+
if (!isPathWithin(options.appRoot, resolvedTarget) && !isPathWithin(options.sourceRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
|
|
197
|
+
return resolvedTarget;
|
|
198
|
+
}
|
|
199
|
+
if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
|
|
200
|
+
const fallbackTarget = path.join(options.appRoot, path.relative(options.standaloneRoot, resolvedTarget));
|
|
201
|
+
if (await pathExists(fallbackTarget)) return fallbackTarget;
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Next.js standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`);
|
|
204
|
+
}
|
|
205
|
+
async function pathExists(targetPath) {
|
|
206
|
+
try {
|
|
207
|
+
await stat(targetPath);
|
|
208
|
+
return true;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function directoryExists(targetPath) {
|
|
214
|
+
try {
|
|
215
|
+
return (await stat(targetPath)).isDirectory();
|
|
216
|
+
} catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function resolveSourceRoot(appRoot) {
|
|
221
|
+
let current = path.resolve(appRoot);
|
|
222
|
+
while (true) {
|
|
223
|
+
if (await pathExists(path.join(current, ".git")) || await pathExists(path.join(current, "pnpm-workspace.yaml")) || await pathExists(path.join(current, "bun.lock")) || await pathExists(path.join(current, "bun.lockb")) || await packageJsonDeclaresWorkspaces(current)) return current;
|
|
224
|
+
const parent = path.dirname(current);
|
|
225
|
+
if (parent === current) return path.resolve(appRoot);
|
|
226
|
+
current = parent;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function packageJsonDeclaresWorkspaces(directory) {
|
|
230
|
+
try {
|
|
231
|
+
const content = await readFile(path.join(directory, "package.json"), "utf8");
|
|
232
|
+
const parsed = JSON.parse(content);
|
|
233
|
+
return Boolean(parsed.workspaces);
|
|
234
|
+
} catch {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
141
238
|
//#endregion
|
|
142
239
|
export { PREVIEW_BUILD_TYPES, PreviewBuildStrategy, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild };
|
|
@@ -1,83 +1,68 @@
|
|
|
1
|
+
import { renderDeployOutputRows } from "./deploy-output.js";
|
|
1
2
|
//#region src/lib/app/preview-progress.ts
|
|
2
|
-
function
|
|
3
|
-
|
|
3
|
+
function createPreviewDeployProgressState() {
|
|
4
|
+
return {
|
|
5
|
+
buildStarted: false,
|
|
6
|
+
buildCompleted: false,
|
|
7
|
+
archiveReady: false,
|
|
8
|
+
uploadCompleted: false,
|
|
9
|
+
versionId: null,
|
|
10
|
+
startRequested: false,
|
|
11
|
+
containerLive: false,
|
|
12
|
+
deploymentUrl: null,
|
|
13
|
+
promotedUrl: null
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function createPreviewDeployProgress(output, ui, enabled, state = createPreviewDeployProgressState()) {
|
|
4
17
|
const write = (line) => {
|
|
18
|
+
if (!enabled) return;
|
|
5
19
|
output.write(`${line}\n`);
|
|
6
20
|
};
|
|
21
|
+
const writeRows = (rows) => {
|
|
22
|
+
for (const line of renderDeployOutputRows(ui, rows)) write(line);
|
|
23
|
+
};
|
|
7
24
|
return {
|
|
8
25
|
onBuildStart() {
|
|
9
|
-
|
|
26
|
+
state.buildStarted = true;
|
|
27
|
+
write("Building locally...");
|
|
10
28
|
},
|
|
11
29
|
onBuildComplete() {
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
onArchiveCreating() {
|
|
15
|
-
write("Creating deployment artifact...");
|
|
30
|
+
state.buildCompleted = true;
|
|
16
31
|
},
|
|
17
32
|
onArchiveReady(byteLength) {
|
|
18
|
-
|
|
33
|
+
state.archiveReady = true;
|
|
34
|
+
writeRows([{
|
|
35
|
+
label: "Built",
|
|
36
|
+
value: formatArtifactSize(byteLength)
|
|
37
|
+
}]);
|
|
38
|
+
},
|
|
39
|
+
onUploadStart() {
|
|
40
|
+
write("Uploading...");
|
|
19
41
|
},
|
|
20
42
|
onVersionCreated(versionId) {
|
|
21
|
-
|
|
43
|
+
state.versionId = versionId;
|
|
22
44
|
},
|
|
23
45
|
onUploadComplete() {
|
|
24
|
-
|
|
46
|
+
state.uploadCompleted = true;
|
|
47
|
+
writeRows([{ label: "Uploaded" }]);
|
|
25
48
|
},
|
|
26
49
|
onStartRequested() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
onStatusChange(status) {
|
|
30
|
-
write(`Status: ${status}`);
|
|
50
|
+
state.startRequested = true;
|
|
51
|
+
write("Deploying...");
|
|
31
52
|
},
|
|
32
53
|
onRunning(url) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
write("Deployment is running.");
|
|
38
|
-
},
|
|
39
|
-
onPromoteStart() {
|
|
40
|
-
write("Promoting deployment...");
|
|
54
|
+
state.containerLive = true;
|
|
55
|
+
state.deploymentUrl = url;
|
|
56
|
+
writeRows([{ label: "Deployed" }]);
|
|
41
57
|
},
|
|
42
58
|
onPromoted(url) {
|
|
43
|
-
|
|
44
|
-
write(`Promoted to ${url}.`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
write("Promotion complete.");
|
|
48
|
-
},
|
|
49
|
-
onPromoteFailed(error) {
|
|
50
|
-
write(`Promotion failed${error?.message ? `: ${error.message}` : "."}`);
|
|
51
|
-
},
|
|
52
|
-
onOldVersionStopping(versionId) {
|
|
53
|
-
write(`Stopping previous deployment ${versionId}...`);
|
|
54
|
-
},
|
|
55
|
-
onOldVersionStopped(versionId) {
|
|
56
|
-
write(`Previous deployment ${versionId} stopped.`);
|
|
57
|
-
},
|
|
58
|
-
onOldVersionStopFailed(versionId) {
|
|
59
|
-
write(`Failed to stop previous deployment ${versionId} (non-fatal).`);
|
|
60
|
-
},
|
|
61
|
-
onOldVersionDeleting(versionId) {
|
|
62
|
-
write(`Deleting previous deployment ${versionId}...`);
|
|
63
|
-
},
|
|
64
|
-
onOldVersionDeleted(versionId) {
|
|
65
|
-
write(`Previous deployment ${versionId} deleted.`);
|
|
66
|
-
},
|
|
67
|
-
onOldVersionDeleteFailed(versionId) {
|
|
68
|
-
write(`Failed to delete previous deployment ${versionId} (non-fatal).`);
|
|
69
|
-
},
|
|
70
|
-
onCleanupDanglingVersion(versionId) {
|
|
71
|
-
write(`Cleaning up deployment ${versionId}...`);
|
|
72
|
-
},
|
|
73
|
-
onCleanupDanglingVersionComplete(versionId) {
|
|
74
|
-
write(`Deployment ${versionId} cleaned up.`);
|
|
75
|
-
},
|
|
76
|
-
onCleanupDanglingVersionFailed(versionId) {
|
|
77
|
-
write(`Failed to clean up deployment ${versionId}.`);
|
|
59
|
+
state.promotedUrl = url;
|
|
78
60
|
}
|
|
79
61
|
};
|
|
80
62
|
}
|
|
63
|
+
function formatArtifactSize(byteLength) {
|
|
64
|
+
return `${(byteLength / 1024 / 1024).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
81
66
|
function createPreviewPromoteProgress(output, enabled) {
|
|
82
67
|
if (!enabled) return;
|
|
83
68
|
const write = (line) => {
|
|
@@ -136,4 +121,4 @@ function createPreviewUpdateEnvProgress(output, enabled) {
|
|
|
136
121
|
};
|
|
137
122
|
}
|
|
138
123
|
//#endregion
|
|
139
|
-
export { createPreviewDeployProgress, createPreviewPromoteProgress, createPreviewUpdateEnvProgress };
|
|
124
|
+
export { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress, createPreviewUpdateEnvProgress };
|
package/dist/presenters/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderDeployOutputRows } from "../lib/app/deploy-output.js";
|
|
1
2
|
import { renderList, renderShow, serializeList } from "../output/patterns.js";
|
|
2
3
|
//#region src/presenters/app.ts
|
|
3
4
|
function renderAppBuild(context, descriptor, result) {
|
|
@@ -25,49 +26,24 @@ function serializeAppBuild(result) {
|
|
|
25
26
|
return result;
|
|
26
27
|
}
|
|
27
28
|
function renderAppDeploy(context, descriptor, result) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
key: "project",
|
|
38
|
-
value: result.project.name
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "branch",
|
|
42
|
-
value: result.branch.name
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: "app",
|
|
46
|
-
value: result.app.name
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
key: "deployment",
|
|
50
|
-
value: result.deployment.id
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
key: "status",
|
|
54
|
-
value: result.deployment.status,
|
|
55
|
-
tone: toneForStatus(result.deployment.status)
|
|
56
|
-
},
|
|
57
|
-
...result.deployment.url ? [{
|
|
58
|
-
key: "url",
|
|
59
|
-
value: result.deployment.url,
|
|
60
|
-
tone: "link"
|
|
61
|
-
}] : []
|
|
62
|
-
]
|
|
63
|
-
}, context.ui);
|
|
64
|
-
if (result.localPin?.written) lines.push(`Bound this directory in ${result.localPin.path}. Subsequent commands target the same Project.`);
|
|
65
|
-
return lines;
|
|
29
|
+
return [
|
|
30
|
+
`Live in ${formatDuration(result.durationMs)}`,
|
|
31
|
+
...result.deployment.url ? [context.ui.link(result.deployment.url)] : [],
|
|
32
|
+
"",
|
|
33
|
+
...renderDeployOutputRows(context.ui, [{
|
|
34
|
+
label: "Logs",
|
|
35
|
+
value: "prisma-cli app logs"
|
|
36
|
+
}])
|
|
37
|
+
];
|
|
66
38
|
}
|
|
67
39
|
function serializeAppDeploy(result) {
|
|
68
40
|
const { localPin: _localPin, ...serialized } = result;
|
|
69
41
|
return serialized;
|
|
70
42
|
}
|
|
43
|
+
function formatDuration(durationMs) {
|
|
44
|
+
if (durationMs < 1e3) return `${durationMs}ms`;
|
|
45
|
+
return `${(durationMs / 1e3).toFixed(1)}s`;
|
|
46
|
+
}
|
|
71
47
|
function renderAppUpdateEnv(context, descriptor, result) {
|
|
72
48
|
return renderShow({
|
|
73
49
|
title: "Updating environment variables for the selected app.",
|
package/dist/shell/errors.js
CHANGED
|
@@ -12,6 +12,7 @@ var CliError = class extends Error {
|
|
|
12
12
|
docsUrl;
|
|
13
13
|
exitCode;
|
|
14
14
|
nextSteps;
|
|
15
|
+
humanLines;
|
|
15
16
|
constructor(options) {
|
|
16
17
|
super(options.summary);
|
|
17
18
|
this.name = "CliError";
|
|
@@ -27,6 +28,7 @@ var CliError = class extends Error {
|
|
|
27
28
|
this.docsUrl = options.docsUrl ?? null;
|
|
28
29
|
this.exitCode = options.exitCode ?? 1;
|
|
29
30
|
this.nextSteps = options.nextSteps ?? [];
|
|
31
|
+
this.humanLines = options.humanLines && options.humanLines.length > 0 ? [...options.humanLines] : null;
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
function usageError(summary, why, fix, nextSteps = [], domain = "cli") {
|
package/dist/shell/output.js
CHANGED
|
@@ -36,6 +36,17 @@ function writeHumanLines(output, lines) {
|
|
|
36
36
|
output.stderr.write(`${lines.join("\n")}\n`);
|
|
37
37
|
}
|
|
38
38
|
function writeHumanError(output, ui, error, options) {
|
|
39
|
+
if (error.humanLines && error.humanLines.length > 0) {
|
|
40
|
+
const lines = [...error.humanLines];
|
|
41
|
+
if (options.trace && error.debug) {
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("Trace:");
|
|
44
|
+
lines.push(...error.debug.trimEnd().split("\n"));
|
|
45
|
+
}
|
|
46
|
+
lines.push(...renderNextSteps(error.nextSteps));
|
|
47
|
+
writeHumanLines(output, lines);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
39
50
|
const lines = [renderSummaryLine(ui, "error", `${error.summary} [${error.code}]`)];
|
|
40
51
|
if (error.where) lines.push(...["", `Where: ${error.where}`]);
|
|
41
52
|
if (error.why) {
|