@prisma/cli 3.0.0-beta.3 → 3.0.0-beta.30
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.
- package/README.md +6 -15
- package/dist/adapters/local-state.js +15 -4
- package/dist/adapters/mock-api.js +244 -0
- package/dist/adapters/token-storage.js +335 -34
- package/dist/cli.js +7 -7
- package/dist/cli2.js +24 -5
- package/dist/commands/agent/index.js +60 -0
- package/dist/commands/app/index.js +91 -61
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/bucket/index.js +123 -0
- package/dist/commands/build/index.js +29 -0
- package/dist/commands/database/index.js +249 -0
- package/dist/commands/env.js +8 -4
- package/dist/commands/feedback/index.js +20 -0
- package/dist/commands/git/index.js +1 -1
- package/dist/commands/init/index.js +33 -0
- package/dist/commands/project/index.js +54 -5
- package/dist/controllers/agent-setup.js +52 -0
- package/dist/controllers/agent.js +228 -0
- package/dist/controllers/app-env-api.js +55 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +227 -104
- package/dist/controllers/app.js +746 -306
- package/dist/controllers/auth.js +247 -3
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/bucket.js +278 -0
- package/dist/controllers/build.js +88 -0
- package/dist/controllers/database.js +567 -0
- package/dist/controllers/feedback.js +86 -0
- package/dist/controllers/init.js +753 -0
- package/dist/controllers/project.js +377 -22
- package/dist/controllers/select-prompt-port.js +1 -0
- package/dist/lib/agent/cli-command.js +20 -0
- package/dist/lib/agent/constants.js +12 -0
- package/dist/lib/agent/package-manager.js +99 -0
- package/dist/lib/agent/setup-status.js +83 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +137 -88
- package/dist/lib/app/branch-database-api.js +102 -0
- package/dist/lib/app/branch-database-deploy.js +326 -0
- package/dist/lib/app/branch-database.js +216 -0
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +83 -0
- package/dist/lib/app/bun-project.js +3 -4
- package/dist/lib/app/compute-config.js +145 -0
- package/dist/lib/app/deploy-plan.js +59 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/env-vars.js +28 -2
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +162 -0
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +10 -4
- package/dist/lib/auth/guard.js +4 -1
- package/dist/lib/auth/login.js +33 -26
- package/dist/lib/auth/recipient.js +42 -0
- package/dist/lib/bucket/provider.js +139 -0
- package/dist/lib/database/provider.js +378 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +53 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +5 -4
- package/dist/lib/project/local-pin.js +171 -41
- package/dist/lib/project/provider.js +92 -0
- package/dist/lib/project/resolution.js +199 -48
- package/dist/lib/project/setup.js +67 -20
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/agent.js +74 -0
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +208 -27
- package/dist/presenters/auth.js +99 -2
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/bucket.js +174 -0
- package/dist/presenters/database.js +448 -0
- package/dist/presenters/feedback.js +26 -0
- package/dist/presenters/init.js +30 -0
- package/dist/presenters/project.js +139 -27
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/cli-command.js +12 -0
- package/dist/shell/command-arguments.js +7 -1
- package/dist/shell/command-meta.js +458 -17
- package/dist/shell/command-runner.js +58 -18
- package/dist/shell/diagnostics-output.js +57 -0
- package/dist/shell/errors.js +56 -1
- package/dist/shell/help.js +31 -20
- package/dist/shell/output.js +72 -1
- package/dist/shell/prompt.js +12 -5
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +42 -3
- package/dist/shell/update-check.js +2 -2
- package/dist/use-cases/auth.js +68 -1
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/dist/use-cases/project.js +2 -1
- package/package.json +21 -4
- package/dist/lib/app/preview-build.js +0 -312
- package/dist/lib/app/preview-interaction.js +0 -5
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { getAuthFilePath } from "../lib/auth/client.js";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import
|
|
3
|
+
import fs from "node:fs/promises";
|
|
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,45 @@ 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
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
const credentials = await this.readCredentialsFromDisk();
|
|
80
|
+
if (this.options.pinnedWorkspaceId) return findTokensForWorkspace(credentials, this.options.pinnedWorkspaceId);
|
|
81
|
+
const context = await this.readAuthContext();
|
|
82
|
+
if (context.state.activeWorkspaceId) return findTokensForWorkspace(credentials, context.state.activeWorkspaceId);
|
|
83
|
+
const latest = findLatestValidTokens(credentials);
|
|
84
|
+
if (!latest) return null;
|
|
85
|
+
if (!context.exists) {
|
|
86
|
+
await this.setActiveWorkspaceId(latest.workspaceId);
|
|
87
|
+
return latest;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} catch (_error) {
|
|
53
91
|
if (this.signal?.aborted) throw this.signal.reason;
|
|
54
92
|
return null;
|
|
55
93
|
}
|
|
56
94
|
}
|
|
57
95
|
async setTokens(tokens) {
|
|
96
|
+
if (this.options.lockSetTokens === false) {
|
|
97
|
+
await this.setTokensUnlocked(tokens);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await this.withRefreshLock(() => this.setTokensUnlocked(tokens));
|
|
101
|
+
}
|
|
102
|
+
async setTokensUnlocked(tokens) {
|
|
58
103
|
this.signal?.throwIfAborted();
|
|
59
104
|
await this.credentialsStore.storeCredentials({
|
|
60
105
|
workspaceId: tokens.workspaceId,
|
|
@@ -62,20 +107,140 @@ var FileTokenStorage = class {
|
|
|
62
107
|
refreshToken: tokens.refreshToken
|
|
63
108
|
});
|
|
64
109
|
this.signal?.throwIfAborted();
|
|
110
|
+
await this.rememberWorkspaceUnlocked(tokens.workspaceId, {
|
|
111
|
+
id: tokens.workspaceId,
|
|
112
|
+
name: tokens.workspaceId
|
|
113
|
+
});
|
|
114
|
+
await this.maybeActivateWorkspaceId(tokens.workspaceId);
|
|
65
115
|
}
|
|
66
116
|
async clearTokens() {
|
|
117
|
+
await this.withRefreshLock(() => this.clearTokensUnlocked());
|
|
118
|
+
}
|
|
119
|
+
async clearTokensUnlocked() {
|
|
67
120
|
this.signal?.throwIfAborted();
|
|
68
|
-
|
|
121
|
+
await fs.mkdir(path.dirname(this.authFilePath), { recursive: true });
|
|
69
122
|
this.signal?.throwIfAborted();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
await fs.writeFile(this.authFilePath, `${JSON.stringify({ tokens: [] }, null, 2)}\n`, {
|
|
124
|
+
encoding: "utf8",
|
|
125
|
+
signal: this.signal
|
|
126
|
+
});
|
|
127
|
+
await this.clearAuthContext();
|
|
128
|
+
await this.credentialsStore.reloadCredentialsFromDisk();
|
|
73
129
|
this.signal?.throwIfAborted();
|
|
74
130
|
}
|
|
75
131
|
async clearTokensIfCurrent(tokens) {
|
|
76
132
|
this.signal?.throwIfAborted();
|
|
77
133
|
if (!tokensEqual(await this.getTokens(), tokens)) return;
|
|
78
|
-
await this.
|
|
134
|
+
await this.credentialsStore.deleteCredentials(tokens.workspaceId);
|
|
135
|
+
await this.removeRememberedWorkspace(tokens.workspaceId, { preserveActivePointer: true });
|
|
136
|
+
this.signal?.throwIfAborted();
|
|
137
|
+
}
|
|
138
|
+
async listWorkspaces() {
|
|
139
|
+
this.signal?.throwIfAborted();
|
|
140
|
+
const credentials = await this.readCredentialsFromDisk();
|
|
141
|
+
const context = await this.ensureMigratedAuthContext(credentials);
|
|
142
|
+
return this.workspacesFromState(credentials, context);
|
|
143
|
+
}
|
|
144
|
+
workspacesFromState(credentials, context) {
|
|
145
|
+
return credentials.map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null).map((tokens) => {
|
|
146
|
+
const cached = context.state.workspaces[tokens.workspaceId];
|
|
147
|
+
const id = typeof cached?.id === "string" && cached.id.trim().length > 0 ? cached.id.trim() : tokens.workspaceId;
|
|
148
|
+
const name = typeof cached?.name === "string" && cached.name.trim().length > 0 ? workspaceDisplayName(cached.name.trim(), tokens.workspaceId) : UNKNOWN_WORKSPACE_NAME;
|
|
149
|
+
const lastSeenAt = typeof cached?.lastSeenAt === "string" && cached.lastSeenAt.trim().length > 0 ? cached.lastSeenAt.trim() : null;
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
name,
|
|
153
|
+
credentialWorkspaceId: tokens.workspaceId,
|
|
154
|
+
active: context.state.activeWorkspaceId === tokens.workspaceId,
|
|
155
|
+
lastSeenAt
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async listWorkspaceTokens() {
|
|
160
|
+
this.signal?.throwIfAborted();
|
|
161
|
+
return (await this.readCredentialsFromDisk()).map((credential) => storedCredentialToTokens(credential)).filter((tokens) => tokens !== null);
|
|
162
|
+
}
|
|
163
|
+
async useWorkspace(workspaceRef) {
|
|
164
|
+
return this.withRefreshLock(() => this.useWorkspaceUnlocked(workspaceRef));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a workspace ref (id, credential workspace id, or cached name)
|
|
168
|
+
* against the locally stored sessions without changing the active
|
|
169
|
+
* workspace. Read-only counterpart of useWorkspace: it reads the auth
|
|
170
|
+
* context as-is and never runs the legacy-state migration, so it writes
|
|
171
|
+
* nothing.
|
|
172
|
+
*/
|
|
173
|
+
async resolveWorkspace(workspaceRef) {
|
|
174
|
+
const ref = workspaceRef.trim();
|
|
175
|
+
if (!ref) throw new WorkspaceSelectionError("missing");
|
|
176
|
+
this.signal?.throwIfAborted();
|
|
177
|
+
const credentials = await this.readCredentialsFromDisk();
|
|
178
|
+
const context = await this.readAuthContext();
|
|
179
|
+
const matches = this.workspacesFromState(credentials, context).filter((workspace) => workspaceMatchesRef(workspace, ref));
|
|
180
|
+
if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
|
|
181
|
+
if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
|
|
182
|
+
return matches[0];
|
|
183
|
+
}
|
|
184
|
+
async useWorkspaceUnlocked(workspaceRef) {
|
|
185
|
+
const ref = workspaceRef.trim();
|
|
186
|
+
if (!ref) throw new WorkspaceSelectionError("missing");
|
|
187
|
+
const workspaces = await this.listWorkspaces();
|
|
188
|
+
const context = await this.readAuthContext();
|
|
189
|
+
const previous = workspaces.find((workspace) => workspace.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null;
|
|
190
|
+
const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
|
|
191
|
+
if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
|
|
192
|
+
if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
|
|
193
|
+
const selected = matches[0];
|
|
194
|
+
await this.setActiveWorkspaceId(selected.credentialWorkspaceId);
|
|
195
|
+
return {
|
|
196
|
+
previous,
|
|
197
|
+
selected
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async logoutWorkspace(workspaceRef) {
|
|
201
|
+
return this.withRefreshLock(() => this.logoutWorkspaceUnlocked(workspaceRef));
|
|
202
|
+
}
|
|
203
|
+
async logoutWorkspaceUnlocked(workspaceRef) {
|
|
204
|
+
const ref = workspaceRef.trim();
|
|
205
|
+
if (!ref) throw new WorkspaceSelectionError("missing");
|
|
206
|
+
const workspaces = await this.listWorkspaces();
|
|
207
|
+
const context = await this.readAuthContext();
|
|
208
|
+
const matches = workspaces.filter((workspace) => workspaceMatchesRef(workspace, ref));
|
|
209
|
+
if (matches.length === 0) throw new WorkspaceSelectionError("not-found", ref);
|
|
210
|
+
if (matches.length > 1) throw new WorkspaceSelectionError("ambiguous", ref, matches);
|
|
211
|
+
const workspace = matches[0];
|
|
212
|
+
const wasActive = context.state.activeWorkspaceId === workspace.credentialWorkspaceId;
|
|
213
|
+
await this.credentialsStore.deleteCredentials(workspace.credentialWorkspaceId);
|
|
214
|
+
this.signal?.throwIfAborted();
|
|
215
|
+
const remainingWorkspaces = workspaces.filter((candidate) => candidate.credentialWorkspaceId !== workspace.credentialWorkspaceId);
|
|
216
|
+
if (remainingWorkspaces.length === 0) {
|
|
217
|
+
await this.clearAuthContext();
|
|
218
|
+
return {
|
|
219
|
+
workspace,
|
|
220
|
+
wasActive,
|
|
221
|
+
activeWorkspace: null
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
delete context.state.workspaces[workspace.credentialWorkspaceId];
|
|
225
|
+
if (wasActive) context.state.activeWorkspaceId = null;
|
|
226
|
+
await this.writeAuthContext(context.state);
|
|
227
|
+
return {
|
|
228
|
+
workspace,
|
|
229
|
+
wasActive,
|
|
230
|
+
activeWorkspace: context.state.activeWorkspaceId === null ? null : remainingWorkspaces.find((candidate) => candidate.credentialWorkspaceId === context.state.activeWorkspaceId) ?? null
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async rememberWorkspace(credentialWorkspaceId, workspace) {
|
|
234
|
+
await this.withRefreshLock(() => this.rememberWorkspaceUnlocked(credentialWorkspaceId, workspace));
|
|
235
|
+
}
|
|
236
|
+
async rememberWorkspaceUnlocked(credentialWorkspaceId, workspace) {
|
|
237
|
+
const context = await this.readAuthContext();
|
|
238
|
+
context.state.workspaces[credentialWorkspaceId] = {
|
|
239
|
+
id: workspace.id,
|
|
240
|
+
name: workspace.name,
|
|
241
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
242
|
+
};
|
|
243
|
+
await this.writeAuthContext(context.state);
|
|
79
244
|
}
|
|
80
245
|
async withRefreshLock(fn) {
|
|
81
246
|
const lockId = await this.acquireRefreshLock();
|
|
@@ -87,34 +252,48 @@ var FileTokenStorage = class {
|
|
|
87
252
|
}
|
|
88
253
|
async acquireRefreshLock() {
|
|
89
254
|
const lockId = randomUUID();
|
|
255
|
+
const startedAt = Date.now();
|
|
256
|
+
const retryMs = this.options.lockRetryMs ?? REFRESH_LOCK_RETRY_MS;
|
|
257
|
+
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? REFRESH_LOCK_WAIT_TIMEOUT_MS;
|
|
90
258
|
this.signal?.throwIfAborted();
|
|
91
259
|
await fs.mkdir(path.dirname(this.lockFilePath), { recursive: true });
|
|
92
260
|
while (true) {
|
|
93
261
|
this.signal?.throwIfAborted();
|
|
94
|
-
|
|
262
|
+
if (await this.tryCreateRefreshLock(lockId)) return lockId;
|
|
263
|
+
if (await this.releaseStaleRefreshLock()) continue;
|
|
264
|
+
this.throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs);
|
|
265
|
+
await sleep(retryMs, this.signal);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async tryCreateRefreshLock(lockId) {
|
|
269
|
+
let lockFileCreated = false;
|
|
270
|
+
try {
|
|
271
|
+
const handle = await fs.open(this.lockFilePath, "wx");
|
|
272
|
+
lockFileCreated = true;
|
|
95
273
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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);
|
|
274
|
+
this.signal?.throwIfAborted();
|
|
275
|
+
await handle.writeFile(lockId, { encoding: "utf8" });
|
|
276
|
+
this.signal?.throwIfAborted();
|
|
277
|
+
} finally {
|
|
278
|
+
await handle.close();
|
|
115
279
|
}
|
|
280
|
+
return true;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (lockFileCreated) await fs.unlink(this.lockFilePath).catch(() => void 0);
|
|
283
|
+
if (error.code === "EEXIST") return false;
|
|
284
|
+
throw error;
|
|
116
285
|
}
|
|
117
286
|
}
|
|
287
|
+
async releaseStaleRefreshLock() {
|
|
288
|
+
const staleLockId = await this.getStaleRefreshLockId();
|
|
289
|
+
if (!staleLockId) return false;
|
|
290
|
+
await this.releaseRefreshLock(staleLockId);
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
throwIfRefreshLockWaitTimedOut(startedAt, waitTimeoutMs) {
|
|
294
|
+
if (Date.now() - startedAt < waitTimeoutMs) return;
|
|
295
|
+
throw new RefreshLockTimeoutError(this.lockFilePath, waitTimeoutMs);
|
|
296
|
+
}
|
|
118
297
|
async getStaleRefreshLockId() {
|
|
119
298
|
this.signal?.throwIfAborted();
|
|
120
299
|
const lockId = await fs.readFile(this.lockFilePath, {
|
|
@@ -129,12 +308,134 @@ var FileTokenStorage = class {
|
|
|
129
308
|
const stats = await fs.stat(this.lockFilePath).catch(() => null);
|
|
130
309
|
this.signal?.throwIfAborted();
|
|
131
310
|
if (!stats) return null;
|
|
132
|
-
|
|
311
|
+
const staleMs = this.options.lockStaleMs ?? REFRESH_LOCK_STALE_MS;
|
|
312
|
+
return Date.now() - stats.mtimeMs > staleMs ? lockId : null;
|
|
133
313
|
}
|
|
134
314
|
async releaseRefreshLock(lockId) {
|
|
135
315
|
if (await fs.readFile(this.lockFilePath, { encoding: "utf8" }).catch(() => null) !== lockId) return;
|
|
136
316
|
await fs.unlink(this.lockFilePath).catch(() => {});
|
|
137
317
|
}
|
|
318
|
+
async readCredentialsFromDisk() {
|
|
319
|
+
this.signal?.throwIfAborted();
|
|
320
|
+
await this.credentialsStore.reloadCredentialsFromDisk();
|
|
321
|
+
this.signal?.throwIfAborted();
|
|
322
|
+
return await this.credentialsStore.getCredentials();
|
|
323
|
+
}
|
|
324
|
+
async ensureMigratedAuthContext(credentials) {
|
|
325
|
+
const context = await this.readAuthContext();
|
|
326
|
+
if (context.state.activeWorkspaceId || context.exists) return context;
|
|
327
|
+
const latest = findLatestValidTokens(credentials);
|
|
328
|
+
if (!latest) return context;
|
|
329
|
+
context.state.activeWorkspaceId = latest.workspaceId;
|
|
330
|
+
context.state.workspaces[latest.workspaceId] ??= {
|
|
331
|
+
id: latest.workspaceId,
|
|
332
|
+
name: latest.workspaceId,
|
|
333
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
334
|
+
};
|
|
335
|
+
await this.writeAuthContext(context.state);
|
|
336
|
+
return {
|
|
337
|
+
exists: true,
|
|
338
|
+
state: context.state
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async setActiveWorkspaceId(workspaceId) {
|
|
342
|
+
const context = await this.readAuthContext();
|
|
343
|
+
context.state.activeWorkspaceId = workspaceId;
|
|
344
|
+
context.state.workspaces[workspaceId] ??= {
|
|
345
|
+
id: workspaceId,
|
|
346
|
+
name: workspaceId,
|
|
347
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
348
|
+
};
|
|
349
|
+
await this.writeAuthContext(context.state);
|
|
350
|
+
}
|
|
351
|
+
async maybeActivateWorkspaceId(workspaceId) {
|
|
352
|
+
if (this.options.pinnedWorkspaceId) return;
|
|
353
|
+
const context = await this.readAuthContext();
|
|
354
|
+
if (this.options.activateOnSetTokens === false && context.exists && context.state.activeWorkspaceId && context.state.activeWorkspaceId !== workspaceId) return;
|
|
355
|
+
context.state.activeWorkspaceId = workspaceId;
|
|
356
|
+
context.state.workspaces[workspaceId] ??= {
|
|
357
|
+
id: workspaceId,
|
|
358
|
+
name: workspaceId,
|
|
359
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
360
|
+
};
|
|
361
|
+
await this.writeAuthContext(context.state);
|
|
362
|
+
}
|
|
363
|
+
async removeRememberedWorkspace(workspaceId, options) {
|
|
364
|
+
const context = await this.readAuthContext();
|
|
365
|
+
delete context.state.workspaces[workspaceId];
|
|
366
|
+
if (!options.preserveActivePointer && context.state.activeWorkspaceId === workspaceId) context.state.activeWorkspaceId = null;
|
|
367
|
+
await this.writeAuthContext(context.state);
|
|
368
|
+
}
|
|
369
|
+
async readAuthContext() {
|
|
370
|
+
this.signal?.throwIfAborted();
|
|
371
|
+
const raw = await fs.readFile(this.contextFilePath, {
|
|
372
|
+
encoding: "utf8",
|
|
373
|
+
signal: this.signal
|
|
374
|
+
}).catch((error) => {
|
|
375
|
+
if (this.signal?.aborted) throw error;
|
|
376
|
+
if (error.code === "ENOENT") return null;
|
|
377
|
+
throw error;
|
|
378
|
+
});
|
|
379
|
+
if (raw === null) return {
|
|
380
|
+
exists: false,
|
|
381
|
+
state: structuredClone(EMPTY_AUTH_CONTEXT)
|
|
382
|
+
};
|
|
383
|
+
try {
|
|
384
|
+
const parsed = JSON.parse(raw);
|
|
385
|
+
return {
|
|
386
|
+
exists: true,
|
|
387
|
+
state: {
|
|
388
|
+
activeWorkspaceId: typeof parsed.activeWorkspaceId === "string" && parsed.activeWorkspaceId.trim().length > 0 ? parsed.activeWorkspaceId.trim() : null,
|
|
389
|
+
workspaces: parsed.workspaces && typeof parsed.workspaces === "object" && !Array.isArray(parsed.workspaces) ? parsed.workspaces : {}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
} catch {
|
|
393
|
+
return {
|
|
394
|
+
exists: false,
|
|
395
|
+
state: structuredClone(EMPTY_AUTH_CONTEXT)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async writeAuthContext(state) {
|
|
400
|
+
this.signal?.throwIfAborted();
|
|
401
|
+
await fs.mkdir(path.dirname(this.contextFilePath), { recursive: true });
|
|
402
|
+
this.signal?.throwIfAborted();
|
|
403
|
+
await fs.writeFile(this.contextFilePath, `${JSON.stringify(state, null, 2)}\n`, {
|
|
404
|
+
encoding: "utf8",
|
|
405
|
+
signal: this.signal
|
|
406
|
+
});
|
|
407
|
+
this.signal?.throwIfAborted();
|
|
408
|
+
}
|
|
409
|
+
async clearAuthContext() {
|
|
410
|
+
await fs.unlink(this.contextFilePath).catch((error) => {
|
|
411
|
+
if (error.code === "ENOENT") return;
|
|
412
|
+
throw error;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
var WorkspaceSelectionError = class extends Error {
|
|
417
|
+
constructor(reason, workspaceRef, matches = []) {
|
|
418
|
+
super(reason);
|
|
419
|
+
this.reason = reason;
|
|
420
|
+
this.workspaceRef = workspaceRef;
|
|
421
|
+
this.matches = matches;
|
|
422
|
+
this.name = "WorkspaceSelectionError";
|
|
423
|
+
}
|
|
138
424
|
};
|
|
425
|
+
var RefreshLockTimeoutError = class extends Error {
|
|
426
|
+
constructor(lockFilePath, waitTimeoutMs) {
|
|
427
|
+
super(`Timed out waiting ${waitTimeoutMs}ms for auth refresh lock at ${lockFilePath}`);
|
|
428
|
+
this.name = "RefreshLockTimeoutError";
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
function workspaceMatchesRef(workspace, ref) {
|
|
432
|
+
return workspace.credentialWorkspaceId === ref || workspace.id === ref || stripWorkspacePrefix(workspace.credentialWorkspaceId) === stripWorkspacePrefix(ref) || stripWorkspacePrefix(workspace.id) === stripWorkspacePrefix(ref) || workspace.name.toLowerCase() === ref.toLowerCase();
|
|
433
|
+
}
|
|
434
|
+
function stripWorkspacePrefix(value) {
|
|
435
|
+
return value.startsWith("wksp_") ? value.slice(5) : value;
|
|
436
|
+
}
|
|
437
|
+
function workspaceDisplayName(name, credentialWorkspaceId) {
|
|
438
|
+
return name === credentialWorkspaceId ? UNKNOWN_WORKSPACE_NAME : name;
|
|
439
|
+
}
|
|
139
440
|
//#endregion
|
|
140
|
-
export { FileTokenStorage };
|
|
441
|
+
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")
|
|
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
|
-
|
|
17
|
-
process.exitCode =
|
|
18
|
-
}
|
|
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,16 +1,22 @@
|
|
|
1
1
|
import { CliError } from "./shell/errors.js";
|
|
2
|
-
import { createShellUi } from "./shell/ui.js";
|
|
3
|
-
import { writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
|
|
4
2
|
import { attachCommandDescriptor } from "./shell/command-meta.js";
|
|
3
|
+
import { createShellUi } from "./shell/ui.js";
|
|
5
4
|
import { addCompactGlobalFlags } from "./shell/global-flags.js";
|
|
6
5
|
import { configureRuntimeCommand, createCommandContext } from "./shell/runtime.js";
|
|
6
|
+
import { formatUnexpectedError, unexpectedErrorFeedbackCommand, writeHumanError, writeJsonError, writeJsonSuccess, writeJsonUnexpectedError } from "./shell/output.js";
|
|
7
|
+
import { createAgentCommand } from "./commands/agent/index.js";
|
|
7
8
|
import "./shell/prompt.js";
|
|
8
9
|
import { createAppCommand } from "./commands/app/index.js";
|
|
9
10
|
import { createAuthCommand } from "./commands/auth/index.js";
|
|
10
11
|
import { createBranchCommand } from "./commands/branch/index.js";
|
|
12
|
+
import { createBucketCommand } from "./commands/bucket/index.js";
|
|
13
|
+
import { createBuildCommand } from "./commands/build/index.js";
|
|
14
|
+
import { createDatabaseCommand } from "./commands/database/index.js";
|
|
15
|
+
import { getCliName, getCliVersion } from "./lib/version.js";
|
|
16
|
+
import { createFeedbackCommand } from "./commands/feedback/index.js";
|
|
11
17
|
import { createGitCommand } from "./commands/git/index.js";
|
|
18
|
+
import { createInitCommand } from "./commands/init/index.js";
|
|
12
19
|
import { createProjectCommand } from "./commands/project/index.js";
|
|
13
|
-
import { getCliName, getCliVersion } from "./lib/version.js";
|
|
14
20
|
import { runVersion } from "./controllers/version.js";
|
|
15
21
|
import { createVersionCommand } from "./commands/version/index.js";
|
|
16
22
|
import { maybeWriteCachedUpdateNotification } from "./shell/update-check.js";
|
|
@@ -33,8 +39,15 @@ async function runCli(options = {}) {
|
|
|
33
39
|
return process.exitCode ?? 0;
|
|
34
40
|
} catch (error) {
|
|
35
41
|
if (error instanceof CommanderError) return error.code === "commander.helpDisplayed" ? 0 : 2;
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
if (runtime.argv.includes("--json")) {
|
|
43
|
+
writeJsonUnexpectedError({
|
|
44
|
+
stdout: runtime.stdout,
|
|
45
|
+
stderr: runtime.stderr
|
|
46
|
+
}, runtime.argv, error);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const quiet = runtime.argv.includes("--quiet") || runtime.argv.includes("-q");
|
|
50
|
+
runtime.stderr.write(formatUnexpectedError(error, runtime.argv.includes("--trace"), quiet ? void 0 : unexpectedErrorFeedbackCommand(runtime.argv, error)));
|
|
38
51
|
return 1;
|
|
39
52
|
} finally {
|
|
40
53
|
runtime.stdin;
|
|
@@ -46,10 +59,16 @@ function createProgram(runtime) {
|
|
|
46
59
|
program.addOption(new Option("--version", "Print the CLI version and exit."));
|
|
47
60
|
program.name("prisma").showSuggestionAfterError();
|
|
48
61
|
program.addCommand(createVersionCommand(runtime));
|
|
62
|
+
program.addCommand(createInitCommand(runtime));
|
|
63
|
+
program.addCommand(createFeedbackCommand(runtime));
|
|
64
|
+
program.addCommand(createAgentCommand(runtime));
|
|
49
65
|
program.addCommand(createAuthCommand(runtime));
|
|
50
66
|
program.addCommand(createProjectCommand(runtime));
|
|
51
67
|
program.addCommand(createGitCommand(runtime));
|
|
52
68
|
program.addCommand(createBranchCommand(runtime));
|
|
69
|
+
program.addCommand(createBuildCommand(runtime));
|
|
70
|
+
program.addCommand(createDatabaseCommand(runtime));
|
|
71
|
+
program.addCommand(createBucketCommand(runtime));
|
|
53
72
|
program.addCommand(createAppCommand(runtime));
|
|
54
73
|
return program;
|
|
55
74
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { runAgentInstall, runAgentStatus } from "../../controllers/agent.js";
|
|
2
|
+
import { attachCommandDescriptor } from "../../shell/command-meta.js";
|
|
3
|
+
import { renderAgentInstall, renderAgentStatus, serializeAgentInstall, serializeAgentStatus } from "../../presenters/agent.js";
|
|
4
|
+
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
|
|
5
|
+
import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
6
|
+
import { runCommand } from "../../shell/command-runner.js";
|
|
7
|
+
import { Command, Option } from "commander";
|
|
8
|
+
//#region src/commands/agent/index.ts
|
|
9
|
+
function createAgentCommand(runtime) {
|
|
10
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("agent"), runtime), "agent");
|
|
11
|
+
addCompactGlobalFlags(command);
|
|
12
|
+
command.addCommand(createAgentInstallCommand(runtime));
|
|
13
|
+
command.addCommand(createAgentUpdateCommand(runtime));
|
|
14
|
+
command.addCommand(createAgentStatusCommand(runtime));
|
|
15
|
+
return command;
|
|
16
|
+
}
|
|
17
|
+
function createAgentInstallCommand(runtime) {
|
|
18
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("install"), runtime), "agent.install");
|
|
19
|
+
addAgentInstallOptions(command);
|
|
20
|
+
addGlobalFlags(command);
|
|
21
|
+
command.action(async (options) => {
|
|
22
|
+
await runCommand(runtime, "agent.install", options, (context) => runAgentInstall(context, options, "install"), {
|
|
23
|
+
renderHuman: (context, descriptor, result) => renderAgentInstall(context, descriptor, result),
|
|
24
|
+
renderJson: (result) => serializeAgentInstall(result)
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
return command;
|
|
28
|
+
}
|
|
29
|
+
function createAgentUpdateCommand(runtime) {
|
|
30
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("update"), runtime), "agent.update");
|
|
31
|
+
addAgentInstallOptions(command);
|
|
32
|
+
addGlobalFlags(command);
|
|
33
|
+
command.action(async (options) => {
|
|
34
|
+
await runCommand(runtime, "agent.update", options, (context) => runAgentInstall(context, options, "update"), {
|
|
35
|
+
renderHuman: (context, descriptor, result) => renderAgentInstall(context, descriptor, result),
|
|
36
|
+
renderJson: (result) => serializeAgentInstall(result)
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return command;
|
|
40
|
+
}
|
|
41
|
+
function createAgentStatusCommand(runtime) {
|
|
42
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("status"), runtime), "agent.status");
|
|
43
|
+
command.addOption(new Option("--global", "Check globally installed Prisma skills instead of project skills"));
|
|
44
|
+
addGlobalFlags(command);
|
|
45
|
+
command.action(async (options) => {
|
|
46
|
+
await runCommand(runtime, "agent.status", options, (context) => runAgentStatus(context, options), {
|
|
47
|
+
renderHuman: (context, descriptor, result) => renderAgentStatus(context, descriptor, result),
|
|
48
|
+
renderJson: (result) => serializeAgentStatus(result)
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
return command;
|
|
52
|
+
}
|
|
53
|
+
function addAgentInstallOptions(command) {
|
|
54
|
+
command.addOption(new Option("--agent <agent>", "Agent target for Prisma skills; repeat for multiple agents").argParser(collectValues)).addOption(new Option("--all-agents", "Install Prisma skills for every agent supported by the skills CLI")).addOption(new Option("--skill <skill>", "Prisma skill to install; repeat for multiple skills").argParser(collectValues)).addOption(new Option("--global", "Install skills into the user directory instead of the project")).addOption(new Option("--copy", "Ask the skills CLI to copy files instead of symlinking them")).addOption(new Option("--dry-run", "Show changes without writing files"));
|
|
55
|
+
}
|
|
56
|
+
function collectValues(value, previous) {
|
|
57
|
+
return [...previous ?? [], value];
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { createAgentCommand };
|