@treeseed/sdk 0.5.3 → 0.6.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.ts +2 -0
- package/dist/index.js +46 -0
- package/dist/operations/providers/default.js +1 -1
- package/dist/operations/services/config-runtime.d.ts +49 -42
- package/dist/operations/services/config-runtime.js +449 -136
- package/dist/operations/services/deploy.d.ts +298 -0
- package/dist/operations/services/deploy.js +381 -137
- package/dist/operations/services/git-workflow.d.ts +9 -0
- package/dist/operations/services/git-workflow.js +32 -0
- package/dist/operations/services/github-api.d.ts +115 -0
- package/dist/operations/services/github-api.js +455 -0
- package/dist/operations/services/github-automation.d.ts +19 -33
- package/dist/operations/services/github-automation.js +44 -131
- package/dist/operations/services/key-agent.d.ts +20 -1
- package/dist/operations/services/key-agent.js +267 -102
- package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
- package/dist/operations/services/knowledge-coop-launch.js +26 -12
- package/dist/operations/services/project-platform.d.ts +157 -150
- package/dist/operations/services/project-platform.js +129 -26
- package/dist/operations/services/railway-api.d.ts +244 -0
- package/dist/operations/services/railway-api.js +882 -0
- package/dist/operations/services/railway-deploy.d.ts +171 -27
- package/dist/operations/services/railway-deploy.js +672 -172
- package/dist/operations/services/runtime-tools.d.ts +18 -0
- package/dist/operations/services/runtime-tools.js +19 -6
- package/dist/operations/services/workspace-preflight.js +2 -2
- package/dist/platform/contracts.d.ts +7 -0
- package/dist/platform/deploy-config.js +23 -0
- package/dist/platform/deploy-runtime.d.ts +1 -0
- package/dist/platform/deploy-runtime.js +7 -9
- package/dist/platform/env.yaml +10 -9
- package/dist/platform/environment.js +4 -0
- package/dist/platform/plugin.d.ts +6 -0
- package/dist/platform/plugins/constants.d.ts +1 -0
- package/dist/platform/plugins/constants.js +1 -0
- package/dist/platform/plugins/runtime.d.ts +4 -0
- package/dist/platform/plugins/runtime.js +8 -1
- package/dist/platform/published-content.js +27 -4
- package/dist/platform/tenant/runtime-config.js +33 -24
- package/dist/plugin-default.d.ts +1 -0
- package/dist/plugin-default.js +1 -0
- package/dist/reconcile/builtin-adapters.d.ts +3 -0
- package/dist/reconcile/builtin-adapters.js +2093 -0
- package/dist/reconcile/contracts.d.ts +155 -0
- package/dist/reconcile/contracts.js +0 -0
- package/dist/reconcile/desired-state.d.ts +179 -0
- package/dist/reconcile/desired-state.js +319 -0
- package/dist/reconcile/engine.d.ts +405 -0
- package/dist/reconcile/engine.js +356 -0
- package/dist/reconcile/errors.d.ts +5 -0
- package/dist/reconcile/errors.js +13 -0
- package/dist/reconcile/index.d.ts +7 -0
- package/dist/reconcile/index.js +7 -0
- package/dist/reconcile/registry.d.ts +7 -0
- package/dist/reconcile/registry.js +64 -0
- package/dist/reconcile/state.d.ts +7 -0
- package/dist/reconcile/state.js +303 -0
- package/dist/reconcile/units.d.ts +6 -0
- package/dist/reconcile/units.js +68 -0
- package/dist/scripts/config-treeseed.js +27 -19
- package/dist/scripts/tenant-deploy.js +35 -14
- package/dist/workflow/operations.js +127 -22
- package/dist/workflow-support.d.ts +3 -1
- package/dist/workflow-support.js +50 -0
- package/dist/workflow.d.ts +2 -0
- package/package.json +7 -1
|
@@ -3,7 +3,8 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
3
3
|
import { dirname, relative, resolve } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveTreeseedWebCachePolicy } from "../../platform/deploy-config.js";
|
|
7
|
+
import { normalizeRailwayEnvironmentName } from "./railway-api.js";
|
|
7
8
|
import { loadCliDeployConfig, resolveWranglerBin } from "./runtime-tools.js";
|
|
8
9
|
const DEFAULT_COMPATIBILITY_DATE = "2026-04-05";
|
|
9
10
|
const DEFAULT_COMPATIBILITY_FLAGS = ["nodejs_compat"];
|
|
@@ -14,6 +15,9 @@ const MANAGED_SERVICE_KEYS = ["api", "manager", "worker", "workdayStart", "workd
|
|
|
14
15
|
const TRESEED_ENVELOPE_SCHEMA_GENERATION = "runtime-envelopes-v1";
|
|
15
16
|
const TRESEED_MIGRATION_WAVE_ID = "0005_runtime_envelopes";
|
|
16
17
|
const TRESEED_SUPPORTED_PAYLOAD_RANGE = { min: 1, max: 1 };
|
|
18
|
+
function sleepSync(milliseconds) {
|
|
19
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
20
|
+
}
|
|
17
21
|
function ensureParent(filePath) {
|
|
18
22
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
19
23
|
}
|
|
@@ -48,6 +52,112 @@ function loadTenantDeployConfig(tenantRoot) {
|
|
|
48
52
|
function sanitizeSegment(value) {
|
|
49
53
|
return String(value).trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 36) || "default";
|
|
50
54
|
}
|
|
55
|
+
function requireConfiguredIdentityValue(value, label) {
|
|
56
|
+
const normalized = typeof value === "string" && value.trim() ? value.trim() : "";
|
|
57
|
+
if (!normalized) {
|
|
58
|
+
throw new Error(`Configure ${label} before reconciling multi-tenant resources.`);
|
|
59
|
+
}
|
|
60
|
+
return normalized;
|
|
61
|
+
}
|
|
62
|
+
function resolveTreeseedResourceIdentity(deployConfig, target) {
|
|
63
|
+
const teamId = requireConfiguredIdentityValue(
|
|
64
|
+
envOrNull("TREESEED_HOSTING_TEAM_ID") ?? deployConfig.runtime?.teamId ?? deployConfig.hosting?.teamId,
|
|
65
|
+
"hosting.teamId or runtime.teamId in treeseed.site.yaml"
|
|
66
|
+
);
|
|
67
|
+
const projectId = requireConfiguredIdentityValue(
|
|
68
|
+
envOrNull("TREESEED_PROJECT_ID") ?? deployConfig.runtime?.projectId ?? deployConfig.hosting?.projectId,
|
|
69
|
+
"hosting.projectId or runtime.projectId in treeseed.site.yaml"
|
|
70
|
+
);
|
|
71
|
+
const teamSegment = sanitizeSegment(teamId);
|
|
72
|
+
const projectSegment = sanitizeSegment(projectId);
|
|
73
|
+
const deploymentKey = `${teamSegment}-${projectSegment}`;
|
|
74
|
+
const environment = target.kind === "persistent" ? target.scope : target.branchName;
|
|
75
|
+
const environmentSegment = target.kind === "persistent" ? target.scope : sanitizeSegment(target.branchName);
|
|
76
|
+
return {
|
|
77
|
+
teamId,
|
|
78
|
+
projectId,
|
|
79
|
+
slug: deployConfig.slug,
|
|
80
|
+
environment,
|
|
81
|
+
deploymentKey,
|
|
82
|
+
environmentKey: `${deploymentKey}-${environmentSegment}`
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function primaryHost(value) {
|
|
86
|
+
return safeUrl(value)?.hostname ?? null;
|
|
87
|
+
}
|
|
88
|
+
function domainZoneFromConfiguredWebDomain(domain) {
|
|
89
|
+
if (typeof domain !== "string" || !domain.trim()) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return domain.trim().replace(/^api\./u, "");
|
|
93
|
+
}
|
|
94
|
+
function resolveSurfaceDomainZone(deployConfig) {
|
|
95
|
+
return domainZoneFromConfiguredWebDomain(
|
|
96
|
+
deployConfig.surfaces?.web?.environments?.prod?.domain ?? primaryHost(deployConfig.surfaces?.web?.publicBaseUrl ?? deployConfig.siteUrl)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
function deriveStagingDomainHash(identity, surface) {
|
|
100
|
+
return stableHash(`${identity.teamId}:${identity.projectId}:${identity.slug}:${surface}:staging`).slice(0, 8);
|
|
101
|
+
}
|
|
102
|
+
function deriveTreeseedStagingSurfaceDomain(deployConfig, identity, surface) {
|
|
103
|
+
const zone = resolveSurfaceDomainZone(deployConfig);
|
|
104
|
+
if (!zone) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const hash = deriveStagingDomainHash(identity, surface);
|
|
108
|
+
return surface === "web" ? `${identity.deploymentKey}-staging-${hash}.${zone}` : `api-${identity.deploymentKey}-staging-${hash}.${zone}`;
|
|
109
|
+
}
|
|
110
|
+
function deriveApiDomainFromWebDomain(domain) {
|
|
111
|
+
if (!domain) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return domain.startsWith("api.") ? domain : `api.${domain}`;
|
|
115
|
+
}
|
|
116
|
+
function resolveConfiguredSurfaceDomain(deployConfig, target, surface) {
|
|
117
|
+
if (target.kind !== "persistent") {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const scope = target.scope;
|
|
121
|
+
const configured = deployConfig.surfaces?.[surface]?.environments?.[scope]?.domain?.trim();
|
|
122
|
+
if (configured) {
|
|
123
|
+
return configured;
|
|
124
|
+
}
|
|
125
|
+
if (scope === "staging") {
|
|
126
|
+
return deriveTreeseedStagingSurfaceDomain(
|
|
127
|
+
deployConfig,
|
|
128
|
+
resolveTreeseedResourceIdentity(deployConfig, target),
|
|
129
|
+
surface
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (scope !== "prod") {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (surface === "web") {
|
|
136
|
+
return primaryHost(deployConfig.surfaces?.web?.publicBaseUrl ?? deployConfig.siteUrl);
|
|
137
|
+
}
|
|
138
|
+
return deriveApiDomainFromWebDomain(resolveConfiguredSurfaceDomain(deployConfig, target, "web"));
|
|
139
|
+
}
|
|
140
|
+
function resolveConfiguredSurfaceBaseUrl(deployConfig, target, surface) {
|
|
141
|
+
const configuredDomain = resolveConfiguredSurfaceDomain(deployConfig, target, surface);
|
|
142
|
+
if (configuredDomain) {
|
|
143
|
+
return `https://${configuredDomain}`;
|
|
144
|
+
}
|
|
145
|
+
if (surface === "web") {
|
|
146
|
+
return deployConfig.surfaces?.web?.publicBaseUrl ?? deployConfig.siteUrl ?? null;
|
|
147
|
+
}
|
|
148
|
+
if (surface === "api") {
|
|
149
|
+
const scope = scopeFromTarget(target);
|
|
150
|
+
return deployConfig.services?.api?.environments?.[scope]?.baseUrl ?? deployConfig.services?.api?.publicBaseUrl ?? null;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function sharedDeploymentName(identity, role = "") {
|
|
155
|
+
return role ? `${identity.deploymentKey}-${sanitizeSegment(role)}` : identity.deploymentKey;
|
|
156
|
+
}
|
|
157
|
+
function environmentScopedIdentityName(identity, role, target) {
|
|
158
|
+
const environmentSegment = target.kind === "persistent" ? target.scope : sanitizeSegment(target.branchName);
|
|
159
|
+
return `${identity.deploymentKey}-${sanitizeSegment(role)}-${environmentSegment}`;
|
|
160
|
+
}
|
|
51
161
|
function normalizePersistentScope(scope = "prod") {
|
|
52
162
|
if (!PERSISTENT_SCOPES.has(scope)) {
|
|
53
163
|
throw new Error(`Unsupported Treeseed environment "${scope}". Expected one of local, staging, prod.`);
|
|
@@ -112,26 +222,13 @@ function deployTargetLabel(scopeOrTarget = "prod") {
|
|
|
112
222
|
return target.kind === "persistent" ? target.scope : `branch:${target.branchName}`;
|
|
113
223
|
}
|
|
114
224
|
function targetWorkerName(deployConfig, target) {
|
|
115
|
-
const
|
|
225
|
+
const identity = resolveTreeseedResourceIdentity(deployConfig, target);
|
|
226
|
+
const configuredBaseName = deployConfig.cloudflare.workerName?.trim();
|
|
227
|
+
const baseName = configuredBaseName && configuredBaseName === deployConfig.slug ? identity.deploymentKey : configuredBaseName || `${identity.deploymentKey}-edge`;
|
|
116
228
|
if (target.kind === "persistent") {
|
|
117
|
-
|
|
118
|
-
return baseName;
|
|
119
|
-
}
|
|
120
|
-
return `${baseName}-${target.scope}`;
|
|
229
|
+
return `${sanitizeSegment(baseName)}-${target.scope}`;
|
|
121
230
|
}
|
|
122
|
-
return `${baseName}-${sanitizeSegment(target.branchName)}`;
|
|
123
|
-
}
|
|
124
|
-
function targetScopedResourceName(baseName, target) {
|
|
125
|
-
if (!baseName) {
|
|
126
|
-
return baseName;
|
|
127
|
-
}
|
|
128
|
-
if (target.kind === "persistent") {
|
|
129
|
-
if (target.scope === "prod") {
|
|
130
|
-
return baseName;
|
|
131
|
-
}
|
|
132
|
-
return `${baseName}-${target.scope}`;
|
|
133
|
-
}
|
|
134
|
-
return `${baseName}-${sanitizeSegment(target.branchName)}`;
|
|
231
|
+
return `${sanitizeSegment(baseName)}-${sanitizeSegment(target.branchName)}`;
|
|
135
232
|
}
|
|
136
233
|
function targetWorkersDevUrl(workerName) {
|
|
137
234
|
return `https://${workerName}.workers.dev`;
|
|
@@ -140,10 +237,11 @@ function relativeFromGeneratedRoot(targetPath, generatedRoot) {
|
|
|
140
237
|
return relative(generatedRoot, targetPath).replaceAll("\\", "/");
|
|
141
238
|
}
|
|
142
239
|
function buildPublicVars(deployConfig) {
|
|
240
|
+
const identity = resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget("prod"));
|
|
143
241
|
const contentRuntimeProvider = deployConfig.providers?.content?.runtime ?? "team_scoped_r2_overlay";
|
|
144
242
|
const contentPublishProvider = deployConfig.providers?.content?.publish ?? contentRuntimeProvider;
|
|
145
243
|
const contentServingMode = envOrNull("TREESEED_CONTENT_SERVING_MODE") ?? deployConfig.providers?.content?.serving ?? "local_collections";
|
|
146
|
-
const contentDefaultTeamId =
|
|
244
|
+
const contentDefaultTeamId = identity.teamId;
|
|
147
245
|
const contentManifestKeyTemplate = deployConfig.cloudflare.r2?.manifestKeyTemplate ?? "teams/{teamId}/published/common.json";
|
|
148
246
|
const contentPreviewRootTemplate = deployConfig.cloudflare.r2?.previewRootTemplate ?? "teams/{teamId}/previews";
|
|
149
247
|
const contentManifestKey = contentManifestKeyTemplate.replaceAll("{teamId}", contentDefaultTeamId);
|
|
@@ -158,7 +256,7 @@ function buildPublicVars(deployConfig) {
|
|
|
158
256
|
TREESEED_RUNTIME_REGISTRATION: deployConfig.runtime?.registration ?? "none",
|
|
159
257
|
TREESEED_MARKET_API_BASE_URL: resolveConfiguredMarketBaseUrl(deployConfig),
|
|
160
258
|
TREESEED_HOSTING_TEAM_ID: contentDefaultTeamId,
|
|
161
|
-
TREESEED_PROJECT_ID:
|
|
259
|
+
TREESEED_PROJECT_ID: identity.projectId,
|
|
162
260
|
TREESEED_AGENT_EXECUTION_PROVIDER: deployConfig.providers?.agents?.execution ?? "stub",
|
|
163
261
|
TREESEED_AGENT_REPOSITORY_PROVIDER: deployConfig.providers?.agents?.repository ?? "stub",
|
|
164
262
|
TREESEED_AGENT_VERIFICATION_PROVIDER: deployConfig.providers?.agents?.verification ?? "stub",
|
|
@@ -210,60 +308,60 @@ function buildSecretMap(deployConfig, state) {
|
|
|
210
308
|
TREESEED_FORM_TOKEN_SECRET: envOrNull("TREESEED_FORM_TOKEN_SECRET") ?? generatedSecret,
|
|
211
309
|
TREESEED_EDITORIAL_PREVIEW_SECRET: envOrNull("TREESEED_EDITORIAL_PREVIEW_SECRET") ?? previewSecret,
|
|
212
310
|
TREESEED_TURNSTILE_SECRET_KEY: envOrNull("TREESEED_TURNSTILE_SECRET_KEY"),
|
|
213
|
-
|
|
214
|
-
TREESEED_SMTP_PORT: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_PORT") : null,
|
|
215
|
-
TREESEED_SMTP_USERNAME: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_USERNAME") : null,
|
|
216
|
-
TREESEED_SMTP_PASSWORD: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_PASSWORD") : null,
|
|
217
|
-
TREESEED_SMTP_FROM: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_FROM") : null,
|
|
218
|
-
TREESEED_SMTP_REPLY_TO: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_REPLY_TO") : null
|
|
311
|
+
TREESEED_SMTP_PASSWORD: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_PASSWORD") : null
|
|
219
312
|
};
|
|
220
313
|
}
|
|
221
314
|
function defaultStateFromConfig(deployConfig, target) {
|
|
315
|
+
const identity = resolveTreeseedResourceIdentity(deployConfig, target);
|
|
222
316
|
const workerName = targetWorkerName(deployConfig, target);
|
|
223
317
|
const suffix = target.kind === "persistent" ? target.scope : sanitizeSegment(target.branchName);
|
|
224
318
|
const contentManifestKeyTemplate = deployConfig.cloudflare.r2?.manifestKeyTemplate ?? "teams/{teamId}/published/common.json";
|
|
225
319
|
const contentPreviewRootTemplate = deployConfig.cloudflare.r2?.previewRootTemplate ?? "teams/{teamId}/previews";
|
|
226
|
-
const contentDefaultTeamId =
|
|
320
|
+
const contentDefaultTeamId = identity.teamId;
|
|
227
321
|
const contentManifestKey = contentManifestKeyTemplate.replaceAll("{teamId}", contentDefaultTeamId);
|
|
228
322
|
return {
|
|
229
323
|
version: 2,
|
|
230
324
|
target,
|
|
325
|
+
identity,
|
|
231
326
|
previewEnabled: target.kind === "branch",
|
|
232
327
|
workerName,
|
|
233
328
|
kvNamespaces: {
|
|
234
329
|
FORM_GUARD_KV: {
|
|
235
|
-
name:
|
|
330
|
+
name: environmentScopedIdentityName(identity, "form-guard", target),
|
|
331
|
+
binding: "FORM_GUARD_KV",
|
|
236
332
|
id: `dryrun-${suffix}-form-guard`,
|
|
237
333
|
previewId: `dryrun-${suffix}-form-guard-preview`
|
|
238
334
|
},
|
|
239
335
|
SESSION: {
|
|
240
|
-
name:
|
|
336
|
+
name: environmentScopedIdentityName(identity, "session", target),
|
|
337
|
+
binding: "SESSION",
|
|
241
338
|
id: `dryrun-${suffix}-session`,
|
|
242
339
|
previewId: `dryrun-${suffix}-session-preview`
|
|
243
340
|
}
|
|
244
341
|
},
|
|
245
342
|
d1Databases: {
|
|
246
343
|
SITE_DATA_DB: {
|
|
247
|
-
databaseName:
|
|
344
|
+
databaseName: environmentScopedIdentityName(identity, "site-data", target),
|
|
345
|
+
binding: "SITE_DATA_DB",
|
|
248
346
|
databaseId: `dryrun-${suffix}-site-data`,
|
|
249
347
|
previewDatabaseId: `dryrun-${suffix}-site-data-preview`
|
|
250
348
|
}
|
|
251
349
|
},
|
|
252
350
|
queues: {
|
|
253
351
|
agentWork: {
|
|
254
|
-
name:
|
|
255
|
-
dlqName:
|
|
352
|
+
name: environmentScopedIdentityName(identity, deployConfig.cloudflare.queueName ?? "agent-work", target),
|
|
353
|
+
dlqName: environmentScopedIdentityName(identity, deployConfig.cloudflare.dlqName ?? "agent-work-dlq", target),
|
|
256
354
|
binding: deployConfig.cloudflare.queueBinding ?? "AGENT_WORK_QUEUE",
|
|
257
355
|
queueId: null,
|
|
258
356
|
dlqId: null
|
|
259
357
|
}
|
|
260
358
|
},
|
|
261
359
|
pages: {
|
|
262
|
-
projectName:
|
|
360
|
+
projectName: resolveConfiguredPagesProjectName(deployConfig),
|
|
263
361
|
productionBranch: deployConfig.cloudflare.pages?.productionBranch ?? "main",
|
|
264
362
|
stagingBranch: deployConfig.cloudflare.pages?.stagingBranch ?? "staging",
|
|
265
363
|
buildOutputDir: deployConfig.cloudflare.pages?.buildOutputDir ?? "dist",
|
|
266
|
-
url:
|
|
364
|
+
url: resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web")
|
|
267
365
|
},
|
|
268
366
|
content: {
|
|
269
367
|
runtimeProvider: deployConfig.providers?.content?.runtime ?? "team_scoped_r2_overlay",
|
|
@@ -283,8 +381,8 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
283
381
|
kind: deployConfig.hosting?.kind ?? "self_hosted_project",
|
|
284
382
|
registration: deployConfig.hosting?.registration ?? "none",
|
|
285
383
|
marketBaseUrl: resolveConfiguredMarketBaseUrl(deployConfig) || null,
|
|
286
|
-
teamId:
|
|
287
|
-
projectId:
|
|
384
|
+
teamId: identity.teamId,
|
|
385
|
+
projectId: identity.projectId
|
|
288
386
|
},
|
|
289
387
|
hub: {
|
|
290
388
|
mode: deployConfig.hub?.mode ?? "treeseed_hosted"
|
|
@@ -293,8 +391,8 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
293
391
|
mode: deployConfig.runtime?.mode ?? "none",
|
|
294
392
|
registration: deployConfig.runtime?.registration ?? "none",
|
|
295
393
|
marketBaseUrl: resolveConfiguredMarketBaseUrl(deployConfig) || null,
|
|
296
|
-
teamId:
|
|
297
|
-
projectId:
|
|
394
|
+
teamId: identity.teamId,
|
|
395
|
+
projectId: identity.projectId
|
|
298
396
|
},
|
|
299
397
|
webCache: {
|
|
300
398
|
webHost: null,
|
|
@@ -332,7 +430,7 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
332
430
|
warnings: [],
|
|
333
431
|
lastValidationSummary: null
|
|
334
432
|
},
|
|
335
|
-
lastDeployedUrl: target.kind === "branch" ? targetWorkersDevUrl(workerName) :
|
|
433
|
+
lastDeployedUrl: target.kind === "branch" ? targetWorkersDevUrl(workerName) : resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web"),
|
|
336
434
|
lastManifestFingerprint: null,
|
|
337
435
|
lastDeploymentTimestamp: null,
|
|
338
436
|
lastDeployedCommit: null,
|
|
@@ -340,19 +438,19 @@ function defaultStateFromConfig(deployConfig, target) {
|
|
|
340
438
|
MANAGED_SERVICE_KEYS.map((serviceKey) => {
|
|
341
439
|
const serviceConfig = deployConfig.services?.[serviceKey];
|
|
342
440
|
const scope = scopeFromTarget(target);
|
|
343
|
-
const baseUrl = serviceConfig?.environments?.[scope]?.baseUrl ?? serviceConfig?.publicBaseUrl ?? null;
|
|
441
|
+
const baseUrl = serviceKey === "api" ? resolveConfiguredSurfaceBaseUrl(deployConfig, target, "api") ?? serviceConfig?.environments?.[scope]?.baseUrl ?? serviceConfig?.publicBaseUrl ?? null : serviceConfig?.environments?.[scope]?.baseUrl ?? serviceConfig?.publicBaseUrl ?? null;
|
|
344
442
|
return [
|
|
345
443
|
serviceKey,
|
|
346
444
|
{
|
|
347
445
|
enabled: serviceConfig?.enabled !== false && Boolean(serviceConfig),
|
|
348
446
|
provider: serviceConfig?.provider ?? (serviceConfig ? "railway" : "none"),
|
|
349
447
|
projectId: serviceConfig?.railway?.projectId ?? null,
|
|
350
|
-
projectName: serviceConfig?.railway?.projectName ??
|
|
448
|
+
projectName: serviceConfig?.railway?.projectName ?? sharedDeploymentName(identity),
|
|
351
449
|
serviceId: serviceConfig?.railway?.serviceId ?? null,
|
|
352
|
-
serviceName: serviceConfig?.railway?.serviceName ??
|
|
450
|
+
serviceName: serviceConfig?.railway?.serviceName ?? sharedDeploymentName(identity, serviceKey),
|
|
353
451
|
workerName: serviceConfig?.cloudflare?.workerName ?? null,
|
|
354
452
|
rootDir: serviceConfig?.railway?.rootDir ?? serviceConfig?.rootDir ?? null,
|
|
355
|
-
environment: serviceConfig?.environments?.[scope]?.railwayEnvironment ?? scope,
|
|
453
|
+
environment: normalizeRailwayEnvironmentName(serviceConfig?.environments?.[scope]?.railwayEnvironment ?? scope),
|
|
356
454
|
schedule: serviceConfig?.railway?.schedule ?? null,
|
|
357
455
|
publicBaseUrl: baseUrl,
|
|
358
456
|
initialized: false,
|
|
@@ -383,6 +481,16 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
383
481
|
...defaults,
|
|
384
482
|
...persisted,
|
|
385
483
|
target,
|
|
484
|
+
identity: {
|
|
485
|
+
...defaults.identity ?? {},
|
|
486
|
+
...persisted.identity ?? {},
|
|
487
|
+
teamId: defaults.identity?.teamId,
|
|
488
|
+
projectId: defaults.identity?.projectId,
|
|
489
|
+
slug: defaults.identity?.slug,
|
|
490
|
+
environment: defaults.identity?.environment,
|
|
491
|
+
deploymentKey: defaults.identity?.deploymentKey,
|
|
492
|
+
environmentKey: defaults.identity?.environmentKey
|
|
493
|
+
},
|
|
386
494
|
previewEnabled: persisted.previewEnabled ?? defaults.previewEnabled,
|
|
387
495
|
workerName: defaults.workerName,
|
|
388
496
|
kvNamespaces: {
|
|
@@ -391,12 +499,14 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
391
499
|
FORM_GUARD_KV: {
|
|
392
500
|
...defaults.kvNamespaces.FORM_GUARD_KV,
|
|
393
501
|
...persisted.kvNamespaces?.FORM_GUARD_KV ?? {},
|
|
394
|
-
name: defaults.kvNamespaces.FORM_GUARD_KV.name
|
|
502
|
+
name: defaults.kvNamespaces.FORM_GUARD_KV.name,
|
|
503
|
+
binding: defaults.kvNamespaces.FORM_GUARD_KV.binding
|
|
395
504
|
},
|
|
396
505
|
SESSION: {
|
|
397
506
|
...defaults.kvNamespaces.SESSION,
|
|
398
507
|
...persisted.kvNamespaces?.SESSION ?? {},
|
|
399
|
-
name: defaults.kvNamespaces.SESSION.name
|
|
508
|
+
name: defaults.kvNamespaces.SESSION.name,
|
|
509
|
+
binding: defaults.kvNamespaces.SESSION.binding
|
|
400
510
|
}
|
|
401
511
|
},
|
|
402
512
|
d1Databases: {
|
|
@@ -405,7 +515,8 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
405
515
|
SITE_DATA_DB: {
|
|
406
516
|
...defaults.d1Databases.SITE_DATA_DB,
|
|
407
517
|
...persistedSiteDataDb,
|
|
408
|
-
databaseName: defaults.d1Databases.SITE_DATA_DB.databaseName
|
|
518
|
+
databaseName: defaults.d1Databases.SITE_DATA_DB.databaseName,
|
|
519
|
+
binding: defaults.d1Databases.SITE_DATA_DB.binding
|
|
409
520
|
}
|
|
410
521
|
},
|
|
411
522
|
queues: {
|
|
@@ -430,14 +541,14 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
430
541
|
...persisted.content ?? {},
|
|
431
542
|
runtimeProvider: defaults.content?.runtimeProvider ?? persisted.content?.runtimeProvider ?? "team_scoped_r2_overlay",
|
|
432
543
|
publishProvider: defaults.content?.publishProvider ?? persisted.content?.publishProvider ?? "team_scoped_r2_overlay",
|
|
433
|
-
defaultTeamId: defaults.content?.defaultTeamId ?? persisted.content?.defaultTeamId ??
|
|
544
|
+
defaultTeamId: defaults.content?.defaultTeamId ?? persisted.content?.defaultTeamId ?? defaults.identity?.teamId,
|
|
434
545
|
r2Binding: defaults.content?.r2Binding ?? persisted.content?.r2Binding ?? null,
|
|
435
546
|
bucketName: defaults.content?.bucketName ?? persisted.content?.bucketName ?? null,
|
|
436
547
|
publicBaseUrl: defaults.content?.publicBaseUrl ?? persisted.content?.publicBaseUrl ?? null,
|
|
437
548
|
manifestKeyTemplate: defaults.content?.manifestKeyTemplate ?? persisted.content?.manifestKeyTemplate ?? "teams/{teamId}/published/common.json",
|
|
438
549
|
previewRootTemplate: defaults.content?.previewRootTemplate ?? persisted.content?.previewRootTemplate ?? "teams/{teamId}/previews",
|
|
439
550
|
previewTtlHours: defaults.content?.previewTtlHours ?? persisted.content?.previewTtlHours ?? 168,
|
|
440
|
-
manifestKey: defaults.content?.manifestKey ?? persisted.content?.manifestKey ?? `teams/${
|
|
551
|
+
manifestKey: defaults.content?.manifestKey ?? persisted.content?.manifestKey ?? `teams/${defaults.identity?.teamId}/published/common.json`,
|
|
441
552
|
lastPublishedManifestRevision: persisted.content?.lastPublishedManifestRevision ?? defaults.content?.lastPublishedManifestRevision ?? null,
|
|
442
553
|
lastPublishedManifestSha256: persisted.content?.lastPublishedManifestSha256 ?? defaults.content?.lastPublishedManifestSha256 ?? null
|
|
443
554
|
},
|
|
@@ -447,8 +558,8 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
447
558
|
kind: defaults.hosting?.kind ?? persisted.hosting?.kind ?? "self_hosted_project",
|
|
448
559
|
registration: defaults.hosting?.registration ?? persisted.hosting?.registration ?? "none",
|
|
449
560
|
marketBaseUrl: defaults.hosting?.marketBaseUrl ?? persisted.hosting?.marketBaseUrl ?? null,
|
|
450
|
-
teamId: defaults.hosting?.teamId ?? persisted.hosting?.teamId ??
|
|
451
|
-
projectId: defaults.hosting?.projectId ?? persisted.hosting?.projectId ??
|
|
561
|
+
teamId: defaults.hosting?.teamId ?? persisted.hosting?.teamId ?? defaults.identity?.teamId,
|
|
562
|
+
projectId: defaults.hosting?.projectId ?? persisted.hosting?.projectId ?? defaults.identity?.projectId
|
|
452
563
|
},
|
|
453
564
|
hub: {
|
|
454
565
|
...defaults.hub ?? {},
|
|
@@ -461,8 +572,8 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
461
572
|
mode: defaults.runtime?.mode ?? persisted.runtime?.mode ?? "none",
|
|
462
573
|
registration: defaults.runtime?.registration ?? persisted.runtime?.registration ?? "none",
|
|
463
574
|
marketBaseUrl: defaults.runtime?.marketBaseUrl ?? persisted.runtime?.marketBaseUrl ?? null,
|
|
464
|
-
teamId: defaults.runtime?.teamId ?? persisted.runtime?.teamId ??
|
|
465
|
-
projectId: defaults.runtime?.projectId ?? persisted.runtime?.projectId ??
|
|
575
|
+
teamId: defaults.runtime?.teamId ?? persisted.runtime?.teamId ?? defaults.identity?.teamId,
|
|
576
|
+
projectId: defaults.runtime?.projectId ?? persisted.runtime?.projectId ?? defaults.identity?.projectId
|
|
466
577
|
},
|
|
467
578
|
webCache: {
|
|
468
579
|
...defaults.webCache ?? {},
|
|
@@ -499,7 +610,7 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
499
610
|
productionBranch: defaults.pages?.productionBranch ?? persisted.pages?.productionBranch ?? "main",
|
|
500
611
|
stagingBranch: defaults.pages?.stagingBranch ?? persisted.pages?.stagingBranch ?? "staging",
|
|
501
612
|
buildOutputDir: defaults.pages?.buildOutputDir ?? persisted.pages?.buildOutputDir ?? "dist",
|
|
502
|
-
url:
|
|
613
|
+
url: defaults.pages?.url ?? persisted.pages?.url ?? null
|
|
503
614
|
},
|
|
504
615
|
readiness: {
|
|
505
616
|
...defaults.readiness,
|
|
@@ -528,7 +639,7 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
528
639
|
schedule: defaultService.schedule ?? persistedService.schedule ?? null,
|
|
529
640
|
publicBaseUrl: defaultService.publicBaseUrl ?? persistedService.publicBaseUrl ?? null,
|
|
530
641
|
lastDeploymentTimestamp: effectiveDeploymentTimestamp,
|
|
531
|
-
lastDeployedUrl:
|
|
642
|
+
lastDeployedUrl: defaultService.publicBaseUrl ?? persistedService.lastDeployedUrl ?? null,
|
|
532
643
|
lastScheduleSyncAt: persistedService.lastScheduleSyncAt ?? defaultService.lastScheduleSyncAt ?? null
|
|
533
644
|
}
|
|
534
645
|
];
|
|
@@ -538,6 +649,9 @@ function loadDeployState(tenantRoot, deployConfig, options = {}) {
|
|
|
538
649
|
...persisted.railwaySchedules ?? {}
|
|
539
650
|
}
|
|
540
651
|
};
|
|
652
|
+
if (target.kind === "persistent") {
|
|
653
|
+
merged.lastDeployedUrl = resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web") ?? merged.lastDeployedUrl ?? null;
|
|
654
|
+
}
|
|
541
655
|
if (target.kind === "branch" && !merged.lastDeployedUrl) {
|
|
542
656
|
merged.lastDeployedUrl = targetWorkersDevUrl(merged.workerName);
|
|
543
657
|
}
|
|
@@ -660,47 +774,86 @@ function isWranglerAlreadyExistsError(error, matchers) {
|
|
|
660
774
|
return matchers.some((matcher) => matcher.test(message));
|
|
661
775
|
}
|
|
662
776
|
function listKvNamespaces(tenantRoot, env) {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
777
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
778
|
+
if (!accountId) {
|
|
779
|
+
return [];
|
|
780
|
+
}
|
|
781
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/storage/kv/namespaces?per_page=1000&order=title&direction=asc`, {
|
|
782
|
+
env,
|
|
783
|
+
allowFailure: true
|
|
667
784
|
});
|
|
668
|
-
return
|
|
785
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
669
786
|
}
|
|
670
787
|
function listD1Databases(tenantRoot, env) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
788
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
789
|
+
if (!accountId) {
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/d1/database`, {
|
|
793
|
+
env,
|
|
794
|
+
allowFailure: true
|
|
675
795
|
});
|
|
676
|
-
return
|
|
796
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
677
797
|
}
|
|
678
798
|
function listQueues(tenantRoot, env) {
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
799
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
800
|
+
if (!accountId) {
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/queues`, {
|
|
682
804
|
env,
|
|
683
805
|
allowFailure: true
|
|
684
806
|
});
|
|
685
|
-
return result
|
|
807
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
686
808
|
}
|
|
687
809
|
function listR2Buckets(tenantRoot, env) {
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
810
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
811
|
+
if (!accountId) {
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/r2/buckets`, {
|
|
691
815
|
env,
|
|
692
816
|
allowFailure: true
|
|
693
817
|
});
|
|
694
|
-
return result
|
|
818
|
+
return Array.isArray(payload?.result?.buckets) ? payload.result.buckets : [];
|
|
695
819
|
}
|
|
696
820
|
function listPagesProjects(tenantRoot, env) {
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
821
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
822
|
+
if (!accountId) {
|
|
823
|
+
return [];
|
|
824
|
+
}
|
|
825
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/pages/projects`, {
|
|
700
826
|
env,
|
|
701
827
|
allowFailure: true
|
|
702
828
|
});
|
|
703
|
-
return result
|
|
829
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
830
|
+
}
|
|
831
|
+
function ensurePagesProjectCompatibility(accountId, projectName, env, currentProject = null) {
|
|
832
|
+
if (!accountId || !projectName) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const projectPath = `/accounts/${encodeURIComponent(accountId)}/pages/projects/${encodeURIComponent(projectName)}`;
|
|
836
|
+
const latestProject = cloudflareApiRequest(projectPath, { env, allowFailure: true })?.result ?? currentProject;
|
|
837
|
+
const currentConfigs = latestProject?.deployment_configs ?? {};
|
|
838
|
+
const mergeCompatibility = (config = {}) => ({
|
|
839
|
+
...config,
|
|
840
|
+
compatibility_date: config.compatibility_date ?? DEFAULT_COMPATIBILITY_DATE,
|
|
841
|
+
compatibility_flags: [.../* @__PURE__ */ new Set([...config.compatibility_flags ?? [], ...DEFAULT_COMPATIBILITY_FLAGS])]
|
|
842
|
+
});
|
|
843
|
+
cloudflareApiRequest(
|
|
844
|
+
projectPath,
|
|
845
|
+
{
|
|
846
|
+
method: "PATCH",
|
|
847
|
+
env,
|
|
848
|
+
body: {
|
|
849
|
+
deployment_configs: {
|
|
850
|
+
...currentConfigs,
|
|
851
|
+
preview: mergeCompatibility(currentConfigs.preview),
|
|
852
|
+
production: mergeCompatibility(currentConfigs.production)
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
);
|
|
704
857
|
}
|
|
705
858
|
function isPlaceholderResourceId(value) {
|
|
706
859
|
if (!value || typeof value !== "string") {
|
|
@@ -710,8 +863,12 @@ function isPlaceholderResourceId(value) {
|
|
|
710
863
|
}
|
|
711
864
|
function buildProvisioningSummary(deployConfig, state, target) {
|
|
712
865
|
const webCachePolicy = resolveTreeseedWebCachePolicy(deployConfig);
|
|
866
|
+
const identity = state.identity ?? resolveTreeseedResourceIdentity(deployConfig, target);
|
|
867
|
+
const configuredWebDomain = resolveConfiguredSurfaceDomain(deployConfig, target, "web");
|
|
868
|
+
const configuredApiDomain = resolveConfiguredSurfaceDomain(deployConfig, target, "api");
|
|
713
869
|
return {
|
|
714
870
|
target: deployTargetLabel(target),
|
|
871
|
+
identity,
|
|
715
872
|
workerName: state.workerName ?? targetWorkerName(deployConfig, target),
|
|
716
873
|
siteUrl: target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : deployConfig.siteUrl,
|
|
717
874
|
accountId: resolveConfiguredCloudflareAccountId(deployConfig),
|
|
@@ -721,6 +878,21 @@ function buildProvisioningSummary(deployConfig, state, target) {
|
|
|
721
878
|
siteDataDb: state.d1Databases.SITE_DATA_DB,
|
|
722
879
|
queue: state.queues?.agentWork ?? null,
|
|
723
880
|
content: state.content ?? null,
|
|
881
|
+
resources: {
|
|
882
|
+
pagesProject: state.pages?.projectName ?? null,
|
|
883
|
+
contentBucket: state.content?.bucketName ?? null,
|
|
884
|
+
queue: state.queues?.agentWork?.name ?? null,
|
|
885
|
+
dlq: state.queues?.agentWork?.dlqName ?? null,
|
|
886
|
+
database: state.d1Databases?.SITE_DATA_DB?.databaseName ?? null,
|
|
887
|
+
formGuardKv: state.kvNamespaces?.FORM_GUARD_KV?.name ?? null,
|
|
888
|
+
sessionKv: state.kvNamespaces?.SESSION?.name ?? null,
|
|
889
|
+
railwayProject: state.services?.worker?.projectName ?? state.services?.api?.projectName ?? null,
|
|
890
|
+
webDomain: configuredWebDomain,
|
|
891
|
+
apiDomain: configuredApiDomain,
|
|
892
|
+
railwayServices: Object.fromEntries(
|
|
893
|
+
Object.entries(state.services ?? {}).filter(([, service]) => service?.enabled === true).map(([serviceKey, service]) => [serviceKey, service?.serviceName ?? null])
|
|
894
|
+
)
|
|
895
|
+
},
|
|
724
896
|
webCache: {
|
|
725
897
|
webHost: state.webCache?.webHost ?? null,
|
|
726
898
|
contentHost: state.webCache?.contentHost ?? null,
|
|
@@ -778,12 +950,7 @@ function shouldManageCloudflareWebCacheRules(deployConfig, target) {
|
|
|
778
950
|
return Boolean(webTarget?.host && !webTarget.host.endsWith(".workers.dev") && !webTarget.host.endsWith(".pages.dev"));
|
|
779
951
|
}
|
|
780
952
|
function cloudflareApiRequest(path, { method = "GET", body, env, allowFailure = false } = {}) {
|
|
781
|
-
const
|
|
782
|
-
process.execPath,
|
|
783
|
-
[
|
|
784
|
-
"--input-type=module",
|
|
785
|
-
"-e",
|
|
786
|
-
`import { readFileSync } from 'node:fs';
|
|
953
|
+
const requestScript = `import { readFileSync } from 'node:fs';
|
|
787
954
|
const input = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
788
955
|
const response = await fetch(input.url, {
|
|
789
956
|
method: input.method,
|
|
@@ -793,30 +960,65 @@ const response = await fetch(input.url, {
|
|
|
793
960
|
},
|
|
794
961
|
body: input.body ? JSON.stringify(input.body) : undefined,
|
|
795
962
|
});
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
963
|
+
const rawBody = await response.text();
|
|
964
|
+
let payload;
|
|
965
|
+
try {
|
|
966
|
+
payload = rawBody ? JSON.parse(rawBody) : {};
|
|
967
|
+
} catch {
|
|
968
|
+
payload = { success: false, errors: [{ message: rawBody || 'empty response' }] };
|
|
969
|
+
}
|
|
970
|
+
process.stdout.write(JSON.stringify({ ok: response.ok, payload }));`;
|
|
971
|
+
const requestInput = JSON.stringify({
|
|
972
|
+
url: `https://api.cloudflare.com/client/v4${path}`,
|
|
973
|
+
method,
|
|
974
|
+
body,
|
|
975
|
+
token: env?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? ""
|
|
976
|
+
});
|
|
977
|
+
const isTransient = (text) => /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted/iu.test(text || "");
|
|
978
|
+
let attempt = 0;
|
|
979
|
+
for (; ; ) {
|
|
980
|
+
const response = spawnSync(
|
|
981
|
+
process.execPath,
|
|
982
|
+
[
|
|
983
|
+
"--input-type=module",
|
|
984
|
+
"-e",
|
|
985
|
+
requestScript
|
|
986
|
+
],
|
|
987
|
+
{
|
|
988
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
989
|
+
encoding: "utf8",
|
|
990
|
+
env: { ...process.env, ...env ?? {} },
|
|
991
|
+
input: requestInput,
|
|
992
|
+
timeout: 15e3
|
|
993
|
+
}
|
|
994
|
+
);
|
|
995
|
+
if (response.error?.code === "ETIMEDOUT") {
|
|
996
|
+
if (attempt < 2) {
|
|
997
|
+
attempt += 1;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (!allowFailure) {
|
|
1001
|
+
throw new Error(`Cloudflare API request timed out: ${method} ${path}`);
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
809
1004
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1005
|
+
const stderr = response.stderr?.trim() || "";
|
|
1006
|
+
if (response.status !== 0) {
|
|
1007
|
+
if (attempt < 2 && isTransient(stderr)) {
|
|
1008
|
+
attempt += 1;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (!allowFailure) {
|
|
1012
|
+
throw new Error(stderr || `Cloudflare API request failed: ${method} ${path}`);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
const parsed = JSON.parse(response.stdout?.trim() || '{"ok":false,"payload":{"success":false,"errors":[{"message":"empty response"}]}}');
|
|
1016
|
+
if (!parsed.ok && !allowFailure) {
|
|
1017
|
+
const details = Array.isArray(parsed.payload?.errors) ? parsed.payload.errors.map((entry) => entry?.message ?? JSON.stringify(entry)).join("; ") : "unknown error";
|
|
1018
|
+
throw new Error(details || `Cloudflare API request failed: ${method} ${path}`);
|
|
1019
|
+
}
|
|
1020
|
+
return parsed.payload;
|
|
818
1021
|
}
|
|
819
|
-
return parsed.payload;
|
|
820
1022
|
}
|
|
821
1023
|
function resolveCloudflareZoneIdForHost(deployConfig, host, env) {
|
|
822
1024
|
if (deployConfig.cloudflare.zoneId) {
|
|
@@ -955,7 +1157,7 @@ function reconcileCloudflareCacheRulesForTarget(role, deployConfig, state, cache
|
|
|
955
1157
|
state.webCache[role === "web" ? "webRulesetId" : "contentRulesetId"] = rulesetId;
|
|
956
1158
|
return { managed: true, zoneId, host: cacheTarget.host, rulesetId };
|
|
957
1159
|
}
|
|
958
|
-
function reconcileCloudflareWebCacheRules(tenantRoot, deployConfig, state, target, { dryRun = false } = {}) {
|
|
1160
|
+
function reconcileCloudflareWebCacheRules(tenantRoot, deployConfig, state, target, { dryRun = false, env: providedEnv } = {}) {
|
|
959
1161
|
if (!shouldManageCloudflareWebCacheRules(deployConfig, target)) {
|
|
960
1162
|
const webTarget2 = resolvePublicWebCacheTarget(deployConfig);
|
|
961
1163
|
const contentTarget2 = resolvePublicContentCacheTarget(deployConfig);
|
|
@@ -966,7 +1168,7 @@ function reconcileCloudflareWebCacheRules(tenantRoot, deployConfig, state, targe
|
|
|
966
1168
|
return { managed: false, skipped: true, reason: "unsupported_target_or_host" };
|
|
967
1169
|
}
|
|
968
1170
|
const env = {
|
|
969
|
-
CLOUDFLARE_API_TOKEN: process.env.CLOUDFLARE_API_TOKEN ?? ""
|
|
1171
|
+
CLOUDFLARE_API_TOKEN: providedEnv?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? ""
|
|
970
1172
|
};
|
|
971
1173
|
if (!env.CLOUDFLARE_API_TOKEN) {
|
|
972
1174
|
state.webCache.webHost = resolvePublicWebCacheTarget(deployConfig)?.host ?? null;
|
|
@@ -977,17 +1179,27 @@ function reconcileCloudflareWebCacheRules(tenantRoot, deployConfig, state, targe
|
|
|
977
1179
|
}
|
|
978
1180
|
const webTarget = resolvePublicWebCacheTarget(deployConfig);
|
|
979
1181
|
const contentTarget = resolvePublicContentCacheTarget(deployConfig);
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1182
|
+
try {
|
|
1183
|
+
const results = [];
|
|
1184
|
+
if (webTarget?.host) {
|
|
1185
|
+
results.push(reconcileCloudflareCacheRulesForTarget("web", deployConfig, state, webTarget, env, { dryRun }));
|
|
1186
|
+
}
|
|
1187
|
+
if (contentTarget?.host) {
|
|
1188
|
+
results.push(reconcileCloudflareCacheRulesForTarget("content", deployConfig, state, contentTarget, env, { dryRun }));
|
|
1189
|
+
}
|
|
1190
|
+
state.webCache.rulesManaged = true;
|
|
1191
|
+
state.webCache.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1192
|
+
state.webCache.lastError = null;
|
|
1193
|
+
return { managed: true, results };
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1196
|
+
if (/Authentication error|permission/i.test(message)) {
|
|
1197
|
+
state.webCache.rulesManaged = false;
|
|
1198
|
+
state.webCache.lastError = message;
|
|
1199
|
+
return { managed: false, skipped: true, reason: "auth_error", error: message };
|
|
1200
|
+
}
|
|
1201
|
+
throw error;
|
|
986
1202
|
}
|
|
987
|
-
state.webCache.rulesManaged = true;
|
|
988
|
-
state.webCache.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
989
|
-
state.webCache.lastError = null;
|
|
990
|
-
return { managed: true, results };
|
|
991
1203
|
}
|
|
992
1204
|
function purgeCloudflareCacheByUrls(urls, deployConfig, { env } = {}) {
|
|
993
1205
|
const uniqueUrls = [...new Set((urls ?? []).filter(Boolean))];
|
|
@@ -1111,23 +1323,14 @@ function resolveConfiguredCloudflareAccountId(deployConfig) {
|
|
|
1111
1323
|
function resolveConfiguredMarketBaseUrl(deployConfig) {
|
|
1112
1324
|
return envOrNull("TREESEED_MARKET_API_BASE_URL") ?? deployConfig.runtime?.marketBaseUrl ?? deployConfig.hosting?.marketBaseUrl ?? "";
|
|
1113
1325
|
}
|
|
1114
|
-
function resolveConfiguredHostedTeamId(deployConfig) {
|
|
1115
|
-
return envOrNull("TREESEED_HOSTING_TEAM_ID") ?? deployConfig.runtime?.teamId ?? deployConfig.hosting?.teamId ?? deployConfig.slug;
|
|
1116
|
-
}
|
|
1117
|
-
function resolveConfiguredProjectId(deployConfig) {
|
|
1118
|
-
return envOrNull("TREESEED_PROJECT_ID") ?? deployConfig.runtime?.projectId ?? deployConfig.hosting?.projectId ?? deployConfig.slug;
|
|
1119
|
-
}
|
|
1120
1326
|
function resolveConfiguredPagesProjectName(deployConfig) {
|
|
1121
|
-
return
|
|
1122
|
-
}
|
|
1123
|
-
function resolveConfiguredPagesPreviewProjectName(deployConfig) {
|
|
1124
|
-
return envOrNull("TREESEED_CLOUDFLARE_PAGES_PREVIEW_PROJECT_NAME") ?? deployConfig.cloudflare.pages?.previewProjectName ?? `${resolveConfiguredPagesProjectName(deployConfig)}-staging`;
|
|
1327
|
+
return sharedDeploymentName(resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget("prod")));
|
|
1125
1328
|
}
|
|
1126
1329
|
function resolveConfiguredContentBucketBinding(deployConfig) {
|
|
1127
1330
|
return envOrNull("TREESEED_CONTENT_BUCKET_BINDING") ?? deployConfig.cloudflare.r2?.binding ?? "TREESEED_CONTENT_BUCKET";
|
|
1128
1331
|
}
|
|
1129
1332
|
function resolveConfiguredContentBucketName(deployConfig) {
|
|
1130
|
-
return
|
|
1333
|
+
return sharedDeploymentName(resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget("prod")), "content");
|
|
1131
1334
|
}
|
|
1132
1335
|
function resolveConfiguredContentPublicBaseUrl(deployConfig) {
|
|
1133
1336
|
return envOrNull("TREESEED_CONTENT_PUBLIC_BASE_URL") ?? deployConfig.cloudflare.r2?.publicBaseUrl ?? "";
|
|
@@ -1581,6 +1784,7 @@ function provisionCloudflareResources(tenantRoot, options = {}) {
|
|
|
1581
1784
|
const exists = pagesProjects.find((entry) => entry?.name === current.projectName);
|
|
1582
1785
|
if (exists) {
|
|
1583
1786
|
current.url = exists.subdomain ? `https://${exists.subdomain}` : current.url ?? `https://${current.projectName}.pages.dev`;
|
|
1787
|
+
ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env, exists);
|
|
1584
1788
|
return;
|
|
1585
1789
|
}
|
|
1586
1790
|
if (dryRun) {
|
|
@@ -1599,6 +1803,7 @@ function provisionCloudflareResources(tenantRoot, options = {}) {
|
|
|
1599
1803
|
capture: true,
|
|
1600
1804
|
env
|
|
1601
1805
|
});
|
|
1806
|
+
ensurePagesProjectCompatibility(env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "", current.projectName, env);
|
|
1602
1807
|
current.url = `https://${current.projectName}.pages.dev`;
|
|
1603
1808
|
};
|
|
1604
1809
|
ensureKv("FORM_GUARD_KV");
|
|
@@ -1702,8 +1907,13 @@ function verifyProvisionedCloudflareResources(tenantRoot, options = {}) {
|
|
|
1702
1907
|
state.queues.agentWork.dlqId = queueId(liveDlq) ?? state.queues.agentWork.dlqId ?? null;
|
|
1703
1908
|
}
|
|
1704
1909
|
const livePages = pagesProjects.find((entry) => entry?.name === state.pages?.projectName);
|
|
1705
|
-
if (state.pages
|
|
1706
|
-
|
|
1910
|
+
if (state.pages) {
|
|
1911
|
+
const configuredWebUrl = resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web");
|
|
1912
|
+
if (configuredWebUrl) {
|
|
1913
|
+
state.pages.url = configuredWebUrl;
|
|
1914
|
+
} else if (livePages?.subdomain) {
|
|
1915
|
+
state.pages.url = target.kind === "persistent" && target.scope === "staging" ? `https://${state.pages.stagingBranch ?? "staging"}.${livePages.subdomain}` : `https://${livePages.subdomain}`;
|
|
1916
|
+
}
|
|
1707
1917
|
}
|
|
1708
1918
|
if (!dryRun) {
|
|
1709
1919
|
try {
|
|
@@ -1728,14 +1938,27 @@ function runRemoteD1Migrations(tenantRoot, options = {}) {
|
|
|
1728
1938
|
if (options.dryRun) {
|
|
1729
1939
|
return { databaseName: state.d1Databases.SITE_DATA_DB.databaseName, dryRun: true };
|
|
1730
1940
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1941
|
+
const args = ["d1", "migrations", "apply", state.d1Databases.SITE_DATA_DB.databaseName, "--remote", "--config", wranglerPath];
|
|
1942
|
+
const env = { CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig) };
|
|
1943
|
+
const isTransient = (output) => /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted|internal error/i.test(output || "");
|
|
1944
|
+
let lastOutput = "";
|
|
1945
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
1946
|
+
const result = runWrangler(args, {
|
|
1734
1947
|
cwd: tenantRoot,
|
|
1735
|
-
env
|
|
1948
|
+
env,
|
|
1949
|
+
capture: true,
|
|
1950
|
+
allowFailure: true
|
|
1951
|
+
});
|
|
1952
|
+
if (result.status === 0) {
|
|
1953
|
+
return { databaseName: state.d1Databases.SITE_DATA_DB.databaseName, dryRun: false };
|
|
1736
1954
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1955
|
+
lastOutput = [result.stderr?.trim(), result.stdout?.trim()].filter(Boolean).join("\n");
|
|
1956
|
+
if (!isTransient(lastOutput) || attempt === 3) {
|
|
1957
|
+
throw new Error(lastOutput || `Wrangler command failed: ${args.join(" ")}`);
|
|
1958
|
+
}
|
|
1959
|
+
sleepSync(2e3 * attempt);
|
|
1960
|
+
}
|
|
1961
|
+
throw new Error(lastOutput || `Wrangler command failed: ${args.join(" ")}`);
|
|
1739
1962
|
}
|
|
1740
1963
|
function markDeploymentInitialized(tenantRoot, options = {}) {
|
|
1741
1964
|
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
@@ -1787,7 +2010,7 @@ function finalizeDeploymentState(tenantRoot, options = {}) {
|
|
|
1787
2010
|
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
1788
2011
|
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
1789
2012
|
state.lastManifestFingerprint = stableHash(JSON.stringify({ deployConfig, targetKey: targetKey(target) }));
|
|
1790
|
-
state.lastDeployedUrl = target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : deployConfig
|
|
2013
|
+
state.lastDeployedUrl = target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : resolveConfiguredSurfaceBaseUrl(deployConfig, target, "web");
|
|
1791
2014
|
state.lastDeploymentTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1792
2015
|
state.lastDeployedCommit = envOrNull("GITHUB_SHA") ?? envOrNull("TREESEED_DEPLOY_COMMIT") ?? null;
|
|
1793
2016
|
state.runtimeCompatibility = {
|
|
@@ -1863,15 +2086,27 @@ function printDestroySummary(result) {
|
|
|
1863
2086
|
}
|
|
1864
2087
|
export {
|
|
1865
2088
|
assertDeploymentInitialized,
|
|
2089
|
+
buildProvisioningSummary,
|
|
2090
|
+
buildPublicVars,
|
|
2091
|
+
buildSecretMap,
|
|
1866
2092
|
buildWranglerConfigContents,
|
|
1867
2093
|
cleanupDestroyedState,
|
|
2094
|
+
cloudflareApiRequest,
|
|
1868
2095
|
collectMissingDeployInputs,
|
|
1869
2096
|
createBranchPreviewDeployTarget,
|
|
1870
2097
|
createPersistentDeployTarget,
|
|
1871
2098
|
deployTargetLabel,
|
|
2099
|
+
deriveTreeseedStagingSurfaceDomain,
|
|
1872
2100
|
destroyCloudflareResources,
|
|
1873
2101
|
ensureGeneratedWranglerConfig,
|
|
1874
2102
|
finalizeDeploymentState,
|
|
2103
|
+
hasProvisionedCloudflareResources,
|
|
2104
|
+
isWranglerAlreadyExistsError,
|
|
2105
|
+
listD1Databases,
|
|
2106
|
+
listKvNamespaces,
|
|
2107
|
+
listPagesProjects,
|
|
2108
|
+
listQueues,
|
|
2109
|
+
listR2Buckets,
|
|
1875
2110
|
loadDeployState,
|
|
1876
2111
|
markDeploymentInitialized,
|
|
1877
2112
|
markManagedServicesInitialized,
|
|
@@ -1882,8 +2117,17 @@ export {
|
|
|
1882
2117
|
provisionCloudflareResources,
|
|
1883
2118
|
purgePublishedContentCaches,
|
|
1884
2119
|
purgeSourcePageCaches,
|
|
2120
|
+
queueId,
|
|
2121
|
+
queueName,
|
|
2122
|
+
reconcileCloudflareWebCacheRules,
|
|
2123
|
+
resolveCloudflareZoneIdForHost,
|
|
2124
|
+
resolveConfiguredCloudflareAccountId,
|
|
2125
|
+
resolveConfiguredSurfaceBaseUrl,
|
|
2126
|
+
resolveConfiguredSurfaceDomain,
|
|
1885
2127
|
resolveGeneratedWranglerPath,
|
|
2128
|
+
resolveTreeseedResourceIdentity,
|
|
1886
2129
|
runRemoteD1Migrations,
|
|
2130
|
+
runWrangler,
|
|
1887
2131
|
scopeFromTarget,
|
|
1888
2132
|
syncCloudflareSecrets,
|
|
1889
2133
|
validateDeployPrerequisites,
|