@prisma/cli 3.0.0-alpha.6 → 3.0.0-alpha.8

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,5 +1,5 @@
1
- import path from "node:path";
2
1
  import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
3
  //#region src/lib/app/bun-project.ts
4
4
  async function readBunPackageJson(appPath) {
5
5
  const packageJsonPath = path.join(appPath, "package.json");
@@ -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,6 +1,6 @@
1
1
  import { readBunPackageEntrypoint, readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
2
- import path from "node:path";
3
2
  import { access } from "node:fs/promises";
3
+ import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
  //#region src/lib/app/local-dev.ts
6
6
  const NEXT_CONFIG_FILENAMES = [
@@ -1,6 +1,6 @@
1
1
  import { resolveBunEntrypoint } from "./bun-project.js";
2
- import path from "node:path";
3
2
  import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
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
6
6
  const PREVIEW_BUILD_TYPES = [
@@ -1,38 +1,5 @@
1
- import { selectPrompt, textPrompt } from "../../shell/prompt.js";
1
+ import "../../shell/prompt.js";
2
2
  //#region src/lib/app/preview-interaction.ts
3
- const CREATE_NEW_APP = "__create_new_app__";
4
3
  const PREVIEW_DEFAULT_REGION = "eu-central-1";
5
- function createPreviewDeployInteraction(context) {
6
- return {
7
- async selectService(services) {
8
- const sorted = services.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
9
- const selection = await selectPrompt({
10
- input: context.runtime.stdin,
11
- output: context.runtime.stderr,
12
- message: "Select an app",
13
- choices: [...sorted.map((service) => ({
14
- label: service.name,
15
- value: service.id
16
- })), {
17
- label: "Create a new app",
18
- value: CREATE_NEW_APP
19
- }]
20
- });
21
- return selection === CREATE_NEW_APP ? null : selection;
22
- },
23
- async provideServiceName() {
24
- return textPrompt({
25
- input: context.runtime.stdin,
26
- output: context.runtime.stderr,
27
- message: "App name",
28
- placeholder: "hello-world",
29
- validate: (value) => !value?.trim() ? "App name is required" : void 0
30
- }).then((value) => value.trim());
31
- },
32
- async selectRegion(_regions) {
33
- return PREVIEW_DEFAULT_REGION;
34
- }
35
- };
36
- }
37
4
  //#endregion
38
- export { PREVIEW_DEFAULT_REGION, createPreviewDeployInteraction };
5
+ export { PREVIEW_DEFAULT_REGION };
@@ -1,83 +1,68 @@
1
+ import { renderDeployOutputRows } from "./deploy-output.js";
1
2
  //#region src/lib/app/preview-progress.ts
2
- function createPreviewDeployProgress(output, enabled) {
3
- if (!enabled) return;
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
- write("Building application...");
26
+ state.buildStarted = true;
27
+ write("Building locally...");
10
28
  },
11
29
  onBuildComplete() {
12
- write("Build complete.");
13
- },
14
- onArchiveCreating() {
15
- write("Creating deployment artifact...");
30
+ state.buildCompleted = true;
16
31
  },
17
32
  onArchiveReady(byteLength) {
18
- write(`Artifact ready (${(byteLength / 1024).toFixed(1)} KB).`);
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
- write(`Deployment ${versionId} created.`);
43
+ state.versionId = versionId;
22
44
  },
23
45
  onUploadComplete() {
24
- write("Upload complete.");
46
+ state.uploadCompleted = true;
47
+ writeRows([{ label: "Uploaded" }]);
25
48
  },
26
49
  onStartRequested() {
27
- write("Starting deployment...");
28
- },
29
- onStatusChange(status) {
30
- write(`Status: ${status}`);
50
+ state.startRequested = true;
51
+ write("Deploying...");
31
52
  },
32
53
  onRunning(url) {
33
- if (url) {
34
- write(`Deployment is running at ${url}.`);
35
- return;
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
- if (url) {
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 };
@@ -14,25 +14,11 @@ function createPreviewAppProvider(client, options) {
14
14
  name: projectResult.value.name
15
15
  };
16
16
  },
17
- async listApps(projectId) {
18
- const servicesResult = await sdk.listServices({ projectId });
19
- if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
20
- return (await Promise.all(servicesResult.value.map(async (service) => {
21
- const detailResult = await sdk.showService({ serviceId: service.id });
22
- return detailResult.isOk() ? detailResult.value : {
23
- id: service.id,
24
- name: service.name,
25
- region: service.region,
26
- latestVersionId: null,
27
- serviceEndpointDomain: void 0
28
- };
29
- }))).map((service) => ({
30
- id: service.id,
31
- name: service.name,
32
- region: service.region ?? null,
33
- liveDeploymentId: service.latestVersionId ?? null,
34
- liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
35
- }));
17
+ async listApps(projectId, options) {
18
+ return listComputeServices(client, {
19
+ projectId,
20
+ branchGitName: options?.branchName
21
+ });
36
22
  },
37
23
  async removeApp(appId) {
38
24
  const appResult = await sdk.showService({ serviceId: appId });
@@ -60,6 +46,20 @@ function createPreviewAppProvider(client, options) {
60
46
  if (promoteResult.isErr()) throw new Error(promoteResult.error.message);
61
47
  },
62
48
  async deployApp(options) {
49
+ const resolvedApp = options.appId ? {
50
+ appId: options.appId,
51
+ appName: options.appName,
52
+ region: options.region
53
+ } : options.branchName && options.appName ? await createBranchApp(client, {
54
+ projectId: options.projectId,
55
+ branchName: options.branchName,
56
+ appName: options.appName,
57
+ region: options.region
58
+ }) : {
59
+ appId: void 0,
60
+ appName: options.appName,
61
+ region: options.region
62
+ };
63
63
  const deployResult = await sdk.deploy({
64
64
  strategy: new PreviewBuildStrategy({
65
65
  appPath: path.resolve(options.cwd),
@@ -67,9 +67,9 @@ function createPreviewAppProvider(client, options) {
67
67
  buildType: options.buildType
68
68
  }),
69
69
  projectId: options.projectId,
70
- serviceId: options.appId,
71
- serviceName: options.appName,
72
- region: options.region,
70
+ serviceId: resolvedApp.appId,
71
+ serviceName: resolvedApp.appName,
72
+ region: resolvedApp.region,
73
73
  portMapping: options.portMapping,
74
74
  envVars: options.envVars,
75
75
  timeoutSeconds: 120,
@@ -213,6 +213,94 @@ function createPreviewAppProvider(client, options) {
213
213
  }
214
214
  };
215
215
  }
216
+ async function listBranches(client, options) {
217
+ const result = await client.GET("/v1/projects/{projectId}/branches", { params: {
218
+ path: { projectId: options.projectId },
219
+ query: { gitName: options.gitName }
220
+ } });
221
+ if (result.error || !result.data) throw apiCallError("Failed to list branches", result.response, result.error);
222
+ return result.data.data;
223
+ }
224
+ async function resolveOrCreateBranch(client, options) {
225
+ const existing = (await listBranches(client, options))[0];
226
+ if (existing) return existing;
227
+ const result = await client.POST("/v1/projects/{projectId}/branches", {
228
+ params: { path: { projectId: options.projectId } },
229
+ body: {
230
+ gitName: options.gitName,
231
+ isDefault: options.gitName === "main"
232
+ }
233
+ });
234
+ if (result.error || !result.data) {
235
+ if (result.response.status === 409) {
236
+ const raced = (await listBranches(client, options))[0];
237
+ if (raced) return raced;
238
+ }
239
+ throw apiCallError(`Failed to create branch "${options.gitName}"`, result.response, result.error);
240
+ }
241
+ return result.data.data;
242
+ }
243
+ async function listComputeServices(client, options) {
244
+ const services = [];
245
+ let cursor;
246
+ while (true) {
247
+ const result = await client.GET("/v1/compute-services", { params: { query: {
248
+ projectId: options.projectId,
249
+ branchGitName: options.branchGitName,
250
+ cursor
251
+ } } });
252
+ if (result.error || !result.data) throw apiCallError("Failed to list apps", result.response, result.error);
253
+ services.push(...result.data.data);
254
+ if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
255
+ cursor = result.data.pagination.nextCursor;
256
+ }
257
+ return services.map((service) => ({
258
+ id: service.id,
259
+ name: service.name,
260
+ region: service.region.id ?? null,
261
+ branchId: service.branchId,
262
+ liveDeploymentId: service.latestVersionId ?? null,
263
+ liveUrl: toAbsoluteUrl(service.serviceEndpointDomain ?? null)
264
+ }));
265
+ }
266
+ async function createBranchApp(client, options) {
267
+ const branch = await resolveOrCreateBranch(client, {
268
+ projectId: options.projectId,
269
+ gitName: options.branchName
270
+ });
271
+ const result = await client.POST("/v1/compute-services", { body: {
272
+ projectId: options.projectId,
273
+ branchId: branch.id,
274
+ displayName: options.appName,
275
+ ...options.region ? { regionId: options.region } : {}
276
+ } });
277
+ if (result.error || !result.data) {
278
+ if (result.response.status === 409) {
279
+ const matched = (await listComputeServices(client, {
280
+ projectId: options.projectId,
281
+ branchGitName: options.branchName
282
+ })).find((app) => app.name === options.appName);
283
+ if (matched) return {
284
+ appId: matched.id,
285
+ appName: matched.name,
286
+ region: matched.region ?? options.region
287
+ };
288
+ }
289
+ throw apiCallError(`Failed to create app "${options.appName}"`, result.response, result.error);
290
+ }
291
+ const service = result.data.data;
292
+ return {
293
+ appId: service.id,
294
+ appName: service.name,
295
+ region: service.region.id ?? options.region
296
+ };
297
+ }
298
+ function apiCallError(summary, response, error) {
299
+ if (response.status === 404) return /* @__PURE__ */ new Error("Resource Not Found");
300
+ const message = error.error?.message ?? `Management API returned HTTP ${response.status}.`;
301
+ const hint = error.error?.hint ? ` ${error.error.hint}` : "";
302
+ return /* @__PURE__ */ new Error(`${summary}: ${message}${hint}`);
303
+ }
216
304
  async function findAppForDeployment(sdk, deploymentId) {
217
305
  const projectsResult = await sdk.listProjects();
218
306
  if (projectsResult.isErr()) throw new Error(projectsResult.error.message);
@@ -1,5 +1,5 @@
1
- import os from "node:os";
2
1
  import path from "node:path";
2
+ import os from "node:os";
3
3
  //#region src/lib/auth/client.ts
4
4
  const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
5
5
  const SERVICE_TOKEN_ENV_VAR = "PRISMA_SERVICE_TOKEN";
@@ -0,0 +1,51 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ //#region src/lib/project/local-pin.ts
4
+ const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
5
+ async function readLocalResolutionPin(cwd) {
6
+ try {
7
+ const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), "utf8");
8
+ const parsed = JSON.parse(raw);
9
+ if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
10
+ return {
11
+ kind: "present",
12
+ pin: parsed
13
+ };
14
+ } catch (error) {
15
+ if (error.code === "ENOENT") return { kind: "missing" };
16
+ if (error instanceof SyntaxError) return { kind: "invalid" };
17
+ throw error;
18
+ }
19
+ }
20
+ async function writeLocalResolutionPin(cwd, pin) {
21
+ const prismaDir = path.join(cwd, ".prisma");
22
+ await mkdir(prismaDir, { recursive: true });
23
+ const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
24
+ const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
25
+ await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, "utf8");
26
+ await rename(tmpPath, pinPath);
27
+ }
28
+ async function ensureLocalResolutionPinGitignore(cwd) {
29
+ const gitignorePath = path.join(cwd, ".gitignore");
30
+ let existing = null;
31
+ try {
32
+ existing = await readFile(gitignorePath, "utf8");
33
+ } catch (error) {
34
+ if (error.code !== "ENOENT") throw error;
35
+ }
36
+ if (existing === null) {
37
+ await writeFile(gitignorePath, ".prisma/\n", "utf8");
38
+ return;
39
+ }
40
+ if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
41
+ await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, "utf8");
42
+ }
43
+ function isLocalResolutionPin(value) {
44
+ if (!value || typeof value !== "object") return false;
45
+ const keys = Object.keys(value);
46
+ if (keys.length !== 2 || !keys.includes("workspaceId") || !keys.includes("projectId")) return false;
47
+ const candidate = value;
48
+ return typeof candidate.workspaceId === "string" && candidate.workspaceId.trim().length > 0 && typeof candidate.projectId === "string" && candidate.projectId.trim().length > 0;
49
+ }
50
+ //#endregion
51
+ export { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin };
@@ -1,33 +1,44 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
2
  import { canPrompt } from "../../shell/runtime.js";
3
- import path from "node:path";
4
3
  import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
5
  //#region src/lib/project/resolution.ts
6
6
  async function resolveProjectTarget(options) {
7
7
  const projects = await options.listProjects();
8
- if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit");
8
+ const inferredName = await inferTargetName(options.context.runtime.cwd);
9
+ if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
10
+ targetName: options.explicitProject,
11
+ targetNameSource: "explicit"
12
+ });
9
13
  const platformMapping = await resolveDurablePlatformMapping();
10
14
  if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
11
- const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
12
15
  let staleRemembered = false;
13
- if (remembered) {
14
- const matched = projects.find((project) => project.id === remembered.id);
15
- if (matched) return rememberIfRequested(options, matched, "remembered-local");
16
- staleRemembered = true;
16
+ if (!options.allowCreate) {
17
+ const rememberedResult = await resolveRememberedProject(options, projects);
18
+ if (rememberedResult.target) return rememberedResult.target;
19
+ staleRemembered = rememberedResult.stale;
17
20
  }
18
- const packageName = await readPackageName(options.context.runtime.cwd);
21
+ const packageName = inferredName.source === "package-name" ? inferredName.name : null;
19
22
  if (packageName) {
20
23
  const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
21
- if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name");
22
- if (matches.length > 1) return resolveAmbiguousProject(options, matches, "package-name");
24
+ if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
25
+ targetName: packageName,
26
+ targetNameSource: "package-name"
27
+ });
28
+ if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
23
29
  }
24
30
  if (options.allowCreate && options.createProject) {
25
- const inferredName = packageName ?? path.basename(options.context.runtime.cwd);
26
- if (inferredName) {
27
- const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName));
28
- if (existing.length === 1) return rememberIfRequested(options, existing[0], "package-name");
29
- if (existing.length > 1) return resolveAmbiguousProject(options, existing, "package-name");
30
- return rememberIfRequested(options, await options.createProject(inferredName), "created");
31
+ if (inferredName.name) {
32
+ const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
33
+ if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
34
+ targetName: inferredName.name,
35
+ targetNameSource: inferredName.source
36
+ });
37
+ if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
38
+ return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
39
+ targetName: inferredName.name,
40
+ targetNameSource: inferredName.source
41
+ });
31
42
  }
32
43
  }
33
44
  if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
@@ -40,6 +51,25 @@ async function resolveProjectTarget(options) {
40
51
  if (staleRemembered && projects.length > 1) throw localStateStaleError();
41
52
  throw projectUnresolvedError();
42
53
  }
54
+ async function resolveRememberedProject(options, projects) {
55
+ const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
56
+ if (!remembered) return {
57
+ target: null,
58
+ stale: false
59
+ };
60
+ const matched = projects.find((project) => project.id === remembered.id);
61
+ if (!matched) return {
62
+ target: null,
63
+ stale: true
64
+ };
65
+ return {
66
+ target: await rememberIfRequested(options, matched, "remembered-local", {
67
+ targetName: remembered.name,
68
+ targetNameSource: "remembered-local"
69
+ }),
70
+ stale: false
71
+ };
72
+ }
43
73
  function projectNotFoundError(projectRef, workspace) {
44
74
  return new CliError({
45
75
  code: "PROJECT_NOT_FOUND",
@@ -104,6 +134,20 @@ async function readPackageName(cwd) {
104
134
  throw error;
105
135
  }
106
136
  }
137
+ async function inferTargetName(cwd) {
138
+ const packageName = await readPackageName(cwd);
139
+ if (packageName && isValidInferredTargetName(packageName)) return {
140
+ name: packageName,
141
+ source: "package-name"
142
+ };
143
+ return {
144
+ name: path.basename(cwd),
145
+ source: "directory-name"
146
+ };
147
+ }
148
+ function isValidInferredTargetName(value) {
149
+ return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
150
+ }
107
151
  function sortProjects(projects) {
108
152
  return projects.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
109
153
  }
@@ -113,14 +157,17 @@ function resolveExplicitProject(projectRef, projects, workspace) {
113
157
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
114
158
  throw projectNotFoundError(projectRef, workspace);
115
159
  }
116
- function resolveAmbiguousProject(options, matches, projectRef) {
160
+ function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
117
161
  if (options.prompt && canPrompt(options.context)) return options.prompt.select({
118
162
  message: "Select a project",
119
163
  choices: sortProjects(matches).map((project) => ({
120
164
  label: `${project.name} (${project.id})`,
121
165
  value: project
122
166
  }))
123
- }).then((selected) => rememberIfRequested(options, selected, "prompt"));
167
+ }).then((selected) => rememberIfRequested(options, selected, "prompt", {
168
+ targetName: projectRef,
169
+ targetNameSource
170
+ }));
124
171
  throw projectAmbiguousError(projectRef, matches);
125
172
  }
126
173
  function projectMatchesPackageName(project, packageName) {
@@ -129,7 +176,7 @@ function projectMatchesPackageName(project, packageName) {
129
176
  async function resolveDurablePlatformMapping() {
130
177
  return null;
131
178
  }
132
- async function rememberIfRequested(options, project, projectSource) {
179
+ async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
133
180
  if (options.remember) await options.context.stateStore.setRememberedProject({
134
181
  id: project.id,
135
182
  name: project.name,
@@ -138,7 +185,10 @@ async function rememberIfRequested(options, project, projectSource) {
138
185
  return {
139
186
  workspace: options.workspace,
140
187
  project: toProjectSummary(project),
141
- resolution: { projectSource }
188
+ resolution: {
189
+ projectSource,
190
+ ...resolutionDetails
191
+ }
142
192
  };
143
193
  }
144
194
  function toProjectSummary(project) {
@@ -148,4 +198,4 @@ function toProjectSummary(project) {
148
198
  };
149
199
  }
150
200
  //#endregion
151
- export { resolveProjectTarget, sortProjects };
201
+ export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
@@ -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,45 +26,23 @@ function serializeAppBuild(result) {
25
26
  return result;
26
27
  }
27
28
  function renderAppDeploy(context, descriptor, result) {
28
- return renderShow({
29
- title: "Deploying the selected app.",
30
- descriptor,
31
- fields: [
32
- {
33
- key: "workspace",
34
- value: result.workspace.name
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);
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
+ ];
64
38
  }
65
39
  function serializeAppDeploy(result) {
66
- return result;
40
+ const { localPin: _localPin, ...serialized } = result;
41
+ return serialized;
42
+ }
43
+ function formatDuration(durationMs) {
44
+ if (durationMs < 1e3) return `${durationMs}ms`;
45
+ return `${(durationMs / 1e3).toFixed(1)}s`;
67
46
  }
68
47
  function renderAppUpdateEnv(context, descriptor, result) {
69
48
  return renderShow({
@@ -105,9 +105,12 @@ function renderGitDisconnect(context, descriptor, result) {
105
105
  function formatProjectSource(source) {
106
106
  switch (source) {
107
107
  case "explicit": return "explicit";
108
+ case "env": return "environment";
109
+ case "local-pin": return "local pin";
108
110
  case "platform-mapping": return "platform mapping";
109
111
  case "remembered-local": return "remembered local context";
110
112
  case "package-name": return "package name";
113
+ case "directory-name": return "directory name";
111
114
  case "created": return "created";
112
115
  case "prompt": return "prompt";
113
116
  }