@theholocron/holocron-plugin-cloudflare 5.0.0-alpha.9 → 5.0.0-alpha.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  # `@theholocron/holocron-plugin-cloudflare`
4
4
 
5
- Cloudflare plugin for [Holocron](../cli). Implements the `dns` and `deployment`
6
- capabilities against the [Cloudflare v4 API](https://developers.cloudflare.com/api/).
5
+ Cloudflare plugin for [Holocron](../cli). Implements the `dns`, `deployment`,
6
+ and `workers` capabilities against the
7
+ [Cloudflare v4 API](https://developers.cloudflare.com/api/).
7
8
 
8
9
  ## Install
9
10
 
@@ -40,12 +41,15 @@ Generate a scoped API token at **dash.cloudflare.com/profile/api-tokens**.
40
41
  // Cloudflare Pages deployments (requires accountId)
41
42
  // accountId falls back to CLOUDFLARE_ACCOUNT_ID env var when omitted
42
43
  "deployment": "cloudflare",
44
+
45
+ // Workers deployment (also requires accountId)
46
+ "workers": "cloudflare",
43
47
  },
44
48
  }
45
49
 
46
50
  ```
47
51
 
48
- Both capabilities can be enabled together:
52
+ Any combination of capabilities can be enabled together:
49
53
 
50
54
  <!-- prettier-ignore -->
51
55
  ```jsonc
@@ -53,12 +57,15 @@ Both capabilities can be enabled together:
53
57
  "providers": {
54
58
  "dns": "cloudflare",
55
59
  "deployment": "cloudflare",
60
+ "workers": "cloudflare",
56
61
  },
57
62
  }
58
63
 
59
64
  ```
60
65
 
61
- The `deployment` capability is only exposed when `accountId` is resolvable — either passed explicitly in options or set via the `CLOUDFLARE_ACCOUNT_ID` env var.
66
+ The `deployment` and `workers` capabilities are only exposed when `accountId`
67
+ is resolvable — either passed explicitly in options or set via the
68
+ `CLOUDFLARE_ACCOUNT_ID` env var.
62
69
 
63
70
  ## `dns` capability
64
71
 
@@ -83,6 +90,20 @@ Manages [Cloudflare Pages](https://developers.cloudflare.com/pages/) projects. U
83
90
  | `triggerDeployment` | Triggers a new Pages deployment from the latest production branch commit. |
84
91
  | `updateProjectSettings` | Updates project-level settings (currently a no-op; CF Pages REST API has no direct settings endpoint for these fields). |
85
92
 
93
+ ## `workers` capability
94
+
95
+ Deploys and manages [Workers](https://developers.cloudflare.com/workers/) scripts.
96
+
97
+ | Method | What it does |
98
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `upsertProxy` | Deploys a generated reverse-proxy Worker forwarding `hostname/*` to `config.target`, injecting `config.headers`. Used by `setup` to wire wiki custom-domain proxies when the wiki provider requires one in addition to the CNAME. |
100
+ | `deployScript` | Deploys (or updates) an arbitrary Worker script — the caller's own code, not a generated proxy — with optional secret bindings and route patterns. For a consumer that isn't a reverse-proxy, e.g. a webhook receiver. |
101
+
102
+ Both methods are idempotent: re-running with the same inputs makes no
103
+ route-write call when nothing changed. Zone ids are cached per plugin
104
+ instance for the lifetime of the process — the `workers` capability's own
105
+ cache, separate from `dns`'s.
106
+
86
107
  ### Preview deployment setup
87
108
 
88
109
  When `preview: true` (or `preview: { project, domain }`) is set in a repo's deploy config, `holocron setup` calls:
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { AuthError, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, ResolveTokenInput, WikiProxyConfig, Workers } from "@theholocron/cli";
2
- import { CloudflareClient, CloudflareClientOptions, CloudflareClientOptions as CloudflareClientOptions$1, createCloudflareClient } from "@theholocron/cloudflare-client";
1
+ import { AuthError, DeployScriptConfig, DeployScriptResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordRequest, ResolveTokenInput, WikiProxyConfig, Workers } from "@theholocron/cli";
2
+ import { CloudflareClient, CloudflareClient as CloudflareClient$1, CloudflareClientOptions, createCloudflareClient } from "@theholocron/cloudflare-client";
3
3
  //#region src/auth.d.ts
4
4
  declare const resolveToken: (input?: ResolveTokenInput) => string;
5
5
  //#endregion
@@ -28,7 +28,7 @@ declare class CloudflareDeployment implements Deployment {
28
28
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
29
29
  listPreviewDeployments(projectId: string, branch: string): Promise<DeploymentRecord[]>;
30
30
  deletePreviewDeployments(projectId: string, deploymentIds: string[]): Promise<number>;
31
- ensureCustomDomain(projectId: string, hostname: string): Promise<void>;
31
+ ensureCustomDomain(projectId: string, hostname: string): Promise<DnsRecordRequest | null>;
32
32
  private getProjectByName;
33
33
  }
34
34
  //#endregion
@@ -53,33 +53,17 @@ declare class CloudflareDns implements Dns {
53
53
  }
54
54
  //#endregion
55
55
  //#region src/capabilities/workers.d.ts
56
- interface ZonesClient {
57
- list(query?: {
58
- name?: string;
59
- per_page?: number;
60
- }): Promise<Array<{
61
- id: string;
62
- }>>;
63
- }
64
56
  declare class CloudflareWorkers implements Workers {
65
- private readonly zones;
57
+ private readonly client;
66
58
  private readonly accountId;
67
59
  readonly key: "workers";
68
60
  readonly providerName = "cloudflare";
69
61
  private readonly zoneCache;
70
- private readonly baseUrl;
71
- private readonly fetchImpl;
72
- /** Memoized token thunk — resolved on the first authenticated request. */
73
- private readonly token;
74
- constructor(zones: () => ZonesClient, accountId: string, opts: {
75
- token: () => string;
76
- } & Pick<CloudflareClientOptions$1, "baseUrl" | "fetch">);
62
+ constructor(client: () => Pick<CloudflareClient$1, "workers" | "zones">, accountId: string);
77
63
  upsertProxy(hostname: string, config: WikiProxyConfig): Promise<void>;
78
- private putScript;
79
- private listRoutes;
80
- private createRoute;
81
- private updateRoute;
82
- private cfRequest;
64
+ deployScript(name: string, config: DeployScriptConfig): Promise<DeployScriptResult>;
65
+ /** Idempotent create-or-update for one route pattern — shared by both public methods. */
66
+ private upsertRoute;
83
67
  private resolveZone;
84
68
  }
85
69
  //#endregion
package/dist/index.mjs CHANGED
@@ -74,8 +74,9 @@ var CloudflareDeployment = class {
74
74
  return deploymentIds.length;
75
75
  }
76
76
  async ensureCustomDomain(projectId, hostname) {
77
- if ((await this.client().pages.listDomains(this.accountId, projectId)).some((d) => d.name === hostname)) return;
77
+ if ((await this.client().pages.listDomains(this.accountId, projectId)).some((d) => d.name === hostname)) return null;
78
78
  await this.client().pages.addDomain(this.accountId, projectId, hostname);
79
+ return null;
79
80
  }
80
81
  async getProjectByName(name) {
81
82
  try {
@@ -187,87 +188,35 @@ function mapRecord(raw) {
187
188
  //#endregion
188
189
  //#region src/capabilities/workers.ts
189
190
  var CloudflareWorkers = class {
190
- zones;
191
+ client;
191
192
  accountId;
192
193
  key = "workers";
193
194
  providerName = "cloudflare";
194
195
  zoneCache = /* @__PURE__ */ new Map();
195
- baseUrl;
196
- fetchImpl;
197
- /** Memoized token thunk — resolved on the first authenticated request. */
198
- token;
199
- constructor(zones, accountId, opts) {
200
- this.zones = zones;
196
+ constructor(client, accountId) {
197
+ this.client = client;
201
198
  this.accountId = accountId;
202
- this.token = opts.token;
203
- this.baseUrl = opts.baseUrl ?? "https://api.cloudflare.com/client/v4";
204
- this.fetchImpl = opts.fetch ?? globalThis.fetch;
205
199
  }
206
200
  async upsertProxy(hostname, config) {
207
201
  const scriptName = hostnameToScriptName(hostname);
208
202
  const script = generateProxyScript(config);
209
- await this.putScript(scriptName, script);
210
- const zoneId = await this.resolveZone(hostname);
211
- const pattern = `${hostname}/*`;
212
- const existing = (await this.listRoutes(zoneId)).find((r) => r.pattern === pattern);
213
- if (existing) {
214
- if (existing.script !== scriptName) await this.updateRoute(zoneId, existing.id, pattern, scriptName);
215
- } else await this.createRoute(zoneId, pattern, scriptName);
216
- }
217
- async putScript(scriptName, script) {
218
- const form = new FormData();
219
- form.append("metadata", new Blob([JSON.stringify({
220
- main_module: "index.js",
221
- compatibility_date: "2025-08-31"
222
- })], { type: "application/json" }));
223
- form.append("index.js", new Blob([script], { type: "application/javascript+module" }), "index.js");
224
- const path = `/accounts/${this.accountId}/workers/scripts/${encodeURIComponent(scriptName)}`;
225
- const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
226
- method: "PUT",
227
- headers: { authorization: `Bearer ${this.token()}` },
228
- body: form
229
- });
230
- if (!res.ok) {
231
- const body = await res.text().catch(() => "");
232
- throw new ProviderApiError(`Cloudflare PUT ${path} → ${res.status}`, res.status, body);
233
- }
234
- }
235
- listRoutes(zoneId) {
236
- return this.cfRequest("GET", `/zones/${zoneId}/workers/routes`);
237
- }
238
- createRoute(zoneId, pattern, script) {
239
- return this.cfRequest("POST", `/zones/${zoneId}/workers/routes`, {
240
- pattern,
241
- script
242
- });
203
+ await this.client().workers.putScript(this.accountId, scriptName, script);
204
+ await this.upsertRoute(`${hostname}/*`, scriptName);
243
205
  }
244
- updateRoute(zoneId, routeId, pattern, script) {
245
- return this.cfRequest("PUT", `/zones/${zoneId}/workers/routes/${routeId}`, {
246
- pattern,
247
- script
248
- });
206
+ async deployScript(name, config) {
207
+ await this.client().workers.putScript(this.accountId, name, config.code);
208
+ for (const [secretName, value] of Object.entries(config.secrets ?? {})) await this.client().workers.putSecret(this.accountId, name, secretName, value);
209
+ for (const pattern of config.routes ?? []) await this.upsertRoute(pattern, name);
210
+ return { scriptName: name };
249
211
  }
250
- async cfRequest(method, path, body) {
251
- const headers = {
252
- authorization: `Bearer ${this.token()}`,
253
- accept: "application/json"
254
- };
255
- const init = {
256
- method,
257
- headers
258
- };
259
- if (body !== void 0) {
260
- headers["content-type"] = "application/json";
261
- init.body = JSON.stringify(body);
262
- }
263
- const res = await this.fetchImpl(`${this.baseUrl}${path}`, init);
264
- if (!res.ok) {
265
- const text = await res.text().catch(() => "");
266
- throw new ProviderApiError(`Cloudflare ${method} ${path} → ${res.status}`, res.status, text);
267
- }
268
- const envelope = await res.json();
269
- if (!envelope.success) throw new ProviderApiError(`Cloudflare ${method} ${path} returned success:false`, 0, JSON.stringify(envelope.errors));
270
- return envelope.result;
212
+ /** Idempotent create-or-update for one route pattern — shared by both public methods. */
213
+ async upsertRoute(pattern, scriptName) {
214
+ const hostname = pattern.split("/")[0];
215
+ const zoneId = await this.resolveZone(hostname);
216
+ const existing = (await this.client().workers.listRoutes(zoneId)).find((r) => r.pattern === pattern);
217
+ if (existing) {
218
+ if (existing.script !== scriptName) await this.client().workers.updateRoute(zoneId, existing.id, pattern, scriptName);
219
+ } else await this.client().workers.createRoute(zoneId, pattern, scriptName);
271
220
  }
272
221
  async resolveZone(domain) {
273
222
  const cached = this.zoneCache.get(domain);
@@ -275,7 +224,7 @@ var CloudflareWorkers = class {
275
224
  const parts = domain.split(".");
276
225
  for (let i = 0; i < parts.length - 1; i++) {
277
226
  const candidate = parts.slice(i).join(".");
278
- const zones = await this.zones().list({ name: candidate });
227
+ const zones = await this.client().zones.list({ name: candidate });
279
228
  if (zones.length > 0) {
280
229
  this.zoneCache.set(domain, zones[0].id);
281
230
  return zones[0].id;
@@ -359,11 +308,7 @@ function deployment(ctx) {
359
308
  }
360
309
  function workersCapability(ctx) {
361
310
  if (!ctx.options.accountId) throw new Error("Cloudflare accountId is required for Workers — set accountId in the plugin options or CLOUDFLARE_ACCOUNT_ID env var");
362
- return new CloudflareWorkers(() => ctx.client().zones, ctx.options.accountId, {
363
- token: ctx.token,
364
- baseUrl: ctx.options.baseUrl,
365
- fetch: ctx.options.fetch
366
- });
311
+ return new CloudflareWorkers(ctx.client, ctx.options.accountId);
367
312
  }
368
313
  function createPlugin(options = {}) {
369
314
  const ctx = createContext(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-cloudflare",
3
- "version": "5.0.0-alpha.9",
3
+ "version": "5.0.0-alpha.91",
4
4
  "description": "Holocron plugin for Cloudflare. Implements the dns capability against Cloudflare's REST API — zone resolution, DNS record management.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -30,11 +30,12 @@
30
30
  "dist"
31
31
  ],
32
32
  "devDependencies": {
33
- "@theholocron/cloudflare-client": "^1.14.2",
34
- "@theholocron/eslint-config": "^8.0.0",
35
- "@theholocron/tsconfig": "^8.0.0",
36
- "@theholocron/tsdown-config": "^8.0.0",
37
- "@theholocron/vitest-config": "^8.0.0",
33
+ "@theholocron/cloudflare-client": "^1.31.0",
34
+ "@theholocron/eslint-config": "^8.6.0",
35
+ "@theholocron/http-client": "^1.31.0",
36
+ "@theholocron/tsconfig": "^8.4.5",
37
+ "@theholocron/tsdown-config": "^8.4.5",
38
+ "@theholocron/vitest-config": "^8.4.5",
38
39
  "@types/node": "^26",
39
40
  "@vitest/coverage-v8": "^4.1.11",
40
41
  "@vitest/eslint-plugin": "^1.6.27",
@@ -46,11 +47,11 @@
46
47
  "tsx": "4.23.12",
47
48
  "typescript": "^5.9.3",
48
49
  "vitest": "^4.1.11",
49
- "@theholocron/cli": "5.0.0-alpha.9"
50
+ "@theholocron/cli": "5.0.0-alpha.91"
50
51
  },
51
52
  "peerDependencies": {
52
- "@theholocron/cloudflare-client": "^1.14.2",
53
- "@theholocron/cli": "5.0.0-alpha.9"
53
+ "@theholocron/cloudflare-client": "^1.31.0",
54
+ "@theholocron/cli": "5.0.0-alpha.91"
54
55
  },
55
56
  "peerDependenciesMeta": {
56
57
  "@theholocron/cloudflare-client": {}