@prisma/cli 3.0.0-beta.8 → 3.0.0-dev.101.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.
Files changed (56) hide show
  1. package/README.md +0 -13
  2. package/dist/adapters/mock-api.js +75 -0
  3. package/dist/adapters/token-storage.js +311 -33
  4. package/dist/cli.js +7 -7
  5. package/dist/cli2.js +5 -3
  6. package/dist/commands/app/index.js +65 -52
  7. package/dist/commands/auth/index.js +55 -2
  8. package/dist/commands/database/index.js +159 -0
  9. package/dist/controllers/app-env.js +10 -7
  10. package/dist/controllers/app.js +550 -208
  11. package/dist/controllers/auth.js +211 -2
  12. package/dist/controllers/branch.js +5 -6
  13. package/dist/controllers/database.js +318 -0
  14. package/dist/controllers/project.js +49 -20
  15. package/dist/lib/app/app-interaction.js +5 -0
  16. package/dist/lib/app/{preview-provider.js → app-provider.js} +32 -28
  17. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  18. package/dist/lib/app/branch-database-deploy.js +13 -60
  19. package/dist/lib/app/branch-database.js +1 -102
  20. package/dist/lib/app/build-settings.js +93 -0
  21. package/dist/lib/app/build.js +82 -0
  22. package/dist/lib/app/bun-project.js +2 -3
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-plan.js +58 -0
  25. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +5 -5
  26. package/dist/lib/app/local-dev.js +3 -60
  27. package/dist/lib/app/production-deploy-gate.js +3 -3
  28. package/dist/lib/app/read-branch.js +30 -0
  29. package/dist/lib/auth/auth-ops.js +10 -4
  30. package/dist/lib/auth/guard.js +4 -1
  31. package/dist/lib/auth/login.js +32 -25
  32. package/dist/lib/database/provider.js +167 -0
  33. package/dist/lib/diagnostics.js +1 -1
  34. package/dist/lib/fs/home-path.js +24 -0
  35. package/dist/lib/git/local-branch.js +15 -3
  36. package/dist/lib/project/local-pin.js +170 -40
  37. package/dist/lib/project/resolution.js +166 -61
  38. package/dist/lib/project/setup.js +65 -19
  39. package/dist/output/patterns.js +1 -1
  40. package/dist/presenters/app.js +44 -39
  41. package/dist/presenters/auth.js +98 -1
  42. package/dist/presenters/branch.js +1 -1
  43. package/dist/presenters/database.js +274 -0
  44. package/dist/presenters/project.js +3 -9
  45. package/dist/shell/command-meta.js +153 -2
  46. package/dist/shell/command-runner.js +39 -21
  47. package/dist/shell/diagnostics-output.js +2 -8
  48. package/dist/shell/errors.js +50 -1
  49. package/dist/shell/help.js +30 -20
  50. package/dist/shell/runtime.js +8 -4
  51. package/dist/shell/ui.js +19 -2
  52. package/dist/use-cases/auth.js +68 -1
  53. package/package.json +18 -3
  54. package/dist/lib/app/preview-build-settings.js +0 -385
  55. package/dist/lib/app/preview-build.js +0 -475
  56. package/dist/lib/app/preview-interaction.js +0 -5
package/README.md CHANGED
@@ -98,19 +98,6 @@ npx prisma-cli app promote <deployment-id>
98
98
  - Stable command groups, flags, and error codes for scripts and agents.
99
99
  - Environment variable values are not printed back to the terminal.
100
100
 
101
- ### Agent skills
102
-
103
- For agent-guided Next.js deploys, install the Prisma CLI skill cluster at the
104
- project level. Match the skill ref to the installed CLI version:
105
-
106
- ```bash
107
- npx prisma-cli version
108
- pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all
109
- ```
110
-
111
- The skills teach agents how to prepare a Next.js app, run `app deploy`, verify
112
- the result, and route CLI / Compute feedback to the right Prisma channel.
113
-
114
101
  ---
115
102
 
116
103
  ## Beta notes
@@ -56,6 +56,81 @@ var MockApi = class MockApi {
56
56
  getDeployment(deploymentId) {
57
57
  return this.data.deployments.find((deployment) => deployment.id === deploymentId);
58
58
  }
59
+ listDatabasesForProject(projectId, branchName) {
60
+ return (this.data.databases ?? []).filter((database) => database.projectId === projectId && (!branchName || database.branchName === branchName));
61
+ }
62
+ getDatabase(databaseId) {
63
+ return (this.data.databases ?? []).find((database) => database.id === databaseId);
64
+ }
65
+ createDatabase(input) {
66
+ this.data.databases ??= [];
67
+ this.data.databaseConnections ??= [];
68
+ const database = {
69
+ id: `db_${this.data.databases.length + 1e3}`,
70
+ projectId: input.projectId,
71
+ branchId: input.branchName ? this.getBranchForProject(input.projectId, input.branchName)?.id ?? null : null,
72
+ branchName: input.branchName ?? null,
73
+ name: input.name,
74
+ region: input.region ?? null,
75
+ status: "ready",
76
+ isDefault: false,
77
+ createdAt: "2026-06-09T00:00:00.000Z"
78
+ };
79
+ const connectionString = `postgresql://${database.id}.example.prisma.io/postgres`;
80
+ const connection = {
81
+ id: `conn_${this.data.databaseConnections.length + 1e3}`,
82
+ databaseId: database.id,
83
+ name: "primary",
84
+ createdAt: "2026-06-09T00:00:00.000Z",
85
+ connectionString
86
+ };
87
+ this.data.databases.push(database);
88
+ this.data.databaseConnections.push(connection);
89
+ return {
90
+ database,
91
+ connection,
92
+ connectionString
93
+ };
94
+ }
95
+ removeDatabase(databaseId) {
96
+ this.data.databases ??= [];
97
+ this.data.databaseConnections ??= [];
98
+ const database = this.getDatabase(databaseId);
99
+ if (!database) return;
100
+ this.data.databases = this.data.databases.filter((candidate) => candidate.id !== databaseId);
101
+ this.data.databaseConnections = this.data.databaseConnections.filter((connection) => connection.databaseId !== databaseId);
102
+ return database;
103
+ }
104
+ listDatabaseConnections(databaseId) {
105
+ return (this.data.databaseConnections ?? []).filter((connection) => connection.databaseId === databaseId);
106
+ }
107
+ getDatabaseConnection(connectionId) {
108
+ return (this.data.databaseConnections ?? []).find((connection) => connection.id === connectionId);
109
+ }
110
+ createDatabaseConnection(input) {
111
+ if (!this.getDatabase(input.databaseId)) return;
112
+ this.data.databaseConnections ??= [];
113
+ const connectionString = `postgresql://${input.databaseId}-${this.data.databaseConnections.length + 1}.example.prisma.io/postgres`;
114
+ const connection = {
115
+ id: `conn_${this.data.databaseConnections.length + 1e3}`,
116
+ databaseId: input.databaseId,
117
+ name: input.name,
118
+ createdAt: "2026-06-09T00:00:00.000Z",
119
+ connectionString
120
+ };
121
+ this.data.databaseConnections.push(connection);
122
+ return {
123
+ connection,
124
+ connectionString
125
+ };
126
+ }
127
+ removeDatabaseConnection(connectionId) {
128
+ this.data.databaseConnections ??= [];
129
+ const connection = this.getDatabaseConnection(connectionId);
130
+ if (!connection) return;
131
+ this.data.databaseConnections = this.data.databaseConnections.filter((candidate) => candidate.id !== connectionId);
132
+ return connection;
133
+ }
59
134
  };
60
135
  //#endregion
61
136
  export { MockApi };
@@ -1,9 +1,22 @@
1
1
  import { getAuthFilePath } from "../lib/auth/client.js";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { CredentialsStore } from "@prisma/credentials-store";
5
4
  import { randomUUID } from "node:crypto";
5
+ import { CredentialsStore } from "@prisma/credentials-store";
6
6
  //#region src/adapters/token-storage.ts
7
+ const REFRESH_LOCK_RETRY_MS = 100;
8
+ const REFRESH_LOCK_STALE_MS = 3e4;
9
+ const REFRESH_LOCK_WAIT_TIMEOUT_MS = 25e3;
10
+ const EMPTY_AUTH_CONTEXT = {
11
+ activeWorkspaceId: null,
12
+ workspaces: {}
13
+ };
14
+ const UNKNOWN_WORKSPACE_NAME = "Unknown workspace";
15
+ function getAuthContextFilePath(authFilePath) {
16
+ const extension = path.extname(authFilePath);
17
+ if (!extension) return `${authFilePath}.context.json`;
18
+ return `${authFilePath.slice(0, -extension.length)}.context${extension}`;
19
+ }
7
20
  function findLatestValidTokens(allCredentials) {
8
21
  for (let i = allCredentials.length - 1; i >= 0; i -= 1) {
9
22
  const credential = allCredentials[i];
@@ -17,6 +30,18 @@ function findLatestValidTokens(allCredentials) {
17
30
  }
18
31
  return null;
19
32
  }
33
+ function storedCredentialToTokens(credential) {
34
+ if (!credential) return null;
35
+ if (typeof credential.workspaceId !== "string" || credential.workspaceId.length === 0 || typeof credential.token !== "string" || credential.token.length === 0 || typeof credential.refreshToken !== "string" || credential.refreshToken.length === 0) return null;
36
+ return {
37
+ workspaceId: credential.workspaceId,
38
+ accessToken: credential.token,
39
+ refreshToken: credential.refreshToken
40
+ };
41
+ }
42
+ function findTokensForWorkspace(allCredentials, workspaceId) {
43
+ return storedCredentialToTokens(allCredentials.find((credential) => credential?.workspaceId === workspaceId)) ?? null;
44
+ }
20
45
  function tokensEqual(a, b) {
21
46
  return a?.workspaceId === b?.workspaceId && a?.accessToken === b?.accessToken && a?.refreshToken === b?.refreshToken;
22
47
  }
@@ -36,25 +61,44 @@ function sleep(ms, signal) {
36
61
  }
37
62
  var FileTokenStorage = class {
38
63
  credentialsStore;
64
+ authFilePath;
65
+ contextFilePath;
39
66
  lockFilePath;
40
- constructor(env = process.env, signal) {
67
+ constructor(env = process.env, signal, options = {}) {
41
68
  this.signal = signal;
69
+ this.options = options;
42
70
  const authFilePath = getAuthFilePath(env);
71
+ this.authFilePath = authFilePath;
72
+ this.contextFilePath = getAuthContextFilePath(authFilePath);
43
73
  this.credentialsStore = new CredentialsStore(authFilePath);
44
74
  this.lockFilePath = `${authFilePath}.lock`;
45
75
  }
46
76
  async getTokens() {
47
77
  this.signal?.throwIfAborted();
48
78
  try {
49
- const all = await this.credentialsStore.getCredentials();
50
- this.signal?.throwIfAborted();
51
- return findLatestValidTokens(all);
52
- } catch (error) {
79
+ const credentials = await this.readCredentialsFromDisk();
80
+ const context = await this.readAuthContext();
81
+ if (context.state.activeWorkspaceId) return findTokensForWorkspace(credentials, context.state.activeWorkspaceId);
82
+ const latest = findLatestValidTokens(credentials);
83
+ if (!latest) return null;
84
+ if (!context.exists) {
85
+ await this.setActiveWorkspaceId(latest.workspaceId);
86
+ return latest;
87
+ }
88
+ return null;
89
+ } catch (_error) {
53
90
  if (this.signal?.aborted) throw this.signal.reason;
54
91
  return null;
55
92
  }
56
93
  }
57
94
  async setTokens(tokens) {
95
+ if (this.options.lockSetTokens === false) {
96
+ await this.setTokensUnlocked(tokens);
97
+ return;
98
+ }
99
+ await this.withRefreshLock(() => this.setTokensUnlocked(tokens));
100
+ }
101
+ async setTokensUnlocked(tokens) {
58
102
  this.signal?.throwIfAborted();
59
103
  await this.credentialsStore.storeCredentials({
60
104
  workspaceId: tokens.workspaceId,
@@ -62,20 +106,119 @@ var FileTokenStorage = class {
62
106
  refreshToken: tokens.refreshToken
63
107
  });
64
108
  this.signal?.throwIfAborted();
109
+ await this.rememberWorkspaceUnlocked(tokens.workspaceId, {
110
+ id: tokens.workspaceId,
111
+ name: tokens.workspaceId
112
+ });
113
+ await this.maybeActivateWorkspaceId(tokens.workspaceId);
65
114
  }
66
115
  async clearTokens() {
116
+ await this.withRefreshLock(() => this.clearTokensUnlocked());
117
+ }
118
+ async clearTokensUnlocked() {
67
119
  this.signal?.throwIfAborted();
68
- const all = await this.credentialsStore.getCredentials();
120
+ await fs.mkdir(path.dirname(this.authFilePath), { recursive: true });
69
121
  this.signal?.throwIfAborted();
70
- const tokens = findLatestValidTokens(all);
71
- if (!tokens) return;
72
- await this.credentialsStore.deleteCredentials(tokens.workspaceId);
122
+ await fs.writeFile(this.authFilePath, `${JSON.stringify({ tokens: [] }, null, 2)}\n`, {
123
+ encoding: "utf8",
124
+ signal: this.signal
125
+ });
126
+ await this.clearAuthContext();
127
+ await this.credentialsStore.reloadCredentialsFromDisk();
73
128
  this.signal?.throwIfAborted();
74
129
  }
75
130
  async clearTokensIfCurrent(tokens) {
76
131
  this.signal?.throwIfAborted();
77
132
  if (!tokensEqual(await this.getTokens(), tokens)) return;
78
- await this.clearTokens();
133
+ await this.credentialsStore.deleteCredentials(tokens.workspaceId);
134
+ await this.removeRememberedWorkspace(tokens.workspaceId, { preserveActivePointer: true });
135
+ this.signal?.throwIfAborted();
136
+ }
137
+ async listWorkspaces() {
138
+ this.signal?.throwIfAborted();
139
+ const credentials = await this.readCredentialsFromDisk();
140
+ const context = await this.ensureMigratedAuthContext(credentials);
141
+ return credentials.map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null).map((tokens) => {
142
+ const cached = context.state.workspaces[tokens.workspaceId];
143
+ const id = typeof cached?.id === "string" && cached.id.trim().length > 0 ? cached.id.trim() : tokens.workspaceId;
144
+ const name = typeof cached?.name === "string" && cached.name.trim().length > 0 ? workspaceDisplayName(cached.name.trim(), tokens.workspaceId) : UNKNOWN_WORKSPACE_NAME;
145
+ const lastSeenAt = typeof cached?.lastSeenAt === "string" && cached.lastSeenAt.trim().length > 0 ? cached.lastSeenAt.trim() : null;
146
+ return {
147
+ id,
148
+ name,
149
+ credentialWorkspaceId: tokens.workspaceId,
150
+ active: context.state.activeWorkspaceId === tokens.workspaceId,
151
+ lastSeenAt
152
+ };
153
+ });
154
+ }
155
+ async listWorkspaceTokens() {
156
+ this.signal?.throwIfAborted();
157
+ return (await this.readCredentialsFromDisk()).map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null);
158
+ }
159
+ async useWorkspace(workspaceRef) {
160
+ return this.withRefreshLock(() => this.useWorkspaceUnlocked(workspaceRef));
161
+ }
162
+ async useWorkspaceUnlocked(workspaceRef) {
163
+ const ref = workspaceRef.trim();
164
+ if (!ref) throw new WorkspaceSelectionError("missing");
165
+ const workspaces = await this.listWorkspaces();
166
+ const context = await this.readAuthContext();
167
+ const previous = workspaces.find((workspace) => workspace.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null;
168
+ const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
169
+ if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
170
+ if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
171
+ const selected = matches[0];
172
+ await this.setActiveWorkspaceId(selected.credentialWorkspaceId);
173
+ return {
174
+ previous,
175
+ selected
176
+ };
177
+ }
178
+ async logoutWorkspace(workspaceRef) {
179
+ return this.withRefreshLock(() => this.logoutWorkspaceUnlocked(workspaceRef));
180
+ }
181
+ async logoutWorkspaceUnlocked(workspaceRef) {
182
+ const ref = workspaceRef.trim();
183
+ if (!ref) throw new WorkspaceSelectionError("missing");
184
+ const workspaces = await this.listWorkspaces();
185
+ const context = await this.readAuthContext();
186
+ const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
187
+ if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
188
+ if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
189
+ const workspace = matches[0];
190
+ const wasActive = context.state.activeWorkspaceId === workspace.credentialWorkspaceId;
191
+ await this.credentialsStore.deleteCredentials(workspace.credentialWorkspaceId);
192
+ this.signal?.throwIfAborted();
193
+ const remainingWorkspaces = workspaces.filter((candidate) => candidate.credentialWorkspaceId !== workspace.credentialWorkspaceId);
194
+ if (remainingWorkspaces.length === 0) {
195
+ await this.clearAuthContext();
196
+ return {
197
+ workspace,
198
+ wasActive,
199
+ activeWorkspace: null
200
+ };
201
+ }
202
+ delete context.state.workspaces[workspace.credentialWorkspaceId];
203
+ if (wasActive) context.state.activeWorkspaceId = null;
204
+ await this.writeAuthContext(context.state);
205
+ return {
206
+ workspace,
207
+ wasActive,
208
+ activeWorkspace: context.state.activeWorkspaceId === null ? null : remainingWorkspaces.find((candidate) => candidate.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null
209
+ };
210
+ }
211
+ async rememberWorkspace(credentialWorkspaceId, workspace) {
212
+ await this.withRefreshLock(() => this.rememberWorkspaceUnlocked(credentialWorkspaceId, workspace));
213
+ }
214
+ async rememberWorkspaceUnlocked(credentialWorkspaceId, workspace) {
215
+ const context = await this.readAuthContext();
216
+ context.state.workspaces[credentialWorkspaceId] = {
217
+ id: workspace.id,
218
+ name: workspace.name,
219
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
220
+ };
221
+ await this.writeAuthContext(context.state);
79
222
  }
80
223
  async withRefreshLock(fn) {
81
224
  const lockId = await this.acquireRefreshLock();
@@ -87,34 +230,48 @@ var FileTokenStorage = class {
87
230
  }
88
231
  async acquireRefreshLock() {
89
232
  const lockId = randomUUID();
233
+ const startedAt = Date.now();
234
+ const retryMs = this.options.lockRetryMs ?? REFRESH_LOCK_RETRY_MS;
235
+ const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? REFRESH_LOCK_WAIT_TIMEOUT_MS;
90
236
  this.signal?.throwIfAborted();
91
237
  await fs.mkdir(path.dirname(this.lockFilePath), { recursive: true });
92
238
  while (true) {
93
239
  this.signal?.throwIfAborted();
94
- let lockFileCreated = false;
240
+ if (await this.tryCreateRefreshLock(lockId)) return lockId;
241
+ if (await this.releaseStaleRefreshLock()) continue;
242
+ this.throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs);
243
+ await sleep(retryMs, this.signal);
244
+ }
245
+ }
246
+ async tryCreateRefreshLock(lockId) {
247
+ let lockFileCreated = false;
248
+ try {
249
+ const handle = await fs.open(this.lockFilePath, "wx");
250
+ lockFileCreated = true;
95
251
  try {
96
- const handle = await fs.open(this.lockFilePath, "wx");
97
- lockFileCreated = true;
98
- try {
99
- this.signal?.throwIfAborted();
100
- await handle.writeFile(lockId, { encoding: "utf8" });
101
- this.signal?.throwIfAborted();
102
- } finally {
103
- await handle.close();
104
- }
105
- return lockId;
106
- } catch (error) {
107
- if (lockFileCreated) await fs.unlink(this.lockFilePath).catch(() => void 0);
108
- if (error.code !== "EEXIST") throw error;
109
- const staleLockId = await this.getStaleRefreshLockId();
110
- if (staleLockId) {
111
- await this.releaseRefreshLock(staleLockId);
112
- continue;
113
- }
114
- await sleep(100, this.signal);
252
+ this.signal?.throwIfAborted();
253
+ await handle.writeFile(lockId, { encoding: "utf8" });
254
+ this.signal?.throwIfAborted();
255
+ } finally {
256
+ await handle.close();
115
257
  }
258
+ return true;
259
+ } catch (error) {
260
+ if (lockFileCreated) await fs.unlink(this.lockFilePath).catch(() => void 0);
261
+ if (error.code === "EEXIST") return false;
262
+ throw error;
116
263
  }
117
264
  }
265
+ async releaseStaleRefreshLock() {
266
+ const staleLockId = await this.getStaleRefreshLockId();
267
+ if (!staleLockId) return false;
268
+ await this.releaseRefreshLock(staleLockId);
269
+ return true;
270
+ }
271
+ throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs) {
272
+ if (Date.now() - startedAt < waitTimeoutMs) return;
273
+ throw new RefreshLockTimeoutError(this.lockFilePath, waitTimeoutMs);
274
+ }
118
275
  async getStaleRefreshLockId() {
119
276
  this.signal?.throwIfAborted();
120
277
  const lockId = await fs.readFile(this.lockFilePath, {
@@ -129,12 +286,133 @@ var FileTokenStorage = class {
129
286
  const stats = await fs.stat(this.lockFilePath).catch(() => null);
130
287
  this.signal?.throwIfAborted();
131
288
  if (!stats) return null;
132
- return Date.now() - stats.mtimeMs > 3e4 ? lockId : null;
289
+ const staleMs = this.options.lockStaleMs ?? REFRESH_LOCK_STALE_MS;
290
+ return Date.now() - stats.mtimeMs > staleMs ? lockId : null;
133
291
  }
134
292
  async releaseRefreshLock(lockId) {
135
293
  if (await fs.readFile(this.lockFilePath, { encoding: "utf8" }).catch(() => null) !== lockId) return;
136
294
  await fs.unlink(this.lockFilePath).catch(() => {});
137
295
  }
296
+ async readCredentialsFromDisk() {
297
+ this.signal?.throwIfAborted();
298
+ await this.credentialsStore.reloadCredentialsFromDisk();
299
+ this.signal?.throwIfAborted();
300
+ return await this.credentialsStore.getCredentials();
301
+ }
302
+ async ensureMigratedAuthContext(credentials) {
303
+ const context = await this.readAuthContext();
304
+ if (context.state.activeWorkspaceId || context.exists) return context;
305
+ const latest = findLatestValidTokens(credentials);
306
+ if (!latest) return context;
307
+ context.state.activeWorkspaceId = latest.workspaceId;
308
+ context.state.workspaces[latest.workspaceId] ??= {
309
+ id: latest.workspaceId,
310
+ name: latest.workspaceId,
311
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
312
+ };
313
+ await this.writeAuthContext(context.state);
314
+ return {
315
+ exists: true,
316
+ state: context.state
317
+ };
318
+ }
319
+ async setActiveWorkspaceId(workspaceId) {
320
+ const context = await this.readAuthContext();
321
+ context.state.activeWorkspaceId = workspaceId;
322
+ context.state.workspaces[workspaceId] ??= {
323
+ id: workspaceId,
324
+ name: workspaceId,
325
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
326
+ };
327
+ await this.writeAuthContext(context.state);
328
+ }
329
+ async maybeActivateWorkspaceId(workspaceId) {
330
+ const context = await this.readAuthContext();
331
+ if (this.options.activateOnSetTokens === false && context.exists && context.state.activeWorkspaceId && context.state.activeWorkspaceId !== workspaceId) return;
332
+ context.state.activeWorkspaceId = workspaceId;
333
+ context.state.workspaces[workspaceId] ??= {
334
+ id: workspaceId,
335
+ name: workspaceId,
336
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
337
+ };
338
+ await this.writeAuthContext(context.state);
339
+ }
340
+ async removeRememberedWorkspace(workspaceId, options) {
341
+ const context = await this.readAuthContext();
342
+ delete context.state.workspaces[workspaceId];
343
+ if (!options.preserveActivePointer && context.state.activeWorkspaceId === workspaceId) context.state.activeWorkspaceId = null;
344
+ await this.writeAuthContext(context.state);
345
+ }
346
+ async readAuthContext() {
347
+ this.signal?.throwIfAborted();
348
+ const raw = await fs.readFile(this.contextFilePath, {
349
+ encoding: "utf8",
350
+ signal: this.signal
351
+ }).catch((error) => {
352
+ if (this.signal?.aborted) throw error;
353
+ if (error.code === "ENOENT") return null;
354
+ throw error;
355
+ });
356
+ if (raw === null) return {
357
+ exists: false,
358
+ state: structuredClone(EMPTY_AUTH_CONTEXT)
359
+ };
360
+ try {
361
+ const parsed = JSON.parse(raw);
362
+ return {
363
+ exists: true,
364
+ state: {
365
+ activeWorkspaceId: typeof parsed.activeWorkspaceId === "string" && parsed.activeWorkspaceId.trim().length > 0 ? parsed.activeWorkspaceId.trim() : null,
366
+ workspaces: parsed.workspaces && typeof parsed.workspaces === "object" && !Array.isArray(parsed.workspaces) ? parsed.workspaces : {}
367
+ }
368
+ };
369
+ } catch {
370
+ return {
371
+ exists: false,
372
+ state: structuredClone(EMPTY_AUTH_CONTEXT)
373
+ };
374
+ }
375
+ }
376
+ async writeAuthContext(state) {
377
+ this.signal?.throwIfAborted();
378
+ await fs.mkdir(path.dirname(this.contextFilePath), { recursive: true });
379
+ this.signal?.throwIfAborted();
380
+ await fs.writeFile(this.contextFilePath, `${JSON.stringify(state, null, 2)}\n`, {
381
+ encoding: "utf8",
382
+ signal: this.signal
383
+ });
384
+ this.signal?.throwIfAborted();
385
+ }
386
+ async clearAuthContext() {
387
+ await fs.unlink(this.contextFilePath).catch((error) => {
388
+ if (error.code === "ENOENT") return;
389
+ throw error;
390
+ });
391
+ }
392
+ };
393
+ var WorkspaceSelectionError = class extends Error {
394
+ constructor(reason, workspaceRef, matches = []) {
395
+ super(reason);
396
+ this.reason = reason;
397
+ this.workspaceRef = workspaceRef;
398
+ this.matches = matches;
399
+ this.name = "WorkspaceSelectionError";
400
+ }
138
401
  };
402
+ var RefreshLockTimeoutError = class extends Error {
403
+ constructor(lockFilePath, waitTimeoutMs) {
404
+ super(`Timed out waiting ${waitTimeoutMs}ms for auth refresh lock at ${lockFilePath}`);
405
+ this.name = "RefreshLockTimeoutError";
406
+ }
407
+ };
408
+ function workspaceMatchesRef(workspace, ref) {
409
+ return workspace.credentialWorkspaceId === ref || workspace.id === ref || stripWorkspacePrefix(workspace.credentialWorkspaceId) === stripWorkspacePrefix(ref) || stripWorkspacePrefix(workspace.id) === stripWorkspacePrefix(ref) || workspace.name.toLowerCase() === ref.toLowerCase();
410
+ }
411
+ function stripWorkspacePrefix(value) {
412
+ return value.startsWith("wksp_") ? value.slice(5) : value;
413
+ }
414
+ function workspaceDisplayName(name, credentialWorkspaceId) {
415
+ return name === credentialWorkspaceId ? UNKNOWN_WORKSPACE_NAME : name;
416
+ }
139
417
  //#endregion
140
- export { FileTokenStorage };
418
+ export { FileTokenStorage, WorkspaceSelectionError };
package/dist/cli.js CHANGED
@@ -3,22 +3,22 @@ import { runUpdateDiscoveryWorker } from "./shell/update-check.js";
3
3
  import { runCli } from "./cli2.js";
4
4
  import process from "node:process";
5
5
  //#region src/bin.ts
6
- if (process.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1") runUpdateDiscoveryWorker().then(() => {
6
+ if (process.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1") {
7
+ await runUpdateDiscoveryWorker();
7
8
  process.exitCode = 0;
8
- });
9
- else {
9
+ } else {
10
10
  const controller = new AbortController();
11
11
  const abortCli = () => {
12
12
  if (!controller.signal.aborted) controller.abort(new DOMException("Command canceled", "AbortError"));
13
13
  };
14
14
  process.once("SIGINT", abortCli);
15
15
  process.once("SIGTERM", abortCli);
16
- runCli({ signal: controller.signal }).then((exitCode) => {
17
- process.exitCode = exitCode;
18
- }).finally(() => {
16
+ try {
17
+ process.exitCode = await runCli({ signal: controller.signal });
18
+ } finally {
19
19
  process.off("SIGINT", abortCli);
20
20
  process.off("SIGTERM", abortCli);
21
- });
21
+ }
22
22
  }
23
23
  //#endregion
24
24
  export {};
package/dist/cli2.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { CliError } from "./shell/errors.js";
2
- import { createShellUi } from "./shell/ui.js";
3
- import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
2
+ import "./shell/prompt.js";
4
3
  import { attachCommandDescriptor } from "./shell/command-meta.js";
5
4
  import { addCompactGlobalFlags } from "./shell/global-flags.js";
5
+ import { createShellUi } from "./shell/ui.js";
6
6
  import { configureRuntimeCommand, createCommandContext } from "./shell/runtime.js";
7
- import "./shell/prompt.js";
7
+ import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
8
8
  import { createAppCommand } from "./commands/app/index.js";
9
9
  import { createAuthCommand } from "./commands/auth/index.js";
10
10
  import { createBranchCommand } from "./commands/branch/index.js";
11
+ import { createDatabaseCommand } from "./commands/database/index.js";
11
12
  import { createGitCommand } from "./commands/git/index.js";
12
13
  import { createProjectCommand } from "./commands/project/index.js";
13
14
  import { getCliName, getCliVersion } from "./lib/version.js";
@@ -49,6 +50,7 @@ function createProgram(runtime) {
49
50
  program.addCommand(createProjectCommand(runtime));
50
51
  program.addCommand(createGitCommand(runtime));
51
52
  program.addCommand(createBranchCommand(runtime));
53
+ program.addCommand(createDatabaseCommand(runtime));
52
54
  program.addCommand(createAppCommand(runtime));
53
55
  return program;
54
56
  }