@prisma/cli 3.0.0-beta.3 → 3.0.0-beta.5

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.
@@ -0,0 +1,160 @@
1
+ import { CliError } from "../../shell/errors.js";
2
+ import { canPrompt } from "../../shell/runtime.js";
3
+ import { confirmPrompt } from "../../shell/prompt.js";
4
+ //#region src/lib/app/production-deploy-gate.ts
5
+ async function enforceProductionDeployGate(context, provider, options) {
6
+ if (options.branchKind !== "production") return;
7
+ if (!options.appId) {
8
+ renderFirstProductionDeployLine(context, options.appName);
9
+ return;
10
+ }
11
+ const currentLiveDeployment = resolveCurrentProductionDeployment(await provider.listDeployments(options.appId).catch((error) => {
12
+ throw productionDeployInspectionFailedError(error);
13
+ }));
14
+ if (!currentLiveDeployment) {
15
+ renderFirstProductionDeployLine(context, options.appName);
16
+ return;
17
+ }
18
+ if (!options.prod) throw productionDeployRequiresFlagError();
19
+ if (context.flags.yes) {
20
+ renderProductionDeployYesLine(context);
21
+ return;
22
+ }
23
+ if (!canPrompt(context)) throw productionDeployConfirmationRequiredError(options.appName);
24
+ renderProductionDeployConfirmation(context, currentLiveDeployment);
25
+ if (!await confirmPrompt({
26
+ input: context.runtime.stdin,
27
+ output: context.output.stderr,
28
+ message: "Deploy to production?",
29
+ initialValue: false
30
+ })) throw productionDeployCancelledError();
31
+ }
32
+ function resolveCurrentProductionDeployment(result) {
33
+ if (result.deployments.length === 0) return null;
34
+ if (result.app.liveDeploymentId) {
35
+ const live = result.deployments.find((deployment) => deployment.id === result.app.liveDeploymentId);
36
+ if (live) return live;
37
+ }
38
+ return result.deployments.find((deployment) => deployment.live === true) ?? result.deployments[0] ?? null;
39
+ }
40
+ function renderFirstProductionDeployLine(context, appName) {
41
+ if (context.flags.json || context.flags.quiet) return;
42
+ context.output.stderr.write(`First deploy of "${appName}" -- promoting to production.\n\n`);
43
+ }
44
+ function renderProductionDeployYesLine(context) {
45
+ if (context.flags.json || context.flags.quiet) return;
46
+ context.output.stderr.write("Deploying to production (--prod --yes).\n\n");
47
+ }
48
+ function renderProductionDeployConfirmation(context, currentLiveDeployment) {
49
+ if (context.flags.json || context.flags.quiet) return;
50
+ const lines = [
51
+ "This will deploy to production and replace the live deployment.",
52
+ "",
53
+ ` Current live: ${currentLiveDeployment.id} deployed ${formatDeploymentAge(currentLiveDeployment.createdAt)}`,
54
+ " New deploy: will be built from your local code",
55
+ ""
56
+ ];
57
+ context.output.stderr.write(`${lines.join("\n")}\n`);
58
+ }
59
+ function formatDeploymentAge(createdAt) {
60
+ const createdAtMs = Date.parse(createdAt);
61
+ if (!Number.isFinite(createdAtMs)) return createdAt;
62
+ const elapsedMs = Math.max(0, Date.now() - createdAtMs);
63
+ for (const unit of [
64
+ {
65
+ label: "day",
66
+ ms: 1440 * 60 * 1e3
67
+ },
68
+ {
69
+ label: "hour",
70
+ ms: 3600 * 1e3
71
+ },
72
+ {
73
+ label: "minute",
74
+ ms: 60 * 1e3
75
+ }
76
+ ]) if (elapsedMs >= unit.ms) {
77
+ const value = Math.floor(elapsedMs / unit.ms);
78
+ return `${value} ${unit.label}${value === 1 ? "" : "s"} ago`;
79
+ }
80
+ return "less than a minute ago";
81
+ }
82
+ function productionDeployRequiresFlagError() {
83
+ return new CliError({
84
+ code: "PROD_DEPLOY_REQUIRES_FLAG",
85
+ domain: "app",
86
+ summary: "Production deploy requires --prod",
87
+ why: "The resolved Branch is production and this App already has a production deployment.",
88
+ fix: "Re-run with --prod, or deploy from a preview Branch.",
89
+ exitCode: 2,
90
+ nextActions: [{
91
+ kind: "run-command",
92
+ journey: "deploy-app",
93
+ label: "Deploy to production",
94
+ command: "prisma-cli app deploy --prod"
95
+ }, {
96
+ kind: "run-command",
97
+ journey: "deploy-app",
98
+ label: "Create a preview branch",
99
+ commands: ["git checkout -b <branch-name>", "prisma-cli app deploy"]
100
+ }],
101
+ humanLines: [
102
+ "This would deploy to production.",
103
+ "",
104
+ "Production deploys require explicit intent. Re-run with:",
105
+ "",
106
+ " prisma app deploy --prod",
107
+ "",
108
+ "Or deploy a preview from a feature branch:",
109
+ "",
110
+ " git checkout -b <branch-name>",
111
+ " prisma app deploy"
112
+ ]
113
+ });
114
+ }
115
+ function productionDeployConfirmationRequiredError(appName) {
116
+ return new CliError({
117
+ code: "CONFIRMATION_REQUIRED",
118
+ domain: "app",
119
+ summary: "Production deploy requires confirmation in the current mode",
120
+ why: "This command cannot prompt for production deploy confirmation in the current mode.",
121
+ fix: `Pass --prod --yes to confirm deployment of "${appName}" to production.`,
122
+ exitCode: 1,
123
+ nextSteps: ["prisma-cli app deploy --prod --yes"],
124
+ nextActions: [{
125
+ kind: "run-command",
126
+ journey: "deploy-app",
127
+ label: "Deploy to production non-interactively",
128
+ command: "prisma-cli app deploy --prod --yes"
129
+ }]
130
+ });
131
+ }
132
+ function productionDeployCancelledError() {
133
+ return new CliError({
134
+ code: "CONFIRMATION_REQUIRED",
135
+ domain: "app",
136
+ summary: "Production deploy cancelled",
137
+ why: null,
138
+ fix: null,
139
+ exitCode: 0,
140
+ humanLines: ["Cancelled."]
141
+ });
142
+ }
143
+ function productionDeployInspectionFailedError(error) {
144
+ return new CliError({
145
+ code: "DEPLOY_FAILED",
146
+ domain: "app",
147
+ summary: "Failed to inspect production deployments",
148
+ why: error instanceof Error ? error.message : String(error),
149
+ fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
150
+ debug: formatDebugDetails(error),
151
+ exitCode: 1,
152
+ nextSteps: ["prisma-cli app list-deploys"]
153
+ });
154
+ }
155
+ function formatDebugDetails(error) {
156
+ if (error instanceof Error) return error.stack ?? error.message;
157
+ return typeof error === "string" ? error : null;
158
+ }
159
+ //#endregion
160
+ export { enforceProductionDeployGate };
@@ -0,0 +1,41 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ //#region src/lib/git/local-branch.ts
4
+ async function readLocalGitBranch(cwd, signal) {
5
+ const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
6
+ if (!headPath) return null;
7
+ try {
8
+ const head = (await readFile(headPath, {
9
+ encoding: "utf8",
10
+ signal
11
+ })).trim();
12
+ if (head.startsWith("ref: refs/heads/")) return head.slice(16);
13
+ } catch (error) {
14
+ if (signal.aborted) throw error;
15
+ return null;
16
+ }
17
+ return null;
18
+ }
19
+ async function resolveGitHeadPath(gitPath, signal) {
20
+ signal.throwIfAborted();
21
+ try {
22
+ const raw = await readFile(gitPath, {
23
+ encoding: "utf8",
24
+ signal
25
+ });
26
+ if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
27
+ } catch (error) {
28
+ if (signal.aborted) throw error;
29
+ }
30
+ signal.throwIfAborted();
31
+ try {
32
+ await access(path.join(gitPath, "HEAD"));
33
+ signal.throwIfAborted();
34
+ return path.join(gitPath, "HEAD");
35
+ } catch (error) {
36
+ if (signal.aborted) throw error;
37
+ return null;
38
+ }
39
+ }
40
+ //#endregion
41
+ export { readLocalGitBranch };
@@ -5,8 +5,12 @@ import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  //#region src/lib/project/resolution.ts
7
7
  async function resolveProjectTarget(options) {
8
+ const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: true });
8
9
  const projects = await options.listProjects();
9
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
+ const target = await resolveBoundProjectTarget(options, projects, {
11
+ allowEnvProjectId: true,
12
+ localPin
13
+ });
10
14
  if (target) return target;
11
15
  throw await projectSetupRequiredError({
12
16
  cwd: options.context.runtime.cwd,
@@ -16,8 +20,12 @@ async function resolveProjectTarget(options) {
16
20
  });
17
21
  }
18
22
  async function inspectProjectBinding(options) {
23
+ const localPin = await readImplicitLocalPin(options, { allowEnvProjectId: false });
19
24
  const projects = await options.listProjects();
20
- const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
25
+ const target = await resolveBoundProjectTarget(options, projects, {
26
+ allowEnvProjectId: false,
27
+ localPin
28
+ });
21
29
  if (target) return target;
22
30
  return {
23
31
  workspace: options.workspace,
@@ -73,6 +81,28 @@ function localStateStaleError() {
73
81
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
74
82
  });
75
83
  }
84
+ function localProjectWorkspaceMismatchError(options) {
85
+ return new CliError({
86
+ code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
87
+ domain: "project",
88
+ summary: "Project link uses another workspace",
89
+ why: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but your current CLI session is workspace "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
90
+ fix: "Sign in to the linked workspace, or relink this directory to a project in the current workspace.",
91
+ meta: {
92
+ pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
93
+ pinnedWorkspaceId: options.pinnedWorkspaceId,
94
+ pinnedProjectId: options.pinnedProjectId,
95
+ activeWorkspaceId: options.activeWorkspace.id,
96
+ activeWorkspaceName: options.activeWorkspace.name
97
+ },
98
+ exitCode: 1,
99
+ nextSteps: [
100
+ "prisma-cli auth login",
101
+ "prisma-cli project list",
102
+ "prisma-cli project link <id-or-name>"
103
+ ]
104
+ });
105
+ }
76
106
  async function buildProjectSetupSuggestion(options) {
77
107
  const suggestedName = await inferTargetName(options.cwd, options.signal);
78
108
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
@@ -192,10 +222,15 @@ async function resolveBoundProjectTarget(options, projects, settings) {
192
222
  targetNameSource: "env"
193
223
  });
194
224
  }
195
- const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
225
+ const localPin = settings.localPin;
226
+ if (!localPin) return null;
196
227
  if (localPin.kind === "invalid") throw localStateStaleError();
197
228
  if (localPin.kind === "present") {
198
- if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
229
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
230
+ pinnedWorkspaceId: localPin.pin.workspaceId,
231
+ pinnedProjectId: localPin.pin.projectId,
232
+ activeWorkspace: options.workspace
233
+ });
199
234
  const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
200
235
  if (!project) throw localStateStaleError();
201
236
  return resolvedTarget(options.workspace, project, "local-pin", {
@@ -210,6 +245,16 @@ async function resolveBoundProjectTarget(options, projects, settings) {
210
245
  });
211
246
  return null;
212
247
  }
248
+ async function readImplicitLocalPin(options, settings) {
249
+ if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return null;
250
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
251
+ if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) throw localProjectWorkspaceMismatchError({
252
+ pinnedWorkspaceId: localPin.pin.workspaceId,
253
+ pinnedProjectId: localPin.pin.projectId,
254
+ activeWorkspace: options.workspace
255
+ });
256
+ return localPin;
257
+ }
213
258
  function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
214
259
  return {
215
260
  workspace,
@@ -2,9 +2,34 @@ import { renderList, renderShow, serializeList } from "../output/patterns.js";
2
2
  //#region src/presenters/app-env.ts
3
3
  function scopeLabel(scope) {
4
4
  if (scope.kind === "role") return scope.role ?? "unknown";
5
+ if (scope.kind === "overview") return "overview";
5
6
  return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
6
7
  }
8
+ function listTargetLabel(result) {
9
+ const target = result.target;
10
+ if (target.source === "overview") return "overview";
11
+ if (target.branchName) {
12
+ const suffix = target.branchExists === false ? " (not created yet)" : "";
13
+ return `branch:${target.branchName} -> ${target.envMap}${suffix}`;
14
+ }
15
+ return scopeLabel(result.scope);
16
+ }
7
17
  function renderEnvAdd(context, descriptor, result) {
18
+ if (result.variables !== void 0) return renderList({
19
+ title: "Setting new environment variables from file.",
20
+ descriptor,
21
+ parentContext: {
22
+ key: "target",
23
+ value: `${scopeLabel(result.scope)} from ${result.file.path}`
24
+ },
25
+ items: result.variables.map((variable) => ({
26
+ noun: "variable",
27
+ label: `${variable.key} (${variable.source})`,
28
+ id: variable.id,
29
+ status: variable.isManagedBySystem ? "default" : null
30
+ })),
31
+ emptyMessage: "No environment variables imported."
32
+ }, context.ui);
8
33
  return renderShow({
9
34
  title: "Setting a new environment variable.",
10
35
  descriptor,
@@ -38,6 +63,21 @@ function serializeEnvAdd(result) {
38
63
  return result;
39
64
  }
40
65
  function renderEnvUpdate(context, descriptor, result) {
66
+ if (result.variables !== void 0) return renderList({
67
+ title: "Replacing environment variable values from file.",
68
+ descriptor,
69
+ parentContext: {
70
+ key: "target",
71
+ value: `${scopeLabel(result.scope)} from ${result.file.path}`
72
+ },
73
+ items: result.variables.map((variable) => ({
74
+ noun: "variable",
75
+ label: `${variable.key} (${variable.source})`,
76
+ id: variable.id,
77
+ status: variable.isManagedBySystem ? "default" : null
78
+ })),
79
+ emptyMessage: "No environment variables updated."
80
+ }, context.ui);
41
81
  return renderShow({
42
82
  title: "Replacing the environment variable's value.",
43
83
  descriptor,
@@ -75,8 +115,8 @@ function renderEnvList(context, descriptor, result) {
75
115
  title: "Listing environment variables for the selected scope.",
76
116
  descriptor,
77
117
  parentContext: {
78
- key: "scope",
79
- value: scopeLabel(result.scope)
118
+ key: "target",
119
+ value: listTargetLabel(result)
80
120
  },
81
121
  items: result.variables.map((variable) => ({
82
122
  noun: "variable",
@@ -91,8 +131,9 @@ function serializeEnvList(result) {
91
131
  return {
92
132
  projectId: result.projectId,
93
133
  scope: result.scope,
134
+ target: result.target,
94
135
  ...serializeList({
95
- context: { scope: scopeLabel(result.scope) },
136
+ context: { target: listTargetLabel(result) },
96
137
  items: result.variables.map((variable) => ({
97
138
  noun: "variable",
98
139
  label: `${variable.key} (${variable.source})`,
@@ -30,6 +30,7 @@ function renderAppDeploy(context, descriptor, result) {
30
30
  return [
31
31
  `Live in ${formatDuration(result.durationMs)}`,
32
32
  ...result.deployment.url ? [context.ui.link(result.deployment.url)] : [],
33
+ ...renderBranchDatabaseDeploySummary(context, result),
33
34
  "",
34
35
  ...renderDeployOutputRows(context.ui, [{
35
36
  label: "Logs",
@@ -41,6 +42,30 @@ function serializeAppDeploy(result) {
41
42
  const { localPin: _localPin, ...serialized } = result;
42
43
  return serialized;
43
44
  }
45
+ function renderBranchDatabaseDeploySummary(context, result) {
46
+ if (!result.branchDatabase || result.branchDatabase.status !== "created") return [];
47
+ return ["", ...renderDeployOutputRows(context.ui, [
48
+ {
49
+ label: "Database",
50
+ value: result.branchDatabase.database?.name ?? "created"
51
+ },
52
+ {
53
+ label: "Env",
54
+ value: result.branchDatabase.envVars.join(", ")
55
+ },
56
+ ...result.branchDatabase.schema ? [{
57
+ label: "Schema",
58
+ value: formatBranchDatabaseSchemaCommand(result.branchDatabase.schema.command)
59
+ }] : []
60
+ ])];
61
+ }
62
+ function formatBranchDatabaseSchemaCommand(command) {
63
+ switch (command) {
64
+ case "migrate-deploy": return "prisma migrate deploy";
65
+ case "db-push": return "prisma db push";
66
+ case "prisma-next-db-init": return "prisma-next db init";
67
+ }
68
+ }
44
69
  function formatDuration(durationMs) {
45
70
  if (durationMs < 1e3) return `${durationMs}ms`;
46
71
  return `${(durationMs / 1e3).toFixed(1)}s`;
@@ -1,111 +1,39 @@
1
- import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
1
+ import { formatColumns } from "../shell/ui.js";
2
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
2
3
  //#region src/presenters/branch.ts
3
4
  function renderBranchList(context, descriptor, result) {
4
- return renderList({
5
- title: "Listing branches for the resolved project.",
6
- descriptor,
7
- parentContext: {
8
- key: "project",
9
- value: result.projectName ?? "not resolved"
10
- },
11
- items: result.branches.map((branch) => ({
12
- noun: "branch",
13
- label: branch.name,
14
- id: branch.id,
15
- status: branch.active ? "active" : null
16
- })),
17
- emptyMessage: "No branches found."
18
- }, context.ui);
5
+ const ui = context.ui;
6
+ const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing branches for the resolved project.")}`, ""];
7
+ const rail = ui.dim("│");
8
+ lines.push(`${rail} ${ui.accent("project:")} ${result.projectName}`);
9
+ lines.push(rail);
10
+ if (result.branches.length === 0) {
11
+ lines.push(`${rail} ${ui.dim("No branches found.")}`);
12
+ return lines;
13
+ }
14
+ const widths = [
15
+ Math.max(4, ...result.branches.map((branch) => branch.name.length)),
16
+ Math.max(4, ...result.branches.map((branch) => branch.role.length)),
17
+ Math.max(7, ...result.branches.map((branch) => branch.envMap.length))
18
+ ];
19
+ lines.push(`${rail} ${ui.accent(formatColumns([
20
+ "Name",
21
+ "Role",
22
+ "Env map"
23
+ ], widths))}`);
24
+ for (const branch of result.branches) lines.push(`${rail} ${formatColumns([
25
+ branch.name,
26
+ branch.role,
27
+ branch.envMap
28
+ ], widths)}`);
29
+ return lines;
19
30
  }
20
31
  function serializeBranchList(result) {
21
- return serializeList({
22
- context: { project: result.projectName ?? "not resolved" },
23
- items: result.branches.map((branch) => ({
24
- noun: "branch",
25
- label: branch.name,
26
- id: branch.id,
27
- status: branch.active ? "active" : null
28
- }))
29
- });
30
- }
31
- function serializeBranchShow(result) {
32
32
  return {
33
33
  projectId: result.projectId,
34
34
  projectName: result.projectName,
35
- branch: {
36
- name: result.branch.name,
37
- kind: result.branch.kind,
38
- active: result.branch.active,
39
- remoteState: result.branch.remoteState,
40
- liveDeployment: result.branch.liveDeployment
41
- }
35
+ branches: result.branches
42
36
  };
43
37
  }
44
- function renderBranchShow(context, descriptor, result) {
45
- const fields = [
46
- {
47
- key: "project",
48
- value: result.projectName ?? "not resolved",
49
- tone: result.projectName ? "default" : "dim"
50
- },
51
- {
52
- key: "branch",
53
- value: result.branch.name,
54
- tone: result.branch.active ? "success" : "default"
55
- },
56
- {
57
- key: "kind",
58
- value: result.branch.kind
59
- }
60
- ];
61
- if (result.branch.liveDeployment) {
62
- fields.push({
63
- key: "status",
64
- value: result.branch.liveDeployment.status,
65
- tone: toneForDeploymentStatus(result.branch.liveDeployment.status)
66
- });
67
- if (result.branch.liveDeployment.url) fields.push({
68
- key: "url",
69
- value: result.branch.liveDeployment.url,
70
- tone: "link"
71
- });
72
- } else if (!result.branch.remoteState) fields.push({
73
- key: "remote state",
74
- value: "not created yet",
75
- tone: "dim"
76
- });
77
- return renderShow({
78
- title: "Showing the current active branch context.",
79
- descriptor,
80
- fields
81
- }, context.ui);
82
- }
83
- function toneForDeploymentStatus(status) {
84
- if (status === "ready" || status === "active" || status === "healthy") return "success";
85
- if (status === "pending" || status === "building" || status === "starting") return "warning";
86
- if (status === "failed" || status === "error") return "error";
87
- return "default";
88
- }
89
- function renderBranchUse(context, descriptor, result) {
90
- return renderMutate({
91
- title: "Changing the local default branch context.",
92
- descriptor,
93
- context: [{
94
- key: "project",
95
- value: result.projectName ?? "not resolved",
96
- tone: result.projectName ? "default" : "dim"
97
- }, {
98
- key: "branch",
99
- value: result.branch.name
100
- }],
101
- operationDescription: "Applying active branch change",
102
- operationCount: 1,
103
- details: ["Active branch updated in local CLI state for this repo."],
104
- alerts: result.branch.kind === "production" ? [{
105
- tone: "warning",
106
- text: "Production is protected and durable. Use with care"
107
- }] : void 0
108
- }, context.ui);
109
- }
110
38
  //#endregion
111
- export { renderBranchList, renderBranchShow, renderBranchUse, serializeBranchList, serializeBranchShow };
39
+ export { renderBranchList, serializeBranchList };
@@ -1,25 +1,24 @@
1
1
  import { padDisplay, renderNextSteps, renderSummaryLine } from "../shell/ui.js";
2
2
  import { formatDescriptorLabel } from "../shell/command-meta.js";
3
3
  import { formatCommandArgument } from "../shell/command-arguments.js";
4
- import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
4
+ import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
5
5
  import path from "node:path";
6
+ import stringWidth from "string-width";
6
7
  //#region src/presenters/project.ts
7
8
  function renderProjectList(context, descriptor, result) {
8
- const lines = renderList({
9
- title: "Listing projects for the authenticated workspace.",
10
- descriptor,
11
- parentContext: {
12
- key: "workspace",
13
- value: result.workspace.name
14
- },
15
- items: result.projects.map((project) => ({
16
- noun: "project",
17
- label: project.name,
18
- id: project.id,
19
- status: null
20
- })),
21
- emptyMessage: "No projects found."
22
- }, context.ui);
9
+ const ui = context.ui;
10
+ const rail = ui.dim("│");
11
+ const lines = [`${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing projects for the authenticated workspace.")}`, ""];
12
+ lines.push(`${rail} ${ui.accent("workspace:")} ${result.workspace.name}`);
13
+ if (result.projects.length === 0) {
14
+ lines.push(`${rail} ${ui.dim("No projects found.")}`);
15
+ if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", "Create a new Project: prisma-cli project create <name>"]));
16
+ return lines;
17
+ }
18
+ const nameWidth = Math.max(4, ...result.projects.map((project) => stringWidth(project.name)));
19
+ lines.push(rail);
20
+ lines.push(`${rail} ${ui.accent(padDisplay("name", nameWidth))} ${ui.accent("id")}`);
21
+ for (const project of result.projects) lines.push(`${rail} ${padDisplay(project.name, nameWidth)} ${project.id}`);
23
22
  if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", "Create a new Project: prisma-cli project create <name>"]));
24
23
  return lines;
25
24
  }
@@ -69,8 +69,8 @@ const DESCRIPTORS = [
69
69
  {
70
70
  id: "branch",
71
71
  path: ["prisma", "branch"],
72
- description: "View your active Platform branches",
73
- examples: ["prisma-cli branch list", "prisma-cli branch show"]
72
+ description: "View your Platform branches",
73
+ examples: ["prisma-cli branch list"]
74
74
  },
75
75
  {
76
76
  id: "git",
@@ -153,29 +153,9 @@ const DESCRIPTORS = [
153
153
  "branch",
154
154
  "list"
155
155
  ],
156
- description: "List active Platform branches for the resolved project",
156
+ description: "List Platform branches for the resolved project",
157
157
  examples: ["prisma-cli branch list", "prisma-cli branch list --json"]
158
158
  },
159
- {
160
- id: "branch.show",
161
- path: [
162
- "prisma",
163
- "branch",
164
- "show"
165
- ],
166
- description: "Show the Platform branch matching your current Git branch",
167
- examples: ["prisma-cli branch show", "prisma-cli branch show --json"]
168
- },
169
- {
170
- id: "branch.use",
171
- path: [
172
- "prisma",
173
- "branch",
174
- "use"
175
- ],
176
- description: "Change the local default branch context.",
177
- examples: ["prisma-cli branch use", "prisma-cli branch use production"]
178
- },
179
159
  {
180
160
  id: "app.build",
181
161
  path: [
@@ -214,8 +194,11 @@ const DESCRIPTORS = [
214
194
  "prisma-cli app deploy --project proj_123",
215
195
  "prisma-cli app deploy --create-project my-app --yes",
216
196
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
197
+ "prisma-cli app deploy --db",
198
+ "prisma-cli app deploy --db --yes",
217
199
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
218
200
  "prisma-cli app deploy --branch feat-login --framework hono",
201
+ "prisma-cli app deploy --prod --yes",
219
202
  "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
220
203
  "prisma-cli app deploy --framework bun --entry src/server.ts"
221
204
  ]
@@ -378,8 +361,9 @@ const DESCRIPTORS = [
378
361
  ],
379
362
  description: "Manage environment variables for the active project",
380
363
  examples: [
381
- "prisma-cli project env list --role production",
364
+ "prisma-cli project env list",
382
365
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
366
+ "prisma-cli project env add --file .env --role preview",
383
367
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
384
368
  "prisma-cli project env remove STRIPE_KEY --role preview"
385
369
  ]
@@ -396,7 +380,9 @@ const DESCRIPTORS = [
396
380
  examples: [
397
381
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
398
382
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role preview",
383
+ "prisma-cli project env add --file .env --role preview",
399
384
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
385
+ "prisma-cli project env add --file .env.local --branch feature/foo",
400
386
  "API_URL=https://api.example prisma-cli project env add API_URL --project proj_123 --role preview"
401
387
  ]
402
388
  },
@@ -412,6 +398,7 @@ const DESCRIPTORS = [
412
398
  examples: [
413
399
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role production",
414
400
  "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role preview",
401
+ "prisma-cli project env update --file .env --role production",
415
402
  "prisma-cli project env update DATABASE_URL=postgresql://branch --branch feature/foo"
416
403
  ]
417
404
  },
@@ -425,6 +412,7 @@ const DESCRIPTORS = [
425
412
  ],
426
413
  description: "List environment variable metadata for a scope (no values).",
427
414
  examples: [
415
+ "prisma-cli project env list",
428
416
  "prisma-cli project env list --role production",
429
417
  "prisma-cli project env list --role preview",
430
418
  "prisma-cli project env list --branch feature/foo"