Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions packages/cloudflare/src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ export async function deployWithCdnWarmup(
});

const wranglerConfig = parseWranglerConfig(root, options.config);
const deploymentStatus = readWranglerDeploymentStatus(root, options);
const deploymentStatus = runWranglerDeploymentStatus(root, options);
const stagingTraffic = getZeroPercentStagingTraffic(deploymentStatus, upload.versionId);
const canVerifyStagedHtml = options.expectedBuildId !== undefined;
if (!canVerifyStagedHtml && paths.length > 0) {
Expand Down Expand Up @@ -652,7 +652,10 @@ export async function deployWithCdnWarmup(
}
const targetUrl =
resolveCdnWarmupTargetUrl(root, triggersDeployedUrl, options) ?? staged.deployedUrl;
const workerName = resolveWorkerNameForVersionOverride(wranglerConfig, options);
const workerName =
options.name ??
upload.workerName ??
resolveWorkerNameForVersionOverride(wranglerConfig, options);
const headers = buildVersionOverrideHeaders(workerName, upload.versionId);
if (targetUrl && headers) {
try {
Expand Down Expand Up @@ -782,17 +785,6 @@ export function resolveCdnWarmupTargetUrl(
return deployedUrl;
}

function readWranglerDeploymentStatus(
root: string,
options: Pick<DeployOptions, "preview" | "env" | "name" | "config">,
): WranglerDeploymentStatus | null {
try {
return runWranglerDeploymentStatus(root, options);
} catch {
return null;
}
}

export function getZeroPercentStagingTraffic(
deployment: WranglerDeploymentStatus | null,
versionId: string,
Expand Down
8 changes: 7 additions & 1 deletion packages/cloudflare/src/version-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { parseWorkersDevUrl } from "./workers-dev-url.js";
export type WranglerVersionUploadResult = {
versionId: string;
previewUrl: string | null;
workerName: string | null;
output: string;
};

Expand Down Expand Up @@ -90,16 +91,21 @@ export function parseVersionId(output: string): string | null {
return output.match(new RegExp(`\\b${versionIdPattern}\\b`))?.[0] ?? null;
}

export function parseUploadedWorkerName(output: string): string | null {
return output.match(/^\s*Uploaded\s+(\S+)\s+\(\d+(?:\.\d+)?\s+sec\)\s*$/im)?.[1] ?? null;
}

export function parseWranglerVersionUploadOutput(output: string): WranglerVersionUploadResult {
const parsed = parseJsonObject(output);
const versionId = findVersionIdInUploadJson(parsed) ?? parseVersionId(output);
const previewUrl = findPreviewUrlInUploadJson(parsed) ?? parseWorkersDevUrl(output);
const workerName = parseUploadedWorkerName(output);

if (!versionId) {
throw new Error("Could not detect Worker version ID from `wrangler versions upload` output.");
}

return { versionId, previewUrl, output };
return { versionId, previewUrl, workerName, output };
}

export function buildWranglerVersionUploadArgs(
Expand Down
6 changes: 5 additions & 1 deletion packages/cloudflare/src/worker-deployment-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export function parseWorkerDeploymentUrl(output: string): string | null {
* canonical deployment URLs for general CLI reporting.
*/
export function parseCdnWarmupDeploymentUrl(output: string): string | null {
return parseWorkerDeploymentUrl(output) ?? parseCatchAllWorkerRouteOrigin(output);
return (
parseCustomDomainUrl(output) ??
parseCatchAllWorkerRouteOrigin(output) ??
parseWorkersDevUrl(output)
);
}

function parseCatchAllWorkerRouteOrigin(output: string): string | null {
Expand Down
65 changes: 65 additions & 0 deletions tests/cloudflare-cdn-warm-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ describe("Cloudflare CDN warmup deploy flow", () => {
expect(hasCdnWarmRequests({ loadingShellPaths: [], paths: [], rscPaths: [] })).toBe(false);
});

it("does not promote when deployment status cannot be read", async () => {
writeFile("wrangler.jsonc", JSON.stringify({ name: "my-worker" }));
execFileSyncMock.mockImplementation((_file: string, args: string[]) => {
if (args.includes("upload")) {
return "Uploaded my-worker (1.23 sec)\nWorker Version ID: 22222222-2222-4222-8222-222222222222\n";
}
if (args.includes("status")) {
throw new Error("deployment status unavailable");
}
throw new Error(`Unexpected Wrangler args: ${args.join(" ")}`);
});
const { deployWithCdnWarmup } = await import("../packages/cloudflare/src/deploy.js");

await expect(
deployWithCdnWarmup(tmpDir, ["/"], {
expectedBuildId: "app-build-a",
warmCdnStrict: true,
}),
).rejects.toThrow("deployment status unavailable");
expect(execFileSyncMock).toHaveBeenCalledTimes(2);
expect(fetch).not.toHaveBeenCalled();
});

it("warms the production custom domain through a 0% staged version override", async () => {
const events: string[] = [];
delayMock.mockImplementation(async (milliseconds: number) => {
Expand Down Expand Up @@ -838,6 +861,48 @@ describe("Cloudflare CDN warmup deploy flow", () => {
}
});

it("uses Wrangler's uploaded Worker name for a TOML config", async () => {
writeFile(
"wrangler.toml",
["name = 'toml-worker'", "workers_dev = false", "route = 'app.example.com/*'"].join("\n"),
);
execFileSyncMock.mockImplementation((_file: string, args: string[]) => {
if (args.includes("upload")) {
return [
"Uploaded toml-worker (1.23 sec)",
"Worker Version ID: 22222222-2222-4222-8222-222222222222",
].join("\n");
}
if (args.includes("status")) {
return JSON.stringify({
versions: [{ version_id: "11111111-1111-4111-8111-111111111111", percentage: 100 }],
});
}
if (args.includes("deploy") && args.includes("22222222-2222-4222-8222-222222222222@0%")) {
return "Staged version\n";
}
if (args.includes("deploy") && args.includes("22222222-2222-4222-8222-222222222222@100%")) {
return "Promoted version\n";
}
if (args.includes("triggers")) {
return "Triggers deployed\n app.example.com/*\n";
}
throw new Error(`Unexpected Wrangler args: ${args.join(" ")}`);
});
const { deployWithCdnWarmup } = await import("../packages/cloudflare/src/deploy.js");

await deployWithCdnWarmup(tmpDir, ["/"], {
expectedBuildId: "app-build-a",
warmCdnConcurrency: 1,
warmCdnPromotionDelay: 0,
});

const headers = new Headers(vi.mocked(fetch).mock.calls[0]?.[1]?.headers);
expect(headers.get("Cloudflare-Workers-Version-Overrides")).toBe(
'toml-worker="22222222-2222-4222-8222-222222222222"',
);
});

it("explains staged version cleanup when strict pre-promotion warmup fails", async () => {
writeFile(
"wrangler.jsonc",
Expand Down
1 change: 1 addition & 0 deletions tests/cloudflare-version-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ describe("Cloudflare Wrangler version deployment helpers", () => {

expect(parseWranglerVersionUploadOutput(output)).toMatchObject({
versionId: "7283300a-90b0-45d6-ba08-7c4b76797f38",
workerName: "rsc-prewarm-32362416100",
previewUrl: "https://7283300a-rsc-prewarm-32362416100.vinext.workers.dev",
});
});
Expand Down
8 changes: 8 additions & 0 deletions tests/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,14 @@ describe("parseWorkerDeploymentUrl", () => {
expect(parseCdnWarmupDeploymentUrl(`Deployed app triggers\n ${route}\n`)).toBe(expected);
});

it("prefers a concrete Worker route over a workers.dev fallback", () => {
expect(
parseCdnWarmupDeploymentUrl(
"Deployed app triggers\n https://app.account.workers.dev\n app.example.com/*\n",
),
).toBe("https://app.example.com");
});

it.each(["*.example.com/*", "app.example.com/api/*", "app.example.com/"])(
"rejects a non-canonical Worker route for CDN warmup: %s",
(route) => {
Expand Down
Loading