@treeseed/sdk 0.1.1 → 0.3.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.
Files changed (228) hide show
  1. package/README.md +97 -494
  2. package/dist/{src/cli-tools.d.ts → cli-tools.d.ts} +1 -1
  3. package/dist/cli-tools.js +5 -3
  4. package/dist/{src/content-store.d.ts → content-store.d.ts} +3 -2
  5. package/dist/content-store.js +52 -20
  6. package/dist/{src/d1-store.d.ts → d1-store.d.ts} +62 -1
  7. package/dist/d1-store.js +625 -65
  8. package/dist/field-aliases.d.ts +11 -0
  9. package/dist/field-aliases.js +41 -0
  10. package/dist/graph/build.d.ts +19 -0
  11. package/dist/graph/build.js +949 -0
  12. package/dist/graph/dsl.d.ts +2 -0
  13. package/dist/graph/dsl.js +243 -0
  14. package/dist/graph/query.d.ts +47 -0
  15. package/dist/graph/query.js +447 -0
  16. package/dist/graph/ranking.d.ts +3 -0
  17. package/dist/graph/ranking.js +483 -0
  18. package/dist/graph/schema.d.ts +142 -0
  19. package/dist/graph/schema.js +133 -0
  20. package/dist/graph.d.ts +52 -0
  21. package/dist/graph.js +133 -0
  22. package/dist/index.d.ts +27 -0
  23. package/dist/index.js +90 -2
  24. package/dist/model-registry.d.ts +8 -0
  25. package/dist/model-registry.js +351 -25
  26. package/dist/operations/providers/default.d.ts +10 -0
  27. package/dist/operations/providers/default.js +514 -0
  28. package/dist/operations/runtime.d.ts +7 -0
  29. package/dist/operations/runtime.js +60 -0
  30. package/dist/operations/services/config-runtime.d.ts +269 -0
  31. package/dist/operations/services/config-runtime.js +1397 -0
  32. package/dist/operations/services/d1-migration.d.ts +6 -0
  33. package/dist/operations/services/d1-migration.js +89 -0
  34. package/dist/operations/services/deploy.d.ts +371 -0
  35. package/dist/operations/services/deploy.js +981 -0
  36. package/dist/operations/services/git-workflow.d.ts +49 -0
  37. package/dist/operations/services/git-workflow.js +218 -0
  38. package/dist/operations/services/github-automation.d.ts +156 -0
  39. package/dist/operations/services/github-automation.js +256 -0
  40. package/dist/operations/services/local-dev.d.ts +9 -0
  41. package/dist/operations/services/local-dev.js +106 -0
  42. package/dist/operations/services/mailpit-runtime.d.ts +4 -0
  43. package/dist/operations/services/mailpit-runtime.js +59 -0
  44. package/dist/operations/services/railway-deploy.d.ts +53 -0
  45. package/dist/operations/services/railway-deploy.js +123 -0
  46. package/dist/operations/services/runtime-paths.d.ts +19 -0
  47. package/dist/operations/services/runtime-paths.js +54 -0
  48. package/dist/operations/services/runtime-tools.d.ts +117 -0
  49. package/dist/operations/services/runtime-tools.js +358 -0
  50. package/dist/operations/services/save-deploy-preflight.d.ts +34 -0
  51. package/dist/operations/services/save-deploy-preflight.js +76 -0
  52. package/dist/operations/services/template-registry.d.ts +88 -0
  53. package/dist/operations/services/template-registry.js +407 -0
  54. package/dist/operations/services/watch-dev.d.ts +21 -0
  55. package/dist/operations/services/watch-dev.js +284 -0
  56. package/dist/operations/services/workspace-preflight.d.ts +40 -0
  57. package/dist/operations/services/workspace-preflight.js +165 -0
  58. package/dist/operations/services/workspace-save.d.ts +42 -0
  59. package/dist/operations/services/workspace-save.js +235 -0
  60. package/dist/operations/services/workspace-tools.d.ts +16 -0
  61. package/dist/operations/services/workspace-tools.js +270 -0
  62. package/dist/operations-registry.d.ts +5 -0
  63. package/dist/operations-registry.js +68 -0
  64. package/dist/operations-types.d.ts +71 -0
  65. package/dist/operations-types.js +17 -0
  66. package/dist/operations.d.ts +6 -0
  67. package/dist/operations.js +16 -0
  68. package/dist/platform/books-data.d.ts +1 -0
  69. package/dist/platform/books-data.js +1 -0
  70. package/dist/platform/contracts.d.ts +158 -0
  71. package/dist/platform/contracts.js +0 -0
  72. package/dist/platform/deploy/config.d.ts +4 -0
  73. package/dist/platform/deploy/config.js +222 -0
  74. package/dist/platform/deploy-config.d.ts +1 -0
  75. package/dist/platform/deploy-config.js +1 -0
  76. package/dist/platform/env.yaml +394 -0
  77. package/dist/platform/environment.d.ts +130 -0
  78. package/dist/platform/environment.js +331 -0
  79. package/dist/platform/plugin.d.ts +2 -0
  80. package/dist/platform/plugin.js +4 -0
  81. package/dist/platform/plugins/constants.d.ts +22 -0
  82. package/dist/platform/plugins/constants.js +29 -0
  83. package/dist/platform/plugins/plugin.d.ts +51 -0
  84. package/dist/platform/plugins/plugin.js +6 -0
  85. package/dist/platform/plugins/runtime.d.ts +35 -0
  86. package/dist/platform/plugins/runtime.js +142 -0
  87. package/dist/platform/plugins.d.ts +5 -0
  88. package/dist/platform/plugins.js +16 -0
  89. package/dist/platform/site-config-schema.js +1 -0
  90. package/dist/platform/tenant/config.d.ts +9 -0
  91. package/dist/platform/tenant/config.js +154 -0
  92. package/dist/platform/tenant/runtime-config.d.ts +4 -0
  93. package/dist/platform/tenant/runtime-config.js +20 -0
  94. package/dist/platform/tenant-config.d.ts +1 -0
  95. package/dist/platform/tenant-config.js +1 -0
  96. package/dist/platform/utils/books-data.d.ts +29 -0
  97. package/dist/platform/utils/books-data.js +82 -0
  98. package/dist/platform/utils/site-config-schema.js +321 -0
  99. package/dist/remote.d.ts +175 -0
  100. package/dist/remote.js +202 -0
  101. package/dist/runtime.js +50 -3
  102. package/dist/scripts/aggregate-book.js +121 -0
  103. package/dist/scripts/build-dist.js +57 -13
  104. package/dist/scripts/build-tenant-worker.js +36 -0
  105. package/dist/scripts/cleanup-markdown.js +373 -0
  106. package/dist/scripts/cli-test-fixtures.js +48 -0
  107. package/dist/scripts/config-treeseed.js +95 -0
  108. package/dist/scripts/ensure-mailpit.js +29 -0
  109. package/dist/scripts/local-dev.js +129 -0
  110. package/dist/scripts/logs-mailpit.js +2 -0
  111. package/dist/scripts/patch-starlight-content-path.js +172 -0
  112. package/dist/scripts/release-verify.js +34 -5
  113. package/dist/scripts/run-fixture-astro-command.js +18 -0
  114. package/dist/scripts/scaffold-site.js +65 -0
  115. package/dist/scripts/stop-mailpit.js +5 -0
  116. package/dist/scripts/sync-dev-vars.js +6 -0
  117. package/dist/scripts/sync-template.js +20 -0
  118. package/dist/scripts/template-catalog.test.js +100 -0
  119. package/dist/scripts/template-command.js +31 -0
  120. package/dist/scripts/tenant-astro-command.js +3 -0
  121. package/dist/scripts/tenant-build.js +16 -0
  122. package/dist/scripts/tenant-check.js +7 -0
  123. package/dist/scripts/tenant-d1-migrate-local.js +11 -0
  124. package/dist/scripts/tenant-deploy.js +180 -0
  125. package/dist/scripts/tenant-destroy.js +104 -0
  126. package/dist/scripts/tenant-dev.js +171 -0
  127. package/dist/scripts/tenant-lint.js +4 -0
  128. package/dist/scripts/tenant-test.js +4 -0
  129. package/dist/scripts/test-cloudflare-local.js +212 -0
  130. package/dist/scripts/test-scaffold.js +314 -0
  131. package/dist/scripts/test-smoke.js +71 -13
  132. package/dist/scripts/treeseed-assert-release-tag-version.js +21 -0
  133. package/dist/scripts/treeseed-build-dist.js +134 -0
  134. package/dist/scripts/treeseed-publish-package.js +19 -0
  135. package/dist/scripts/treeseed-release-verify.js +131 -0
  136. package/dist/scripts/treeseed-run-ts.js +45 -0
  137. package/dist/scripts/validate-templates.js +6 -0
  138. package/dist/scripts/verify-driver.js +29 -0
  139. package/dist/scripts/workflow-commands.test.js +39 -0
  140. package/dist/scripts/workspace-close.js +24 -0
  141. package/dist/scripts/workspace-command-e2e.js +718 -0
  142. package/dist/scripts/workspace-lint.js +9 -0
  143. package/dist/scripts/workspace-preflight.js +22 -0
  144. package/dist/scripts/workspace-publish-changed-packages.js +16 -0
  145. package/dist/scripts/workspace-release-verify.js +81 -0
  146. package/dist/scripts/workspace-release.js +42 -0
  147. package/dist/scripts/workspace-save.js +124 -0
  148. package/dist/scripts/workspace-start-warning.js +3 -0
  149. package/dist/scripts/workspace-start.js +71 -0
  150. package/dist/scripts/workspace-test-unit.js +4 -0
  151. package/dist/scripts/workspace-test.js +11 -0
  152. package/dist/sdk-fields.d.ts +11 -0
  153. package/dist/sdk-fields.js +169 -0
  154. package/dist/sdk-filters.d.ts +4 -0
  155. package/dist/sdk-filters.js +12 -15
  156. package/dist/sdk-types.d.ts +796 -0
  157. package/dist/sdk-types.js +7 -1
  158. package/dist/sdk-version.d.ts +2 -0
  159. package/dist/sdk-version.js +42 -0
  160. package/dist/sdk.d.ts +215 -0
  161. package/dist/sdk.js +235 -11
  162. package/dist/stores/cursor-store.js +9 -3
  163. package/dist/stores/lease-store.js +8 -2
  164. package/dist/{src/stores → stores}/message-store.d.ts +1 -1
  165. package/dist/stores/message-store.js +27 -3
  166. package/dist/stores/operational-store.d.ts +24 -0
  167. package/dist/stores/operational-store.js +279 -0
  168. package/dist/stores/run-store.js +8 -1
  169. package/dist/stores/subscription-store.js +7 -5
  170. package/dist/template-catalog.d.ts +13 -0
  171. package/dist/template-catalog.js +141 -0
  172. package/dist/treeseed/services/compose.yml +7 -0
  173. package/dist/treeseed/template-catalog/catalog.fixture.json +55 -0
  174. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +2 -0
  175. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +3 -0
  176. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +32 -0
  177. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +40 -0
  178. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +1 -0
  179. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +11 -0
  180. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +11 -0
  181. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +1 -0
  182. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +3 -0
  183. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +1 -0
  184. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +19 -0
  185. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +26 -0
  186. package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +9 -0
  187. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +90 -0
  188. package/dist/verification.d.ts +20 -0
  189. package/dist/verification.js +98 -0
  190. package/dist/workflow/operations.d.ts +396 -0
  191. package/dist/workflow/operations.js +841 -0
  192. package/dist/workflow-state.d.ts +56 -0
  193. package/dist/workflow-state.js +195 -0
  194. package/dist/workflow-support.d.ts +9 -0
  195. package/dist/workflow-support.js +176 -0
  196. package/dist/workflow.d.ts +111 -0
  197. package/dist/workflow.js +97 -0
  198. package/package.json +97 -5
  199. package/scripts/verify-driver.mjs +29 -0
  200. package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +0 -22
  201. package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +0 -126
  202. package/dist/scripts/assert-release-tag-version.d.ts +0 -1
  203. package/dist/scripts/build-dist.d.ts +0 -1
  204. package/dist/scripts/package-tools.d.ts +0 -15
  205. package/dist/scripts/publish-package.d.ts +0 -1
  206. package/dist/scripts/release-verify.d.ts +0 -1
  207. package/dist/scripts/test-smoke.d.ts +0 -1
  208. package/dist/src/index.d.ts +0 -6
  209. package/dist/src/model-registry.d.ts +0 -4
  210. package/dist/src/sdk-filters.d.ts +0 -4
  211. package/dist/src/sdk-types.d.ts +0 -285
  212. package/dist/src/sdk.d.ts +0 -109
  213. package/dist/test/test-fixture.d.ts +0 -1
  214. package/dist/test/utils/envelopes.test.d.ts +0 -1
  215. package/dist/test/utils/sdk.test.d.ts +0 -1
  216. package/dist/vitest.config.d.ts +0 -2
  217. /package/dist/{src/frontmatter.d.ts → frontmatter.d.ts} +0 -0
  218. /package/dist/{src/git-runtime.d.ts → git-runtime.d.ts} +0 -0
  219. /package/dist/{src/runtime.d.ts → runtime.d.ts} +0 -0
  220. /package/dist/{src/stores → stores}/cursor-store.d.ts +0 -0
  221. /package/dist/{src/stores → stores}/envelopes.d.ts +0 -0
  222. /package/dist/{src/stores → stores}/helpers.d.ts +0 -0
  223. /package/dist/{src/stores → stores}/lease-store.d.ts +0 -0
  224. /package/dist/{src/stores → stores}/run-store.d.ts +0 -0
  225. /package/dist/{src/stores → stores}/subscription-store.d.ts +0 -0
  226. /package/dist/{src/types → types}/agents.d.ts +0 -0
  227. /package/dist/{src/types → types}/cloudflare.d.ts +0 -0
  228. /package/dist/{src/wrangler-d1.d.ts → wrangler-d1.d.ts} +0 -0
@@ -0,0 +1,981 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { deriveCloudflareWorkerName } from "../../platform/deploy/config.js";
7
+ import { loadCliDeployConfig, resolveWranglerBin } from "./runtime-tools.js";
8
+ const DEFAULT_COMPATIBILITY_DATE = "2026-04-05";
9
+ const DEFAULT_COMPATIBILITY_FLAGS = ["nodejs_compat"];
10
+ const GENERATED_ROOT = ".treeseed/generated";
11
+ const STATE_ROOT = ".treeseed/state";
12
+ const PERSISTENT_SCOPES = /* @__PURE__ */ new Set(["local", "staging", "prod"]);
13
+ const MANAGED_SERVICE_KEYS = ["api", "agents", "gateway", "manager", "worker", "workdayStart", "workdayReport"];
14
+ const TRESEED_ENVELOPE_SCHEMA_GENERATION = "runtime-envelopes-v1";
15
+ const TRESEED_MIGRATION_WAVE_ID = "0005_runtime_envelopes";
16
+ const TRESEED_SUPPORTED_PAYLOAD_RANGE = { min: 1, max: 1 };
17
+ function ensureParent(filePath) {
18
+ mkdirSync(dirname(filePath), { recursive: true });
19
+ }
20
+ function stableHash(value) {
21
+ return createHash("sha256").update(value).digest("hex");
22
+ }
23
+ function readJson(filePath, fallback) {
24
+ if (!existsSync(filePath)) {
25
+ return fallback;
26
+ }
27
+ try {
28
+ return JSON.parse(readFileSync(filePath, "utf8"));
29
+ } catch {
30
+ return fallback;
31
+ }
32
+ }
33
+ function writeJson(filePath, value) {
34
+ ensureParent(filePath);
35
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
36
+ `, "utf8");
37
+ }
38
+ function renderTomlString(value) {
39
+ return JSON.stringify(String(value));
40
+ }
41
+ function envOrNull(key) {
42
+ const value = process.env[key];
43
+ return typeof value === "string" && value.length ? value : null;
44
+ }
45
+ function loadTenantDeployConfig(tenantRoot) {
46
+ return loadCliDeployConfig(tenantRoot);
47
+ }
48
+ function sanitizeSegment(value) {
49
+ return String(value).trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 36) || "default";
50
+ }
51
+ function normalizePersistentScope(scope = "prod") {
52
+ if (!PERSISTENT_SCOPES.has(scope)) {
53
+ throw new Error(`Unsupported Treeseed environment "${scope}". Expected one of local, staging, prod.`);
54
+ }
55
+ return scope;
56
+ }
57
+ function createPersistentDeployTarget(scope = "prod") {
58
+ return {
59
+ kind: "persistent",
60
+ scope: normalizePersistentScope(scope)
61
+ };
62
+ }
63
+ function createBranchPreviewDeployTarget(branchName) {
64
+ const normalized = String(branchName ?? "").trim();
65
+ if (!normalized) {
66
+ throw new Error("Branch preview target requires a branch name.");
67
+ }
68
+ return {
69
+ kind: "branch",
70
+ branchName: normalized
71
+ };
72
+ }
73
+ function normalizeTarget(scopeOrTarget = "prod") {
74
+ if (!scopeOrTarget || typeof scopeOrTarget === "string") {
75
+ return createPersistentDeployTarget(scopeOrTarget ?? "prod");
76
+ }
77
+ if (scopeOrTarget.kind === "persistent") {
78
+ return createPersistentDeployTarget(scopeOrTarget.scope);
79
+ }
80
+ if (scopeOrTarget.kind === "branch") {
81
+ return createBranchPreviewDeployTarget(scopeOrTarget.branchName);
82
+ }
83
+ throw new Error("Unsupported Treeseed deployment target.");
84
+ }
85
+ function scopeFromTarget(target) {
86
+ return target.kind === "persistent" ? target.scope : "staging";
87
+ }
88
+ function targetDirectoryParts(target) {
89
+ if (target.kind === "persistent") {
90
+ return ["environments", target.scope];
91
+ }
92
+ return ["branches", sanitizeSegment(target.branchName)];
93
+ }
94
+ function targetKey(target) {
95
+ return target.kind === "persistent" ? target.scope : `branch:${target.branchName}`;
96
+ }
97
+ function resolveTargetPaths(tenantRoot, scopeOrTarget = "prod") {
98
+ const target = normalizeTarget(scopeOrTarget);
99
+ const pathParts = targetDirectoryParts(target);
100
+ const generatedRoot = resolve(tenantRoot, GENERATED_ROOT, ...pathParts);
101
+ const statePath = resolve(tenantRoot, STATE_ROOT, ...pathParts, "deploy.json");
102
+ return {
103
+ target,
104
+ generatedRoot,
105
+ wranglerPath: resolve(generatedRoot, "wrangler.toml"),
106
+ workerEntryPath: resolve(generatedRoot, "worker/index.js"),
107
+ statePath
108
+ };
109
+ }
110
+ function deployTargetLabel(scopeOrTarget = "prod") {
111
+ const target = normalizeTarget(scopeOrTarget);
112
+ return target.kind === "persistent" ? target.scope : `branch:${target.branchName}`;
113
+ }
114
+ function targetWorkerName(deployConfig, target) {
115
+ const baseName = deriveCloudflareWorkerName(deployConfig);
116
+ if (target.kind === "persistent") {
117
+ if (target.scope === "prod") {
118
+ return baseName;
119
+ }
120
+ return `${baseName}-${target.scope}`;
121
+ }
122
+ return `${baseName}-${sanitizeSegment(target.branchName)}`;
123
+ }
124
+ function targetWorkersDevUrl(workerName) {
125
+ return `https://${workerName}.workers.dev`;
126
+ }
127
+ function relativeFromGeneratedRoot(targetPath, generatedRoot) {
128
+ return relative(generatedRoot, targetPath).replaceAll("\\", "/");
129
+ }
130
+ function buildPublicVars(deployConfig) {
131
+ return {
132
+ TREESEED_AGENT_EXECUTION_PROVIDER: deployConfig.providers?.agents?.execution ?? "stub",
133
+ TREESEED_AGENT_REPOSITORY_PROVIDER: deployConfig.providers?.agents?.repository ?? "stub",
134
+ TREESEED_AGENT_VERIFICATION_PROVIDER: deployConfig.providers?.agents?.verification ?? "stub",
135
+ TREESEED_PUBLIC_TURNSTILE_SITE_KEY: envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY") ?? ""
136
+ };
137
+ }
138
+ function buildSecretMap(deployConfig, state) {
139
+ const generatedSecret = state.generatedSecrets?.TREESEED_FORM_TOKEN_SECRET ?? randomBytes(24).toString("hex");
140
+ return {
141
+ TREESEED_FORM_TOKEN_SECRET: envOrNull("TREESEED_FORM_TOKEN_SECRET") ?? generatedSecret,
142
+ TREESEED_TURNSTILE_SECRET_KEY: envOrNull("TREESEED_TURNSTILE_SECRET_KEY"),
143
+ TREESEED_SMTP_HOST: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_HOST") : null,
144
+ TREESEED_SMTP_PORT: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_PORT") : null,
145
+ TREESEED_SMTP_USERNAME: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_USERNAME") : null,
146
+ TREESEED_SMTP_PASSWORD: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_PASSWORD") : null,
147
+ TREESEED_SMTP_FROM: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_FROM") : null,
148
+ TREESEED_SMTP_REPLY_TO: deployConfig.smtp?.enabled ? envOrNull("TREESEED_SMTP_REPLY_TO") : null
149
+ };
150
+ }
151
+ function defaultStateFromConfig(deployConfig, target) {
152
+ const workerName = targetWorkerName(deployConfig, target);
153
+ const suffix = target.kind === "persistent" ? target.scope : sanitizeSegment(target.branchName);
154
+ return {
155
+ version: 2,
156
+ target,
157
+ previewEnabled: target.kind === "branch",
158
+ workerName,
159
+ kvNamespaces: {
160
+ FORM_GUARD_KV: {
161
+ name: `${workerName}-form-guard`,
162
+ id: `dryrun-${suffix}-form-guard`,
163
+ previewId: `dryrun-${suffix}-form-guard-preview`
164
+ },
165
+ SESSION: {
166
+ name: `${workerName}-session`,
167
+ id: `dryrun-${suffix}-session`,
168
+ previewId: `dryrun-${suffix}-session-preview`
169
+ }
170
+ },
171
+ d1Databases: {
172
+ SITE_DATA_DB: {
173
+ databaseName: `${workerName}-site-data`,
174
+ databaseId: `dryrun-${suffix}-site-data`,
175
+ previewDatabaseId: `dryrun-${suffix}-site-data-preview`
176
+ }
177
+ },
178
+ queues: {
179
+ agentWork: {
180
+ name: deployConfig.cloudflare.queueName ?? "agent-work",
181
+ dlqName: deployConfig.cloudflare.dlqName ?? "agent-work-dlq",
182
+ binding: deployConfig.cloudflare.queueBinding ?? "AGENT_WORK_QUEUE"
183
+ }
184
+ },
185
+ generatedSecrets: {},
186
+ readiness: {
187
+ initialized: false,
188
+ initializedAt: null,
189
+ lastValidatedAt: null,
190
+ lastConfigFingerprint: null
191
+ },
192
+ lastDeployedUrl: target.kind === "branch" ? targetWorkersDevUrl(workerName) : null,
193
+ lastManifestFingerprint: null,
194
+ lastDeploymentTimestamp: null,
195
+ lastDeployedCommit: null,
196
+ services: Object.fromEntries(
197
+ MANAGED_SERVICE_KEYS.map((serviceKey) => {
198
+ const serviceConfig = deployConfig.services?.[serviceKey];
199
+ const scope = scopeFromTarget(target);
200
+ const baseUrl = serviceConfig?.environments?.[scope]?.baseUrl ?? serviceConfig?.publicBaseUrl ?? null;
201
+ return [
202
+ serviceKey,
203
+ {
204
+ enabled: serviceConfig?.enabled !== false && Boolean(serviceConfig),
205
+ provider: serviceConfig?.provider ?? (serviceConfig ? "railway" : "none"),
206
+ projectId: serviceConfig?.railway?.projectId ?? null,
207
+ projectName: serviceConfig?.railway?.projectName ?? null,
208
+ serviceId: serviceConfig?.railway?.serviceId ?? null,
209
+ serviceName: serviceConfig?.railway?.serviceName ?? null,
210
+ workerName: serviceConfig?.cloudflare?.workerName ?? null,
211
+ rootDir: serviceConfig?.railway?.rootDir ?? serviceConfig?.rootDir ?? null,
212
+ environment: serviceConfig?.environments?.[scope]?.railwayEnvironment ?? scope,
213
+ publicBaseUrl: baseUrl,
214
+ initialized: false,
215
+ lastDeploymentTimestamp: null,
216
+ lastDeployedUrl: baseUrl,
217
+ lastDeploymentCommand: null
218
+ }
219
+ ];
220
+ })
221
+ ),
222
+ runtimeCompatibility: {
223
+ envelopeSchemaGeneration: TRESEED_ENVELOPE_SCHEMA_GENERATION,
224
+ migrationWaveId: TRESEED_MIGRATION_WAVE_ID,
225
+ supportedPayloadVersionRange: TRESEED_SUPPORTED_PAYLOAD_RANGE
226
+ },
227
+ deploymentHistory: []
228
+ };
229
+ }
230
+ function loadDeployState(tenantRoot, deployConfig, options = {}) {
231
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
232
+ const defaults = defaultStateFromConfig(deployConfig, target);
233
+ const { statePath } = resolveTargetPaths(tenantRoot, target);
234
+ const persisted = readJson(statePath, {});
235
+ const persistedSiteDataDb = persisted.d1Databases?.SITE_DATA_DB ?? persisted.d1Databases?.SUBSCRIBERS_DB ?? {};
236
+ const merged = {
237
+ ...defaults,
238
+ ...persisted,
239
+ target,
240
+ previewEnabled: persisted.previewEnabled ?? defaults.previewEnabled,
241
+ workerName: defaults.workerName,
242
+ kvNamespaces: {
243
+ ...defaults.kvNamespaces,
244
+ ...persisted.kvNamespaces ?? {},
245
+ FORM_GUARD_KV: {
246
+ ...defaults.kvNamespaces.FORM_GUARD_KV,
247
+ ...persisted.kvNamespaces?.FORM_GUARD_KV ?? {},
248
+ name: defaults.kvNamespaces.FORM_GUARD_KV.name
249
+ },
250
+ SESSION: {
251
+ ...defaults.kvNamespaces.SESSION,
252
+ ...persisted.kvNamespaces?.SESSION ?? {},
253
+ name: defaults.kvNamespaces.SESSION.name
254
+ }
255
+ },
256
+ d1Databases: {
257
+ ...defaults.d1Databases,
258
+ ...persisted.d1Databases ?? {},
259
+ SITE_DATA_DB: {
260
+ ...defaults.d1Databases.SITE_DATA_DB,
261
+ ...persistedSiteDataDb,
262
+ databaseName: defaults.d1Databases.SITE_DATA_DB.databaseName
263
+ }
264
+ },
265
+ queues: {
266
+ ...defaults.queues ?? {},
267
+ ...persisted.queues ?? {},
268
+ agentWork: {
269
+ ...defaults.queues?.agentWork ?? {},
270
+ ...persisted.queues?.agentWork ?? {},
271
+ name: defaults.queues?.agentWork?.name ?? persisted.queues?.agentWork?.name ?? "agent-work",
272
+ dlqName: defaults.queues?.agentWork?.dlqName ?? persisted.queues?.agentWork?.dlqName ?? "agent-work-dlq",
273
+ binding: defaults.queues?.agentWork?.binding ?? persisted.queues?.agentWork?.binding ?? "AGENT_WORK_QUEUE"
274
+ }
275
+ },
276
+ generatedSecrets: {
277
+ ...defaults.generatedSecrets ?? {},
278
+ ...persisted.generatedSecrets ?? {}
279
+ },
280
+ readiness: {
281
+ ...defaults.readiness,
282
+ ...persisted.readiness ?? {}
283
+ },
284
+ services: Object.fromEntries(
285
+ MANAGED_SERVICE_KEYS.map((serviceKey) => {
286
+ const defaultService = defaults.services?.[serviceKey] ?? {};
287
+ const persistedService = persisted.services?.[serviceKey] ?? {};
288
+ return [
289
+ serviceKey,
290
+ {
291
+ ...defaultService,
292
+ ...persistedService,
293
+ enabled: defaultService.enabled,
294
+ provider: defaultService.provider,
295
+ projectId: defaultService.projectId ?? persistedService.projectId ?? null,
296
+ projectName: defaultService.projectName ?? persistedService.projectName ?? null,
297
+ serviceId: defaultService.serviceId ?? persistedService.serviceId ?? null,
298
+ serviceName: defaultService.serviceName ?? persistedService.serviceName ?? null,
299
+ workerName: defaultService.workerName ?? persistedService.workerName ?? null,
300
+ rootDir: defaultService.rootDir ?? persistedService.rootDir ?? null,
301
+ environment: defaultService.environment ?? persistedService.environment ?? null,
302
+ publicBaseUrl: defaultService.publicBaseUrl ?? persistedService.publicBaseUrl ?? null,
303
+ lastDeployedUrl: persistedService.lastDeployedUrl ?? defaultService.publicBaseUrl ?? null
304
+ }
305
+ ];
306
+ })
307
+ )
308
+ };
309
+ if (target.kind === "branch" && !merged.lastDeployedUrl) {
310
+ merged.lastDeployedUrl = targetWorkersDevUrl(merged.workerName);
311
+ }
312
+ return merged;
313
+ }
314
+ function writeDeployState(tenantRoot, state, options = {}) {
315
+ const target = normalizeTarget(options.scope ?? options.target ?? state.target ?? "prod");
316
+ const { statePath } = resolveTargetPaths(tenantRoot, target);
317
+ writeJson(statePath, {
318
+ ...state,
319
+ target
320
+ });
321
+ }
322
+ function resolveGeneratedWranglerPath(tenantRoot, options = {}) {
323
+ return resolveTargetPaths(tenantRoot, options.scope ?? options.target ?? "prod").wranglerPath;
324
+ }
325
+ function buildWranglerConfigContents(tenantRoot, deployConfig, state, options = {}) {
326
+ const target = normalizeTarget(options.scope ?? options.target ?? state.target ?? "prod");
327
+ const { generatedRoot } = resolveTargetPaths(tenantRoot, target);
328
+ const workerName = state.workerName ?? targetWorkerName(deployConfig, target);
329
+ const mainPath = relativeFromGeneratedRoot(resolve(generatedRoot, "worker/index.js"), generatedRoot);
330
+ const assetsDirectory = relativeFromGeneratedRoot(resolve(tenantRoot, "dist"), generatedRoot);
331
+ const migrationsDir = relativeFromGeneratedRoot(resolve(tenantRoot, "migrations"), generatedRoot);
332
+ const vars = buildPublicVars(deployConfig);
333
+ return [
334
+ `name = ${renderTomlString(workerName)}`,
335
+ `compatibility_date = ${renderTomlString(DEFAULT_COMPATIBILITY_DATE)}`,
336
+ `compatibility_flags = [${DEFAULT_COMPATIBILITY_FLAGS.map((flag) => renderTomlString(flag)).join(", ")}]`,
337
+ `main = ${renderTomlString(mainPath)}`,
338
+ "workers_dev = true",
339
+ "preview_urls = true",
340
+ "",
341
+ "[assets]",
342
+ `directory = ${renderTomlString(assetsDirectory)}`,
343
+ "",
344
+ "[vars]",
345
+ ...Object.entries(vars).map(([key, value]) => `${key} = ${renderTomlString(value)}`),
346
+ "",
347
+ "[[kv_namespaces]]",
348
+ 'binding = "FORM_GUARD_KV"',
349
+ `id = ${renderTomlString(state.kvNamespaces.FORM_GUARD_KV.id)}`,
350
+ `preview_id = ${renderTomlString(state.kvNamespaces.FORM_GUARD_KV.previewId ?? state.kvNamespaces.FORM_GUARD_KV.id)}`,
351
+ "",
352
+ "[[kv_namespaces]]",
353
+ 'binding = "SESSION"',
354
+ `id = ${renderTomlString(state.kvNamespaces.SESSION.id)}`,
355
+ `preview_id = ${renderTomlString(state.kvNamespaces.SESSION.previewId ?? state.kvNamespaces.SESSION.id)}`,
356
+ "",
357
+ "[[d1_databases]]",
358
+ 'binding = "SITE_DATA_DB"',
359
+ `database_name = ${renderTomlString(state.d1Databases.SITE_DATA_DB.databaseName)}`,
360
+ `database_id = ${renderTomlString(state.d1Databases.SITE_DATA_DB.databaseId)}`,
361
+ `preview_database_id = ${renderTomlString(state.d1Databases.SITE_DATA_DB.previewDatabaseId ?? state.d1Databases.SITE_DATA_DB.databaseId)}`,
362
+ `migrations_dir = ${renderTomlString(migrationsDir)}`,
363
+ ""
364
+ ].join("\n");
365
+ }
366
+ function ensureGeneratedWranglerConfig(tenantRoot, options = {}) {
367
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
368
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
369
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
370
+ const { wranglerPath } = resolveTargetPaths(tenantRoot, target);
371
+ const manifestFingerprint = stableHash(JSON.stringify({ deployConfig, targetKey: targetKey(target) }));
372
+ const contents = buildWranglerConfigContents(tenantRoot, deployConfig, state, { target });
373
+ ensureParent(wranglerPath);
374
+ writeFileSync(wranglerPath, contents, "utf8");
375
+ state.lastManifestFingerprint = manifestFingerprint;
376
+ state.readiness.lastConfigFingerprint = manifestFingerprint;
377
+ if (!state.generatedSecrets) {
378
+ state.generatedSecrets = {};
379
+ }
380
+ const secretMap = buildSecretMap(deployConfig, state);
381
+ state.generatedSecrets.TREESEED_FORM_TOKEN_SECRET = secretMap.TREESEED_FORM_TOKEN_SECRET;
382
+ writeDeployState(tenantRoot, state, { target });
383
+ return { wranglerPath, deployConfig, state, manifestFingerprint, target };
384
+ }
385
+ function runWrangler(args, { cwd, allowFailure = false, json = false, capture = false, env = {}, input } = {}) {
386
+ const result = spawnSync(process.execPath, [resolveWranglerBin(), ...args], {
387
+ stdio: json || capture || input !== void 0 ? ["pipe", "pipe", "pipe"] : "inherit",
388
+ cwd,
389
+ env: { ...process.env, ...env },
390
+ encoding: "utf8",
391
+ input
392
+ });
393
+ if (result.status !== 0 && !allowFailure) {
394
+ const stderr = result.stderr?.trim();
395
+ const stdout = result.stdout?.trim();
396
+ const output = [stderr, stdout].filter(Boolean).join("\n");
397
+ if (/Authentication error/i.test(output) || /\[code:\s*10000\]/i.test(output)) {
398
+ throw new Error([
399
+ output || `Wrangler command failed: ${args.join(" ")}`,
400
+ "",
401
+ "Treeseed Cloudflare authentication failed. Check that CLOUDFLARE_API_TOKEN is an account-level token scoped to the target account and domain.",
402
+ "Required Cloudflare permissions: Account Cloudflare Pages edit, Account Workers Scripts edit, Account Workers KV Storage edit, Account D1 edit, Account Queues edit, Zone DNS edit."
403
+ ].join("\n"));
404
+ }
405
+ throw new Error(output || `Wrangler command failed: ${args.join(" ")}`);
406
+ }
407
+ return result;
408
+ }
409
+ function parseWranglerJsonOutput(result, label) {
410
+ const source = `${result.stdout ?? ""}`.trim();
411
+ if (!source) {
412
+ throw new Error(`Expected JSON output from ${label}.`);
413
+ }
414
+ return JSON.parse(source);
415
+ }
416
+ function listKvNamespaces(tenantRoot, env) {
417
+ const result = runWrangler(["kv", "namespace", "list"], {
418
+ cwd: tenantRoot,
419
+ capture: true,
420
+ env
421
+ });
422
+ return parseWranglerJsonOutput(result, "KV namespace list");
423
+ }
424
+ function listD1Databases(tenantRoot, env) {
425
+ const result = runWrangler(["d1", "list", "--json"], {
426
+ cwd: tenantRoot,
427
+ capture: true,
428
+ env
429
+ });
430
+ return parseWranglerJsonOutput(result, "D1 list");
431
+ }
432
+ function isPlaceholderResourceId(value) {
433
+ if (!value || typeof value !== "string") {
434
+ return true;
435
+ }
436
+ return value.startsWith("local-") || value.startsWith("dryrun-") || value.endsWith("-id") || value.endsWith("-preview-id");
437
+ }
438
+ function buildProvisioningSummary(deployConfig, state, target) {
439
+ return {
440
+ target: deployTargetLabel(target),
441
+ workerName: state.workerName ?? targetWorkerName(deployConfig, target),
442
+ siteUrl: target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : deployConfig.siteUrl,
443
+ accountId: deployConfig.cloudflare.accountId,
444
+ formGuardKv: state.kvNamespaces.FORM_GUARD_KV,
445
+ sessionKv: state.kvNamespaces.SESSION,
446
+ siteDataDb: state.d1Databases.SITE_DATA_DB
447
+ };
448
+ }
449
+ function buildDestroySummary(deployConfig, state, target) {
450
+ return buildProvisioningSummary(deployConfig, state, target);
451
+ }
452
+ function isPlaceholderAccountId(value) {
453
+ return !value || value === "replace-with-cloudflare-account-id";
454
+ }
455
+ function missingTurnstileRequirements() {
456
+ const issues = [];
457
+ if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
458
+ issues.push("Set TREESEED_PUBLIC_TURNSTILE_SITE_KEY before deploying.");
459
+ }
460
+ if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
461
+ issues.push("Set TREESEED_TURNSTILE_SECRET_KEY before deploying.");
462
+ }
463
+ return issues;
464
+ }
465
+ function collectMissingDeployInputs(tenantRoot) {
466
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
467
+ const missing = [];
468
+ if (isPlaceholderAccountId(deployConfig.cloudflare.accountId)) {
469
+ missing.push({
470
+ key: "CLOUDFLARE_ACCOUNT_ID",
471
+ label: "Cloudflare account ID",
472
+ message: `Cloudflare account ID is missing. Add it to ${relative(tenantRoot, deployConfig.__configPath ?? resolve(tenantRoot, "treeseed.site.yaml"))} or provide it now.`
473
+ });
474
+ }
475
+ if (!envOrNull("TREESEED_PUBLIC_TURNSTILE_SITE_KEY")) {
476
+ missing.push({
477
+ key: "TREESEED_PUBLIC_TURNSTILE_SITE_KEY",
478
+ label: "Turnstile public site key",
479
+ message: "Turnstile public site key is missing for deploy."
480
+ });
481
+ }
482
+ if (!envOrNull("TREESEED_TURNSTILE_SECRET_KEY")) {
483
+ missing.push({
484
+ key: "TREESEED_TURNSTILE_SECRET_KEY",
485
+ label: "Turnstile secret key",
486
+ message: "Turnstile secret key is missing for deploy."
487
+ });
488
+ }
489
+ return missing;
490
+ }
491
+ async function promptForMissingDeployInputs(tenantRoot) {
492
+ const missing = collectMissingDeployInputs(tenantRoot);
493
+ if (!missing.length || !process.stdin.isTTY || !process.stdout.isTTY) {
494
+ return { prompted: false, provided: [] };
495
+ }
496
+ const rl = createInterface({
497
+ input: process.stdin,
498
+ output: process.stdout
499
+ });
500
+ const provided = [];
501
+ try {
502
+ console.log("Treeseed deploy needs a few missing values before it can continue.");
503
+ console.log("These values will be used for this deploy process only. Persist them in your env files or CI secrets afterward.");
504
+ for (const item of missing) {
505
+ console.log(`- ${item.message}`);
506
+ const answer = (await rl.question(`${item.label}: `)).trim();
507
+ if (!answer) {
508
+ continue;
509
+ }
510
+ process.env[item.key] = answer;
511
+ provided.push(item.key);
512
+ }
513
+ } finally {
514
+ rl.close();
515
+ }
516
+ return { prompted: true, provided };
517
+ }
518
+ function validateDeployPrerequisites(tenantRoot, { requireRemote = true } = {}) {
519
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
520
+ const issues = [];
521
+ if (isPlaceholderAccountId(deployConfig.cloudflare.accountId)) {
522
+ issues.push(
523
+ `Set cloudflare.accountId in ${relative(tenantRoot, deployConfig.__configPath ?? resolve(tenantRoot, "treeseed.site.yaml"))} or export CLOUDFLARE_ACCOUNT_ID.`
524
+ );
525
+ }
526
+ if (requireRemote) {
527
+ issues.push(...missingTurnstileRequirements());
528
+ const result = runWrangler(["whoami"], {
529
+ cwd: tenantRoot,
530
+ allowFailure: true,
531
+ capture: true
532
+ });
533
+ const output = `${result.stdout ?? ""}
534
+ ${result.stderr ?? ""}`;
535
+ if (/You are not authenticated/i.test(output) || /wrangler login/i.test(output)) {
536
+ issues.push("Authenticate Wrangler first with `wrangler login`.");
537
+ }
538
+ }
539
+ if (issues.length > 0) {
540
+ throw new Error(`Treeseed deploy prerequisites are not satisfied:
541
+ - ${issues.join("\n- ")}`);
542
+ }
543
+ return deployConfig;
544
+ }
545
+ function validateDestroyPrerequisites(tenantRoot, { requireRemote = true } = {}) {
546
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
547
+ const issues = [];
548
+ if (requireRemote && isPlaceholderAccountId(deployConfig.cloudflare.accountId)) {
549
+ issues.push(
550
+ `Set cloudflare.accountId in ${relative(tenantRoot, deployConfig.__configPath ?? resolve(tenantRoot, "treeseed.site.yaml"))} or export CLOUDFLARE_ACCOUNT_ID.`
551
+ );
552
+ }
553
+ if (requireRemote) {
554
+ const result = runWrangler(["whoami"], {
555
+ cwd: tenantRoot,
556
+ allowFailure: true,
557
+ capture: true
558
+ });
559
+ const output = `${result.stdout ?? ""}
560
+ ${result.stderr ?? ""}`;
561
+ if (/You are not authenticated/i.test(output) || /wrangler login/i.test(output)) {
562
+ issues.push("Authenticate Wrangler first with `wrangler login`.");
563
+ }
564
+ }
565
+ if (issues.length > 0) {
566
+ throw new Error(`Treeseed destroy prerequisites are not satisfied:
567
+ - ${issues.join("\n- ")}`);
568
+ }
569
+ return deployConfig;
570
+ }
571
+ function resolveExistingKvIdByName(kvNamespaces, expectedName, fallbackId) {
572
+ if (fallbackId && !isPlaceholderResourceId(fallbackId)) {
573
+ return fallbackId;
574
+ }
575
+ return kvNamespaces.find((entry) => entry?.title === expectedName)?.id ?? null;
576
+ }
577
+ function resolveExistingD1ByName(d1Databases, expectedName, current) {
578
+ if (current?.databaseId && !isPlaceholderResourceId(current.databaseId)) {
579
+ return current;
580
+ }
581
+ const existing = d1Databases.find((entry) => entry?.name === expectedName);
582
+ if (!existing?.uuid) {
583
+ return current;
584
+ }
585
+ return {
586
+ ...current,
587
+ databaseId: existing.uuid,
588
+ previewDatabaseId: existing.previewDatabaseUuid ?? existing.uuid
589
+ };
590
+ }
591
+ function looksLikeMissingResource(output) {
592
+ return /not found|does not exist|could not find|unknown/i.test(output);
593
+ }
594
+ function deleteKvNamespace(tenantRoot, namespaceId, { env, dryRun, preview = false }) {
595
+ if (!namespaceId || isPlaceholderResourceId(namespaceId)) {
596
+ return { status: "missing", id: namespaceId };
597
+ }
598
+ if (dryRun) {
599
+ return { status: "planned", id: namespaceId, preview };
600
+ }
601
+ const args = ["kv", "namespace", "delete", "--namespace-id", namespaceId, "--skip-confirmation"];
602
+ if (preview) {
603
+ args.push("--preview");
604
+ }
605
+ const result = runWrangler(args, {
606
+ cwd: tenantRoot,
607
+ allowFailure: true,
608
+ capture: true,
609
+ env
610
+ });
611
+ const output = `${result.stdout ?? ""}
612
+ ${result.stderr ?? ""}`;
613
+ if (result.status !== 0 && !looksLikeMissingResource(output)) {
614
+ throw new Error(output.trim() || `Failed to delete KV namespace ${namespaceId}.`);
615
+ }
616
+ return { status: result.status === 0 ? "deleted" : "missing", id: namespaceId, preview };
617
+ }
618
+ function deleteD1Database(tenantRoot, databaseName, { env, dryRun }) {
619
+ if (!databaseName) {
620
+ return { status: "missing", name: databaseName };
621
+ }
622
+ if (dryRun) {
623
+ return { status: "planned", name: databaseName };
624
+ }
625
+ const result = runWrangler(["d1", "delete", databaseName, "--skip-confirmation"], {
626
+ cwd: tenantRoot,
627
+ allowFailure: true,
628
+ capture: true,
629
+ env
630
+ });
631
+ const output = `${result.stdout ?? ""}
632
+ ${result.stderr ?? ""}`;
633
+ if (result.status !== 0 && !looksLikeMissingResource(output)) {
634
+ throw new Error(output.trim() || `Failed to delete D1 database ${databaseName}.`);
635
+ }
636
+ return { status: result.status === 0 ? "deleted" : "missing", name: databaseName };
637
+ }
638
+ function deleteWorker(tenantRoot, workerName, { env, dryRun, force = false }) {
639
+ if (!workerName) {
640
+ return { status: "missing", name: workerName };
641
+ }
642
+ if (dryRun) {
643
+ return { status: "planned", name: workerName };
644
+ }
645
+ const args = ["delete", workerName];
646
+ if (force) {
647
+ args.push("--force");
648
+ }
649
+ const result = runWrangler(args, {
650
+ cwd: tenantRoot,
651
+ allowFailure: true,
652
+ capture: true,
653
+ env,
654
+ input: "y\n"
655
+ });
656
+ const output = `${result.stdout ?? ""}
657
+ ${result.stderr ?? ""}`;
658
+ if (result.status !== 0 && !looksLikeMissingResource(output)) {
659
+ throw new Error(output.trim() || `Failed to delete Worker ${workerName}.`);
660
+ }
661
+ return { status: result.status === 0 ? "deleted" : "missing", name: workerName };
662
+ }
663
+ function destroyCloudflareResources(tenantRoot, options = {}) {
664
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
665
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
666
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
667
+ state.workerName = targetWorkerName(deployConfig, target);
668
+ const env = {
669
+ CLOUDFLARE_ACCOUNT_ID: deployConfig.cloudflare.accountId
670
+ };
671
+ const dryRun = options.dryRun ?? false;
672
+ const force = options.force ?? false;
673
+ const kvNamespaces = dryRun ? [] : listKvNamespaces(tenantRoot, env);
674
+ const d1Databases = dryRun ? [] : listD1Databases(tenantRoot, env);
675
+ state.kvNamespaces.FORM_GUARD_KV.id = resolveExistingKvIdByName(
676
+ kvNamespaces,
677
+ state.kvNamespaces.FORM_GUARD_KV.name,
678
+ state.kvNamespaces.FORM_GUARD_KV.id
679
+ );
680
+ state.kvNamespaces.SESSION.id = resolveExistingKvIdByName(
681
+ kvNamespaces,
682
+ state.kvNamespaces.SESSION.name,
683
+ state.kvNamespaces.SESSION.id
684
+ );
685
+ state.d1Databases.SITE_DATA_DB = resolveExistingD1ByName(
686
+ d1Databases,
687
+ state.d1Databases.SITE_DATA_DB.databaseName,
688
+ state.d1Databases.SITE_DATA_DB
689
+ );
690
+ const worker = deleteWorker(tenantRoot, state.workerName, { env, dryRun, force });
691
+ const formGuard = deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.id, { env, dryRun });
692
+ const formGuardPreview = state.kvNamespaces.FORM_GUARD_KV.previewId && state.kvNamespaces.FORM_GUARD_KV.previewId !== state.kvNamespaces.FORM_GUARD_KV.id ? deleteKvNamespace(tenantRoot, state.kvNamespaces.FORM_GUARD_KV.previewId, { env, dryRun, preview: true }) : null;
693
+ const session = deleteKvNamespace(tenantRoot, state.kvNamespaces.SESSION.id, { env, dryRun });
694
+ const sessionPreview = state.kvNamespaces.SESSION.previewId && state.kvNamespaces.SESSION.previewId !== state.kvNamespaces.SESSION.id ? deleteKvNamespace(tenantRoot, state.kvNamespaces.SESSION.previewId, { env, dryRun, preview: true }) : null;
695
+ const database = deleteD1Database(tenantRoot, state.d1Databases.SITE_DATA_DB.databaseName, { env, dryRun });
696
+ return {
697
+ target,
698
+ summary: buildDestroySummary(deployConfig, state, target),
699
+ operations: {
700
+ worker,
701
+ formGuard,
702
+ formGuardPreview,
703
+ session,
704
+ sessionPreview,
705
+ database
706
+ }
707
+ };
708
+ }
709
+ function cleanupDestroyedState(tenantRoot, options = {}) {
710
+ const target = options.scope || options.target ? normalizeTarget(options.scope ?? options.target) : null;
711
+ if (target) {
712
+ const { statePath, generatedRoot } = resolveTargetPaths(tenantRoot, target);
713
+ rmSync(statePath, { force: true });
714
+ rmSync(generatedRoot, { recursive: true, force: true });
715
+ return;
716
+ }
717
+ rmSync(resolve(tenantRoot, STATE_ROOT), { recursive: true, force: true });
718
+ rmSync(resolve(tenantRoot, GENERATED_ROOT), { recursive: true, force: true });
719
+ if (options.removeBuildArtifacts) {
720
+ rmSync(resolve(tenantRoot, "dist"), { recursive: true, force: true });
721
+ }
722
+ }
723
+ function provisionCloudflareResources(tenantRoot, options = {}) {
724
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
725
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
726
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
727
+ state.workerName = targetWorkerName(deployConfig, target);
728
+ const env = {
729
+ CLOUDFLARE_ACCOUNT_ID: deployConfig.cloudflare.accountId
730
+ };
731
+ const dryRun = options.dryRun ?? false;
732
+ const kvNamespaces = dryRun ? [] : listKvNamespaces(tenantRoot, env);
733
+ const d1Databases = dryRun ? [] : listD1Databases(tenantRoot, env);
734
+ const ensureKv = (binding) => {
735
+ const current = state.kvNamespaces[binding];
736
+ if (current?.id && !isPlaceholderResourceId(current.id)) {
737
+ state.kvNamespaces[binding].previewId = current.previewId ?? current.id;
738
+ return;
739
+ }
740
+ const existing = kvNamespaces.find((entry) => entry?.title === current.name);
741
+ if (existing?.id) {
742
+ state.kvNamespaces[binding].id = existing.id;
743
+ state.kvNamespaces[binding].previewId = existing.id;
744
+ return;
745
+ }
746
+ if (dryRun) {
747
+ state.kvNamespaces[binding].id = `dryrun-${current.name}`;
748
+ state.kvNamespaces[binding].previewId = `dryrun-${current.name}-preview`;
749
+ return;
750
+ }
751
+ runWrangler(["kv", "namespace", "create", current.name], { cwd: tenantRoot, capture: true, env });
752
+ const refreshed = listKvNamespaces(tenantRoot, env);
753
+ const created = refreshed.find((entry) => entry?.title === current.name);
754
+ if (!created?.id) {
755
+ throw new Error(`Unable to resolve created KV namespace id for ${current.name}.`);
756
+ }
757
+ state.kvNamespaces[binding].id = created.id;
758
+ state.kvNamespaces[binding].previewId = created.id;
759
+ };
760
+ const ensureD1 = () => {
761
+ const current = state.d1Databases.SITE_DATA_DB;
762
+ if (current?.databaseId && !isPlaceholderResourceId(current.databaseId)) {
763
+ return;
764
+ }
765
+ const existing = d1Databases.find((entry) => entry?.name === current.databaseName);
766
+ if (existing?.uuid) {
767
+ current.databaseId = existing.uuid;
768
+ current.previewDatabaseId = existing.previewDatabaseUuid ?? existing.uuid;
769
+ return;
770
+ }
771
+ if (dryRun) {
772
+ current.databaseId = `dryrun-${current.databaseName}`;
773
+ current.previewDatabaseId = `dryrun-${current.databaseName}-preview`;
774
+ return;
775
+ }
776
+ runWrangler(["d1", "create", current.databaseName], {
777
+ cwd: tenantRoot,
778
+ capture: true,
779
+ env
780
+ });
781
+ const refreshed = listD1Databases(tenantRoot, env);
782
+ const created = refreshed.find((entry) => entry?.name === current.databaseName);
783
+ if (!created?.uuid) {
784
+ throw new Error(`Unable to resolve created D1 database id for ${current.databaseName}.`);
785
+ }
786
+ current.databaseId = created.uuid;
787
+ current.previewDatabaseId = created.previewDatabaseUuid ?? created.uuid;
788
+ };
789
+ ensureKv("FORM_GUARD_KV");
790
+ ensureKv("SESSION");
791
+ ensureD1();
792
+ state.readiness.initialized = true;
793
+ state.readiness.initializedAt = (/* @__PURE__ */ new Date()).toISOString();
794
+ state.readiness.lastValidatedAt = state.readiness.initializedAt;
795
+ writeDeployState(tenantRoot, state, { target });
796
+ return buildProvisioningSummary(deployConfig, state, target);
797
+ }
798
+ function syncCloudflareSecrets(tenantRoot, options = {}) {
799
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
800
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
801
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
802
+ const env = {
803
+ CLOUDFLARE_ACCOUNT_ID: deployConfig.cloudflare.accountId
804
+ };
805
+ const secrets = buildSecretMap(deployConfig, state);
806
+ const synced = [];
807
+ const dryRun = options.dryRun ?? false;
808
+ for (const [key, value] of Object.entries(secrets)) {
809
+ if (!value) {
810
+ continue;
811
+ }
812
+ synced.push(key);
813
+ if (dryRun) {
814
+ continue;
815
+ }
816
+ const result = spawnSync(process.execPath, [resolveWranglerBin(), "secret", "put", key, "--config", resolveGeneratedWranglerPath(tenantRoot, { target })], {
817
+ cwd: tenantRoot,
818
+ input: `${value}
819
+ `,
820
+ stdio: ["pipe", "inherit", "inherit"],
821
+ env: { ...process.env, ...env },
822
+ encoding: "utf8"
823
+ });
824
+ if (result.status !== 0) {
825
+ throw new Error(`Failed to sync secret ${key}.`);
826
+ }
827
+ }
828
+ state.generatedSecrets = {
829
+ ...state.generatedSecrets ?? {},
830
+ TREESEED_FORM_TOKEN_SECRET: secrets.TREESEED_FORM_TOKEN_SECRET ?? state.generatedSecrets?.TREESEED_FORM_TOKEN_SECRET
831
+ };
832
+ writeDeployState(tenantRoot, state, { target });
833
+ return synced;
834
+ }
835
+ function runRemoteD1Migrations(tenantRoot, options = {}) {
836
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
837
+ const { wranglerPath, deployConfig, state } = ensureGeneratedWranglerConfig(tenantRoot, { target });
838
+ if (options.dryRun) {
839
+ return { databaseName: state.d1Databases.SITE_DATA_DB.databaseName, dryRun: true };
840
+ }
841
+ runWrangler(
842
+ ["d1", "migrations", "apply", state.d1Databases.SITE_DATA_DB.databaseName, "--remote", "--config", wranglerPath],
843
+ {
844
+ cwd: tenantRoot,
845
+ env: { CLOUDFLARE_ACCOUNT_ID: deployConfig.cloudflare.accountId }
846
+ }
847
+ );
848
+ return { databaseName: state.d1Databases.SITE_DATA_DB.databaseName, dryRun: false };
849
+ }
850
+ function markDeploymentInitialized(tenantRoot, options = {}) {
851
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
852
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
853
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
854
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
855
+ state.readiness.initialized = true;
856
+ state.readiness.initializedAt = state.readiness.initializedAt ?? timestamp;
857
+ state.readiness.lastValidatedAt = timestamp;
858
+ state.readiness.lastConfigFingerprint = state.lastManifestFingerprint ?? state.readiness.lastConfigFingerprint;
859
+ writeDeployState(tenantRoot, state, { target });
860
+ return state;
861
+ }
862
+ function markManagedServicesInitialized(tenantRoot, options = {}) {
863
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
864
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
865
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
866
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
867
+ for (const serviceKey of MANAGED_SERVICE_KEYS) {
868
+ if (!state.services?.[serviceKey]?.enabled) {
869
+ continue;
870
+ }
871
+ state.services[serviceKey].initialized = true;
872
+ state.services[serviceKey].lastDeploymentTimestamp = state.services[serviceKey].lastDeploymentTimestamp ?? timestamp;
873
+ state.services[serviceKey].lastDeployedUrl = state.services[serviceKey].lastDeployedUrl ?? state.services[serviceKey].publicBaseUrl ?? null;
874
+ }
875
+ writeDeployState(tenantRoot, state, { target });
876
+ return state;
877
+ }
878
+ function assertDeploymentInitialized(tenantRoot, options = {}) {
879
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
880
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
881
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
882
+ if (state.readiness?.initialized) {
883
+ return state;
884
+ }
885
+ throw new Error(
886
+ `Treeseed environment ${deployTargetLabel(target)} has not been initialized. Run \`treeseed config --environment ${scopeFromTarget(target)}\` first.`
887
+ );
888
+ }
889
+ function finalizeDeploymentState(tenantRoot, options = {}) {
890
+ const target = normalizeTarget(options.scope ?? options.target ?? "prod");
891
+ const deployConfig = loadTenantDeployConfig(tenantRoot);
892
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
893
+ state.lastManifestFingerprint = stableHash(JSON.stringify({ deployConfig, targetKey: targetKey(target) }));
894
+ state.lastDeployedUrl = target.kind === "branch" ? targetWorkersDevUrl(state.workerName) : deployConfig.siteUrl;
895
+ state.lastDeploymentTimestamp = (/* @__PURE__ */ new Date()).toISOString();
896
+ state.lastDeployedCommit = envOrNull("GITHUB_SHA") ?? envOrNull("TREESEED_DEPLOY_COMMIT") ?? null;
897
+ state.runtimeCompatibility = {
898
+ envelopeSchemaGeneration: TRESEED_ENVELOPE_SCHEMA_GENERATION,
899
+ migrationWaveId: TRESEED_MIGRATION_WAVE_ID,
900
+ supportedPayloadVersionRange: TRESEED_SUPPORTED_PAYLOAD_RANGE
901
+ };
902
+ const nextHistoryEntry = {
903
+ commit: state.lastDeployedCommit,
904
+ timestamp: state.lastDeploymentTimestamp,
905
+ url: state.lastDeployedUrl,
906
+ target: deployTargetLabel(target),
907
+ appVersion: envOrNull("npm_package_version") ?? envOrNull("TREESEED_APP_VERSION") ?? null,
908
+ envelopeSchemaGeneration: TRESEED_ENVELOPE_SCHEMA_GENERATION,
909
+ migrationWaveId: TRESEED_MIGRATION_WAVE_ID,
910
+ supportedPayloadVersionRange: TRESEED_SUPPORTED_PAYLOAD_RANGE
911
+ };
912
+ const history = Array.isArray(state.deploymentHistory) ? state.deploymentHistory : [];
913
+ state.deploymentHistory = [...history, nextHistoryEntry].slice(-20);
914
+ state.readiness.initialized = true;
915
+ state.readiness.lastValidatedAt = state.lastDeploymentTimestamp;
916
+ for (const result of options.serviceResults ?? []) {
917
+ if (!result?.service || !state.services?.[result.service]) {
918
+ continue;
919
+ }
920
+ state.services[result.service].initialized = true;
921
+ state.services[result.service].lastDeploymentTimestamp = state.lastDeploymentTimestamp;
922
+ state.services[result.service].lastDeployedUrl = result.publicBaseUrl ?? state.services[result.service].publicBaseUrl ?? state.services[result.service].lastDeployedUrl ?? null;
923
+ state.services[result.service].lastDeploymentCommand = result.command ?? null;
924
+ }
925
+ writeDeployState(tenantRoot, state, { target });
926
+ return state;
927
+ }
928
+ function printDeploySummary(summary) {
929
+ console.log("Treeseed deployment summary");
930
+ console.log(` Target: ${summary.target}`);
931
+ console.log(` Worker: ${summary.workerName}`);
932
+ console.log(` Site URL: ${summary.siteUrl}`);
933
+ console.log(` Account ID: ${summary.accountId}`);
934
+ console.log(` D1: ${summary.siteDataDb.databaseName} (${summary.siteDataDb.databaseId})`);
935
+ console.log(` KV FORM_GUARD_KV: ${summary.formGuardKv.id}`);
936
+ console.log(` KV SESSION: ${summary.sessionKv.id}`);
937
+ }
938
+ function printDestroySummary(result) {
939
+ const { summary, operations } = result;
940
+ console.log("Treeseed destroy summary");
941
+ console.log(` Target: ${summary.target}`);
942
+ console.log(` Worker: ${summary.workerName} -> ${operations.worker.status}`);
943
+ console.log(` Site URL: ${summary.siteUrl}`);
944
+ console.log(` Account ID: ${summary.accountId}`);
945
+ console.log(` D1: ${summary.siteDataDb.databaseName} -> ${operations.database.status}`);
946
+ console.log(` KV FORM_GUARD_KV: ${summary.formGuardKv.name} -> ${operations.formGuard.status}`);
947
+ if (operations.formGuardPreview) {
948
+ console.log(` KV FORM_GUARD_KV preview -> ${operations.formGuardPreview.status}`);
949
+ }
950
+ console.log(` KV SESSION: ${summary.sessionKv.name} -> ${operations.session.status}`);
951
+ if (operations.sessionPreview) {
952
+ console.log(` KV SESSION preview -> ${operations.sessionPreview.status}`);
953
+ }
954
+ }
955
+ export {
956
+ assertDeploymentInitialized,
957
+ buildWranglerConfigContents,
958
+ cleanupDestroyedState,
959
+ collectMissingDeployInputs,
960
+ createBranchPreviewDeployTarget,
961
+ createPersistentDeployTarget,
962
+ deployTargetLabel,
963
+ destroyCloudflareResources,
964
+ ensureGeneratedWranglerConfig,
965
+ finalizeDeploymentState,
966
+ loadDeployState,
967
+ markDeploymentInitialized,
968
+ markManagedServicesInitialized,
969
+ normalizePersistentScope,
970
+ printDeploySummary,
971
+ printDestroySummary,
972
+ promptForMissingDeployInputs,
973
+ provisionCloudflareResources,
974
+ resolveGeneratedWranglerPath,
975
+ runRemoteD1Migrations,
976
+ scopeFromTarget,
977
+ syncCloudflareSecrets,
978
+ validateDeployPrerequisites,
979
+ validateDestroyPrerequisites,
980
+ writeDeployState
981
+ };