@treeseed/sdk 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/control-plane-client.d.ts +45 -0
- package/dist/control-plane-client.js +229 -0
- package/dist/control-plane.d.ts +94 -0
- package/dist/control-plane.js +125 -0
- package/dist/d1-store.d.ts +56 -1
- package/dist/d1-store.js +132 -0
- package/dist/dispatch.d.ts +4 -0
- package/dist/dispatch.js +180 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +94 -4
- package/dist/operations/services/config-runtime.d.ts +10 -0
- package/dist/operations/services/config-runtime.js +62 -4
- package/dist/operations/services/deploy.d.ts +95 -3
- package/dist/operations/services/deploy.js +351 -10
- package/dist/operations/services/github-automation.d.ts +37 -1
- package/dist/operations/services/github-automation.js +71 -14
- package/dist/operations/services/project-platform.d.ts +835 -0
- package/dist/operations/services/project-platform.js +782 -0
- package/dist/operations/services/railway-deploy.d.ts +113 -18
- package/dist/operations/services/railway-deploy.js +357 -8
- package/dist/operations/services/runtime-tools.d.ts +25 -1
- package/dist/operations/services/runtime-tools.js +66 -5
- package/dist/operations/services/template-registry.d.ts +1 -1
- package/dist/operations/services/template-registry.js +17 -3
- package/dist/platform/books-data.d.ts +3 -4
- package/dist/platform/books-data.js +30 -4
- package/dist/platform/contracts.d.ts +56 -4
- package/dist/platform/deploy-config.js +109 -4
- package/dist/platform/deploy-runtime.d.ts +2 -0
- package/dist/platform/deploy-runtime.js +9 -1
- package/dist/platform/env.yaml +677 -0
- package/dist/platform/environment.js +57 -2
- package/dist/platform/plugin.d.ts +8 -0
- package/dist/platform/plugins/constants.d.ts +2 -0
- package/dist/platform/plugins/constants.js +2 -0
- package/dist/platform/plugins/runtime.d.ts +2 -0
- package/dist/platform/plugins/runtime.js +9 -1
- package/dist/platform/plugins.d.ts +1 -1
- package/dist/platform/plugins.js +4 -0
- package/dist/platform/published-content-pipeline.d.ts +84 -0
- package/dist/platform/published-content-pipeline.js +543 -0
- package/dist/platform/published-content.d.ts +223 -0
- package/dist/platform/published-content.js +588 -0
- package/dist/platform/tenant/runtime-config.d.ts +1 -1
- package/dist/platform/tenant/runtime-config.js +34 -1
- package/dist/platform/tenant-config.d.ts +2 -1
- package/dist/platform/tenant-config.js +17 -1
- package/dist/platform/utils/site-config-schema.js +104 -0
- package/dist/plugin-default.d.ts +2 -0
- package/dist/plugin-default.js +2 -0
- package/dist/remote.d.ts +65 -9
- package/dist/remote.js +104 -28
- package/dist/scripts/check-build-warnings.js +50 -0
- package/dist/scripts/config-treeseed.js +7 -0
- package/dist/scripts/tenant-workflow-action.js +71 -0
- package/dist/sdk-dispatch.d.ts +12 -0
- package/dist/sdk-dispatch.js +142 -0
- package/dist/sdk-types.d.ts +579 -7
- package/dist/sdk-types.js +53 -1
- package/dist/sdk.d.ts +17 -1
- package/dist/sdk.js +109 -0
- package/dist/stores/operational-store.d.ts +22 -2
- package/dist/stores/operational-store.js +235 -0
- package/dist/template-catalog.js +8 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +20 -0
- package/dist/types/cloudflare.d.ts +23 -0
- package/dist/workflow/operations.d.ts +12 -3
- package/dist/workflow/policy.d.ts +1 -1
- package/dist/workflow-state.js +2 -1
- package/package.json +7 -2
- package/templates/github/deploy.workflow.yml +442 -0
- package/templates/github/hosted-project.workflow.yml +77 -0
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { basename, extname, join, resolve } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
createControlPlaneReporter
|
|
8
|
+
} from "../../control-plane.js";
|
|
9
|
+
import {
|
|
10
|
+
resolvePublishedContentPreviewTtlHours,
|
|
11
|
+
resolveTeamScopedContentLocator,
|
|
12
|
+
signEditorialPreviewToken
|
|
13
|
+
} from "../../platform/published-content.js";
|
|
14
|
+
import { createPublishedContentPipeline } from "../../platform/published-content-pipeline.js";
|
|
15
|
+
import { loadTreeseedManifest } from "../../platform/tenant-config.js";
|
|
16
|
+
import { applyTreeseedEnvironmentToProcess, syncTreeseedRailwayEnvironment } from "./config-runtime.js";
|
|
17
|
+
import {
|
|
18
|
+
createPersistentDeployTarget,
|
|
19
|
+
deployTargetLabel,
|
|
20
|
+
ensureGeneratedWranglerConfig,
|
|
21
|
+
finalizeDeploymentState,
|
|
22
|
+
loadDeployState,
|
|
23
|
+
provisionCloudflareResources,
|
|
24
|
+
syncCloudflareSecrets,
|
|
25
|
+
verifyProvisionedCloudflareResources,
|
|
26
|
+
writeDeployState
|
|
27
|
+
} from "./deploy.js";
|
|
28
|
+
import { currentManagedBranch, PRODUCTION_BRANCH, STAGING_BRANCH } from "./git-workflow.js";
|
|
29
|
+
import {
|
|
30
|
+
configuredRailwayServices,
|
|
31
|
+
deployRailwayService,
|
|
32
|
+
ensureRailwayScheduledJobs,
|
|
33
|
+
validateRailwayDeployPrerequisites,
|
|
34
|
+
validateRailwayServiceConfiguration,
|
|
35
|
+
verifyRailwayScheduledJobs
|
|
36
|
+
} from "./railway-deploy.js";
|
|
37
|
+
import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "./runtime-tools.js";
|
|
38
|
+
import { CloudflareQueuePullClient, CloudflareQueuePushClient } from "../../remote.js";
|
|
39
|
+
function stableHash(value) {
|
|
40
|
+
return createHash("sha256").update(value).digest("hex");
|
|
41
|
+
}
|
|
42
|
+
function inferEnvironmentFromBranch(tenantRoot) {
|
|
43
|
+
const branch = currentManagedBranch(tenantRoot);
|
|
44
|
+
if (branch === STAGING_BRANCH) {
|
|
45
|
+
return "staging";
|
|
46
|
+
}
|
|
47
|
+
if (branch === PRODUCTION_BRANCH) {
|
|
48
|
+
return "prod";
|
|
49
|
+
}
|
|
50
|
+
return "staging";
|
|
51
|
+
}
|
|
52
|
+
function resolveScope(environment) {
|
|
53
|
+
const scope = environment ?? (process.env.CI ? inferEnvironmentFromBranch(process.cwd()) : "local");
|
|
54
|
+
if (!["local", "staging", "prod"].includes(scope)) {
|
|
55
|
+
throw new Error(`Unsupported environment "${scope}". Expected local, staging, or prod.`);
|
|
56
|
+
}
|
|
57
|
+
return scope;
|
|
58
|
+
}
|
|
59
|
+
function currentCommit(tenantRoot) {
|
|
60
|
+
const result = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
61
|
+
cwd: tenantRoot,
|
|
62
|
+
stdio: "pipe",
|
|
63
|
+
encoding: "utf8"
|
|
64
|
+
});
|
|
65
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
66
|
+
}
|
|
67
|
+
function currentRef(tenantRoot) {
|
|
68
|
+
const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
69
|
+
cwd: tenantRoot,
|
|
70
|
+
stdio: "pipe",
|
|
71
|
+
encoding: "utf8"
|
|
72
|
+
});
|
|
73
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
74
|
+
}
|
|
75
|
+
function sanitizeSegment(value, fallback) {
|
|
76
|
+
const normalized = String(value ?? "").trim().replaceAll(/[\\/]+/g, "-").replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
|
|
77
|
+
return normalized || fallback;
|
|
78
|
+
}
|
|
79
|
+
function runNodeScript(tenantRoot, scriptName, scriptArgs = []) {
|
|
80
|
+
const result = spawnSync(process.execPath, [packageScriptPath(scriptName), ...scriptArgs], {
|
|
81
|
+
cwd: tenantRoot,
|
|
82
|
+
stdio: "inherit",
|
|
83
|
+
env: { ...process.env }
|
|
84
|
+
});
|
|
85
|
+
if (result.status !== 0) {
|
|
86
|
+
throw new Error(`${scriptName} failed.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function runWrangler(tenantRoot, args, extraEnv = {}, options = {}) {
|
|
90
|
+
const result = spawnSync(process.execPath, [resolveWranglerBin(), ...args], {
|
|
91
|
+
cwd: tenantRoot,
|
|
92
|
+
stdio: options.capture ? "pipe" : "inherit",
|
|
93
|
+
encoding: options.capture ? "utf8" : void 0,
|
|
94
|
+
env: { ...process.env, ...extraEnv }
|
|
95
|
+
});
|
|
96
|
+
if (result.status !== 0 && !options.allowFailure) {
|
|
97
|
+
throw new Error(`wrangler ${args.join(" ")} failed`);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function inferContentType(filePath) {
|
|
102
|
+
const extension = extname(filePath).toLowerCase();
|
|
103
|
+
if (extension === ".json") return "application/json";
|
|
104
|
+
if (extension === ".md") return "text/markdown; charset=utf-8";
|
|
105
|
+
if (extension === ".mdx") return "text/mdx; charset=utf-8";
|
|
106
|
+
return "application/octet-stream";
|
|
107
|
+
}
|
|
108
|
+
function writeTempFile(root, name, body) {
|
|
109
|
+
const filePath = resolve(root, name);
|
|
110
|
+
writeFileSync(filePath, body);
|
|
111
|
+
return filePath;
|
|
112
|
+
}
|
|
113
|
+
function toBuffer(body) {
|
|
114
|
+
if (typeof body === "string") {
|
|
115
|
+
return Buffer.from(body);
|
|
116
|
+
}
|
|
117
|
+
if (body instanceof ArrayBuffer) {
|
|
118
|
+
return Buffer.from(body);
|
|
119
|
+
}
|
|
120
|
+
return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
121
|
+
}
|
|
122
|
+
function readR2JsonObject(tenantRoot, bucketName, objectKey, wranglerPath, wranglerEnv) {
|
|
123
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-r2-read-"));
|
|
124
|
+
const filePath = resolve(tempRoot, basename(objectKey) || "payload.json");
|
|
125
|
+
try {
|
|
126
|
+
const result = runWrangler(tenantRoot, [
|
|
127
|
+
"r2",
|
|
128
|
+
"object",
|
|
129
|
+
"get",
|
|
130
|
+
`${bucketName}/${objectKey}`,
|
|
131
|
+
"--config",
|
|
132
|
+
wranglerPath,
|
|
133
|
+
"--remote",
|
|
134
|
+
"--file",
|
|
135
|
+
filePath
|
|
136
|
+
], wranglerEnv, { allowFailure: true });
|
|
137
|
+
if (result.status !== 0 || !statSafe(filePath)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
141
|
+
} finally {
|
|
142
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function statSafe(filePath) {
|
|
146
|
+
try {
|
|
147
|
+
return statSync(filePath);
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function reportDeployment(reporter, input) {
|
|
153
|
+
await reporter.reportDeployment(input);
|
|
154
|
+
}
|
|
155
|
+
function resolveReporter(tenantRoot, explicit) {
|
|
156
|
+
if (explicit) {
|
|
157
|
+
return explicit;
|
|
158
|
+
}
|
|
159
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
160
|
+
return createControlPlaneReporter({ deployConfig });
|
|
161
|
+
}
|
|
162
|
+
function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer, filePath) {
|
|
163
|
+
runWrangler(tenantRoot, [
|
|
164
|
+
"r2",
|
|
165
|
+
"object",
|
|
166
|
+
"put",
|
|
167
|
+
`${bucketName}/${pointer.objectKey}`,
|
|
168
|
+
"--config",
|
|
169
|
+
wranglerPath,
|
|
170
|
+
"--remote",
|
|
171
|
+
"--force",
|
|
172
|
+
"--file",
|
|
173
|
+
filePath,
|
|
174
|
+
"--content-type",
|
|
175
|
+
pointer.contentType ?? inferContentType(filePath)
|
|
176
|
+
], wranglerEnv);
|
|
177
|
+
}
|
|
178
|
+
function objectFileName(pointer) {
|
|
179
|
+
const ext = extname(pointer.objectKey) || ".json";
|
|
180
|
+
return `${pointer.sha256}${ext}`;
|
|
181
|
+
}
|
|
182
|
+
async function fetchJson(url, init = {}) {
|
|
183
|
+
const response = await fetch(url, init);
|
|
184
|
+
const body = await response.json().catch(() => null);
|
|
185
|
+
return { response, body };
|
|
186
|
+
}
|
|
187
|
+
async function probeHttp(url) {
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(url, {
|
|
190
|
+
headers: { accept: "application/json,text/html;q=0.9,*/*;q=0.8" }
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
ok: response.ok,
|
|
194
|
+
status: response.status,
|
|
195
|
+
url
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
status: null,
|
|
201
|
+
url,
|
|
202
|
+
error: error instanceof Error ? error.message : String(error)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function probeRunnerHealth(siteConfig, environment) {
|
|
207
|
+
const baseUrl = String(process.env.TREESEED_MARKET_API_BASE_URL ?? siteConfig.hosting?.marketBaseUrl ?? "").trim();
|
|
208
|
+
const projectId = String(process.env.TREESEED_PROJECT_ID ?? siteConfig.hosting?.projectId ?? "").trim();
|
|
209
|
+
const runnerToken = String(process.env.TREESEED_PROJECT_RUNNER_TOKEN ?? "").trim();
|
|
210
|
+
if (!baseUrl || !projectId || !runnerToken || environment === "local") {
|
|
211
|
+
return { ok: true, skipped: true, reason: "runner_health_unconfigured" };
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
let lastResult = null;
|
|
215
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
216
|
+
const { response, body } = await fetchJson(
|
|
217
|
+
`${baseUrl.replace(/\/+$/u, "")}/v1/projects/${encodeURIComponent(projectId)}/runner/health?environment=${encodeURIComponent(environment)}`,
|
|
218
|
+
{
|
|
219
|
+
headers: {
|
|
220
|
+
accept: "application/json",
|
|
221
|
+
authorization: `Bearer ${runnerToken}`
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
const pools = Array.isArray(body?.payload?.pools) ? body.payload.pools : [];
|
|
226
|
+
const heartbeatPresent = pools.some((entry) => entry?.latestRegistration);
|
|
227
|
+
lastResult = {
|
|
228
|
+
ok: response.ok && body?.ok === true && heartbeatPresent,
|
|
229
|
+
status: response.status,
|
|
230
|
+
payload: body?.payload ?? null,
|
|
231
|
+
heartbeatPresent,
|
|
232
|
+
attempt: attempt + 1
|
|
233
|
+
};
|
|
234
|
+
if (lastResult.ok) {
|
|
235
|
+
return lastResult;
|
|
236
|
+
}
|
|
237
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
238
|
+
}
|
|
239
|
+
return lastResult ?? { ok: false, reason: "runner_health_unavailable" };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: error instanceof Error ? error.message : String(error)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function queueClientConfig(siteConfig, state) {
|
|
248
|
+
const accountId = siteConfig.cloudflare.accountId;
|
|
249
|
+
const queueId = state.queues?.agentWork?.queueId;
|
|
250
|
+
const token = process.env.TREESEED_QUEUE_PUSH_TOKEN?.trim() || process.env.TREESEED_QUEUE_PULL_TOKEN?.trim() || process.env.CLOUDFLARE_API_TOKEN?.trim() || "";
|
|
251
|
+
if (!accountId || !queueId || !token) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
accountId,
|
|
256
|
+
queueId,
|
|
257
|
+
token,
|
|
258
|
+
apiBaseUrl: process.env.TREESEED_QUEUE_API_BASE_URL?.trim() || void 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function probeQueue(siteConfig, state) {
|
|
262
|
+
const config = queueClientConfig(siteConfig, state);
|
|
263
|
+
if (!config) {
|
|
264
|
+
return { ok: false, skipped: true, reason: "queue_probe_unconfigured" };
|
|
265
|
+
}
|
|
266
|
+
const pushClient = new CloudflareQueuePushClient(config);
|
|
267
|
+
const pullClient = new CloudflareQueuePullClient(config);
|
|
268
|
+
const messageId = `health-${Date.now()}`;
|
|
269
|
+
await pushClient.enqueue({
|
|
270
|
+
message: {
|
|
271
|
+
messageId,
|
|
272
|
+
taskId: messageId,
|
|
273
|
+
workDayId: "health-check",
|
|
274
|
+
agentId: "health-check",
|
|
275
|
+
taskType: "health_check",
|
|
276
|
+
idempotencyKey: messageId,
|
|
277
|
+
payloadRef: "health",
|
|
278
|
+
graphVersion: null,
|
|
279
|
+
budgetHint: 0
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
const pulled = await pullClient.pull({
|
|
283
|
+
batchSize: 1,
|
|
284
|
+
visibilityTimeoutMs: 1e4
|
|
285
|
+
});
|
|
286
|
+
const message = pulled.messages.find((entry) => entry.body?.messageId === messageId) ?? pulled.messages[0] ?? null;
|
|
287
|
+
if (!message) {
|
|
288
|
+
return { ok: false, reason: "queue_pull_empty" };
|
|
289
|
+
}
|
|
290
|
+
await pullClient.ack([message.leaseId]);
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
messageId,
|
|
294
|
+
attempts: message.attempts
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function r2HealthKey(state) {
|
|
298
|
+
return `${state.content?.manifestKey?.replace(/\/common\.json$/u, "") ?? "health"}/healthchecks/${Date.now()}.json`;
|
|
299
|
+
}
|
|
300
|
+
function deleteR2Object(tenantRoot, bucketName, objectKey, wranglerPath, wranglerEnv) {
|
|
301
|
+
runWrangler(tenantRoot, [
|
|
302
|
+
"r2",
|
|
303
|
+
"object",
|
|
304
|
+
"delete",
|
|
305
|
+
`${bucketName}/${objectKey}`,
|
|
306
|
+
"--config",
|
|
307
|
+
wranglerPath,
|
|
308
|
+
"--remote"
|
|
309
|
+
], wranglerEnv, { allowFailure: true });
|
|
310
|
+
}
|
|
311
|
+
function probeR2(tenantRoot, siteConfig, state, target) {
|
|
312
|
+
const bucketName = state.content?.bucketName;
|
|
313
|
+
if (!bucketName) {
|
|
314
|
+
return { ok: false, skipped: true, reason: "r2_unconfigured" };
|
|
315
|
+
}
|
|
316
|
+
const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
317
|
+
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: siteConfig.cloudflare.accountId };
|
|
318
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-r2-health-"));
|
|
319
|
+
const objectKey = r2HealthKey(state);
|
|
320
|
+
try {
|
|
321
|
+
const payload = JSON.stringify({ ok: true, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
322
|
+
const writeFile = writeTempFile(tempRoot, "probe.json", payload);
|
|
323
|
+
runWrangler(tenantRoot, [
|
|
324
|
+
"r2",
|
|
325
|
+
"object",
|
|
326
|
+
"put",
|
|
327
|
+
`${bucketName}/${objectKey}`,
|
|
328
|
+
"--config",
|
|
329
|
+
wranglerPath,
|
|
330
|
+
"--remote",
|
|
331
|
+
"--force",
|
|
332
|
+
"--file",
|
|
333
|
+
writeFile,
|
|
334
|
+
"--content-type",
|
|
335
|
+
"application/json"
|
|
336
|
+
], wranglerEnv);
|
|
337
|
+
const readBack = readR2JsonObject(tenantRoot, bucketName, objectKey, wranglerPath, wranglerEnv);
|
|
338
|
+
return {
|
|
339
|
+
ok: Boolean(readBack?.ok),
|
|
340
|
+
objectKey
|
|
341
|
+
};
|
|
342
|
+
} finally {
|
|
343
|
+
deleteR2Object(tenantRoot, bucketName, objectKey, wranglerPath, wranglerEnv);
|
|
344
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function probeScaleConfiguration(siteConfig, state) {
|
|
348
|
+
const worker = state.services?.worker ?? {};
|
|
349
|
+
const scalerKind = String(process.env.TREESEED_WORKER_POOL_SCALER ?? "").trim();
|
|
350
|
+
return {
|
|
351
|
+
ok: Boolean(
|
|
352
|
+
(scalerKind === "railway" || siteConfig.services?.worker?.provider === "railway") && (worker.serviceId || process.env.TREESEED_RAILWAY_WORKER_SERVICE_ID) && (process.env.TREESEED_RAILWAY_ENVIRONMENT_ID || process.env.TREESEED_RAILWAY_PROJECT_ID || worker.projectId)
|
|
353
|
+
),
|
|
354
|
+
mocked: true,
|
|
355
|
+
serviceId: worker.serviceId ?? null
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function publishContent(options, reporter) {
|
|
359
|
+
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
360
|
+
const tenantConfig = loadTreeseedManifest(resolve(options.tenantRoot, "src", "manifest.yaml"));
|
|
361
|
+
const teamId = siteConfig.hosting?.teamId ?? siteConfig.slug;
|
|
362
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
|
+
const commitSha = currentCommit(options.tenantRoot);
|
|
364
|
+
const branchName = currentRef(options.tenantRoot);
|
|
365
|
+
const previewId = options.previewId ?? `staging-${sanitizeSegment(branchName, "preview")}-${sanitizeSegment(commitSha?.slice(0, 12), "latest")}`;
|
|
366
|
+
const locator = resolveTeamScopedContentLocator(siteConfig, teamId);
|
|
367
|
+
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
368
|
+
const { wranglerPath } = ensureGeneratedWranglerConfig(options.tenantRoot, { target });
|
|
369
|
+
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: siteConfig.cloudflare.accountId };
|
|
370
|
+
const bucketName = siteConfig.cloudflare.r2?.bucketName;
|
|
371
|
+
if (!bucketName) {
|
|
372
|
+
throw new Error("Treeseed content publish requires cloudflare.r2.bucketName in treeseed.site.yaml.");
|
|
373
|
+
}
|
|
374
|
+
const previousManifest = readR2JsonObject(
|
|
375
|
+
options.tenantRoot,
|
|
376
|
+
bucketName,
|
|
377
|
+
locator.manifestKey,
|
|
378
|
+
wranglerPath,
|
|
379
|
+
wranglerEnv
|
|
380
|
+
);
|
|
381
|
+
const pipeline = createPublishedContentPipeline({
|
|
382
|
+
projectRoot: options.tenantRoot,
|
|
383
|
+
siteConfig,
|
|
384
|
+
tenantConfig,
|
|
385
|
+
teamId,
|
|
386
|
+
generatedAt: timestamp,
|
|
387
|
+
sourceCommit: commitSha,
|
|
388
|
+
sourceRef: branchName,
|
|
389
|
+
previewId
|
|
390
|
+
});
|
|
391
|
+
const built = options.scope === "staging" ? await pipeline.buildEditorialOverlay({ previousManifest, previewId }) : await pipeline.buildProductionRevision({ previousManifest });
|
|
392
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-content-publish-"));
|
|
393
|
+
try {
|
|
394
|
+
if (!options.dryRun) {
|
|
395
|
+
for (const object of built.objects) {
|
|
396
|
+
const filePath = writeTempFile(tempRoot, objectFileName(object.pointer), toBuffer(object.body));
|
|
397
|
+
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, object.pointer, filePath);
|
|
398
|
+
}
|
|
399
|
+
if ("overlay" in built) {
|
|
400
|
+
const overlayFile = writeTempFile(tempRoot, "overlay.json", Buffer.from(JSON.stringify(built.overlay, null, 2)));
|
|
401
|
+
uploadObject(
|
|
402
|
+
options.tenantRoot,
|
|
403
|
+
wranglerPath,
|
|
404
|
+
wranglerEnv,
|
|
405
|
+
bucketName,
|
|
406
|
+
{
|
|
407
|
+
objectKey: built.overlay.locator?.overlayKey ?? `${locator.previewRoot}/${previewId}/overlay.json`,
|
|
408
|
+
sha256: stableHash(readFileSync(overlayFile)),
|
|
409
|
+
size: statSync(overlayFile).size,
|
|
410
|
+
contentType: "application/json"
|
|
411
|
+
},
|
|
412
|
+
overlayFile
|
|
413
|
+
);
|
|
414
|
+
} else {
|
|
415
|
+
const manifestFile = writeTempFile(tempRoot, "manifest.json", Buffer.from(JSON.stringify(built.manifest, null, 2)));
|
|
416
|
+
const snapshotKey = locator.manifestKey.replace(/\/common\.json$/u, `/manifests/${built.manifest.revision}.json`);
|
|
417
|
+
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
418
|
+
objectKey: snapshotKey,
|
|
419
|
+
sha256: stableHash(readFileSync(manifestFile)),
|
|
420
|
+
size: statSync(manifestFile).size,
|
|
421
|
+
contentType: "application/json"
|
|
422
|
+
}, manifestFile);
|
|
423
|
+
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
424
|
+
objectKey: locator.manifestKey,
|
|
425
|
+
sha256: stableHash(readFileSync(manifestFile)),
|
|
426
|
+
size: statSync(manifestFile).size,
|
|
427
|
+
contentType: "application/json"
|
|
428
|
+
}, manifestFile);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
432
|
+
state.content.lastPublishedManifestRevision = "overlay" in built ? built.overlay.previewId : built.manifest.revision;
|
|
433
|
+
state.content.lastPublishedManifestSha256 = stableHash(
|
|
434
|
+
JSON.stringify("overlay" in built ? built.overlay : built.manifest)
|
|
435
|
+
);
|
|
436
|
+
writeDeployState(options.tenantRoot, state, { target });
|
|
437
|
+
const previewToken = options.scope === "staging" && process.env.TREESEED_EDITORIAL_PREVIEW_SECRET ? signEditorialPreviewToken({
|
|
438
|
+
teamId,
|
|
439
|
+
previewId,
|
|
440
|
+
expiresAt: "overlay" in built ? built.overlay.expiresAt ?? new Date(Date.now() + resolvePublishedContentPreviewTtlHours(siteConfig) * 60 * 60 * 1e3).toISOString() : new Date(Date.now() + resolvePublishedContentPreviewTtlHours(siteConfig) * 60 * 60 * 1e3).toISOString()
|
|
441
|
+
}, process.env.TREESEED_EDITORIAL_PREVIEW_SECRET) : null;
|
|
442
|
+
const previewBaseUrl = state.pages?.url ?? siteConfig.siteUrl;
|
|
443
|
+
const previewUrl = previewToken ? `${previewBaseUrl}?preview=${encodeURIComponent(previewToken)}` : null;
|
|
444
|
+
await reportDeployment(reporter, {
|
|
445
|
+
environment: options.scope,
|
|
446
|
+
deploymentKind: "content",
|
|
447
|
+
status: "success",
|
|
448
|
+
sourceRef: branchName,
|
|
449
|
+
commitSha,
|
|
450
|
+
triggeredByType: "project_runner",
|
|
451
|
+
metadata: {
|
|
452
|
+
mode: options.scope === "staging" ? "editorial_overlay" : "production",
|
|
453
|
+
revision: "overlay" in built ? built.overlay.previewId : built.manifest.revision,
|
|
454
|
+
previewId: options.scope === "staging" ? previewId : null,
|
|
455
|
+
previewUrl,
|
|
456
|
+
entries: ("overlay" in built ? built.overlay.entries : built.manifest.entries).length,
|
|
457
|
+
artifacts: ("overlay" in built ? built.overlay.artifacts : built.manifest.artifacts)?.length ?? 0,
|
|
458
|
+
catalog: built.catalog.length
|
|
459
|
+
},
|
|
460
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
ok: true,
|
|
464
|
+
scope: options.scope,
|
|
465
|
+
mode: options.scope === "staging" ? "editorial_overlay" : "production",
|
|
466
|
+
revision: "overlay" in built ? built.overlay.previewId : built.manifest.revision,
|
|
467
|
+
previewId: options.scope === "staging" ? previewId : null,
|
|
468
|
+
previewUrl,
|
|
469
|
+
target: deployTargetLabel(target)
|
|
470
|
+
};
|
|
471
|
+
} finally {
|
|
472
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async function provisionProjectPlatform(options) {
|
|
476
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
477
|
+
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
478
|
+
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
479
|
+
const summary = provisionCloudflareResources(options.tenantRoot, { dryRun: options.dryRun, target });
|
|
480
|
+
const verification = verifyProvisionedCloudflareResources(options.tenantRoot, { dryRun: options.dryRun, target });
|
|
481
|
+
ensureGeneratedWranglerConfig(options.tenantRoot, { target });
|
|
482
|
+
const syncedSecrets = syncCloudflareSecrets(options.tenantRoot, { dryRun: options.dryRun, target });
|
|
483
|
+
const syncedRailway = options.scope === "local" ? [] : syncTreeseedRailwayEnvironment({ tenantRoot: options.tenantRoot, scope: options.scope, dryRun: options.dryRun });
|
|
484
|
+
const railwayValidation = options.scope === "local" ? validateRailwayServiceConfiguration(options.tenantRoot, options.scope) : validateRailwayDeployPrerequisites(options.tenantRoot, options.scope);
|
|
485
|
+
const railwaySchedules = options.scope === "local" ? [] : await ensureRailwayScheduledJobs(options.tenantRoot, options.scope, { dryRun: options.dryRun });
|
|
486
|
+
const railwayScheduleVerification = options.scope === "local" || options.dryRun ? { ok: true, checks: railwaySchedules } : await verifyRailwayScheduledJobs(options.tenantRoot, options.scope);
|
|
487
|
+
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
488
|
+
await reporter.reportEnvironment({
|
|
489
|
+
environment: options.scope,
|
|
490
|
+
deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
|
|
491
|
+
baseUrl: state.lastDeployedUrl,
|
|
492
|
+
cloudflareAccountId: siteConfig.cloudflare.accountId,
|
|
493
|
+
pagesProjectName: state.pages?.projectName ?? null,
|
|
494
|
+
workerName: state.workerName,
|
|
495
|
+
r2BucketName: state.content?.bucketName ?? null,
|
|
496
|
+
d1DatabaseName: state.d1Databases?.SITE_DATA_DB?.databaseName ?? null,
|
|
497
|
+
queueName: state.queues?.agentWork?.name ?? null,
|
|
498
|
+
railwayProjectName: railwayValidation.services[0]?.projectName ?? null,
|
|
499
|
+
metadata: {
|
|
500
|
+
target: deployTargetLabel(target),
|
|
501
|
+
previewEnabled: state.previewEnabled ?? false,
|
|
502
|
+
readiness: state.readiness
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
const resourceReports = [
|
|
506
|
+
{
|
|
507
|
+
environment: options.scope,
|
|
508
|
+
provider: "cloudflare",
|
|
509
|
+
resourceKind: "pages",
|
|
510
|
+
logicalName: state.pages?.projectName ?? "pages",
|
|
511
|
+
locator: state.pages?.url ?? null,
|
|
512
|
+
metadata: state.pages ?? {}
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
environment: options.scope,
|
|
516
|
+
provider: "cloudflare",
|
|
517
|
+
resourceKind: "worker",
|
|
518
|
+
logicalName: state.workerName,
|
|
519
|
+
locator: state.lastDeployedUrl ?? null,
|
|
520
|
+
metadata: { workerName: state.workerName }
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
environment: options.scope,
|
|
524
|
+
provider: "cloudflare",
|
|
525
|
+
resourceKind: "r2",
|
|
526
|
+
logicalName: state.content?.bucketName ?? "content",
|
|
527
|
+
locator: state.content?.manifestKey ?? null,
|
|
528
|
+
metadata: state.content ?? {}
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
environment: options.scope,
|
|
532
|
+
provider: "cloudflare",
|
|
533
|
+
resourceKind: "d1",
|
|
534
|
+
logicalName: state.d1Databases?.SITE_DATA_DB?.databaseName ?? "site-data",
|
|
535
|
+
locator: state.d1Databases?.SITE_DATA_DB?.databaseId ?? null,
|
|
536
|
+
metadata: state.d1Databases?.SITE_DATA_DB ?? {}
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
environment: options.scope,
|
|
540
|
+
provider: "cloudflare",
|
|
541
|
+
resourceKind: "queue",
|
|
542
|
+
logicalName: state.queues?.agentWork?.name ?? "agent-work",
|
|
543
|
+
locator: state.queues?.agentWork?.binding ?? null,
|
|
544
|
+
metadata: state.queues?.agentWork ?? {}
|
|
545
|
+
}
|
|
546
|
+
];
|
|
547
|
+
for (const resource of resourceReports) {
|
|
548
|
+
await reporter.reportResource(resource);
|
|
549
|
+
}
|
|
550
|
+
for (const service of railwayValidation.services) {
|
|
551
|
+
await reporter.reportResource({
|
|
552
|
+
environment: options.scope,
|
|
553
|
+
provider: "railway",
|
|
554
|
+
resourceKind: service.serviceId ? "railway_service" : "railway_project",
|
|
555
|
+
logicalName: service.key,
|
|
556
|
+
locator: service.serviceName ?? service.serviceId ?? service.projectName ?? service.projectId ?? null,
|
|
557
|
+
metadata: service
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
for (const schedule of railwaySchedules) {
|
|
561
|
+
const serviceState = state.services?.[schedule.service];
|
|
562
|
+
if (serviceState) {
|
|
563
|
+
serviceState.lastScheduleSyncAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
564
|
+
}
|
|
565
|
+
state.railwaySchedules[schedule.logicalName] = {
|
|
566
|
+
...state.railwaySchedules[schedule.logicalName] ?? {},
|
|
567
|
+
...schedule,
|
|
568
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
569
|
+
};
|
|
570
|
+
await reporter.reportResource({
|
|
571
|
+
environment: options.scope,
|
|
572
|
+
provider: "railway",
|
|
573
|
+
resourceKind: "railway_schedule",
|
|
574
|
+
logicalName: schedule.logicalName,
|
|
575
|
+
locator: schedule.id ?? schedule.expression,
|
|
576
|
+
metadata: schedule
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
writeDeployState(options.tenantRoot, state, { target });
|
|
580
|
+
await reportDeployment(reporter, {
|
|
581
|
+
environment: options.scope,
|
|
582
|
+
deploymentKind: "provision",
|
|
583
|
+
status: "success",
|
|
584
|
+
sourceRef: currentRef(options.tenantRoot),
|
|
585
|
+
commitSha: currentCommit(options.tenantRoot),
|
|
586
|
+
triggeredByType: "project_runner",
|
|
587
|
+
metadata: {
|
|
588
|
+
target: deployTargetLabel(target),
|
|
589
|
+
summary,
|
|
590
|
+
verification,
|
|
591
|
+
syncedSecrets,
|
|
592
|
+
syncedRailway,
|
|
593
|
+
railwayServices: railwayValidation.services.map((service) => service.key),
|
|
594
|
+
railwaySchedules,
|
|
595
|
+
railwayScheduleVerification
|
|
596
|
+
},
|
|
597
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
598
|
+
});
|
|
599
|
+
return {
|
|
600
|
+
ok: true,
|
|
601
|
+
scope: options.scope,
|
|
602
|
+
target: deployTargetLabel(target),
|
|
603
|
+
summary,
|
|
604
|
+
verification,
|
|
605
|
+
railway: {
|
|
606
|
+
services: railwayValidation.services.map((service) => service.key),
|
|
607
|
+
schedules: railwaySchedules,
|
|
608
|
+
verification: railwayScheduleVerification
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
async function deployProjectPlatform(options) {
|
|
613
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
614
|
+
const commitSha = currentCommit(options.tenantRoot);
|
|
615
|
+
const branchName = currentRef(options.tenantRoot);
|
|
616
|
+
await reportDeployment(reporter, {
|
|
617
|
+
environment: options.scope,
|
|
618
|
+
deploymentKind: "code",
|
|
619
|
+
status: "running",
|
|
620
|
+
sourceRef: branchName,
|
|
621
|
+
commitSha,
|
|
622
|
+
triggeredByType: "project_runner",
|
|
623
|
+
metadata: { scope: options.scope }
|
|
624
|
+
});
|
|
625
|
+
await provisionProjectPlatform({ ...options, reporter });
|
|
626
|
+
runNodeScript(options.tenantRoot, "tenant-deploy", ["--environment", options.scope, ...options.dryRun ? ["--dry-run"] : []]);
|
|
627
|
+
const serviceResults = [];
|
|
628
|
+
if (options.scope !== "local") {
|
|
629
|
+
const validation = validateRailwayDeployPrerequisites(options.tenantRoot, options.scope);
|
|
630
|
+
for (const service of validation.services) {
|
|
631
|
+
serviceResults.push(deployRailwayService(options.tenantRoot, service, { dryRun: options.dryRun }));
|
|
632
|
+
}
|
|
633
|
+
finalizeDeploymentState(options.tenantRoot, {
|
|
634
|
+
target: createPersistentDeployTarget(options.scope),
|
|
635
|
+
serviceResults
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
const monitor = await monitorProjectPlatform({ ...options, reporter });
|
|
639
|
+
await reportDeployment(reporter, {
|
|
640
|
+
environment: options.scope,
|
|
641
|
+
deploymentKind: "code",
|
|
642
|
+
status: "success",
|
|
643
|
+
sourceRef: branchName,
|
|
644
|
+
commitSha,
|
|
645
|
+
triggeredByType: "project_runner",
|
|
646
|
+
metadata: {
|
|
647
|
+
scope: options.scope,
|
|
648
|
+
railway: options.scope === "local" ? [] : configuredRailwayServices(options.tenantRoot, options.scope).map((service) => service.key),
|
|
649
|
+
monitor
|
|
650
|
+
},
|
|
651
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
652
|
+
});
|
|
653
|
+
return {
|
|
654
|
+
ok: true,
|
|
655
|
+
scope: options.scope,
|
|
656
|
+
monitor,
|
|
657
|
+
serviceResults
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async function publishProjectContent(options) {
|
|
661
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
662
|
+
return publishContent(options, reporter);
|
|
663
|
+
}
|
|
664
|
+
async function monitorProjectPlatform(options) {
|
|
665
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
666
|
+
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
667
|
+
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
668
|
+
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
669
|
+
const apiBaseUrl = siteConfig.services?.api?.environments?.[options.scope]?.baseUrl ?? siteConfig.services?.api?.publicBaseUrl ?? state.services?.api?.lastDeployedUrl ?? null;
|
|
670
|
+
const checks = {
|
|
671
|
+
pages: await probeHttp(state.pages?.url ?? siteConfig.siteUrl),
|
|
672
|
+
apiHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
673
|
+
apiReady: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/readyz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
674
|
+
d1Health: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz/deep`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
675
|
+
agentHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/agent/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
676
|
+
r2: options.dryRun ? { ok: true, skipped: true, reason: "dry_run" } : probeR2(options.tenantRoot, siteConfig, state, target),
|
|
677
|
+
queue: options.dryRun ? Promise.resolve({ ok: true, skipped: true, reason: "dry_run" }) : probeQueue(siteConfig, state),
|
|
678
|
+
runner: probeRunnerHealth(siteConfig, options.scope),
|
|
679
|
+
scaleProbe: probeScaleConfiguration(siteConfig, state),
|
|
680
|
+
readiness: state.readiness
|
|
681
|
+
};
|
|
682
|
+
const resolvedChecks = {
|
|
683
|
+
...checks,
|
|
684
|
+
r2: await checks.r2,
|
|
685
|
+
queue: await checks.queue,
|
|
686
|
+
runner: await checks.runner
|
|
687
|
+
};
|
|
688
|
+
const ok = [
|
|
689
|
+
resolvedChecks.pages,
|
|
690
|
+
resolvedChecks.apiHealth,
|
|
691
|
+
resolvedChecks.apiReady,
|
|
692
|
+
resolvedChecks.d1Health,
|
|
693
|
+
resolvedChecks.agentHealth,
|
|
694
|
+
resolvedChecks.r2,
|
|
695
|
+
resolvedChecks.queue,
|
|
696
|
+
resolvedChecks.runner,
|
|
697
|
+
resolvedChecks.scaleProbe
|
|
698
|
+
].every((check) => check?.ok === true || check?.skipped === true);
|
|
699
|
+
if (!ok) {
|
|
700
|
+
throw new Error(`Treeseed monitor failed for ${options.scope}.`);
|
|
701
|
+
}
|
|
702
|
+
await reportDeployment(reporter, {
|
|
703
|
+
environment: options.scope,
|
|
704
|
+
deploymentKind: "mixed",
|
|
705
|
+
status: "success",
|
|
706
|
+
sourceRef: currentRef(options.tenantRoot),
|
|
707
|
+
commitSha: currentCommit(options.tenantRoot),
|
|
708
|
+
triggeredByType: "project_runner",
|
|
709
|
+
metadata: {
|
|
710
|
+
mode: "monitor",
|
|
711
|
+
target: deployTargetLabel(target),
|
|
712
|
+
checks: resolvedChecks
|
|
713
|
+
},
|
|
714
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
715
|
+
});
|
|
716
|
+
return {
|
|
717
|
+
ok,
|
|
718
|
+
target: deployTargetLabel(target),
|
|
719
|
+
checks: resolvedChecks
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
async function syncControlPlaneState(options) {
|
|
723
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
724
|
+
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
725
|
+
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
726
|
+
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
727
|
+
await reporter.reportEnvironment({
|
|
728
|
+
environment: options.scope,
|
|
729
|
+
deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
|
|
730
|
+
baseUrl: state.lastDeployedUrl,
|
|
731
|
+
cloudflareAccountId: siteConfig.cloudflare.accountId,
|
|
732
|
+
pagesProjectName: state.pages?.projectName ?? null,
|
|
733
|
+
workerName: state.workerName,
|
|
734
|
+
r2BucketName: state.content?.bucketName ?? null,
|
|
735
|
+
d1DatabaseName: state.d1Databases?.SITE_DATA_DB?.databaseName ?? null,
|
|
736
|
+
queueName: state.queues?.agentWork?.name ?? null,
|
|
737
|
+
railwayProjectName: state.services.api?.provider === "railway" ? state.services.api?.lastDeployedUrl ?? null : null,
|
|
738
|
+
metadata: { target: deployTargetLabel(target) }
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
async function runProjectPlatformAction(action, options) {
|
|
742
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: options.tenantRoot, scope: options.scope, override: true });
|
|
743
|
+
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
744
|
+
try {
|
|
745
|
+
switch (action) {
|
|
746
|
+
case "provision":
|
|
747
|
+
return await provisionProjectPlatform({ ...options, reporter });
|
|
748
|
+
case "deploy_code":
|
|
749
|
+
return await deployProjectPlatform({ ...options, reporter });
|
|
750
|
+
case "publish_content":
|
|
751
|
+
return await publishProjectContent({ ...options, reporter });
|
|
752
|
+
case "monitor":
|
|
753
|
+
return await monitorProjectPlatform({ ...options, reporter });
|
|
754
|
+
default:
|
|
755
|
+
throw new Error(`Unsupported workflow action "${action}".`);
|
|
756
|
+
}
|
|
757
|
+
} catch (error) {
|
|
758
|
+
await reportDeployment(reporter, {
|
|
759
|
+
environment: options.scope,
|
|
760
|
+
deploymentKind: action === "provision" ? "provision" : action === "publish_content" ? "content" : action === "deploy_code" ? "code" : "mixed",
|
|
761
|
+
status: "failed",
|
|
762
|
+
sourceRef: currentRef(options.tenantRoot),
|
|
763
|
+
commitSha: currentCommit(options.tenantRoot),
|
|
764
|
+
triggeredByType: "project_runner",
|
|
765
|
+
metadata: {
|
|
766
|
+
message: error instanceof Error ? error.message : String(error)
|
|
767
|
+
},
|
|
768
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
769
|
+
}).catch(() => void 0);
|
|
770
|
+
throw error;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
export {
|
|
774
|
+
deployProjectPlatform,
|
|
775
|
+
inferEnvironmentFromBranch,
|
|
776
|
+
monitorProjectPlatform,
|
|
777
|
+
provisionProjectPlatform,
|
|
778
|
+
publishProjectContent,
|
|
779
|
+
resolveScope,
|
|
780
|
+
runProjectPlatformAction,
|
|
781
|
+
syncControlPlaneState
|
|
782
|
+
};
|