@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.54.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.
@@ -24,20 +24,21 @@ function workspaceIdFromClaims(claims) {
24
24
  const id = sub.slice(10).trim();
25
25
  return id.length > 0 ? id : null;
26
26
  }
27
- async function performLogin(env) {
27
+ async function performLogin(env, signal) {
28
28
  await login({
29
- tokenStorage: new FileTokenStorage(env),
30
- env
29
+ tokenStorage: new FileTokenStorage(env, signal),
30
+ env,
31
+ signal
31
32
  });
32
33
  }
33
- async function readAuthState(env) {
34
+ async function readAuthState(env, signal) {
34
35
  const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
35
36
  if (rawServiceToken !== void 0) {
36
37
  const serviceToken = rawServiceToken.trim();
37
38
  if (serviceToken.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
38
- return readServiceTokenAuthState(serviceToken, env);
39
+ return readServiceTokenAuthState(serviceToken, env, signal);
39
40
  }
40
- const tokens = await new FileTokenStorage(env).getTokens();
41
+ const tokens = await new FileTokenStorage(env, signal).getTokens();
41
42
  if (!tokens) return {
42
43
  authenticated: false,
43
44
  provider: null,
@@ -45,20 +46,21 @@ async function readAuthState(env) {
45
46
  workspace: null,
46
47
  credential: null
47
48
  };
48
- const client = await requireComputeAuth(env);
49
- const currentPrincipal = await readCurrentPrincipalAuthState(client);
49
+ const client = await requireComputeAuth(env, signal);
50
+ const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
50
51
  if (currentPrincipal) return currentPrincipal;
51
52
  const claims = decodeJwtPayload(tokens.accessToken);
52
53
  return buildAuthState({
53
54
  workspaceIdFromCredential: tokens.workspaceId,
54
55
  claims,
55
56
  env,
56
- client
57
+ client,
58
+ signal
57
59
  });
58
60
  }
59
- async function readServiceTokenAuthState(token, env) {
60
- const client = await requireComputeAuth(env);
61
- const currentPrincipal = await readCurrentPrincipalAuthState(client);
61
+ async function readServiceTokenAuthState(token, env, signal) {
62
+ const client = await requireComputeAuth(env, signal);
63
+ const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
62
64
  if (currentPrincipal) return currentPrincipal;
63
65
  const claims = decodeJwtPayload(token);
64
66
  const workspaceId = workspaceIdFromClaims(claims);
@@ -73,15 +75,19 @@ async function readServiceTokenAuthState(token, env) {
73
75
  workspaceIdFromCredential: workspaceId,
74
76
  claims,
75
77
  env,
76
- client
78
+ client,
79
+ signal
77
80
  });
78
81
  }
79
- async function buildAuthState({ workspaceIdFromCredential, claims, env, client }) {
82
+ async function buildAuthState({ workspaceIdFromCredential, claims, env, client, signal }) {
80
83
  let workspaceId = workspaceIdFromCredential;
81
84
  let workspaceName = workspaceIdFromCredential;
82
- client ??= await requireComputeAuth(env);
85
+ client ??= await requireComputeAuth(env, signal);
83
86
  if (client) try {
84
- const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
87
+ const { data, response } = await client.GET("/v1/workspaces/{id}", {
88
+ params: { path: { id: workspaceIdFromCredential } },
89
+ signal
90
+ });
85
91
  if (response?.status === 401) return {
86
92
  authenticated: false,
87
93
  provider: null,
@@ -94,7 +100,9 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env, client }
94
100
  workspaceName = data.data.id;
95
101
  }
96
102
  if (data?.data?.name) workspaceName = data.data.name;
97
- } catch {}
103
+ } catch {
104
+ signal?.throwIfAborted();
105
+ }
98
106
  const email = emailFromClaims(claims);
99
107
  return {
100
108
  authenticated: true,
@@ -107,10 +115,10 @@ async function buildAuthState({ workspaceIdFromCredential, claims, env, client }
107
115
  credential: null
108
116
  };
109
117
  }
110
- async function readCurrentPrincipalAuthState(client) {
118
+ async function readCurrentPrincipalAuthState(client, signal) {
111
119
  if (!client) return null;
112
120
  try {
113
- const { data, response } = await client.GET("/v1/me");
121
+ const { data, response } = await client.GET("/v1/me", { signal });
114
122
  if (response?.status === 401) return {
115
123
  authenticated: false,
116
124
  provider: null,
@@ -133,11 +141,12 @@ async function readCurrentPrincipalAuthState(client) {
133
141
  credential: principal.credential
134
142
  };
135
143
  } catch {
144
+ signal?.throwIfAborted();
136
145
  return null;
137
146
  }
138
147
  }
139
- async function performLogout(env) {
140
- await new FileTokenStorage(env).clearTokens();
148
+ async function performLogout(env, signal) {
149
+ await new FileTokenStorage(env, signal).clearTokens();
141
150
  }
142
151
  //#endregion
143
152
  export { performLogin, performLogout, readAuthState };
@@ -11,7 +11,8 @@ import { createManagementApiClient, createManagementApiSdk } from "@prisma/manag
11
11
  *
12
12
  * Returns null if not authenticated.
13
13
  */
14
- async function requireComputeAuth(env = process.env) {
14
+ async function requireComputeAuth(env = process.env, signal) {
15
+ signal?.throwIfAborted();
15
16
  const rawToken = env[SERVICE_TOKEN_ENV_VAR];
16
17
  if (rawToken !== void 0) {
17
18
  const token = rawToken.trim();
@@ -21,7 +22,7 @@ async function requireComputeAuth(env = process.env) {
21
22
  token
22
23
  });
23
24
  }
24
- const tokenStorage = new FileTokenStorage(env);
25
+ const tokenStorage = new FileTokenStorage(env, signal);
25
26
  if (!await tokenStorage.getTokens()) return null;
26
27
  return createManagementApiSdk({
27
28
  clientId: CLIENT_ID,
@@ -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,
@@ -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.54.1",
4
4
  "description": "Command-line interface for the Prisma Developer Platform.",
5
5
  "type": "module",
6
6
  "bin": {