@forgezero/agent 0.1.57 → 0.1.58
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/README.md +6 -5
- package/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap.js +65 -4
- package/dist/cli/cloudflare-bootstrap.d.ts +10 -0
- package/dist/cloudflare-bootstrap.js +64 -3
- package/dist/cloudflare-edge.d.ts +18 -0
- package/dist/cloudflare-edge.js +65 -3
- package/dist/fz-agent.js +1 -1
- package/dist/fz.js +93 -15
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +65 -4
- package/dist/platform-fleet-verification.js +1 -1
- package/dist/provision.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -359,13 +359,14 @@ import type { CloudflareBootstrapTokens, CloudflareConnectorHandoff, CloudflareH
|
|
|
359
359
|
<a id="forgezero-agent-cloudflare-edge"></a>
|
|
360
360
|
## @forgezero/agent/cloudflare-edge
|
|
361
361
|
|
|
362
|
-
Strict Cloudflare REST operations used by the attended bootstrap controller. Named value imports: configureCloudflareEdge, configureCloudflareRealtimeSecrets, ensureCloudflareMeshConnector, ensureCloudflarePrivateDatabaseRoute, ensureCloudflarePrivateRoute, ensureCloudflareTunnel, ensureCloudflareWarpDatabaseInclude, ensureCloudflareWarpNetworkIncludes, removeCloudflarePrivateDatabaseRoute, removeCloudflarePrivateRoute, removeCloudflareWarpDatabaseInclude, verifyCloudflareWorkerDurableObjects. Named type imports: CloudflareDurableObjectNamespace, CloudflareEdgeConfig, CloudflareMeshConnector, CloudflarePrivateRoute, CloudflarePrivateRouteConfig, CloudflareTunnel, CloudflareWarpIncludeConfig. Import only the names used by this file.
|
|
362
|
+
Strict Cloudflare REST operations used by the attended bootstrap controller. Named value imports: configureCloudflareEdge, configureCloudflareRealtimeSecrets, discoverCloudflareBootstrapResources, ensureCloudflareMeshConnector, ensureCloudflarePrivateDatabaseRoute, ensureCloudflarePrivateRoute, ensureCloudflareTunnel, ensureCloudflareWarpDatabaseInclude, ensureCloudflareWarpNetworkIncludes, removeCloudflarePrivateDatabaseRoute, removeCloudflarePrivateRoute, removeCloudflareWarpDatabaseInclude, verifyCloudflareWorkerDurableObjects. Named type imports: CloudflareBootstrapDiscovery, CloudflareDurableObjectNamespace, CloudflareEdgeConfig, CloudflareMeshConnector, CloudflarePrivateRoute, CloudflarePrivateRouteConfig, CloudflareTunnel, CloudflareWarpIncludeConfig. Import only the names used by this file.
|
|
363
363
|
|
|
364
364
|
```text
|
|
365
|
-
import { configureCloudflareEdge, configureCloudflareRealtimeSecrets, ensureCloudflareMeshConnector, ensureCloudflarePrivateDatabaseRoute, ensureCloudflarePrivateRoute
|
|
366
|
-
import { ensureCloudflareWarpDatabaseInclude, ensureCloudflareWarpNetworkIncludes, removeCloudflarePrivateDatabaseRoute, removeCloudflarePrivateRoute, removeCloudflareWarpDatabaseInclude
|
|
367
|
-
import
|
|
368
|
-
import type {
|
|
365
|
+
import { configureCloudflareEdge, configureCloudflareRealtimeSecrets, discoverCloudflareBootstrapResources, ensureCloudflareMeshConnector, ensureCloudflarePrivateDatabaseRoute, ensureCloudflarePrivateRoute } from '@forgezero/agent/cloudflare-edge';
|
|
366
|
+
import { ensureCloudflareTunnel, ensureCloudflareWarpDatabaseInclude, ensureCloudflareWarpNetworkIncludes, removeCloudflarePrivateDatabaseRoute, removeCloudflarePrivateRoute, removeCloudflareWarpDatabaseInclude } from '@forgezero/agent/cloudflare-edge';
|
|
367
|
+
import { verifyCloudflareWorkerDurableObjects } from '@forgezero/agent/cloudflare-edge';
|
|
368
|
+
import type { CloudflareBootstrapDiscovery, CloudflareDurableObjectNamespace, CloudflareEdgeConfig, CloudflareMeshConnector, CloudflarePrivateRoute, CloudflarePrivateRouteConfig } from '@forgezero/agent/cloudflare-edge';
|
|
369
|
+
import type { CloudflareTunnel, CloudflareWarpIncludeConfig } from '@forgezero/agent/cloudflare-edge';
|
|
369
370
|
```
|
|
370
371
|
|
|
371
372
|
<a id="forgezero-agent-mesh-connector"></a>
|
package/dist/agent-heartbeat.js
CHANGED
package/dist/bootstrap.js
CHANGED
|
@@ -22,7 +22,7 @@ function privateDatabaseHostRoute(value) {
|
|
|
22
22
|
return `${address}/${isIP(address) === 4 ? 32 : 128}`;
|
|
23
23
|
}
|
|
24
24
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
25
|
-
async function
|
|
25
|
+
async function cfEnvelope(config, path, init = {}, fetcher = fetch) {
|
|
26
26
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
27
27
|
...init,
|
|
28
28
|
headers: {
|
|
@@ -35,7 +35,68 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
35
35
|
if (!response.ok || body.success !== true) {
|
|
36
36
|
throw new Error(body.errors?.map(({ message }) => message).filter(Boolean).join("; ") || `Cloudflare returned HTTP ${response.status}`);
|
|
37
37
|
}
|
|
38
|
-
return body
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
41
|
+
return (await cfEnvelope(config, path, init, fetcher)).result;
|
|
42
|
+
}
|
|
43
|
+
async function cfPages(config, path, perPage, fetcher) {
|
|
44
|
+
const output = [];
|
|
45
|
+
for (let page = 1;page <= 100; page += 1) {
|
|
46
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
47
|
+
const envelope = await cfEnvelope(config, `${path}${separator}page=${page}&per_page=${perPage}`, {}, fetcher);
|
|
48
|
+
const result = Array.isArray(envelope.result) ? envelope.result : [];
|
|
49
|
+
output.push(...result);
|
|
50
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
51
|
+
if (Number.isInteger(totalPages) ? page >= totalPages : result.length < perPage)
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
throw new Error("Cloudflare pagination exceeded the reviewed 100-page bound");
|
|
55
|
+
}
|
|
56
|
+
var exactHexId = (value, label) => {
|
|
57
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
58
|
+
if (!/^[a-f0-9]{32}$/.test(normalized))
|
|
59
|
+
throw new Error(`${label} is malformed`);
|
|
60
|
+
return normalized;
|
|
61
|
+
};
|
|
62
|
+
async function discoverCloudflareBootstrapResources(config, fetcher = fetch) {
|
|
63
|
+
const zoneName = config.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
64
|
+
const kvNamespaceTitle = config.kvNamespaceTitle.trim();
|
|
65
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
66
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
67
|
+
}
|
|
68
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
69
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
70
|
+
}
|
|
71
|
+
const [managementStatus, runtimeStatus] = await Promise.all([
|
|
72
|
+
cf({ apiToken: config.tunnelToken }, "/user/tokens/verify", {}, fetcher),
|
|
73
|
+
cf({ apiToken: config.apiToken }, "/user/tokens/verify", {}, fetcher)
|
|
74
|
+
]);
|
|
75
|
+
if (managementStatus.status !== "active")
|
|
76
|
+
throw new Error("CF_TUNNEL_TOKEN is not active");
|
|
77
|
+
if (runtimeStatus.status !== "active")
|
|
78
|
+
throw new Error("CF_API_TOKEN is not active");
|
|
79
|
+
const zones = await cfPages({ apiToken: config.tunnelToken }, `/zones?name=${encodeURIComponent(zoneName)}&match=all&status=active`, 50, fetcher);
|
|
80
|
+
const matchingZones = zones.filter(({ name }) => name?.trim().toLowerCase() === zoneName);
|
|
81
|
+
if (matchingZones.length !== 1) {
|
|
82
|
+
throw new Error(`Cloudflare zone ${zoneName} must resolve to exactly one active zone`);
|
|
83
|
+
}
|
|
84
|
+
const zoneId = exactHexId(matchingZones[0].id, "Cloudflare zone id");
|
|
85
|
+
const accountId = exactHexId(matchingZones[0].account?.id, "Cloudflare account id");
|
|
86
|
+
const namespaces = await cfPages({ apiToken: config.apiToken }, `/accounts/${accountId}/storage/kv/namespaces?order=title&direction=asc`, 1000, fetcher);
|
|
87
|
+
const matchingNamespaces = namespaces.filter(({ title }) => title === kvNamespaceTitle);
|
|
88
|
+
if (matchingNamespaces.length !== 1) {
|
|
89
|
+
throw new Error(`Cloudflare KV namespace ${kvNamespaceTitle} must resolve to exactly one namespace`);
|
|
90
|
+
}
|
|
91
|
+
const kvNamespaceId = exactHexId(matchingNamespaces[0].id, "Cloudflare KV namespace id");
|
|
92
|
+
if (config.workerScriptName) {
|
|
93
|
+
await verifyCloudflareWorkerDurableObjects({
|
|
94
|
+
accountId,
|
|
95
|
+
scriptName: config.workerScriptName,
|
|
96
|
+
apiToken: config.apiToken
|
|
97
|
+
}, fetcher);
|
|
98
|
+
}
|
|
99
|
+
return { accountId, zoneId, kvNamespaceId };
|
|
39
100
|
}
|
|
40
101
|
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
41
102
|
const [address, prefixText, ...extra] = config.network.split("/");
|
|
@@ -211,7 +272,7 @@ async function verifyCloudflareWorkerDurableObjects(config, fetcher = fetch) {
|
|
|
211
272
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(config.scriptName)) {
|
|
212
273
|
throw new Error("Cloudflare Worker script name is invalid");
|
|
213
274
|
}
|
|
214
|
-
const namespaces = await
|
|
275
|
+
const namespaces = await cfPages(config, `/accounts/${config.accountId}/workers/durable_objects/namespaces`, 1000, fetcher);
|
|
215
276
|
const owned = namespaces.filter(({ script }) => script === config.scriptName);
|
|
216
277
|
if (!owned.length)
|
|
217
278
|
throw new Error(`Cloudflare Worker ${config.scriptName} has no Durable Object namespace`);
|
|
@@ -1207,7 +1268,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1207
1268
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1208
1269
|
|
|
1209
1270
|
// src/version.ts
|
|
1210
|
-
var VERSION = "0.1.
|
|
1271
|
+
var VERSION = "0.1.58";
|
|
1211
1272
|
|
|
1212
1273
|
// src/software.ts
|
|
1213
1274
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokenFiles } from '../cloudflare-bootstrap';
|
|
2
|
+
import { discoverCloudflareBootstrapResources, type CloudflareBootstrapDiscovery } from '../cloudflare-edge';
|
|
2
3
|
export interface CloudflareBootstrapCommandConfig {
|
|
3
4
|
format: 1;
|
|
4
5
|
kind: 'forgezero-cloudflare-bootstrap-request';
|
|
@@ -10,6 +11,15 @@ export interface CloudflareBootstrapCommandDependencies {
|
|
|
10
11
|
run?: CloudflareBootstrapPhaseRunner;
|
|
11
12
|
write?: (text: string) => void;
|
|
12
13
|
}
|
|
14
|
+
export declare function discoverCloudflareBootstrapCommandResources(input: {
|
|
15
|
+
zoneName: string;
|
|
16
|
+
kvNamespaceTitle: string;
|
|
17
|
+
workerScriptName?: string;
|
|
18
|
+
tokenFiles: CloudflareBootstrapTokenFiles;
|
|
19
|
+
}, dependencies?: {
|
|
20
|
+
fetcher?: typeof fetch;
|
|
21
|
+
discover?: typeof discoverCloudflareBootstrapResources;
|
|
22
|
+
}): Promise<CloudflareBootstrapDiscovery>;
|
|
13
23
|
export interface CloudflareBootstrapFinalizeConfig {
|
|
14
24
|
format: 1;
|
|
15
25
|
kind: 'forgezero-cloudflare-bootstrap-finalize';
|
|
@@ -22,7 +22,7 @@ function privateDatabaseHostRoute(value) {
|
|
|
22
22
|
return `${address}/${isIP(address) === 4 ? 32 : 128}`;
|
|
23
23
|
}
|
|
24
24
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
25
|
-
async function
|
|
25
|
+
async function cfEnvelope(config, path, init = {}, fetcher = fetch) {
|
|
26
26
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
27
27
|
...init,
|
|
28
28
|
headers: {
|
|
@@ -35,7 +35,68 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
35
35
|
if (!response.ok || body.success !== true) {
|
|
36
36
|
throw new Error(body.errors?.map(({ message }) => message).filter(Boolean).join("; ") || `Cloudflare returned HTTP ${response.status}`);
|
|
37
37
|
}
|
|
38
|
-
return body
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
41
|
+
return (await cfEnvelope(config, path, init, fetcher)).result;
|
|
42
|
+
}
|
|
43
|
+
async function cfPages(config, path, perPage, fetcher) {
|
|
44
|
+
const output = [];
|
|
45
|
+
for (let page = 1;page <= 100; page += 1) {
|
|
46
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
47
|
+
const envelope = await cfEnvelope(config, `${path}${separator}page=${page}&per_page=${perPage}`, {}, fetcher);
|
|
48
|
+
const result = Array.isArray(envelope.result) ? envelope.result : [];
|
|
49
|
+
output.push(...result);
|
|
50
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
51
|
+
if (Number.isInteger(totalPages) ? page >= totalPages : result.length < perPage)
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
throw new Error("Cloudflare pagination exceeded the reviewed 100-page bound");
|
|
55
|
+
}
|
|
56
|
+
var exactHexId = (value, label) => {
|
|
57
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
58
|
+
if (!/^[a-f0-9]{32}$/.test(normalized))
|
|
59
|
+
throw new Error(`${label} is malformed`);
|
|
60
|
+
return normalized;
|
|
61
|
+
};
|
|
62
|
+
async function discoverCloudflareBootstrapResources(config, fetcher = fetch) {
|
|
63
|
+
const zoneName = config.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
64
|
+
const kvNamespaceTitle = config.kvNamespaceTitle.trim();
|
|
65
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
66
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
67
|
+
}
|
|
68
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
69
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
70
|
+
}
|
|
71
|
+
const [managementStatus, runtimeStatus] = await Promise.all([
|
|
72
|
+
cf({ apiToken: config.tunnelToken }, "/user/tokens/verify", {}, fetcher),
|
|
73
|
+
cf({ apiToken: config.apiToken }, "/user/tokens/verify", {}, fetcher)
|
|
74
|
+
]);
|
|
75
|
+
if (managementStatus.status !== "active")
|
|
76
|
+
throw new Error("CF_TUNNEL_TOKEN is not active");
|
|
77
|
+
if (runtimeStatus.status !== "active")
|
|
78
|
+
throw new Error("CF_API_TOKEN is not active");
|
|
79
|
+
const zones = await cfPages({ apiToken: config.tunnelToken }, `/zones?name=${encodeURIComponent(zoneName)}&match=all&status=active`, 50, fetcher);
|
|
80
|
+
const matchingZones = zones.filter(({ name }) => name?.trim().toLowerCase() === zoneName);
|
|
81
|
+
if (matchingZones.length !== 1) {
|
|
82
|
+
throw new Error(`Cloudflare zone ${zoneName} must resolve to exactly one active zone`);
|
|
83
|
+
}
|
|
84
|
+
const zoneId = exactHexId(matchingZones[0].id, "Cloudflare zone id");
|
|
85
|
+
const accountId = exactHexId(matchingZones[0].account?.id, "Cloudflare account id");
|
|
86
|
+
const namespaces = await cfPages({ apiToken: config.apiToken }, `/accounts/${accountId}/storage/kv/namespaces?order=title&direction=asc`, 1000, fetcher);
|
|
87
|
+
const matchingNamespaces = namespaces.filter(({ title }) => title === kvNamespaceTitle);
|
|
88
|
+
if (matchingNamespaces.length !== 1) {
|
|
89
|
+
throw new Error(`Cloudflare KV namespace ${kvNamespaceTitle} must resolve to exactly one namespace`);
|
|
90
|
+
}
|
|
91
|
+
const kvNamespaceId = exactHexId(matchingNamespaces[0].id, "Cloudflare KV namespace id");
|
|
92
|
+
if (config.workerScriptName) {
|
|
93
|
+
await verifyCloudflareWorkerDurableObjects({
|
|
94
|
+
accountId,
|
|
95
|
+
scriptName: config.workerScriptName,
|
|
96
|
+
apiToken: config.apiToken
|
|
97
|
+
}, fetcher);
|
|
98
|
+
}
|
|
99
|
+
return { accountId, zoneId, kvNamespaceId };
|
|
39
100
|
}
|
|
40
101
|
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
41
102
|
const [address, prefixText, ...extra] = config.network.split("/");
|
|
@@ -211,7 +272,7 @@ async function verifyCloudflareWorkerDurableObjects(config, fetcher = fetch) {
|
|
|
211
272
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(config.scriptName)) {
|
|
212
273
|
throw new Error("Cloudflare Worker script name is invalid");
|
|
213
274
|
}
|
|
214
|
-
const namespaces = await
|
|
275
|
+
const namespaces = await cfPages(config, `/accounts/${config.accountId}/workers/durable_objects/namespaces`, 1000, fetcher);
|
|
215
276
|
const owned = namespaces.filter(({ script }) => script === config.scriptName);
|
|
216
277
|
if (!owned.length)
|
|
217
278
|
throw new Error(`Cloudflare Worker ${config.scriptName} has no Durable Object namespace`);
|
|
@@ -7,6 +7,24 @@ export interface CloudflareEdgeConfig {
|
|
|
7
7
|
/** The single attended management capability used for both Tunnel and DNS writes. */
|
|
8
8
|
apiToken: string;
|
|
9
9
|
}
|
|
10
|
+
export interface CloudflareBootstrapDiscovery {
|
|
11
|
+
accountId: string;
|
|
12
|
+
zoneId: string;
|
|
13
|
+
kvNamespaceId: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve stable Cloudflare ids from owner-recognizable names without ever
|
|
17
|
+
* persisting or printing either token. The management token is used only for
|
|
18
|
+
* the exact zone lookup; the runtime token is used only for the existing KV
|
|
19
|
+
* namespace and optional Worker/DO ownership proof.
|
|
20
|
+
*/
|
|
21
|
+
export declare function discoverCloudflareBootstrapResources(config: {
|
|
22
|
+
zoneName: string;
|
|
23
|
+
kvNamespaceTitle: string;
|
|
24
|
+
workerScriptName?: string;
|
|
25
|
+
tunnelToken: string;
|
|
26
|
+
apiToken: string;
|
|
27
|
+
}, fetcher?: typeof fetch): Promise<CloudflareBootstrapDiscovery>;
|
|
10
28
|
export interface CloudflarePrivateRouteConfig {
|
|
11
29
|
accountId: string;
|
|
12
30
|
tunnelId: string;
|
package/dist/cloudflare-edge.js
CHANGED
|
@@ -22,7 +22,7 @@ function privateDatabaseHostRoute(value) {
|
|
|
22
22
|
return `${address}/${isIP(address) === 4 ? 32 : 128}`;
|
|
23
23
|
}
|
|
24
24
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
25
|
-
async function
|
|
25
|
+
async function cfEnvelope(config, path, init = {}, fetcher = fetch) {
|
|
26
26
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
27
27
|
...init,
|
|
28
28
|
headers: {
|
|
@@ -35,7 +35,68 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
35
35
|
if (!response.ok || body.success !== true) {
|
|
36
36
|
throw new Error(body.errors?.map(({ message }) => message).filter(Boolean).join("; ") || `Cloudflare returned HTTP ${response.status}`);
|
|
37
37
|
}
|
|
38
|
-
return body
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
41
|
+
return (await cfEnvelope(config, path, init, fetcher)).result;
|
|
42
|
+
}
|
|
43
|
+
async function cfPages(config, path, perPage, fetcher) {
|
|
44
|
+
const output = [];
|
|
45
|
+
for (let page = 1;page <= 100; page += 1) {
|
|
46
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
47
|
+
const envelope = await cfEnvelope(config, `${path}${separator}page=${page}&per_page=${perPage}`, {}, fetcher);
|
|
48
|
+
const result = Array.isArray(envelope.result) ? envelope.result : [];
|
|
49
|
+
output.push(...result);
|
|
50
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
51
|
+
if (Number.isInteger(totalPages) ? page >= totalPages : result.length < perPage)
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
throw new Error("Cloudflare pagination exceeded the reviewed 100-page bound");
|
|
55
|
+
}
|
|
56
|
+
var exactHexId = (value, label) => {
|
|
57
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
58
|
+
if (!/^[a-f0-9]{32}$/.test(normalized))
|
|
59
|
+
throw new Error(`${label} is malformed`);
|
|
60
|
+
return normalized;
|
|
61
|
+
};
|
|
62
|
+
async function discoverCloudflareBootstrapResources(config, fetcher = fetch) {
|
|
63
|
+
const zoneName = config.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
64
|
+
const kvNamespaceTitle = config.kvNamespaceTitle.trim();
|
|
65
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
66
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
67
|
+
}
|
|
68
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
69
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
70
|
+
}
|
|
71
|
+
const [managementStatus, runtimeStatus] = await Promise.all([
|
|
72
|
+
cf({ apiToken: config.tunnelToken }, "/user/tokens/verify", {}, fetcher),
|
|
73
|
+
cf({ apiToken: config.apiToken }, "/user/tokens/verify", {}, fetcher)
|
|
74
|
+
]);
|
|
75
|
+
if (managementStatus.status !== "active")
|
|
76
|
+
throw new Error("CF_TUNNEL_TOKEN is not active");
|
|
77
|
+
if (runtimeStatus.status !== "active")
|
|
78
|
+
throw new Error("CF_API_TOKEN is not active");
|
|
79
|
+
const zones = await cfPages({ apiToken: config.tunnelToken }, `/zones?name=${encodeURIComponent(zoneName)}&match=all&status=active`, 50, fetcher);
|
|
80
|
+
const matchingZones = zones.filter(({ name }) => name?.trim().toLowerCase() === zoneName);
|
|
81
|
+
if (matchingZones.length !== 1) {
|
|
82
|
+
throw new Error(`Cloudflare zone ${zoneName} must resolve to exactly one active zone`);
|
|
83
|
+
}
|
|
84
|
+
const zoneId = exactHexId(matchingZones[0].id, "Cloudflare zone id");
|
|
85
|
+
const accountId = exactHexId(matchingZones[0].account?.id, "Cloudflare account id");
|
|
86
|
+
const namespaces = await cfPages({ apiToken: config.apiToken }, `/accounts/${accountId}/storage/kv/namespaces?order=title&direction=asc`, 1000, fetcher);
|
|
87
|
+
const matchingNamespaces = namespaces.filter(({ title }) => title === kvNamespaceTitle);
|
|
88
|
+
if (matchingNamespaces.length !== 1) {
|
|
89
|
+
throw new Error(`Cloudflare KV namespace ${kvNamespaceTitle} must resolve to exactly one namespace`);
|
|
90
|
+
}
|
|
91
|
+
const kvNamespaceId = exactHexId(matchingNamespaces[0].id, "Cloudflare KV namespace id");
|
|
92
|
+
if (config.workerScriptName) {
|
|
93
|
+
await verifyCloudflareWorkerDurableObjects({
|
|
94
|
+
accountId,
|
|
95
|
+
scriptName: config.workerScriptName,
|
|
96
|
+
apiToken: config.apiToken
|
|
97
|
+
}, fetcher);
|
|
98
|
+
}
|
|
99
|
+
return { accountId, zoneId, kvNamespaceId };
|
|
39
100
|
}
|
|
40
101
|
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
41
102
|
const [address, prefixText, ...extra] = config.network.split("/");
|
|
@@ -211,7 +272,7 @@ async function verifyCloudflareWorkerDurableObjects(config, fetcher = fetch) {
|
|
|
211
272
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(config.scriptName)) {
|
|
212
273
|
throw new Error("Cloudflare Worker script name is invalid");
|
|
213
274
|
}
|
|
214
|
-
const namespaces = await
|
|
275
|
+
const namespaces = await cfPages(config, `/accounts/${config.accountId}/workers/durable_objects/namespaces`, 1000, fetcher);
|
|
215
276
|
const owned = namespaces.filter(({ script }) => script === config.scriptName);
|
|
216
277
|
if (!owned.length)
|
|
217
278
|
throw new Error(`Cloudflare Worker ${config.scriptName} has no Durable Object namespace`);
|
|
@@ -294,6 +355,7 @@ export {
|
|
|
294
355
|
ensureCloudflarePrivateRoute,
|
|
295
356
|
ensureCloudflarePrivateDatabaseRoute,
|
|
296
357
|
ensureCloudflareMeshConnector,
|
|
358
|
+
discoverCloudflareBootstrapResources,
|
|
297
359
|
configureCloudflareRealtimeSecrets,
|
|
298
360
|
configureCloudflareEdge
|
|
299
361
|
};
|
package/dist/fz-agent.js
CHANGED
package/dist/fz.js
CHANGED
|
@@ -4830,7 +4830,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
4830
4830
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
4831
4831
|
|
|
4832
4832
|
// src/version.ts
|
|
4833
|
-
var VERSION2 = "0.1.
|
|
4833
|
+
var VERSION2 = "0.1.58";
|
|
4834
4834
|
|
|
4835
4835
|
// src/software.ts
|
|
4836
4836
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -11958,7 +11958,7 @@ function isPrivateDatabaseAddress(value) {
|
|
|
11958
11958
|
return false;
|
|
11959
11959
|
}
|
|
11960
11960
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
11961
|
-
async function
|
|
11961
|
+
async function cfEnvelope(config, path, init = {}, fetcher = fetch) {
|
|
11962
11962
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
11963
11963
|
...init,
|
|
11964
11964
|
headers: {
|
|
@@ -11971,7 +11971,68 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
11971
11971
|
if (!response.ok || body.success !== true) {
|
|
11972
11972
|
throw new Error(body.errors?.map(({ message }) => message).filter(Boolean).join("; ") || `Cloudflare returned HTTP ${response.status}`);
|
|
11973
11973
|
}
|
|
11974
|
-
return body
|
|
11974
|
+
return body;
|
|
11975
|
+
}
|
|
11976
|
+
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
11977
|
+
return (await cfEnvelope(config, path, init, fetcher)).result;
|
|
11978
|
+
}
|
|
11979
|
+
async function cfPages(config, path, perPage, fetcher) {
|
|
11980
|
+
const output = [];
|
|
11981
|
+
for (let page = 1;page <= 100; page += 1) {
|
|
11982
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
11983
|
+
const envelope = await cfEnvelope(config, `${path}${separator}page=${page}&per_page=${perPage}`, {}, fetcher);
|
|
11984
|
+
const result = Array.isArray(envelope.result) ? envelope.result : [];
|
|
11985
|
+
output.push(...result);
|
|
11986
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
11987
|
+
if (Number.isInteger(totalPages) ? page >= totalPages : result.length < perPage)
|
|
11988
|
+
return output;
|
|
11989
|
+
}
|
|
11990
|
+
throw new Error("Cloudflare pagination exceeded the reviewed 100-page bound");
|
|
11991
|
+
}
|
|
11992
|
+
var exactHexId = (value, label) => {
|
|
11993
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
11994
|
+
if (!/^[a-f0-9]{32}$/.test(normalized))
|
|
11995
|
+
throw new Error(`${label} is malformed`);
|
|
11996
|
+
return normalized;
|
|
11997
|
+
};
|
|
11998
|
+
async function discoverCloudflareBootstrapResources(config, fetcher = fetch) {
|
|
11999
|
+
const zoneName = config.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
12000
|
+
const kvNamespaceTitle = config.kvNamespaceTitle.trim();
|
|
12001
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
12002
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
12003
|
+
}
|
|
12004
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
12005
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
12006
|
+
}
|
|
12007
|
+
const [managementStatus, runtimeStatus] = await Promise.all([
|
|
12008
|
+
cf({ apiToken: config.tunnelToken }, "/user/tokens/verify", {}, fetcher),
|
|
12009
|
+
cf({ apiToken: config.apiToken }, "/user/tokens/verify", {}, fetcher)
|
|
12010
|
+
]);
|
|
12011
|
+
if (managementStatus.status !== "active")
|
|
12012
|
+
throw new Error("CF_TUNNEL_TOKEN is not active");
|
|
12013
|
+
if (runtimeStatus.status !== "active")
|
|
12014
|
+
throw new Error("CF_API_TOKEN is not active");
|
|
12015
|
+
const zones = await cfPages({ apiToken: config.tunnelToken }, `/zones?name=${encodeURIComponent(zoneName)}&match=all&status=active`, 50, fetcher);
|
|
12016
|
+
const matchingZones = zones.filter(({ name }) => name?.trim().toLowerCase() === zoneName);
|
|
12017
|
+
if (matchingZones.length !== 1) {
|
|
12018
|
+
throw new Error(`Cloudflare zone ${zoneName} must resolve to exactly one active zone`);
|
|
12019
|
+
}
|
|
12020
|
+
const zoneId = exactHexId(matchingZones[0].id, "Cloudflare zone id");
|
|
12021
|
+
const accountId = exactHexId(matchingZones[0].account?.id, "Cloudflare account id");
|
|
12022
|
+
const namespaces = await cfPages({ apiToken: config.apiToken }, `/accounts/${accountId}/storage/kv/namespaces?order=title&direction=asc`, 1000, fetcher);
|
|
12023
|
+
const matchingNamespaces = namespaces.filter(({ title }) => title === kvNamespaceTitle);
|
|
12024
|
+
if (matchingNamespaces.length !== 1) {
|
|
12025
|
+
throw new Error(`Cloudflare KV namespace ${kvNamespaceTitle} must resolve to exactly one namespace`);
|
|
12026
|
+
}
|
|
12027
|
+
const kvNamespaceId = exactHexId(matchingNamespaces[0].id, "Cloudflare KV namespace id");
|
|
12028
|
+
if (config.workerScriptName) {
|
|
12029
|
+
await verifyCloudflareWorkerDurableObjects({
|
|
12030
|
+
accountId,
|
|
12031
|
+
scriptName: config.workerScriptName,
|
|
12032
|
+
apiToken: config.apiToken
|
|
12033
|
+
}, fetcher);
|
|
12034
|
+
}
|
|
12035
|
+
return { accountId, zoneId, kvNamespaceId };
|
|
11975
12036
|
}
|
|
11976
12037
|
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
11977
12038
|
const [address, prefixText, ...extra] = config.network.split("/");
|
|
@@ -12085,7 +12146,7 @@ async function verifyCloudflareWorkerDurableObjects(config, fetcher = fetch) {
|
|
|
12085
12146
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(config.scriptName)) {
|
|
12086
12147
|
throw new Error("Cloudflare Worker script name is invalid");
|
|
12087
12148
|
}
|
|
12088
|
-
const namespaces = await
|
|
12149
|
+
const namespaces = await cfPages(config, `/accounts/${config.accountId}/workers/durable_objects/namespaces`, 1000, fetcher);
|
|
12089
12150
|
const owned = namespaces.filter(({ script }) => script === config.scriptName);
|
|
12090
12151
|
if (!owned.length)
|
|
12091
12152
|
throw new Error(`Cloudflare Worker ${config.scriptName} has no Durable Object namespace`);
|
|
@@ -14337,6 +14398,15 @@ function localBootstrapHost() {
|
|
|
14337
14398
|
// src/cli/cloudflare-bootstrap.ts
|
|
14338
14399
|
import { constants as constants2, closeSync, fstatSync, openSync, readFileSync as readFileSync9 } from "fs";
|
|
14339
14400
|
import { dirname as dirname9, resolve as resolve6 } from "path";
|
|
14401
|
+
async function discoverCloudflareBootstrapCommandResources(input, dependencies = {}) {
|
|
14402
|
+
const tokens = await readCloudflareBootstrapTokens(input.tokenFiles);
|
|
14403
|
+
return (dependencies.discover ?? discoverCloudflareBootstrapResources)({
|
|
14404
|
+
zoneName: input.zoneName,
|
|
14405
|
+
kvNamespaceTitle: input.kvNamespaceTitle,
|
|
14406
|
+
...input.workerScriptName ? { workerScriptName: input.workerScriptName } : {},
|
|
14407
|
+
...tokens
|
|
14408
|
+
}, dependencies.fetcher);
|
|
14409
|
+
}
|
|
14340
14410
|
var exactKeys3 = (value, allowed, label) => {
|
|
14341
14411
|
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
14342
14412
|
if (unknown.length)
|
|
@@ -17138,13 +17208,26 @@ function writeBootstrapConfig(path, config) {
|
|
|
17138
17208
|
`, { mode: 384, flag: "wx" });
|
|
17139
17209
|
return path;
|
|
17140
17210
|
}
|
|
17141
|
-
function interactiveCloudflareBootstrap() {
|
|
17211
|
+
async function interactiveCloudflareBootstrap() {
|
|
17142
17212
|
if (!process.stdin.isTTY)
|
|
17143
17213
|
throw new Error("interactive Cloudflare config generation requires a terminal");
|
|
17214
|
+
const tokenFiles = {
|
|
17215
|
+
tunnelTokenFile: bootstrapAnswer("Owner-only CF_TUNNEL_TOKEN file", "./CF_TUNNEL_TOKEN"),
|
|
17216
|
+
apiTokenFile: bootstrapAnswer("Owner-only CF_API_TOKEN file", "./CF_API_TOKEN")
|
|
17217
|
+
};
|
|
17218
|
+
const zoneName = bootstrapAnswer("Cloudflare DNS zone name", "forgezero.net");
|
|
17219
|
+
const kvNamespaceTitle = bootstrapAnswer("Existing Worker-bound KV namespace title");
|
|
17220
|
+
const realtimeEnabled = bootstrapAnswer("Configure existing Worker realtime fan-out? (yes/no)", "yes") === "yes";
|
|
17221
|
+
const workerScriptName = realtimeEnabled ? bootstrapAnswer("Existing Worker script name") : undefined;
|
|
17222
|
+
const discovered = await discoverCloudflareBootstrapCommandResources({
|
|
17223
|
+
zoneName,
|
|
17224
|
+
kvNamespaceTitle,
|
|
17225
|
+
...workerScriptName ? { workerScriptName } : {},
|
|
17226
|
+
tokenFiles
|
|
17227
|
+
});
|
|
17144
17228
|
const nodeCount = bootstrapNumber("Number of public API nodes", "3");
|
|
17145
17229
|
if (nodeCount < 1 || nodeCount > 32)
|
|
17146
17230
|
throw new Error("Number of public API nodes must be between 1 and 32");
|
|
17147
|
-
const realtimeEnabled = bootstrapAnswer("Configure existing Worker realtime fan-out? (yes/no)", "yes") === "yes";
|
|
17148
17231
|
const meshEnabled = bootstrapAnswer("Configure private Mesh/WARP routes? (yes/no)", "no") === "yes";
|
|
17149
17232
|
const meshDevicePolicyId = meshEnabled ? bootstrapAnswer("Existing Mesh device policy id") : undefined;
|
|
17150
17233
|
const nodes = Array.from({ length: nodeCount }, (_, index) => {
|
|
@@ -17168,23 +17251,18 @@ function interactiveCloudflareBootstrap() {
|
|
|
17168
17251
|
return createCloudflareBootstrapCommandConfig({
|
|
17169
17252
|
checkpointPath: bootstrapAnswer("Owner-only resumable checkpoint path", "./cloudflare-handoff.json"),
|
|
17170
17253
|
coordinates: {
|
|
17171
|
-
|
|
17172
|
-
zoneId: bootstrapAnswer("Cloudflare zone id"),
|
|
17173
|
-
kvNamespaceId: bootstrapAnswer("Existing Worker-bound KV namespace id"),
|
|
17254
|
+
...discovered,
|
|
17174
17255
|
...meshDevicePolicyId ? { meshDevicePolicyId } : {},
|
|
17175
17256
|
...realtimeEnabled ? {
|
|
17176
17257
|
realtime: {
|
|
17177
|
-
workerScriptName
|
|
17258
|
+
workerScriptName,
|
|
17178
17259
|
endpoint: bootstrapAnswer("Stable public Worker HTTPS endpoint"),
|
|
17179
17260
|
producer: bootstrapAnswer("Realtime producer identity", "platform-api")
|
|
17180
17261
|
}
|
|
17181
17262
|
} : {},
|
|
17182
17263
|
nodes
|
|
17183
17264
|
},
|
|
17184
|
-
tokenFiles
|
|
17185
|
-
tunnelTokenFile: bootstrapAnswer("Owner-only CF_TUNNEL_TOKEN file", "./CF_TUNNEL_TOKEN"),
|
|
17186
|
-
apiTokenFile: bootstrapAnswer("Owner-only CF_API_TOKEN file", "./CF_API_TOKEN")
|
|
17187
|
-
}
|
|
17265
|
+
tokenFiles
|
|
17188
17266
|
});
|
|
17189
17267
|
}
|
|
17190
17268
|
function genesisOutputDirectory(path) {
|
|
@@ -17367,7 +17445,7 @@ async function cmdBootstrap(options, args) {
|
|
|
17367
17445
|
return 0;
|
|
17368
17446
|
}
|
|
17369
17447
|
if (kind === "cloudflare") {
|
|
17370
|
-
const path = writeBootstrapConfig(options.outputPath, interactiveCloudflareBootstrap());
|
|
17448
|
+
const path = writeBootstrapConfig(options.outputPath, await interactiveCloudflareBootstrap());
|
|
17371
17449
|
readCloudflareBootstrapCommandConfig(path, "plan");
|
|
17372
17450
|
out.line(JSON.stringify({ kind, path, mode: "0600" }, null, 2));
|
|
17373
17451
|
return 0;
|
package/dist/metal-bootstrap.js
CHANGED
|
@@ -292,7 +292,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/version.ts
|
|
295
|
-
var VERSION = "0.1.
|
|
295
|
+
var VERSION = "0.1.58";
|
|
296
296
|
|
|
297
297
|
// src/otel-collector.ts
|
|
298
298
|
var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
|
|
@@ -22,7 +22,7 @@ function privateDatabaseHostRoute(value) {
|
|
|
22
22
|
return `${address}/${isIP(address) === 4 ? 32 : 128}`;
|
|
23
23
|
}
|
|
24
24
|
var endpoint = "https://api.cloudflare.com/client/v4";
|
|
25
|
-
async function
|
|
25
|
+
async function cfEnvelope(config, path, init = {}, fetcher = fetch) {
|
|
26
26
|
const response = await fetcher(`${endpoint}${path}`, {
|
|
27
27
|
...init,
|
|
28
28
|
headers: {
|
|
@@ -35,7 +35,68 @@ async function cf(config, path, init = {}, fetcher = fetch) {
|
|
|
35
35
|
if (!response.ok || body.success !== true) {
|
|
36
36
|
throw new Error(body.errors?.map(({ message }) => message).filter(Boolean).join("; ") || `Cloudflare returned HTTP ${response.status}`);
|
|
37
37
|
}
|
|
38
|
-
return body
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
async function cf(config, path, init = {}, fetcher = fetch) {
|
|
41
|
+
return (await cfEnvelope(config, path, init, fetcher)).result;
|
|
42
|
+
}
|
|
43
|
+
async function cfPages(config, path, perPage, fetcher) {
|
|
44
|
+
const output = [];
|
|
45
|
+
for (let page = 1;page <= 100; page += 1) {
|
|
46
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
47
|
+
const envelope = await cfEnvelope(config, `${path}${separator}page=${page}&per_page=${perPage}`, {}, fetcher);
|
|
48
|
+
const result = Array.isArray(envelope.result) ? envelope.result : [];
|
|
49
|
+
output.push(...result);
|
|
50
|
+
const totalPages = envelope.result_info?.total_pages;
|
|
51
|
+
if (Number.isInteger(totalPages) ? page >= totalPages : result.length < perPage)
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
throw new Error("Cloudflare pagination exceeded the reviewed 100-page bound");
|
|
55
|
+
}
|
|
56
|
+
var exactHexId = (value, label) => {
|
|
57
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
58
|
+
if (!/^[a-f0-9]{32}$/.test(normalized))
|
|
59
|
+
throw new Error(`${label} is malformed`);
|
|
60
|
+
return normalized;
|
|
61
|
+
};
|
|
62
|
+
async function discoverCloudflareBootstrapResources(config, fetcher = fetch) {
|
|
63
|
+
const zoneName = config.zoneName.trim().toLowerCase().replace(/\.$/, "");
|
|
64
|
+
const kvNamespaceTitle = config.kvNamespaceTitle.trim();
|
|
65
|
+
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(zoneName)) {
|
|
66
|
+
throw new Error("Cloudflare zone name is invalid");
|
|
67
|
+
}
|
|
68
|
+
if (!kvNamespaceTitle || kvNamespaceTitle.length > 512) {
|
|
69
|
+
throw new Error("Cloudflare KV namespace title is invalid");
|
|
70
|
+
}
|
|
71
|
+
const [managementStatus, runtimeStatus] = await Promise.all([
|
|
72
|
+
cf({ apiToken: config.tunnelToken }, "/user/tokens/verify", {}, fetcher),
|
|
73
|
+
cf({ apiToken: config.apiToken }, "/user/tokens/verify", {}, fetcher)
|
|
74
|
+
]);
|
|
75
|
+
if (managementStatus.status !== "active")
|
|
76
|
+
throw new Error("CF_TUNNEL_TOKEN is not active");
|
|
77
|
+
if (runtimeStatus.status !== "active")
|
|
78
|
+
throw new Error("CF_API_TOKEN is not active");
|
|
79
|
+
const zones = await cfPages({ apiToken: config.tunnelToken }, `/zones?name=${encodeURIComponent(zoneName)}&match=all&status=active`, 50, fetcher);
|
|
80
|
+
const matchingZones = zones.filter(({ name }) => name?.trim().toLowerCase() === zoneName);
|
|
81
|
+
if (matchingZones.length !== 1) {
|
|
82
|
+
throw new Error(`Cloudflare zone ${zoneName} must resolve to exactly one active zone`);
|
|
83
|
+
}
|
|
84
|
+
const zoneId = exactHexId(matchingZones[0].id, "Cloudflare zone id");
|
|
85
|
+
const accountId = exactHexId(matchingZones[0].account?.id, "Cloudflare account id");
|
|
86
|
+
const namespaces = await cfPages({ apiToken: config.apiToken }, `/accounts/${accountId}/storage/kv/namespaces?order=title&direction=asc`, 1000, fetcher);
|
|
87
|
+
const matchingNamespaces = namespaces.filter(({ title }) => title === kvNamespaceTitle);
|
|
88
|
+
if (matchingNamespaces.length !== 1) {
|
|
89
|
+
throw new Error(`Cloudflare KV namespace ${kvNamespaceTitle} must resolve to exactly one namespace`);
|
|
90
|
+
}
|
|
91
|
+
const kvNamespaceId = exactHexId(matchingNamespaces[0].id, "Cloudflare KV namespace id");
|
|
92
|
+
if (config.workerScriptName) {
|
|
93
|
+
await verifyCloudflareWorkerDurableObjects({
|
|
94
|
+
accountId,
|
|
95
|
+
scriptName: config.workerScriptName,
|
|
96
|
+
apiToken: config.apiToken
|
|
97
|
+
}, fetcher);
|
|
98
|
+
}
|
|
99
|
+
return { accountId, zoneId, kvNamespaceId };
|
|
39
100
|
}
|
|
40
101
|
async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
|
|
41
102
|
const [address, prefixText, ...extra] = config.network.split("/");
|
|
@@ -211,7 +272,7 @@ async function verifyCloudflareWorkerDurableObjects(config, fetcher = fetch) {
|
|
|
211
272
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(config.scriptName)) {
|
|
212
273
|
throw new Error("Cloudflare Worker script name is invalid");
|
|
213
274
|
}
|
|
214
|
-
const namespaces = await
|
|
275
|
+
const namespaces = await cfPages(config, `/accounts/${config.accountId}/workers/durable_objects/namespaces`, 1000, fetcher);
|
|
215
276
|
const owned = namespaces.filter(({ script }) => script === config.scriptName);
|
|
216
277
|
if (!owned.length)
|
|
217
278
|
throw new Error(`Cloudflare Worker ${config.scriptName} has no Durable Object namespace`);
|
|
@@ -1207,7 +1268,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
|
|
|
1207
1268
|
var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
|
|
1208
1269
|
|
|
1209
1270
|
// src/version.ts
|
|
1210
|
-
var VERSION = "0.1.
|
|
1271
|
+
var VERSION = "0.1.58";
|
|
1211
1272
|
|
|
1212
1273
|
// src/software.ts
|
|
1213
1274
|
var PINNED_BUN_VERSION = "1.3.14";
|
|
@@ -2086,7 +2086,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2086
2086
|
}
|
|
2087
2087
|
|
|
2088
2088
|
// src/version.ts
|
|
2089
|
-
var VERSION3 = "0.1.
|
|
2089
|
+
var VERSION3 = "0.1.58";
|
|
2090
2090
|
|
|
2091
2091
|
// src/egress-policy.ts
|
|
2092
2092
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/provision.js
CHANGED
|
@@ -2086,7 +2086,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2086
2086
|
}
|
|
2087
2087
|
|
|
2088
2088
|
// src/version.ts
|
|
2089
|
-
var VERSION3 = "0.1.
|
|
2089
|
+
var VERSION3 = "0.1.58";
|
|
2090
2090
|
|
|
2091
2091
|
// src/egress-policy.ts
|
|
2092
2092
|
import { realpathSync as realpathSync3 } from "node:fs";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.58";
|