@prisma/cli 8.0.0-rc.1 → 8.0.0-rc.2

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 (110) hide show
  1. package/dist/cli.js +15115 -16
  2. package/dist/deploy-framework-C7bQM00A.js +23 -0
  3. package/dist/{v8/sender.js → sender.js} +2 -2
  4. package/package.json +11 -15
  5. package/dist/adapters/git.js +0 -54
  6. package/dist/adapters/local-state.js +0 -155
  7. package/dist/auth/client.js +0 -30
  8. package/dist/auth/errors.js +0 -50
  9. package/dist/auth/guard.js +0 -39
  10. package/dist/auth/login.js +0 -471
  11. package/dist/auth/operations.js +0 -208
  12. package/dist/auth/recipient.js +0 -42
  13. package/dist/auth/token-storage.js +0 -439
  14. package/dist/auth/workspaces.js +0 -171
  15. package/dist/cli-command.js +0 -14
  16. package/dist/cli-name.js +0 -12
  17. package/dist/cli2.js +0 -136
  18. package/dist/command-arguments.js +0 -12
  19. package/dist/commands/agent/index.js +0 -60
  20. package/dist/commands/app/index.js +0 -350
  21. package/dist/commands/auth/index.js +0 -95
  22. package/dist/commands/branch/index.js +0 -27
  23. package/dist/commands/bucket/index.js +0 -123
  24. package/dist/commands/build/index.js +0 -29
  25. package/dist/commands/database/index.js +0 -249
  26. package/dist/commands/env.js +0 -99
  27. package/dist/commands/feedback/index.js +0 -20
  28. package/dist/commands/git/index.js +0 -36
  29. package/dist/commands/init/index.js +0 -33
  30. package/dist/commands/project/index.js +0 -118
  31. package/dist/commands/version/index.js +0 -18
  32. package/dist/controllers/agent-setup.js +0 -52
  33. package/dist/controllers/agent.js +0 -228
  34. package/dist/controllers/app-env-api.js +0 -55
  35. package/dist/controllers/app-env-file.js +0 -181
  36. package/dist/controllers/app-env.js +0 -502
  37. package/dist/controllers/app.js +0 -2508
  38. package/dist/controllers/auth.js +0 -124
  39. package/dist/controllers/branch.js +0 -92
  40. package/dist/controllers/bucket.js +0 -184
  41. package/dist/controllers/build.js +0 -88
  42. package/dist/controllers/database.js +0 -463
  43. package/dist/controllers/feedback.js +0 -90
  44. package/dist/controllers/init.js +0 -814
  45. package/dist/controllers/project.js +0 -892
  46. package/dist/controllers/select-prompt-port.js +0 -13
  47. package/dist/controllers/version.js +0 -12
  48. package/dist/errors.js +0 -102
  49. package/dist/lib/agent/cli-command.js +0 -20
  50. package/dist/lib/agent/constants.js +0 -12
  51. package/dist/lib/agent/package-manager.js +0 -99
  52. package/dist/lib/agent/setup-status.js +0 -83
  53. package/dist/lib/app/app-provider.js +0 -617
  54. package/dist/lib/app/branch-database-api.js +0 -102
  55. package/dist/lib/app/branch-database-deploy.js +0 -353
  56. package/dist/lib/app/branch-database.js +0 -234
  57. package/dist/lib/app/build-settings.js +0 -98
  58. package/dist/lib/app/build.js +0 -82
  59. package/dist/lib/app/bun-project.js +0 -45
  60. package/dist/lib/app/compute-config.js +0 -145
  61. package/dist/lib/app/deploy-output.js +0 -24
  62. package/dist/lib/app/deploy-plan.js +0 -59
  63. package/dist/lib/app/deploy-progress.js +0 -100
  64. package/dist/lib/app/domain-guidance.js +0 -14
  65. package/dist/lib/app/env-config.js +0 -67
  66. package/dist/lib/app/env-file.js +0 -82
  67. package/dist/lib/app/env-vars.js +0 -50
  68. package/dist/lib/app/local-dev.js +0 -109
  69. package/dist/lib/app/production-deploy-gate.js +0 -162
  70. package/dist/lib/app/read-branch.js +0 -30
  71. package/dist/lib/bucket/provider.js +0 -139
  72. package/dist/lib/database/provider.js +0 -385
  73. package/dist/lib/diagnostics.js +0 -16
  74. package/dist/lib/feedback.js +0 -15
  75. package/dist/lib/fs/home-path.js +0 -24
  76. package/dist/lib/git/local-branch.js +0 -53
  77. package/dist/lib/git/local-status.js +0 -57
  78. package/dist/lib/project/interactive-setup.js +0 -57
  79. package/dist/lib/project/local-pin.js +0 -200
  80. package/dist/lib/project/provider.js +0 -93
  81. package/dist/lib/project/resolution.js +0 -389
  82. package/dist/lib/project/setup.js +0 -138
  83. package/dist/lib/version.js +0 -56
  84. package/dist/lib/workspace-id.js +0 -18
  85. package/dist/output/patterns.js +0 -90
  86. package/dist/presenters/agent.js +0 -74
  87. package/dist/presenters/app-env.js +0 -265
  88. package/dist/presenters/app.js +0 -672
  89. package/dist/presenters/auth.js +0 -184
  90. package/dist/presenters/branch.js +0 -46
  91. package/dist/presenters/bucket.js +0 -174
  92. package/dist/presenters/database.js +0 -462
  93. package/dist/presenters/feedback.js +0 -26
  94. package/dist/presenters/init.js +0 -30
  95. package/dist/presenters/project.js +0 -268
  96. package/dist/presenters/verbose-context.js +0 -64
  97. package/dist/presenters/version.js +0 -29
  98. package/dist/shell/cli-command.js +0 -2
  99. package/dist/shell/command-meta.js +0 -923
  100. package/dist/shell/command-runner.js +0 -119
  101. package/dist/shell/diagnostics-output.js +0 -57
  102. package/dist/shell/global-flags.js +0 -37
  103. package/dist/shell/help.js +0 -90
  104. package/dist/shell/output.js +0 -144
  105. package/dist/shell/prompt.js +0 -48
  106. package/dist/shell/runtime.js +0 -38
  107. package/dist/shell/ui.js +0 -116
  108. package/dist/state-dir.js +0 -12
  109. package/dist/update-check.js +0 -248
  110. package/dist/v8/cli.js +0 -14699
@@ -1,471 +0,0 @@
1
- import { getApiBaseUrl } from "./client.js";
2
- import { FileTokenStorage } from "./token-storage.js";
3
- import { AuthError, createManagementApiSdk } from "@prisma/management-api-sdk";
4
- import events from "node:events";
5
- import http from "node:http";
6
- import readline from "node:readline/promises";
7
- import open from "open";
8
- //#region src/auth/login.ts
9
- var AuthError$1 = class extends Error {
10
- constructor(message) {
11
- super(message);
12
- this.name = "AuthError";
13
- }
14
- };
15
- async function login(options = {}) {
16
- const hostname = options.hostname ?? "localhost";
17
- const port = options.port ?? 0;
18
- const input = options.input ?? process.stdin;
19
- const output = options.output ?? process.stderr;
20
- const interactive = input.isTTY === true;
21
- const server = http.createServer();
22
- server.listen({
23
- host: hostname,
24
- port
25
- });
26
- const pasteAbort = new AbortController();
27
- try {
28
- const state = new LoginState({
29
- hostname,
30
- port: (await events.once(server, "listening").then(() => server.address())).port,
31
- tokenStorage: options.tokenStorage,
32
- clientId: options.clientId,
33
- apiBaseUrl: options.apiBaseUrl,
34
- authBaseUrl: options.authBaseUrl,
35
- openUrl: options.openUrl,
36
- onVerificationUrl: options.onVerificationUrl,
37
- env: options.env,
38
- signal: options.signal,
39
- output
40
- });
41
- let completed = false;
42
- let completion;
43
- const completeOnce = (url) => {
44
- if (!completion) completion = state.handleCallback(url).then(() => {
45
- completed = true;
46
- }, (error) => {
47
- completion = void 0;
48
- throw error;
49
- });
50
- return completion;
51
- };
52
- const httpResult = new Promise((resolve, reject) => {
53
- const onAbort = () => {
54
- reject(options.signal?.reason);
55
- };
56
- options.signal?.addEventListener("abort", onAbort, { once: true });
57
- const settle = (callback) => {
58
- options.signal?.removeEventListener("abort", onAbort);
59
- callback();
60
- };
61
- server.on("request", async (req, res) => {
62
- const url = new URL(`http://${state.host}${req.url}`);
63
- if (url.pathname !== "/auth/callback") {
64
- res.statusCode = 404;
65
- res.end("Not found");
66
- return;
67
- }
68
- if (completed) {
69
- const workspaceName = await state.resolveWorkspaceName();
70
- res.setHeader("Content-Type", "text/html; charset=utf-8");
71
- res.end(renderSuccessPage(workspaceName));
72
- return;
73
- }
74
- try {
75
- await completeOnce(url);
76
- const workspaceName = await state.resolveWorkspaceName();
77
- res.setHeader("Content-Type", "text/html; charset=utf-8");
78
- res.end(renderSuccessPage(workspaceName));
79
- settle(resolve);
80
- } catch (error) {
81
- res.statusCode = 400;
82
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
83
- res.end("Sign-in could not be completed. Return to your terminal.");
84
- settle(() => reject(error));
85
- return;
86
- }
87
- });
88
- });
89
- options.signal?.throwIfAborted();
90
- const callbackResult = interactive ? Promise.race([httpResult, consumePastedCallback({
91
- input,
92
- output,
93
- signal: pasteAbort.signal,
94
- complete: completeOnce
95
- })]) : httpResult;
96
- await Promise.all([state.openLoginPage(interactive), callbackResult]);
97
- } finally {
98
- pasteAbort.abort();
99
- if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
100
- }
101
- }
102
- async function consumePastedCallback(options) {
103
- if (!options.input.isTTY) return;
104
- const rl = readline.createInterface({
105
- input: options.input,
106
- output: options.output
107
- });
108
- try {
109
- for (;;) {
110
- const url = await readPastedCallbackUrl(rl, options);
111
- if (url === null) return;
112
- if (url === void 0) continue;
113
- if (await tryCompletePastedCallback(url, options)) return;
114
- }
115
- } finally {
116
- rl.close();
117
- }
118
- }
119
- async function readPastedCallbackUrl(rl, options) {
120
- let answer;
121
- try {
122
- answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
123
- } catch (error) {
124
- if (error?.name === "AbortError") return null;
125
- throw error;
126
- }
127
- const trimmed = answer.trim().replace(/^["']|["']$/g, "");
128
- try {
129
- if (!trimmed) throw new Error("empty input");
130
- return new URL(trimmed);
131
- } catch {
132
- options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
133
- return;
134
- }
135
- }
136
- async function tryCompletePastedCallback(url, options) {
137
- try {
138
- await options.complete(url);
139
- return true;
140
- } catch (error) {
141
- const message = error instanceof Error ? error.message : String(error);
142
- options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
143
- return false;
144
- }
145
- }
146
- var LoginState = class {
147
- latestVerifier;
148
- latestState;
149
- sdk;
150
- openUrl;
151
- tokenStorage;
152
- output;
153
- constructor(options) {
154
- this.options = options;
155
- this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal, { activateOnSetTokens: true });
156
- this.sdk = createManagementApiSdk({
157
- clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
158
- redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
159
- tokenStorage: this.tokenStorage,
160
- apiBaseUrl: options.apiBaseUrl ?? getApiBaseUrl(options.env),
161
- authBaseUrl: options.authBaseUrl
162
- });
163
- this.openUrl = options.openUrl ?? open;
164
- this.output = options.output;
165
- }
166
- async openLoginPage(interactive) {
167
- this.options.signal?.throwIfAborted();
168
- const { url, state, verifier } = await this.sdk.getLoginUrl({
169
- scope: "workspace:admin offline_access",
170
- additionalParams: {
171
- utm_source: "prisma-cli",
172
- utm_medium: "command-login",
173
- utm_campaign: "prisma-cli"
174
- }
175
- });
176
- this.latestState = state;
177
- this.latestVerifier = verifier;
178
- try {
179
- this.options.onVerificationUrl?.(url);
180
- } catch {}
181
- this.options.signal?.throwIfAborted();
182
- if (interactive) this.printLoginInstructions(url);
183
- try {
184
- await this.openUrl(url);
185
- } catch (error) {
186
- if (!interactive) throw error;
187
- }
188
- this.options.signal?.throwIfAborted();
189
- }
190
- printLoginInstructions(url) {
191
- const output = this.output;
192
- if (!output) return;
193
- output.write(`\nOpen this URL to sign in: ${url}\n\nIf the browser opens on another machine, finish sign-in there. When it\nredirects to localhost, copy the full localhost URL from the address bar\nand paste it here.\n\n`);
194
- }
195
- async handleCallback(url) {
196
- if (url.pathname !== "/auth/callback") throw new AuthError$1("Not a callback URL");
197
- const params = url.searchParams;
198
- const error = params.get("error");
199
- if (error) {
200
- const desc = params.get("error_description");
201
- throw new AuthError$1(desc ? `${error}: ${desc}` : error);
202
- }
203
- if (!this.latestVerifier) throw new AuthError$1("No verifier found");
204
- if (!this.latestState) throw new AuthError$1("No state found");
205
- try {
206
- await this.sdk.handleCallback({
207
- callbackUrl: url,
208
- verifier: this.latestVerifier,
209
- expectedState: this.latestState
210
- });
211
- } catch (error) {
212
- if (error instanceof AuthError) throw new AuthError$1(error.message);
213
- throw new AuthError$1(error instanceof Error ? error.message : "Unknown error during login");
214
- }
215
- }
216
- async resolveWorkspaceName() {
217
- try {
218
- const tokens = await this.tokenStorage.getTokens();
219
- if (!tokens?.workspaceId) return null;
220
- const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", {
221
- params: { path: { id: tokens.workspaceId } },
222
- signal: this.options.signal
223
- });
224
- const name = data?.data?.name;
225
- return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
226
- } catch {
227
- this.options.signal?.throwIfAborted();
228
- return null;
229
- }
230
- }
231
- get host() {
232
- return `${this.options.hostname}:${this.options.port}`;
233
- }
234
- };
235
- function renderSuccessPage(workspaceName) {
236
- return `<!doctype html>
237
- <html lang="en">
238
- <head>
239
- <meta charset="utf-8">
240
- <meta name="viewport" content="width=device-width, initial-scale=1">
241
- <title>Prisma Developer Platform</title>
242
- <style>
243
- :root {
244
- color-scheme: light dark;
245
- --background: #ffffff;
246
- --foreground: #1f2430;
247
- --muted: #4f5665;
248
- --mark-color: #050812;
249
- --surface: #f6f7fb;
250
- --border: #e4e7ee;
251
- --success: #15803d;
252
- }
253
-
254
- @media (prefers-color-scheme: dark) {
255
- :root {
256
- --background: #050812;
257
- --foreground: #f6f7fb;
258
- --muted: #c5cad6;
259
- --mark-color: #ffffff;
260
- --surface: #0d1322;
261
- --border: #232a3d;
262
- --success: #4ade80;
263
- }
264
- }
265
-
266
- * {
267
- box-sizing: border-box;
268
- }
269
-
270
- body {
271
- min-height: 100vh;
272
- margin: 0;
273
- background: var(--background);
274
- color: var(--foreground);
275
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
276
- display: grid;
277
- grid-template-rows: 128px 1fr;
278
- }
279
-
280
- .mark {
281
- align-self: end;
282
- justify-self: center;
283
- width: 36px;
284
- height: 36px;
285
- color: var(--mark-color);
286
- }
287
-
288
- .mark path {
289
- fill: currentColor !important;
290
- }
291
-
292
- main {
293
- align-self: center;
294
- justify-self: center;
295
- width: min(520px, calc(100vw - 48px));
296
- margin-top: -128px;
297
- text-align: center;
298
- }
299
-
300
- h1 {
301
- margin: 0 0 12px;
302
- font-size: 26px;
303
- line-height: 1.2;
304
- font-weight: 700;
305
- letter-spacing: 0;
306
- }
307
-
308
- p {
309
- margin: 0 auto;
310
- max-width: 480px;
311
- color: var(--muted);
312
- font-size: 15px;
313
- line-height: 1.55;
314
- letter-spacing: 0;
315
- }
316
-
317
- .skills {
318
- margin-top: 40px;
319
- padding-top: 28px;
320
- border-top: 1px solid var(--border);
321
- text-align: left;
322
- }
323
-
324
- .skills-lead {
325
- display: flex;
326
- align-items: center;
327
- gap: 8px;
328
- margin: 0 0 12px;
329
- font-size: 15px;
330
- color: var(--foreground);
331
- }
332
-
333
- .skills-lead svg {
334
- flex: none;
335
- color: var(--muted);
336
- }
337
-
338
- .command {
339
- display: flex;
340
- align-items: center;
341
- gap: 8px;
342
- padding: 10px 8px 10px 16px;
343
- background: var(--surface);
344
- border: 1px solid var(--border);
345
- border-radius: 8px;
346
- }
347
-
348
- .command code {
349
- flex: 1;
350
- overflow-x: auto;
351
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
352
- font-size: 13.5px;
353
- white-space: nowrap;
354
- }
355
-
356
- .command .prompt {
357
- color: var(--muted);
358
- user-select: none;
359
- }
360
-
361
- .copy {
362
- flex: none;
363
- display: grid;
364
- place-items: center;
365
- padding: 6px;
366
- border: 0;
367
- border-radius: 6px;
368
- background: transparent;
369
- color: var(--muted);
370
- cursor: pointer;
371
- }
372
-
373
- .copy:hover {
374
- background: var(--border);
375
- color: var(--foreground);
376
- }
377
-
378
- .copy .icon-check,
379
- .copy.copied .icon-copy {
380
- display: none;
381
- }
382
-
383
- .copy.copied .icon-check {
384
- display: block;
385
- }
386
-
387
- .copy.copied,
388
- .copy.copied:hover {
389
- color: var(--success);
390
- }
391
-
392
- .visually-hidden {
393
- position: absolute;
394
- width: 1px;
395
- height: 1px;
396
- margin: -1px;
397
- padding: 0;
398
- overflow: hidden;
399
- clip: rect(0 0 0 0);
400
- white-space: nowrap;
401
- border: 0;
402
- }
403
- </style>
404
- </head>
405
- <body>
406
- <svg class="mark" width="36" height="36" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M25.21,24.21,12.739,27.928a.525.525,0,0,1-.667-.606L16.528,5.811a.43.43,0,0,1,.809-.094l8.249,17.661A.6.6,0,0,1,25.21,24.21Zm2.139-.878L17.8,2.883h0A1.531,1.531,0,0,0,16.491,2a1.513,1.513,0,0,0-1.4.729L4.736,19.648a1.592,1.592,0,0,0,.018,1.7l5.064,7.909a1.628,1.628,0,0,0,1.83.678l14.7-4.383a1.6,1.6,0,0,0,1-2.218Z" style="fill:#0c344b;fill-rule:evenodd"/></svg>
407
- <main>
408
- <h1>You're all set.</h1>
409
- <p>${workspaceName ? `Your terminal is now connected to your ${escapeHtml(workspaceName)} workspace. Head back to your terminal to continue.` : "Your terminal is now connected to your Prisma workspace. Head back to your terminal to continue."}</p>
410
- <section class="skills">
411
- <div class="skills-lead">
412
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/></svg>
413
- Using an AI coding agent? Add the Prisma skills:
414
- </div>
415
- <div class="command">
416
- <code><span class="prompt">$ </span><span class="command-text">npx skills add prisma/skills</span></code>
417
- <button class="copy" type="button" aria-label="Copy command to clipboard">
418
- <svg class="icon-copy" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
419
- <svg class="icon-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
420
- </button>
421
- </div>
422
- <span class="visually-hidden skills-status" role="status"></span>
423
- </section>
424
- </main>
425
- <script>
426
- (() => {
427
- const command = "npx skills add prisma/skills";
428
- const button = document.querySelector(".copy");
429
- const status = document.querySelector(".skills-status");
430
- let timer;
431
- button.addEventListener("click", async () => {
432
- try {
433
- await navigator.clipboard.writeText(command);
434
- } catch {
435
- // Clipboard access denied: select the command so Cmd/Ctrl+C works.
436
- const range = document.createRange();
437
- range.selectNodeContents(document.querySelector(".command-text"));
438
- const selection = window.getSelection();
439
- selection.removeAllRanges();
440
- selection.addRange(range);
441
- status.textContent =
442
- "Copying failed. The command is selected; press Ctrl+C or Cmd+C to copy it.";
443
- return;
444
- }
445
- button.classList.add("copied");
446
- status.textContent = "Command copied to clipboard.";
447
- clearTimeout(timer);
448
- timer = setTimeout(() => {
449
- button.classList.remove("copied");
450
- status.textContent = "";
451
- }, 2000);
452
- });
453
- })();
454
- <\/script>
455
- </body>
456
- </html>`;
457
- }
458
- function escapeHtml(value) {
459
- return value.replace(/[&<>"']/g, (char) => {
460
- switch (char) {
461
- case "&": return "&amp;";
462
- case "<": return "&lt;";
463
- case ">": return "&gt;";
464
- case "\"": return "&quot;";
465
- case "'": return "&#39;";
466
- default: return char;
467
- }
468
- });
469
- }
470
- //#endregion
471
- export { AuthError$1 as AuthError, login };
@@ -1,208 +0,0 @@
1
- import { FileTokenStorage } from "./token-storage.js";
2
- import { authenticatedManagementApiClient } from "./guard.js";
3
- import { AuthError, login } from "./login.js";
4
- import { SERVICE_TOKEN_ENV_VAR, claimedExpiresAt, credentialWorkspaceId } from "@prisma/cli-engine";
5
- //#region src/auth/operations.ts
6
- const WORKSPACE_SUB_PREFIX = "workspace:";
7
- /** Thrown when PRISMA_SERVICE_TOKEN is set to an empty/blank value. */
8
- var EmptyServiceTokenError = class extends Error {
9
- code = "EMPTY_SERVICE_TOKEN";
10
- constructor() {
11
- super(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
12
- this.name = "EmptyServiceTokenError";
13
- }
14
- };
15
- /** Matches by the code discriminator, not instanceof, so the check
16
- * survives duplicate module instances. */
17
- function isEmptyServiceTokenError(error) {
18
- return typeof error === "object" && error !== null && error.code === "EMPTY_SERVICE_TOKEN";
19
- }
20
- function decodeJwtPayload(token) {
21
- try {
22
- const payload = token.split(".")[1];
23
- if (!payload) return {};
24
- return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
25
- } catch {
26
- return {};
27
- }
28
- }
29
- function emailFromClaims(claims) {
30
- const email = claims.email;
31
- return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
32
- }
33
- function workspaceIdFromClaims(claims) {
34
- const sub = claims.sub;
35
- if (typeof sub !== "string") return null;
36
- if (!sub.startsWith(WORKSPACE_SUB_PREFIX)) return null;
37
- const id = sub.slice(10).trim();
38
- return id.length > 0 ? id : null;
39
- }
40
- /** Holds the tokens the SDK writes at callback time and nothing else:
41
- * minting and custody stay separate, so a login never writes through
42
- * the credential manager. */
43
- var ThrowawayTokenStorage = class {
44
- tokens = null;
45
- async getTokens() {
46
- return this.tokens;
47
- }
48
- async setTokens(tokens) {
49
- this.tokens = tokens;
50
- }
51
- async clearTokens() {
52
- this.tokens = null;
53
- }
54
- };
55
- /** Runs the browser consent flow and RETURNS the minted credential.
56
- * Storing it is the caller's job. */
57
- async function performLogin(env, signal, options) {
58
- const tokenStorage = new ThrowawayTokenStorage();
59
- await login({
60
- tokenStorage,
61
- env,
62
- signal,
63
- onVerificationUrl: options?.onVerificationUrl
64
- });
65
- const tokens = tokenStorage.tokens;
66
- if (!tokens) throw new AuthError("Sign-in finished without producing a credential.");
67
- return {
68
- token: tokens.accessToken,
69
- refreshToken: tokens.refreshToken,
70
- expiresAt: claimedExpiresAt(tokens.accessToken)
71
- };
72
- }
73
- /** Writes a minted credential into the legacy store, which the legacy
74
- * shell still reads. Dies with the legacy shell. */
75
- async function storeLegacyCredential(env, credential, signal) {
76
- const workspaceId = credentialWorkspaceId(credential.token);
77
- if (workspaceId === void 0) return;
78
- await new FileTokenStorage(env, signal, { activateOnSetTokens: true }).setTokens({
79
- workspaceId,
80
- accessToken: credential.token,
81
- ...credential.refreshToken === void 0 ? {} : { refreshToken: credential.refreshToken }
82
- });
83
- }
84
- async function readAuthState(env, signal) {
85
- const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
86
- if (rawServiceToken !== void 0) {
87
- const serviceToken = rawServiceToken.trim();
88
- if (serviceToken.length === 0) throw new EmptyServiceTokenError();
89
- return readServiceTokenAuthState(serviceToken, env, signal);
90
- }
91
- const tokenStorage = new FileTokenStorage(env, signal);
92
- const tokens = await tokenStorage.getTokens();
93
- if (!tokens) return {
94
- authenticated: false,
95
- provider: null,
96
- user: null,
97
- workspace: null,
98
- credential: null
99
- };
100
- const client = await authenticatedManagementApiClient(env, signal);
101
- const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
102
- if (currentPrincipal) {
103
- if (currentPrincipal.authenticated && currentPrincipal.workspace) await tokenStorage.rememberWorkspace?.(tokens.workspaceId, currentPrincipal.workspace);
104
- return currentPrincipal;
105
- }
106
- const claims = decodeJwtPayload(tokens.accessToken);
107
- const authState = await buildAuthState({
108
- workspaceIdFromCredential: tokens.workspaceId,
109
- claims,
110
- env,
111
- client,
112
- signal
113
- });
114
- if (authState.authenticated && authState.workspace) await tokenStorage.rememberWorkspace?.(tokens.workspaceId, authState.workspace);
115
- return authState;
116
- }
117
- async function readServiceTokenAuthState(token, env, signal) {
118
- const client = await authenticatedManagementApiClient(env, signal);
119
- const currentPrincipal = await readCurrentPrincipalAuthState(client, signal);
120
- if (currentPrincipal) return currentPrincipal;
121
- const claims = decodeJwtPayload(token);
122
- const workspaceId = workspaceIdFromClaims(claims);
123
- if (!workspaceId) return {
124
- authenticated: false,
125
- provider: null,
126
- user: null,
127
- workspace: null,
128
- credential: null
129
- };
130
- return buildAuthState({
131
- workspaceIdFromCredential: workspaceId,
132
- claims,
133
- env,
134
- client,
135
- signal
136
- });
137
- }
138
- async function buildAuthState({ workspaceIdFromCredential, claims, env, client, signal }) {
139
- let workspaceId = workspaceIdFromCredential;
140
- let workspaceName = workspaceIdFromCredential;
141
- client ??= await authenticatedManagementApiClient(env, signal);
142
- if (client) try {
143
- const { data, response } = await client.GET("/v1/workspaces/{id}", {
144
- params: { path: { id: workspaceIdFromCredential } },
145
- signal
146
- });
147
- if (response?.status === 401) return {
148
- authenticated: false,
149
- provider: null,
150
- user: null,
151
- workspace: null,
152
- credential: null
153
- };
154
- if (data?.data?.id) {
155
- workspaceId = data.data.id;
156
- workspaceName = data.data.id;
157
- }
158
- if (data?.data?.name) workspaceName = data.data.name;
159
- } catch {
160
- signal?.throwIfAborted();
161
- }
162
- const email = emailFromClaims(claims);
163
- return {
164
- authenticated: true,
165
- provider: null,
166
- user: email ? { email } : null,
167
- workspace: {
168
- id: workspaceId,
169
- name: workspaceName
170
- },
171
- credential: null
172
- };
173
- }
174
- async function readCurrentPrincipalAuthState(client, signal) {
175
- if (!client) return null;
176
- try {
177
- const { data, response } = await client.GET("/v1/me", { signal });
178
- if (response?.status === 401) return {
179
- authenticated: false,
180
- provider: null,
181
- user: null,
182
- workspace: null,
183
- credential: null
184
- };
185
- const principal = data?.data;
186
- if (!principal) return null;
187
- if (!principal.credential) return null;
188
- return {
189
- authenticated: true,
190
- provider: null,
191
- user: principal.user ? {
192
- id: principal.user.id,
193
- email: principal.user.email,
194
- name: principal.user.name
195
- } : null,
196
- workspace: principal.workspace,
197
- credential: principal.credential
198
- };
199
- } catch {
200
- signal?.throwIfAborted();
201
- return null;
202
- }
203
- }
204
- async function performLogout(env, signal) {
205
- await new FileTokenStorage(env, signal).clearTokens();
206
- }
207
- //#endregion
208
- export { isEmptyServiceTokenError, performLogin, performLogout, readAuthState, storeLegacyCredential };