fix: ESM multipart deploy + auto-enable subdomain — 小橘 🍊

- Fix CF API deploy to use multipart form (ESM modules need metadata)
- Auto-enable workers.dev subdomain after deploy
- Verified end-to-end: deploy + invoke + list + health all working
This commit is contained in:
2026-04-03 04:30:14 +00:00
parent cfac87411e
commit 4be91b9bc8
+77
View File
@@ -0,0 +1,77 @@
import { CONFIG } from './config.js'
import type { CfApi } from './backend/worker-pool.js'
export function createCfApi(accountId: string, apiToken: string): CfApi {
const baseUrl = `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts`
return {
async deployWorker(name: string, code: string): Promise<void> {
// CF API: PUT /accounts/{account_id}/workers/scripts/{script_name}
// ESM format requires multipart form upload with metadata
const metadata = JSON.stringify({
main_module: 'worker.js',
compatibility_date: '2026-04-03',
})
// Build multipart form body
const formData = new FormData()
formData.append('metadata', new Blob([metadata], { type: 'application/json' }))
formData.append('worker.js', new Blob([code], { type: 'application/javascript+module' }), 'worker.js')
const resp = await fetch(`${baseUrl}/${name}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiToken}`,
},
body: formData,
})
if (!resp.ok) {
const text = await resp.text()
throw new Error(`CF API deploy failed (${resp.status}): ${text}`)
}
// Enable workers.dev subdomain for the newly deployed Worker
const subdomainResp = await fetch(`${baseUrl}/${name}/subdomain`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: true }),
})
if (!subdomainResp.ok) {
console.warn(`[sigil] failed to enable subdomain for ${name}: ${subdomainResp.status}`)
}
},
async deleteWorker(name: string): Promise<void> {
// CF API: DELETE /accounts/{account_id}/workers/scripts/{script_name}
const resp = await fetch(`${baseUrl}/${name}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiToken}`,
},
})
if (!resp.ok && resp.status !== 404) {
const text = await resp.text()
throw new Error(`CF API delete failed (${resp.status}): ${text}`)
}
},
getWorkerSubdomain(name: string): string {
return `${name}${CONFIG.SUBDOMAIN_SUFFIX}`
},
async invoke(workerName: string, request: Request): Promise<Response> {
// 转发请求到 Worker 子域名
const url = new URL(request.url)
const targetUrl = `https://${workerName}${CONFIG.SUBDOMAIN_SUFFIX}${url.pathname}${url.search}`
return fetch(targetUrl, {
method: request.method,
headers: request.headers,
body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined,
})
},
}
}