Add Cloudflare Workers deployment method#2205
Add Cloudflare Workers deployment method#2205rjocoleman wants to merge 1 commit intoGetPublii:masterfrom
Conversation
5810658 to
7a434a4
Compare
YasharF
left a comment
There was a problem hiding this comment.
-
What testing and verification have you done for this?
-
Can you add the proper required API permissions to UI/docs so users create a token with the proper scopes?
-
I think uploadFiles() and deployWorker() build a FormData body but do not merge form.getHeaders() into the fetch request headers. This will omit the required Content-Type: multipart/form-data; boundary=... and break uploads/deploys. Fix: set headers to { ...form.getHeaders(), Authorization: 'Bearer ...' }.
-
If fetch(...).json() fails because response isn't JSON (e.g., upload failed due to missing multipart headers), the error will surface but logs may be unclear. Consider logging response.status and raw text on non-OK responses for easier debugging.
| validateCloudflareWorkers () { | ||
| let fields = ['cloudflareWorkers_accountId', 'cloudflareWorkers_apiToken', 'cloudflareWorkers_scriptName']; | ||
| return this.validateFields(fields); | ||
| }, |
There was a problem hiding this comment.
Cloudflare Workers validation errors won’t map to the fields in the template. validateFields() will push error keys like cloudflareWorkers-accountId (underscore replaced with hyphen), but the inputs are keyed off cf-account-id / cf-api-token / cf-script-name, so the invalid styling and cleanError() won’t work and users won’t see which fields are failing validation. Align the input ids / error checks with the keys generated by validateFields(), or handle these fields with custom error keys instead of validateFields().
| }, | |
| // Custom validation for Cloudflare Workers to ensure error keys | |
| // match the template input IDs: cf-account-id, cf-api-token, cf-script-name | |
| const workers = (this.deploymentSettings && this.deploymentSettings.cloudflareWorkers) || {}; | |
| // Clear any previous errors for these specific fields | |
| if (typeof this.cleanError === 'function') { | |
| this.cleanError('cf-account-id'); | |
| this.cleanError('cf-api-token'); | |
| this.cleanError('cf-script-name'); | |
| } | |
| // Ensure there is an errors object to store field-level errors | |
| if (!this.errors) { | |
| this.$set(this, 'errors', {}); | |
| } | |
| let isValid = true; | |
| if (!workers.accountId) { | |
| this.$set(this.errors, 'cf-account-id', true); | |
| isValid = false; | |
| } | |
| if (!workers.apiToken) { | |
| this.$set(this.errors, 'cf-api-token', true); | |
| isValid = false; | |
| } | |
| if (!workers.scriptName) { | |
| this.$set(this.errors, 'cf-script-name', true); | |
| isValid = false; | |
| } | |
| return isValid; |
| const crypto = require('crypto'); | ||
| const mime = require('mime'); | ||
| const FormData = require('form-data'); | ||
| const fetch = require('node-fetch'); |
There was a problem hiding this comment.
'node-fetch' as a npm module is no longer maintained. Remove this line and use the built-in fetch().
| ] | ||
| }; | ||
|
|
||
| console.log(`[${new Date().toUTCString()}] Cloudflare Workers metadata:`, JSON.stringify(metadata, null, 2)); |
There was a problem hiding this comment.
This console.log prints the full deployment metadata including the assets upload JWT (metadata.assets.jwt). That token can be used to authorize asset operations and shouldn’t be written to logs. I would remove this log line or use conditional debug prints while redacting sensitive fields before logging (and ideally gate behind an explicit debug flag).
| console.log(`[${new Date().toUTCString()}] Cloudflare Workers metadata:`, JSON.stringify(metadata, null, 2)); | |
| if (process.env.CF_WORKERS_DEBUG === 'true') { | |
| const safeMetadata = { | |
| ...metadata, | |
| assets: { | |
| ...metadata.assets, | |
| jwt: '[REDACTED]' | |
| } | |
| }; | |
| console.log( | |
| `[${new Date().toUTCString()}] Cloudflare Workers metadata:`, | |
| JSON.stringify(safeMetadata, null, 2) | |
| ); | |
| } |
| } | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
findFileByHash() does an O(n) scan over fileMetadata for every hash in every bucket, which can easily become O(n^2) for larger sites. Build a reverse index (e.g., hash -> relativePath) once when gathering metadata so uploads can locate files in O(1).
| this.deployment.operationsCounter = files.length + 3; // +3 for upload session, worker deploy, and completion | ||
| this.deployment.progressPerFile = 90.0 / this.deployment.operationsCounter; | ||
| this.deployment.currentOperationNumber = 0; | ||
|
|
There was a problem hiding this comment.
operationsCounter is set based on the number of files, but updateProgress() is only called once for the upload session, once per bucket upload, and once for worker deploy (not once per file). This makes the (current/total) operations display and progress step calculation inaccurate. Make operationsCounter match the number of progress updates you actually emit, or call updateProgress() per uploaded file instead of per bucket.
// Track progress based on high-level operations (upload session, worker deploy, completion)
this.deployment.operationsCounter = 3;
| config.settings.deployment.cloudflareWorkers && | ||
| config.settings.deployment.cloudflareWorkers.accountId !== '' && | ||
| config.settings.deployment.cloudflareWorkers.apiToken !== '' && | ||
| config.settings.deployment.cloudflareWorkers.accountId !== 'publii-cf-account-id ' + siteID |
There was a problem hiding this comment.
When deciding whether to store Cloudflare Workers credentials in the keychain, the condition checks whether accountId is already the placeholder value, but it doesn’t check the same for apiToken. For consistency with other credential blocks (and to avoid trying to re-store placeholder values), add a placeholder check for cloudflareWorkers.apiToken as well.
| config.settings.deployment.cloudflareWorkers.accountId !== 'publii-cf-account-id ' + siteID | |
| config.settings.deployment.cloudflareWorkers.accountId !== 'publii-cf-account-id ' + siteID && | |
| config.settings.deployment.cloudflareWorkers.apiToken !== 'publii-cf-api-token ' + siteID |
| const path = require('path'); | ||
| const crypto = require('crypto'); | ||
| const mime = require('mime'); | ||
| const FormData = require('form-data'); |
There was a problem hiding this comment.
Maybe I missed it, but I didn't see form-data as a direct dependency in package.json
It works for me - uploads/deploys. I won't pretend to have encountered every edge case but it's dependable and without visible errors in my workflow.
Thanks - I will look at the balance of this feedback if I get a chance. |
This PR adds Cloudflare Workers Static Assets as a new deployment method.
Is is a direct API integration, and could replace the to the existing Cloudflare Pages deployment method via Github.
Overview
This implementation uses Cloudflare Workers' static asset hosting capabilities to deploy sites directly to Cloudflare's edge network. It's strongly recommended by Cloudflare that new projects use this rather than CloudFlare Pages.
Key Features & Changes
Core Functionality:
• New
CloudflareWorkersdeployment class (cloudflare-workers.js) with full upload/deployment pipeline• Direct API integration with Cloudflare Workers Static Assets API
• Automatic file hashing and deduplication for efficient uploads
• Batch file upload system using form-data multipart uploads
• Auto-generated worker script that serves static assets with proper routing
Configuration & UI:
• New deployment option in server settings with Cloudflare logo
• Three required configuration fields:
Account ID,API Token, andWorker Name• Secure credential storage via keytar
• Connection testing functionality to validate API access
• Form validation and error handling
Technical Implementation:
• Follows existing Publii deployment patterns and idioms
• Integrated into main deployment system (
deployment.js)• Support for 404s via the existing 404.html in worker configuration
• Auto-trailing-slash HTML handling for clean URLs
• Progress tracking and status reporting during deployment
Setup Requirements:
• Cloudflare account with Workers plan (free is fine)
• API token with Workers edit permissions
• Account ID from Cloudflare dashboard
• Worker name (can be created via one of the Cloudflare onboarding starters, gets overwritten)
• Optional: Custom domain can be later added via Cloudflare Workers Web UI
Refs: #2049 #996 #1174