@treeseed/sdk 0.4.12 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control-plane-client.d.ts +60 -1
- package/dist/control-plane-client.js +59 -0
- package/dist/control-plane.d.ts +1 -1
- package/dist/control-plane.js +11 -4
- package/dist/d1-store.d.ts +58 -0
- package/dist/d1-store.js +64 -0
- package/dist/dispatch.js +6 -0
- package/dist/graph/schema.js +4 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +32 -0
- package/dist/knowledge-coop.d.ts +223 -0
- package/dist/knowledge-coop.js +82 -0
- package/dist/model-registry.js +79 -0
- package/dist/operations/providers/default.js +128 -7
- package/dist/operations/services/config-runtime.d.ts +102 -24
- package/dist/operations/services/config-runtime.js +896 -160
- package/dist/operations/services/deploy.d.ts +223 -15
- package/dist/operations/services/deploy.js +626 -55
- package/dist/operations/services/git-workflow.d.ts +47 -3
- package/dist/operations/services/git-workflow.js +125 -19
- package/dist/operations/services/github-automation.d.ts +85 -0
- package/dist/operations/services/github-automation.js +220 -1
- package/dist/operations/services/key-agent.d.ts +118 -0
- package/dist/operations/services/key-agent.js +476 -0
- package/dist/operations/services/knowledge-coop-launch.d.ts +90 -0
- package/dist/operations/services/knowledge-coop-launch.js +753 -0
- package/dist/operations/services/knowledge-coop-packaging.d.ts +59 -0
- package/dist/operations/services/knowledge-coop-packaging.js +234 -0
- package/dist/operations/services/local-dev.d.ts +0 -1
- package/dist/operations/services/local-dev.js +1 -14
- package/dist/operations/services/project-platform.d.ts +42 -182
- package/dist/operations/services/project-platform.js +162 -59
- package/dist/operations/services/railway-deploy.d.ts +1 -0
- package/dist/operations/services/railway-deploy.js +31 -13
- package/dist/operations/services/runtime-tools.d.ts +52 -5
- package/dist/operations/services/runtime-tools.js +186 -26
- package/dist/operations/services/watch-dev.js +2 -4
- package/dist/operations/services/workspace-preflight.d.ts +4 -4
- package/dist/operations/services/workspace-preflight.js +22 -20
- package/dist/operations/services/workspace-save.d.ts +10 -1
- package/dist/operations/services/workspace-save.js +54 -3
- package/dist/operations/services/workspace-tools.d.ts +1 -0
- package/dist/operations/services/workspace-tools.js +20 -5
- package/dist/operations-registry.js +15 -8
- package/dist/operations-types.d.ts +2 -2
- package/dist/platform/contracts.d.ts +39 -3
- package/dist/platform/deploy-config.d.ts +12 -1
- package/dist/platform/deploy-config.js +214 -15
- package/dist/platform/deploy-runtime.d.ts +1 -0
- package/dist/platform/deploy-runtime.js +10 -2
- package/dist/platform/env.yaml +93 -61
- package/dist/platform/environment.d.ts +13 -2
- package/dist/platform/environment.js +90 -20
- package/dist/platform/plugins/constants.d.ts +1 -0
- package/dist/platform/plugins/constants.js +7 -6
- package/dist/platform/tenant/runtime-config.js +8 -1
- package/dist/platform/tenant-config.js +4 -0
- package/dist/platform/utils/site-config-schema.js +18 -0
- package/dist/plugin-default.js +2 -2
- package/dist/scripts/key-agent.js +165 -0
- package/dist/scripts/tenant-build.js +4 -1
- package/dist/scripts/tenant-check.js +4 -1
- package/dist/scripts/tenant-deploy.js +43 -4
- package/dist/scripts/tenant-dev.js +0 -1
- package/dist/scripts/workspace-start-warning.js +2 -2
- package/dist/sdk-types.d.ts +2 -2
- package/dist/sdk-types.js +2 -0
- package/dist/sdk.d.ts +13 -0
- package/dist/sdk.js +40 -0
- package/dist/stores/knowledge-coop-store.d.ts +56 -0
- package/dist/stores/knowledge-coop-store.js +482 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +6 -2
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +4 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +25 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +22 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +11 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +17 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +17 -10
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +69 -7
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +1 -0
- package/dist/workflow/operations.d.ts +592 -243
- package/dist/workflow/operations.js +1908 -219
- package/dist/workflow/runs.d.ts +90 -0
- package/dist/workflow/runs.js +242 -0
- package/dist/workflow/session.d.ts +31 -0
- package/dist/workflow/session.js +97 -0
- package/dist/workflow-state.d.ts +88 -2
- package/dist/workflow-state.js +288 -26
- package/dist/workflow-support.d.ts +1 -1
- package/dist/workflow-support.js +32 -2
- package/dist/workflow.d.ts +93 -3
- package/dist/workflow.js +12 -0
- package/package.json +1 -1
- package/templates/github/deploy.workflow.yml +11 -1
- package/dist/scripts/sync-dev-vars.js +0 -6
- package/dist/scripts/workspace-close.js +0 -24
- package/dist/scripts/workspace-release.js +0 -42
- package/dist/scripts/workspace-start.js +0 -71
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
ensureGeneratedWranglerConfig,
|
|
21
21
|
finalizeDeploymentState,
|
|
22
22
|
loadDeployState,
|
|
23
|
+
purgePublishedContentCaches,
|
|
23
24
|
provisionCloudflareResources,
|
|
24
25
|
syncCloudflareSecrets,
|
|
25
26
|
verifyProvisionedCloudflareResources,
|
|
@@ -175,14 +176,66 @@ function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer
|
|
|
175
176
|
pointer.contentType ?? inferContentType(filePath)
|
|
176
177
|
], wranglerEnv);
|
|
177
178
|
}
|
|
179
|
+
function deleteObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey) {
|
|
180
|
+
runWrangler(tenantRoot, [
|
|
181
|
+
"r2",
|
|
182
|
+
"object",
|
|
183
|
+
"delete",
|
|
184
|
+
`${bucketName}/${objectKey}`,
|
|
185
|
+
"--config",
|
|
186
|
+
wranglerPath,
|
|
187
|
+
"--remote"
|
|
188
|
+
], wranglerEnv, { allowFailure: true });
|
|
189
|
+
}
|
|
178
190
|
function objectFileName(pointer) {
|
|
179
191
|
const ext = extname(pointer.objectKey) || ".json";
|
|
180
192
|
return `${pointer.sha256}${ext}`;
|
|
181
193
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
194
|
+
function canonicalSitePathForEntry(entry) {
|
|
195
|
+
return entry.model === "pages" ? `/${entry.slug || entry.id || ""}`.replace(/\/+$/u, "") || "/" : `/${entry.model}/${entry.slug || entry.id || ""}`.replace(/\/+$/u, "");
|
|
196
|
+
}
|
|
197
|
+
function contentIndexPathForModel(model) {
|
|
198
|
+
return model === "pages" ? null : `/${model}`;
|
|
199
|
+
}
|
|
200
|
+
function sourceLikeFreshnessPaths() {
|
|
201
|
+
return ["/", "/feed.xml", "/sitemap-index.xml"];
|
|
202
|
+
}
|
|
203
|
+
function entrySignature(entry) {
|
|
204
|
+
const { publishedAt, ...rest } = entry;
|
|
205
|
+
return stableHash(JSON.stringify(rest));
|
|
206
|
+
}
|
|
207
|
+
function artifactSignature(artifact) {
|
|
208
|
+
const { publishedAt, ...rest } = artifact;
|
|
209
|
+
return stableHash(JSON.stringify(rest));
|
|
210
|
+
}
|
|
211
|
+
function canonicalEntryPath(entry) {
|
|
212
|
+
return `${entry.model}/${entry.slug || entry.id || ""}`.replace(/^\/+|\/+$/gu, "");
|
|
213
|
+
}
|
|
214
|
+
function changedEntries(previousManifest, nextEntries) {
|
|
215
|
+
const previous = new Map((previousManifest?.entries ?? []).map((entry) => [canonicalEntryPath(entry), entrySignature(entry)]));
|
|
216
|
+
return nextEntries.filter((entry) => previous.get(canonicalEntryPath(entry)) !== entrySignature(entry));
|
|
217
|
+
}
|
|
218
|
+
function changedArtifacts(previousManifest, nextArtifacts) {
|
|
219
|
+
const previous = new Map((previousManifest?.artifacts ?? []).map((artifact) => [`${artifact.kind}:${artifact.itemId}`, artifactSignature(artifact)]));
|
|
220
|
+
return nextArtifacts.filter((artifact) => previous.get(`${artifact.kind}:${artifact.itemId}`) !== artifactSignature(artifact));
|
|
221
|
+
}
|
|
222
|
+
function absoluteUrl(baseUrl, path) {
|
|
223
|
+
if (!baseUrl) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
return new URL(path.startsWith("/") ? path : `/${path}`, baseUrl).toString();
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function stableEntryAliasKey(teamId, entry) {
|
|
233
|
+
const ext = extname(entry.content.objectKey) || ".md";
|
|
234
|
+
return `teams/${teamId}/published/entries/${entry.model}/${entry.slug || entry.id}${ext}`;
|
|
235
|
+
}
|
|
236
|
+
function stableArtifactAliasKey(teamId, artifact) {
|
|
237
|
+
const fileName = typeof artifact.metadata?.fileName === "string" && artifact.metadata.fileName ? artifact.metadata.fileName : `${artifact.itemId}${extname(artifact.content.objectKey) || ".bin"}`;
|
|
238
|
+
return `teams/${teamId}/published/artifacts/${artifact.kind}/${artifact.itemId}/${fileName}`;
|
|
186
239
|
}
|
|
187
240
|
async function probeHttp(url) {
|
|
188
241
|
try {
|
|
@@ -203,49 +256,8 @@ async function probeHttp(url) {
|
|
|
203
256
|
};
|
|
204
257
|
}
|
|
205
258
|
}
|
|
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
259
|
function queueClientConfig(siteConfig, state) {
|
|
248
|
-
const accountId = siteConfig.cloudflare.accountId;
|
|
260
|
+
const accountId = String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim();
|
|
249
261
|
const queueId = state.queues?.agentWork?.queueId;
|
|
250
262
|
const token = process.env.TREESEED_QUEUE_PUSH_TOKEN?.trim() || process.env.TREESEED_QUEUE_PULL_TOKEN?.trim() || process.env.CLOUDFLARE_API_TOKEN?.trim() || "";
|
|
251
263
|
if (!accountId || !queueId || !token) {
|
|
@@ -310,11 +322,12 @@ function deleteR2Object(tenantRoot, bucketName, objectKey, wranglerPath, wrangle
|
|
|
310
322
|
}
|
|
311
323
|
function probeR2(tenantRoot, siteConfig, state, target) {
|
|
312
324
|
const bucketName = state.content?.bucketName;
|
|
325
|
+
const cloudflareAccountId = String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim();
|
|
313
326
|
if (!bucketName) {
|
|
314
327
|
return { ok: false, skipped: true, reason: "r2_unconfigured" };
|
|
315
328
|
}
|
|
316
329
|
const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
317
|
-
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID:
|
|
330
|
+
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: cloudflareAccountId };
|
|
318
331
|
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-r2-health-"));
|
|
319
332
|
const objectKey = r2HealthKey(state);
|
|
320
333
|
try {
|
|
@@ -358,7 +371,7 @@ function probeScaleConfiguration(siteConfig, state) {
|
|
|
358
371
|
async function publishContent(options, reporter) {
|
|
359
372
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
360
373
|
const tenantConfig = loadTreeseedManifest(resolve(options.tenantRoot, "src", "manifest.yaml"));
|
|
361
|
-
const teamId = siteConfig.hosting?.teamId ?? siteConfig.slug;
|
|
374
|
+
const teamId = String(process.env.TREESEED_HOSTING_TEAM_ID ?? siteConfig.hosting?.teamId ?? siteConfig.slug).trim() || siteConfig.slug;
|
|
362
375
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
376
|
const commitSha = currentCommit(options.tenantRoot);
|
|
364
377
|
const branchName = currentRef(options.tenantRoot);
|
|
@@ -366,10 +379,10 @@ async function publishContent(options, reporter) {
|
|
|
366
379
|
const locator = resolveTeamScopedContentLocator(siteConfig, teamId);
|
|
367
380
|
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
368
381
|
const { wranglerPath } = ensureGeneratedWranglerConfig(options.tenantRoot, { target });
|
|
369
|
-
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: siteConfig.cloudflare.accountId };
|
|
370
|
-
const bucketName = siteConfig.cloudflare.r2?.bucketName;
|
|
382
|
+
const wranglerEnv = { CLOUDFLARE_ACCOUNT_ID: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() };
|
|
383
|
+
const bucketName = String(process.env.TREESEED_CONTENT_BUCKET_NAME ?? siteConfig.cloudflare.r2?.bucketName ?? "").trim();
|
|
371
384
|
if (!bucketName) {
|
|
372
|
-
throw new Error("Treeseed content publish requires
|
|
385
|
+
throw new Error("Treeseed content publish requires TREESEED_CONTENT_BUCKET_NAME to be configured.");
|
|
373
386
|
}
|
|
374
387
|
const previousManifest = readR2JsonObject(
|
|
375
388
|
options.tenantRoot,
|
|
@@ -389,6 +402,85 @@ async function publishContent(options, reporter) {
|
|
|
389
402
|
previewId
|
|
390
403
|
});
|
|
391
404
|
const built = options.scope === "staging" ? await pipeline.buildEditorialOverlay({ previousManifest, previewId }) : await pipeline.buildProductionRevision({ previousManifest });
|
|
405
|
+
const changedEntrySet = "manifest" in built ? changedEntries(previousManifest, built.manifest.entries) : [];
|
|
406
|
+
const changedArtifactSet = "manifest" in built ? changedArtifacts(previousManifest, built.manifest.artifacts ?? []) : [];
|
|
407
|
+
const tombstones = "manifest" in built ? built.manifest.tombstones ?? [] : [];
|
|
408
|
+
const objectByKey = new Map(built.objects.map((object) => [object.pointer.objectKey, object]));
|
|
409
|
+
const publicObjectBaseUrl = process.env.TREESEED_CONTENT_PUBLIC_BASE_URL ?? siteConfig.cloudflare.r2?.publicBaseUrl ?? null;
|
|
410
|
+
const stableObjectUploads = [];
|
|
411
|
+
const deletedObjectKeys = [];
|
|
412
|
+
const contentPurgeUrls = /* @__PURE__ */ new Set();
|
|
413
|
+
if ("manifest" in built && publicObjectBaseUrl) {
|
|
414
|
+
for (const entry of built.manifest.entries) {
|
|
415
|
+
entry.content.publicUrl = absoluteUrl(publicObjectBaseUrl, stableEntryAliasKey(teamId, entry)) ?? void 0;
|
|
416
|
+
}
|
|
417
|
+
for (const artifact of built.manifest.artifacts ?? []) {
|
|
418
|
+
artifact.content.publicUrl = artifact.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableArtifactAliasKey(teamId, artifact)) ?? void 0;
|
|
419
|
+
}
|
|
420
|
+
for (const entry of changedEntrySet) {
|
|
421
|
+
const current = entry;
|
|
422
|
+
const sourceObject = objectByKey.get(current.content.objectKey);
|
|
423
|
+
const publicUrl = current.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableEntryAliasKey(teamId, current));
|
|
424
|
+
if (sourceObject) {
|
|
425
|
+
stableObjectUploads.push({
|
|
426
|
+
pointer: {
|
|
427
|
+
objectKey: stableEntryAliasKey(teamId, current),
|
|
428
|
+
sha256: sourceObject.pointer.sha256,
|
|
429
|
+
size: sourceObject.pointer.size,
|
|
430
|
+
contentType: sourceObject.pointer.contentType,
|
|
431
|
+
publicUrl: publicUrl ?? void 0
|
|
432
|
+
},
|
|
433
|
+
body: toBuffer(sourceObject.body)
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (publicUrl) {
|
|
437
|
+
contentPurgeUrls.add(publicUrl);
|
|
438
|
+
}
|
|
439
|
+
contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, canonicalSitePathForEntry(current)) ?? "");
|
|
440
|
+
const indexPath = contentIndexPathForModel(current.model);
|
|
441
|
+
if (indexPath) {
|
|
442
|
+
contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, indexPath) ?? "");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
for (const artifact of changedArtifactSet) {
|
|
446
|
+
const current = artifact;
|
|
447
|
+
const sourceObject = objectByKey.get(current.content.objectKey);
|
|
448
|
+
const publicUrl = current.content.publicUrl ?? absoluteUrl(publicObjectBaseUrl, stableArtifactAliasKey(teamId, current));
|
|
449
|
+
if (sourceObject && publicUrl) {
|
|
450
|
+
stableObjectUploads.push({
|
|
451
|
+
pointer: {
|
|
452
|
+
objectKey: stableArtifactAliasKey(teamId, current),
|
|
453
|
+
sha256: sourceObject.pointer.sha256,
|
|
454
|
+
size: sourceObject.pointer.size,
|
|
455
|
+
contentType: sourceObject.pointer.contentType,
|
|
456
|
+
publicUrl
|
|
457
|
+
},
|
|
458
|
+
body: toBuffer(sourceObject.body)
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
if (publicUrl) {
|
|
462
|
+
contentPurgeUrls.add(publicUrl);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (const tombstone of tombstones) {
|
|
466
|
+
const [model, ...slugParts] = String(tombstone.path ?? "").split("/");
|
|
467
|
+
if (!model || slugParts.length === 0) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const slug = slugParts.join("/");
|
|
471
|
+
const aliasKey = stableEntryAliasKey(teamId, { model, slug, content: { objectKey: `${slug}.md` } });
|
|
472
|
+
deletedObjectKeys.push(aliasKey);
|
|
473
|
+
contentPurgeUrls.add(absoluteUrl(publicObjectBaseUrl, aliasKey) ?? "");
|
|
474
|
+
contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, canonicalSitePathForEntry({ model, slug })) ?? "");
|
|
475
|
+
const indexPath = contentIndexPathForModel(model);
|
|
476
|
+
if (indexPath) {
|
|
477
|
+
contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, indexPath) ?? "");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const freshnessPath of sourceLikeFreshnessPaths()) {
|
|
481
|
+
contentPurgeUrls.add(absoluteUrl(siteConfig.siteUrl, freshnessPath) ?? "");
|
|
482
|
+
}
|
|
483
|
+
}
|
|
392
484
|
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-content-publish-"));
|
|
393
485
|
try {
|
|
394
486
|
if (!options.dryRun) {
|
|
@@ -396,6 +488,13 @@ async function publishContent(options, reporter) {
|
|
|
396
488
|
const filePath = writeTempFile(tempRoot, objectFileName(object.pointer), toBuffer(object.body));
|
|
397
489
|
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, object.pointer, filePath);
|
|
398
490
|
}
|
|
491
|
+
for (const alias of stableObjectUploads) {
|
|
492
|
+
const filePath = writeTempFile(tempRoot, objectFileName(alias.pointer), alias.body);
|
|
493
|
+
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, alias.pointer, filePath);
|
|
494
|
+
}
|
|
495
|
+
for (const objectKey of deletedObjectKeys) {
|
|
496
|
+
deleteObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey);
|
|
497
|
+
}
|
|
399
498
|
if ("overlay" in built) {
|
|
400
499
|
const overlayFile = writeTempFile(tempRoot, "overlay.json", Buffer.from(JSON.stringify(built.overlay, null, 2)));
|
|
401
500
|
uploadObject(
|
|
@@ -426,6 +525,12 @@ async function publishContent(options, reporter) {
|
|
|
426
525
|
size: statSync(manifestFile).size,
|
|
427
526
|
contentType: "application/json"
|
|
428
527
|
}, manifestFile);
|
|
528
|
+
if (contentPurgeUrls.size > 0) {
|
|
529
|
+
try {
|
|
530
|
+
purgePublishedContentCaches(options.tenantRoot, [...contentPurgeUrls].filter(Boolean), { target });
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
429
534
|
}
|
|
430
535
|
}
|
|
431
536
|
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
@@ -455,7 +560,8 @@ async function publishContent(options, reporter) {
|
|
|
455
560
|
previewUrl,
|
|
456
561
|
entries: ("overlay" in built ? built.overlay.entries : built.manifest.entries).length,
|
|
457
562
|
artifacts: ("overlay" in built ? built.overlay.artifacts : built.manifest.artifacts)?.length ?? 0,
|
|
458
|
-
catalog: built.catalog.length
|
|
563
|
+
catalog: built.catalog.length,
|
|
564
|
+
cachePurgeCount: contentPurgeUrls.size
|
|
459
565
|
},
|
|
460
566
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
461
567
|
});
|
|
@@ -489,7 +595,7 @@ async function provisionProjectPlatform(options) {
|
|
|
489
595
|
environment: options.scope,
|
|
490
596
|
deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
|
|
491
597
|
baseUrl: state.lastDeployedUrl,
|
|
492
|
-
cloudflareAccountId: siteConfig.cloudflare.accountId,
|
|
598
|
+
cloudflareAccountId: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() || null,
|
|
493
599
|
pagesProjectName: state.pages?.projectName ?? null,
|
|
494
600
|
workerName: state.workerName,
|
|
495
601
|
r2BucketName: state.content?.bucketName ?? null,
|
|
@@ -666,7 +772,7 @@ async function monitorProjectPlatform(options) {
|
|
|
666
772
|
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
667
773
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
668
774
|
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;
|
|
775
|
+
const apiBaseUrl = siteConfig.services?.api?.environments?.[options.scope]?.baseUrl ?? siteConfig.services?.api?.publicBaseUrl ?? process.env.TREESEED_API_BASE_URL ?? state.services?.api?.lastDeployedUrl ?? null;
|
|
670
776
|
const checks = {
|
|
671
777
|
pages: await probeHttp(state.pages?.url ?? siteConfig.siteUrl),
|
|
672
778
|
apiHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
@@ -675,15 +781,13 @@ async function monitorProjectPlatform(options) {
|
|
|
675
781
|
agentHealth: apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/agent/healthz`) : { ok: false, skipped: true, reason: "api_url_unconfigured" },
|
|
676
782
|
r2: options.dryRun ? { ok: true, skipped: true, reason: "dry_run" } : probeR2(options.tenantRoot, siteConfig, state, target),
|
|
677
783
|
queue: options.dryRun ? Promise.resolve({ ok: true, skipped: true, reason: "dry_run" }) : probeQueue(siteConfig, state),
|
|
678
|
-
runner: probeRunnerHealth(siteConfig, options.scope),
|
|
679
784
|
scaleProbe: probeScaleConfiguration(siteConfig, state),
|
|
680
785
|
readiness: state.readiness
|
|
681
786
|
};
|
|
682
787
|
const resolvedChecks = {
|
|
683
788
|
...checks,
|
|
684
789
|
r2: await checks.r2,
|
|
685
|
-
queue: await checks.queue
|
|
686
|
-
runner: await checks.runner
|
|
790
|
+
queue: await checks.queue
|
|
687
791
|
};
|
|
688
792
|
const ok = [
|
|
689
793
|
resolvedChecks.pages,
|
|
@@ -693,7 +797,6 @@ async function monitorProjectPlatform(options) {
|
|
|
693
797
|
resolvedChecks.agentHealth,
|
|
694
798
|
resolvedChecks.r2,
|
|
695
799
|
resolvedChecks.queue,
|
|
696
|
-
resolvedChecks.runner,
|
|
697
800
|
resolvedChecks.scaleProbe
|
|
698
801
|
].every((check) => check?.ok === true || check?.skipped === true);
|
|
699
802
|
if (!ok) {
|
|
@@ -728,7 +831,7 @@ async function syncControlPlaneState(options) {
|
|
|
728
831
|
environment: options.scope,
|
|
729
832
|
deploymentProfile: siteConfig.hosting?.kind ?? "self_hosted_project",
|
|
730
833
|
baseUrl: state.lastDeployedUrl,
|
|
731
|
-
cloudflareAccountId: siteConfig.cloudflare.accountId,
|
|
834
|
+
cloudflareAccountId: String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim() || null,
|
|
732
835
|
pagesProjectName: state.pages?.projectName ?? null,
|
|
733
836
|
workerName: state.workerName,
|
|
734
837
|
r2BucketName: state.content?.bucketName ?? null,
|
|
@@ -6,8 +6,8 @@ const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
|
6
6
|
function normalizeScope(scope) {
|
|
7
7
|
return scope === "prod" ? "prod" : scope === "staging" ? "staging" : "local";
|
|
8
8
|
}
|
|
9
|
-
const RAILWAY_SERVICE_KEYS = ["api", "
|
|
10
|
-
const HOSTED_PROJECT_SERVICE_KEYS = ["api", "manager", "worker"
|
|
9
|
+
const RAILWAY_SERVICE_KEYS = ["api", "manager", "worker", "workdayStart", "workdayReport"];
|
|
10
|
+
const HOSTED_PROJECT_SERVICE_KEYS = ["api", "manager", "worker"];
|
|
11
11
|
function normalizeScheduleExpressions(value) {
|
|
12
12
|
if (typeof value === "string" && value.trim()) {
|
|
13
13
|
return [value.trim()];
|
|
@@ -179,14 +179,18 @@ function runRailway(args, { cwd, capture = false, allowFailure = false } = {}) {
|
|
|
179
179
|
function configuredRailwayServices(tenantRoot, scope) {
|
|
180
180
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
181
181
|
const normalizedScope = normalizeScope(scope);
|
|
182
|
-
const
|
|
182
|
+
const managedRuntime = deployConfig.runtime?.mode === "treeseed_managed";
|
|
183
|
+
const hostingKind = deployConfig.hosting?.kind ?? (managedRuntime ? "hosted_project" : "self_hosted_project");
|
|
184
|
+
if (!managedRuntime) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
183
187
|
const serviceKeys = hostingKind === "hosted_project" ? HOSTED_PROJECT_SERVICE_KEYS : RAILWAY_SERVICE_KEYS;
|
|
184
188
|
return serviceKeys.map((serviceKey) => {
|
|
185
189
|
const service = deployConfig.services?.[serviceKey];
|
|
186
190
|
if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
|
|
187
191
|
return null;
|
|
188
192
|
}
|
|
189
|
-
const defaultRootDir = ["api", "manager", "worker", "
|
|
193
|
+
const defaultRootDir = ["api", "manager", "worker", "workdayStart", "workdayReport"].includes(serviceKey) ? "." : "packages/core";
|
|
190
194
|
const serviceRoot = resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
|
|
191
195
|
const railwayEnvironment = service.environments?.[normalizedScope]?.railwayEnvironment ?? normalizedScope;
|
|
192
196
|
const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? null;
|
|
@@ -226,10 +230,10 @@ function configuredRailwayScheduledJobs(tenantRoot, scope) {
|
|
|
226
230
|
}
|
|
227
231
|
function resolveRailwayDeploymentProfile(tenantRoot) {
|
|
228
232
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
229
|
-
const hostingKind = deployConfig.hosting?.kind ?? "self_hosted_project";
|
|
233
|
+
const hostingKind = deployConfig.hosting?.kind ?? (deployConfig.runtime?.mode === "treeseed_managed" ? "hosted_project" : "self_hosted_project");
|
|
230
234
|
return {
|
|
231
235
|
hostingKind,
|
|
232
|
-
managedTopology: hostingKind === "hosted_project" ? [...HOSTED_PROJECT_SERVICE_KEYS] : [...RAILWAY_SERVICE_KEYS]
|
|
236
|
+
managedTopology: deployConfig.runtime?.mode === "treeseed_managed" ? hostingKind === "hosted_project" ? [...HOSTED_PROJECT_SERVICE_KEYS] : [...RAILWAY_SERVICE_KEYS] : []
|
|
233
237
|
};
|
|
234
238
|
}
|
|
235
239
|
function validateRailwayServiceConfiguration(tenantRoot, scope) {
|
|
@@ -257,12 +261,6 @@ function validateRailwayServiceConfiguration(tenantRoot, scope) {
|
|
|
257
261
|
if (service.schedule?.length && !service.startCommand) {
|
|
258
262
|
issues.push(`${service.key}: scheduled Railway services require railway.startCommand in treeseed.site.yaml.`);
|
|
259
263
|
}
|
|
260
|
-
if (service.schedule?.length && !service.serviceId) {
|
|
261
|
-
issues.push(`${service.key}: scheduled Railway services require railway.serviceId for Railway API reconciliation.`);
|
|
262
|
-
}
|
|
263
|
-
if (service.schedule?.length && !envValue("TREESEED_RAILWAY_ENVIRONMENT_ID")) {
|
|
264
|
-
issues.push(`${service.key}: scheduled Railway services require TREESEED_RAILWAY_ENVIRONMENT_ID to be configured.`);
|
|
265
|
-
}
|
|
266
264
|
}
|
|
267
265
|
if (issues.length > 0) {
|
|
268
266
|
throw new Error(`Railway service configuration is incomplete:
|
|
@@ -288,6 +286,16 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
|
|
|
288
286
|
const queries = defaultRailwayScheduleQueries();
|
|
289
287
|
const results = [];
|
|
290
288
|
for (const schedule of schedules) {
|
|
289
|
+
if (!schedule.serviceId || !schedule.environmentId) {
|
|
290
|
+
results.push({
|
|
291
|
+
...schedule,
|
|
292
|
+
id: null,
|
|
293
|
+
status: "skipped_missing_identifiers",
|
|
294
|
+
enabled: schedule.enabled !== false,
|
|
295
|
+
command: schedule.command
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
291
299
|
const variables = {
|
|
292
300
|
projectId: schedule.projectId,
|
|
293
301
|
serviceId: schedule.serviceId,
|
|
@@ -391,6 +399,15 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
|
|
|
391
399
|
const queries = defaultRailwayScheduleQueries();
|
|
392
400
|
const checks = [];
|
|
393
401
|
for (const schedule of configured) {
|
|
402
|
+
if (!schedule.serviceId || !schedule.environmentId) {
|
|
403
|
+
checks.push({
|
|
404
|
+
...schedule,
|
|
405
|
+
id: null,
|
|
406
|
+
ok: false,
|
|
407
|
+
status: "skipped_missing_identifiers"
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
394
411
|
const listed = await railwayGraphqlRequest({
|
|
395
412
|
query: queries.listQuery,
|
|
396
413
|
variables: {
|
|
@@ -410,7 +427,8 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
|
|
|
410
427
|
id: existing?.id ?? null,
|
|
411
428
|
ok: Boolean(
|
|
412
429
|
existing && existing.expression === schedule.expression && (existing.command ?? null) === (schedule.command ?? null) && existing.enabled !== false
|
|
413
|
-
)
|
|
430
|
+
),
|
|
431
|
+
status: existing ? "checked" : "missing"
|
|
414
432
|
});
|
|
415
433
|
}
|
|
416
434
|
return {
|
|
@@ -37,13 +37,24 @@ export declare function loadCliDeployConfig(tenantRoot: any): {
|
|
|
37
37
|
contactEmail: string;
|
|
38
38
|
hosting: {
|
|
39
39
|
kind: string;
|
|
40
|
-
registration:
|
|
41
|
-
marketBaseUrl:
|
|
42
|
-
teamId:
|
|
43
|
-
projectId:
|
|
44
|
-
}
|
|
40
|
+
registration: any;
|
|
41
|
+
marketBaseUrl: any;
|
|
42
|
+
teamId: any;
|
|
43
|
+
projectId: any;
|
|
44
|
+
};
|
|
45
|
+
hub: {
|
|
46
|
+
mode: string;
|
|
47
|
+
};
|
|
48
|
+
runtime: {
|
|
49
|
+
mode: string;
|
|
50
|
+
registration: any;
|
|
51
|
+
marketBaseUrl: any;
|
|
52
|
+
teamId: any;
|
|
53
|
+
projectId: any;
|
|
54
|
+
};
|
|
45
55
|
cloudflare: {
|
|
46
56
|
accountId: string;
|
|
57
|
+
zoneId: string | undefined;
|
|
47
58
|
workerName: string | undefined;
|
|
48
59
|
queueName: string | undefined;
|
|
49
60
|
dlqName: string | undefined;
|
|
@@ -88,9 +99,45 @@ export declare function loadCliDeployConfig(tenantRoot: any): {
|
|
|
88
99
|
runtime: string;
|
|
89
100
|
publish: string;
|
|
90
101
|
docs: string;
|
|
102
|
+
serving: any;
|
|
91
103
|
};
|
|
92
104
|
site: string;
|
|
93
105
|
};
|
|
106
|
+
surfaces: {
|
|
107
|
+
[k: string]: {
|
|
108
|
+
enabled: boolean | undefined;
|
|
109
|
+
provider: string | undefined;
|
|
110
|
+
rootDir: string | undefined;
|
|
111
|
+
publicBaseUrl: string | undefined;
|
|
112
|
+
localBaseUrl: string | undefined;
|
|
113
|
+
cache: {
|
|
114
|
+
sourcePages: {
|
|
115
|
+
browserTtlSeconds: number | undefined;
|
|
116
|
+
edgeTtlSeconds: number | undefined;
|
|
117
|
+
staleWhileRevalidateSeconds: number | undefined;
|
|
118
|
+
staleIfErrorSeconds: number | undefined;
|
|
119
|
+
} | {
|
|
120
|
+
paths: any[];
|
|
121
|
+
} | undefined;
|
|
122
|
+
contentPages: {
|
|
123
|
+
browserTtlSeconds: number | undefined;
|
|
124
|
+
edgeTtlSeconds: number | undefined;
|
|
125
|
+
staleWhileRevalidateSeconds: number | undefined;
|
|
126
|
+
staleIfErrorSeconds: number | undefined;
|
|
127
|
+
} | {
|
|
128
|
+
paths: any[];
|
|
129
|
+
} | undefined;
|
|
130
|
+
r2PublishedObjects: {
|
|
131
|
+
browserTtlSeconds: number | undefined;
|
|
132
|
+
edgeTtlSeconds: number | undefined;
|
|
133
|
+
staleWhileRevalidateSeconds: number | undefined;
|
|
134
|
+
staleIfErrorSeconds: number | undefined;
|
|
135
|
+
} | {
|
|
136
|
+
paths: any[];
|
|
137
|
+
} | undefined;
|
|
138
|
+
} | undefined;
|
|
139
|
+
} | undefined;
|
|
140
|
+
} | undefined;
|
|
94
141
|
services: {
|
|
95
142
|
[k: string]: {
|
|
96
143
|
enabled: boolean | undefined;
|