@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.
package/dist/hosting/builtins.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
86
|
-
source: providerPresent ? "provider" : machinePresent ? "machine-config" : "missing",
|
|
87
|
-
observation:
|
|
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) {
|