@theholocron/holocron-plugin-cloudflare 3.45.0 → 3.45.2

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/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { AuthError, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, ResolveTokenInput } from "@theholocron/cli";
2
- import { CloudflareClient, CloudflareClientOptions, createCloudflareClient } from "@theholocron/cloudflare-client";
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";
3
3
  //#region src/auth.d.ts
4
4
  declare const resolveToken: (input?: ResolveTokenInput) => string;
5
5
  //#endregion
@@ -52,6 +52,34 @@ declare class CloudflareDns implements Dns {
52
52
  private resolveZone;
53
53
  }
54
54
  //#endregion
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
+ declare class CloudflareWorkers implements Workers {
65
+ private readonly zones;
66
+ private readonly accountId;
67
+ readonly key: "workers";
68
+ readonly providerName = "cloudflare";
69
+ private readonly zoneCache;
70
+ private readonly baseUrl;
71
+ private readonly fetchImpl;
72
+ private readonly token;
73
+ constructor(zones: ZonesClient, accountId: string, opts: Pick<CloudflareClientOptions$1, "token" | "baseUrl" | "fetch">);
74
+ upsertProxy(hostname: string, config: WikiProxyConfig): Promise<void>;
75
+ private putScript;
76
+ private listRoutes;
77
+ private createRoute;
78
+ private updateRoute;
79
+ private cfRequest;
80
+ private resolveZone;
81
+ }
82
+ //#endregion
55
83
  //#region src/verify-token.d.ts
56
84
  interface VerifyTokenSuccess {
57
85
  ok: true;
@@ -86,13 +114,15 @@ interface PluginContext {
86
114
  declare function createContext(options?: CloudflarePluginOptions): PluginContext;
87
115
  declare function dns(ctx: PluginContext): Dns;
88
116
  declare function deployment(ctx: PluginContext): Deployment;
117
+ declare function workersCapability(ctx: PluginContext): Workers;
89
118
  declare function createPlugin(options?: CloudflarePluginOptions): {
90
119
  name: string;
91
120
  capabilities: {
92
121
  deployment?: (() => Deployment) | undefined;
122
+ workers?: (() => Workers) | undefined;
93
123
  dns: () => Dns;
94
124
  };
95
125
  };
96
126
  declare const AUTH_HINT: string;
97
127
  //#endregion
98
- export { AUTH_HINT, AuthError, type CloudflareClient, type CloudflareClientOptions, CloudflareDeployment, CloudflareDns, CloudflarePluginOptions, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createCloudflareClient, createContext, createPlugin, deployment, dns, resolveToken, verifyToken };
128
+ export { AUTH_HINT, AuthError, type CloudflareClient, type CloudflareClientOptions, CloudflareDeployment, CloudflareDns, CloudflarePluginOptions, CloudflareWorkers, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createCloudflareClient, createContext, createPlugin, deployment, dns, resolveToken, verifyToken, workersCapability };
package/dist/index.mjs CHANGED
@@ -182,6 +182,123 @@ function mapRecord(raw) {
182
182
  };
183
183
  }
184
184
  //#endregion
185
+ //#region src/capabilities/workers.ts
186
+ var CloudflareWorkers = class {
187
+ zones;
188
+ accountId;
189
+ key = "workers";
190
+ providerName = "cloudflare";
191
+ zoneCache = /* @__PURE__ */ new Map();
192
+ baseUrl;
193
+ fetchImpl;
194
+ token;
195
+ constructor(zones, accountId, opts) {
196
+ this.zones = zones;
197
+ this.accountId = accountId;
198
+ this.token = opts.token;
199
+ this.baseUrl = opts.baseUrl ?? "https://api.cloudflare.com/client/v4";
200
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
201
+ }
202
+ async upsertProxy(hostname, config) {
203
+ const scriptName = hostnameToScriptName(hostname);
204
+ const script = generateProxyScript(config);
205
+ await this.putScript(scriptName, script);
206
+ const zoneId = await this.resolveZone(hostname);
207
+ const pattern = `${hostname}/*`;
208
+ const existing = (await this.listRoutes(zoneId)).find((r) => r.pattern === pattern);
209
+ if (existing) {
210
+ if (existing.script !== scriptName) await this.updateRoute(zoneId, existing.id, pattern, scriptName);
211
+ } else await this.createRoute(zoneId, pattern, scriptName);
212
+ }
213
+ async putScript(scriptName, script) {
214
+ const form = new FormData();
215
+ form.append("metadata", new Blob([JSON.stringify({
216
+ main_module: "index.js",
217
+ compatibility_date: "2025-08-31"
218
+ })], { type: "application/json" }));
219
+ form.append("index.js", new Blob([script], { type: "application/javascript+module" }), "index.js");
220
+ const path = `/accounts/${this.accountId}/workers/scripts/${encodeURIComponent(scriptName)}`;
221
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
222
+ method: "PUT",
223
+ headers: { authorization: `Bearer ${this.token}` },
224
+ body: form
225
+ });
226
+ if (!res.ok) {
227
+ const body = await res.text().catch(() => "");
228
+ throw new ProviderApiError(`Cloudflare PUT ${path} → ${res.status}`, res.status, body);
229
+ }
230
+ }
231
+ listRoutes(zoneId) {
232
+ return this.cfRequest("GET", `/zones/${zoneId}/workers/routes`);
233
+ }
234
+ createRoute(zoneId, pattern, script) {
235
+ return this.cfRequest("POST", `/zones/${zoneId}/workers/routes`, {
236
+ pattern,
237
+ script
238
+ });
239
+ }
240
+ updateRoute(zoneId, routeId, pattern, script) {
241
+ return this.cfRequest("PUT", `/zones/${zoneId}/workers/routes/${routeId}`, {
242
+ pattern,
243
+ script
244
+ });
245
+ }
246
+ async cfRequest(method, path, body) {
247
+ const headers = {
248
+ authorization: `Bearer ${this.token}`,
249
+ accept: "application/json"
250
+ };
251
+ const init = {
252
+ method,
253
+ headers
254
+ };
255
+ if (body !== void 0) {
256
+ headers["content-type"] = "application/json";
257
+ init.body = JSON.stringify(body);
258
+ }
259
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, init);
260
+ if (!res.ok) {
261
+ const text = await res.text().catch(() => "");
262
+ throw new ProviderApiError(`Cloudflare ${method} ${path} → ${res.status}`, res.status, text);
263
+ }
264
+ const envelope = await res.json();
265
+ if (!envelope.success) throw new ProviderApiError(`Cloudflare ${method} ${path} returned success:false`, 0, JSON.stringify(envelope.errors));
266
+ return envelope.result;
267
+ }
268
+ async resolveZone(domain) {
269
+ const cached = this.zoneCache.get(domain);
270
+ if (cached) return cached;
271
+ const parts = domain.split(".");
272
+ for (let i = 0; i < parts.length - 1; i++) {
273
+ const candidate = parts.slice(i).join(".");
274
+ const zones = await this.zones.list({ name: candidate });
275
+ if (zones.length > 0) {
276
+ this.zoneCache.set(domain, zones[0].id);
277
+ return zones[0].id;
278
+ }
279
+ }
280
+ throw new ProviderApiError(`No Cloudflare zone found for domain: ${domain}`, 404, void 0);
281
+ }
282
+ };
283
+ function hostnameToScriptName(hostname) {
284
+ return hostname.replace(/\./g, "-") + "-proxy";
285
+ }
286
+ function generateProxyScript(config) {
287
+ const targetJson = JSON.stringify(config.target);
288
+ const headersJson = JSON.stringify(config.headers);
289
+ return [
290
+ `export default {`,
291
+ ` async fetch(request) {`,
292
+ ` const url = new URL(request.url);`,
293
+ ` const target = new URL(url.pathname + url.search, ${targetJson});`,
294
+ ` const headers = new Headers(request.headers);`,
295
+ ` for (const [k, v] of Object.entries(${headersJson})) headers.set(k, v);`,
296
+ ` return fetch(target, { method: request.method, headers, body: request.body, redirect: "follow" });`,
297
+ ` },`,
298
+ `};`
299
+ ].join("\n");
300
+ }
301
+ //#endregion
185
302
  //#region src/verify-token.ts
186
303
  async function verifyToken(token, opts = {}) {
187
304
  const client = createCloudflareClient({
@@ -195,6 +312,12 @@ async function verifyToken(token, opts = {}) {
195
312
  ok: true,
196
313
  subject: `token ${result.id} (${result.status})`
197
314
  };
315
+ } catch {}
316
+ try {
317
+ return {
318
+ ok: true,
319
+ subject: `zone: ${(await client.zones.list({ per_page: 1 }))[0]?.name ?? "unknown"}`
320
+ };
198
321
  } catch (err) {
199
322
  return {
200
323
  ok: false,
@@ -226,16 +349,28 @@ function deployment(ctx) {
226
349
  if (!ctx.options.accountId) throw new Error("Cloudflare accountId is required for Pages deployments — set accountId in the plugin options or CLOUDFLARE_ACCOUNT_ID env var");
227
350
  return new CloudflareDeployment(ctx.client, ctx.options.accountId);
228
351
  }
352
+ function workersCapability(ctx) {
353
+ 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");
354
+ const token = resolveToken(ctx.options);
355
+ return new CloudflareWorkers(ctx.client.zones, ctx.options.accountId, {
356
+ token,
357
+ baseUrl: ctx.options.baseUrl,
358
+ fetch: ctx.options.fetch
359
+ });
360
+ }
229
361
  function createPlugin(options = {}) {
230
362
  const ctx = createContext(options);
231
363
  return {
232
364
  name: "@theholocron/holocron-plugin-cloudflare",
233
365
  capabilities: {
234
366
  dns: () => dns(ctx),
235
- ...ctx.options.accountId ? { deployment: () => deployment(ctx) } : {}
367
+ ...ctx.options.accountId ? {
368
+ deployment: () => deployment(ctx),
369
+ workers: () => workersCapability(ctx)
370
+ } : {}
236
371
  }
237
372
  };
238
373
  }
239
- const AUTH_HINT = "create an API token at https://dash.cloudflare.com/profile/api-tokens with Zone:Read, DNS:Edit, and Cloudflare Pages:Edit permissions, then run: holocron auth set cloudflare <TOKEN>";
374
+ const AUTH_HINT = "create an API token at https://dash.cloudflare.com/profile/api-tokens with Zone:Read, DNS:Edit, Cloudflare Pages:Edit, Workers Scripts:Edit (account-level), and Workers Routes:Edit (zone-level) permissions, then run: holocron auth set cloudflare <TOKEN>";
240
375
  //#endregion
241
- export { AUTH_HINT, AuthError, CloudflareDeployment, CloudflareDns, createCloudflareClient, createContext, createPlugin, deployment, dns, resolveToken, verifyToken };
376
+ export { AUTH_HINT, AuthError, CloudflareDeployment, CloudflareDns, CloudflareWorkers, createCloudflareClient, createContext, createPlugin, deployment, dns, resolveToken, verifyToken, workersCapability };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-cloudflare",
3
- "version": "3.45.0",
3
+ "version": "3.45.2",
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",
@@ -31,10 +31,10 @@
31
31
  ],
32
32
  "devDependencies": {
33
33
  "@theholocron/cloudflare-client": "^1.12.0",
34
- "@theholocron/eslint-config": "^7.25.4",
35
- "@theholocron/tsconfig": "^7.20.0",
36
- "@theholocron/tsdown-config": "^7.20.0",
37
- "@theholocron/vitest-config": "^7.20.0",
34
+ "@theholocron/eslint-config": "^7.29.0",
35
+ "@theholocron/tsconfig": "^7.29.0",
36
+ "@theholocron/tsdown-config": "^7.29.0",
37
+ "@theholocron/vitest-config": "^7.29.0",
38
38
  "@types/node": "^26",
39
39
  "@vitest/coverage-v8": "^4.1.10",
40
40
  "@vitest/eslint-plugin": "^1.6.27",
@@ -46,11 +46,11 @@
46
46
  "tsx": "4.23.12",
47
47
  "typescript": "^5.9.3",
48
48
  "vitest": "^4.1.10",
49
- "@theholocron/cli": "3.45.0"
49
+ "@theholocron/cli": "3.45.2"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@theholocron/cloudflare-client": "^1.12.0",
53
- "@theholocron/cli": "3.45.0"
53
+ "@theholocron/cli": "3.45.2"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@theholocron/cloudflare-client": {}