@theholocron/holocron-plugin-cloudflare 3.44.0 → 3.45.1

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({
@@ -226,16 +343,28 @@ function deployment(ctx) {
226
343
  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
344
  return new CloudflareDeployment(ctx.client, ctx.options.accountId);
228
345
  }
346
+ function workersCapability(ctx) {
347
+ 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");
348
+ const token = resolveToken(ctx.options);
349
+ return new CloudflareWorkers(ctx.client.zones, ctx.options.accountId, {
350
+ token,
351
+ baseUrl: ctx.options.baseUrl,
352
+ fetch: ctx.options.fetch
353
+ });
354
+ }
229
355
  function createPlugin(options = {}) {
230
356
  const ctx = createContext(options);
231
357
  return {
232
358
  name: "@theholocron/holocron-plugin-cloudflare",
233
359
  capabilities: {
234
360
  dns: () => dns(ctx),
235
- ...ctx.options.accountId ? { deployment: () => deployment(ctx) } : {}
361
+ ...ctx.options.accountId ? {
362
+ deployment: () => deployment(ctx),
363
+ workers: () => workersCapability(ctx)
364
+ } : {}
236
365
  }
237
366
  };
238
367
  }
239
368
  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>";
240
369
  //#endregion
241
- export { AUTH_HINT, AuthError, CloudflareDeployment, CloudflareDns, createCloudflareClient, createContext, createPlugin, deployment, dns, resolveToken, verifyToken };
370
+ 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.44.0",
3
+ "version": "3.45.1",
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.44.0"
49
+ "@theholocron/cli": "3.45.1"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@theholocron/cloudflare-client": "^1.12.0",
53
- "@theholocron/cli": "3.44.0"
53
+ "@theholocron/cli": "3.45.1"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@theholocron/cloudflare-client": {}