void 0.21.9 → 0.22.0
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/dist/{account-cmd-DZZGK80W.mjs → account-cmd-CjyxcGkM.mjs} +4 -4
- package/dist/{auth-link-B_CeugBw.mjs → auth-link-BLO4ptzo.mjs} +4 -4
- package/dist/{auth-router-BgEFRuvZ.mjs → auth-router-ZUg9MF_U.mjs} +5 -5
- package/dist/{build-cmd-Dpt0jd-x.mjs → build-cmd-BxR5FROK.mjs} +3 -3
- package/dist/{cache-7_UeZdTk.mjs → cache-D0sWhgKI.mjs} +3 -3
- package/dist/{cancel-deploy-D23R1RXT.mjs → cancel-deploy-D4NUqFbi.mjs} +3 -3
- package/dist/{cf-build-output-CGT03qCD.mjs → cf-build-output-BJ6yGEIS.mjs} +139 -62
- package/dist/cli/cf-compat.mjs +164 -50
- package/dist/cli/cli.mjs +73 -44
- package/dist/cli/env-schema-probe.d.mts +1 -1
- package/dist/{client-QTl6ko_D.mjs → client-BTZ3XkrB.mjs} +25 -0
- package/dist/client-Czz8o5jP.mjs +2 -0
- package/dist/{cloudflare-auth-C4_GPZr0.mjs → cloudflare-auth-DQkqoMYa.mjs} +1 -6
- package/dist/{cloudflare-cmd-bSmZ1d5L.mjs → cloudflare-cmd-BKlfGHAy.mjs} +6 -7
- package/dist/{cloudflare-connect-jAwacxxP.mjs → cloudflare-connect-Ctmrw579.mjs} +3 -3
- package/dist/{cloudflare-operations-sMZXpk_S.mjs → cloudflare-operations-B9tzgjmf.mjs} +10 -39
- package/dist/{cloudflare-operations-BipGMJ5O.mjs → cloudflare-operations-BT6OWFBk.mjs} +1 -1
- package/dist/{config-CafTW6Cz.mjs → config-Br_JZD6u.mjs} +2 -7
- package/dist/{config-s7Xj7tPb.mjs → config-CyQ-wVd7.mjs} +1 -1
- package/dist/{config-CF69HgXc.d.mts → config-VavjpDnp.d.mts} +1 -2
- package/dist/config-entry.d.mts +1 -1
- package/dist/{config-write-BSduPMY8.mjs → config-write-B1f88wJA.mjs} +1 -1
- package/dist/{connect-BsUSRzln.mjs → connect-Dg-WkW-C.mjs} +5 -5
- package/dist/{create-project-BniEV0OW.mjs → create-project-DdoqwFLF.mjs} +1 -1
- package/dist/{create-project-DBfFSZKY.mjs → create-project-l9J7pbtm.mjs} +8 -4
- package/dist/{db-hRrvZaq_.mjs → db-gp2sCXyN.mjs} +14 -14
- package/dist/{delete-D6dZ9B6B.mjs → delete-TTedbH6B.mjs} +3 -3
- package/dist/{deploy-DDz7c8LK.mjs → deploy-B19L2Bra.mjs} +1 -1
- package/dist/{deploy-WaAQez1O.mjs → deploy-CH-o2CaZ.mjs} +47 -36
- package/dist/{dist-BR1quN_w.mjs → dist-AoCzRTJE.mjs} +226 -60
- package/dist/{dist-C5fND3R0.mjs → dist-BuDuKZJv.mjs} +1 -1
- package/dist/{dist-Dn6nn2IU.mjs → dist-CTBk70IR.mjs} +42 -42
- package/dist/{domain-Dmhvb2oU.mjs → domain-JRF59P_r.mjs} +4 -4
- package/dist/{email-DFi-s2t4.mjs → email-Bo6G9LOZ.mjs} +6 -6
- package/dist/{env-Csi-tMbT.mjs → env-BYOrWQnv.mjs} +4 -4
- package/dist/{env-public-D_6u46fX.d.mts → env-public-BxU_0yTL.d.mts} +2 -1
- package/dist/gen-CNJ62MM7.mjs +2 -0
- package/dist/{gen-Dz3X1Qab.mjs → gen-sCtlCOdA.mjs} +3 -3
- package/dist/{github-cmd-BED3_9JD.mjs → github-cmd-0-7PexDq.mjs} +5 -7
- package/dist/{handler-BXJTXd02.d.mts → handler-HEcZsaij.d.mts} +2 -1
- package/dist/{headers-BOg_velo.mjs → headers-DWi2IXWx.mjs} +1 -1
- package/dist/help-CmZzxUba.mjs +2 -0
- package/dist/{help-DC7gdz7L.mjs → help-GKtwl07I.mjs} +124 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +53 -24
- package/dist/{init-WO0JlPx8.mjs → init-FZx3Elvz.mjs} +13 -13
- package/dist/{link-CsHOinF7.mjs → link-BrQfb_CU.mjs} +4 -4
- package/dist/{list-CDb-4bZ1.mjs → list-D44jmIAM.mjs} +4 -4
- package/dist/{live-CKiJilLr.d.mts → live-CKJlvlNp.d.mts} +1 -1
- package/dist/{local-d1-CC8sKFGu.mjs → local-d1-Bg9OzEEO.mjs} +1 -1
- package/dist/{login-DvqXsfGs.mjs → login-CAsAsQ-_.mjs} +4 -4
- package/dist/login-CGcRKEoi.mjs +2 -0
- package/dist/{logs-BdfiOezj.mjs → logs-BjKvFnVM.mjs} +4 -4
- package/dist/{migrate-B8KuoYsO.mjs → migrate-BPITvDJN.mjs} +4 -4
- package/dist/migrate-CYfbKkXh.mjs +2 -0
- package/dist/{node-BM43oz4G.mjs → node-Dt7z256D.mjs} +1 -1
- package/dist/{operator-cmd-03819ATr.mjs → operator-cmd-BK5CCiT9.mjs} +4 -4
- package/dist/output-Dm_A4Tbv.mjs +81 -0
- package/dist/pages/client.d.mts +1 -1
- package/dist/pages/index.d.mts +1 -1
- package/dist/pages/index.mjs +2 -2
- package/dist/pages/islands-plugin.d.mts +16 -6
- package/dist/pages/islands-plugin.mjs +1 -1
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/{output-CCH48AMM.mjs → picocolors-BTps1_gs.mjs} +2 -76
- package/dist/{platform-auth-config-z92h63q5.mjs → platform-auth-config-BlN8xTdD.mjs} +3 -3
- package/dist/{platform-auth-protection-DeE3yr2R.mjs → platform-auth-protection-7d5aV2Jg.mjs} +3 -3
- package/dist/{platform-auth-recovery-o_340Ixx.mjs → platform-auth-recovery-Dxij8ZbR.mjs} +3 -3
- package/dist/{platform-cmd-DNl8WosH.mjs → platform-cmd-B3hwKrFK.mjs} +5 -5
- package/dist/{platform-cmd-C4ZV3Vpy.mjs → platform-cmd-E0FuL212.mjs} +1 -1
- package/dist/{platform-domain-CU1JtJkW.mjs → platform-domain-D6Xcy9ZX.mjs} +36 -9
- package/dist/{platform-lifecycle-Bs2qx1E3.mjs → platform-lifecycle-DMh_qrry.mjs} +1 -1
- package/dist/{platform-lifecycle-Bc047IeT.mjs → platform-lifecycle-k0E0xoxx.mjs} +548 -86
- package/dist/{platform-management-CVpGI9Y5.mjs → platform-management-Brz_BDiT.mjs} +54 -9
- package/dist/{platform-management-C8tt6D8h.mjs → platform-management-DOtss0BN.mjs} +1 -1
- package/dist/{platform-recovery-dvUaptLr.mjs → platform-recovery-BvtcXON5.mjs} +2 -2
- package/dist/prepare-Bm3iq-u4.mjs +2 -0
- package/dist/{prepare-DyZ-Yok5.mjs → prepare-C-6YZyyg.mjs} +3 -3
- package/dist/{prepare-C3kt3Rst.mjs → prepare-CRhJbVrG.mjs} +1 -1
- package/dist/{project-cmd-B44I8J_W.mjs → project-cmd-BZLCqOqw.mjs} +29 -15
- package/dist/{project-team-BkbYIsXN.mjs → project-team-BvgM0WoX.mjs} +3 -3
- package/dist/{project-token-C8xEEQnB.mjs → project-token-l5DqK6Az.mjs} +3 -3
- package/dist/project-zero-trust-v6Mvpp8y.mjs +63 -0
- package/dist/{protocol-ZH3jP4a7.d.mts → protocol-BBa6cstI.d.mts} +1 -1
- package/dist/{provision-BhreDAOS.mjs → provision-C4IGqkBf.mjs} +24 -19
- package/dist/{provision-DNtrtaVD.mjs → provision-Fr9pRqce.mjs} +1 -1
- package/dist/{requests-DFyhBMaf.mjs → requests-DN-BbNiM.mjs} +3 -3
- package/dist/{rollback-DHHxXZiS.mjs → rollback-D7rzSaZM.mjs} +4 -4
- package/dist/runtime/env-public.d.mts +1 -1
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/sandbox-container.d.mts +3 -0
- package/dist/runtime/sandbox-container.mjs +2 -0
- package/dist/runtime/sandbox.d.mts +4 -32
- package/dist/runtime/sandbox.mjs +118 -72
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +1 -1
- package/dist/runtime/ws.d.mts +1 -1
- package/dist/sandbox-container-4fdqLnyb.mjs +279 -0
- package/dist/sandbox-container-DdNEBfCc.d.mts +72 -0
- package/dist/sandbox-qpNBT8a3.d.mts +49 -0
- package/dist/{secret-DN9sSNiV.mjs → secret-BOtOl_cb.mjs} +4 -4
- package/dist/{skills-O6FUaizK.mjs → skills-D1II1Juz.mjs} +1 -1
- package/dist/{subcommand-prompt-BuGYkAkC.mjs → subcommand-prompt-CY1C4fvl.mjs} +2 -2
- package/dist/{wrangler-BymcxrRa.mjs → wrangler-DQF1vKyf.mjs} +37 -27
- package/dist/{ws-BwcqizuH.d.mts → ws-BoY7vQML.d.mts} +1 -1
- package/package.json +40 -32
- package/sandbox.Dockerfile +4 -0
- package/schema.json +10 -22
- package/skills/void/SKILL.md +2 -0
- package/skills/void/docs/guide/deployment.md +1 -1
- package/skills/void/docs/guide/edge/revalidation.md +1 -1
- package/skills/void/docs/guide/edge/static-assets.md +1 -0
- package/skills/void/docs/guide/platform/administration/zero-trust.md +318 -0
- package/skills/void/docs/guide/platform/development/local.md +2 -2
- package/skills/void/docs/guide/platform/installation/domains.md +1 -1
- package/skills/void/docs/guide/platform/installation/first-deployment.md +4 -0
- package/skills/void/docs/guide/platform/installation/prerequisites.md +1 -1
- package/skills/void/docs/guide/platform/installation/uninstall.md +17 -2
- package/skills/void/docs/guide/platform-administration.md +1 -0
- package/skills/void/docs/guide/sandboxes.md +70 -41
- package/skills/void/docs/integrations/cloudflare.md +1 -1
- package/skills/void/docs/reference/api.md +35 -35
- package/skills/void/docs/reference/cli.md +68 -6
- package/skills/void/docs/reference/config.md +16 -16
- package/dist/client-4cDVv7BO.mjs +0 -2
- package/dist/gen-Vnv2f65C.mjs +0 -2
- package/dist/help-DQMfeKMz.mjs +0 -2
- package/dist/login-DFQk7rbW.mjs +0 -2
- package/dist/migrate-TsHGBnDA.mjs +0 -2
- package/dist/prepare-BZXkjdNe.mjs +0 -2
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
|
|
2
2
|
import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
|
|
4
4
|
import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
|
|
5
5
|
import { d as openBrowser } from "./auth-DLNN0D3Z.mjs";
|
|
6
6
|
import { a as listInstallationManifests, d as readInstallationRecoverySecrets, f as readPendingInstallationSecrets, g as savePlatformConnection, h as resolveInstallationManifest, i as latestPlatformWorkerCutoverGeneration, l as platformWorkerSecretUpdateIntentState, n as assertPlatformInstallationSlugAvailable, s as normalizePlatformApiUrl, t as acquirePlatformLifecycleLock, u as promoteInstallationSecrets, v as writeInstallationManifest, y as writePendingInstallationSecrets } from "./platform-registry-QBa4DK32.mjs";
|
|
7
7
|
import { n as ensurePlatformAccessToken } from "./cf-access-P0iZnu0q.mjs";
|
|
8
|
-
import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-
|
|
8
|
+
import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-CTBk70IR.mjs";
|
|
9
9
|
import { o as resolveWranglerArgv } from "./preset-UHj9ARyP.mjs";
|
|
10
|
-
import {
|
|
11
|
-
import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-
|
|
10
|
+
import { s as runCloudflareAuthCommand } from "./cloudflare-auth-DQkqoMYa.mjs";
|
|
11
|
+
import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-C4IGqkBf.mjs";
|
|
12
|
+
import { c as import_picocolors$1, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
|
|
12
13
|
import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import { execFile } from "node:child_process";
|
|
17
|
-
import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_SANDBOX_DRAIN_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
|
|
18
|
+
import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_SANDBOX_DRAIN_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
|
|
18
19
|
import { stripVTControlCharacters } from "node:util";
|
|
19
20
|
import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
|
|
20
21
|
import { readFile } from "node:fs/promises";
|
|
@@ -652,7 +653,7 @@ function parsePostgresConnectionString(value) {
|
|
|
652
653
|
let mtls;
|
|
653
654
|
if (sslmode) {
|
|
654
655
|
if (sslmode !== "require" && sslmode !== "verify-ca" && sslmode !== "verify-full") throw new Error("platform: Unsupported sslmode. Cloudflare Hyperdrive supports \"require\", \"verify-ca\", or \"verify-full\".");
|
|
655
|
-
mtls = { sslmode };
|
|
656
|
+
mtls = { sslmode: "require" };
|
|
656
657
|
}
|
|
657
658
|
return {
|
|
658
659
|
database,
|
|
@@ -756,6 +757,11 @@ var PlatformAccessClient = class {
|
|
|
756
757
|
throw new Error("platform: Access inventory exceeded the supported pagination limit.");
|
|
757
758
|
}
|
|
758
759
|
};
|
|
760
|
+
/**
|
|
761
|
+
* Project Zero Trust keeps its own, broader copy of this list in
|
|
762
|
+
* platform/packages/api/src/zero-trust/cloudflare.ts; review both when adding
|
|
763
|
+
* a rule type.
|
|
764
|
+
*/
|
|
759
765
|
function identityRule(rule) {
|
|
760
766
|
if (rule.email_domain) {
|
|
761
767
|
const domain = object(rule.email_domain, "email domain").domain;
|
|
@@ -1411,7 +1417,6 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
|
|
|
1411
1417
|
input,
|
|
1412
1418
|
credential: result.credential
|
|
1413
1419
|
};
|
|
1414
|
-
announceCloudflareLogin();
|
|
1415
1420
|
const login = await runWrangler(["login"], false);
|
|
1416
1421
|
assertNotCancelled();
|
|
1417
1422
|
if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
|
|
@@ -1500,7 +1505,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
|
|
|
1500
1505
|
}
|
|
1501
1506
|
const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
|
|
1502
1507
|
if (existingWorkers.length) {
|
|
1503
|
-
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-
|
|
1508
|
+
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-7d5aV2Jg.mjs");
|
|
1504
1509
|
await verifyProtectionWorkerOrigins({
|
|
1505
1510
|
request: input.client.request.bind(input.client),
|
|
1506
1511
|
listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
|
|
@@ -1831,6 +1836,18 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1831
1836
|
};
|
|
1832
1837
|
}
|
|
1833
1838
|
async function revalidatePlatformInstallAction(client, original, manifest, action, options) {
|
|
1839
|
+
if (options?.stableWorkerUpdate && !options.repairMissing && action.kind === "worker" && action.action === "update" && original.actions.some((entry) => entry.key === action.key && entry.kind === action.kind && entry.action === action.action && entry.id === action.id && entry.name === action.name) && original.actions.every((entry) => {
|
|
1840
|
+
const resource = manifest.resources.find((candidate) => candidate.key === entry.key);
|
|
1841
|
+
return (entry.kind === "worker" ? entry.action === "update" : entry.action === "reuse") && resource?.kind === entry.kind && resource.state === "ready" && resource.id === entry.id && resource.name === entry.name;
|
|
1842
|
+
})) {
|
|
1843
|
+
const recorded = manifest.resources.find((resource) => resource.key === action.key);
|
|
1844
|
+
if (!recorded || recorded.kind !== "worker" || recorded.state !== "ready" || recorded.id !== action.name || recorded.name !== action.name) throw new Error(`platform: Worker ${JSON.stringify(action.name)} no longer matches the approved installation plan. Review a new plan before retrying.`);
|
|
1845
|
+
if (!matchesResourceMarker((await client.getWorkerPlainTextBindings(original.account.id, action.name)).PLATFORM_OWNERSHIP_MARKER, manifest, action.key, "worker", {
|
|
1846
|
+
id: action.name,
|
|
1847
|
+
name: action.name
|
|
1848
|
+
})) throw new Error(`platform: Cloudflare Worker ${JSON.stringify(action.name)} no longer has this installation's ownership marker. Review a new plan before retrying.`);
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1834
1851
|
const deployment = original.deployment;
|
|
1835
1852
|
const current = (await createPlatformInstallPlan(client, {
|
|
1836
1853
|
accountId: original.account.id,
|
|
@@ -2216,6 +2233,284 @@ function recordPlatformWorkerTraffic(manifest, workerName, inventory, updatedAt)
|
|
|
2216
2233
|
return next;
|
|
2217
2234
|
}
|
|
2218
2235
|
//#endregion
|
|
2236
|
+
//#region src/cli/platform-trigger-proof.ts
|
|
2237
|
+
function record(value) {
|
|
2238
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2239
|
+
}
|
|
2240
|
+
function sameStrings(actual, expected) {
|
|
2241
|
+
const sortedExpected = [...expected].sort();
|
|
2242
|
+
return actual.length === expected.length && [...actual].sort().every((value, index) => value === sortedExpected[index]);
|
|
2243
|
+
}
|
|
2244
|
+
function desiredQueue(value) {
|
|
2245
|
+
const queue = record(value);
|
|
2246
|
+
if (!queue || typeof queue.queue !== "string" || Object.keys(queue).some((key) => ![
|
|
2247
|
+
"queue",
|
|
2248
|
+
"max_batch_size",
|
|
2249
|
+
"max_batch_timeout",
|
|
2250
|
+
"max_retries",
|
|
2251
|
+
"max_concurrency",
|
|
2252
|
+
"retry_delay",
|
|
2253
|
+
"dead_letter_queue"
|
|
2254
|
+
].includes(key)) || [
|
|
2255
|
+
"max_batch_size",
|
|
2256
|
+
"max_batch_timeout",
|
|
2257
|
+
"max_retries",
|
|
2258
|
+
"max_concurrency",
|
|
2259
|
+
"retry_delay"
|
|
2260
|
+
].some((key) => queue[key] !== void 0 && typeof queue[key] !== "number") || queue.dead_letter_queue !== void 0 && typeof queue.dead_letter_queue !== "string") return null;
|
|
2261
|
+
return queue;
|
|
2262
|
+
}
|
|
2263
|
+
function matchesQueueConsumer(liveValue, desired, workerName) {
|
|
2264
|
+
const live = record(liveValue);
|
|
2265
|
+
const settings = record(live?.settings);
|
|
2266
|
+
if (!live || live.type !== "worker" || live.script_name !== void 0 && live.script_name !== workerName || live.queue_name !== desired.queue || !settings) return false;
|
|
2267
|
+
const expected = {
|
|
2268
|
+
batch_size: desired.max_batch_size ?? 10,
|
|
2269
|
+
max_wait_time_ms: Number(desired.max_batch_timeout ?? 5) * 1e3,
|
|
2270
|
+
max_retries: desired.max_retries ?? 3,
|
|
2271
|
+
max_concurrency: desired.max_concurrency ?? null,
|
|
2272
|
+
retry_delay: desired.retry_delay ?? 0
|
|
2273
|
+
};
|
|
2274
|
+
return Object.entries(expected).every(([key, value]) => key === "max_concurrency" ? (settings[key] ?? null) === value : settings[key] === value) && (live.dead_letter_queue ?? "") === (desired.dead_letter_queue ?? "");
|
|
2275
|
+
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Read every surface that `triggers deploy` can mutate for a platform Worker. An incomplete
|
|
2278
|
+
* response, unsupported trigger kind, or read failure requires normal trigger publication.
|
|
2279
|
+
*/
|
|
2280
|
+
async function platformWorkerLiveTriggersMatch(options) {
|
|
2281
|
+
const { accountId, workerName, desired, readCloudflare } = options;
|
|
2282
|
+
if (desired.preserve || desired.addresses !== void 0 || desired.workflows.length > 0 || !desired.crons.every((cron) => typeof cron === "string")) return false;
|
|
2283
|
+
const queues = desired.queueConsumers.map(desiredQueue);
|
|
2284
|
+
if (queues.some((queue) => queue === null)) return false;
|
|
2285
|
+
const base = `/accounts/${encodeURIComponent(accountId)}`;
|
|
2286
|
+
const script = `${base}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
2287
|
+
try {
|
|
2288
|
+
const [workerValue, schedulesValue, workflowsValue, nextWorkflowsValue] = await Promise.all([
|
|
2289
|
+
readCloudflare(`${base}/workers/workers/${encodeURIComponent(workerName)}`),
|
|
2290
|
+
readCloudflare(`${script}/schedules`),
|
|
2291
|
+
readCloudflare(`${base}/workflows?page=1&per_page=100`),
|
|
2292
|
+
readCloudflare(`${base}/workflows?page=2&per_page=100`)
|
|
2293
|
+
]);
|
|
2294
|
+
const worker = record(workerValue);
|
|
2295
|
+
const subdomain = record(worker?.subdomain);
|
|
2296
|
+
const schedules = record(schedulesValue)?.schedules;
|
|
2297
|
+
if (worker?.name !== workerName || !subdomain || !desired.preserveWorkersDev && subdomain.enabled !== desired.workers_dev || !desired.preservePreviewUrls && subdomain.previews_enabled !== desired.preview_urls || !Array.isArray(schedules) || !Array.isArray(worker.references && record(worker.references)?.queues) || !Array.isArray(workflowsValue) || workflowsValue.length >= 100 || !Array.isArray(nextWorkflowsValue) || nextWorkflowsValue.length !== 0) return false;
|
|
2298
|
+
const liveCrons = schedules.map((entry) => record(entry)?.cron);
|
|
2299
|
+
if (!liveCrons.every((cron) => typeof cron === "string") || !sameStrings(liveCrons, desired.crons) || workflowsValue.some((entry) => {
|
|
2300
|
+
const workflow = record(entry);
|
|
2301
|
+
return !workflow || typeof workflow.script_name !== "string" || workflow.script_name === workerName;
|
|
2302
|
+
})) return false;
|
|
2303
|
+
const references = record(worker.references).queues;
|
|
2304
|
+
if (references.length !== queues.length) return false;
|
|
2305
|
+
const matched = /* @__PURE__ */ new Set();
|
|
2306
|
+
const consumers = await Promise.all(references.map(async (value) => {
|
|
2307
|
+
const reference = record(value);
|
|
2308
|
+
if (typeof reference?.queue_id !== "string" || typeof reference.queue_consumer_id !== "string" || typeof reference.queue_name !== "string") return null;
|
|
2309
|
+
return {
|
|
2310
|
+
queueName: reference.queue_name,
|
|
2311
|
+
consumer: await readCloudflare(`${base}/queues/${encodeURIComponent(reference.queue_id)}/consumers/${encodeURIComponent(reference.queue_consumer_id)}`)
|
|
2312
|
+
};
|
|
2313
|
+
}));
|
|
2314
|
+
for (const entry of consumers) {
|
|
2315
|
+
if (!entry || matched.has(entry.queueName)) return false;
|
|
2316
|
+
const desiredQueueConfig = queues.find((queue) => queue?.queue === entry.queueName);
|
|
2317
|
+
if (!desiredQueueConfig || !matchesQueueConsumer(entry.consumer, desiredQueueConfig, workerName)) return false;
|
|
2318
|
+
matched.add(entry.queueName);
|
|
2319
|
+
}
|
|
2320
|
+
return matched.size === queues.length;
|
|
2321
|
+
} catch {
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
//#endregion
|
|
2326
|
+
//#region src/cli/platform-zero-trust-uninstall.ts
|
|
2327
|
+
/**
|
|
2328
|
+
* Uninstall turns off the platform API, and only that API can remove the
|
|
2329
|
+
* Cloudflare Access applications project Zero Trust created. The shared
|
|
2330
|
+
* project application covers every hostname under the application domain, so
|
|
2331
|
+
* a left-over one keeps asking for sign-in on hosts Void no longer serves.
|
|
2332
|
+
* Uninstall therefore reads the saved Zero Trust state straight from
|
|
2333
|
+
* PostgreSQL, which works whether or not the API still answers, and refuses
|
|
2334
|
+
* while anything remains to clean up or the state cannot be read.
|
|
2335
|
+
*/
|
|
2336
|
+
const ZERO_TRUST_SETTING = "zeroTrust.configuration";
|
|
2337
|
+
const ZERO_TRUST_LOCK = "hashtext('zero-trust:configuration')";
|
|
2338
|
+
const UNINSTALL_SETTING = "platform.uninstall";
|
|
2339
|
+
const LISTED_APPLICATIONS = 20;
|
|
2340
|
+
function isRecord(value) {
|
|
2341
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2342
|
+
}
|
|
2343
|
+
function optionalText(value, label) {
|
|
2344
|
+
if (value === null || value === void 0) return null;
|
|
2345
|
+
if (typeof value !== "string") throw new Error(`the saved ${label} is malformed`);
|
|
2346
|
+
return value;
|
|
2347
|
+
}
|
|
2348
|
+
function savedApplication(value, label) {
|
|
2349
|
+
if (value === null || value === void 0) return null;
|
|
2350
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.name !== "string") throw new Error(`the saved ${label} is malformed`);
|
|
2351
|
+
const destinations = Array.isArray(value.destinations) ? value.destinations.filter((entry) => typeof entry === "string") : [];
|
|
2352
|
+
return {
|
|
2353
|
+
id: value.id,
|
|
2354
|
+
name: value.name,
|
|
2355
|
+
destinations
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Interpret the saved configuration and project rows. Anything that does not
|
|
2360
|
+
* have the expected shape throws, so the caller refuses instead of guessing.
|
|
2361
|
+
*/
|
|
2362
|
+
function projectZeroTrustUninstallState(setting, projectRows) {
|
|
2363
|
+
const applications = [];
|
|
2364
|
+
let active = false;
|
|
2365
|
+
let destinations = [];
|
|
2366
|
+
if (setting !== null && setting !== void 0) {
|
|
2367
|
+
if (!isRecord(setting) || typeof setting.enabled !== "boolean") throw new Error("the saved Zero Trust configuration is malformed");
|
|
2368
|
+
const operation = setting.operation ?? null;
|
|
2369
|
+
if (operation !== null && !isRecord(operation)) throw new Error("the saved Zero Trust operation is malformed");
|
|
2370
|
+
active = setting.enabled || operation !== null;
|
|
2371
|
+
const wildcard = savedApplication(setting.wildcardApplication, "shared project application");
|
|
2372
|
+
const health = savedApplication(setting.healthApplication, "health check exception");
|
|
2373
|
+
const wildcardName = optionalText(setting.wildcardApplicationName, "application name");
|
|
2374
|
+
const healthName = optionalText(setting.healthApplicationName, "application name");
|
|
2375
|
+
if (wildcard || active && wildcardName) applications.push({
|
|
2376
|
+
role: "shared project application",
|
|
2377
|
+
id: wildcard?.id ?? null,
|
|
2378
|
+
name: wildcard?.name ?? wildcardName
|
|
2379
|
+
});
|
|
2380
|
+
if (health || active && healthName) applications.push({
|
|
2381
|
+
role: "health check exception",
|
|
2382
|
+
id: health?.id ?? null,
|
|
2383
|
+
name: health?.name ?? healthName
|
|
2384
|
+
});
|
|
2385
|
+
destinations = wildcard?.destinations ?? [];
|
|
2386
|
+
}
|
|
2387
|
+
const unfinishedDeletions = [];
|
|
2388
|
+
for (const row of projectRows) {
|
|
2389
|
+
const deleting = row.project_status === "deleting";
|
|
2390
|
+
const owned = applications.length;
|
|
2391
|
+
if (row.bypass_application_id || row.bypass_application_name) applications.push({
|
|
2392
|
+
role: `public exception for ${row.project_id}`,
|
|
2393
|
+
id: row.bypass_application_id,
|
|
2394
|
+
name: row.bypass_application_name,
|
|
2395
|
+
...deleting ? { deleting } : {}
|
|
2396
|
+
});
|
|
2397
|
+
if (row.protected_application_id || row.protected_application_name) applications.push({
|
|
2398
|
+
role: `custom domain application for ${row.project_id}`,
|
|
2399
|
+
id: row.protected_application_id,
|
|
2400
|
+
name: row.protected_application_name,
|
|
2401
|
+
...deleting ? { deleting } : {}
|
|
2402
|
+
});
|
|
2403
|
+
if (deleting && applications.length > owned) unfinishedDeletions.push(row.project_slug ?? row.project_id);
|
|
2404
|
+
}
|
|
2405
|
+
return {
|
|
2406
|
+
active,
|
|
2407
|
+
destinations,
|
|
2408
|
+
applications,
|
|
2409
|
+
unfinishedDeletions
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
/** Read the project Zero Trust state from the platform database. */
|
|
2413
|
+
async function readPlatformProjectZeroTrustStateConnection(client) {
|
|
2414
|
+
const present = (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "settings",
|
|
2415
|
+
to_regclass('public.project_zero_trust') IS NOT NULL AS "projects"`)).rows[0];
|
|
2416
|
+
if (!present) throw new Error("the database did not report its Zero Trust tables");
|
|
2417
|
+
return projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
|
|
2418
|
+
z."bypass_application_id", z."bypass_application_name",
|
|
2419
|
+
z."protected_application_id", z."protected_application_name"
|
|
2420
|
+
FROM "project_zero_trust" z
|
|
2421
|
+
LEFT JOIN "projects" p ON p."id" = z."project_id"
|
|
2422
|
+
WHERE z."bypass_application_id" IS NOT NULL
|
|
2423
|
+
OR z."bypass_application_name" IS NOT NULL
|
|
2424
|
+
OR z."protected_application_id" IS NOT NULL
|
|
2425
|
+
OR z."protected_application_name" IS NOT NULL
|
|
2426
|
+
ORDER BY z."project_id"`)).rows : []);
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Take the Zero Trust configuration lock exclusively on this session. Every
|
|
2430
|
+
* API path that records or creates a Void-managed Access application holds
|
|
2431
|
+
* the same lock, so none can commit until the returned release runs.
|
|
2432
|
+
*/
|
|
2433
|
+
async function lockPlatformProjectZeroTrustConnection(client) {
|
|
2434
|
+
await client.query(`SELECT pg_advisory_lock(${ZERO_TRUST_LOCK})`);
|
|
2435
|
+
let released = false;
|
|
2436
|
+
return async () => {
|
|
2437
|
+
if (released) return;
|
|
2438
|
+
released = true;
|
|
2439
|
+
await client.query(`SELECT pg_advisory_unlock(${ZERO_TRUST_LOCK})`);
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
async function hasSettingsTable(client) {
|
|
2443
|
+
return (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "present"`)).rows[0]?.present === true;
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Record that uninstall passed its final check. Runs while the configuration
|
|
2447
|
+
* lock is held. An API request admitted before traffic was disabled may be
|
|
2448
|
+
* waiting on that lock; once it gets the lock it reads this record and
|
|
2449
|
+
* refuses. Without the settings table the API has no Zero Trust writers.
|
|
2450
|
+
*/
|
|
2451
|
+
async function fencePlatformProjectZeroTrustConnection(client) {
|
|
2452
|
+
if (!await hasSettingsTable(client)) return;
|
|
2453
|
+
await client.query(`INSERT INTO "platform_settings" ("key", "value") VALUES ($1, $2::jsonb)
|
|
2454
|
+
ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value", "updated_at" = now()`, [UNINSTALL_SETTING, JSON.stringify({ startedAt: (/* @__PURE__ */ new Date()).toISOString() })]);
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Remove the uninstall record. Uninstall does this when it stops before
|
|
2458
|
+
* disabling traffic; installing or enabling does it before traffic opens.
|
|
2459
|
+
*/
|
|
2460
|
+
async function liftPlatformProjectZeroTrustFenceConnection(client) {
|
|
2461
|
+
if (!await hasSettingsTable(client)) return;
|
|
2462
|
+
await client.query("DELETE FROM \"platform_settings\" WHERE \"key\" = $1", [UNINSTALL_SETTING]);
|
|
2463
|
+
}
|
|
2464
|
+
function formatApplications(applications) {
|
|
2465
|
+
const lines = applications.slice(0, LISTED_APPLICATIONS).map((application) => ` ${application.role}: ${application.name ?? "(name not recorded)"}${application.id ? ` (${application.id})` : ""}`);
|
|
2466
|
+
if (applications.length > LISTED_APPLICATIONS) lines.push(` …and ${applications.length - LISTED_APPLICATIONS} more`);
|
|
2467
|
+
return lines.join("\n");
|
|
2468
|
+
}
|
|
2469
|
+
const DASHBOARD_HINT = "To lift sign-in right away, delete these applications in the Cloudflare dashboard under Zero Trust → Access → Applications.";
|
|
2470
|
+
function unfinishedDeletionLines(state) {
|
|
2471
|
+
if (!state.unfinishedDeletions.length) return [];
|
|
2472
|
+
return [`Deletion of ${state.unfinishedDeletions.length === 1 ? "project" : "projects"} ${state.unfinishedDeletions.join(", ")} stopped before it removed their Access applications. Rerun \`void project delete <name>\` for each, or delete it again from the admin dashboard. This removes their applications, including ones already deleted in the Cloudflare dashboard, and Void's records of them.`];
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Refuse to uninstall while project Zero Trust still owns Access applications,
|
|
2476
|
+
* or while its state cannot be read. Runs before uninstall changes anything.
|
|
2477
|
+
*/
|
|
2478
|
+
async function assertProjectZeroTrustAllowsUninstall(read) {
|
|
2479
|
+
let state;
|
|
2480
|
+
try {
|
|
2481
|
+
if (!read) throw new Error("no platform database connection is available");
|
|
2482
|
+
state = await read();
|
|
2483
|
+
} catch (error) {
|
|
2484
|
+
const reason = (error instanceof Error ? error.message : String(error)).replace(/^platform: /, "");
|
|
2485
|
+
throw new Error([
|
|
2486
|
+
`platform: Refusing to uninstall because the project Zero Trust state could not be read from the platform database: ${reason}`,
|
|
2487
|
+
"Uninstall turns off the platform API, which Void needs to remove the Cloudflare Access applications that project Zero Trust created.",
|
|
2488
|
+
"Make the installation database reachable (export VOID_PLATFORM_DATABASE_URL if this machine has not recorded it), then retry. If project Zero Trust was ever enabled, run `void platform zero-trust disable` first."
|
|
2489
|
+
].join("\n"));
|
|
2490
|
+
}
|
|
2491
|
+
if (state.active) {
|
|
2492
|
+
const covered = state.destinations.length ? ` on ${state.destinations.join(" and ")}` : " on every hostname under the application domain";
|
|
2493
|
+
throw new Error([
|
|
2494
|
+
"platform: Refusing to uninstall while project Zero Trust is enabled or still changing.",
|
|
2495
|
+
`Uninstall turns off the platform API, so Void could no longer remove its Cloudflare Access applications, and Access would keep asking for sign-in${covered}, including hostnames Void does not serve.`,
|
|
2496
|
+
"Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
|
|
2497
|
+
"If the platform API does not answer, bring it back with `void platform enable` or `void platform repair` first.",
|
|
2498
|
+
...state.applications.length ? ["Void-managed Access applications:", formatApplications(state.applications)] : [],
|
|
2499
|
+
...unfinishedDeletionLines(state),
|
|
2500
|
+
DASHBOARD_HINT
|
|
2501
|
+
].join("\n"));
|
|
2502
|
+
}
|
|
2503
|
+
if (state.applications.length) throw new Error([
|
|
2504
|
+
"platform: Refusing to uninstall while Void-managed Access applications from project Zero Trust remain.",
|
|
2505
|
+
"Zero Trust is disabled, but it could not remove the applications of some projects. Retry the uninstall once they are gone.",
|
|
2506
|
+
"Void-managed Access applications:",
|
|
2507
|
+
formatApplications(state.applications),
|
|
2508
|
+
...state.applications.some((application) => !application.deleting) ? [`Hourly maintenance retries ${state.unfinishedDeletions.length ? "the other applications" : "them"} while the platform is enabled, and clears Void's records of any you delete in the dashboard.`] : [],
|
|
2509
|
+
...unfinishedDeletionLines(state),
|
|
2510
|
+
DASHBOARD_HINT
|
|
2511
|
+
].join("\n"));
|
|
2512
|
+
}
|
|
2513
|
+
//#endregion
|
|
2219
2514
|
//#region src/cli/platform-installer.ts
|
|
2220
2515
|
const REMOTE_INSTALLATION_MANIFEST_KEY = PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY;
|
|
2221
2516
|
const PLATFORM_WORKER_SECRET_UPDATE_NAMES = [
|
|
@@ -2530,7 +2825,13 @@ async function preparePlatformDatabaseSchema(manifest, runtime, runtimeDirectory
|
|
|
2530
2825
|
}
|
|
2531
2826
|
async function recoverInterruptedPlatformWorkerCutover(manifest, interruptedCutover, workerActions, configs, previousConfigs, plan, dependencies, ids, zoneId, previousPublicEndpoints, hasEmailGatewayArtifact, runPinnedPlatformWrangler, now, writeRemoteInstallationCheckpoint, onManifestChange) {
|
|
2532
2827
|
if (interruptedCutover) {
|
|
2533
|
-
for (const key of [
|
|
2828
|
+
for (const key of [
|
|
2829
|
+
"dispatch",
|
|
2830
|
+
"tail",
|
|
2831
|
+
"emailGateway",
|
|
2832
|
+
"proxy",
|
|
2833
|
+
"api"
|
|
2834
|
+
].filter((key) => interruptedCutover.staged.has(key))) {
|
|
2534
2835
|
const snapshot = interruptedCutover.staged.get(key);
|
|
2535
2836
|
const preservedRoutes = dependencies.recoverWorkerCutover ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, snapshot.workerName);
|
|
2536
2837
|
const recoveryConfig = mergePlatformWorkerTrafficConfig({
|
|
@@ -2633,6 +2934,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
2633
2934
|
const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2634
2935
|
const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
|
|
2635
2936
|
const runPinnedPlatformWrangler = (args, accountId, cwd, capture) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, cloudflareApiBaseUrl);
|
|
2937
|
+
const runParallelPinnedPlatformWrangler = (args, accountId, cwd, capture) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, cloudflareApiBaseUrl, true);
|
|
2636
2938
|
const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
|
|
2637
2939
|
const runtimeDirectory = resolvedRuntime.directory;
|
|
2638
2940
|
const runtime = resolvedRuntime.manifest;
|
|
@@ -2724,6 +3026,10 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
2724
3026
|
writeInstallationManifest(manifest);
|
|
2725
3027
|
}
|
|
2726
3028
|
assertRequiredPlatformSecrets(plan, manifest, secrets, !!dependencies.prepareAuthentication);
|
|
3029
|
+
if (hyperdriveAction?.action === "create") {
|
|
3030
|
+
parsePostgresConnectionString(secrets.databaseUrl);
|
|
3031
|
+
assertVoidCreatedHyperdriveTrustsDatabase(secrets.databaseUrl);
|
|
3032
|
+
}
|
|
2727
3033
|
const pendingSecrets = secretCheckpoint(secrets, workerSecretUpdateIntent);
|
|
2728
3034
|
writePendingInstallationSecrets(manifest, {
|
|
2729
3035
|
...pendingSecrets,
|
|
@@ -3075,7 +3381,59 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3075
3381
|
};
|
|
3076
3382
|
writePendingInstallationSecrets(manifest, secretCheckpoint(secrets, workerSecretUpdateIntent));
|
|
3077
3383
|
}
|
|
3078
|
-
|
|
3384
|
+
const parallelWorkerUploads = !dependencies.deployWorker && !requiresStagedCommit && !workerSecretUpdateIntent && !recoveringUnsafeTraffic && workerActions.length === workerKeys.length && workerActions.every((action) => action.action === "update");
|
|
3385
|
+
let workerManifestQueue = Promise.resolve();
|
|
3386
|
+
let workerManifestFailed = false;
|
|
3387
|
+
let workerManifestFailure;
|
|
3388
|
+
const checkpointWorkerManifest = (update, safetyRecovery = false) => {
|
|
3389
|
+
if (!parallelWorkerUploads) return update();
|
|
3390
|
+
const result = workerManifestQueue.then(async () => {
|
|
3391
|
+
if (workerManifestFailed && !safetyRecovery) throw workerManifestFailure;
|
|
3392
|
+
try {
|
|
3393
|
+
await update();
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
workerManifestFailed = true;
|
|
3396
|
+
workerManifestFailure = error;
|
|
3397
|
+
throw error;
|
|
3398
|
+
}
|
|
3399
|
+
});
|
|
3400
|
+
workerManifestQueue = result.then(() => void 0, () => void 0);
|
|
3401
|
+
return result;
|
|
3402
|
+
};
|
|
3403
|
+
let availableUploadSlots = 2;
|
|
3404
|
+
let uploadFailure;
|
|
3405
|
+
const uploadWaiters = [];
|
|
3406
|
+
const acquireUploadSlot = async () => {
|
|
3407
|
+
if (uploadFailure !== void 0) throw uploadFailure;
|
|
3408
|
+
if (availableUploadSlots === 0) await new Promise((resolve, reject) => uploadWaiters.push({
|
|
3409
|
+
resolve,
|
|
3410
|
+
reject
|
|
3411
|
+
}));
|
|
3412
|
+
else availableUploadSlots--;
|
|
3413
|
+
return (failure) => {
|
|
3414
|
+
if (failure !== void 0 && uploadFailure === void 0) {
|
|
3415
|
+
uploadFailure = failure;
|
|
3416
|
+
for (const waiter of uploadWaiters.splice(0)) waiter.reject(failure);
|
|
3417
|
+
}
|
|
3418
|
+
const next = uploadFailure === void 0 ? uploadWaiters.shift() : void 0;
|
|
3419
|
+
if (next) next.resolve();
|
|
3420
|
+
else availableUploadSlots++;
|
|
3421
|
+
};
|
|
3422
|
+
};
|
|
3423
|
+
const workerReady = (parallelWorkerUploads ? workerKeys : []).map(() => {
|
|
3424
|
+
let resolve;
|
|
3425
|
+
let reject;
|
|
3426
|
+
return {
|
|
3427
|
+
promise: new Promise((onResolve, onReject) => {
|
|
3428
|
+
resolve = onResolve;
|
|
3429
|
+
reject = onReject;
|
|
3430
|
+
}),
|
|
3431
|
+
resolve,
|
|
3432
|
+
reject
|
|
3433
|
+
};
|
|
3434
|
+
});
|
|
3435
|
+
const uploadBatch = Promise.all(workerReady.map((ready) => ready.promise)).then(() => ({ error: null }), (error) => ({ error }));
|
|
3436
|
+
const deployOneWorker = async (key, waitForPriorWorker, onReadyForActivation) => {
|
|
3079
3437
|
dependencies.onProgress?.(`Deploying the ${key} service`);
|
|
3080
3438
|
let workerStepStarted = performance.now();
|
|
3081
3439
|
const markWorkerStep = (step) => {
|
|
@@ -3084,12 +3442,14 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3084
3442
|
};
|
|
3085
3443
|
const action = workerActions.find((entry) => entry.key === (key === "emailGateway" ? "email-gateway-worker" : `${key}-worker`));
|
|
3086
3444
|
const workerSecrets = platformWorkerSecretPayload(key, workerDeploymentSecrets, plan.account.id, zoneId, action.action !== "create");
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
manifest
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3445
|
+
await checkpointWorkerManifest(async () => {
|
|
3446
|
+
const intended = recordProvisionIntent(manifest, action, now());
|
|
3447
|
+
if (intended !== manifest) {
|
|
3448
|
+
manifest = intended;
|
|
3449
|
+
writeInstallationManifest(manifest);
|
|
3450
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3451
|
+
}
|
|
3452
|
+
});
|
|
3093
3453
|
await dependencies.revalidateBeforeWorkerDeploy(manifest, action);
|
|
3094
3454
|
markWorkerStep("revalidate");
|
|
3095
3455
|
const preservedRoutes = dependencies.deployWorker ? [] : action.action === "create" ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, action.name);
|
|
@@ -3098,29 +3458,40 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3098
3458
|
const previousWorkerConfig = mergePlatformWorkerTrafficConfig(previousConfigs[key], preservedRoutes);
|
|
3099
3459
|
const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
|
|
3100
3460
|
apiBaseUrl: cloudflareApiBaseUrl,
|
|
3461
|
+
...parallelWorkerUploads ? {
|
|
3462
|
+
acquireUploadSlot,
|
|
3463
|
+
onReadyForActivation,
|
|
3464
|
+
waitForPriorWorker
|
|
3465
|
+
} : {},
|
|
3101
3466
|
...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
|
|
3102
3467
|
onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3468
|
+
await checkpointWorkerManifest(async () => {
|
|
3469
|
+
manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
|
|
3470
|
+
writeInstallationManifest(manifest);
|
|
3471
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3472
|
+
});
|
|
3106
3473
|
},
|
|
3107
3474
|
onTrafficPublished: async () => {
|
|
3108
3475
|
if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
|
|
3109
3476
|
const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
manifest
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3477
|
+
await checkpointWorkerManifest(async () => {
|
|
3478
|
+
const next = recordPlatformWorkerTraffic(manifest, action.name, {
|
|
3479
|
+
customDomains,
|
|
3480
|
+
routes
|
|
3481
|
+
}, now());
|
|
3482
|
+
if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
|
|
3483
|
+
manifest = next;
|
|
3484
|
+
writeInstallationManifest(manifest);
|
|
3485
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3486
|
+
}
|
|
3487
|
+
});
|
|
3119
3488
|
},
|
|
3120
3489
|
onUnsafeTraffic: async () => {
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3490
|
+
await checkpointWorkerManifest(async () => {
|
|
3491
|
+
manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
|
|
3492
|
+
writeInstallationManifest(manifest);
|
|
3493
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3494
|
+
}, true);
|
|
3124
3495
|
},
|
|
3125
3496
|
managedRoutes: preserveDisabled ? [] : managedRoutes.filter((route) => route.script === action.name).map((route) => route.pattern),
|
|
3126
3497
|
preservedRoutes,
|
|
@@ -3140,19 +3511,37 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3140
3511
|
retainNewWorker: async () => {
|
|
3141
3512
|
await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
|
|
3142
3513
|
},
|
|
3143
|
-
runner: runPinnedPlatformWrangler,
|
|
3514
|
+
runner: parallelWorkerUploads ? runParallelPinnedPlatformWrangler : runPinnedPlatformWrangler,
|
|
3144
3515
|
workerExisted: action.action !== "create"
|
|
3145
3516
|
});
|
|
3146
3517
|
markWorkerStep("deploy");
|
|
3147
3518
|
if (rollback) workerRollbacks.push(rollback);
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3519
|
+
await checkpointWorkerManifest(async () => {
|
|
3520
|
+
const recorded = recordResource(manifest, action, action.name, "created", now());
|
|
3521
|
+
if (rollback || dependencies.deployWorker || JSON.stringify(recorded.resources) !== JSON.stringify(manifest.resources)) {
|
|
3522
|
+
manifest = recorded;
|
|
3523
|
+
writeInstallationManifest(manifest);
|
|
3524
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3525
|
+
}
|
|
3526
|
+
});
|
|
3154
3527
|
markWorkerStep("record");
|
|
3155
|
-
}
|
|
3528
|
+
};
|
|
3529
|
+
if (parallelWorkerUploads) {
|
|
3530
|
+
const tasks = [];
|
|
3531
|
+
for (const [index, key] of workerKeys.entries()) {
|
|
3532
|
+
const priorWorker = tasks.at(-1);
|
|
3533
|
+
const ready = workerReady[index];
|
|
3534
|
+
const task = deployOneWorker(key, priorWorker ? () => priorWorker : void 0, async () => {
|
|
3535
|
+
ready.resolve();
|
|
3536
|
+
const batch = await uploadBatch;
|
|
3537
|
+
if (batch.error !== null) throw batch.error;
|
|
3538
|
+
});
|
|
3539
|
+
task.then(() => ready.resolve(), (error) => ready.reject(error));
|
|
3540
|
+
tasks.push(task);
|
|
3541
|
+
}
|
|
3542
|
+
const firstFailure = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
|
|
3543
|
+
if (firstFailure) throw firstFailure.reason;
|
|
3544
|
+
} else for (const key of workerKeys) await deployOneWorker(key);
|
|
3156
3545
|
manifest = setInstallationEndpoints(manifest, {
|
|
3157
3546
|
apiUrl: plan.apiUrl,
|
|
3158
3547
|
dashboardUrl: null,
|
|
@@ -3338,6 +3727,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3338
3727
|
retainNewWorker: async () => {
|
|
3339
3728
|
await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
|
|
3340
3729
|
},
|
|
3730
|
+
reuseMatchingVersion: false,
|
|
3341
3731
|
runner: runPinnedPlatformWrangler,
|
|
3342
3732
|
workerExisted: true
|
|
3343
3733
|
});
|
|
@@ -3361,6 +3751,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3361
3751
|
dependencies.onProgress?.("Preparing shared email routing");
|
|
3362
3752
|
emailOperationId = await (dependencies.bootstrapEmail ?? bootstrapPlatformSharedEmail)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce));
|
|
3363
3753
|
}
|
|
3754
|
+
await releaseDatabaseLock.liftProjectZeroTrustFence?.();
|
|
3364
3755
|
if (dependencies.client.deleteKvValue) await dependencies.client.deleteKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY);
|
|
3365
3756
|
else if (wasDisabled || !previousPublicEndpoints) throw new Error("platform: Routing disablement removal is unavailable.");
|
|
3366
3757
|
if (requiresStagedCommit) {
|
|
@@ -3747,6 +4138,14 @@ function assertRequiredPlatformSecrets(plan, manifest, secrets, authenticationWi
|
|
|
3747
4138
|
}
|
|
3748
4139
|
if (missing.size > 0) throw new Error(`platform: Creating a missing platform Worker requires: ${[...missing].sort().join(", ")}. Restore these values from your secret manager and retry.`);
|
|
3749
4140
|
}
|
|
4141
|
+
/**
|
|
4142
|
+
* A Hyperdrive that Void creates validates the server against public certificate authorities
|
|
4143
|
+
* only, so it cannot honor a private CA that the local database client would trust.
|
|
4144
|
+
*/
|
|
4145
|
+
function assertVoidCreatedHyperdriveTrustsDatabase(databaseUrl) {
|
|
4146
|
+
const sslrootcert = new URL(databaseUrl).searchParams.getAll("sslrootcert").at(-1);
|
|
4147
|
+
if (sslrootcert && sslrootcert !== "system") throw new Error("platform: A Hyperdrive created by Void trusts only public certificate authorities, so it cannot use the certificate authority in DATABASE_URL sslrootcert. If the database certificate comes from a public certificate authority, use sslrootcert=system. Otherwise create a separately managed Hyperdrive that trusts it, then select it with \"Use an existing Hyperdrive\" when you start a new installation, or set VOID_PLATFORM_HYPERDRIVE_ID, VOID_PLATFORM_HYPERDRIVE_ORIGIN_HOST, and VOID_PLATFORM_HYPERDRIVE_ORIGIN_USER.");
|
|
4148
|
+
}
|
|
3750
4149
|
/** Make pg's current strict TLS behavior explicit without rewriting saved connection details. */
|
|
3751
4150
|
function platformDatabaseClientUrl(databaseUrl) {
|
|
3752
4151
|
let url;
|
|
@@ -3755,7 +4154,13 @@ function platformDatabaseClientUrl(databaseUrl) {
|
|
|
3755
4154
|
} catch {
|
|
3756
4155
|
return databaseUrl;
|
|
3757
4156
|
}
|
|
3758
|
-
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:"
|
|
4157
|
+
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return databaseUrl;
|
|
4158
|
+
if (url.searchParams.getAll("sslrootcert").at(-1) === "system") {
|
|
4159
|
+
url.searchParams.delete("sslrootcert");
|
|
4160
|
+
url.searchParams.set("sslmode", "verify-full");
|
|
4161
|
+
return url.toString();
|
|
4162
|
+
}
|
|
4163
|
+
if (url.searchParams.getAll("sslmode").at(-1) !== "require" || url.searchParams.getAll("uselibpqcompat").at(-1) === "true") return databaseUrl;
|
|
3759
4164
|
url.searchParams.set("sslmode", "verify-full");
|
|
3760
4165
|
return url.toString();
|
|
3761
4166
|
}
|
|
@@ -3812,6 +4217,21 @@ async function readPlatformDatabaseLifecycleManifest(databaseUrl, installationId
|
|
|
3812
4217
|
await pool.end();
|
|
3813
4218
|
}
|
|
3814
4219
|
}
|
|
4220
|
+
/** Read project Zero Trust state for a plan, which holds no lifecycle lock. */
|
|
4221
|
+
async function readPlatformProjectZeroTrustState(databaseUrl, installationId, ownershipNonce) {
|
|
4222
|
+
if (!databaseUrl) throw new Error("the installation database URL is not recorded on this machine");
|
|
4223
|
+
const { Pool } = await import("pg");
|
|
4224
|
+
const pool = new Pool({
|
|
4225
|
+
connectionString: platformDatabaseClientUrl(databaseUrl),
|
|
4226
|
+
max: 1
|
|
4227
|
+
});
|
|
4228
|
+
try {
|
|
4229
|
+
await readPlatformDatabaseLifecycleManifestConnection(pool, installationId, ownershipNonce);
|
|
4230
|
+
return await readPlatformProjectZeroTrustStateConnection(pool);
|
|
4231
|
+
} finally {
|
|
4232
|
+
await pool.end();
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
3815
4235
|
async function inspectPlatformDatabaseStateConnection(client, database) {
|
|
3816
4236
|
const relation = await client.query(`SELECT to_regclass('"drizzle"."__drizzle_migrations"')::text AS "relation"`);
|
|
3817
4237
|
if (relation.rows.length === 0 || relation.rows[0]?.relation === null) return {
|
|
@@ -3939,6 +4359,10 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
|
|
|
3939
4359
|
protection: parsePlatformAccessProtection(settings.protection)
|
|
3940
4360
|
} : null;
|
|
3941
4361
|
};
|
|
4362
|
+
release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
|
|
4363
|
+
release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
|
|
4364
|
+
release.fenceProjectZeroTrust = () => fencePlatformProjectZeroTrustConnection(client);
|
|
4365
|
+
release.liftProjectZeroTrustFence = () => liftPlatformProjectZeroTrustFenceConnection(client);
|
|
3942
4366
|
release.requiredSafetyProtocols = await readRequiredPlatformSafetyProtocols(client);
|
|
3943
4367
|
release.checkpoint = async (manifest) => {
|
|
3944
4368
|
if (released) throw new Error("platform: Cannot checkpoint lifecycle state after releasing its lock.");
|
|
@@ -4269,24 +4693,26 @@ function snapshotPlatformWorkerUploadInputs(config, directory) {
|
|
|
4269
4693
|
function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
|
|
4270
4694
|
const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
4271
4695
|
return async (args, account, cwd, capture) => {
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4696
|
+
try {
|
|
4697
|
+
if (args[0] === "deployments" && (args[1] === "status" || args[1] === "list")) {
|
|
4698
|
+
const response = await readCloudflare(`${base}/deployments`);
|
|
4699
|
+
const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
|
|
4700
|
+
if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
|
|
4701
|
+
return {
|
|
4702
|
+
status: args[1] === "status" && deployments.length === 0 ? 1 : 0,
|
|
4703
|
+
stderr: "",
|
|
4704
|
+
stdout: JSON.stringify(args[1] === "status" ? deployments[0] : deployments)
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
if (args[0] === "versions" && args[1] === "view" && args[2]) {
|
|
4708
|
+
const version = await readCloudflare(`${base}/versions/${encodeURIComponent(args[2])}`);
|
|
4709
|
+
return {
|
|
4710
|
+
status: 0,
|
|
4711
|
+
stderr: "",
|
|
4712
|
+
stdout: JSON.stringify(version)
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
} catch {}
|
|
4290
4716
|
return fallback(args, account, cwd, capture);
|
|
4291
4717
|
};
|
|
4292
4718
|
}
|
|
@@ -4294,6 +4720,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4294
4720
|
const directory = mkdtempSync(join(tmpdir(), "void-platform-deploy-"));
|
|
4295
4721
|
const workerName = String(config.name);
|
|
4296
4722
|
const accountId = String(config.account_id);
|
|
4723
|
+
const readRunner = options.readCloudflare ? platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner) : options.runner;
|
|
4297
4724
|
let activatedVersionId = null;
|
|
4298
4725
|
let activatedDeploymentId = null;
|
|
4299
4726
|
let uploadedVersionId = null;
|
|
@@ -4305,13 +4732,22 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4305
4732
|
let uploadConfig = withoutPlatformWorkerTrafficAttachments(nextTraffic.wranglerConfig);
|
|
4306
4733
|
const nextTriggers = readTriggerConfigFromObject(nextTraffic.wranglerConfig);
|
|
4307
4734
|
const contentHash = secrets === null ? platformWorkerUploadFingerprint(config, options.managedRoutes) : null;
|
|
4735
|
+
const reuseMatchingVersion = options.reuseMatchingVersion !== false;
|
|
4308
4736
|
let previousTriggers = readTriggerConfigFromObject(previousTraffic.wranglerConfig);
|
|
4309
4737
|
try {
|
|
4310
4738
|
const configPath = join(directory, "wrangler.json");
|
|
4311
4739
|
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4312
4740
|
const reconcileExistingVersion = async (versionId, triggerConfig, runner, deploymentId) => {
|
|
4313
4741
|
try {
|
|
4314
|
-
|
|
4742
|
+
if (deploymentId && options.readCloudflare ? await platformWorkerLiveTriggersMatch({
|
|
4743
|
+
accountId,
|
|
4744
|
+
workerName,
|
|
4745
|
+
desired: nextTriggers,
|
|
4746
|
+
readCloudflare: options.readCloudflare
|
|
4747
|
+
}) : false) {
|
|
4748
|
+
const beforeTraffic = await readPlatformWorkerDeployments(configPath, accountId, workerName, runner);
|
|
4749
|
+
if (beforeTraffic[0]?.id !== deploymentId || !deploymentRunsOnlyVersion(beforeTraffic[0], versionId)) throw new Error(`platform: Worker ${workerName} changed deployment lineage during trigger verification.`);
|
|
4750
|
+
} else assertPlatformTriggerStabilization(await stabilizeCloudflareVersionTriggers({
|
|
4315
4751
|
configPath,
|
|
4316
4752
|
knownTriggerConfigs: { [versionId]: nextTriggers },
|
|
4317
4753
|
manageRoutes: false,
|
|
@@ -4344,16 +4780,15 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4344
4780
|
throw new PlatformWorkerRecoveryIncompleteError(`platform: Worker ${workerName} traffic reconciliation failed: ${errorMessage(error)}${failures.length > 0 ? ` Safety recovery also failed: ${failures.join("; ")}` : ""}`, { cause: error });
|
|
4345
4781
|
}
|
|
4346
4782
|
};
|
|
4347
|
-
if (options.workerExisted && contentHash && options.readCloudflare) {
|
|
4348
|
-
const directRunner = platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner);
|
|
4783
|
+
if (options.workerExisted && contentHash && reuseMatchingVersion && options.readCloudflare) {
|
|
4349
4784
|
let candidate;
|
|
4350
4785
|
let changedDuringRead = false;
|
|
4351
4786
|
try {
|
|
4352
|
-
const before = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4787
|
+
const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4353
4788
|
const activeVersion = before[0]?.versions.length === 1 && before[0].versions[0]?.percentage === 100 ? before[0].versions[0].versionId : null;
|
|
4354
4789
|
if (!activeVersion) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
|
|
4355
|
-
const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion,
|
|
4356
|
-
const after = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4790
|
+
const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, readRunner);
|
|
4791
|
+
const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4357
4792
|
changedDuringRead = before[0]?.id !== after[0]?.id || !deploymentRunsOnlyVersion(after[0], snapshot.versionId);
|
|
4358
4793
|
if (!changedDuringRead && snapshot.contentHash === contentHash) candidate = {
|
|
4359
4794
|
deploymentId: after[0].id,
|
|
@@ -4363,16 +4798,24 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4363
4798
|
if (error instanceof Error && (error.message.includes("changed concurrently") || error.message.includes("does not belong to this installation"))) throw error;
|
|
4364
4799
|
}
|
|
4365
4800
|
if (changedDuringRead) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
|
|
4366
|
-
if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash)
|
|
4801
|
+
if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
|
|
4802
|
+
await options.onReadyForActivation?.();
|
|
4803
|
+
await options.waitForPriorWorker?.();
|
|
4804
|
+
return await reconcileExistingVersion(candidate.snapshot.versionId, candidate.snapshot.triggerConfig, readRunner, candidate.deploymentId);
|
|
4805
|
+
}
|
|
4367
4806
|
}
|
|
4368
4807
|
if (options.workerExisted) {
|
|
4369
|
-
const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config),
|
|
4808
|
+
const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), readRunner);
|
|
4370
4809
|
previousVersionId = previous.versionId;
|
|
4371
|
-
const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4810
|
+
const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4372
4811
|
if (!deploymentRunsOnlyVersion(deployments[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
|
|
4373
4812
|
previousDeployment = deployments[0];
|
|
4374
4813
|
previousDeploymentId = previousDeployment.id;
|
|
4375
|
-
if (contentHash && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash)
|
|
4814
|
+
if (contentHash && reuseMatchingVersion && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
|
|
4815
|
+
await options.onReadyForActivation?.();
|
|
4816
|
+
await options.waitForPriorWorker?.();
|
|
4817
|
+
return await reconcileExistingVersion(previousVersionId, previous.triggerConfig, readRunner);
|
|
4818
|
+
}
|
|
4376
4819
|
uploadConfig = withoutCompletedPlatformWorkerMigrations(uploadConfig, previous.migrationTag);
|
|
4377
4820
|
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4378
4821
|
previousTriggers = resolvePlatformPreviousTriggerConfig(withoutPlatformCustomDomainTriggers(previous.triggerConfig), previousTriggers, options.previousPublicEndpoints);
|
|
@@ -4404,14 +4847,26 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4404
4847
|
writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
|
|
4405
4848
|
argv.push("--secrets-file", secretsPath);
|
|
4406
4849
|
}
|
|
4407
|
-
const
|
|
4850
|
+
const releaseUploadSlot = await options.acquireUploadSlot?.();
|
|
4851
|
+
let uploaded;
|
|
4852
|
+
let uploadError;
|
|
4853
|
+
try {
|
|
4854
|
+
uploaded = options.workerExisted ? await options.runner(argv, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(argv, accountId, directory, true, workerName, options.apiBaseUrl);
|
|
4855
|
+
assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
|
|
4856
|
+
} catch (error) {
|
|
4857
|
+
uploadError = error;
|
|
4858
|
+
throw error;
|
|
4859
|
+
} finally {
|
|
4860
|
+
releaseUploadSlot?.(uploadError);
|
|
4861
|
+
}
|
|
4408
4862
|
uploadedVersionId = parseUploadedPlatformWorkerVersion(`${uploaded.stdout}\n${uploaded.stderr}`);
|
|
4409
4863
|
if (!options.workerExisted) activatedVersionId = uploadedVersionId;
|
|
4410
|
-
assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
|
|
4411
4864
|
if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
|
|
4412
4865
|
await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
|
|
4866
|
+
await options.onReadyForActivation?.();
|
|
4867
|
+
await options.waitForPriorWorker?.();
|
|
4413
4868
|
if (options.workerExisted) {
|
|
4414
|
-
const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4869
|
+
const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4415
4870
|
if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
|
|
4416
4871
|
activatedVersionId = uploadedVersionId;
|
|
4417
4872
|
assertCleanPlatformWrangler(await options.runner([
|
|
@@ -4425,7 +4880,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4425
4880
|
"--yes"
|
|
4426
4881
|
], accountId, directory, true), `activate ${workerName}`);
|
|
4427
4882
|
}
|
|
4428
|
-
const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4883
|
+
const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4429
4884
|
const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
|
|
4430
4885
|
if (lineage.kind === "concurrent-after") {
|
|
4431
4886
|
activatedVersionId = null;
|
|
@@ -4445,7 +4900,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4445
4900
|
expectedActiveDeploymentId: lineage.deploymentId,
|
|
4446
4901
|
expectedActiveVersionId: uploadedVersionId,
|
|
4447
4902
|
restoration: interveningDeployment,
|
|
4448
|
-
runner:
|
|
4903
|
+
runner: readRunner,
|
|
4449
4904
|
workerName
|
|
4450
4905
|
});
|
|
4451
4906
|
} catch (recoveryError) {
|
|
@@ -4471,7 +4926,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4471
4926
|
await options.removeCustomDomains();
|
|
4472
4927
|
return cleanPlatformCommandResult();
|
|
4473
4928
|
},
|
|
4474
|
-
runWrangler: platformWorkerCommandRunner(accountId, directory,
|
|
4929
|
+
runWrangler: platformWorkerCommandRunner(accountId, directory, readRunner),
|
|
4475
4930
|
targetTriggerConfig: nextTriggers,
|
|
4476
4931
|
targetVersionId: uploadedVersionId,
|
|
4477
4932
|
workerName
|
|
@@ -4495,7 +4950,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4495
4950
|
removeCustomDomains: options.removeCustomDomains,
|
|
4496
4951
|
publishCustomDomains: options.publishCustomDomains,
|
|
4497
4952
|
retainNewWorker: options.retainNewWorker,
|
|
4498
|
-
runner:
|
|
4953
|
+
runner: readRunner,
|
|
4499
4954
|
workerExisted: options.workerExisted,
|
|
4500
4955
|
workerName
|
|
4501
4956
|
});
|
|
@@ -4522,7 +4977,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4522
4977
|
removeCustomDomains: options.removeCustomDomains,
|
|
4523
4978
|
publishCustomDomains: options.publishCustomDomains,
|
|
4524
4979
|
retainNewWorker: options.retainNewWorker,
|
|
4525
|
-
runner:
|
|
4980
|
+
runner: readRunner,
|
|
4526
4981
|
workerExisted: options.workerExisted,
|
|
4527
4982
|
workerName
|
|
4528
4983
|
}) : null,
|
|
@@ -4623,10 +5078,11 @@ async function runPlatformWrangler(args, accountId, cwd, capture) {
|
|
|
4623
5078
|
return runPlatformWranglerAtApiBase(args, accountId, cwd, capture);
|
|
4624
5079
|
}
|
|
4625
5080
|
const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
|
|
4626
|
-
async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl()) {
|
|
5081
|
+
async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
|
|
4627
5082
|
const startedAt = performance.now();
|
|
4628
5083
|
const result = await createDefaultRunWrangler(process.cwd(), {
|
|
4629
5084
|
launcher: resolveWranglerArgv(process.cwd()),
|
|
5085
|
+
parallelWorkerUpload,
|
|
4630
5086
|
timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
|
|
4631
5087
|
})([...args], {
|
|
4632
5088
|
accountId,
|
|
@@ -4676,8 +5132,11 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4676
5132
|
const upstreamBase = new URL(upstreamApiBaseUrl);
|
|
4677
5133
|
if (upstreamBase.protocol !== "http:" && upstreamBase.protocol !== "https:") throw new Error("platform: Cloudflare API base URL must use HTTP or HTTPS.");
|
|
4678
5134
|
const basePath = upstreamBase.pathname.replace(/\/+$/, "");
|
|
4679
|
-
const
|
|
5135
|
+
const accountPath = `${basePath}/accounts/${encodeURIComponent(accountId)}`;
|
|
5136
|
+
const expectedPath = `${accountPath}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
5137
|
+
const assetUploadPath = `${accountPath}/workers/assets/upload`;
|
|
4680
5138
|
const state = {
|
|
5139
|
+
assetSessionStarted: false,
|
|
4681
5140
|
attempted: false,
|
|
4682
5141
|
conflictStatus: null,
|
|
4683
5142
|
created: false,
|
|
@@ -4697,7 +5156,9 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4697
5156
|
const isConditionalCreate = incoming.method === "PUT" && incomingUrl.pathname === expectedPath && supportedCreateQuery;
|
|
4698
5157
|
const isTopLevelWorkerUpload = incoming.method === "PUT" && new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/accounts/[^/]+/workers/scripts/[^/]+$`).test(incomingUrl.pathname);
|
|
4699
5158
|
const isReadOnly = incoming.method === "GET" || incoming.method === "HEAD" || incoming.method === "OPTIONS";
|
|
4700
|
-
|
|
5159
|
+
const isAssetUploadSession = incoming.method === "POST" && incomingUrl.pathname === `${expectedPath}/assets-upload-session` && incomingUrl.search === "";
|
|
5160
|
+
const isAssetUpload = incoming.method === "POST" && state.assetSessionStarted && (incomingUrl.pathname === assetUploadPath && incomingUrl.search === "?base64=true" || incomingUrl.search === "" && incomingUrl.pathname.startsWith(`${assetUploadPath}/`) && /^[0-9a-f]{32}$/.test(incomingUrl.pathname.slice(assetUploadPath.length + 1)));
|
|
5161
|
+
if (isTopLevelWorkerUpload && !isConditionalCreate || !isReadOnly && !isConditionalCreate && !isAssetUploadSession && !isAssetUpload && !state.created) {
|
|
4701
5162
|
state.rejectedMutation = `${incoming.method ?? "UNKNOWN"} ${incomingUrl.pathname}${incomingUrl.pathname === expectedPath && !supportedCreateQuery ? " (unsupported Worker create query)" : ""}`;
|
|
4702
5163
|
incoming.resume();
|
|
4703
5164
|
outgoing.writeHead(409, { "Content-Type": "application/json" });
|
|
@@ -4710,6 +5171,7 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4710
5171
|
state.attempted = true;
|
|
4711
5172
|
headers["if-none-match"] = "*";
|
|
4712
5173
|
}
|
|
5174
|
+
if (isAssetUploadSession) state.assetSessionStarted = true;
|
|
4713
5175
|
const upstreamUrl = new URL(`${incomingUrl.pathname}${incomingUrl.search}`, upstreamBase.origin);
|
|
4714
5176
|
const request$2 = (upstreamUrl.protocol === "https:" ? request$1 : request)(upstreamUrl, {
|
|
4715
5177
|
headers,
|
|
@@ -5214,7 +5676,7 @@ async function verifyPlatformHealth(apiUrl, installationId, proxyUrl, applicatio
|
|
|
5214
5676
|
redirect: "manual",
|
|
5215
5677
|
signal: AbortSignal.timeout(3e4)
|
|
5216
5678
|
});
|
|
5217
|
-
const dispatchUrl = dispatchOrigin ?? `https
|
|
5679
|
+
const dispatchUrl = dispatchOrigin ?? `https://${PLATFORM_HEALTH_HOSTNAME_LABEL}.${applicationBaseDomain}`;
|
|
5218
5680
|
const [health, discovery, proxy, dispatch, emailGateway] = await Promise.all([
|
|
5219
5681
|
request(`${apiUrl}/health`),
|
|
5220
5682
|
request(`${apiUrl}/.well-known/void-platform`),
|
|
@@ -5816,14 +6278,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
|
|
|
5816
6278
|
}
|
|
5817
6279
|
async function runPlatformLifecycleCommand(args) {
|
|
5818
6280
|
if (args.subcommand === "domain") {
|
|
5819
|
-
const { runPlatformDomainCommand } = await import("./platform-domain-
|
|
6281
|
+
const { runPlatformDomainCommand } = await import("./platform-domain-D6Xcy9ZX.mjs");
|
|
5820
6282
|
await runPlatformDomainCommand(args);
|
|
5821
6283
|
return;
|
|
5822
6284
|
}
|
|
5823
6285
|
if (args.subcommand === "discover") {
|
|
5824
6286
|
const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
|
|
5825
6287
|
const accountId = await selectAccount(await readAccounts(client), args.account);
|
|
5826
|
-
const { recoverPlatformInstallations } = await import("./platform-recovery-
|
|
6288
|
+
const { recoverPlatformInstallations } = await import("./platform-recovery-BvtcXON5.mjs");
|
|
5827
6289
|
const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
|
|
5828
6290
|
if (recovered.length === 0) {
|
|
5829
6291
|
log.info(`No recoverable Void platform installations were found in ${accountId}.`);
|
|
@@ -5833,7 +6295,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
5833
6295
|
return;
|
|
5834
6296
|
}
|
|
5835
6297
|
if (args.subcommand !== "install") {
|
|
5836
|
-
const { runInstalledPlatformCommand } = await import("./platform-management-
|
|
6298
|
+
const { runInstalledPlatformCommand } = await import("./platform-management-DOtss0BN.mjs");
|
|
5837
6299
|
await runInstalledPlatformCommand(args);
|
|
5838
6300
|
return;
|
|
5839
6301
|
}
|
|
@@ -6057,7 +6519,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6057
6519
|
progress?.stop("Read the saved installation.");
|
|
6058
6520
|
progress = void 0;
|
|
6059
6521
|
if (authoritative) {
|
|
6060
|
-
const { selectAuthoritativePlatformManifest } = await import("./platform-management-
|
|
6522
|
+
const { selectAuthoritativePlatformManifest } = await import("./platform-management-DOtss0BN.mjs");
|
|
6061
6523
|
const selected = selectAuthoritativePlatformManifest(existing, authoritative);
|
|
6062
6524
|
authoritativeProvisionIntents = true;
|
|
6063
6525
|
if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
|
|
@@ -6286,7 +6748,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6286
6748
|
})).actions.filter((action) => action.kind === "worker"),
|
|
6287
6749
|
revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
|
|
6288
6750
|
verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
|
|
6289
|
-
const { verifyPlatformInstallationOwnership } = await import("./platform-management-
|
|
6751
|
+
const { verifyPlatformInstallationOwnership } = await import("./platform-management-DOtss0BN.mjs");
|
|
6290
6752
|
const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
|
|
6291
6753
|
const previous = manifest.resources.find((resource) => resource.key === action.key);
|
|
6292
6754
|
return action.action === "create" && previous && previous.name !== action.name;
|
|
@@ -6426,4 +6888,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
|
|
|
6426
6888
|
return value;
|
|
6427
6889
|
}
|
|
6428
6890
|
//#endregion
|
|
6429
|
-
export {
|
|
6891
|
+
export { PlatformCloudflareClient as A, revalidatePlatformInstallAction as C, checkpointPlatformAccessResources as D, resolvePlatformCloudflareCredential as E, hyperdriveOriginFromConnectionString as M, hyperdriveOriginMatches as N, parsePlatformAccessSetupSpec as O, verifyPlatformRuntimeTokenPermissions as P, platformProvisionIntentHash as S, assertPlatformOperatorCredentialCanApplyPlan as T, recordPlatformWorkerTraffic as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, formatPlatformInstallPlan as b, createPlatformHealthProbeToken as c, loadPlatformRuntime as d, platformDatabaseClientUrl as f, assertProjectZeroTrustAllowsUninstall as g, resolveBootstrapGitHubId as h, verifyPlatformAiGatewayPlan as i, hyperdriveOriginConfigHash as j, preparePlatformAccessSetup as k, createPlatformInstallationSecrets as l, recordResource as m, resolvePlatformInstallDatabaseUrl as n, acquirePlatformDatabaseLifecycleLock as o, readPlatformProjectZeroTrustState as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLockConnection as s, assertNoninteractivePlatformMutationAcknowledged as t, installPlatform as u, trackedPlatformWorkerTraffic as v, platformDatabaseNameFromOwnerUrl as w, platformAdoptionIntentHash as x, createPlatformInstallPlan as y };
|