我们的 API 使用户能够上传静态资产并将其作为 Worker 的一部分。这些静态资产可免费提供服务;此外,用户还可通过可选的资产绑定(binding)获取资产,以支持更高级的应用。本指南说明如何通过 API 将资产直接附加到 Worker。
sequenceDiagram
participant User
participant Workers API
User<<->>Workers API: Submit manifest<br/>POST /client/v4/accounts/:accountId/workers/scripts/:scriptName/assets-upload-session
User<<->>Workers API: Upload files<br/>POST /client/v4/accounts/:accountId/workers/assets/upload?base64=true
User<<->>Workers API: Upload script version<br/>PUT /client/v4/accounts/:accountId/workers/scripts/:scriptName
sequenceDiagram
participant User
participant Workers API
User<<->>Workers API: Submit manifest<br/>POST /client/v4/accounts/:accountId/workers/dispatch/namespaces/:dispatchNamespace/scripts/:scriptName/assets-upload-session
User<<->>Workers API: Upload files<br/>POST /client/v4/accounts/:accountId/workers/assets/upload?base64=true
User<<->>Workers API: Upload script version<br/>PUT /client/v4/accounts/:accountId/workers/dispatch/namespaces/:dispatchNamespace/scripts/:scriptName
资产上传流程可归纳为三个阶段:
- 注册 manifest
- 上传资产
- 部署 Worker
资产 manifest 是一份账本,记录我们希望在 Worker 中使用的文件。该 manifest 用于跟踪每个 Worker 版本关联的资产,并避免在新上传前重复上传未变更的文件。
manifest 上传请求描述我们打算上传的每个文件。每个文件对应一个键,表示文件路径和名称,其值为包含文件元数据的对象。
hash 表示文件的 32 位十六进制字符哈希,size 表示文件大小(字节)。
curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/{script_name}/assets-upload-session \
--header 'content-type: application/json' \
--header 'Authorization: Bearer <API_TOKEN>' \
--data '{
"manifest": {
"/filea.html": {
"hash": "08f1dfda4574284ab3c21666d1",
"size": 12
},
"/fileb.html": {
"hash": "4f1c1af44620d531446ceef93f",
"size": 23
},
"/filec.html": {
"hash": "54995e302614e0523757a04ec1",
"size": 23
}
}
}'curl -X POST https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/dispatch/namespaces/{dispatch_namespace}/scripts/{script_name}/assets-upload-session \
--header 'content-type: application/json' \
--header 'Authorization: Bearer <API_TOKEN>' \
--data '{
"manifest": {
"/filea.html": {
"hash": "08f1dfda4574284ab3c21666d1",
"size": 12
},
"/fileb.html": {
"hash": "4f1c1af44620d531446ceef93f",
"size": 23
},
"/filec.html": {
"hash": "54995e302614e0523757a04ec1",
"size": 23
}
}
}'响应会包含 JWT,用于文件上传时的身份验证。JWT 有效期为一小时。
除 JWT 外,响应还会说明如何最优地批量上传文件。这些说明编码在 buckets 字段中。buckets 中的每个数组包含应一起上传的文件哈希列表。若文件在 Worker 先前版本中最近已上传且未修改,则不会出现在 buckets 字段中(无需重新上传)。
{
"result": {
"jwt": "<UPLOAD_TOKEN>",
"buckets": [
["08f1dfda4574284ab3c21666d1", "4f1c1af44620d531446ceef93f"],
["54995e302614e0523757a04ec1"]
]
},
"success": true,
"errors": null,
"messages": null
}- 限制因账户套餐而异。有关静态资产限制的更多信息,请参阅账户套餐限制。
文件上传 API要求使用 multipart/form-data 上传文件。每个文件的内容必须进行 base64 编码,且 URL 中的 base64 查询参数须设为 true。
每个文件部分的 Content-Type 标头会在最终提供文件时附加。若部署时不想发送 Content-Type 标头,可在上传时使用 application/null。
Authorization 标头须以 bearer token 形式提供,使用前述 manifest 上传调用返回的 JWT(上传令牌)。
manifest 中的每个文件上传完成后,将返回状态码 201,并包含 jwt 字段。该 JWT 为最终「完成」令牌,可用于创建包含此资产集的 Worker 部署。完成令牌有效期为 1 小时。
Script、Version 和 Workers for Platform script 上传端点要求在表单数据中指定 metadata 部分。此处可提供上一步(上传资产)的完成令牌。
{
"main_module": "main.js",
"assets": {
"jwt": "<completion_token>"
},
"compatibility_date": "2021-09-14"
}若 Worker 已有资产且仅希望复用现有资产集,则无需再次指定完成令牌,可传递布尔值 keep_assets 选项。
{
"main_module": "main.js",
"keep_assets": true,
"compatibility_date": "2021-09-14"
}资产路由配置可在 assets 对象中提供,例如 html_handling 和 not_found_handling。
{
"main_module": "main.js",
"assets": {
"jwt": "<completion_token>",
"config" {
"html_handling": "auto-trailing-slash"
}
},
"compatibility_date": "2021-09-14"
}若希望在 Worker 代码中获取并提供资产,可选提供资产绑定(binding)。
{
"main_module": "main.js",
"assets": {
...
},
"bindings": [
...
{
"name": "ASSETS",
"type": "assets"
}
...
]
"compatibility_date": "2021-09-14"
}此示例来自 cloudflare-typescript ↗。
#!/usr/bin/env -S npm run tsn -T
/**
* Create a Worker that serves static assets
*
* This example demonstrates how to:
* - Upload static assets to Cloudflare Workers
* - Create and deploy a Worker that serves those assets
*
* Docs:
* - https://developers.cloudflare.com/workers/static-assets/direct-upload
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - ASSETS_DIRECTORY (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Place your static files in the ASSETS_DIRECTORY, then run this script.
* Assets will be available at: my-script-with-assets.$subdomain.workers.dev/$filename
*/
import crypto from "crypto";
import fs from "fs";
import { readFile } from "node:fs/promises";
import { extname } from "node:path";
import path from "path";
import { exit } from "node:process";
import Cloudflare from "cloudflare";
const WORKER_NAME = "my-worker-with-assets";
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig() {
const apiToken = process.env["CLOUDFLARE_API_TOKEN"];
if (!apiToken) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_API_TOKEN",
);
}
const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"];
if (!accountId) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_ACCOUNT_ID",
);
}
const assetsDirectory = process.env["ASSETS_DIRECTORY"];
if (!assetsDirectory) {
throw new Error("Missing required environment variable: ASSETS_DIRECTORY");
}
if (!fs.existsSync(assetsDirectory)) {
throw new Error(`Assets directory does not exist: ${assetsDirectory}`);
}
const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"];
return {
apiToken,
accountId,
assetsDirectory,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
/**
* Recursively reads all files from a directory and creates a manifest
* mapping file paths to their hash and size.
*/
function createManifest(directory) {
const manifest = {};
function processDirectory(currentDir, basePath = "") {
try {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
const relativePath = path.join(basePath, entry.name);
if (entry.isDirectory()) {
processDirectory(fullPath, relativePath);
} else if (entry.isFile()) {
try {
const fileContent = fs.readFileSync(fullPath);
const extension = extname(relativePath).substring(1);
// Generate a hash for the file
const hash = crypto
.createHash("sha256")
.update(fileContent.toString("base64") + extension)
.digest("hex")
.slice(0, 32);
// Normalize path separators to forward slashes
const manifestPath = `/${relativePath.replace(/\\/g, "/")}`;
manifest[manifestPath] = {
hash,
size: fileContent.length,
};
console.log(
`Added to manifest: ${manifestPath} (${fileContent.length} bytes)`,
);
} catch (error) {
console.warn(`Failed to process file ${fullPath}:`, error);
}
}
}
} catch (error) {
throw new Error(`Failed to read directory ${currentDir}: ${error}`);
}
}
processDirectory(directory);
if (Object.keys(manifest).length === 0) {
throw new Error(`No files found in assets directory: ${directory}`);
}
console.log(`Created manifest with ${Object.keys(manifest).length} files`);
return manifest;
}
/**
* Generates the Worker script content that serves static assets
*/
function generateWorkerScript(exampleFile) {
return `
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Serve a simple index page at the root
if (url.pathname === '/') {
return new Response(
\`<!DOCTYPE html>
<html>
<head>
<title>Static Assets Worker</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #f38020; }
.asset-info { background: #f5f5f5; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<h1>This Worker serves static assets!</h1>
<div class="asset-info">
<p><strong>To access your assets,</strong> add <code>/filename</code> to the URL.</p>
<p>Try visiting <a href="\${url.origin}/${exampleFile}">/${exampleFile}</a></p>
</div>
</body>
</html>\`,
{
status: 200,
headers: { 'Content-Type': 'text/html' }
}
);
}
// Serve static assets for all other paths
return env.ASSETS.fetch(request);
}
};
`.trim();
}
/**
* Creates upload payloads from buckets and manifest
*/
async function createUploadPayloads(buckets, manifest, assetsDirectory) {
const payloads = [];
for (const bucket of buckets) {
const payload = {};
for (const hash of bucket) {
// Find the file path for this hash
const manifestEntry = Object.entries(manifest).find(
([_, data]) => data.hash === hash,
);
if (!manifestEntry) {
throw new Error(`Could not find file for hash: ${hash}`);
}
const [relativePath] = manifestEntry;
const fullPath = path.join(assetsDirectory, relativePath);
try {
const fileContent = await readFile(fullPath);
payload[hash] = fileContent.toString("base64");
console.log(`Prepared for upload: ${relativePath}`);
} catch (error) {
throw new Error(`Failed to read file ${fullPath}: ${error}`);
}
}
payloads.push(payload);
}
return payloads;
}
/**
* Uploads asset payloads
*/
async function uploadAssets(payloads, uploadJwt, accountId) {
let completionJwt;
console.log(`Uploading ${payloads.length} payload(s)...`);
for (let i = 0; i < payloads.length; i++) {
const payload = payloads[i];
console.log(`Uploading payload ${i + 1}/${payloads.length}...`);
try {
const response = await client.workers.assets.upload.create(
{
account_id: accountId,
base64: true,
body: payload,
},
{
headers: { Authorization: `Bearer ${uploadJwt}` },
},
);
if (response?.jwt) {
completionJwt = response.jwt;
}
} catch (error) {
throw new Error(`Failed to upload payload ${i + 1}: ${error}`);
}
}
if (!completionJwt) {
throw new Error("Upload completed but no completion JWT received");
}
console.log("✅ All assets uploaded successfully");
return completionJwt;
}
async function main() {
try {
console.log(
"🚀 Starting Worker creation and deployment with static assets...",
);
console.log(`📁 Assets directory: ${config.assetsDirectory}`);
console.log("📝 Creating asset manifest...");
const manifest = createManifest(config.assetsDirectory);
const exampleFile =
Object.keys(manifest)[0]?.replace(/^\//, "") || "file.txt";
const scriptContent = generateWorkerScript(exampleFile);
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) {
throw error;
}
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log("🔄 Starting asset upload session...");
const uploadResponse = await client.workers.scripts.assets.upload.create(
config.workerName,
{
account_id: config.accountId,
manifest,
},
);
const { buckets, jwt: uploadJwt } = uploadResponse;
if (!uploadJwt || !buckets) {
throw new Error("Failed to start asset upload session");
}
let completionJwt;
if (buckets.length === 0) {
console.log("✅ No new assets to upload!");
// Use the initial upload JWT as completion JWT when no uploads are needed
completionJwt = uploadJwt;
} else {
const payloads = await createUploadPayloads(
buckets,
manifest,
config.assetsDirectory,
);
completionJwt = await uploadAssets(payloads, uploadJwt, config.accountId);
}
console.log("✏️ Creating Worker version...");
// Create a new version with assets
const version = await client.workers.beta.workers.versions.create(
worker.id,
{
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split("T")[0],
bindings: [
{
type: "assets",
name: "ASSETS",
},
],
assets: {
jwt: completionJwt,
},
modules: [
{
name: SCRIPT_FILENAME,
content_type: "application/javascript+module",
content_base64: Buffer.from(scriptContent).toString("base64"),
},
],
},
);
console.log("🚚 Creating Worker deployment...");
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: "percentage",
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log("✅ Deployment successful!");
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/
📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile}
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error("❌ Deployment failed:", error);
exit(1);
}
}
main();#!/usr/bin/env -S npm run tsn -T
/**
* Create a Worker that serves static assets
*
* This example demonstrates how to:
* - Upload static assets to Cloudflare Workers
* - Create and deploy a Worker that serves those assets
*
* Docs:
* - https://developers.cloudflare.com/workers/static-assets/direct-upload
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - ASSETS_DIRECTORY (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Place your static files in the ASSETS_DIRECTORY, then run this script.
* Assets will be available at: my-script-with-assets.$subdomain.workers.dev/$filename
*/
import crypto from 'crypto';
import fs from 'fs';
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';
import path from 'path';
import { exit } from 'node:process';
import Cloudflare from 'cloudflare';
interface Config {
apiToken: string;
accountId: string;
assetsDirectory: string;
subdomain: string | undefined;
workerName: string;
}
interface AssetManifest {
[path: string]: {
hash: string;
size: number;
};
}
interface UploadPayload {
[hash: string]: string; // base64 encoded content
}
const WORKER_NAME = 'my-worker-with-assets';
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig(): Config {
const apiToken = process.env['CLOUDFLARE_API_TOKEN'];
if (!apiToken) {
throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN');
}
const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
if (!accountId) {
throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID');
}
const assetsDirectory = process.env['ASSETS_DIRECTORY'];
if (!assetsDirectory) {
throw new Error('Missing required environment variable: ASSETS_DIRECTORY');
}
if (!fs.existsSync(assetsDirectory)) {
throw new Error(`Assets directory does not exist: ${assetsDirectory}`);
}
const subdomain = process.env['CLOUDFLARE_SUBDOMAIN'];
return {
apiToken,
accountId,
assetsDirectory,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
/**
* Recursively reads all files from a directory and creates a manifest
* mapping file paths to their hash and size.
*/
function createManifest(directory: string): AssetManifest {
const manifest: AssetManifest = {};
function processDirectory(currentDir: string, basePath = ''): void {
try {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
const relativePath = path.join(basePath, entry.name);
if (entry.isDirectory()) {
processDirectory(fullPath, relativePath);
} else if (entry.isFile()) {
try {
const fileContent = fs.readFileSync(fullPath);
const extension = extname(relativePath).substring(1);
// Generate a hash for the file
const hash = crypto
.createHash('sha256')
.update(fileContent.toString('base64') + extension)
.digest('hex')
.slice(0, 32);
// Normalize path separators to forward slashes
const manifestPath = `/${relativePath.replace(/\\/g, '/')}`;
manifest[manifestPath] = {
hash,
size: fileContent.length,
};
console.log(`Added to manifest: ${manifestPath} (${fileContent.length} bytes)`);
} catch (error) {
console.warn(`Failed to process file ${fullPath}:`, error);
}
}
}
} catch (error) {
throw new Error(`Failed to read directory ${currentDir}: ${error}`);
}
}
processDirectory(directory);
if (Object.keys(manifest).length === 0) {
throw new Error(`No files found in assets directory: ${directory}`);
}
console.log(`Created manifest with ${Object.keys(manifest).length} files`);
return manifest;
}
/**
* Generates the Worker script content that serves static assets
*/
function generateWorkerScript(exampleFile: string): string {
return `
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Serve a simple index page at the root
if (url.pathname === '/') {
return new Response(
\`<!DOCTYPE html>
<html>
<head>
<title>Static Assets Worker</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #f38020; }
.asset-info { background: #f5f5f5; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<h1>This Worker serves static assets!</h1>
<div class="asset-info">
<p><strong>To access your assets,</strong> add <code>/filename</code> to the URL.</p>
<p>Try visiting <a href="\${url.origin}/${exampleFile}">/${exampleFile}</a></p>
</div>
</body>
</html>\`,
{
status: 200,
headers: { 'Content-Type': 'text/html' }
}
);
}
// Serve static assets for all other paths
return env.ASSETS.fetch(request);
}
};
`.trim();
}
/**
* Creates upload payloads from buckets and manifest
*/
async function createUploadPayloads(
buckets: string[][],
manifest: AssetManifest,
assetsDirectory: string
): Promise<UploadPayload[]> {
const payloads: UploadPayload[] = [];
for (const bucket of buckets) {
const payload: UploadPayload = {};
for (const hash of bucket) {
// Find the file path for this hash
const manifestEntry = Object.entries(manifest).find(
([_, data]) => data.hash === hash
);
if (!manifestEntry) {
throw new Error(`Could not find file for hash: ${hash}`);
}
const [relativePath] = manifestEntry;
const fullPath = path.join(assetsDirectory, relativePath);
try {
const fileContent = await readFile(fullPath);
payload[hash] = fileContent.toString('base64');
console.log(`Prepared for upload: ${relativePath}`);
} catch (error) {
throw new Error(`Failed to read file ${fullPath}: ${error}`);
}
}
payloads.push(payload);
}
return payloads;
}
/**
* Uploads asset payloads
*/
async function uploadAssets(
payloads: UploadPayload[],
uploadJwt: string,
accountId: string
): Promise<string> {
let completionJwt: string | undefined;
console.log(`Uploading ${payloads.length} payload(s)...`);
for (let i = 0; i < payloads.length; i++) {
const payload = payloads[i]!;
console.log(`Uploading payload ${i + 1}/${payloads.length}...`);
try {
const response = await client.workers.assets.upload.create(
{
account_id: accountId,
base64: true,
body: payload,
},
{
headers: { Authorization: `Bearer ${uploadJwt}` },
}
);
if (response?.jwt) {
completionJwt = response.jwt;
}
} catch (error) {
throw new Error(`Failed to upload payload ${i + 1}: ${error}`);
}
}
if (!completionJwt) {
throw new Error('Upload completed but no completion JWT received');
}
console.log('✅ All assets uploaded successfully');
return completionJwt;
}
async function main(): Promise<void> {
try {
console.log('🚀 Starting Worker creation and deployment with static assets...');
console.log(`📁 Assets directory: ${config.assetsDirectory}`);
console.log('📝 Creating asset manifest...');
const manifest = createManifest(config.assetsDirectory);
const exampleFile = Object.keys(manifest)[0]?.replace(/^\//, '') || 'file.txt';
const scriptContent = generateWorkerScript(exampleFile);
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) { throw error; }
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log('🔄 Starting asset upload session...');
const uploadResponse = await client.workers.scripts.assets.upload.create(
config.workerName,
{
account_id: config.accountId,
manifest,
}
);
const { buckets, jwt: uploadJwt } = uploadResponse;
if (!uploadJwt || !buckets) {
throw new Error('Failed to start asset upload session');
}
let completionJwt: string;
if (buckets.length === 0) {
console.log('✅ No new assets to upload!');
// Use the initial upload JWT as completion JWT when no uploads are needed
completionJwt = uploadJwt;
} else {
const payloads = await createUploadPayloads(
buckets,
manifest,
config.assetsDirectory
);
completionJwt = await uploadAssets(
payloads,
uploadJwt,
config.accountId
);
}
console.log('✏️ Creating Worker version...');
// Create a new version with assets
const version = await client.workers.beta.workers.versions.create(worker.id, {
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split('T')[0]!,
bindings: [
{
type: 'assets',
name: 'ASSETS',
},
],
assets: {
jwt: completionJwt,
},
modules: [
{
name: SCRIPT_FILENAME,
content_type: 'application/javascript+module',
content_base64: Buffer.from(scriptContent).toString('base64'),
},
],
});
console.log('🚚 Creating Worker deployment...');
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: 'percentage',
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log('✅ Deployment successful!');
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/
📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile}
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error('❌ Deployment failed:', error);
exit(1);
}
}
main();