@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.4

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.
Files changed (38) hide show
  1. package/README.md +4 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +11 -3
  4. package/dist/adapters/mock-api.js +6 -2
  5. package/dist/adapters/token-storage.js +63 -22
  6. package/dist/cli.js +17 -2
  7. package/dist/cli2.js +3 -0
  8. package/dist/commands/app/index.js +4 -2
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/controllers/app-env.js +227 -64
  11. package/dist/controllers/app.js +116 -91
  12. package/dist/controllers/auth.js +8 -8
  13. package/dist/controllers/branch.js +73 -47
  14. package/dist/controllers/project.js +107 -67
  15. package/dist/lib/app/bun-project.js +12 -5
  16. package/dist/lib/app/local-dev.js +34 -18
  17. package/dist/lib/app/preview-build.js +90 -62
  18. package/dist/lib/app/preview-provider.js +143 -54
  19. package/dist/lib/app/production-deploy-gate.js +160 -0
  20. package/dist/lib/auth/auth-ops.js +30 -21
  21. package/dist/lib/auth/guard.js +3 -2
  22. package/dist/lib/auth/login.js +109 -14
  23. package/dist/lib/git/local-branch.js +41 -0
  24. package/dist/lib/project/interactive-setup.js +1 -1
  25. package/dist/lib/project/local-pin.js +27 -8
  26. package/dist/lib/project/resolution.js +14 -8
  27. package/dist/lib/project/setup.js +2 -2
  28. package/dist/presenters/app-env.js +14 -3
  29. package/dist/presenters/branch.js +29 -101
  30. package/dist/shell/command-meta.js +6 -24
  31. package/dist/shell/command-runner.js +9 -4
  32. package/dist/shell/errors.js +12 -1
  33. package/dist/shell/runtime.js +2 -2
  34. package/dist/shell/ui.js +4 -1
  35. package/dist/shell/update-check.js +247 -0
  36. package/dist/use-cases/branch.js +20 -68
  37. package/dist/use-cases/create-cli-gateways.js +2 -17
  38. package/package.json +10 -10
@@ -4,6 +4,7 @@ import open from "open";
4
4
  import { AuthError, createManagementApiSdk } from "@prisma/management-api-sdk";
5
5
  import events from "node:events";
6
6
  import http from "node:http";
7
+ import readline from "node:readline/promises";
7
8
  //#region src/lib/auth/login.ts
8
9
  var AuthError$1 = class extends Error {
9
10
  constructor(message) {
@@ -14,11 +15,15 @@ var AuthError$1 = class extends Error {
14
15
  async function login(options = {}) {
15
16
  const hostname = options.hostname ?? "localhost";
16
17
  const port = options.port ?? 0;
18
+ const input = options.input ?? process.stdin;
19
+ const output = options.output ?? process.stderr;
20
+ const interactive = input.isTTY === true;
17
21
  const server = http.createServer();
18
22
  server.listen({
19
23
  host: hostname,
20
24
  port
21
25
  });
26
+ const pasteAbort = new AbortController();
22
27
  try {
23
28
  const state = new LoginState({
24
29
  hostname,
@@ -28,9 +33,30 @@ async function login(options = {}) {
28
33
  apiBaseUrl: options.apiBaseUrl,
29
34
  authBaseUrl: options.authBaseUrl,
30
35
  openUrl: options.openUrl,
31
- env: options.env
36
+ env: options.env,
37
+ signal: options.signal,
38
+ output
32
39
  });
33
- const authResult = new Promise((resolve, reject) => {
40
+ let completed = false;
41
+ let completion;
42
+ const completeOnce = (url) => {
43
+ if (!completion) completion = state.handleCallback(url).then(() => {
44
+ completed = true;
45
+ }, (error) => {
46
+ completion = void 0;
47
+ throw error;
48
+ });
49
+ return completion;
50
+ };
51
+ const httpResult = new Promise((resolve, reject) => {
52
+ const onAbort = () => {
53
+ reject(options.signal?.reason);
54
+ };
55
+ options.signal?.addEventListener("abort", onAbort, { once: true });
56
+ const settle = (callback) => {
57
+ options.signal?.removeEventListener("abort", onAbort);
58
+ callback();
59
+ };
34
60
  server.on("request", async (req, res) => {
35
61
  const url = new URL(`http://${state.host}${req.url}`);
36
62
  if (url.pathname !== "/auth/callback") {
@@ -38,36 +64,87 @@ async function login(options = {}) {
38
64
  res.end("Not found");
39
65
  return;
40
66
  }
67
+ if (completed) {
68
+ const workspaceName = await state.resolveWorkspaceName();
69
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
70
+ res.end(renderSuccessPage(workspaceName));
71
+ return;
72
+ }
41
73
  try {
42
- await state.handleCallback(url);
74
+ await completeOnce(url);
75
+ const workspaceName = await state.resolveWorkspaceName();
76
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
77
+ res.end(renderSuccessPage(workspaceName));
78
+ settle(resolve);
43
79
  } catch (error) {
44
80
  res.statusCode = 400;
45
81
  const message = error instanceof Error ? error.message : String(error);
46
82
  res.end(message);
47
- reject(error);
83
+ settle(() => reject(error));
48
84
  return;
49
85
  }
50
- const workspaceName = await state.resolveWorkspaceName();
51
- res.setHeader("Content-Type", "text/html; charset=utf-8");
52
- res.end(renderSuccessPage(workspaceName));
53
- resolve();
54
86
  });
55
87
  });
56
- await state.openLoginPage();
57
- await authResult;
88
+ options.signal?.throwIfAborted();
89
+ const callbackResult = interactive ? Promise.race([httpResult, consumePastedCallback({
90
+ input,
91
+ output,
92
+ signal: pasteAbort.signal,
93
+ complete: completeOnce
94
+ })]) : httpResult;
95
+ await Promise.all([state.openLoginPage(interactive), callbackResult]);
58
96
  } finally {
97
+ pasteAbort.abort();
59
98
  if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
60
99
  }
61
100
  }
101
+ async function consumePastedCallback(options) {
102
+ if (!options.input.isTTY) return;
103
+ const rl = readline.createInterface({
104
+ input: options.input,
105
+ output: options.output
106
+ });
107
+ try {
108
+ for (;;) {
109
+ let answer;
110
+ try {
111
+ answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
112
+ } catch (error) {
113
+ if (error?.name === "AbortError") return;
114
+ throw error;
115
+ }
116
+ const trimmed = answer.trim().replace(/^["']|["']$/g, "");
117
+ let url;
118
+ try {
119
+ if (!trimmed) throw new Error("empty input");
120
+ url = new URL(trimmed);
121
+ } catch {
122
+ options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
123
+ continue;
124
+ }
125
+ try {
126
+ await options.complete(url);
127
+ return;
128
+ } catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
131
+ continue;
132
+ }
133
+ }
134
+ } finally {
135
+ rl.close();
136
+ }
137
+ }
62
138
  var LoginState = class {
63
139
  latestVerifier;
64
140
  latestState;
65
141
  sdk;
66
142
  openUrl;
67
143
  tokenStorage;
144
+ output;
68
145
  constructor(options) {
69
146
  this.options = options;
70
- this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
147
+ this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal);
71
148
  this.sdk = createManagementApiSdk({
72
149
  clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
73
150
  redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
@@ -76,8 +153,10 @@ var LoginState = class {
76
153
  authBaseUrl: options.authBaseUrl
77
154
  });
78
155
  this.openUrl = options.openUrl ?? open;
156
+ this.output = options.output;
79
157
  }
80
- async openLoginPage() {
158
+ async openLoginPage(interactive) {
159
+ this.options.signal?.throwIfAborted();
81
160
  const { url, state, verifier } = await this.sdk.getLoginUrl({
82
161
  scope: "workspace:admin offline_access",
83
162
  additionalParams: {
@@ -88,7 +167,19 @@ var LoginState = class {
88
167
  });
89
168
  this.latestState = state;
90
169
  this.latestVerifier = verifier;
91
- await this.openUrl(url);
170
+ this.options.signal?.throwIfAborted();
171
+ if (interactive) this.printLoginInstructions(url);
172
+ try {
173
+ await this.openUrl(url);
174
+ } catch (error) {
175
+ if (!interactive) throw error;
176
+ }
177
+ this.options.signal?.throwIfAborted();
178
+ }
179
+ printLoginInstructions(url) {
180
+ const output = this.output;
181
+ if (!output) return;
182
+ output.write(`\nOpen this URL to sign in: ${url}\n\nIf the browser opens on another machine, finish sign-in there. When it\nredirects to localhost, copy the full localhost URL from the address bar\nand paste it here.\n\n`);
92
183
  }
93
184
  async handleCallback(url) {
94
185
  if (url.pathname !== "/auth/callback") throw new AuthError$1("Not a callback URL");
@@ -115,10 +206,14 @@ var LoginState = class {
115
206
  try {
116
207
  const tokens = await this.tokenStorage.getTokens();
117
208
  if (!tokens?.workspaceId) return null;
118
- const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
209
+ const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", {
210
+ params: { path: { id: tokens.workspaceId } },
211
+ signal: this.options.signal
212
+ });
119
213
  const name = data?.data?.name;
120
214
  return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
121
215
  } catch {
216
+ this.options.signal?.throwIfAborted();
122
217
  return null;
123
218
  }
124
219
  }
@@ -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 };
@@ -36,7 +36,7 @@ async function promptForProjectSetupChoice(options) {
36
36
  targetName: choice.project.name,
37
37
  targetNameSource: "prompt"
38
38
  };
39
- const suggestedName = await inferTargetName(options.context.runtime.cwd);
39
+ const suggestedName = await inferTargetName(options.context.runtime.cwd, options.context.runtime.signal);
40
40
  const rawName = await textPrompt({
41
41
  input: options.context.runtime.stdin,
42
42
  output: options.context.runtime.stderr,
@@ -2,9 +2,13 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/lib/project/local-pin.ts
4
4
  const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
5
- async function readLocalResolutionPin(cwd) {
5
+ async function readLocalResolutionPin(cwd, signal) {
6
+ signal?.throwIfAborted();
6
7
  try {
7
- const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), "utf8");
8
+ const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
9
+ encoding: "utf8",
10
+ signal
11
+ });
8
12
  const parsed = JSON.parse(raw);
9
13
  if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
10
14
  return {
@@ -17,28 +21,43 @@ async function readLocalResolutionPin(cwd) {
17
21
  throw error;
18
22
  }
19
23
  }
20
- async function writeLocalResolutionPin(cwd, pin) {
24
+ async function writeLocalResolutionPin(cwd, pin, signal) {
21
25
  const prismaDir = path.join(cwd, ".prisma");
26
+ signal?.throwIfAborted();
22
27
  await mkdir(prismaDir, { recursive: true });
23
28
  const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
24
29
  const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
25
- await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, "utf8");
30
+ await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, {
31
+ encoding: "utf8",
32
+ signal
33
+ });
34
+ signal?.throwIfAborted();
26
35
  await rename(tmpPath, pinPath);
27
36
  }
28
- async function ensureLocalResolutionPinGitignore(cwd) {
37
+ async function ensureLocalResolutionPinGitignore(cwd, signal) {
29
38
  const gitignorePath = path.join(cwd, ".gitignore");
30
39
  let existing = null;
40
+ signal?.throwIfAborted();
31
41
  try {
32
- existing = await readFile(gitignorePath, "utf8");
42
+ existing = await readFile(gitignorePath, {
43
+ encoding: "utf8",
44
+ signal
45
+ });
33
46
  } catch (error) {
34
47
  if (error.code !== "ENOENT") throw error;
35
48
  }
36
49
  if (existing === null) {
37
- await writeFile(gitignorePath, ".prisma/\n", "utf8");
50
+ await writeFile(gitignorePath, ".prisma/\n", {
51
+ encoding: "utf8",
52
+ signal
53
+ });
38
54
  return;
39
55
  }
40
56
  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");
57
+ await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
58
+ encoding: "utf8",
59
+ signal
60
+ });
42
61
  }
43
62
  function isLocalResolutionPin(value) {
44
63
  if (!value || typeof value !== "object") return false;
@@ -11,7 +11,8 @@ async function resolveProjectTarget(options) {
11
11
  throw await projectSetupRequiredError({
12
12
  cwd: options.context.runtime.cwd,
13
13
  projects,
14
- commandName: options.commandName
14
+ commandName: options.commandName,
15
+ signal: options.context.runtime.signal
15
16
  });
16
17
  }
17
18
  async function inspectProjectBinding(options) {
@@ -26,7 +27,8 @@ async function inspectProjectBinding(options) {
26
27
  ...await buildProjectSetupSuggestion({
27
28
  cwd: options.context.runtime.cwd,
28
29
  projects,
29
- commandName: options.commandName ?? "project show"
30
+ commandName: options.commandName ?? "project show",
31
+ signal: options.context.runtime.signal
30
32
  })
31
33
  };
32
34
  }
@@ -72,7 +74,7 @@ function localStateStaleError() {
72
74
  });
73
75
  }
74
76
  async function buildProjectSetupSuggestion(options) {
75
- const suggestedName = await inferTargetName(options.cwd);
77
+ const suggestedName = await inferTargetName(options.cwd, options.signal);
76
78
  const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
77
79
  return {
78
80
  suggestedProjectName: suggestedName.name,
@@ -131,9 +133,13 @@ function buildProjectSetupNextActions(options = {}) {
131
133
  });
132
134
  return actions;
133
135
  }
134
- async function readPackageName(cwd) {
136
+ async function readPackageName(cwd, signal) {
137
+ signal?.throwIfAborted();
135
138
  try {
136
- const raw = await readFile(path.join(cwd, "package.json"), "utf8");
139
+ const raw = await readFile(path.join(cwd, "package.json"), {
140
+ encoding: "utf8",
141
+ signal
142
+ });
137
143
  const parsed = JSON.parse(raw);
138
144
  if (!parsed || typeof parsed !== "object") return null;
139
145
  const packageName = "name" in parsed ? parsed.name : null;
@@ -144,8 +150,8 @@ async function readPackageName(cwd) {
144
150
  throw error;
145
151
  }
146
152
  }
147
- async function inferTargetName(cwd) {
148
- const packageName = await readPackageName(cwd);
153
+ async function inferTargetName(cwd, signal) {
154
+ const packageName = await readPackageName(cwd, signal);
149
155
  if (packageName && isValidInferredTargetName(packageName)) return {
150
156
  name: packageName,
151
157
  source: "package-name"
@@ -186,7 +192,7 @@ async function resolveBoundProjectTarget(options, projects, settings) {
186
192
  targetNameSource: "env"
187
193
  });
188
194
  }
189
- const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
195
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
190
196
  if (localPin.kind === "invalid") throw localStateStaleError();
191
197
  if (localPin.kind === "present") {
192
198
  if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
@@ -20,8 +20,8 @@ async function bindProjectToDirectory(context, workspace, project, action) {
20
20
  await writeLocalResolutionPin(context.runtime.cwd, {
21
21
  workspaceId: workspace.id,
22
22
  projectId: project.id
23
- });
24
- await ensureLocalResolutionPinGitignore(context.runtime.cwd);
23
+ }, context.runtime.signal);
24
+ await ensureLocalResolutionPinGitignore(context.runtime.cwd, context.runtime.signal);
25
25
  return {
26
26
  workspace,
27
27
  project,
@@ -2,8 +2,18 @@ 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) {
8
18
  return renderShow({
9
19
  title: "Setting a new environment variable.",
@@ -75,8 +85,8 @@ function renderEnvList(context, descriptor, result) {
75
85
  title: "Listing environment variables for the selected scope.",
76
86
  descriptor,
77
87
  parentContext: {
78
- key: "scope",
79
- value: scopeLabel(result.scope)
88
+ key: "target",
89
+ value: listTargetLabel(result)
80
90
  },
81
91
  items: result.variables.map((variable) => ({
82
92
  noun: "variable",
@@ -91,8 +101,9 @@ function serializeEnvList(result) {
91
101
  return {
92
102
  projectId: result.projectId,
93
103
  scope: result.scope,
104
+ target: result.target,
94
105
  ...serializeList({
95
- context: { scope: scopeLabel(result.scope) },
106
+ context: { target: listTargetLabel(result) },
96
107
  items: result.variables.map((variable) => ({
97
108
  noun: "variable",
98
109
  label: `${variable.key} (${variable.source})`,
@@ -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 };
@@ -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: [
@@ -216,6 +196,7 @@ const DESCRIPTORS = [
216
196
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
217
197
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
218
198
  "prisma-cli app deploy --branch feat-login --framework hono",
199
+ "prisma-cli app deploy --prod --yes",
219
200
  "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
220
201
  "prisma-cli app deploy --framework bun --entry src/server.ts"
221
202
  ]
@@ -378,7 +359,7 @@ const DESCRIPTORS = [
378
359
  ],
379
360
  description: "Manage environment variables for the active project",
380
361
  examples: [
381
- "prisma-cli project env list --role production",
362
+ "prisma-cli project env list",
382
363
  "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
383
364
  "prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
384
365
  "prisma-cli project env remove STRIPE_KEY --role preview"
@@ -425,6 +406,7 @@ const DESCRIPTORS = [
425
406
  ],
426
407
  description: "List environment variable metadata for a scope (no values).",
427
408
  examples: [
409
+ "prisma-cli project env list",
428
410
  "prisma-cli project env list --role production",
429
411
  "prisma-cli project env list --role preview",
430
412
  "prisma-cli project env list --branch feature/foo"
@@ -1,15 +1,20 @@
1
- import { CliError, authRequiredError } from "./errors.js";
1
+ import { CliError, authRequiredError, commandCanceledError } from "./errors.js";
2
2
  import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
3
3
  import { getCommandDescriptor } from "./command-meta.js";
4
4
  import { resolveGlobalFlags } from "./global-flags.js";
5
5
  import { createCommandContext } from "./runtime.js";
6
6
  import { AuthError } from "@prisma/management-api-sdk";
7
7
  //#region src/shell/command-runner.ts
8
- function toCliError(error) {
8
+ function toCliError(error, runtime) {
9
+ if (isCancellationError(error) || runtime.signal.aborted) return commandCanceledError();
9
10
  if (error instanceof CliError) return error;
10
11
  if (error instanceof AuthError) return authRequiredError(["prisma-cli auth login"], { debug: error.message });
11
12
  return null;
12
13
  }
14
+ function isCancellationError(error) {
15
+ if (!(error instanceof Error)) return false;
16
+ return error.name === "AbortError" || error.name === "CancelledError";
17
+ }
13
18
  async function runCommand(runtime, commandName, options, handler, presenter) {
14
19
  const flags = resolveGlobalFlags(runtime.argv, options);
15
20
  const context = await createCommandContext(runtime, flags);
@@ -26,7 +31,7 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
26
31
  if (flags.quiet) return;
27
32
  writeHumanLines(context.output, presenter.renderHuman(context, descriptor, success.result));
28
33
  } catch (error) {
29
- const cliError = toCliError(error);
34
+ const cliError = toCliError(error, runtime);
30
35
  if (cliError) {
31
36
  if (flags.json) writeJsonError(context.output, commandName, cliError);
32
37
  else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
@@ -51,7 +56,7 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
51
56
  nextActions: []
52
57
  });
53
58
  } catch (error) {
54
- const cliError = toCliError(error);
59
+ const cliError = toCliError(error, runtime);
55
60
  if (cliError) {
56
61
  if (flags.json) writeJsonEvent(context.output, {
57
62
  type: "error",