@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,6 +34,7 @@ async function login(options = {}) {
34
34
  authBaseUrl: options.authBaseUrl,
35
35
  openUrl: options.openUrl,
36
36
  env: options.env,
37
+ signal: options.signal,
37
38
  output
38
39
  });
39
40
  let completed = false;
@@ -48,6 +49,14 @@ async function login(options = {}) {
48
49
  return completion;
49
50
  };
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
+ };
51
60
  server.on("request", async (req, res) => {
52
61
  const url = new URL(`http://${state.host}${req.url}`);
53
62
  if (url.pathname !== "/auth/callback") {
@@ -63,29 +72,27 @@ async function login(options = {}) {
63
72
  }
64
73
  try {
65
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);
66
79
  } catch (error) {
67
80
  res.statusCode = 400;
68
81
  const message = error instanceof Error ? error.message : String(error);
69
82
  res.end(message);
70
- reject(error);
83
+ settle(() => reject(error));
71
84
  return;
72
85
  }
73
- const workspaceName = await state.resolveWorkspaceName();
74
- res.setHeader("Content-Type", "text/html; charset=utf-8");
75
- res.end(renderSuccessPage(workspaceName));
76
- resolve();
77
86
  });
78
87
  });
79
- await state.openLoginPage(interactive);
80
- if (interactive) {
81
- const pasteResult = consumePastedCallback({
82
- input,
83
- output,
84
- signal: pasteAbort.signal,
85
- complete: completeOnce
86
- });
87
- await Promise.race([httpResult, pasteResult]);
88
- } else await httpResult;
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]);
89
96
  } finally {
90
97
  pasteAbort.abort();
91
98
  if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
@@ -137,7 +144,7 @@ var LoginState = class {
137
144
  output;
138
145
  constructor(options) {
139
146
  this.options = options;
140
- this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
147
+ this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal);
141
148
  this.sdk = createManagementApiSdk({
142
149
  clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
143
150
  redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
@@ -149,6 +156,7 @@ var LoginState = class {
149
156
  this.output = options.output;
150
157
  }
151
158
  async openLoginPage(interactive) {
159
+ this.options.signal?.throwIfAborted();
152
160
  const { url, state, verifier } = await this.sdk.getLoginUrl({
153
161
  scope: "workspace:admin offline_access",
154
162
  additionalParams: {
@@ -159,12 +167,14 @@ var LoginState = class {
159
167
  });
160
168
  this.latestState = state;
161
169
  this.latestVerifier = verifier;
170
+ this.options.signal?.throwIfAborted();
162
171
  if (interactive) this.printLoginInstructions(url);
163
172
  try {
164
173
  await this.openUrl(url);
165
174
  } catch (error) {
166
175
  if (!interactive) throw error;
167
176
  }
177
+ this.options.signal?.throwIfAborted();
168
178
  }
169
179
  printLoginInstructions(url) {
170
180
  const output = this.output;
@@ -196,10 +206,14 @@ var LoginState = class {
196
206
  try {
197
207
  const tokens = await this.tokenStorage.getTokens();
198
208
  if (!tokens?.workspaceId) return null;
199
- 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
+ });
200
213
  const name = data?.data?.name;
201
214
  return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
202
215
  } catch {
216
+ this.options.signal?.throwIfAborted();
203
217
  return null;
204
218
  }
205
219
  }
@@ -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,
@@ -216,6 +216,7 @@ const DESCRIPTORS = [
216
216
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
217
217
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
218
218
  "prisma-cli app deploy --branch feat-login --framework hono",
219
+ "prisma-cli app deploy --prod --yes",
219
220
  "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
220
221
  "prisma-cli app deploy --framework bun --entry src/server.ts"
221
222
  ]
@@ -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",
@@ -56,6 +56,17 @@ function authRequiredError(nextSteps = ["prisma-cli auth login"], options = {})
56
56
  nextSteps
57
57
  });
58
58
  }
59
+ function commandCanceledError() {
60
+ return new CliError({
61
+ code: "COMMAND_CANCELED",
62
+ domain: "cli",
63
+ summary: "Command canceled",
64
+ why: null,
65
+ fix: null,
66
+ exitCode: 130,
67
+ humanLines: ["Command canceled [COMMAND_CANCELED]"]
68
+ });
69
+ }
59
70
  function workspaceRequiredError() {
60
71
  return usageError("Workspace required", "This command needs an active workspace, but the authenticated session does not have one.", "Run prisma-cli auth login and choose a workspace.", ["prisma-cli auth login"], "auth");
61
72
  }
@@ -71,4 +82,4 @@ function featureUnavailableError(summary, why, fix, nextSteps = [], domain = "cl
71
82
  });
72
83
  }
73
84
  //#endregion
74
- export { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError };
85
+ export { CliError, authRequiredError, commandCanceledError, featureUnavailableError, usageError, workspaceRequiredError };
@@ -22,13 +22,13 @@ async function createCommandContext(runtime, flags) {
22
22
  const fixturePath = runtime.fixturePath ?? runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
23
23
  const stateDir = resolveStateDir(runtime);
24
24
  let loadedApi;
25
- if (fixturePath) loadedApi = await MockApi.load(fixturePath);
25
+ if (fixturePath) loadedApi = await MockApi.load(fixturePath, runtime.signal);
26
26
  return {
27
27
  get api() {
28
28
  if (!loadedApi) throw new Error("context.api accessed in real mode. Set runtime.fixturePath or PRISMA_CLI_MOCK_FIXTURE_PATH to use fixture mode.");
29
29
  return loadedApi;
30
30
  },
31
- stateStore: new LocalStateStore(stateDir),
31
+ stateStore: new LocalStateStore(stateDir, runtime.signal),
32
32
  output: {
33
33
  stdout: runtime.stdout,
34
34
  stderr: runtime.stderr
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/cli",
3
- "version": "3.0.0-dev.53.1",
3
+ "version": "3.0.0-dev.56.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {