@theholocron/holocron-plugin-cloudflare 3.63.0 → 3.64.0

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
@@ -9,7 +9,7 @@ declare class CloudflareDeployment implements Deployment {
9
9
  private readonly accountId;
10
10
  readonly key: "deployment";
11
11
  readonly providerName = "cloudflare";
12
- constructor(client: CloudflareClient, accountId: string);
12
+ constructor(client: () => CloudflareClient, accountId: string);
13
13
  listProjects(): Promise<DeploymentProject[]>;
14
14
  ensureProject(input: {
15
15
  name: string;
@@ -38,7 +38,7 @@ declare class CloudflareDns implements Dns {
38
38
  readonly key: "dns";
39
39
  readonly providerName = "cloudflare";
40
40
  private readonly zoneCache;
41
- constructor(client: CloudflareClient);
41
+ constructor(client: () => CloudflareClient);
42
42
  listRecords(domain: string): Promise<DnsRecord[]>;
43
43
  upsertRecord(domain: string, record: DnsRecord): Promise<DnsRecord>;
44
44
  deleteRecord(domain: string, id: string): Promise<void>;
@@ -69,8 +69,11 @@ declare class CloudflareWorkers implements Workers {
69
69
  private readonly zoneCache;
70
70
  private readonly baseUrl;
71
71
  private readonly fetchImpl;
72
+ /** Memoized token thunk — resolved on the first authenticated request. */
72
73
  private readonly token;
73
- constructor(zones: ZonesClient, accountId: string, opts: Pick<CloudflareClientOptions$1, "token" | "baseUrl" | "fetch">);
74
+ constructor(zones: () => ZonesClient, accountId: string, opts: {
75
+ token: () => string;
76
+ } & Pick<CloudflareClientOptions$1, "baseUrl" | "fetch">);
74
77
  upsertProxy(hostname: string, config: WikiProxyConfig): Promise<void>;
75
78
  private putScript;
76
79
  private listRoutes;
@@ -109,7 +112,10 @@ interface CloudflarePluginOptions extends ResolveTokenInput {
109
112
  }
110
113
  interface PluginContext {
111
114
  options: CloudflarePluginOptions;
112
- client: CloudflareClient;
115
+ /** Memoized client — the token is resolved on first use, not at plugin load. */
116
+ client: () => CloudflareClient;
117
+ /** Memoized token resolver — shared by the client and the Workers capability. */
118
+ token: () => string;
113
119
  }
114
120
  declare function createContext(options?: CloudflarePluginOptions): PluginContext;
115
121
  declare function dns(ctx: PluginContext): Dns;
package/dist/index.mjs CHANGED
@@ -19,26 +19,26 @@ var CloudflareDeployment = class {
19
19
  this.accountId = accountId;
20
20
  }
21
21
  async listProjects() {
22
- return (await this.client.pages.listProjects(this.accountId)).map(mapProject);
22
+ return (await this.client().pages.listProjects(this.accountId)).map(mapProject);
23
23
  }
24
24
  async ensureProject(input) {
25
25
  const existing = await this.getProjectByName(input.name);
26
26
  if (existing) return existing;
27
- return mapProject(await this.client.pages.createProject(this.accountId, {
27
+ return mapProject(await this.client().pages.createProject(this.accountId, {
28
28
  name: input.name,
29
29
  production_branch: "main"
30
30
  }));
31
31
  }
32
32
  async updateProjectSettings(projectId, settings) {
33
- return mapProject(await this.client.pages.getProject(this.accountId, projectId));
33
+ return mapProject(await this.client().pages.getProject(this.accountId, projectId));
34
34
  }
35
35
  async listEnvVars(projectId, target) {
36
- const project = await this.client.pages.getProject(this.accountId, projectId);
36
+ const project = await this.client().pages.getProject(this.accountId, projectId);
37
37
  const envConfig = target === "production" ? project.deployment_configs.production : project.deployment_configs.preview;
38
38
  return Object.keys(envConfig.env_vars);
39
39
  }
40
40
  async setEnvVar(projectId, target, name, value) {
41
- const cfg = (await this.client.pages.getProject(this.accountId, projectId)).deployment_configs;
41
+ const cfg = (await this.client().pages.getProject(this.accountId, projectId)).deployment_configs;
42
42
  const scope = target === "production" ? "production" : "preview";
43
43
  const updated = {
44
44
  ...cfg[scope].env_vars,
@@ -47,39 +47,39 @@ var CloudflareDeployment = class {
47
47
  type: "plain_text"
48
48
  }
49
49
  };
50
- await this.client.pages.updateProject(this.accountId, projectId, { deployment_configs: {
50
+ await this.client().pages.updateProject(this.accountId, projectId, { deployment_configs: {
51
51
  ...cfg,
52
52
  [scope]: { env_vars: updated }
53
53
  } });
54
54
  }
55
55
  async triggerDeployment(input) {
56
- return mapDeployment(await this.client.pages.createDeployment(this.accountId, input.projectId, input.branch), input.projectId, input.branch, input.target);
56
+ return mapDeployment(await this.client().pages.createDeployment(this.accountId, input.projectId, input.branch), input.projectId, input.branch, input.target);
57
57
  }
58
58
  async getDeployment(deploymentId) {
59
59
  const sep = deploymentId.indexOf(":");
60
60
  if (sep === -1) throw new ProviderApiError(`Invalid Cloudflare Pages deployment id "${deploymentId}" — expected "projectName:deploymentId"`, 400, void 0);
61
61
  const projectName = deploymentId.slice(0, sep);
62
62
  const cfDeployId = deploymentId.slice(sep + 1);
63
- const raw = await this.client.pages.getDeployment(this.accountId, projectName, cfDeployId);
63
+ const raw = await this.client().pages.getDeployment(this.accountId, projectName, cfDeployId);
64
64
  return mapDeployment(raw, projectName, raw.deployment_trigger.metadata.branch, void 0);
65
65
  }
66
66
  async listPreviewDeployments(projectId, branch) {
67
- return (await this.client.pages.listDeployments(this.accountId, projectId)).filter((d) => d.deployment_trigger.metadata.branch === branch).map((d) => mapDeployment(d, projectId, d.deployment_trigger.metadata.branch, void 0));
67
+ return (await this.client().pages.listDeployments(this.accountId, projectId)).filter((d) => d.deployment_trigger.metadata.branch === branch).map((d) => mapDeployment(d, projectId, d.deployment_trigger.metadata.branch, void 0));
68
68
  }
69
69
  async deletePreviewDeployments(projectId, deploymentIds) {
70
70
  await Promise.all(deploymentIds.map((id) => {
71
71
  const cfId = id.includes(":") ? id.slice(id.indexOf(":") + 1) : id;
72
- return this.client.pages.deleteDeployment(this.accountId, projectId, cfId);
72
+ return this.client().pages.deleteDeployment(this.accountId, projectId, cfId);
73
73
  }));
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;
78
- await this.client.pages.addDomain(this.accountId, projectId, hostname);
77
+ if ((await this.client().pages.listDomains(this.accountId, projectId)).some((d) => d.name === hostname)) return;
78
+ await this.client().pages.addDomain(this.accountId, projectId, hostname);
79
79
  }
80
80
  async getProjectByName(name) {
81
81
  try {
82
- return mapProject(await this.client.pages.getProject(this.accountId, name));
82
+ return mapProject(await this.client().pages.getProject(this.accountId, name));
83
83
  } catch (err) {
84
84
  if (err instanceof ProviderApiError && err.status === 404) return null;
85
85
  throw err;
@@ -125,22 +125,23 @@ var CloudflareDns = class {
125
125
  }
126
126
  async listRecords(domain) {
127
127
  const zoneId = await this.resolveZone(domain);
128
- return (await this.client.dns.list(zoneId)).map(mapRecord);
128
+ return (await this.client().dns.list(zoneId)).map(mapRecord);
129
129
  }
130
130
  async upsertRecord(domain, record) {
131
+ const client = this.client();
131
132
  const zoneId = await this.resolveZone(domain);
132
- const existing = await this.client.dns.list(zoneId, {
133
+ const existing = await client.dns.list(zoneId, {
133
134
  type: record.type,
134
135
  name: record.name
135
136
  });
136
- if (existing.length > 0) return mapRecord(await this.client.dns.update(zoneId, existing[0].id, {
137
+ if (existing.length > 0) return mapRecord(await client.dns.update(zoneId, existing[0].id, {
137
138
  type: record.type,
138
139
  name: record.name,
139
140
  content: record.content,
140
141
  ...record.ttl !== void 0 ? { ttl: record.ttl } : {},
141
142
  ...record.proxied !== void 0 ? { proxied: record.proxied } : {}
142
143
  }));
143
- return mapRecord(await this.client.dns.create(zoneId, {
144
+ return mapRecord(await client.dns.create(zoneId, {
144
145
  type: record.type,
145
146
  name: record.name,
146
147
  content: record.content,
@@ -150,7 +151,7 @@ var CloudflareDns = class {
150
151
  }
151
152
  async deleteRecord(domain, id) {
152
153
  const zoneId = await this.resolveZone(domain);
153
- await this.client.dns.delete(zoneId, id);
154
+ await this.client().dns.delete(zoneId, id);
154
155
  }
155
156
  /**
156
157
  * Walk from full domain up to apex to find the Cloudflare zone.
@@ -165,7 +166,7 @@ var CloudflareDns = class {
165
166
  const parts = domain.split(".");
166
167
  for (let i = 0; i < parts.length - 1; i++) {
167
168
  const candidate = parts.slice(i).join(".");
168
- const zones = await this.client.zones.list({ name: candidate });
169
+ const zones = await this.client().zones.list({ name: candidate });
169
170
  if (zones.length > 0) {
170
171
  this.zoneCache.set(domain, zones[0].id);
171
172
  return zones[0].id;
@@ -193,6 +194,7 @@ var CloudflareWorkers = class {
193
194
  zoneCache = /* @__PURE__ */ new Map();
194
195
  baseUrl;
195
196
  fetchImpl;
197
+ /** Memoized token thunk — resolved on the first authenticated request. */
196
198
  token;
197
199
  constructor(zones, accountId, opts) {
198
200
  this.zones = zones;
@@ -222,7 +224,7 @@ var CloudflareWorkers = class {
222
224
  const path = `/accounts/${this.accountId}/workers/scripts/${encodeURIComponent(scriptName)}`;
223
225
  const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
224
226
  method: "PUT",
225
- headers: { authorization: `Bearer ${this.token}` },
227
+ headers: { authorization: `Bearer ${this.token()}` },
226
228
  body: form
227
229
  });
228
230
  if (!res.ok) {
@@ -247,7 +249,7 @@ var CloudflareWorkers = class {
247
249
  }
248
250
  async cfRequest(method, path, body) {
249
251
  const headers = {
250
- authorization: `Bearer ${this.token}`,
252
+ authorization: `Bearer ${this.token()}`,
251
253
  accept: "application/json"
252
254
  };
253
255
  const init = {
@@ -273,7 +275,7 @@ var CloudflareWorkers = class {
273
275
  const parts = domain.split(".");
274
276
  for (let i = 0; i < parts.length - 1; i++) {
275
277
  const candidate = parts.slice(i).join(".");
276
- const zones = await this.zones.list({ name: candidate });
278
+ const zones = await this.zones().list({ name: candidate });
277
279
  if (zones.length > 0) {
278
280
  this.zoneCache.set(domain, zones[0].id);
279
281
  return zones[0].id;
@@ -330,17 +332,21 @@ async function verifyToken(token, opts = {}) {
330
332
  //#endregion
331
333
  //#region src/index.ts
332
334
  function createContext(options = {}) {
333
- const token = resolveToken(options);
334
335
  const accountId = options.accountId ?? process.env.CLOUDFLARE_ACCOUNT_ID;
336
+ const opts = {
337
+ ...options,
338
+ accountId
339
+ };
340
+ let token;
341
+ let client;
342
+ const resolve = () => token ??= resolveToken(opts);
335
343
  return {
336
- options: {
337
- ...options,
338
- accountId
339
- },
340
- client: createCloudflareClient({
341
- token,
342
- baseUrl: options.baseUrl,
343
- fetch: options.fetch
344
+ options: opts,
345
+ token: resolve,
346
+ client: () => client ??= createCloudflareClient({
347
+ token: resolve(),
348
+ baseUrl: opts.baseUrl,
349
+ fetch: opts.fetch
344
350
  })
345
351
  };
346
352
  }
@@ -353,9 +359,8 @@ function deployment(ctx) {
353
359
  }
354
360
  function workersCapability(ctx) {
355
361
  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");
356
- const token = resolveToken(ctx.options);
357
- return new CloudflareWorkers(ctx.client.zones, ctx.options.accountId, {
358
- token,
362
+ return new CloudflareWorkers(() => ctx.client().zones, ctx.options.accountId, {
363
+ token: ctx.token,
359
364
  baseUrl: ctx.options.baseUrl,
360
365
  fetch: ctx.options.fetch
361
366
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-cloudflare",
3
- "version": "3.63.0",
3
+ "version": "3.64.0",
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",
@@ -46,11 +46,11 @@
46
46
  "tsx": "4.23.12",
47
47
  "typescript": "^5.9.3",
48
48
  "vitest": "^4.1.11",
49
- "@theholocron/cli": "3.63.0"
49
+ "@theholocron/cli": "3.64.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@theholocron/cloudflare-client": "^1.14.2",
53
- "@theholocron/cli": "3.63.0"
53
+ "@theholocron/cli": "3.64.0"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@theholocron/cloudflare-client": {}