@treeseed/sdk 0.12.19 → 0.12.21
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/operations/services/deploy.d.ts +6 -2
- package/dist/operations/services/deploy.js +31 -8
- package/dist/operations/services/hosted-service-checks.d.ts +1 -0
- package/dist/operations/services/hosted-service-checks.js +9 -6
- package/dist/operations/services/live-hosted-service-checks.js +8 -6
- package/dist/operations/services/project-platform.js +23 -22
- package/dist/workflow/operations.d.ts +1 -0
- package/dist/workflow/operations.js +32 -2
- package/package.json +1 -1
|
@@ -294,7 +294,8 @@ export declare function queueName(entry: any): any;
|
|
|
294
294
|
export declare function queueId(entry: any): any;
|
|
295
295
|
export declare function hasProvisionedCloudflareResources(state: any): boolean;
|
|
296
296
|
export declare function purgeSourcePageCaches(tenantRoot: any, options?: {}): {
|
|
297
|
-
skipped:
|
|
297
|
+
skipped: boolean;
|
|
298
|
+
reason: string;
|
|
298
299
|
urls: (string | null)[];
|
|
299
300
|
results: never[];
|
|
300
301
|
} | {
|
|
@@ -305,9 +306,11 @@ export declare function purgeSourcePageCaches(tenantRoot: any, options?: {}): {
|
|
|
305
306
|
success: boolean;
|
|
306
307
|
}[];
|
|
307
308
|
skipped?: undefined;
|
|
309
|
+
reason?: undefined;
|
|
308
310
|
};
|
|
309
311
|
export declare function purgePublishedContentCaches(tenantRoot: any, urls: any, options?: {}): {
|
|
310
|
-
skipped:
|
|
312
|
+
skipped: boolean;
|
|
313
|
+
reason: string;
|
|
311
314
|
urls: any;
|
|
312
315
|
results: never[];
|
|
313
316
|
} | {
|
|
@@ -318,6 +321,7 @@ export declare function purgePublishedContentCaches(tenantRoot: any, urls: any,
|
|
|
318
321
|
success: boolean;
|
|
319
322
|
}[];
|
|
320
323
|
skipped?: undefined;
|
|
324
|
+
reason?: undefined;
|
|
321
325
|
};
|
|
322
326
|
export declare function resolveConfiguredCloudflareAccountId(deployConfig: any): any;
|
|
323
327
|
export declare function collectMissingDeployInputs(tenantRoot: any): {
|
|
@@ -1683,19 +1683,30 @@ function recordCachePurgeResult(targetState, results, error = null) {
|
|
|
1683
1683
|
targetState.purgeCount = Array.isArray(results) ? results.reduce((sum, result) => sum + (result?.count ?? 0), 0) : 0;
|
|
1684
1684
|
targetState.lastError = null;
|
|
1685
1685
|
}
|
|
1686
|
+
function resolveCloudflareCachePurgeEnv(options = {}) {
|
|
1687
|
+
const env = options.env ?? {};
|
|
1688
|
+
const token = env.TREESEED_CLOUDFLARE_API_TOKEN ?? env.CLOUDFLARE_API_TOKEN ?? process.env.TREESEED_CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN;
|
|
1689
|
+
return token ? { ...env, TREESEED_CLOUDFLARE_API_TOKEN: token, CLOUDFLARE_API_TOKEN: token } : null;
|
|
1690
|
+
}
|
|
1686
1691
|
function purgeSourcePageCaches(tenantRoot, options = {}) {
|
|
1687
1692
|
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
1688
1693
|
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
1689
1694
|
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
1690
1695
|
const urls = resolveSourcePagePurgeUrls(deployConfig);
|
|
1691
|
-
|
|
1696
|
+
const env = resolveCloudflareCachePurgeEnv(options);
|
|
1697
|
+
if ((options.dryRun ?? false) || urls.length === 0 || !env) {
|
|
1692
1698
|
recordCachePurgeResult(state.webCache.deployPurge, urls.map((url) => ({ count: url ? 1 : 0 })));
|
|
1693
1699
|
writeDeployState(tenantRoot, state, { target });
|
|
1694
|
-
return {
|
|
1700
|
+
return {
|
|
1701
|
+
skipped: true,
|
|
1702
|
+
reason: options.dryRun ? "dry_run" : urls.length === 0 ? "no_urls" : "missing_cloudflare_token",
|
|
1703
|
+
urls,
|
|
1704
|
+
results: []
|
|
1705
|
+
};
|
|
1695
1706
|
}
|
|
1696
1707
|
try {
|
|
1697
1708
|
const results = purgeCloudflareCacheByUrls(urls, deployConfig, {
|
|
1698
|
-
env
|
|
1709
|
+
env
|
|
1699
1710
|
});
|
|
1700
1711
|
recordCachePurgeResult(state.webCache.deployPurge, results);
|
|
1701
1712
|
writeDeployState(tenantRoot, state, { target });
|
|
@@ -1710,14 +1721,20 @@ function purgePublishedContentCaches(tenantRoot, urls, options = {}) {
|
|
|
1710
1721
|
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
1711
1722
|
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
1712
1723
|
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
1713
|
-
|
|
1724
|
+
const env = resolveCloudflareCachePurgeEnv(options);
|
|
1725
|
+
if ((options.dryRun ?? false) || !urls?.length || !env) {
|
|
1714
1726
|
recordCachePurgeResult(state.webCache.contentPurge, (urls ?? []).map((url) => ({ count: url ? 1 : 0 })));
|
|
1715
1727
|
writeDeployState(tenantRoot, state, { target });
|
|
1716
|
-
return {
|
|
1728
|
+
return {
|
|
1729
|
+
skipped: true,
|
|
1730
|
+
reason: options.dryRun ? "dry_run" : !urls?.length ? "no_urls" : "missing_cloudflare_token",
|
|
1731
|
+
urls: urls ?? [],
|
|
1732
|
+
results: []
|
|
1733
|
+
};
|
|
1717
1734
|
}
|
|
1718
1735
|
try {
|
|
1719
1736
|
const results = purgeCloudflareCacheByUrls(urls, deployConfig, {
|
|
1720
|
-
env
|
|
1737
|
+
env
|
|
1721
1738
|
});
|
|
1722
1739
|
recordCachePurgeResult(state.webCache.contentPurge, results);
|
|
1723
1740
|
writeDeployState(tenantRoot, state, { target });
|
|
@@ -3657,8 +3674,14 @@ function finalizeDeploymentState(tenantRoot, options = {}) {
|
|
|
3657
3674
|
writeDeployState(tenantRoot, state, { target });
|
|
3658
3675
|
if (target.kind === "persistent") {
|
|
3659
3676
|
try {
|
|
3660
|
-
purgeSourcePageCaches(tenantRoot, { target });
|
|
3661
|
-
|
|
3677
|
+
const purgeResult = purgeSourcePageCaches(tenantRoot, { target, env: options.env });
|
|
3678
|
+
if (target.scope === "prod" && purgeResult?.skipped) {
|
|
3679
|
+
throw new Error(`Production source-page cache purge was skipped: ${purgeResult.reason ?? "unknown"}.`);
|
|
3680
|
+
}
|
|
3681
|
+
} catch (error) {
|
|
3682
|
+
if (target.scope === "prod") {
|
|
3683
|
+
throw error;
|
|
3684
|
+
}
|
|
3662
3685
|
}
|
|
3663
3686
|
return loadDeployState(tenantRoot, deployConfig, { target });
|
|
3664
3687
|
}
|
|
@@ -65,6 +65,7 @@ export interface TreeseedHostedServiceCheckOptions {
|
|
|
65
65
|
appId?: string;
|
|
66
66
|
serviceKeys?: string[];
|
|
67
67
|
now?: Date;
|
|
68
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
68
69
|
valuesOverlay?: Record<string, string | undefined>;
|
|
69
70
|
observedRailwayServices?: Record<string, TreeseedObservedRailwayServiceState | undefined>;
|
|
70
71
|
httpChecks?: Record<string, {
|
|
@@ -145,7 +145,8 @@ function httpStatus(url, options) {
|
|
|
145
145
|
function collectTreeseedHostedServiceChecks(options) {
|
|
146
146
|
const target = options.target ?? "prod";
|
|
147
147
|
const tenantRoot = options.tenantRoot;
|
|
148
|
-
const
|
|
148
|
+
const configEnv = { ...process.env, ...options.env ?? {} };
|
|
149
|
+
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot, environment: target, env: configEnv }).deployConfig;
|
|
149
150
|
const registry = collectTreeseedEnvironmentContext(tenantRoot);
|
|
150
151
|
let machineValues = {};
|
|
151
152
|
try {
|
|
@@ -153,17 +154,18 @@ function collectTreeseedHostedServiceChecks(options) {
|
|
|
153
154
|
} catch {
|
|
154
155
|
machineValues = {};
|
|
155
156
|
}
|
|
156
|
-
const values = { ...machineValues, ...options.valuesOverlay ?? {} };
|
|
157
|
+
const values = { ...machineValues, ...configEnv, ...options.valuesOverlay ?? {} };
|
|
157
158
|
const checks = [];
|
|
158
159
|
const selectedServiceKeys = new Set((options.serviceKeys ?? []).map((key) => key.trim()).filter(Boolean));
|
|
159
160
|
const selectedAppId = options.appId?.trim() || null;
|
|
160
161
|
const applications = discoverTreeseedApplications(tenantRoot);
|
|
161
162
|
const selectedApplication = selectedAppId ? applications.find((application) => application.id === selectedAppId || application.relativeRoot === selectedAppId) : null;
|
|
163
|
+
const selectedService = (serviceKey) => selectedServiceKeys.size === 0 || selectedServiceKeys.has(serviceKey);
|
|
162
164
|
const workspaceHasApiApplication = applications.some(
|
|
163
165
|
(application) => application.roles.includes("api") || application.config.surfaces?.api?.enabled === true || application.config.services?.api?.enabled !== false && Boolean(application.config.services?.api)
|
|
164
166
|
);
|
|
165
|
-
const includeWeb = !selectedAppId || selectedAppId === "web" || selectedApplication?.roles.includes("web") === true;
|
|
166
|
-
const includeApi = !selectedAppId || selectedAppId === "api" || selectedApplication?.roles.includes("api") === true;
|
|
167
|
+
const includeWeb = selectedService("web") && (!selectedAppId || selectedAppId === "web" || selectedApplication?.roles.includes("web") === true);
|
|
168
|
+
const includeApi = selectedService("api") && (!selectedAppId || selectedAppId === "api" || selectedApplication?.roles.includes("api") === true);
|
|
167
169
|
const selectedAppHasApi = Boolean(
|
|
168
170
|
selectedApplication?.roles.includes("api") || selectedApplication?.config.surfaces?.api?.enabled === true || selectedApplication?.config.services?.api?.enabled !== false && selectedApplication?.config.services?.api || selectedAppId === "web" && workspaceHasApiApplication || selectedAppId === "web" && (deployConfig.surfaces?.api?.enabled === true || deployConfig.services?.api?.enabled !== false && deployConfig.services?.api)
|
|
169
171
|
);
|
|
@@ -189,12 +191,13 @@ function collectTreeseedHostedServiceChecks(options) {
|
|
|
189
191
|
if (domain) {
|
|
190
192
|
const url = String(domain).startsWith("http") ? String(domain) : `https://${domain}`;
|
|
191
193
|
checks.push({ ...httpStatus(url, options), id: `http:${selectedWeb.appId}`, serviceKey: selectedWeb.appId, serviceType: "web", description: "Web public URL responds." });
|
|
192
|
-
if (!selectedAppId || selectedAppHasApi) {
|
|
194
|
+
if (selectedServiceKeys.size === 0 && (!selectedAppId || selectedAppHasApi)) {
|
|
193
195
|
checks.push({ ...httpStatus(`${url.replace(/\/+$/u, "")}/v1/healthz`, options), id: `http:${selectedWeb.appId}:v1-healthz`, serviceKey: selectedWeb.appId, serviceType: "web", description: "Web proxy reaches API health." });
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
198
|
}
|
|
197
199
|
for (const [surfaceKey, surface] of Object.entries(deployConfig.surfaces ?? {})) {
|
|
200
|
+
if (!selectedService(surfaceKey)) continue;
|
|
198
201
|
if (selectedAppId === "api" && surfaceKey === "web") continue;
|
|
199
202
|
if (surface && typeof surface === "object" && surface.enabled !== false && surface.provider && !["cloudflare", "railway"].includes(surface.provider)) {
|
|
200
203
|
checks.push(check({
|
|
@@ -211,7 +214,7 @@ function collectTreeseedHostedServiceChecks(options) {
|
|
|
211
214
|
}));
|
|
212
215
|
}
|
|
213
216
|
}
|
|
214
|
-
const configuredServices = configuredRailwayServices(tenantRoot, target).filter((service) => !selectedAppId || service.application?.id === selectedAppId).filter((service) => selectedServiceKeys.size === 0 || selectedServiceKeys.has(service.key));
|
|
217
|
+
const configuredServices = configuredRailwayServices(tenantRoot, target, configEnv).filter((service) => !selectedAppId || service.application?.id === selectedAppId).filter((service) => selectedServiceKeys.size === 0 || selectedServiceKeys.has(service.key));
|
|
215
218
|
for (const service of configuredServices) {
|
|
216
219
|
const serviceType = serviceTypeFor(service.key);
|
|
217
220
|
const observed = observedFor(options, service.serviceName);
|
|
@@ -218,7 +218,7 @@ async function collectRailwayObservations(options) {
|
|
|
218
218
|
try {
|
|
219
219
|
const workspace = await resolveRailwayWorkspaceContext({ env: options.env, fetchImpl: options.fetchImpl });
|
|
220
220
|
const projects = await listRailwayProjects({ workspaceId: workspace.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
221
|
-
const configuredServices = configuredRailwayServices(options.tenantRoot, options.target).filter((entry) => !options.appId || entry.application?.id === options.appId).filter((entry) => serviceIsSelected(selectedServiceKeys, entry.key));
|
|
221
|
+
const configuredServices = configuredRailwayServices(options.tenantRoot, options.target, options.env).filter((entry) => !options.appId || entry.application?.id === options.appId).filter((entry) => serviceIsSelected(selectedServiceKeys, entry.key));
|
|
222
222
|
if (selectedServiceKeys.size === 0 || selectedServiceKeys.has("api") || selectedServiceKeys.has("operationsRunner")) {
|
|
223
223
|
for (const descriptor of treeseedDatabaseDescriptors(options.tenantRoot, options)) {
|
|
224
224
|
await verifyRailwayPostgresTopology({ descriptor, configuredServices, projects, options, issues });
|
|
@@ -347,7 +347,7 @@ async function collectRailwayObservations(options) {
|
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
349
|
if (selectedServiceKeys.size === 0) {
|
|
350
|
-
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot: options.tenantRoot, environment: options.target, env:
|
|
350
|
+
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot: options.tenantRoot, environment: options.target, env: options.env }).deployConfig;
|
|
351
351
|
const appConfigs = [
|
|
352
352
|
...!options.appId || options.appId === "web" ? [deployConfig] : [],
|
|
353
353
|
...discoverTreeseedApplications(options.tenantRoot).filter((application) => application.id === "api" && (!options.appId || options.appId === "api")).map((application) => application.config)
|
|
@@ -418,18 +418,19 @@ async function collectRailwayObservations(options) {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
async function collectHttpObservations(options) {
|
|
421
|
-
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot: options.tenantRoot, environment: options.target, env:
|
|
421
|
+
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot: options.tenantRoot, environment: options.target, env: options.env }).deployConfig;
|
|
422
422
|
const urls = /* @__PURE__ */ new Set();
|
|
423
423
|
const fallbacks = /* @__PURE__ */ new Map();
|
|
424
424
|
const selectedServiceKeys = selectedServiceKeySet(options);
|
|
425
425
|
const selectedApplication = options.appId ? discoverTreeseedApplications(options.tenantRoot).find((application) => application.id === options.appId || application.relativeRoot === options.appId) : null;
|
|
426
|
-
|
|
426
|
+
const webHttpSelected = selectedServiceKeys.size === 0 || selectedServiceKeys.has("web");
|
|
427
|
+
if (webHttpSelected && (!options.appId || options.appId === "web" || selectedApplication?.roles.includes("web"))) {
|
|
427
428
|
const webConfig = selectedWebConfig(deployConfig, selectedApplication);
|
|
428
429
|
const webDomain = webConfig.surfaces?.web?.environments?.[options.target]?.domain ?? webConfig.surfaces?.web?.publicBaseUrl ?? webConfig.siteUrl;
|
|
429
430
|
const webUrl = urlForDomain(webDomain);
|
|
430
431
|
if (webUrl) {
|
|
431
432
|
urls.add(webUrl);
|
|
432
|
-
if (!options.appId || options.appId === "web" || selectedApplication?.roles.includes("api")) {
|
|
433
|
+
if (selectedServiceKeys.size === 0 && (!options.appId || options.appId === "web" || selectedApplication?.roles.includes("api"))) {
|
|
433
434
|
urls.add(`${webUrl}/v1/healthz`);
|
|
434
435
|
}
|
|
435
436
|
const pagesProjectName = webConfig.cloudflare?.pages?.projectName;
|
|
@@ -440,7 +441,7 @@ async function collectHttpObservations(options) {
|
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
|
-
for (const service of configuredRailwayServices(options.tenantRoot, options.target).filter((entry) => !options.appId || entry.application?.id === options.appId).filter((entry) => serviceIsSelected(selectedServiceKeys, entry.key))) {
|
|
444
|
+
for (const service of configuredRailwayServices(options.tenantRoot, options.target, options.env).filter((entry) => !options.appId || entry.application?.id === options.appId).filter((entry) => serviceIsSelected(selectedServiceKeys, entry.key))) {
|
|
444
445
|
const serviceConfig = deployConfig.services?.[service.key];
|
|
445
446
|
const domain = service.publicBaseUrl ?? serviceConfig?.environments?.[options.target]?.baseUrl ?? serviceConfig?.environments?.[options.target]?.domain ?? (service.key === "api" ? deployConfig.surfaces?.api?.environments?.[options.target]?.domain : null);
|
|
446
447
|
const baseUrl = urlForDomain(domain);
|
|
@@ -500,6 +501,7 @@ async function collectTreeseedLiveHostedServiceChecks(options) {
|
|
|
500
501
|
target: effectiveOptions.target,
|
|
501
502
|
appId: effectiveOptions.appId,
|
|
502
503
|
serviceKeys: effectiveOptions.serviceKeys,
|
|
504
|
+
env: effectiveOptions.env,
|
|
503
505
|
observedRailwayServices: railway.observed,
|
|
504
506
|
httpChecks
|
|
505
507
|
});
|
|
@@ -196,7 +196,7 @@ function isTransientWranglerOutput(output) {
|
|
|
196
196
|
return /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|connectivity issue|internal error|aborted/i.test(output);
|
|
197
197
|
}
|
|
198
198
|
async function runPrefixedWranglerWithRetry(tenantRoot, args, {
|
|
199
|
-
env = {},
|
|
199
|
+
env: env2 = {},
|
|
200
200
|
write,
|
|
201
201
|
prefix
|
|
202
202
|
}) {
|
|
@@ -208,7 +208,7 @@ async function runPrefixedWranglerWithRetry(tenantRoot, args, {
|
|
|
208
208
|
}
|
|
209
209
|
const result = await runPrefixedCommand(wrangler.command, [...wrangler.argsPrefix, ...args], {
|
|
210
210
|
cwd: tenantRoot,
|
|
211
|
-
env,
|
|
211
|
+
env: env2,
|
|
212
212
|
write,
|
|
213
213
|
prefix
|
|
214
214
|
});
|
|
@@ -235,7 +235,7 @@ function prepareTenantCloudflareDeploy({
|
|
|
235
235
|
target: explicitTarget,
|
|
236
236
|
dryRun,
|
|
237
237
|
write,
|
|
238
|
-
env = process.env
|
|
238
|
+
env: env2 = process.env
|
|
239
239
|
}) {
|
|
240
240
|
const target = explicitTarget ?? createPersistentDeployTarget(scope === "local" ? "staging" : scope);
|
|
241
241
|
if (scope !== "local") {
|
|
@@ -260,7 +260,7 @@ function prepareTenantCloudflareDeploy({
|
|
|
260
260
|
pagesBranchName,
|
|
261
261
|
env: {
|
|
262
262
|
...process.env,
|
|
263
|
-
...
|
|
263
|
+
...env2,
|
|
264
264
|
CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig),
|
|
265
265
|
...target.kind === "persistent" && target.scope !== "local" ? { TREESEED_CONTENT_SERVING_MODE: "published_runtime" } : {}
|
|
266
266
|
},
|
|
@@ -478,12 +478,12 @@ async function repairHostingAfterSuccessfulDeploy(options, systems) {
|
|
|
478
478
|
};
|
|
479
479
|
}
|
|
480
480
|
const environment = options.scope === "prod" ? "prod" : "staging";
|
|
481
|
-
const
|
|
481
|
+
const env2 = { ...process.env, ...options.env ?? {} };
|
|
482
482
|
const audit = await runTreeseedHostingAudit({
|
|
483
483
|
tenantRoot: options.tenantRoot,
|
|
484
484
|
environment,
|
|
485
485
|
repair: false,
|
|
486
|
-
env,
|
|
486
|
+
env: env2,
|
|
487
487
|
hostKinds: [...hostKinds]
|
|
488
488
|
});
|
|
489
489
|
if (audit.ok) {
|
|
@@ -494,7 +494,7 @@ async function repairHostingAfterSuccessfulDeploy(options, systems) {
|
|
|
494
494
|
tenantRoot: options.tenantRoot,
|
|
495
495
|
environment,
|
|
496
496
|
repair: true,
|
|
497
|
-
env,
|
|
497
|
+
env: env2,
|
|
498
498
|
hostKinds: [...hostKinds],
|
|
499
499
|
write: (line) => options.write?.(`[${environment}][hosting][repair] ${line}`)
|
|
500
500
|
});
|
|
@@ -955,7 +955,7 @@ async function publishContent(options, reporter, publishOptions = {}) {
|
|
|
955
955
|
}, manifestFile, uploadOptions);
|
|
956
956
|
if (contentPurgeUrls.size > 0) {
|
|
957
957
|
try {
|
|
958
|
-
purgePublishedContentCaches(options.tenantRoot, [...contentPurgeUrls].filter(Boolean), { target });
|
|
958
|
+
purgePublishedContentCaches(options.tenantRoot, [...contentPurgeUrls].filter(Boolean), { target, env });
|
|
959
959
|
} catch {
|
|
960
960
|
}
|
|
961
961
|
}
|
|
@@ -1020,12 +1020,12 @@ async function provisionProjectPlatform(options) {
|
|
|
1020
1020
|
writeWorkflowStatus("provision:resolve-bootstrap-systems");
|
|
1021
1021
|
const bootstrapSystems = resolveProjectPlatformBootstrapSystems(options, siteConfig);
|
|
1022
1022
|
const selectedSystems = new Set(bootstrapSystems);
|
|
1023
|
-
const
|
|
1023
|
+
const env2 = { ...process.env, ...options.env ?? {} };
|
|
1024
1024
|
writeWorkflowStatus(`provision:reconcile:start systems=${bootstrapSystems.join(",") || "(none)"}`);
|
|
1025
1025
|
const summary = await timedPhase(timings, "provision:reconcile", () => reconcileTreeseedTarget({
|
|
1026
1026
|
tenantRoot: options.tenantRoot,
|
|
1027
1027
|
target,
|
|
1028
|
-
env,
|
|
1028
|
+
env: env2,
|
|
1029
1029
|
systems: bootstrapSystems,
|
|
1030
1030
|
write: options.write,
|
|
1031
1031
|
dryRun: options.dryRun
|
|
@@ -1036,7 +1036,7 @@ async function provisionProjectPlatform(options) {
|
|
|
1036
1036
|
const verification = await timedPhase(timings, "provision:collect-reconcile-status", () => collectTreeseedReconcileStatus({
|
|
1037
1037
|
tenantRoot: options.tenantRoot,
|
|
1038
1038
|
target,
|
|
1039
|
-
env,
|
|
1039
|
+
env: env2,
|
|
1040
1040
|
systems: bootstrapSystems
|
|
1041
1041
|
}));
|
|
1042
1042
|
writeWorkflowStatus("provision:collect-reconcile-status:done");
|
|
@@ -1050,7 +1050,7 @@ async function provisionProjectPlatform(options) {
|
|
|
1050
1050
|
writeWorkflowStatus("provision:ensure-wrangler-config:skipped");
|
|
1051
1051
|
}
|
|
1052
1052
|
const shouldValidateRailway = selectedSystems.has("api") || selectedSystems.has("agents");
|
|
1053
|
-
const railwayValidation = shouldValidateRailway ? options.scope === "local" ? validateRailwayServiceConfiguration(options.tenantRoot, options.scope) : validateRailwayDeployPrerequisites(options.tenantRoot, options.scope, { env }) : { services: [] };
|
|
1053
|
+
const railwayValidation = shouldValidateRailway ? options.scope === "local" ? validateRailwayServiceConfiguration(options.tenantRoot, options.scope) : validateRailwayDeployPrerequisites(options.tenantRoot, options.scope, { env: env2 }) : { services: [] };
|
|
1054
1054
|
const railwaySchedules = [];
|
|
1055
1055
|
const railwayScheduleVerification = {
|
|
1056
1056
|
ok: true,
|
|
@@ -1210,7 +1210,7 @@ async function deployProjectPlatform(options) {
|
|
|
1210
1210
|
const selectedSystems = new Set(bootstrapSystems);
|
|
1211
1211
|
const execution = options.bootstrapExecution ?? "parallel";
|
|
1212
1212
|
const write = options.write;
|
|
1213
|
-
const
|
|
1213
|
+
const env2 = { ...process.env, ...options.env ?? {} };
|
|
1214
1214
|
writeWorkflowStatus(`deploy:bootstrap-systems ${bootstrapSystems.join(",") || "(none)"}`);
|
|
1215
1215
|
writeWorkflowStatus("deploy:report-running");
|
|
1216
1216
|
await reportDeployment(reporter, {
|
|
@@ -1238,7 +1238,7 @@ async function deployProjectPlatform(options) {
|
|
|
1238
1238
|
tenantRoot: options.tenantRoot,
|
|
1239
1239
|
scope: "local",
|
|
1240
1240
|
dryRun: options.dryRun,
|
|
1241
|
-
env,
|
|
1241
|
+
env: env2,
|
|
1242
1242
|
write
|
|
1243
1243
|
})
|
|
1244
1244
|
});
|
|
@@ -1248,7 +1248,7 @@ async function deployProjectPlatform(options) {
|
|
|
1248
1248
|
scope: options.scope,
|
|
1249
1249
|
dryRun: options.dryRun,
|
|
1250
1250
|
write,
|
|
1251
|
-
env
|
|
1251
|
+
env: env2
|
|
1252
1252
|
});
|
|
1253
1253
|
}
|
|
1254
1254
|
if (cloudflareContext && selectedSystems.has("data")) {
|
|
@@ -1283,11 +1283,11 @@ async function deployProjectPlatform(options) {
|
|
|
1283
1283
|
const serviceResultsByKey = /* @__PURE__ */ new Map();
|
|
1284
1284
|
let selectedRailwayServiceKeys = [];
|
|
1285
1285
|
if (options.scope !== "local" && (selectedSystems.has("api") || selectedSystems.has("agents"))) {
|
|
1286
|
-
const validation = validateRailwayDeployPrerequisites(options.tenantRoot, options.scope, { env });
|
|
1286
|
+
const validation = validateRailwayDeployPrerequisites(options.tenantRoot, options.scope, { env: env2 });
|
|
1287
1287
|
const selectedServices = validation.services.filter(
|
|
1288
1288
|
(service) => service.key === "api" ? selectedSystems.has("api") : selectedSystems.has("agents")
|
|
1289
1289
|
);
|
|
1290
|
-
const sequentialRailwayDeploys = String(
|
|
1290
|
+
const sequentialRailwayDeploys = String(env2.TREESEED_RAILWAY_DEPLOY_SEQUENTIAL ?? "").trim() === "1";
|
|
1291
1291
|
let previousRailwayDeployNodeId = null;
|
|
1292
1292
|
for (const service of selectedServices) {
|
|
1293
1293
|
const system = service.key === "api" ? "api" : "agents";
|
|
@@ -1304,7 +1304,7 @@ async function deployProjectPlatform(options) {
|
|
|
1304
1304
|
const result = await deployRailwayService(options.tenantRoot, service, {
|
|
1305
1305
|
dryRun: options.dryRun,
|
|
1306
1306
|
write,
|
|
1307
|
-
env,
|
|
1307
|
+
env: env2,
|
|
1308
1308
|
prefix: {
|
|
1309
1309
|
scope: options.scope,
|
|
1310
1310
|
system,
|
|
@@ -1336,7 +1336,7 @@ async function deployProjectPlatform(options) {
|
|
|
1336
1336
|
task: "schedules",
|
|
1337
1337
|
stage: "deploy"
|
|
1338
1338
|
}, "Reconciling Railway schedules...");
|
|
1339
|
-
railwaySchedules = await ensureRailwayScheduledJobs(options.tenantRoot, options.scope, { dryRun: options.dryRun, env });
|
|
1339
|
+
railwaySchedules = await ensureRailwayScheduledJobs(options.tenantRoot, options.scope, { dryRun: options.dryRun, env: env2 });
|
|
1340
1340
|
railwayScheduleVerification = !options.dryRun ? await verifyRailwayScheduledJobs(options.tenantRoot, options.scope) : { ok: true, checks: railwaySchedules, skipped: true, reason: "dry_run" };
|
|
1341
1341
|
return {
|
|
1342
1342
|
service: "railway-schedules",
|
|
@@ -1358,7 +1358,8 @@ async function deployProjectPlatform(options) {
|
|
|
1358
1358
|
if (options.scope !== "local" && !options.dryRun && (selectedSystems.has("web") || serviceResults.length > 0)) {
|
|
1359
1359
|
finalizeDeploymentState(options.tenantRoot, {
|
|
1360
1360
|
target: createPersistentDeployTarget(options.scope),
|
|
1361
|
-
serviceResults
|
|
1361
|
+
serviceResults,
|
|
1362
|
+
env: env2
|
|
1362
1363
|
});
|
|
1363
1364
|
}
|
|
1364
1365
|
if (!managesRailwaySchedules || !selectedSystems.has("agents")) {
|
|
@@ -1423,7 +1424,7 @@ async function publishProjectContent(options) {
|
|
|
1423
1424
|
async function monitorProjectPlatform(options) {
|
|
1424
1425
|
const timings = [];
|
|
1425
1426
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
1426
|
-
const
|
|
1427
|
+
const env2 = { ...process.env, ...options.env ?? {} };
|
|
1427
1428
|
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
1428
1429
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
1429
1430
|
const selectedSystems = new Set(resolveProjectPlatformBootstrapSystems(options, siteConfig));
|
|
@@ -1434,7 +1435,7 @@ async function monitorProjectPlatform(options) {
|
|
|
1434
1435
|
const apiBaseUrl = resolveImmediateApiProbeUrl(siteConfig, state, target);
|
|
1435
1436
|
const apiMonitorEndpoints = resolveApiMonitorEndpoints(siteConfig, apiBaseUrl);
|
|
1436
1437
|
const railwayResourcesPromise = options.scope === "local" || !apiSelected && !agentsSelected ? { ok: true, skipped: true, reason: options.scope === "local" ? "local_scope" : "railway_not_selected" } : timedPhase(timings, "monitor:railway-resources", () => verifyRailwayManagedResources(options.tenantRoot, options.scope, {
|
|
1437
|
-
env,
|
|
1438
|
+
env: env2,
|
|
1438
1439
|
settleDeployments: true,
|
|
1439
1440
|
onProgress: options.write
|
|
1440
1441
|
}));
|
|
@@ -1167,6 +1167,7 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1167
1167
|
releaseBlockingFailures: number;
|
|
1168
1168
|
};
|
|
1169
1169
|
};
|
|
1170
|
+
productionWebVerification: Record<string, unknown> | null;
|
|
1170
1171
|
backMerge: {
|
|
1171
1172
|
packages: {
|
|
1172
1173
|
status: string;
|
|
@@ -534,7 +534,7 @@ function selectorFromWorkflowHostingGraph(graph) {
|
|
|
534
534
|
}))]
|
|
535
535
|
};
|
|
536
536
|
}
|
|
537
|
-
async function reconcileSaveHostedEnvironment(root, environment, helpers, workflowRunId, operation = "save", envOverlay = {}) {
|
|
537
|
+
async function reconcileSaveHostedEnvironment(root, environment, helpers, workflowRunId, operation = "save", envOverlay = {}, options = {}) {
|
|
538
538
|
const target = createPersistentDeployTarget(environment);
|
|
539
539
|
const env = {
|
|
540
540
|
...helpers.context.env,
|
|
@@ -585,6 +585,7 @@ ${status.blockers.join("\n")}`, {
|
|
|
585
585
|
const live = await collectTreeseedLiveHostedServiceChecks({
|
|
586
586
|
tenantRoot: root,
|
|
587
587
|
target: environment,
|
|
588
|
+
appId: options.liveAppId,
|
|
588
589
|
strict: true,
|
|
589
590
|
requireLiveRailway: true,
|
|
590
591
|
requireLiveHttp: true,
|
|
@@ -616,6 +617,33 @@ ${liveFailures.join("\n")}`, {
|
|
|
616
617
|
liveVerification: live
|
|
617
618
|
};
|
|
618
619
|
}
|
|
620
|
+
async function runReleaseWebLiveVerification(root, environment, helpers, operation) {
|
|
621
|
+
const env = {
|
|
622
|
+
...helpers.context.env,
|
|
623
|
+
...collectTreeseedConfigSeedValues(root, environment, helpers.context.env)
|
|
624
|
+
};
|
|
625
|
+
const live = await collectTreeseedLiveHostedServiceChecks({
|
|
626
|
+
tenantRoot: root,
|
|
627
|
+
target: environment,
|
|
628
|
+
appId: "web",
|
|
629
|
+
serviceKeys: ["web"],
|
|
630
|
+
strict: true,
|
|
631
|
+
requireLiveRailway: false,
|
|
632
|
+
requireLiveHttp: true,
|
|
633
|
+
env
|
|
634
|
+
});
|
|
635
|
+
const liveFailures = [
|
|
636
|
+
...live.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.issues.join("; ") || "failed"}`),
|
|
637
|
+
...live.liveObservation.issues
|
|
638
|
+
];
|
|
639
|
+
if (liveFailures.length > 0) {
|
|
640
|
+
workflowError(operation, "hosted_live_verification_failed", `Production web live verification failed after root deployment:
|
|
641
|
+
${liveFailures.join("\n")}`, {
|
|
642
|
+
details: { environment, live }
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return live;
|
|
646
|
+
}
|
|
619
647
|
function productionReleaseImageRefEnv(selectedVersions) {
|
|
620
648
|
const refs = {};
|
|
621
649
|
const apiVersion = selectedVersions.get("@treeseed/api");
|
|
@@ -5611,7 +5639,7 @@ ${rendered}`);
|
|
|
5611
5639
|
packageReleases.push(packageRelease);
|
|
5612
5640
|
}
|
|
5613
5641
|
const publishedArtifacts = await executeJournalStep(root, workflowRun.runId, "verify-published-artifacts", () => verifyPublishedReleaseArtifacts(selectedVersions));
|
|
5614
|
-
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release", productionReleaseImageRefEnv(selectedVersions)));
|
|
5642
|
+
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release", productionReleaseImageRefEnv(selectedVersions), { liveAppId: "api" }));
|
|
5615
5643
|
const productionApiGuarantees = await executeJournalStep(root, workflowRun.runId, "production-api-guarantees", () => runReleaseApiGuarantees(root, "prod", helpers, "release", normalizeSceneArtifactsMode(effectiveInput.sceneArtifacts)));
|
|
5616
5644
|
const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
5617
5645
|
const rootInstall = runReleaseNpmInstall(root, { workspaceRoot: root });
|
|
@@ -5663,6 +5691,7 @@ ${rendered}`);
|
|
|
5663
5691
|
runId: workflowRun.runId,
|
|
5664
5692
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5665
5693
|
}).then((workflowGates) => ({ workflowGates })));
|
|
5694
|
+
const productionWebVerification = await executeJournalStep(root, workflowRun.runId, "production-web-live-verification", () => runReleaseWebLiveVerification(root, "prod", helpers, "release"));
|
|
5666
5695
|
const backMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => {
|
|
5667
5696
|
const packageBackMerges = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => backMergeProductionIntoStaging(pkg.dir, pkg.name, releaseAdminMessage({
|
|
5668
5697
|
subject: `release: back-merge ${PRODUCTION_BRANCH} into ${STAGING_BRANCH}`,
|
|
@@ -5691,6 +5720,7 @@ ${rendered}`);
|
|
|
5691
5720
|
publishedArtifacts,
|
|
5692
5721
|
productionHosting,
|
|
5693
5722
|
productionApiGuarantees,
|
|
5723
|
+
productionWebVerification,
|
|
5694
5724
|
backMerge,
|
|
5695
5725
|
workspaceLinks,
|
|
5696
5726
|
releasedCommit: String(rootRelease.commit.commitSha ?? ""),
|