diff --git a/packages/worker/src/deploy/cf-deploy.ts b/packages/worker/src/deploy/cf-deploy.ts index d2a48c7..aa723ad 100644 --- a/packages/worker/src/deploy/cf-deploy.ts +++ b/packages/worker/src/deploy/cf-deploy.ts @@ -85,13 +85,39 @@ export interface UploadWorkerParams { export async function uploadWorkerScript(p: UploadWorkerParams): Promise { const doFetch = p.fetchImpl ?? fetch; - const url = `${API}/accounts/${p.accountId}/workers/scripts/${p.upload.scriptName}`; - const res = await doFetch(url, { + const auth = { Authorization: `Bearer ${p.apiToken}` }; + const scriptUrl = `${API}/accounts/${p.accountId}/workers/scripts/${p.upload.scriptName}`; + const res = await doFetch(scriptUrl, { method: "PUT", - headers: { Authorization: `Bearer ${p.apiToken}` }, + headers: auth, body: buildScriptUploadForm(p.upload), }); - return envelope(res); + const result = await envelope(res); + if (!result.ok) return result; + + // Enable the workers.dev subdomain. The raw Scripts API upload leaves it + // DISABLED, so without this the freshly-uploaded worker returns 404 at + // ..workers.dev (wrangler enables it automatically; the API + // does not). We have no custom-domain support yet, so workers.dev is the + // only way to reach a deployed worker — enable it by default. + await doFetch(`${scriptUrl}/subdomain`, { + method: "POST", + headers: { ...auth, "Content-Type": "application/json" }, + body: JSON.stringify({ enabled: true, previews_enabled: false }), + }).catch(() => undefined); + + // Best-effort: resolve the account's workers.dev subdomain to return a + // reachable URL for the deployments UI. + try { + const subRes = await doFetch(`${API}/accounts/${p.accountId}/workers/subdomain`, { headers: auth }); + const sub = await json<{ result?: { subdomain?: string } }>(subRes); + if (sub?.result?.subdomain) { + result.url = `https://${p.upload.scriptName}.${sub.result.subdomain}.workers.dev`; + } + } catch { + // No URL — the deploy still succeeded. + } + return result; } // --------------------------------------------------------------------------- diff --git a/packages/worker/test/cf-deploy.test.ts b/packages/worker/test/cf-deploy.test.ts index 072f4eb..cdbb318 100644 --- a/packages/worker/test/cf-deploy.test.ts +++ b/packages/worker/test/cf-deploy.test.ts @@ -48,12 +48,14 @@ describe("buildScriptUploadForm", () => { }); describe("uploadWorkerScript", () => { - it("PUTs to the scripts API with bearer auth", async () => { - let url = ""; - let method = ""; - const fakeFetch = (async (u: string, init: RequestInit) => { - url = u; - method = init.method ?? ""; + it("PUTs the script, enables the workers.dev subdomain, and returns the URL", async () => { + const calls: Array<{ url: string; method: string; body?: string }> = []; + const fakeFetch = (async (u: string, init: RequestInit = {}) => { + calls.push({ url: u, method: init.method ?? "GET", body: typeof init.body === "string" ? init.body : undefined }); + if (u.endsWith("/subdomain") && (init.method ?? "GET") === "GET") { + // account subdomain lookup + return new Response(JSON.stringify({ success: true, result: { subdomain: "myacct" } }), { status: 200 }); + } return new Response(JSON.stringify({ success: true, errors: [] }), { status: 200 }); }) as unknown as typeof fetch; const r = await uploadWorkerScript({ @@ -63,13 +65,23 @@ describe("uploadWorkerScript", () => { fetchImpl: fakeFetch, }); expect(r.ok).toBe(true); - expect(method).toBe("PUT"); - expect(url).toContain("/accounts/acc/workers/scripts/w"); + // 1. the PUT upload + const put = calls.find((c) => c.method === "PUT")!; + expect(put.url).toContain("/accounts/acc/workers/scripts/w"); + // 2. the subdomain-enable POST (without it the worker 404s) + const enable = calls.find((c) => c.method === "POST" && c.url.endsWith("/scripts/w/subdomain"))!; + expect(enable).toBeTruthy(); + expect(JSON.parse(enable.body!)).toMatchObject({ enabled: true }); + // 3. the URL resolved from the account subdomain + expect(r.url).toBe("https://w.myacct.workers.dev"); }); - it("surfaces API errors", async () => { - const fakeFetch = (async () => - new Response(JSON.stringify({ success: false, errors: [{ code: 10001, message: "bad" }] }), { status: 400 })) as unknown as typeof fetch; + it("surfaces API errors and does NOT try to enable the subdomain on a failed upload", async () => { + const calls: string[] = []; + const fakeFetch = (async (u: string, init: RequestInit = {}) => { + calls.push(`${init.method ?? "GET"} ${u}`); + return new Response(JSON.stringify({ success: false, errors: [{ code: 10001, message: "bad" }] }), { status: 400 }); + }) as unknown as typeof fetch; const r = await uploadWorkerScript({ accountId: "a", apiToken: "t", @@ -78,6 +90,8 @@ describe("uploadWorkerScript", () => { }); expect(r.ok).toBe(false); expect(r.detail).toContain("bad"); + // Only the PUT ran — no subdomain calls after a failed upload. + expect(calls).toEqual(["PUT https://api.cloudflare.com/client/v4/accounts/a/workers/scripts/w"]); }); });