@treeseed/sdk 0.12.37 → 0.12.39

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.
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
2
3
  import { dirname, resolve } from "node:path";
3
4
  import { resolveTreeseedLaunchEnvironment } from "../operations/services/config-runtime.js";
4
5
  import { cloudflareApiRequest, resolveCloudflareZoneIdForHost, resolveConfiguredCloudflareAccountId, runWrangler } from "../operations/services/deploy.js";
@@ -161,6 +162,62 @@ function cloudflarePagesDomain(input) {
161
162
  function cloudflarePagesDeploymentUrl(projectName, branchName, environment) {
162
163
  return environment === "prod" ? `https://${projectName}.pages.dev` : `https://${branchName}.${projectName}.pages.dev`;
163
164
  }
165
+ function probeCloudflarePagesPublicUrl(url) {
166
+ if (!url) {
167
+ return { ok: false, status: null, finalUrl: null, headers: {}, error: "missing_url" };
168
+ }
169
+ const script = `
170
+ const url = process.argv[1];
171
+ try {
172
+ const response = await fetch(url, {
173
+ redirect: 'follow',
174
+ headers: {
175
+ 'user-agent': 'treeseed-hosting-verifier/1.0',
176
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
177
+ },
178
+ signal: AbortSignal.timeout(15000),
179
+ });
180
+ const headers = {};
181
+ for (const key of ['cf-cache-status', 'age', 'server', 'cache-control', 'content-type']) {
182
+ const value = response.headers.get(key);
183
+ if (value) headers[key] = value;
184
+ }
185
+ process.stdout.write(JSON.stringify({
186
+ ok: response.ok,
187
+ status: response.status,
188
+ finalUrl: response.url,
189
+ headers,
190
+ error: null,
191
+ }));
192
+ } catch (error) {
193
+ process.stdout.write(JSON.stringify({
194
+ ok: false,
195
+ status: null,
196
+ finalUrl: url,
197
+ headers: {},
198
+ error: error instanceof Error ? error.message : String(error),
199
+ }));
200
+ }
201
+ `;
202
+ const result = spawnSync(process.execPath, ["--input-type=module", "-e", script, url], {
203
+ encoding: "utf8",
204
+ timeout: 2e4
205
+ });
206
+ if (result.error) {
207
+ return { ok: false, status: null, finalUrl: url, headers: {}, error: result.error.message };
208
+ }
209
+ try {
210
+ return JSON.parse(result.stdout || "{}");
211
+ } catch {
212
+ return {
213
+ ok: false,
214
+ status: null,
215
+ finalUrl: url,
216
+ headers: {},
217
+ error: result.stderr?.trim() || result.stdout?.trim() || "invalid_probe_output"
218
+ };
219
+ }
220
+ }
164
221
  function cloudflarePagesDnsTarget(projectName, branchName, environment) {
165
222
  return environment === "prod" ? `${projectName}.pages.dev` : `${branchName}.${projectName}.pages.dev`;
166
223
  }
@@ -337,6 +394,8 @@ function createCloudflareHostAdapter() {
337
394
  const observedDomain = observedState.observedDomain ?? null;
338
395
  const observedDnsRecord = observedState.observedDnsRecord ?? null;
339
396
  const observedDeployment = observedState.observedDeployment ?? null;
397
+ const publicUrl = domain ? `https://${domain}` : projectName && branchName ? cloudflarePagesDeploymentUrl(projectName, branchName, input.environment) : null;
398
+ const publicProbe = input.environment === "prod" ? probeCloudflarePagesPublicUrl(publicUrl) : null;
340
399
  const checks = [
341
400
  {
342
401
  key: "pages-project.exists",
@@ -376,6 +435,16 @@ function createCloudflareHostAdapter() {
376
435
  issues: !domain || observedDnsRecord ? [] : [`Cloudflare DNS record ${domain} -> ${projectName && branchName ? cloudflarePagesDnsTarget(projectName, branchName, input.environment) : "(unset)"} is missing.`]
377
436
  }
378
437
  ];
438
+ if (input.environment === "prod") {
439
+ checks.push({
440
+ key: "pages-public-url.ok",
441
+ label: "Cloudflare Pages public URL responds successfully",
442
+ ok: publicProbe?.ok === true,
443
+ expected: { url: publicUrl, ok: true },
444
+ observed: publicProbe,
445
+ issues: publicProbe?.ok === true ? [] : [`Cloudflare Pages public URL ${publicUrl ?? "(unset)"} did not return a successful status.`]
446
+ });
447
+ }
379
448
  return {
380
449
  unitId: input.unit.id,
381
450
  status: checks.every((check) => check.ok) ? "ready" : "pending",
@@ -300,11 +300,17 @@ export declare function purgeSourcePageCaches(tenantRoot: any, options?: {}): {
300
300
  results: never[];
301
301
  } | {
302
302
  urls: (string | null)[];
303
- results: {
303
+ results: ({
304
304
  zoneId: any;
305
305
  count: number;
306
306
  success: boolean;
307
- }[];
307
+ errors: any;
308
+ } | {
309
+ zoneId: unknown;
310
+ count: string;
311
+ success: boolean;
312
+ errors: any;
313
+ })[];
308
314
  skipped?: undefined;
309
315
  reason?: undefined;
310
316
  };
@@ -319,6 +325,7 @@ export declare function purgePublishedContentCaches(tenantRoot: any, urls: any,
319
325
  zoneId: any;
320
326
  count: number;
321
327
  success: boolean;
328
+ errors: any;
322
329
  }[];
323
330
  skipped?: undefined;
324
331
  reason?: undefined;
@@ -1476,7 +1476,7 @@ function buildTreeseedManagedCloudflareCacheRules(deployConfig, cacheTarget, kin
1476
1476
  ];
1477
1477
  if (sourcePathExpression) {
1478
1478
  rules.push({
1479
- description: "treeseed-managed: cache source html routes",
1479
+ description: "treeseed-managed: bypass source html routes",
1480
1480
  expression: joinCloudflareAndExpression([
1481
1481
  hostExpression,
1482
1482
  pathExpression,
@@ -1485,15 +1485,7 @@ function buildTreeseedManagedCloudflareCacheRules(deployConfig, cacheTarget, kin
1485
1485
  ]),
1486
1486
  action: "set_cache_settings",
1487
1487
  action_parameters: {
1488
- cache: true,
1489
- edge_ttl: {
1490
- mode: "override_origin",
1491
- default: policy.sourcePages.edgeTtlSeconds
1492
- },
1493
- browser_ttl: {
1494
- mode: "override_origin",
1495
- default: policy.sourcePages.browserTtlSeconds
1496
- }
1488
+ cache: false
1497
1489
  },
1498
1490
  enabled: true
1499
1491
  });
@@ -1643,13 +1635,41 @@ function purgeCloudflareCacheByUrls(urls, deployConfig, { env } = {}) {
1643
1635
  body: { files: [...new Set(files)] },
1644
1636
  env
1645
1637
  });
1638
+ const errors = Array.isArray(payload?.errors) ? payload.errors.map((entry) => entry?.message ?? JSON.stringify(entry)).filter(Boolean) : [];
1646
1639
  return {
1647
1640
  zoneId,
1648
1641
  count: [...new Set(files)].length,
1649
- success: payload?.success === true
1642
+ success: payload?.success === true,
1643
+ errors
1650
1644
  };
1651
1645
  });
1652
1646
  }
1647
+ function purgeCloudflareCacheEverythingByHosts(hosts, deployConfig, { env } = {}) {
1648
+ const zoneIds = [...new Set((hosts ?? []).map((host) => typeof host === "string" ? host.trim() : "").filter(Boolean).map((host) => resolveCloudflareZoneIdForHost(deployConfig, host, env)).filter(Boolean))];
1649
+ return zoneIds.map((zoneId) => {
1650
+ const payload = cloudflareApiRequest(`/zones/${zoneId}/purge_cache`, {
1651
+ method: "POST",
1652
+ body: { purge_everything: true },
1653
+ env
1654
+ });
1655
+ const errors = Array.isArray(payload?.errors) ? payload.errors.map((entry) => entry?.message ?? JSON.stringify(entry)).filter(Boolean) : [];
1656
+ return {
1657
+ zoneId,
1658
+ count: "everything",
1659
+ success: payload?.success === true,
1660
+ errors
1661
+ };
1662
+ });
1663
+ }
1664
+ function assertCloudflareCachePurgeSucceeded(results) {
1665
+ const failures = (results ?? []).filter((result) => result?.success !== true);
1666
+ if (failures.length === 0) return;
1667
+ const detail = failures.map((result) => {
1668
+ const errors = Array.isArray(result?.errors) && result.errors.length > 0 ? `: ${result.errors.join("; ")}` : "";
1669
+ return `${result?.zoneId ?? "unknown-zone"} (${result?.count ?? 0} urls)${errors}`;
1670
+ }).join(", ");
1671
+ throw new Error(`Cloudflare cache purge did not succeed for ${detail}.`);
1672
+ }
1653
1673
  function queueName(entry) {
1654
1674
  return entry?.queue_name ?? entry?.queueName ?? entry?.name ?? null;
1655
1675
  }
@@ -1680,7 +1700,7 @@ function recordCachePurgeResult(targetState, results, error = null) {
1680
1700
  return;
1681
1701
  }
1682
1702
  targetState.lastPurgedAt = (/* @__PURE__ */ new Date()).toISOString();
1683
- targetState.purgeCount = Array.isArray(results) ? results.reduce((sum, result) => sum + (result?.count ?? 0), 0) : 0;
1703
+ targetState.purgeCount = Array.isArray(results) ? results.reduce((sum, result) => sum + (typeof result?.count === "number" ? result.count : 0), 0) : 0;
1684
1704
  targetState.lastError = null;
1685
1705
  }
1686
1706
  function resolveCloudflareCachePurgeEnv(options = {}) {
@@ -1708,9 +1728,12 @@ function purgeSourcePageCaches(tenantRoot, options = {}) {
1708
1728
  const results = purgeCloudflareCacheByUrls(urls, deployConfig, {
1709
1729
  env
1710
1730
  });
1711
- recordCachePurgeResult(state.webCache.deployPurge, results);
1731
+ const hosts = [...new Set(urls.map((url) => safeUrl(url)?.hostname).filter(Boolean))];
1732
+ const allResults = target.scope === "prod" ? purgeCloudflareCacheEverythingByHosts(hosts, deployConfig, { env }) : [];
1733
+ assertCloudflareCachePurgeSucceeded([...results, ...allResults]);
1734
+ recordCachePurgeResult(state.webCache.deployPurge, [...results, ...allResults]);
1712
1735
  writeDeployState(tenantRoot, state, { target });
1713
- return { urls, results };
1736
+ return { urls, results: [...results, ...allResults] };
1714
1737
  } catch (error) {
1715
1738
  recordCachePurgeResult(state.webCache.deployPurge, [], error);
1716
1739
  writeDeployState(tenantRoot, state, { target });
@@ -1736,6 +1759,7 @@ function purgePublishedContentCaches(tenantRoot, urls, options = {}) {
1736
1759
  const results = purgeCloudflareCacheByUrls(urls, deployConfig, {
1737
1760
  env
1738
1761
  });
1762
+ assertCloudflareCachePurgeSucceeded(results);
1739
1763
  recordCachePurgeResult(state.webCache.contentPurge, results);
1740
1764
  writeDeployState(tenantRoot, state, { target });
1741
1765
  return { urls, results };
@@ -79,12 +79,14 @@ function variableObserved(observed, key) {
79
79
  function valuePresence(values, observed, key, serviceTarget) {
80
80
  const providerPresent = variableObserved(observed, key);
81
81
  const machinePresent = hasConfiguredValue(values, key);
82
+ const providerObserved = Boolean(observed);
83
+ const present = providerObserved ? providerPresent : machinePresent;
82
84
  return {
83
85
  key,
84
86
  serviceTarget,
85
- present: providerPresent || machinePresent,
86
- source: providerPresent ? "provider" : machinePresent ? "machine-config" : "missing",
87
- observation: observed ? "provider-live" : "not-observed"
87
+ present,
88
+ source: providerPresent ? "provider" : !providerObserved && machinePresent ? "machine-config" : "missing",
89
+ observation: providerObserved ? "provider-live" : "not-observed"
88
90
  };
89
91
  }
90
92
  function serviceTypeFor(key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.37",
3
+ "version": "0.12.39",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {