diff --git a/src/content/changelog/workers/2025-08-11-new-workers-api.mdx b/src/content/changelog/workers/2025-08-11-new-workers-api.mdx new file mode 100644 index 000000000000000..caff648edcec03e --- /dev/null +++ b/src/content/changelog/workers/2025-08-11-new-workers-api.mdx @@ -0,0 +1,100 @@ +--- +title: Simpler Workers API that lets you directly manage Workers, Versions, and Deployments +description: Simpler Workers API, SDK methods, and Terraform resources for directly managing Workers, Versions, and Deployments +date: 2025-08-11 +--- + +The current Workers script upload API is convenient for a one-shot Workers deploy. However, it unavoidably creates [Versions and Deployments](/workers/configuration/versions-and-deployments/), and sometimes you don't want that to happen. Sometimes, you want more (and simpler!) control over how and when these are created. + +In [Cloudflare Terraform Provider](https://registry.terraform.io/providers/cloudflare/cloudflare/5.9.0/docs) 5.9.0 and [cloudflare-typescript](https://github.com/cloudflare/cloudflare-typescript) 4.6.0 (TODO double check these versions), we're introducing a clean, separated API that makes Workers, Versions, and Deployments individual "capital R" resources with clear concerns. It's now possible to create a Worker entity–and reference it elsewhere–before uploading code, bindings, or making it routable from the internet. You'll also enjoy a more ergonomic way to upload modules, fewer total endpoints, and clearer [API docs](/api/resources/workers/). Check out [Infrastructure as Code (IaC)](/workers/platform/infrastructure-as-code) for detailed examples. + +## cloudflare-typescript example + +```javascript +const scriptContent = ` + export default { + async fetch(request, env, ctx) { + return new Response(env.MESSAGE, { status: 200 }); + } + }; +`; + +/** + * Create a Worker and set non-versioned settings like observability + */ +const worker = await client.workers.create({ + "my-worker", + account_id: "...", + observability: { + enabled: true, + }, +}); + +/** + * Create the first version of the Worker + * This is where code and bindings are defined and can be different between versions + */ +const version = await client.workers.versions.create(worker.id, { + account_id: "...", + main_module: "my-worker-script", + compatibility_date: $today, + bindings: [ /*...*/ ], + modules: [ + { + name: "my-worker-script", + content_type: "application/javascript+module", + content_base64: Buffer.from(scriptContent).toString("base64"), + }, + ], +}); + +/** + * Create a deployment and point all traffic to the version we created + */ +const deployment = await client.workers.scripts.deployments.create(worker.name, { + account_id: "...", + strategy: "percentage", + versions: [ + { + percentage: 100, + version_id: version.id, + }, + ], +}); +``` + +## Terraform + +In Terraform, you now have the option to just manage the Worker entity. You don't need to manage code, config, or deployments in your plan. This gives you flexibility to use Wrangler for build and deploy—in a different repo—while maintaining presence in your Terraform plan. + +```tf +resource "cloudflare_worker" "my_worker" { + account_id = "..." + name = "my-important-service" +} + +# Manage Versions and Deployments here or outside of Terraform +# resource "cloudflare_worker_version" "my_worker_version" {} +# resource "cloudflare_workers_deployment" "my_worker_deployment" {} +``` + +## A radically simpler API + +We wanted to make things easier, so we simplified these 8 APIs... +- /workers/scripts/:script_name +- /workers/scripts/:script_name/content +- /workers/scripts/:script_name/script-settings +- /workers/scripts/:script_name/settings +- /workers/scripts/:script_name/subdomain +- /workers/scripts/:script_name/secrets +- /workers/scripts/:script_name/versions +- /workers/scripts/:script_name/deployments + +Into just three! +- /workers/workers/:worker_id +- /workers/workers/:worker_id/versions +- /workers/scripts/:script_name/deployments + +It's not just fewer endpoints... can you guess which of the old APIs created a new Version and Deployment and which didn't? That's no longer a mystery. POST `/versions` creates a Version and POST `/deployments` creates a Deployment. + +For now, the new APIs are in beta and the old ones will remain operational. In the future, we plan to mark the old APIs as deprecated in our OpenAPI docs, and remove the old Terraform resources and SDK methods in future major versions. diff --git a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx index 03c8c7ebf513e5d..9aec264312bbf52 100644 --- a/src/content/docs/workers/configuration/multipart-upload-metadata.mdx +++ b/src/content/docs/workers/configuration/multipart-upload-metadata.mdx @@ -5,6 +5,11 @@ title: Multipart upload metadata import { Type, MetaInfo } from "~/components"; +:::note + +There is a new API for uploading Workers. See [here](/workers/platform/infrastructure-as-code#cloudflare-rest-api) for more information. +::: + If you're using the [Workers Script Upload API](/api/resources/workers/subresources/scripts/methods/update/) or [Version Upload API](/api/resources/workers/subresources/scripts/subresources/versions/methods/create/) directly, `multipart/form-data` uploads require you to specify a `metadata` part. This metadata defines the Worker's configuration in JSON format, analogue to the [wrangler.toml file](/workers/wrangler/configuration/). ## Sample `metadata` diff --git a/src/content/docs/workers/configuration/versions-and-deployments/index.mdx b/src/content/docs/workers/configuration/versions-and-deployments/index.mdx index b2c8f3085132611..8053d04c2d9a4f5 100644 --- a/src/content/docs/workers/configuration/versions-and-deployments/index.mdx +++ b/src/content/docs/workers/configuration/versions-and-deployments/index.mdx @@ -69,6 +69,10 @@ When using [Wrangler](/workers/wrangler/), changes made to a Worker's triggers [ New versions are not created when you make changes to [resources connected to your Worker](/workers/runtime-apis/bindings/). For example, if two Workers (Worker A and Worker B) are connected via a [service binding](/workers/runtime-apis/bindings/service-bindings/), changing the code of Worker B will not create a new version of Worker A. Changing the code of Worker B will only create a new version of Worker B. Changes to the service binding (such as, deleting the binding or updating the [environment](/workers/wrangler/environments/) it points to) on Worker A will also not create a new version of Worker B. ::: +#### Directly manage Versions and Deployments + +See examples of creating a Worker, Versions, and Deployments directly with the API, library SDKs, and Terraform in [Infrastructure as Code](/workers/platform/infrastructure-as-code/). + ### View versions and deployments #### Via Wrangler diff --git a/src/content/docs/workers/observability/logs/logpush.mdx b/src/content/docs/workers/observability/logs/logpush.mdx index 9f42eab90b46817..9ff5f4e90da4890 100644 --- a/src/content/docs/workers/observability/logs/logpush.mdx +++ b/src/content/docs/workers/observability/logs/logpush.mdx @@ -93,16 +93,6 @@ route = { pattern = "example.org/*", zone_name = "example.org" } -Configure via multipart script upload API: - -```bash -curl --request PUT \ -"https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/scripts/{script_name}" \ ---header "Authorization: Bearer " \ ---form 'metadata={"main_module": "my-worker.js", "logpush": true}' \ ---form '"my-worker.js"=@./my-worker.js;type=application/javascript+module' -``` - ## Limits The `logs` and `exceptions` fields have a combined limit of 16,384 characters before fields will start being truncated. Characters are counted in the order of all `exception.name`s, `exception.message`s, and then `log.message`s. diff --git a/src/content/docs/workers/platform/infrastructure-as-code.mdx b/src/content/docs/workers/platform/infrastructure-as-code.mdx index ff492fe82ab68cc..b7409474a4203a7 100644 --- a/src/content/docs/workers/platform/infrastructure-as-code.mdx +++ b/src/content/docs/workers/platform/infrastructure-as-code.mdx @@ -5,161 +5,344 @@ sidebar: order: 11 --- -import { GitHubCode } from "~/components"; +import { GitHubCode, TabItem, Tabs } from "~/components"; +import { Icon } from "astro-icon/components"; -Uploading and managing Workers is easy with [Wrangler](/workers/wrangler/configuration), but sometimes you need to do it more programmatically. You might do this with IaC ("Infrastructure as Code") tools or by calling the [Cloudflare API](/api) directly. Use cases for the API include build and deploy scripts, CI/CD pipelines, custom dev tools, and testing. We provide API SDK libraries for common languages that make interacting with the API easier, such as [cloudflare-typescript](https://github.com/cloudflare/cloudflare-typescript) and [cloudflare-python](https://github.com/cloudflare/cloudflare-python). For IaC, a common tool is HashiCorp's Terraform. You can use the [Cloudflare Terraform Provider](/terraform) to create and manage Workers resources. +While [Wrangler](/workers/wrangler/configuration) makes it easy to upload and manage Workers, there are times when you need a more programmatic approach. This could involve using Infrastructure as Code (IaC) tools or interacting directly with the [Workers API](/api/resources/workers/). Examples include build and deploy scripts, CI/CD pipelines, custom developer tools, and automated testing. -Here are examples of deploying a Worker with common tools and languages, and considerations for successfully managing Workers with IaC. In particular, the examples highlight how to upload script content and metadata which is different with each approach. Reference the Upload Worker Module API docs [here](/api/resources/workers/subresources/scripts/methods/update) for an exact definition of how script upload works. +To make this easier, Cloudflare provides SDK libraries for popular languages, including [cloudflare-typescript](https://github.com/cloudflare/cloudflare-typescript) and [cloudflare-python](https://github.com/cloudflare/cloudflare-python). For IaC, you can use tools like HashiCorp's Terraform and the [Cloudflare Terraform Provider](/terraform) manage Workers resources. + +Below are examples of deploying a Worker using different tools and languages, along with important considerations for managing Workers with IaC. All of these examples need an [account ID](/fundamentals/account/find-account-and-zone-ids) and [API token](/fundamentals/api/get-started/create-token) (not Global API key) to work. ## Workers Bundling -None of the examples below do [Workers Bundling](/workers/wrangler/bundling) which is usually the function of a tool like Wrangler or [esbuild](https://esbuild.github.io). Generally, you'd run this bundling step before applying your Terraform plan or using the API for script upload: +None of the examples below do [Workers Bundling](/workers/wrangler/bundling). This is usually done with Wrangler or a tool like [esbuild](https://esbuild.github.io). + +Generally, you'd run this bundling step before applying your Terraform plan or using the API for script upload: ```bash -wrangler deploy --dry-run -outdir build +wrangler deploy --dry-run --outdir build ``` -Then you'd reference the bundled script like `build/index.js`. - -:::note - -Depending on your Wrangler project and `-outdir`, the name and location of your bundled script might vary. -::: - -Make sure to copy all of your config from `wrangler.json` into your Terraform config or API request. This is especially important for compatibility date or compatibility flags your script relies on. +When using Wrangler for building but another method for uploading, make sure to copy all of your config from `wrangler.json` into your Terraform config, API request, or other. This is especially important with `compatibility_date` or flags your script relies on. ## Terraform -In this example, you need a local file named `my-hello-world-script.mjs` with script content similar to the above examples. Replace `account_id` with your own. Learn more about the Cloudflare Terraform Provider [here](/terraform), and see an example with all the Workers script resource settings [here](https://github.com/cloudflare/terraform-provider-cloudflare/blob/main/examples/resources/cloudflare_workers_script/resource.tf). +In this example, you need a local file named `my-script.mjs` with script content similar to the below examples. Learn more about the Cloudflare Terraform Provider [here](/terraform), and see an example with all the Workers script resource settings [here](https://github.com/cloudflare/terraform-provider-cloudflare/blob/main/examples/resources/cloudflare_workers_script/resource.tf). ```tf -terraform { - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "~> 5" - } - } +variable "account_id" { + default = "replace_me" +} + +resource "cloudflare_worker" "my_worker" { + account_id = var.account_id + name = "my-worker" } -resource "cloudflare_workers_script" "my-hello-world-script" { - account_id = "" - script_name = "my-hello-world-script" - main_module = "my-hello-world-script.mjs" - content = trimspace(file("my-hello-world-script.mjs")) +resource "cloudflare_worker_version" "my_worker_version" { + account_id = var.account_id + worker_id = cloudflare_worker.my_worker.id compatibility_date = "$today" - bindings = [{ - name = "MESSAGE" - type = "plain_text" - text = "Hello World!" + main_module = "my-script.mjs" + modules = [ + { + name = "my-script.mjs" + content_type = "application/javascript+module" + # Replacement (version creation) is triggered whenever this file changes + content_file = "my-script.mjs" + } + ] +} + +resource "cloudflare_workers_deployment" "my_worker_deployment" { + account_id = var.account_id + script_name = cloudflare_worker.my_worker.name + strategy = "percentage" + versions = [{ + percentage = 100 + version_id = cloudflare_worker_version.my_worker_version.id }] } ``` -:::note - -- `trimspace()` removes empty lines in the file -- The Workers Script resource does not have a `metadata` property like in the other examples. All of the properties found in `metadata` are instead at the top-level of the resource class, such as `bindings` or `compatibility_date`. Please see the [cloudflare_workers_script (Resource) docs](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/workers_script). - ::: - ## Cloudflare API Libraries -### JavaScript/TypeScript - -This example uses the [cloudflare-typescript](https://github.com/cloudflare/cloudflare-typescript) library which provides convenient access to the Cloudflare REST API from server-side JavaScript or TypeScript. +This example uses the [cloudflare-typescript](https://github.com/cloudflare/cloudflare-typescript) SDK which provides convenient access to the Cloudflare REST API from server-side JavaScript or TypeScript. -### Python +## Cloudflare REST API -This example uses the [cloudflare-python](https://github.com/cloudflare/cloudflare-python) library. +Open a terminal or create a shell script to upload a Worker and manage versions and deployments with curl. Workers scripts are Javascript [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), but we also support [Python Workers](/workers/languages/python/) (open beta) and [Rust Workers](/workers/languages/rust/). - +:::note[Warning] -## Cloudflare REST API +This API is in beta. See the multipart/form-data API below for the stable API. +::: -Open a terminal or create a shell script to upload a Worker easily with curl. For this example, replace `` and `` with your own. What's notable about interacting with the Workers Script Upload API directly is that it uses [multipart/form-data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) for uploading metadata, multiple JavaScript modules, source maps, and more. This is abstracted away in Terraform and the API libraries. + + ```bash -curl https://api.cloudflare.com/client/v4/accounts//workers/scripts/my-hello-world-script \ - -X PUT \ - -H 'Authorization: Bearer ' \ - -F 'metadata={ - "main_module": "my-hello-world-script.mjs", - "bindings": [ - { - "type": "plain_text", - "name": "MESSAGE", - "text": "Hello World!" - } - ], - "compatibility_date": "$today" - };type=application/json' \ - -F 'my-hello-world-script.mjs=@-;filename=my-hello-world-script.mjs;type=application/javascript+module' < + -Change `my-hello-world-script.mjs=@-;` to `my-hello-world-script.mjs=@./my-hello-world-script.mjs;` and remove everything after and including `</workers/dispatch/namespaces//scripts/my-hello-world-script +def on_fetch(request, env): + return Response(env.MESSAGE) +' | base64) + +# Note the below will fail if the worker already exists! +# Here's how to delete the Worker +# +# worker_id="replace-me" +# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \ +# -X DELETE \ +# -H "Authorization: Bearer $api_token" + +# Create the Worker +worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \ + -X POST \ + -H "Authorization: Bearer $api_token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "'$worker_name'" + }' \ + | jq -r '.result.id') + +echo "\nWorker ID: $worker_id\n" + +# Upload the Worker's first version +version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \ + -X POST \ + -H "Authorization: Bearer $api_token" \ + -H "Content-Type: application/json" \ + -d '{ + "compatibility_date": "2025-08-06", + "compatibility_flags": [ + "python_workers" + ], + "main_module": "'$worker_name'.py", + "modules": [ + { + "name": "'$worker_name'.py", + "content_type": "text/x-python", + "content_base64": "'$worker_script_base64'" + } + ], + "bindings": [ + { + "type": "plain_text", + "name": "MESSAGE", + "text": "Hello World!" + } + ] + }' \ + | jq -r '.result.id') + +echo "\nVersion ID: $version_id\n" + +# Create a deployment for the Worker +deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \ + -X POST \ + -H "Authorization: Bearer $api_token" \ + -H "Content-Type: application/json" \ + -d '{ + "strategy": "percentage", + "versions": [ + { + "percentage": 100, + "version_id": "'$version_id'" + } + ] + }' \ + | jq -r '.result.id') + +echo "\nDeployment ID: $deployment_id\n" ``` -For this to work, you first need to configure [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms/get-started/configuration), create a dispatch namespace, and replace `` with your own. + + + +### multipart/form-data upload API -### Python Workers +This API uses [multipart/form-data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) to upload a Worker and will implicitly create a version and deployment. The above API is recommended for direct management of versions and deployments. -[Python Workers](/workers/languages/python/) (open beta) have their own special `text/x-python` content type and `python_workers` compatibility flag for uploading. + + ```bash -curl https://api.cloudflare.com/client/v4/accounts//workers/scripts/my-hello-world-script \ +account_id="replace_me" +api_token="replace_me" +worker_name="my-hello-world-script" + +script_content='export default { + async fetch(request, env, ctx) { + return new Response(env.MESSAGE, { status: 200 }); + } +};' + +# Upload the Worker +curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name" \ -X PUT \ - -H 'Authorization: Bearer ' \ - -F 'metadata={ - "main_module": "my-hello-world-script.py", - "bindings": [ - { - "type": "plain_text", - "name": "MESSAGE", - "text": "Hello World!" - } - ], - "compatibility_date": "$today", - "compatibility_flags": [ - "python_workers" - ] - };type=application/json' \ - -F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' < + + +For [Workers for Platforms](/cloudflare-for-platforms/workers-for-platforms), you can upload a [User Worker](/cloudflare-for-platforms/workers-for-platforms/get-started/user-workers) to a [dispatch namespace](/cloudflare-for-platforms/workers-for-platforms/reference/how-workers-for-platforms-works/#dispatch-namespace). Note the [API endpoint](/api/resources/workers_for_platforms/subresources/dispatch/subresources/namespaces/subresources/scripts/methods/update/) is on `/workers/dispatch/namespaces/$DISPATCH_NAMESPACE/scripts/$SCRIPT_NAME`. + +```bash +account_id="replace_me" +api_token="replace_me" +dispatch_namespace="replace_me" +worker_name="my-hello-world-script" + +script_content='export default { + async fetch(request, env, ctx) { + return new Response(env.MESSAGE, { status: 200 }); + } +};' + +# Create a dispatch namespace +curl https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces \ + -X POST \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $api_token" \ + -d '{ + "name": "'$dispatch_namespace'" + }' + +# Upload the Worker +curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces/$dispatch_namespace/scripts/$worker_name" \ + -X PUT \ + -H "Authorization: Bearer $api_token" \ + -F "metadata={ + 'main_module': '"$worker_name".mjs', + 'bindings': [ + { + 'type': 'plain_text', + 'name': 'MESSAGE', + 'text': 'Hello World!' + } + ], + 'compatibility_date': '$today' + };type=application/json" \ + -F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" < + diff --git a/src/content/docs/workers/static-assets/direct-upload.mdx b/src/content/docs/workers/static-assets/direct-upload.mdx index 545902f83d7f3e8..137336fb8fbb1ee 100644 --- a/src/content/docs/workers/static-assets/direct-upload.mdx +++ b/src/content/docs/workers/static-assets/direct-upload.mdx @@ -11,6 +11,7 @@ import { Badge, Description, FileTree, + GitHubCode, InlineBadge, Render, TabItem, @@ -214,218 +215,12 @@ Optionally, an assets binding can be provided if you wish to fetch and serve ass ## Programmatic Example - +TODO update this with the prod commit -```ts -import * as fs from "fs"; -import * as path from "path"; -import * as crypto from "crypto"; -import { FormData, fetch } from "undici"; -import "node:process"; - -const accountId: string = ""; // Replace with your actual account ID -const filesDirectory: string = "assets"; // Adjust to your assets directory -const scriptName: string = "my-new-script"; // Replace with desired script name -const dispatchNamespace: string = ""; // Replace with a dispatch namespace if using Workers for Platforms - -interface FileMetadata { - hash: string; - size: number; -} - -interface UploadSessionData { - uploadToken: string; - buckets: string[][]; - fileMetadata: Record; -} - -interface UploadResponse { - result: { - jwt: string; - buckets: string[][]; - }; - success: boolean; - errors: any; - messages: any; -} - -// Function to calculate the SHA-256 hash of a file and truncate to 32 characters -function calculateFileHash(filePath: string): { - fileHash: string; - fileSize: number; -} { - const hash = crypto.createHash("sha256"); - const fileBuffer = fs.readFileSync(filePath); - hash.update(fileBuffer); - const fileHash = hash.digest("hex").slice(0, 32); // Grab the first 32 characters - const fileSize = fileBuffer.length; - return { fileHash, fileSize }; -} - -// Function to gather file metadata for all files in the directory -function gatherFileMetadata(directory: string): Record { - const files = fs.readdirSync(directory); - const fileMetadata: Record = {}; - - files.forEach((file) => { - const filePath = path.join(directory, file); - const { fileHash, fileSize } = calculateFileHash(filePath); - fileMetadata["/" + file] = { - hash: fileHash, - size: fileSize, - }; - }); - - return fileMetadata; -} - -function findMatch( - fileHash: string, - fileMetadata: Record, -): string { - for (let prop in fileMetadata) { - const file = fileMetadata[prop] as FileMetadata; - if (file.hash === fileHash) { - return prop; - } - } - throw new Error("unknown fileHash"); -} - -// Function to upload a batch of files using the JWT from the first response -async function uploadFilesBatch( - jwt: string, - fileHashes: string[][], - fileMetadata: Record, -): Promise { - const form = new FormData(); - - for (const bucket of fileHashes) { - bucket.forEach((fileHash) => { - const fullPath = findMatch(fileHash, fileMetadata); - const relPath = filesDirectory + "/" + path.basename(fullPath); - const fileBuffer = fs.readFileSync(relPath); - const base64Data = fileBuffer.toString("base64"); // Convert file to Base64 - - form.append( - fileHash, - new File([base64Data], fileHash, { - type: "text/html", // Modify Content-Type header based on type of file - }), - fileHash, - ); - }); - - const response = await fetch( - `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/assets/upload?base64=true`, - { - method: "POST", - headers: { - Authorization: `Bearer ${jwt}`, - }, - body: form, - }, - ); - - const data = (await response.json()) as UploadResponse; - if (data && data.result.jwt) { - return data.result.jwt; - } - } - - throw new Error("Should have received completion token"); -} - -async function scriptUpload(completionToken: string): Promise { - const form = new FormData(); - - // Configure metadata - form.append( - "metadata", - JSON.stringify({ - main_module: "index.mjs", - compatibility_date: "2022-03-11", - assets: { - jwt: completionToken, // Provide the completion token from file uploads - }, - bindings: [{ name: "ASSETS", type: "assets" }], // Optional assets binding to fetch from user worker - }), - ); - - // Configure (optional) user worker - form.append( - "index.js", - new File( - [ - "export default {async fetch(request, env) { return new Response('Hello world from user worker!'); }}", - ], - "index.mjs", - { - type: "application/javascript+module", - }, - ), - ); - - const url = dispatchNamespace - ? `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${scriptName}` - : `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts/${scriptName}`; - - const response = await fetch(url, { - method: "PUT", - headers: { - Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, - }, - body: form, - }); - - if (response.status != 200) { - throw new Error("unexpected status code"); - } -} - -// Function to make the POST request to start the assets upload session -async function startUploadSession(): Promise { - const fileMetadata = gatherFileMetadata(filesDirectory); - - const requestBody = JSON.stringify({ - manifest: fileMetadata, - }); - - const url = dispatchNamespace - ? `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${scriptName}/assets-upload-session` - : `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts/${scriptName}/assets-upload-session`; - - const response = await fetch(url, { - method: "POST", - headers: { - Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, - "Content-Type": "application/json", - }, - body: requestBody, - }); - - const data = (await response.json()) as UploadResponse; - const jwt = data.result.jwt; - - return { - uploadToken: jwt, - buckets: data.result.buckets, - fileMetadata, - }; -} - -// Begin the upload session by uploading a new manifest -const { uploadToken, buckets, fileMetadata } = await startUploadSession(); - -// If all files are already uploaded, a completion token will be immediately returned. Otherwise, -// we should upload the missing files -let completionToken = uploadToken; -if (buckets.length > 0) { - completionToken = await uploadFilesBatch(uploadToken, buckets, fileMetadata); -} - -// Once we have uploaded all of our files, we can upload a new script, and assets, with completion token -await scriptUpload(completionToken); -``` - - +