void 0.21.8 → 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-CQ2wi4Th.mjs → account-cmd-CjyxcGkM.mjs} +4 -4
- package/dist/{auth-link-B_lceSHK.mjs → auth-link-BLO4ptzo.mjs} +4 -4
- package/dist/{auth-router-cZq0HuWe.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 -42
- 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-BY38i-9E.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-BIo37Y7H.mjs → db-gp2sCXyN.mjs} +14 -14
- package/dist/{delete-D6dZ9B6B.mjs → delete-TTedbH6B.mjs} +3 -3
- package/dist/{deploy-BM_LoBXC.mjs → deploy-B19L2Bra.mjs} +1 -1
- package/dist/{deploy-C0JaQSWS.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-dMnE_Lat.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-CJSnEcuj.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-DZldyb8c.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-B9dbG3Od.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-Bex45lQt.mjs → platform-auth-config-BlN8xTdD.mjs} +3 -3
- package/dist/{platform-auth-protection-Dma02l6-.mjs → platform-auth-protection-7d5aV2Jg.mjs} +3 -3
- package/dist/{platform-auth-recovery-COOhMCvx.mjs → platform-auth-recovery-Dxij8ZbR.mjs} +3 -3
- package/dist/{platform-cmd-BtxZrT6w.mjs → platform-cmd-B3hwKrFK.mjs} +5 -5
- package/dist/{platform-cmd-CjNB09OF.mjs → platform-cmd-E0FuL212.mjs} +1 -1
- package/dist/{platform-domain-DppaTARe.mjs → platform-domain-D6Xcy9ZX.mjs} +36 -9
- package/dist/{platform-lifecycle-CYll-oKE.mjs → platform-lifecycle-DMh_qrry.mjs} +1 -1
- package/dist/{platform-lifecycle-BpDlxT3t.mjs → platform-lifecycle-k0E0xoxx.mjs} +769 -59
- package/dist/{platform-management-BKYenDlE.mjs → platform-management-Brz_BDiT.mjs} +72 -10
- package/dist/{platform-management-BS5r6Ao8.mjs → platform-management-DOtss0BN.mjs} +1 -1
- package/dist/{platform-recovery-dJ5Me-I0.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-t6rndSRW.mjs +0 -2
- package/dist/migrate-TsHGBnDA.mjs +0 -2
- package/dist/prepare-BZXkjdNe.mjs +0 -2
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
|
|
2
|
-
import { a as join } from "./dist-BVqXeR_h.mjs";
|
|
3
|
-
import {
|
|
2
|
+
import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
|
|
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-
|
|
12
|
-
import {
|
|
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";
|
|
13
|
+
import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
13
15
|
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
14
16
|
import { tmpdir } from "node:os";
|
|
15
17
|
import { execFile } from "node:child_process";
|
|
16
|
-
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";
|
|
17
19
|
import { stripVTControlCharacters } from "node:util";
|
|
18
20
|
import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
|
|
19
21
|
import { readFile } from "node:fs/promises";
|
|
@@ -651,7 +653,7 @@ function parsePostgresConnectionString(value) {
|
|
|
651
653
|
let mtls;
|
|
652
654
|
if (sslmode) {
|
|
653
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\".");
|
|
654
|
-
mtls = { sslmode };
|
|
656
|
+
mtls = { sslmode: "require" };
|
|
655
657
|
}
|
|
656
658
|
return {
|
|
657
659
|
database,
|
|
@@ -755,6 +757,11 @@ var PlatformAccessClient = class {
|
|
|
755
757
|
throw new Error("platform: Access inventory exceeded the supported pagination limit.");
|
|
756
758
|
}
|
|
757
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
|
+
*/
|
|
758
765
|
function identityRule(rule) {
|
|
759
766
|
if (rule.email_domain) {
|
|
760
767
|
const domain = object(rule.email_domain, "email domain").domain;
|
|
@@ -1410,7 +1417,6 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
|
|
|
1410
1417
|
input,
|
|
1411
1418
|
credential: result.credential
|
|
1412
1419
|
};
|
|
1413
|
-
announceCloudflareLogin();
|
|
1414
1420
|
const login = await runWrangler(["login"], false);
|
|
1415
1421
|
assertNotCancelled();
|
|
1416
1422
|
if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
|
|
@@ -1499,7 +1505,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
|
|
|
1499
1505
|
}
|
|
1500
1506
|
const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
|
|
1501
1507
|
if (existingWorkers.length) {
|
|
1502
|
-
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-
|
|
1508
|
+
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-7d5aV2Jg.mjs");
|
|
1503
1509
|
await verifyProtectionWorkerOrigins({
|
|
1504
1510
|
request: input.client.request.bind(input.client),
|
|
1505
1511
|
listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
|
|
@@ -1830,6 +1836,18 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1830
1836
|
};
|
|
1831
1837
|
}
|
|
1832
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
|
+
}
|
|
1833
1851
|
const deployment = original.deployment;
|
|
1834
1852
|
const current = (await createPlatformInstallPlan(client, {
|
|
1835
1853
|
accountId: original.account.id,
|
|
@@ -2215,6 +2233,284 @@ function recordPlatformWorkerTraffic(manifest, workerName, inventory, updatedAt)
|
|
|
2215
2233
|
return next;
|
|
2216
2234
|
}
|
|
2217
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
|
|
2218
2514
|
//#region src/cli/platform-installer.ts
|
|
2219
2515
|
const REMOTE_INSTALLATION_MANIFEST_KEY = PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY;
|
|
2220
2516
|
const PLATFORM_WORKER_SECRET_UPDATE_NAMES = [
|
|
@@ -2529,7 +2825,13 @@ async function preparePlatformDatabaseSchema(manifest, runtime, runtimeDirectory
|
|
|
2529
2825
|
}
|
|
2530
2826
|
async function recoverInterruptedPlatformWorkerCutover(manifest, interruptedCutover, workerActions, configs, previousConfigs, plan, dependencies, ids, zoneId, previousPublicEndpoints, hasEmailGatewayArtifact, runPinnedPlatformWrangler, now, writeRemoteInstallationCheckpoint, onManifestChange) {
|
|
2531
2827
|
if (interruptedCutover) {
|
|
2532
|
-
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))) {
|
|
2533
2835
|
const snapshot = interruptedCutover.staged.get(key);
|
|
2534
2836
|
const preservedRoutes = dependencies.recoverWorkerCutover ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, snapshot.workerName);
|
|
2535
2837
|
const recoveryConfig = mergePlatformWorkerTrafficConfig({
|
|
@@ -2632,6 +2934,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
2632
2934
|
const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2633
2935
|
const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
|
|
2634
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);
|
|
2635
2938
|
const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
|
|
2636
2939
|
const runtimeDirectory = resolvedRuntime.directory;
|
|
2637
2940
|
const runtime = resolvedRuntime.manifest;
|
|
@@ -2723,6 +3026,10 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
2723
3026
|
writeInstallationManifest(manifest);
|
|
2724
3027
|
}
|
|
2725
3028
|
assertRequiredPlatformSecrets(plan, manifest, secrets, !!dependencies.prepareAuthentication);
|
|
3029
|
+
if (hyperdriveAction?.action === "create") {
|
|
3030
|
+
parsePostgresConnectionString(secrets.databaseUrl);
|
|
3031
|
+
assertVoidCreatedHyperdriveTrustsDatabase(secrets.databaseUrl);
|
|
3032
|
+
}
|
|
2726
3033
|
const pendingSecrets = secretCheckpoint(secrets, workerSecretUpdateIntent);
|
|
2727
3034
|
writePendingInstallationSecrets(manifest, {
|
|
2728
3035
|
...pendingSecrets,
|
|
@@ -3074,38 +3381,117 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3074
3381
|
};
|
|
3075
3382
|
writePendingInstallationSecrets(manifest, secretCheckpoint(secrets, workerSecretUpdateIntent));
|
|
3076
3383
|
}
|
|
3077
|
-
|
|
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) => {
|
|
3078
3437
|
dependencies.onProgress?.(`Deploying the ${key} service`);
|
|
3438
|
+
let workerStepStarted = performance.now();
|
|
3439
|
+
const markWorkerStep = (step) => {
|
|
3440
|
+
if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] worker ${key} ${step} ${(performance.now() - workerStepStarted).toFixed(0)} ms\n`);
|
|
3441
|
+
workerStepStarted = performance.now();
|
|
3442
|
+
};
|
|
3079
3443
|
const action = workerActions.find((entry) => entry.key === (key === "emailGateway" ? "email-gateway-worker" : `${key}-worker`));
|
|
3080
3444
|
const workerSecrets = platformWorkerSecretPayload(key, workerDeploymentSecrets, plan.account.id, zoneId, action.action !== "create");
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
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
|
+
});
|
|
3084
3453
|
await dependencies.revalidateBeforeWorkerDeploy(manifest, action);
|
|
3454
|
+
markWorkerStep("revalidate");
|
|
3085
3455
|
const preservedRoutes = dependencies.deployWorker ? [] : action.action === "create" ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, action.name);
|
|
3456
|
+
markWorkerStep("traffic inventory");
|
|
3086
3457
|
const workerConfig = mergePlatformWorkerTrafficConfig(configs[key], preservedRoutes);
|
|
3087
3458
|
const previousWorkerConfig = mergePlatformWorkerTrafficConfig(previousConfigs[key], preservedRoutes);
|
|
3088
3459
|
const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
|
|
3089
3460
|
apiBaseUrl: cloudflareApiBaseUrl,
|
|
3461
|
+
...parallelWorkerUploads ? {
|
|
3462
|
+
acquireUploadSlot,
|
|
3463
|
+
onReadyForActivation,
|
|
3464
|
+
waitForPriorWorker
|
|
3465
|
+
} : {},
|
|
3466
|
+
...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
|
|
3090
3467
|
onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
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
|
+
});
|
|
3094
3473
|
},
|
|
3095
3474
|
onTrafficPublished: async () => {
|
|
3096
3475
|
if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
|
|
3097
3476
|
const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
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
|
+
});
|
|
3104
3488
|
},
|
|
3105
3489
|
onUnsafeTraffic: async () => {
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
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);
|
|
3109
3495
|
},
|
|
3110
3496
|
managedRoutes: preserveDisabled ? [] : managedRoutes.filter((route) => route.script === action.name).map((route) => route.pattern),
|
|
3111
3497
|
preservedRoutes,
|
|
@@ -3125,14 +3511,37 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3125
3511
|
retainNewWorker: async () => {
|
|
3126
3512
|
await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
|
|
3127
3513
|
},
|
|
3128
|
-
runner: runPinnedPlatformWrangler,
|
|
3514
|
+
runner: parallelWorkerUploads ? runParallelPinnedPlatformWrangler : runPinnedPlatformWrangler,
|
|
3129
3515
|
workerExisted: action.action !== "create"
|
|
3130
3516
|
});
|
|
3517
|
+
markWorkerStep("deploy");
|
|
3131
3518
|
if (rollback) workerRollbacks.push(rollback);
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
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
|
+
});
|
|
3527
|
+
markWorkerStep("record");
|
|
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);
|
|
3136
3545
|
manifest = setInstallationEndpoints(manifest, {
|
|
3137
3546
|
apiUrl: plan.apiUrl,
|
|
3138
3547
|
dashboardUrl: null,
|
|
@@ -3276,6 +3685,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3276
3685
|
const previousWorkerConfig = mergePlatformWorkerTrafficConfig(configs[key], preservedRoutes);
|
|
3277
3686
|
const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
|
|
3278
3687
|
apiBaseUrl: cloudflareApiBaseUrl,
|
|
3688
|
+
...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
|
|
3279
3689
|
onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
|
|
3280
3690
|
manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
|
|
3281
3691
|
writeInstallationManifest(manifest);
|
|
@@ -3284,12 +3694,15 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3284
3694
|
onTrafficPublished: async () => {
|
|
3285
3695
|
if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
|
|
3286
3696
|
const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
|
|
3287
|
-
|
|
3697
|
+
const next = recordPlatformWorkerTraffic(manifest, action.name, {
|
|
3288
3698
|
customDomains,
|
|
3289
3699
|
routes
|
|
3290
3700
|
}, now());
|
|
3291
|
-
|
|
3292
|
-
|
|
3701
|
+
if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
|
|
3702
|
+
manifest = next;
|
|
3703
|
+
writeInstallationManifest(manifest);
|
|
3704
|
+
await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
|
|
3705
|
+
}
|
|
3293
3706
|
},
|
|
3294
3707
|
onUnsafeTraffic: async () => {
|
|
3295
3708
|
manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
|
|
@@ -3314,6 +3727,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3314
3727
|
retainNewWorker: async () => {
|
|
3315
3728
|
await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
|
|
3316
3729
|
},
|
|
3730
|
+
reuseMatchingVersion: false,
|
|
3317
3731
|
runner: runPinnedPlatformWrangler,
|
|
3318
3732
|
workerExisted: true
|
|
3319
3733
|
});
|
|
@@ -3337,6 +3751,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3337
3751
|
dependencies.onProgress?.("Preparing shared email routing");
|
|
3338
3752
|
emailOperationId = await (dependencies.bootstrapEmail ?? bootstrapPlatformSharedEmail)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce));
|
|
3339
3753
|
}
|
|
3754
|
+
await releaseDatabaseLock.liftProjectZeroTrustFence?.();
|
|
3340
3755
|
if (dependencies.client.deleteKvValue) await dependencies.client.deleteKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY);
|
|
3341
3756
|
else if (wasDisabled || !previousPublicEndpoints) throw new Error("platform: Routing disablement removal is unavailable.");
|
|
3342
3757
|
if (requiresStagedCommit) {
|
|
@@ -3407,10 +3822,14 @@ function saveInstalledPlatformConnection(plan, manifest) {
|
|
|
3407
3822
|
});
|
|
3408
3823
|
}
|
|
3409
3824
|
async function writeRemoteInstallationCheckpointWithoutDatabase(client, plan, manifest, databaseLock) {
|
|
3825
|
+
const startedAt = performance.now();
|
|
3410
3826
|
await databaseLock?.checkpoint?.(manifest);
|
|
3827
|
+
if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] database checkpoint ${(performance.now() - startedAt).toFixed(0)} ms\n`);
|
|
3411
3828
|
const routing = manifest.resources.find((resource) => resource.key === "routing-kv" && resource.kind === "kv-namespace" && resource.ownership === "created" && resource.state === "ready");
|
|
3412
3829
|
if (!routing) return;
|
|
3830
|
+
const kvStartedAt = performance.now();
|
|
3413
3831
|
await client.putKvValue(plan.account.id, routing.id, REMOTE_INSTALLATION_MANIFEST_KEY, JSON.stringify(manifest));
|
|
3832
|
+
if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] remote checkpoint ${(performance.now() - kvStartedAt).toFixed(0)} ms\n`);
|
|
3414
3833
|
}
|
|
3415
3834
|
function recordProvisionIntent(manifest, action, now) {
|
|
3416
3835
|
const previousResourceId = manifest.resources.find((resource) => resource.key === action.key)?.id ?? null;
|
|
@@ -3719,6 +4138,14 @@ function assertRequiredPlatformSecrets(plan, manifest, secrets, authenticationWi
|
|
|
3719
4138
|
}
|
|
3720
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.`);
|
|
3721
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
|
+
}
|
|
3722
4149
|
/** Make pg's current strict TLS behavior explicit without rewriting saved connection details. */
|
|
3723
4150
|
function platformDatabaseClientUrl(databaseUrl) {
|
|
3724
4151
|
let url;
|
|
@@ -3727,7 +4154,13 @@ function platformDatabaseClientUrl(databaseUrl) {
|
|
|
3727
4154
|
} catch {
|
|
3728
4155
|
return databaseUrl;
|
|
3729
4156
|
}
|
|
3730
|
-
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;
|
|
3731
4164
|
url.searchParams.set("sslmode", "verify-full");
|
|
3732
4165
|
return url.toString();
|
|
3733
4166
|
}
|
|
@@ -3784,6 +4217,21 @@ async function readPlatformDatabaseLifecycleManifest(databaseUrl, installationId
|
|
|
3784
4217
|
await pool.end();
|
|
3785
4218
|
}
|
|
3786
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
|
+
}
|
|
3787
4235
|
async function inspectPlatformDatabaseStateConnection(client, database) {
|
|
3788
4236
|
const relation = await client.query(`SELECT to_regclass('"drizzle"."__drizzle_migrations"')::text AS "relation"`);
|
|
3789
4237
|
if (relation.rows.length === 0 || relation.rows[0]?.relation === null) return {
|
|
@@ -3911,6 +4359,10 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
|
|
|
3911
4359
|
protection: parsePlatformAccessProtection(settings.protection)
|
|
3912
4360
|
} : null;
|
|
3913
4361
|
};
|
|
4362
|
+
release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
|
|
4363
|
+
release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
|
|
4364
|
+
release.fenceProjectZeroTrust = () => fencePlatformProjectZeroTrustConnection(client);
|
|
4365
|
+
release.liftProjectZeroTrustFence = () => liftPlatformProjectZeroTrustFenceConnection(client);
|
|
3914
4366
|
release.requiredSafetyProtocols = await readRequiredPlatformSafetyProtocols(client);
|
|
3915
4367
|
release.checkpoint = async (manifest) => {
|
|
3916
4368
|
if (released) throw new Error("platform: Cannot checkpoint lifecycle state after releasing its lock.");
|
|
@@ -4122,10 +4574,153 @@ async function claimPlatformDatabaseConnection(client, installationId, ownership
|
|
|
4122
4574
|
throw error;
|
|
4123
4575
|
}
|
|
4124
4576
|
}
|
|
4577
|
+
let cachedPlatformUploaderHash;
|
|
4578
|
+
function platformWorkerUploaderHash() {
|
|
4579
|
+
if (cachedPlatformUploaderHash !== void 0) return cachedPlatformUploaderHash;
|
|
4580
|
+
try {
|
|
4581
|
+
const launcher = resolveWranglerArgv(process.cwd())[1];
|
|
4582
|
+
if (!launcher) return cachedPlatformUploaderHash = null;
|
|
4583
|
+
const digest = createHash("sha256");
|
|
4584
|
+
digest.update("platform-uploader:v1\0");
|
|
4585
|
+
digest.update(process.version);
|
|
4586
|
+
digest.update(readFileSync(resolve(dirname(launcher), "..", "..", "package.json")));
|
|
4587
|
+
const cfDirectory = dirname(fileURLToPath(import.meta.resolve("cf/package.json")));
|
|
4588
|
+
digest.update(readFileSync(join(cfDirectory, "package.json")));
|
|
4589
|
+
digest.update(readFileSync(join(cfDirectory, "bin", "cf")));
|
|
4590
|
+
const cfCodeHash = platformWorkerDirectoryHash(join(cfDirectory, "dist"));
|
|
4591
|
+
if (!cfCodeHash) throw new Error("Cloudflare CLI code is unavailable.");
|
|
4592
|
+
digest.update(cfCodeHash);
|
|
4593
|
+
let dependencyDirectory = dirname(fileURLToPath(import.meta.resolve("@cloudflare/build-output-utils")));
|
|
4594
|
+
while (!existsSync(join(dependencyDirectory, "package.json"))) {
|
|
4595
|
+
const parent = dirname(dependencyDirectory);
|
|
4596
|
+
if (parent === dependencyDirectory) throw new Error("Cloudflare build output package is unavailable.");
|
|
4597
|
+
dependencyDirectory = parent;
|
|
4598
|
+
}
|
|
4599
|
+
digest.update(readFileSync(join(dependencyDirectory, "package.json")));
|
|
4600
|
+
const buildOutputCodeHash = platformWorkerDirectoryHash(join(dependencyDirectory, "dist"));
|
|
4601
|
+
if (!buildOutputCodeHash) throw new Error("Cloudflare build output code is unavailable.");
|
|
4602
|
+
digest.update(buildOutputCodeHash);
|
|
4603
|
+
const visited = /* @__PURE__ */ new Set();
|
|
4604
|
+
const visit = (path, name) => {
|
|
4605
|
+
if (visited.has(path)) return;
|
|
4606
|
+
visited.add(path);
|
|
4607
|
+
const source = readFileSync(path);
|
|
4608
|
+
digest.update(`${name}\0`);
|
|
4609
|
+
digest.update(source);
|
|
4610
|
+
const imports = [...source.toString("utf8").matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"](\.{1,2}\/[^'"]+)['"]/g)].map((match) => match[1]).sort();
|
|
4611
|
+
for (const specifier of imports) visit(resolve(dirname(path), specifier), specifier);
|
|
4612
|
+
};
|
|
4613
|
+
visit(launcher, "launcher");
|
|
4614
|
+
return cachedPlatformUploaderHash = digest.digest("hex");
|
|
4615
|
+
} catch {
|
|
4616
|
+
return cachedPlatformUploaderHash = null;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
function platformWorkerDirectoryHash(directory) {
|
|
4620
|
+
if (!lstatSync(directory).isDirectory()) return null;
|
|
4621
|
+
const digest = createHash("sha256");
|
|
4622
|
+
const visit = (path, prefix) => {
|
|
4623
|
+
for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4624
|
+
const relativePath = `${prefix}${entry.name}`;
|
|
4625
|
+
const absolutePath = join(path, entry.name);
|
|
4626
|
+
if (entry.isDirectory()) {
|
|
4627
|
+
digest.update(`directory:${relativePath}\0`);
|
|
4628
|
+
if (!visit(absolutePath, `${relativePath}/`)) return false;
|
|
4629
|
+
} else if (entry.isFile()) {
|
|
4630
|
+
digest.update(`file:${relativePath}\0`);
|
|
4631
|
+
digest.update(createHash("sha256").update(readFileSync(absolutePath)).digest("hex"));
|
|
4632
|
+
} else return false;
|
|
4633
|
+
}
|
|
4634
|
+
return true;
|
|
4635
|
+
};
|
|
4636
|
+
return visit(directory, "") ? digest.digest("hex") : null;
|
|
4637
|
+
}
|
|
4638
|
+
/** Hash every input that can change a version upload; missing or unusual files disable reuse. */
|
|
4639
|
+
function platformWorkerUploadFingerprint(config, managedRoutes = []) {
|
|
4640
|
+
const uploaderHash = platformWorkerUploaderHash();
|
|
4641
|
+
if (typeof config.main !== "string" || !uploaderHash) return null;
|
|
4642
|
+
try {
|
|
4643
|
+
if (!lstatSync(config.main).isFile()) return null;
|
|
4644
|
+
const mainSha256 = createHash("sha256").update(readFileSync(config.main)).digest("hex");
|
|
4645
|
+
const normalized = {
|
|
4646
|
+
...config,
|
|
4647
|
+
main: { sha256: mainSha256 }
|
|
4648
|
+
};
|
|
4649
|
+
if (config.assets !== void 0) {
|
|
4650
|
+
const assets = config.assets;
|
|
4651
|
+
if (!assets || typeof assets !== "object" || Array.isArray(assets)) return null;
|
|
4652
|
+
const directory = assets.directory;
|
|
4653
|
+
if (typeof directory !== "string") return null;
|
|
4654
|
+
const assetsHash = platformWorkerDirectoryHash(directory);
|
|
4655
|
+
if (!assetsHash) return null;
|
|
4656
|
+
normalized.assets = {
|
|
4657
|
+
...assets,
|
|
4658
|
+
directory: assetsHash
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
return createHash("sha256").update(JSON.stringify({
|
|
4662
|
+
schema: 2,
|
|
4663
|
+
config: normalized,
|
|
4664
|
+
managedRoutes,
|
|
4665
|
+
uploaderHash
|
|
4666
|
+
})).digest("hex");
|
|
4667
|
+
} catch {
|
|
4668
|
+
return null;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
function snapshotPlatformWorkerUploadInputs(config, directory) {
|
|
4672
|
+
const main = config.main;
|
|
4673
|
+
if (typeof main !== "string") throw new Error("platform: Worker upload has no script artifact.");
|
|
4674
|
+
const snapshotMain = join(directory, "worker-upload.js");
|
|
4675
|
+
copyFileSync(main, snapshotMain);
|
|
4676
|
+
const snapshot = {
|
|
4677
|
+
...config,
|
|
4678
|
+
main: snapshotMain
|
|
4679
|
+
};
|
|
4680
|
+
if (config.assets !== void 0) {
|
|
4681
|
+
const assets = config.assets;
|
|
4682
|
+
const sourceDirectory = assets.directory;
|
|
4683
|
+
if (typeof sourceDirectory !== "string") throw new Error("platform: Worker upload has no assets directory.");
|
|
4684
|
+
const snapshotDirectory = join(directory, "worker-assets");
|
|
4685
|
+
cpSync(sourceDirectory, snapshotDirectory, { recursive: true });
|
|
4686
|
+
snapshot.assets = {
|
|
4687
|
+
...assets,
|
|
4688
|
+
directory: snapshotDirectory
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
return snapshot;
|
|
4692
|
+
}
|
|
4693
|
+
function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
|
|
4694
|
+
const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
4695
|
+
return async (args, account, cwd, capture) => {
|
|
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 {}
|
|
4716
|
+
return fallback(args, account, cwd, capture);
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4125
4719
|
async function deployPlatformWorker(config, secrets, options) {
|
|
4126
4720
|
const directory = mkdtempSync(join(tmpdir(), "void-platform-deploy-"));
|
|
4127
4721
|
const workerName = String(config.name);
|
|
4128
4722
|
const accountId = String(config.account_id);
|
|
4723
|
+
const readRunner = options.readCloudflare ? platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner) : options.runner;
|
|
4129
4724
|
let activatedVersionId = null;
|
|
4130
4725
|
let activatedDeploymentId = null;
|
|
4131
4726
|
let uploadedVersionId = null;
|
|
@@ -4136,22 +4731,106 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4136
4731
|
const previousTraffic = isolatePlatformWorkerCustomDomains(options.previousConfig);
|
|
4137
4732
|
let uploadConfig = withoutPlatformWorkerTrafficAttachments(nextTraffic.wranglerConfig);
|
|
4138
4733
|
const nextTriggers = readTriggerConfigFromObject(nextTraffic.wranglerConfig);
|
|
4734
|
+
const contentHash = secrets === null ? platformWorkerUploadFingerprint(config, options.managedRoutes) : null;
|
|
4735
|
+
const reuseMatchingVersion = options.reuseMatchingVersion !== false;
|
|
4139
4736
|
let previousTriggers = readTriggerConfigFromObject(previousTraffic.wranglerConfig);
|
|
4140
4737
|
try {
|
|
4141
4738
|
const configPath = join(directory, "wrangler.json");
|
|
4142
4739
|
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4740
|
+
const reconcileExistingVersion = async (versionId, triggerConfig, runner, deploymentId) => {
|
|
4741
|
+
try {
|
|
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({
|
|
4751
|
+
configPath,
|
|
4752
|
+
knownTriggerConfigs: { [versionId]: nextTriggers },
|
|
4753
|
+
manageRoutes: false,
|
|
4754
|
+
previousTriggerConfig: triggerConfig,
|
|
4755
|
+
publishEmptyCustomDomains: async () => {
|
|
4756
|
+
await options.removeCustomDomains();
|
|
4757
|
+
return cleanPlatformCommandResult();
|
|
4758
|
+
},
|
|
4759
|
+
runWrangler: platformWorkerCommandRunner(accountId, directory, runner),
|
|
4760
|
+
targetTriggerConfig: nextTriggers,
|
|
4761
|
+
targetVersionId: versionId,
|
|
4762
|
+
workerName
|
|
4763
|
+
}), workerName, versionId, "publish");
|
|
4764
|
+
if (nextTraffic.customDomains.length > 0) await options.publishCustomDomains(workerName, nextTraffic.customDomains);
|
|
4765
|
+
if (options.managedRoutes.length > 0) await options.publishRoutes(workerName, options.managedRoutes);
|
|
4766
|
+
await options.onTrafficPublished();
|
|
4767
|
+
if (!deploymentId && await readActivePlatformWorkerVersion(configPath, accountId, workerName, runner) !== versionId) throw new Error(`platform: Worker ${workerName} changed during traffic reconciliation.`);
|
|
4768
|
+
if (deploymentId) {
|
|
4769
|
+
const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, runner);
|
|
4770
|
+
if (after[0]?.id !== deploymentId || !deploymentRunsOnlyVersion(after[0], versionId)) throw new Error(`platform: Worker ${workerName} changed deployment lineage during reconciliation.`);
|
|
4771
|
+
}
|
|
4772
|
+
return null;
|
|
4773
|
+
} catch (error) {
|
|
4774
|
+
const failures = await recoverFailedPlatformWorkerDeployment({
|
|
4775
|
+
checkpointUnsafeTraffic: options.onUnsafeTraffic,
|
|
4776
|
+
closeRoutingGate: options.retainNewWorker,
|
|
4777
|
+
rollback: null,
|
|
4778
|
+
unsafeTraffic: true
|
|
4779
|
+
});
|
|
4780
|
+
throw new PlatformWorkerRecoveryIncompleteError(`platform: Worker ${workerName} traffic reconciliation failed: ${errorMessage(error)}${failures.length > 0 ? ` Safety recovery also failed: ${failures.join("; ")}` : ""}`, { cause: error });
|
|
4781
|
+
}
|
|
4782
|
+
};
|
|
4783
|
+
if (options.workerExisted && contentHash && reuseMatchingVersion && options.readCloudflare) {
|
|
4784
|
+
let candidate;
|
|
4785
|
+
let changedDuringRead = false;
|
|
4786
|
+
try {
|
|
4787
|
+
const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4788
|
+
const activeVersion = before[0]?.versions.length === 1 && before[0].versions[0]?.percentage === 100 ? before[0].versions[0].versionId : null;
|
|
4789
|
+
if (!activeVersion) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
|
|
4790
|
+
const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, readRunner);
|
|
4791
|
+
const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4792
|
+
changedDuringRead = before[0]?.id !== after[0]?.id || !deploymentRunsOnlyVersion(after[0], snapshot.versionId);
|
|
4793
|
+
if (!changedDuringRead && snapshot.contentHash === contentHash) candidate = {
|
|
4794
|
+
deploymentId: after[0].id,
|
|
4795
|
+
snapshot
|
|
4796
|
+
};
|
|
4797
|
+
} catch (error) {
|
|
4798
|
+
if (error instanceof Error && (error.message.includes("changed concurrently") || error.message.includes("does not belong to this installation"))) throw error;
|
|
4799
|
+
}
|
|
4800
|
+
if (changedDuringRead) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
|
|
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
|
+
}
|
|
4806
|
+
}
|
|
4143
4807
|
if (options.workerExisted) {
|
|
4144
|
-
const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config),
|
|
4808
|
+
const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), readRunner);
|
|
4145
4809
|
previousVersionId = previous.versionId;
|
|
4146
|
-
const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4810
|
+
const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4147
4811
|
if (!deploymentRunsOnlyVersion(deployments[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
|
|
4148
4812
|
previousDeployment = deployments[0];
|
|
4149
4813
|
previousDeploymentId = previousDeployment.id;
|
|
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
|
+
}
|
|
4150
4819
|
uploadConfig = withoutCompletedPlatformWorkerMigrations(uploadConfig, previous.migrationTag);
|
|
4151
4820
|
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4152
4821
|
previousTriggers = resolvePlatformPreviousTriggerConfig(withoutPlatformCustomDomainTriggers(previous.triggerConfig), previousTriggers, options.previousPublicEndpoints);
|
|
4153
4822
|
previousTriggers = withoutPlatformCustomDomainTriggers(mergePreservedTriggerRoutes(previousTriggers, options.preservedRoutes));
|
|
4154
4823
|
}
|
|
4824
|
+
const immutableConfig = contentHash ? snapshotPlatformWorkerUploadInputs(config, directory) : null;
|
|
4825
|
+
const uploadedContentHash = immutableConfig ? platformWorkerUploadFingerprint(immutableConfig, options.managedRoutes) : null;
|
|
4826
|
+
if (immutableConfig) {
|
|
4827
|
+
uploadConfig = {
|
|
4828
|
+
...uploadConfig,
|
|
4829
|
+
main: immutableConfig.main,
|
|
4830
|
+
...immutableConfig.assets === void 0 ? {} : { assets: immutableConfig.assets }
|
|
4831
|
+
};
|
|
4832
|
+
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4833
|
+
}
|
|
4155
4834
|
const argv = [
|
|
4156
4835
|
...options.workerExisted ? ["versions", "upload"] : ["deploy"],
|
|
4157
4836
|
"--config",
|
|
@@ -4161,21 +4840,33 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4161
4840
|
"--experimental-auto-create=false",
|
|
4162
4841
|
...!options.workerExisted ? ["--autoconfig=false"] : [],
|
|
4163
4842
|
"--message",
|
|
4164
|
-
encodeCloudflareVersionMessage([], nextTriggers)
|
|
4843
|
+
encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
|
|
4165
4844
|
];
|
|
4166
4845
|
if (secrets) {
|
|
4167
4846
|
const secretsPath = join(directory, "secrets.json");
|
|
4168
4847
|
writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
|
|
4169
4848
|
argv.push("--secrets-file", secretsPath);
|
|
4170
4849
|
}
|
|
4171
|
-
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
|
+
}
|
|
4172
4862
|
uploadedVersionId = parseUploadedPlatformWorkerVersion(`${uploaded.stdout}\n${uploaded.stderr}`);
|
|
4173
4863
|
if (!options.workerExisted) activatedVersionId = uploadedVersionId;
|
|
4174
|
-
assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
|
|
4175
4864
|
if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
|
|
4176
4865
|
await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
|
|
4866
|
+
await options.onReadyForActivation?.();
|
|
4867
|
+
await options.waitForPriorWorker?.();
|
|
4177
4868
|
if (options.workerExisted) {
|
|
4178
|
-
const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4869
|
+
const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4179
4870
|
if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
|
|
4180
4871
|
activatedVersionId = uploadedVersionId;
|
|
4181
4872
|
assertCleanPlatformWrangler(await options.runner([
|
|
@@ -4189,7 +4880,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4189
4880
|
"--yes"
|
|
4190
4881
|
], accountId, directory, true), `activate ${workerName}`);
|
|
4191
4882
|
}
|
|
4192
|
-
const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName,
|
|
4883
|
+
const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4193
4884
|
const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
|
|
4194
4885
|
if (lineage.kind === "concurrent-after") {
|
|
4195
4886
|
activatedVersionId = null;
|
|
@@ -4209,7 +4900,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4209
4900
|
expectedActiveDeploymentId: lineage.deploymentId,
|
|
4210
4901
|
expectedActiveVersionId: uploadedVersionId,
|
|
4211
4902
|
restoration: interveningDeployment,
|
|
4212
|
-
runner:
|
|
4903
|
+
runner: readRunner,
|
|
4213
4904
|
workerName
|
|
4214
4905
|
});
|
|
4215
4906
|
} catch (recoveryError) {
|
|
@@ -4235,7 +4926,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4235
4926
|
await options.removeCustomDomains();
|
|
4236
4927
|
return cleanPlatformCommandResult();
|
|
4237
4928
|
},
|
|
4238
|
-
runWrangler: platformWorkerCommandRunner(accountId, directory,
|
|
4929
|
+
runWrangler: platformWorkerCommandRunner(accountId, directory, readRunner),
|
|
4239
4930
|
targetTriggerConfig: nextTriggers,
|
|
4240
4931
|
targetVersionId: uploadedVersionId,
|
|
4241
4932
|
workerName
|
|
@@ -4259,7 +4950,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4259
4950
|
removeCustomDomains: options.removeCustomDomains,
|
|
4260
4951
|
publishCustomDomains: options.publishCustomDomains,
|
|
4261
4952
|
retainNewWorker: options.retainNewWorker,
|
|
4262
|
-
runner:
|
|
4953
|
+
runner: readRunner,
|
|
4263
4954
|
workerExisted: options.workerExisted,
|
|
4264
4955
|
workerName
|
|
4265
4956
|
});
|
|
@@ -4286,7 +4977,7 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4286
4977
|
removeCustomDomains: options.removeCustomDomains,
|
|
4287
4978
|
publishCustomDomains: options.publishCustomDomains,
|
|
4288
4979
|
retainNewWorker: options.retainNewWorker,
|
|
4289
|
-
runner:
|
|
4980
|
+
runner: readRunner,
|
|
4290
4981
|
workerExisted: options.workerExisted,
|
|
4291
4982
|
workerName
|
|
4292
4983
|
}) : null,
|
|
@@ -4387,9 +5078,11 @@ async function runPlatformWrangler(args, accountId, cwd, capture) {
|
|
|
4387
5078
|
return runPlatformWranglerAtApiBase(args, accountId, cwd, capture);
|
|
4388
5079
|
}
|
|
4389
5080
|
const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
|
|
4390
|
-
async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl()) {
|
|
5081
|
+
async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
|
|
5082
|
+
const startedAt = performance.now();
|
|
4391
5083
|
const result = await createDefaultRunWrangler(process.cwd(), {
|
|
4392
5084
|
launcher: resolveWranglerArgv(process.cwd()),
|
|
5085
|
+
parallelWorkerUpload,
|
|
4393
5086
|
timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
|
|
4394
5087
|
})([...args], {
|
|
4395
5088
|
accountId,
|
|
@@ -4397,6 +5090,10 @@ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBa
|
|
|
4397
5090
|
captureJson: capture,
|
|
4398
5091
|
cwd
|
|
4399
5092
|
});
|
|
5093
|
+
if (process.env.VOID_PLATFORM_TIMINGS === "1") {
|
|
5094
|
+
const operation = args[0] === "versions" || args[0] === "deployments" ? `${args[0]} ${args[1] ?? ""}` : args[0] ?? "unknown";
|
|
5095
|
+
process.stderr.write(`[platform timing] wrangler ${operation} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
|
|
5096
|
+
}
|
|
4400
5097
|
const stdout = result.stdout || "";
|
|
4401
5098
|
const stderr = result.stderr || "";
|
|
4402
5099
|
if (!capture && result.status !== 0) {
|
|
@@ -4435,8 +5132,11 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4435
5132
|
const upstreamBase = new URL(upstreamApiBaseUrl);
|
|
4436
5133
|
if (upstreamBase.protocol !== "http:" && upstreamBase.protocol !== "https:") throw new Error("platform: Cloudflare API base URL must use HTTP or HTTPS.");
|
|
4437
5134
|
const basePath = upstreamBase.pathname.replace(/\/+$/, "");
|
|
4438
|
-
const
|
|
5135
|
+
const accountPath = `${basePath}/accounts/${encodeURIComponent(accountId)}`;
|
|
5136
|
+
const expectedPath = `${accountPath}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
5137
|
+
const assetUploadPath = `${accountPath}/workers/assets/upload`;
|
|
4439
5138
|
const state = {
|
|
5139
|
+
assetSessionStarted: false,
|
|
4440
5140
|
attempted: false,
|
|
4441
5141
|
conflictStatus: null,
|
|
4442
5142
|
created: false,
|
|
@@ -4456,7 +5156,9 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4456
5156
|
const isConditionalCreate = incoming.method === "PUT" && incomingUrl.pathname === expectedPath && supportedCreateQuery;
|
|
4457
5157
|
const isTopLevelWorkerUpload = incoming.method === "PUT" && new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/accounts/[^/]+/workers/scripts/[^/]+$`).test(incomingUrl.pathname);
|
|
4458
5158
|
const isReadOnly = incoming.method === "GET" || incoming.method === "HEAD" || incoming.method === "OPTIONS";
|
|
4459
|
-
|
|
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) {
|
|
4460
5162
|
state.rejectedMutation = `${incoming.method ?? "UNKNOWN"} ${incomingUrl.pathname}${incomingUrl.pathname === expectedPath && !supportedCreateQuery ? " (unsupported Worker create query)" : ""}`;
|
|
4461
5163
|
incoming.resume();
|
|
4462
5164
|
outgoing.writeHead(409, { "Content-Type": "application/json" });
|
|
@@ -4469,6 +5171,7 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
|
|
|
4469
5171
|
state.attempted = true;
|
|
4470
5172
|
headers["if-none-match"] = "*";
|
|
4471
5173
|
}
|
|
5174
|
+
if (isAssetUploadSession) state.assetSessionStarted = true;
|
|
4472
5175
|
const upstreamUrl = new URL(`${incomingUrl.pathname}${incomingUrl.search}`, upstreamBase.origin);
|
|
4473
5176
|
const request$2 = (upstreamUrl.protocol === "https:" ? request$1 : request)(upstreamUrl, {
|
|
4474
5177
|
headers,
|
|
@@ -4655,6 +5358,11 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
|
|
|
4655
5358
|
const readActiveVersion = () => readActivePlatformWorkerVersion(configPath, accountId, workerName, runner);
|
|
4656
5359
|
const versionId = await readActiveVersion();
|
|
4657
5360
|
if (!versionId) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
|
|
5361
|
+
return await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, async () => {
|
|
5362
|
+
if (await readActiveVersion() !== versionId) throw new Error(`platform: Worker ${workerName} changed concurrently while its active version was inspected.`);
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5365
|
+
async function readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, validateCurrentVersion) {
|
|
4658
5366
|
const result = await runner([
|
|
4659
5367
|
"versions",
|
|
4660
5368
|
"view",
|
|
@@ -4677,13 +5385,15 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
|
|
|
4677
5385
|
const bindings = resources && typeof resources === "object" && !Array.isArray(resources) ? resources.bindings : null;
|
|
4678
5386
|
const ownershipBindings = Array.isArray(bindings) ? bindings.filter((binding) => binding && typeof binding === "object" && binding.name === "PLATFORM_OWNERSHIP_MARKER" && binding.type === "plain_text" && typeof binding.text === "string") : [];
|
|
4679
5387
|
if (ownershipBindings.length !== 1 || ownershipBindings[0].text !== expectedOwnershipMarker) throw new Error(`platform: Active Worker version ${versionId} for ${workerName} does not belong to this installation.`);
|
|
4680
|
-
|
|
5388
|
+
await validateCurrentVersion?.();
|
|
4681
5389
|
const annotations = value.annotations;
|
|
4682
5390
|
const message = annotations && typeof annotations === "object" && typeof annotations["workers/message"] === "string" ? annotations["workers/message"] ?? null : null;
|
|
4683
5391
|
const metadata = parseCloudflareVersionMessage(message);
|
|
4684
5392
|
if (metadata?.schema !== 2) throw new Error(`platform: Active Worker ${workerName} predates safe trigger snapshots. Refusing to upgrade because its routes, cron, and queue consumers could not be restored after a failed cutover.`);
|
|
5393
|
+
const migrationTag = readCloudflareVersionMigrationTag(value);
|
|
4685
5394
|
return {
|
|
4686
|
-
|
|
5395
|
+
...metadata.contentHash ? { contentHash: metadata.contentHash } : {},
|
|
5396
|
+
migrationTag,
|
|
4687
5397
|
triggerConfig: metadata.triggerConfig,
|
|
4688
5398
|
versionId
|
|
4689
5399
|
};
|
|
@@ -4966,7 +5676,7 @@ async function verifyPlatformHealth(apiUrl, installationId, proxyUrl, applicatio
|
|
|
4966
5676
|
redirect: "manual",
|
|
4967
5677
|
signal: AbortSignal.timeout(3e4)
|
|
4968
5678
|
});
|
|
4969
|
-
const dispatchUrl = dispatchOrigin ?? `https
|
|
5679
|
+
const dispatchUrl = dispatchOrigin ?? `https://${PLATFORM_HEALTH_HOSTNAME_LABEL}.${applicationBaseDomain}`;
|
|
4970
5680
|
const [health, discovery, proxy, dispatch, emailGateway] = await Promise.all([
|
|
4971
5681
|
request(`${apiUrl}/health`),
|
|
4972
5682
|
request(`${apiUrl}/.well-known/void-platform`),
|
|
@@ -5568,14 +6278,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
|
|
|
5568
6278
|
}
|
|
5569
6279
|
async function runPlatformLifecycleCommand(args) {
|
|
5570
6280
|
if (args.subcommand === "domain") {
|
|
5571
|
-
const { runPlatformDomainCommand } = await import("./platform-domain-
|
|
6281
|
+
const { runPlatformDomainCommand } = await import("./platform-domain-D6Xcy9ZX.mjs");
|
|
5572
6282
|
await runPlatformDomainCommand(args);
|
|
5573
6283
|
return;
|
|
5574
6284
|
}
|
|
5575
6285
|
if (args.subcommand === "discover") {
|
|
5576
6286
|
const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
|
|
5577
6287
|
const accountId = await selectAccount(await readAccounts(client), args.account);
|
|
5578
|
-
const { recoverPlatformInstallations } = await import("./platform-recovery-
|
|
6288
|
+
const { recoverPlatformInstallations } = await import("./platform-recovery-BvtcXON5.mjs");
|
|
5579
6289
|
const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
|
|
5580
6290
|
if (recovered.length === 0) {
|
|
5581
6291
|
log.info(`No recoverable Void platform installations were found in ${accountId}.`);
|
|
@@ -5585,7 +6295,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
5585
6295
|
return;
|
|
5586
6296
|
}
|
|
5587
6297
|
if (args.subcommand !== "install") {
|
|
5588
|
-
const { runInstalledPlatformCommand } = await import("./platform-management-
|
|
6298
|
+
const { runInstalledPlatformCommand } = await import("./platform-management-DOtss0BN.mjs");
|
|
5589
6299
|
await runInstalledPlatformCommand(args);
|
|
5590
6300
|
return;
|
|
5591
6301
|
}
|
|
@@ -5809,7 +6519,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
5809
6519
|
progress?.stop("Read the saved installation.");
|
|
5810
6520
|
progress = void 0;
|
|
5811
6521
|
if (authoritative) {
|
|
5812
|
-
const { selectAuthoritativePlatformManifest } = await import("./platform-management-
|
|
6522
|
+
const { selectAuthoritativePlatformManifest } = await import("./platform-management-DOtss0BN.mjs");
|
|
5813
6523
|
const selected = selectAuthoritativePlatformManifest(existing, authoritative);
|
|
5814
6524
|
authoritativeProvisionIntents = true;
|
|
5815
6525
|
if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
|
|
@@ -6038,7 +6748,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6038
6748
|
})).actions.filter((action) => action.kind === "worker"),
|
|
6039
6749
|
revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
|
|
6040
6750
|
verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
|
|
6041
|
-
const { verifyPlatformInstallationOwnership } = await import("./platform-management-
|
|
6751
|
+
const { verifyPlatformInstallationOwnership } = await import("./platform-management-DOtss0BN.mjs");
|
|
6042
6752
|
const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
|
|
6043
6753
|
const previous = manifest.resources.find((resource) => resource.key === action.key);
|
|
6044
6754
|
return action.action === "create" && previous && previous.name !== action.name;
|
|
@@ -6178,4 +6888,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
|
|
|
6178
6888
|
return value;
|
|
6179
6889
|
}
|
|
6180
6890
|
//#endregion
|
|
6181
|
-
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 };
|