@treeseed/sdk 0.5.3 → 0.6.1

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.
Files changed (66) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +46 -0
  3. package/dist/operations/providers/default.js +1 -1
  4. package/dist/operations/services/config-runtime.d.ts +49 -42
  5. package/dist/operations/services/config-runtime.js +449 -136
  6. package/dist/operations/services/deploy.d.ts +298 -0
  7. package/dist/operations/services/deploy.js +381 -137
  8. package/dist/operations/services/git-workflow.d.ts +9 -0
  9. package/dist/operations/services/git-workflow.js +32 -0
  10. package/dist/operations/services/github-api.d.ts +115 -0
  11. package/dist/operations/services/github-api.js +455 -0
  12. package/dist/operations/services/github-automation.d.ts +19 -33
  13. package/dist/operations/services/github-automation.js +44 -131
  14. package/dist/operations/services/key-agent.d.ts +20 -1
  15. package/dist/operations/services/key-agent.js +267 -102
  16. package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
  17. package/dist/operations/services/knowledge-coop-launch.js +26 -12
  18. package/dist/operations/services/project-platform.d.ts +157 -150
  19. package/dist/operations/services/project-platform.js +129 -26
  20. package/dist/operations/services/railway-api.d.ts +244 -0
  21. package/dist/operations/services/railway-api.js +882 -0
  22. package/dist/operations/services/railway-deploy.d.ts +171 -27
  23. package/dist/operations/services/railway-deploy.js +672 -172
  24. package/dist/operations/services/runtime-tools.d.ts +18 -0
  25. package/dist/operations/services/runtime-tools.js +19 -6
  26. package/dist/operations/services/workspace-preflight.js +2 -2
  27. package/dist/platform/contracts.d.ts +7 -0
  28. package/dist/platform/deploy-config.js +23 -0
  29. package/dist/platform/deploy-runtime.d.ts +1 -0
  30. package/dist/platform/deploy-runtime.js +7 -9
  31. package/dist/platform/env.yaml +10 -9
  32. package/dist/platform/environment.js +4 -0
  33. package/dist/platform/plugin.d.ts +6 -0
  34. package/dist/platform/plugins/constants.d.ts +1 -0
  35. package/dist/platform/plugins/constants.js +1 -0
  36. package/dist/platform/plugins/runtime.d.ts +4 -0
  37. package/dist/platform/plugins/runtime.js +8 -1
  38. package/dist/platform/published-content.js +27 -4
  39. package/dist/platform/tenant/runtime-config.js +33 -24
  40. package/dist/plugin-default.d.ts +1 -0
  41. package/dist/plugin-default.js +1 -0
  42. package/dist/reconcile/builtin-adapters.d.ts +3 -0
  43. package/dist/reconcile/builtin-adapters.js +2116 -0
  44. package/dist/reconcile/contracts.d.ts +155 -0
  45. package/dist/reconcile/contracts.js +0 -0
  46. package/dist/reconcile/desired-state.d.ts +179 -0
  47. package/dist/reconcile/desired-state.js +319 -0
  48. package/dist/reconcile/engine.d.ts +405 -0
  49. package/dist/reconcile/engine.js +356 -0
  50. package/dist/reconcile/errors.d.ts +5 -0
  51. package/dist/reconcile/errors.js +13 -0
  52. package/dist/reconcile/index.d.ts +7 -0
  53. package/dist/reconcile/index.js +7 -0
  54. package/dist/reconcile/registry.d.ts +7 -0
  55. package/dist/reconcile/registry.js +64 -0
  56. package/dist/reconcile/state.d.ts +7 -0
  57. package/dist/reconcile/state.js +303 -0
  58. package/dist/reconcile/units.d.ts +6 -0
  59. package/dist/reconcile/units.js +68 -0
  60. package/dist/scripts/config-treeseed.js +27 -19
  61. package/dist/scripts/tenant-deploy.js +35 -14
  62. package/dist/workflow/operations.js +127 -22
  63. package/dist/workflow-support.d.ts +3 -1
  64. package/dist/workflow-support.js +50 -0
  65. package/dist/workflow.d.ts +2 -0
  66. 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 { deriveCloudflareWorkerName, resolveTreeseedWebCachePolicy } from "../../platform/deploy-config.js";
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 baseName = deriveCloudflareWorkerName(deployConfig);
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
- if (target.scope === "prod") {
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 = resolveConfiguredHostedTeamId(deployConfig);
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: resolveConfiguredProjectId(deployConfig),
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
- TREESEED_SMTP_HOST: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_HOST") : null,
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 = resolveConfiguredHostedTeamId(deployConfig);
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: `${workerName}-form-guard`,
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: `${workerName}-session`,
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: `${workerName}-site-data`,
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: targetScopedResourceName(deployConfig.cloudflare.queueName ?? "agent-work", target),
255
- dlqName: targetScopedResourceName(deployConfig.cloudflare.dlqName ?? "agent-work-dlq", target),
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: target.kind === "persistent" ? target.scope === "prod" ? resolveConfiguredPagesProjectName(deployConfig) : resolveConfiguredPagesPreviewProjectName(deployConfig) : `${resolveConfiguredPagesProjectName(deployConfig)}-preview`,
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: null
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: contentDefaultTeamId,
287
- projectId: resolveConfiguredProjectId(deployConfig)
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: contentDefaultTeamId,
297
- projectId: resolveConfiguredProjectId(deployConfig)
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) : null,
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 ?? null,
448
+ projectName: serviceConfig?.railway?.projectName ?? sharedDeploymentName(identity),
351
449
  serviceId: serviceConfig?.railway?.serviceId ?? null,
352
- serviceName: serviceConfig?.railway?.serviceName ?? null,
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 ?? deployConfig.slug,
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/${deployConfig.slug}/published/common.json`,
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 ?? deployConfig.slug,
451
- projectId: defaults.hosting?.projectId ?? persisted.hosting?.projectId ?? deployConfig.slug
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 ?? deployConfig.slug,
465
- projectId: defaults.runtime?.projectId ?? persisted.runtime?.projectId ?? deployConfig.slug
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: persisted.pages?.url ?? defaults.pages?.url ?? null
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: persistedService.lastDeployedUrl ?? defaultService.publicBaseUrl ?? null,
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 result = runWrangler(["kv", "namespace", "list"], {
664
- cwd: tenantRoot,
665
- capture: true,
666
- env
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 parseWranglerJsonOutput(result, "KV namespace list");
785
+ return Array.isArray(payload?.result) ? payload.result : [];
669
786
  }
670
787
  function listD1Databases(tenantRoot, env) {
671
- const result = runWrangler(["d1", "list", "--json"], {
672
- cwd: tenantRoot,
673
- capture: true,
674
- env
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 parseWranglerJsonOutput(result, "D1 list");
796
+ return Array.isArray(payload?.result) ? payload.result : [];
677
797
  }
678
798
  function listQueues(tenantRoot, env) {
679
- const result = runWrangler(["queues", "list", "--json"], {
680
- cwd: tenantRoot,
681
- capture: true,
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.status === 0 ? parseWranglerJsonOutput(result, "Queues list") : [];
807
+ return Array.isArray(payload?.result) ? payload.result : [];
686
808
  }
687
809
  function listR2Buckets(tenantRoot, env) {
688
- const result = runWrangler(["r2", "bucket", "list", "--json"], {
689
- cwd: tenantRoot,
690
- capture: true,
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.status === 0 ? parseWranglerJsonOutput(result, "R2 bucket list") : [];
818
+ return Array.isArray(payload?.result?.buckets) ? payload.result.buckets : [];
695
819
  }
696
820
  function listPagesProjects(tenantRoot, env) {
697
- const result = runWrangler(["pages", "project", "list", "--json"], {
698
- cwd: tenantRoot,
699
- capture: true,
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.status === 0 ? parseWranglerJsonOutput(result, "Pages project list") : [];
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 response = spawnSync(
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 payload = await response.json().catch(async () => ({ success: false, errors: [{ message: await response.text() }] }));
797
- process.stdout.write(JSON.stringify({ ok: response.ok, payload }));`
798
- ],
799
- {
800
- stdio: ["pipe", "pipe", "pipe"],
801
- encoding: "utf8",
802
- env: { ...process.env, ...env ?? {} },
803
- input: JSON.stringify({
804
- url: `https://api.cloudflare.com/client/v4${path}`,
805
- method,
806
- body,
807
- token: env?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? ""
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
- if (response.status !== 0 && !allowFailure) {
812
- throw new Error(response.stderr?.trim() || `Cloudflare API request failed: ${method} ${path}`);
813
- }
814
- const parsed = JSON.parse(response.stdout?.trim() || '{"ok":false,"payload":{"success":false,"errors":[{"message":"empty response"}]}}');
815
- if (!parsed.ok && !allowFailure) {
816
- const details = Array.isArray(parsed.payload?.errors) ? parsed.payload.errors.map((entry) => entry?.message ?? JSON.stringify(entry)).join("; ") : "unknown error";
817
- throw new Error(details || `Cloudflare API request failed: ${method} ${path}`);
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
- const results = [];
981
- if (webTarget?.host) {
982
- results.push(reconcileCloudflareCacheRulesForTarget("web", deployConfig, state, webTarget, env, { dryRun }));
983
- }
984
- if (contentTarget?.host) {
985
- results.push(reconcileCloudflareCacheRulesForTarget("content", deployConfig, state, contentTarget, env, { dryRun }));
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 envOrNull("TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME") ?? deployConfig.cloudflare.pages?.projectName ?? deployConfig.slug;
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 envOrNull("TREESEED_CONTENT_BUCKET_NAME") ?? deployConfig.cloudflare.r2?.bucketName ?? `${deployConfig.slug}-content`;
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 && livePages?.subdomain) {
1706
- state.pages.url = `https://${livePages.subdomain}`;
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
- runWrangler(
1732
- ["d1", "migrations", "apply", state.d1Databases.SITE_DATA_DB.databaseName, "--remote", "--config", wranglerPath],
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: { CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig) }
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
- return { databaseName: state.d1Databases.SITE_DATA_DB.databaseName, dryRun: false };
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.siteUrl;
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,