void 0.20.1 → 0.20.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 (87) hide show
  1. package/README.md +5 -1
  2. package/dist/{auth-W9WII-mN.mjs → auth-DPl6kck4.mjs} +46 -26
  3. package/dist/{auth-cmd-CAH62yDU.mjs → auth-cmd-gniL2fNt.mjs} +5 -4
  4. package/dist/auth-link-NZdjCmSc.mjs +28 -0
  5. package/dist/{build-cmd-CJvZvPQO.mjs → build-cmd-sI18tX_O.mjs} +3 -3
  6. package/dist/{cache-BlNeQjuP.mjs → cache-IHn5MwBC.mjs} +3 -3
  7. package/dist/{cancel-deploy-CmlAZ9P6.mjs → cancel-deploy-C5qTOdLi.mjs} +3 -3
  8. package/dist/cf-access-DRsQRe6k.mjs +75 -0
  9. package/dist/cli/cli.mjs +308 -1958
  10. package/dist/cli/env-schema-probe.mjs +11 -2
  11. package/dist/{client-Clirrol3.mjs → client-dHfSJvAN.mjs} +300 -81
  12. package/dist/{cloudflare-auth-B1QtTO1b.mjs → cloudflare-auth-6M5llVPC.mjs} +2 -2
  13. package/dist/{cloudflare-cmd-B6_OZx2V.mjs → cloudflare-cmd-4RPGN3KB.mjs} +2 -2
  14. package/dist/{cloudflare-connect-j5D4hhrG.mjs → cloudflare-connect-t1UU5svD.mjs} +2 -2
  15. package/dist/{cloudflare-operations-CPTpRW6d.mjs → cloudflare-operations-BzWnlC1_.mjs} +1 -1
  16. package/dist/{config-BQFq7QvD.mjs → config-uNGuFsI2.mjs} +1 -1
  17. package/dist/{connect-C04Wdy_h.mjs → connect-Bfk31O_8.mjs} +6 -6
  18. package/dist/{create-project-ChGZ1DFd.mjs → create-project-Bk9Z0-Jg.mjs} +6 -5
  19. package/dist/{db-D2d_mUsB.mjs → db-BkRoptAt.mjs} +47 -30
  20. package/dist/{delete-D8GigDk8.mjs → delete-DouASY9P.mjs} +3 -3
  21. package/dist/{deploy-iXZ3F0N6.mjs → deploy-DTaWUS1S.mjs} +121 -110
  22. package/dist/{dev-inbox-DkgRWLkW.mjs → dev-inbox-P0u4tM8Y.mjs} +1 -1
  23. package/dist/{domain-B1VmoSr0.mjs → domain-1RhhOVrC.mjs} +4 -4
  24. package/dist/email-C-lGh51B.mjs +795 -0
  25. package/dist/{env-D4Emu-M_.mjs → env-DBKmK4vc.mjs} +1 -0
  26. package/dist/{env-BcQzYgoG.mjs → env-DJHsPE7Z.mjs} +5 -5
  27. package/dist/{env-validation-ENpMy6Ez.mjs → env-validation-CF6KvTRf.mjs} +3 -1
  28. package/dist/{gen-DI2YwdBM.mjs → gen-B_wPnVTK.mjs} +2 -2
  29. package/dist/{github-cmd-xItS5Zwf.mjs → github-cmd-PW7ZnWTp.mjs} +3 -3
  30. package/dist/{headers-D8QfRX9Y.mjs → headers-BAHwgHdW.mjs} +1 -1
  31. package/dist/help-CwOX-zmI.mjs +2216 -0
  32. package/dist/{inbound-afAcWeQ9.d.mts → inbound-CH5Mksyy.d.mts} +34 -48
  33. package/dist/{inbound-2d0zi2yS.mjs → inbound-aVHEUhKo.mjs} +130 -100
  34. package/dist/index.mjs +54 -17
  35. package/dist/{init-BD-9THgn.mjs → init-BWZ7q5Z4.mjs} +11 -11
  36. package/dist/{link-RMdgjF1v.mjs → link-Rmvu2Wl_.mjs} +4 -4
  37. package/dist/{list-3F52R_yO.mjs → list-DEE2S6mY.mjs} +4 -4
  38. package/dist/{login-pV69H-ZO.mjs → login-Uvferzmm.mjs} +28 -10
  39. package/dist/{logs-DFHHD6wE.mjs → logs-27FenuiC.mjs} +4 -4
  40. package/dist/{mime-BJD7d_qL.mjs → mime-D5Nmdzf7.mjs} +23 -9
  41. package/dist/{node-Dk3H2jmU.mjs → node-Ez5KW5rn.mjs} +2 -2
  42. package/dist/operator-auth-B3e08unv.mjs +52 -0
  43. package/dist/operator-client-LUZnlnYk.mjs +82 -0
  44. package/dist/{operator-cmd-DYWRbWUA.mjs → operator-cmd-CjOTmAYE.mjs} +35 -55
  45. package/dist/{output-tFQLLj26.mjs → output-B0cfNSx5.mjs} +316 -2
  46. package/dist/pages/index.mjs +2 -2
  47. package/dist/platform-auth-config-DrbQXXiW.mjs +368 -0
  48. package/dist/platform-auth-protection-Bhtvp0B_.mjs +219 -0
  49. package/dist/platform-auth-recovery-CeOKGVeJ.mjs +310 -0
  50. package/dist/{platform-cmd-DxJ2FRwR.mjs → platform-cmd-BFhieCdV.mjs} +16 -6
  51. package/dist/{platform-domain-ChvbJkdy.mjs → platform-domain-C74PULqV.mjs} +4 -4
  52. package/dist/{platform-lifecycle-DN4MzJF_.mjs → platform-lifecycle-BwAIgz-t.mjs} +1667 -191
  53. package/dist/{platform-management-Db2PXw0B.mjs → platform-management-COogu_Se.mjs} +33 -7
  54. package/dist/{platform-recovery-C_YO-tIs.mjs → platform-recovery-ewqLefp1.mjs} +6 -5
  55. package/dist/{prepare-CBetXvsN.mjs → prepare-CtDJjoOj.mjs} +2 -2
  56. package/dist/{prepare-BfJvFUtJ.mjs → prepare-blNRQvQl.mjs} +2 -2
  57. package/dist/prerender-render.d.mts +11 -0
  58. package/dist/prerender-render.mjs +111 -0
  59. package/dist/{project-cmd-Mo0V9yKS.mjs → project-cmd-DmZK9Hxf.mjs} +32 -14
  60. package/dist/project-team-D8jOJMUJ.mjs +130 -0
  61. package/dist/project-token-DA34bf-C.mjs +75 -0
  62. package/dist/{provision-Blnstcm2.mjs → provision-CSJOjjQk.mjs} +2 -0
  63. package/dist/{requests-BcKOVpRg.mjs → requests-CUExwGQQ.mjs} +3 -3
  64. package/dist/{rollback-Bx85-0xh.mjs → rollback-CDNGU1gr.mjs} +4 -4
  65. package/dist/runtime/ai.mjs +3 -2
  66. package/dist/runtime/email/testing.d.mts +1 -1
  67. package/dist/runtime/email/testing.mjs +3 -3
  68. package/dist/runtime/email-protocol.d.mts +15 -0
  69. package/dist/runtime/email-protocol.mjs +70 -0
  70. package/dist/runtime/email.d.mts +2 -2
  71. package/dist/runtime/email.mjs +189 -96
  72. package/dist/runtime/remote/index.mjs +5 -3
  73. package/dist/{secret-ByhJ9AMl.mjs → secret-Bzzi2e9E.mjs} +5 -5
  74. package/dist/{skills-Q46GZMO-.mjs → skills-C0RvGjeE.mjs} +1 -1
  75. package/dist/{subcommand-prompt-WfySCQ7S.mjs → subcommand-prompt-Bmyn5Rlc.mjs} +1 -1
  76. package/package.json +12 -7
  77. package/skills/void/SKILL.md +35 -4
  78. package/skills/void/docs/guide/deployment.md +2 -0
  79. package/skills/void/docs/guide/email.md +102 -110
  80. package/skills/void/docs/guide/platform-administration.md +214 -2
  81. package/skills/void/docs/guide/platform-development.md +64 -2
  82. package/skills/void/docs/guide/project-collaboration.md +94 -0
  83. package/skills/void/docs/guide/self-hosted-platform.md +184 -30
  84. package/skills/void/docs/reference/cli.md +249 -21
  85. package/dist/cf-access-AJ1ehiFR.mjs +0 -42
  86. package/dist/cf-access-DsSsZUPr.mjs +0 -67
  87. package/dist/email-uKyQYUVY.mjs +0 -1016
@@ -1,9 +1,8 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-rQ84J-ij.mjs";
2
2
  import { o as join } from "./dist-BrsS7cai.mjs";
3
- import { G as normalizePlatformApiUrl, p as getPackageDir } from "./output-tFQLLj26.mjs";
4
- import { t as cfAccessHeaders } from "./cf-access-AJ1ehiFR.mjs";
5
- import { a as getApiUrl } from "./auth-W9WII-mN.mjs";
6
- import { t as ensureCloudflaredToken } from "./cf-access-DsSsZUPr.mjs";
3
+ import { B as ensureCloudflaredToken, U as isCloudflareAccessChallenge, V as ensurePlatformAccessToken, X as normalizePlatformApiUrl, p as getPackageDir } from "./output-B0cfNSx5.mjs";
4
+ import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
5
+ import { o as getApiUrl } from "./auth-DPl6kck4.mjs";
7
6
  import { readFileSync } from "node:fs";
8
7
  //#region src/cli/client.ts
9
8
  var client_exports = /* @__PURE__ */ __exportAll({
@@ -80,11 +79,34 @@ function isExpiredTokenError(error) {
80
79
  return false;
81
80
  }
82
81
  /**
82
+ * Retry contract for the deploy POST's transport phase.
83
+ *
84
+ * - Only `fetch` rejections (`TypeError` — no HTTP response) are retried.
85
+ * A Response of any status means the server answered; surface it
86
+ * verbatim rather than replay a request it may have accepted.
87
+ * - A rejected fetch does NOT prove the request never arrived — the
88
+ * connection may have dropped after delivery. Arbitration is left to
89
+ * the server's precheck, the only atomic (project-locked) verdict: if
90
+ * an earlier attempt or a concurrent deploy is still in flight, the
91
+ * replay is rejected with 409 and surfaced as a deploy conflict
92
+ * naming the deployment id. A client-side GET-then-decide lookup can
93
+ * never be authoritative (the row can transition between lookup and
94
+ * replay), so no dedupe query is made — the residual case is the lost
95
+ * attempt completing within seconds and being redeployed identically.
96
+ */
97
+ const DEPLOY_POST_MAX_RETRIES = 3;
98
+ const DEPLOY_POST_BACKOFF_MS = [
99
+ 1e3,
100
+ 5e3,
101
+ 1e4
102
+ ];
103
+ /**
83
104
  * HTTP client for the Void platform API.
84
105
  */
85
106
  var PlatformClient = class {
86
107
  baseUrl;
87
108
  authHeaders;
109
+ accessHeaders;
88
110
  cliLog;
89
111
  apiUrl;
90
112
  constructor(token, options) {
@@ -95,31 +117,179 @@ var PlatformClient = class {
95
117
  }
96
118
  this.apiUrl = this.baseUrl;
97
119
  ensureCloudflaredToken(this.baseUrl);
120
+ this.accessHeaders = cfAccessHeaders(process.env, this.baseUrl);
98
121
  this.authHeaders = {
99
122
  Authorization: `Bearer ${token}`,
100
- ...VOID_CLI_VERSION !== null && { "X-Void-CLI-Version": VOID_CLI_VERSION },
101
- ...cfAccessHeaders(process.env, this.baseUrl)
123
+ ...VOID_CLI_VERSION !== null && { "X-Void-CLI-Version": VOID_CLI_VERSION }
124
+ };
125
+ }
126
+ async request(url, options = {}) {
127
+ if (new URL(url).origin !== this.baseUrl) throw new Error("Platform credentials cannot be sent to another origin.");
128
+ if (!this.accessHeaders["CF-Access-Client-Id"]) ensureCloudflaredToken(this.baseUrl);
129
+ const send = () => {
130
+ const currentAccessHeaders = cfAccessHeaders(process.env, this.baseUrl);
131
+ const accessHeaders = this.accessHeaders["CF-Access-Client-Id"] ? this.accessHeaders : Object.keys(currentAccessHeaders).length > 0 ? currentAccessHeaders : this.accessHeaders;
132
+ return {
133
+ accessHeaders,
134
+ response: fetch(url, {
135
+ ...options,
136
+ headers: {
137
+ ...options.headers,
138
+ ...accessHeaders
139
+ },
140
+ redirect: "manual"
141
+ })
142
+ };
102
143
  };
144
+ const first = send();
145
+ const response = await first.response;
146
+ if (!isCloudflareAccessChallenge(response)) return response;
147
+ if (first.accessHeaders["CF-Access-Client-Id"]) return response;
148
+ await response.body?.cancel();
149
+ ensurePlatformAccessToken(this.baseUrl, true, false, first.accessHeaders["cf-access-token"]);
150
+ return send().response;
151
+ }
152
+ async startIdentityLink(connectionId) {
153
+ const response = await this.request(this.baseUrl + "/auth/link", {
154
+ method: "POST",
155
+ headers: {
156
+ ...this.authHeaders,
157
+ "Content-Type": "application/json"
158
+ },
159
+ body: JSON.stringify({ connectionId })
160
+ });
161
+ if (!response.ok) await throwPlatformApiError("Failed to link login method", response);
162
+ const result = await response.json();
163
+ const url = new URL(result.url);
164
+ if (url.origin !== this.baseUrl || url.pathname !== "/auth/link" || url.username || url.password) throw new Error("auth: Invalid account-link URL from platform.");
165
+ return result;
103
166
  }
104
167
  async whoami() {
105
- const res = await fetch(`${this.baseUrl}/me`, { headers: { ...this.authHeaders } });
168
+ const res = await this.request(`${this.baseUrl}/me`, { headers: { ...this.authHeaders } });
106
169
  if (!res.ok) await throwPlatformApiError("Failed to fetch user info", res);
107
170
  return res.json();
108
171
  }
109
172
  async listProjects() {
110
- const res = await fetch(`${this.baseUrl}/projects`, { headers: { ...this.authHeaders } });
173
+ const res = await this.request(`${this.baseUrl}/projects`, { headers: { ...this.authHeaders } });
111
174
  if (!res.ok) await throwPlatformApiError("Failed to list projects", res);
112
175
  return res.json();
113
176
  }
177
+ async listProjectTokens(projectId) {
178
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/tokens`, { headers: { ...this.authHeaders } });
179
+ if (!res.ok) await throwPlatformApiError("Failed to list project tokens", res);
180
+ return res.json();
181
+ }
182
+ async createProjectToken(projectId, input) {
183
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/tokens`, {
184
+ method: "POST",
185
+ headers: {
186
+ ...this.authHeaders,
187
+ "Content-Type": "application/json"
188
+ },
189
+ body: JSON.stringify(input)
190
+ });
191
+ if (!res.ok) await throwPlatformApiError("Failed to create project token", res);
192
+ return res.json();
193
+ }
194
+ async listProjectMembers(projectId) {
195
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/members`, { headers: { ...this.authHeaders } });
196
+ if (!res.ok) await throwPlatformApiError("Failed to list project members", res);
197
+ return (await res.json()).members;
198
+ }
199
+ async listProjectInvitations(projectId) {
200
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/invitations`, { headers: { ...this.authHeaders } });
201
+ if (!res.ok) await throwPlatformApiError("Failed to list project invitations", res);
202
+ return (await res.json()).invitations;
203
+ }
204
+ async inviteProjectMember(projectId, email, role) {
205
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/invitations`, {
206
+ method: "POST",
207
+ headers: {
208
+ "Content-Type": "application/json",
209
+ ...this.authHeaders
210
+ },
211
+ body: JSON.stringify({
212
+ email,
213
+ role
214
+ })
215
+ });
216
+ if (!res.ok) await throwPlatformApiError("Failed to invite project member", res);
217
+ return res.json();
218
+ }
219
+ async renewProjectToken(projectId, credentialId, expiresInDays) {
220
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/tokens/${encodeURIComponent(credentialId)}/renew`, {
221
+ method: "POST",
222
+ headers: {
223
+ ...this.authHeaders,
224
+ "Content-Type": "application/json"
225
+ },
226
+ body: JSON.stringify(expiresInDays ? { expiresInDays } : {})
227
+ });
228
+ if (!res.ok) await throwPlatformApiError("Failed to renew project token", res);
229
+ return res.json();
230
+ }
231
+ async updateProjectMemberRole(projectId, userId, role) {
232
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/members/${encodeURIComponent(userId)}`, {
233
+ method: "PATCH",
234
+ headers: {
235
+ "Content-Type": "application/json",
236
+ ...this.authHeaders
237
+ },
238
+ body: JSON.stringify({ role })
239
+ });
240
+ if (!res.ok) await throwPlatformApiError("Failed to change project member role", res);
241
+ return res.json();
242
+ }
243
+ async revokeProjectToken(projectId, credentialId) {
244
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/tokens/${encodeURIComponent(credentialId)}`, {
245
+ method: "DELETE",
246
+ headers: { ...this.authHeaders }
247
+ });
248
+ if (!res.ok) await throwPlatformApiError("Failed to revoke project token", res);
249
+ return res.json();
250
+ }
251
+ async removeProjectMember(projectId, userId) {
252
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/members/${encodeURIComponent(userId)}`, {
253
+ method: "DELETE",
254
+ headers: { ...this.authHeaders }
255
+ });
256
+ if (!res.ok) await throwPlatformApiError("Failed to remove project member", res);
257
+ }
258
+ async revokeProjectInvitation(projectId, invitationId) {
259
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/invitations/${encodeURIComponent(invitationId)}`, {
260
+ method: "DELETE",
261
+ headers: { ...this.authHeaders }
262
+ });
263
+ if (!res.ok) await throwPlatformApiError("Failed to revoke project invitation", res);
264
+ }
265
+ async leaveProject(projectId) {
266
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/leave`, {
267
+ method: "POST",
268
+ headers: { ...this.authHeaders }
269
+ });
270
+ if (!res.ok) await throwPlatformApiError("Failed to leave project", res);
271
+ }
272
+ async listPendingProjectInvitations() {
273
+ const res = await this.request(`${this.baseUrl}/me/project-invitations`, { headers: { ...this.authHeaders } });
274
+ if (!res.ok) await throwPlatformApiError("Failed to list pending project invitations", res);
275
+ return (await res.json()).invitations;
276
+ }
277
+ async respondToProjectInvitation(invitationId, action) {
278
+ const res = await this.request(`${this.baseUrl}/project-invitations/${encodeURIComponent(invitationId)}/${action}`, {
279
+ method: "POST",
280
+ headers: { ...this.authHeaders }
281
+ });
282
+ if (!res.ok) await throwPlatformApiError(`Failed to ${action} project invitation`, res);
283
+ }
114
284
  async listDeployments(projectId) {
115
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/deployments`, { headers: { ...this.authHeaders } });
285
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/deployments`, { headers: { ...this.authHeaders } });
116
286
  if (!res.ok) await throwPlatformApiError("Failed to list deployments", res);
117
287
  return res.json();
118
288
  }
119
289
  async rollback(projectId, deploymentId, options) {
120
290
  const body = { deploymentId };
121
291
  if (options?.allowDestructive && options.allowDestructive.length > 0) body.allowDestructive = options.allowDestructive;
122
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/rollback`, {
292
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/rollback`, {
123
293
  method: "POST",
124
294
  headers: {
125
295
  "Content-Type": "application/json",
@@ -131,7 +301,7 @@ var PlatformClient = class {
131
301
  return res.json();
132
302
  }
133
303
  async cancelDeployment(projectId, deploymentId) {
134
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/deployments/${deploymentId}/cancel`, {
304
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/deployments/${deploymentId}/cancel`, {
135
305
  method: "POST",
136
306
  headers: { ...this.authHeaders }
137
307
  });
@@ -139,7 +309,7 @@ var PlatformClient = class {
139
309
  return res.json();
140
310
  }
141
311
  async createProject(slug) {
142
- const res = await fetch(`${this.baseUrl}/projects`, {
312
+ const res = await this.request(`${this.baseUrl}/projects`, {
143
313
  method: "POST",
144
314
  headers: {
145
315
  "Content-Type": "application/json",
@@ -153,7 +323,7 @@ var PlatformClient = class {
153
323
  async preflight(projectId, assetManifest, hasWorker) {
154
324
  const total = Object.keys(assetManifest).length;
155
325
  try {
156
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/deploy/preflight`, {
326
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/deploy/preflight`, {
157
327
  method: "POST",
158
328
  headers: {
159
329
  "Content-Type": "application/json",
@@ -195,7 +365,7 @@ var PlatformClient = class {
195
365
  url,
196
366
  assetCount: assets.length
197
367
  });
198
- const res = await fetch(url, {
368
+ const res = await this.request(url, {
199
369
  method: "POST",
200
370
  headers: {
201
371
  "Content-Type": "application/json",
@@ -221,7 +391,7 @@ var PlatformClient = class {
221
391
  */
222
392
  async *finalizeDeploy(projectId, payload) {
223
393
  const url = `${this.baseUrl}/projects/${projectId}/deploy`;
224
- yield* this.streamDeployRequest(url, {
394
+ yield* this.streamDeployRequest(url, projectId, {
225
395
  method: "POST",
226
396
  headers: {
227
397
  "Content-Type": "application/json",
@@ -234,15 +404,30 @@ var PlatformClient = class {
234
404
  * POST + NDJSON-stream-reader used by `finalizeDeploy()`. Captures
235
405
  * `cf-ray` + `cf-cache-status` for postmortem correlation.
236
406
  */
237
- async *streamDeployRequest(url, init) {
407
+ async *streamDeployRequest(url, projectId, init) {
238
408
  const cliLog = this.cliLog;
239
409
  cliLog?.info("deploy_post", { url });
240
410
  let res;
241
- try {
242
- res = await fetch(url, init);
411
+ let attempt = 0;
412
+ while (true) try {
413
+ res = await this.request(url, init);
414
+ break;
243
415
  } catch (err) {
244
- cliLog?.error("deploy_fetch_rejected", err, { url });
245
- throw err;
416
+ if (!(err instanceof TypeError) || attempt >= DEPLOY_POST_MAX_RETRIES) {
417
+ cliLog?.error("deploy_fetch_rejected", err, {
418
+ url,
419
+ attempts: attempt + 1
420
+ });
421
+ throw err;
422
+ }
423
+ cliLog?.warn("deploy_fetch_retry", {
424
+ url,
425
+ attempt: attempt + 1,
426
+ delayMs: DEPLOY_POST_BACKOFF_MS[attempt],
427
+ error: err instanceof Error ? err.message : String(err)
428
+ });
429
+ await sleep(DEPLOY_POST_BACKOFF_MS[attempt]);
430
+ attempt++;
246
431
  }
247
432
  const cfRay = res.headers.get("cf-ray");
248
433
  const cfCacheStatus = res.headers.get("cf-cache-status");
@@ -252,7 +437,30 @@ var PlatformClient = class {
252
437
  cfCacheStatus,
253
438
  contentType: res.headers.get("content-type")
254
439
  });
255
- if (!res.ok) await throwPlatformApiError("Deploy failed", res);
440
+ if (!res.ok) {
441
+ const body = await res.text();
442
+ if (res.status === 409) {
443
+ let parsed = null;
444
+ try {
445
+ const raw = JSON.parse(body);
446
+ if (raw !== null && typeof raw === "object") parsed = raw;
447
+ } catch {}
448
+ if (parsed?.deployment) {
449
+ cliLog?.warn("deploy_conflict", {
450
+ url,
451
+ deploymentId: parsed.deployment,
452
+ error: parsed.error
453
+ });
454
+ yield {
455
+ event: "error",
456
+ step: "finalize",
457
+ message: `${parsed.error ?? "deploy in progress"}: deployment ${parsed.deployment} — it may be an earlier attempt of this deploy or a concurrent one; inspect with \`void project status\` before redeploying`
458
+ };
459
+ return;
460
+ }
461
+ }
462
+ throw new PlatformApiError("Deploy failed", res.status, body);
463
+ }
256
464
  const reader = res.body.getReader();
257
465
  const decoder = new TextDecoder();
258
466
  let buffer = "";
@@ -305,12 +513,12 @@ var PlatformClient = class {
305
513
  }
306
514
  }
307
515
  async listSecrets(projectId) {
308
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/secrets`, { headers: { ...this.authHeaders } });
516
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/secrets`, { headers: { ...this.authHeaders } });
309
517
  if (!res.ok) await throwPlatformApiError("Failed to list secrets", res);
310
518
  return res.json();
311
519
  }
312
520
  async setSecret(projectId, name, value) {
313
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/secrets/${encodeURIComponent(name)}`, {
521
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/secrets/${encodeURIComponent(name)}`, {
314
522
  method: "PUT",
315
523
  headers: {
316
524
  "Content-Type": "application/json",
@@ -322,7 +530,7 @@ var PlatformClient = class {
322
530
  return res.json();
323
531
  }
324
532
  async deleteProject(projectId) {
325
- const res = await fetch(`${this.baseUrl}/projects/${projectId}`, {
533
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}`, {
326
534
  method: "DELETE",
327
535
  headers: { ...this.authHeaders }
328
536
  });
@@ -332,12 +540,12 @@ var PlatformClient = class {
332
540
  async getMigrationStatus(projectId, dialect) {
333
541
  const url = new URL(`${this.baseUrl}/projects/${projectId}/migrations`);
334
542
  if (dialect) url.searchParams.set("dialect", dialect);
335
- const res = await fetch(url, { headers: { ...this.authHeaders } });
543
+ const res = await this.request(url, { headers: { ...this.authHeaders } });
336
544
  if (!res.ok) await throwPlatformApiError("Failed to get migration status", res);
337
545
  return res.json();
338
546
  }
339
547
  async applyRemoteMigrations(projectId, migrations) {
340
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/migrations/apply`, {
548
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/migrations/apply`, {
341
549
  method: "POST",
342
550
  headers: {
343
551
  "Content-Type": "application/json",
@@ -349,7 +557,7 @@ var PlatformClient = class {
349
557
  return res.json();
350
558
  }
351
559
  async renameMigrations(projectId, renames) {
352
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/migrations/rename`, {
560
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/migrations/rename`, {
353
561
  method: "POST",
354
562
  headers: {
355
563
  "Content-Type": "application/json",
@@ -361,7 +569,7 @@ var PlatformClient = class {
361
569
  return res.json();
362
570
  }
363
571
  async addDomain(projectId, hostname) {
364
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/domains`, {
572
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/domains`, {
365
573
  method: "POST",
366
574
  headers: {
367
575
  "Content-Type": "application/json",
@@ -373,17 +581,17 @@ var PlatformClient = class {
373
581
  return res.json();
374
582
  }
375
583
  async listDomains(projectId) {
376
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/domains`, { headers: { ...this.authHeaders } });
584
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/domains`, { headers: { ...this.authHeaders } });
377
585
  if (!res.ok) await throwPlatformApiError("Failed to list domains", res);
378
586
  return res.json();
379
587
  }
380
588
  async getDomainStatus(projectId, domainId) {
381
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/domains/${domainId}`, { headers: { ...this.authHeaders } });
589
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/domains/${domainId}`, { headers: { ...this.authHeaders } });
382
590
  if (!res.ok) await throwPlatformApiError("Failed to get domain status", res);
383
591
  return res.json();
384
592
  }
385
593
  async removeDomain(projectId, domainId) {
386
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/domains/${domainId}`, {
594
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/domains/${domainId}`, {
387
595
  method: "DELETE",
388
596
  headers: { ...this.authHeaders }
389
597
  });
@@ -391,7 +599,7 @@ var PlatformClient = class {
391
599
  return res.json();
392
600
  }
393
601
  async deleteSecret(projectId, name) {
394
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/secrets/${encodeURIComponent(name)}`, {
602
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/secrets/${encodeURIComponent(name)}`, {
395
603
  method: "DELETE",
396
604
  headers: { ...this.authHeaders }
397
605
  });
@@ -399,12 +607,12 @@ var PlatformClient = class {
399
607
  return res.json();
400
608
  }
401
609
  async getResources(projectId) {
402
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/resources`, { headers: { ...this.authHeaders } });
610
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/resources`, { headers: { ...this.authHeaders } });
403
611
  if (!res.ok) await throwPlatformApiError("Failed to get resources", res);
404
612
  return res.json();
405
613
  }
406
614
  async createPrerenderToken(projectId) {
407
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/prerender-token`, {
615
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/prerender-token`, {
408
616
  method: "POST",
409
617
  headers: { ...this.authHeaders }
410
618
  });
@@ -412,7 +620,7 @@ var PlatformClient = class {
412
620
  return (await res.json()).token;
413
621
  }
414
622
  async provisionDatabase(projectId, connectionString) {
415
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/database`, {
623
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/database`, {
416
624
  method: "POST",
417
625
  headers: {
418
626
  "Content-Type": "application/json",
@@ -424,7 +632,7 @@ var PlatformClient = class {
424
632
  return res.json();
425
633
  }
426
634
  async setDatabaseUrl(projectId, connectionString) {
427
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/database-url`, {
635
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/database-url`, {
428
636
  method: "PUT",
429
637
  headers: {
430
638
  "Content-Type": "application/json",
@@ -436,12 +644,12 @@ var PlatformClient = class {
436
644
  return res.json();
437
645
  }
438
646
  async getDatabaseUrl(projectId) {
439
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/database-url`, { headers: { ...this.authHeaders } });
647
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/database-url`, { headers: { ...this.authHeaders } });
440
648
  if (!res.ok) await throwPlatformApiError("Failed to fetch database URL", res);
441
649
  return res.json();
442
650
  }
443
651
  async purgeCache(projectId) {
444
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/revalidate`, {
652
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/revalidate`, {
445
653
  method: "POST",
446
654
  headers: {
447
655
  "Content-Type": "application/json",
@@ -459,7 +667,7 @@ var PlatformClient = class {
459
667
  });
460
668
  if (deploymentId) params.set("deploymentId", deploymentId);
461
669
  if (level && level !== "all") params.set("level", level);
462
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/logs?${params}`, { headers: { ...this.authHeaders } });
670
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/logs?${params}`, { headers: { ...this.authHeaders } });
463
671
  if (!res.ok) await throwPlatformApiError("Failed to fetch logs", res);
464
672
  return (await res.json()).data;
465
673
  }
@@ -469,34 +677,34 @@ var PlatformClient = class {
469
677
  limit: String(limit)
470
678
  });
471
679
  if (status && status !== "all") params.set("status", status);
472
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/requests?${params}`, { headers: { ...this.authHeaders } });
680
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/requests?${params}`, { headers: { ...this.authHeaders } });
473
681
  if (!res.ok) await throwPlatformApiError("Failed to fetch requests", res);
474
682
  return (await res.json()).data;
475
683
  }
476
684
  async listBuilds(projectId, limit = 20) {
477
685
  const params = new URLSearchParams({ limit: String(limit) });
478
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/builds?${params}`, { headers: { ...this.authHeaders } });
686
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/builds?${params}`, { headers: { ...this.authHeaders } });
479
687
  if (!res.ok) await throwPlatformApiError("Failed to list builds", res);
480
688
  return (await res.json()).builds;
481
689
  }
482
690
  async getBuildLogs(projectId, buildId, since = 0) {
483
691
  const params = new URLSearchParams({ since: String(since) });
484
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/builds/${buildId}/logs?${params}`, { headers: { ...this.authHeaders } });
692
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/builds/${buildId}/logs?${params}`, { headers: { ...this.authHeaders } });
485
693
  if (!res.ok) await throwPlatformApiError("Failed to fetch build logs", res);
486
694
  return res.json();
487
695
  }
488
696
  async getGithubInstallUrl() {
489
- const res = await fetch(`${this.baseUrl}/github/install-url`, { headers: { ...this.authHeaders } });
697
+ const res = await this.request(`${this.baseUrl}/github/install-url`, { headers: { ...this.authHeaders } });
490
698
  if (!res.ok) await throwPlatformApiError("Failed to get GitHub install URL", res);
491
699
  return res.json();
492
700
  }
493
701
  async listGithubInstallations() {
494
- const res = await fetch(`${this.baseUrl}/github/installations`, { headers: { ...this.authHeaders } });
702
+ const res = await this.request(`${this.baseUrl}/github/installations`, { headers: { ...this.authHeaders } });
495
703
  if (!res.ok) await throwPlatformApiError("Failed to list GitHub installations", res);
496
704
  return res.json();
497
705
  }
498
706
  async connectGithub(projectId, body) {
499
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/github`, {
707
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/github`, {
500
708
  method: "POST",
501
709
  headers: {
502
710
  "Content-Type": "application/json",
@@ -522,17 +730,17 @@ var PlatformClient = class {
522
730
  installationId,
523
731
  repoFullName
524
732
  });
525
- const res = await fetch(`${this.baseUrl}/github/connect-authorize-url?${params}`, { headers: { ...this.authHeaders } });
733
+ const res = await this.request(`${this.baseUrl}/github/connect-authorize-url?${params}`, { headers: { ...this.authHeaders } });
526
734
  if (!res.ok) await throwPlatformApiError("Failed to start GitHub connect authorization", res);
527
735
  return res.json();
528
736
  }
529
737
  async getGithubConnection(projectId) {
530
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/github`, { headers: { ...this.authHeaders } });
738
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/github`, { headers: { ...this.authHeaders } });
531
739
  if (!res.ok) await throwPlatformApiError("Failed to get GitHub connection", res);
532
740
  return res.json();
533
741
  }
534
742
  async updateGithubConnection(projectId, body) {
535
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/github`, {
743
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/github`, {
536
744
  method: "PATCH",
537
745
  headers: {
538
746
  "Content-Type": "application/json",
@@ -544,7 +752,7 @@ var PlatformClient = class {
544
752
  return res.json();
545
753
  }
546
754
  async deleteGithubConnection(projectId) {
547
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/github`, {
755
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/github`, {
548
756
  method: "DELETE",
549
757
  headers: { ...this.authHeaders }
550
758
  });
@@ -557,12 +765,12 @@ var PlatformClient = class {
557
765
  code_challenge: codeChallenge,
558
766
  code_challenge_method: "S256"
559
767
  });
560
- const res = await fetch(`${this.baseUrl}/github/link-url?${params}`, { headers: { ...this.authHeaders } });
768
+ const res = await this.request(`${this.baseUrl}/github/link-url?${params}`, { headers: { ...this.authHeaders } });
561
769
  if (!res.ok) await throwPlatformApiError("Failed to start GitHub link", res);
562
770
  return res.json();
563
771
  }
564
772
  async completeGithubLink(linkCode, codeVerifier) {
565
- const res = await fetch(`${this.baseUrl}/github/link/complete`, {
773
+ const res = await this.request(`${this.baseUrl}/github/link/complete`, {
566
774
  method: "POST",
567
775
  headers: {
568
776
  "Content-Type": "application/json",
@@ -582,12 +790,12 @@ var PlatformClient = class {
582
790
  code_challenge: codeChallenge,
583
791
  code_challenge_method: "S256"
584
792
  });
585
- const res = await fetch(`${this.baseUrl}/github/join-url?${params}`, { headers: { ...this.authHeaders } });
793
+ const res = await this.request(`${this.baseUrl}/github/join-url?${params}`, { headers: { ...this.authHeaders } });
586
794
  if (!res.ok) await throwPlatformApiError("Failed to start GitHub join", res);
587
795
  return res.json();
588
796
  }
589
797
  async completeGithubJoin(joinCode, codeVerifier) {
590
- const res = await fetch(`${this.baseUrl}/github/join/complete`, {
798
+ const res = await this.request(`${this.baseUrl}/github/join/complete`, {
591
799
  method: "POST",
592
800
  headers: {
593
801
  "Content-Type": "application/json",
@@ -602,24 +810,29 @@ var PlatformClient = class {
602
810
  return res.json();
603
811
  }
604
812
  async getEmailUsage(projectId) {
605
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/usage`, { headers: { ...this.authHeaders } });
813
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/usage`, { headers: { ...this.authHeaders } });
606
814
  if (!res.ok) await throwPlatformApiError("Failed to fetch email usage", res);
607
815
  return res.json();
608
816
  }
609
817
  async getEmailLogs(projectId, limit) {
610
818
  const url = new URL(`${this.baseUrl}/projects/${projectId}/email/logs`);
611
819
  if (limit !== void 0) url.searchParams.set("limit", String(limit));
612
- const res = await fetch(url, { headers: { ...this.authHeaders } });
820
+ const res = await this.request(url, { headers: { ...this.authHeaders } });
613
821
  if (!res.ok) await throwPlatformApiError("Failed to fetch email logs", res);
614
822
  return res.json();
615
823
  }
824
+ async getEmailOperation(projectId, operationId) {
825
+ const res = await this.request(`${this.baseUrl}/projects/${encodeURIComponent(projectId)}/email/operations/${encodeURIComponent(operationId)}`, { headers: { ...this.authHeaders } });
826
+ if (!res.ok) await throwPlatformApiError("Failed to fetch email operation", res);
827
+ return res.json();
828
+ }
616
829
  async listEmailDestinations(projectId) {
617
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/destinations`, { headers: { ...this.authHeaders } });
830
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/destinations`, { headers: { ...this.authHeaders } });
618
831
  if (!res.ok) await throwPlatformApiError("Failed to list email destinations", res);
619
832
  return res.json();
620
833
  }
621
834
  async addEmailDestination(projectId, address) {
622
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/destinations`, {
835
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/destinations`, {
623
836
  method: "POST",
624
837
  headers: {
625
838
  "Content-Type": "application/json",
@@ -634,27 +847,27 @@ var PlatformClient = class {
634
847
  };
635
848
  }
636
849
  async removeEmailDestination(projectId, destinationId) {
637
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/destinations/${destinationId}`, {
850
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/destinations/${destinationId}`, {
638
851
  method: "DELETE",
639
852
  headers: { ...this.authHeaders }
640
853
  });
641
854
  if (!res.ok) await throwPlatformApiError("Failed to remove email destination", res);
642
855
  return res.json();
643
856
  }
857
+ /** Installation email settings the CLI reads before starting a credential flow. */
858
+ async getEmailSettings(projectId) {
859
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/settings`, { headers: { ...this.authHeaders } });
860
+ if (!res.ok) await throwPlatformApiError("Failed to read email settings", res);
861
+ return res.json();
862
+ }
644
863
  async listEmailDomains(projectId) {
645
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/domains`, { headers: { ...this.authHeaders } });
864
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains`, { headers: { ...this.authHeaders } });
646
865
  if (!res.ok) await throwPlatformApiError("Failed to list email domains", res);
647
866
  return res.json();
648
867
  }
649
- /**
650
- * `domain add` sends the credential exactly once — the OAuth grant's
651
- * refresh token xor a pasted API token (the api enforces exactly one). It
652
- * is the only call that ever carries it: the platform stores it encrypted
653
- * per project, and every later step (`status`, `sync`, `remove`) addresses
654
- * the row by domain. The caller must not log the input.
655
- */
868
+ /** Submit a scoped token once; later operations address the domain without it. */
656
869
  async addEmailDomain(projectId, input) {
657
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/domains`, {
870
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains`, {
658
871
  method: "POST",
659
872
  headers: {
660
873
  "Content-Type": "application/json",
@@ -662,7 +875,6 @@ var PlatformClient = class {
662
875
  },
663
876
  body: JSON.stringify({
664
877
  domain: input.domain,
665
- ...input.refreshToken !== void 0 ? { refreshToken: input.refreshToken } : {},
666
878
  ...input.apiToken !== void 0 ? { apiToken: input.apiToken } : {},
667
879
  ...input.attemptId !== void 0 ? { attemptId: input.attemptId } : {}
668
880
  })
@@ -671,29 +883,33 @@ var PlatformClient = class {
671
883
  return res.json();
672
884
  }
673
885
  async getEmailDomain(projectId, domain) {
674
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}`, { headers: { ...this.authHeaders } });
886
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}`, { headers: { ...this.authHeaders } });
675
887
  if (!res.ok) await throwPlatformApiError("Failed to fetch email domain status", res);
676
888
  return res.json();
677
889
  }
678
- /** Redeploys the relay at the current version and rotates its secret (0078, `sync`). */
890
+ /** Rotates the shared zone connection secret. */
891
+ async rotateEmailDomainSecret(projectId, domain, attemptId) {
892
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}/rotate-secret`, {
893
+ method: "POST",
894
+ headers: {
895
+ "Content-Type": "application/json",
896
+ ...this.authHeaders
897
+ },
898
+ body: JSON.stringify({ attemptId })
899
+ });
900
+ if (!res.ok) await throwPlatformApiError("Failed to rotate email connection secret", res);
901
+ return res.json();
902
+ }
679
903
  async syncEmailDomain(projectId, domain) {
680
- const res = await fetch(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}/sync`, {
904
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}/sync`, {
681
905
  method: "POST",
682
906
  headers: { ...this.authHeaders }
683
907
  });
684
908
  if (!res.ok) await throwPlatformApiError("Failed to sync email domain", res);
685
909
  return res.json();
686
910
  }
687
- /**
688
- * `force` appends `?force=1`: the api drops the row even when a
689
- * Cloudflare-side cleanup step fails (and answers `forced: true` +
690
- * `failed_steps`). Without it a CF-side failure keeps the row for retry
691
- * (`removed: false`).
692
- */
693
- async removeEmailDomain(projectId, domain, force) {
694
- const url = new URL(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}`);
695
- if (force) url.searchParams.set("force", "1");
696
- const res = await fetch(url, {
911
+ async removeEmailDomain(projectId, domain) {
912
+ const res = await this.request(`${this.baseUrl}/projects/${projectId}/email/domains/${encodeURIComponent(domain)}`, {
697
913
  method: "DELETE",
698
914
  headers: { ...this.authHeaders }
699
915
  });
@@ -701,5 +917,8 @@ var PlatformClient = class {
701
917
  return res.json();
702
918
  }
703
919
  };
920
+ function sleep(ms) {
921
+ return new Promise((resolve) => setTimeout(resolve, ms));
922
+ }
704
923
  //#endregion
705
924
  export { isExpiredTokenError as a, isCliOutdatedError as i, PlatformClient as n, parsePlatformErrorBody as o, client_exports as r, PlatformApiError as t };