@treeseed/sdk 0.8.3 → 0.8.4
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/capacity.d.ts +33 -0
- package/dist/fixture-support.d.ts +1 -1
- package/dist/fixture-support.js +5 -5
- package/dist/managed-dependencies.js +132 -10
- package/dist/operations/services/bootstrap-runner.js +7 -1
- package/dist/operations/services/config-runtime.js +13 -4
- package/dist/operations/services/github-actions-verification.d.ts +3 -0
- package/dist/operations/services/github-actions-verification.js +3 -0
- package/dist/operations/services/github-api.d.ts +4 -1
- package/dist/operations/services/github-api.js +26 -8
- package/dist/operations/services/github-automation.d.ts +14 -5
- package/dist/operations/services/github-automation.js +45 -11
- package/dist/operations/services/hub-provider-launch.js +9 -8
- package/dist/operations/services/project-platform.d.ts +93 -210
- package/dist/operations/services/project-platform.js +74 -34
- package/dist/operations/services/railway-deploy.d.ts +25 -2
- package/dist/operations/services/railway-deploy.js +312 -20
- package/dist/operations/services/repository-save-orchestrator.d.ts +8 -0
- package/dist/operations/services/repository-save-orchestrator.js +40 -3
- package/dist/operations/services/runtime-paths.d.ts +1 -0
- package/dist/operations/services/runtime-paths.js +3 -1
- package/dist/operations/services/runtime-tools.d.ts +1 -0
- package/dist/operations/services/runtime-tools.js +2 -0
- package/dist/operations/services/template-registry.js +3 -0
- package/dist/platform/contracts.d.ts +9 -0
- package/dist/platform/deploy-config.js +28 -0
- package/dist/platform/env.yaml +1 -745
- package/dist/platform/environment.js +69 -9
- package/dist/reconcile/builtin-adapters.js +7 -2
- package/dist/scripts/install-managed-dependencies.js +12 -0
- package/dist/scripts/tenant-workflow-action.js +11 -9
- package/dist/scripts/test-scaffold.js +3 -1
- package/dist/scripts/workflow-commands.test.js +10 -6
- package/dist/scripts/workspace-command-e2e.js +1 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +1 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +1 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +7 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +6 -0
- package/dist/workflow/operations.d.ts +41 -8
- package/dist/workflow/operations.js +119 -24
- package/dist/workflow/runs.js +31 -0
- package/package.json +1 -1
- package/templates/github/deploy-processing.workflow.yml +115 -0
- package/templates/github/deploy-web.workflow.yml +111 -0
- package/templates/github/hosted-project.workflow.yml +4 -4
- package/templates/github/deploy.managed.workflow.yml +0 -208
- package/templates/github/deploy.workflow.yml +0 -746
|
@@ -44,6 +44,8 @@ import { CloudflareQueuePullClient, CloudflareQueuePushClient } from "../../remo
|
|
|
44
44
|
import { runPrefixedCommand, runTreeseedBootstrapDag, sleep, writeTreeseedBootstrapLine } from "./bootstrap-runner.js";
|
|
45
45
|
import { runTenantDeployPreflight } from "./save-deploy-preflight.js";
|
|
46
46
|
const PROJECT_PLATFORM_BOOTSTRAP_SYSTEMS = ["data", "web", "api", "agents"];
|
|
47
|
+
const WEB_PLATFORM_BOOTSTRAP_SYSTEMS = ["data", "web"];
|
|
48
|
+
const PROCESSING_PLATFORM_BOOTSTRAP_SYSTEMS = ["api", "agents"];
|
|
47
49
|
function stableHash(value) {
|
|
48
50
|
return createHash("sha256").update(value).digest("hex");
|
|
49
51
|
}
|
|
@@ -448,24 +450,39 @@ function stableArtifactAliasKey(teamId, artifact) {
|
|
|
448
450
|
const fileName = typeof artifact.metadata?.fileName === "string" && artifact.metadata.fileName ? artifact.metadata.fileName : `${artifact.itemId}${extname(artifact.content.objectKey) || ".bin"}`;
|
|
449
451
|
return `teams/${teamId}/published/artifacts/${artifact.kind}/${artifact.itemId}/${fileName}`;
|
|
450
452
|
}
|
|
451
|
-
async function probeHttp(url) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
453
|
+
async function probeHttp(url, { attempts = 1, delayMs = 2e3 } = {}) {
|
|
454
|
+
let result = {
|
|
455
|
+
ok: false,
|
|
456
|
+
status: null,
|
|
457
|
+
url
|
|
458
|
+
};
|
|
459
|
+
const maxAttempts = Math.max(1, Math.floor(attempts));
|
|
460
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
461
|
+
try {
|
|
462
|
+
const response = await fetch(url, {
|
|
463
|
+
headers: { accept: "application/json,text/html;q=0.9,*/*;q=0.8" }
|
|
464
|
+
});
|
|
465
|
+
result = {
|
|
466
|
+
ok: response.ok,
|
|
467
|
+
status: response.status,
|
|
468
|
+
url,
|
|
469
|
+
attempts: attempt
|
|
470
|
+
};
|
|
471
|
+
} catch (error) {
|
|
472
|
+
result = {
|
|
473
|
+
ok: false,
|
|
474
|
+
status: null,
|
|
475
|
+
url,
|
|
476
|
+
error: error instanceof Error ? error.message : String(error),
|
|
477
|
+
attempts: attempt
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (result.ok || attempt >= maxAttempts) {
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
await sleep(delayMs);
|
|
468
484
|
}
|
|
485
|
+
return result;
|
|
469
486
|
}
|
|
470
487
|
function queueClientConfig(siteConfig, state) {
|
|
471
488
|
const accountId = String(process.env.CLOUDFLARE_ACCOUNT_ID ?? siteConfig.cloudflare.accountId ?? "").trim();
|
|
@@ -1219,29 +1236,29 @@ async function monitorProjectPlatform(options) {
|
|
|
1219
1236
|
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
1220
1237
|
const webProbeUrl = resolveImmediatePagesProbeUrl(siteConfig, state, target);
|
|
1221
1238
|
const apiBaseUrl = resolveImmediateApiProbeUrl(siteConfig, state, target);
|
|
1239
|
+
const railwayResources = options.scope === "local" || !apiSelected && !agentsSelected ? { ok: true, skipped: true, reason: options.scope === "local" ? "local_scope" : "railway_not_selected" } : await verifyRailwayManagedResources(options.tenantRoot, options.scope, {
|
|
1240
|
+
env,
|
|
1241
|
+
settleDeployments: true,
|
|
1242
|
+
onProgress: options.write
|
|
1243
|
+
});
|
|
1222
1244
|
const skippedApiCheck = apiSelected ? { ok: false, skipped: true, reason: "api_url_unconfigured" } : { ok: true, skipped: true, reason: "api_not_selected" };
|
|
1223
1245
|
const skippedAgentCheck = agentsSelected ? { ok: false, skipped: true, reason: "api_url_unconfigured" } : { ok: true, skipped: true, reason: "agents_not_selected" };
|
|
1224
1246
|
const checks = {
|
|
1225
|
-
pages: await probeHttp(webProbeUrl),
|
|
1226
|
-
apiHealth: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz
|
|
1227
|
-
apiReady: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/readyz
|
|
1228
|
-
d1Health: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz/deep
|
|
1229
|
-
agentHealth: agentsSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/internal/core/agent/healthz
|
|
1247
|
+
pages: await probeHttp(webProbeUrl, { attempts: 3, delayMs: 5e3 }),
|
|
1248
|
+
apiHealth: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz`, { attempts: 8, delayMs: 1e4 }) : skippedApiCheck,
|
|
1249
|
+
apiReady: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/readyz`, { attempts: 8, delayMs: 1e4 }) : skippedApiCheck,
|
|
1250
|
+
d1Health: apiSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/healthz/deep`, { attempts: 8, delayMs: 1e4 }) : skippedApiCheck,
|
|
1251
|
+
agentHealth: agentsSelected && apiBaseUrl ? await probeHttp(`${String(apiBaseUrl).replace(/\/+$/u, "")}/internal/core/agent/healthz`, { attempts: 8, delayMs: 1e4 }) : skippedAgentCheck,
|
|
1230
1252
|
r2: options.dryRun ? { ok: true, skipped: true, reason: "dry_run" } : probeR2(options.tenantRoot, siteConfig, state, target),
|
|
1231
1253
|
queue: options.dryRun ? Promise.resolve({ ok: true, skipped: true, reason: "dry_run" }) : probeQueue(siteConfig, state),
|
|
1232
1254
|
scaleProbe: probeScaleConfiguration(siteConfig, state),
|
|
1233
|
-
railwayResources
|
|
1234
|
-
env,
|
|
1235
|
-
settleDeployments: true,
|
|
1236
|
-
onProgress: options.write
|
|
1237
|
-
}),
|
|
1255
|
+
railwayResources,
|
|
1238
1256
|
readiness: state.readiness
|
|
1239
1257
|
};
|
|
1240
1258
|
const resolvedChecks = {
|
|
1241
1259
|
...checks,
|
|
1242
1260
|
r2: await checks.r2,
|
|
1243
|
-
queue: await checks.queue
|
|
1244
|
-
railwayResources: await checks.railwayResources
|
|
1261
|
+
queue: await checks.queue
|
|
1245
1262
|
};
|
|
1246
1263
|
const ok = [
|
|
1247
1264
|
resolvedChecks.pages,
|
|
@@ -1299,14 +1316,26 @@ async function syncControlPlaneState(options) {
|
|
|
1299
1316
|
});
|
|
1300
1317
|
}
|
|
1301
1318
|
async function runProjectPlatformAction(action, options) {
|
|
1319
|
+
const previousWorkflowAction = process.env.TREESEED_WORKFLOW_ACTION;
|
|
1320
|
+
const previousWorkflowPlane = process.env.TREESEED_WORKFLOW_PLANE;
|
|
1321
|
+
process.env.TREESEED_WORKFLOW_ACTION = action;
|
|
1322
|
+
process.env.TREESEED_WORKFLOW_PLANE = previousWorkflowPlane ?? (action === "deploy_processing" ? "processing" : "web");
|
|
1302
1323
|
applyTreeseedEnvironmentToProcess({ tenantRoot: options.tenantRoot, scope: options.scope, override: true });
|
|
1303
1324
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
1304
1325
|
try {
|
|
1305
1326
|
switch (action) {
|
|
1306
|
-
case "
|
|
1307
|
-
return await
|
|
1308
|
-
|
|
1309
|
-
|
|
1327
|
+
case "deploy_web":
|
|
1328
|
+
return await deployProjectPlatform({
|
|
1329
|
+
...options,
|
|
1330
|
+
reporter,
|
|
1331
|
+
bootstrapSystems: options.bootstrapSystems ?? WEB_PLATFORM_BOOTSTRAP_SYSTEMS
|
|
1332
|
+
});
|
|
1333
|
+
case "deploy_processing":
|
|
1334
|
+
return await deployProjectPlatform({
|
|
1335
|
+
...options,
|
|
1336
|
+
reporter,
|
|
1337
|
+
bootstrapSystems: options.bootstrapSystems ?? PROCESSING_PLATFORM_BOOTSTRAP_SYSTEMS
|
|
1338
|
+
});
|
|
1310
1339
|
case "publish_content":
|
|
1311
1340
|
return await publishProjectContent({ ...options, reporter });
|
|
1312
1341
|
case "monitor":
|
|
@@ -1317,7 +1346,7 @@ async function runProjectPlatformAction(action, options) {
|
|
|
1317
1346
|
} catch (error) {
|
|
1318
1347
|
await reportDeployment(reporter, {
|
|
1319
1348
|
environment: options.scope,
|
|
1320
|
-
deploymentKind: action === "
|
|
1349
|
+
deploymentKind: action === "publish_content" ? "content" : action === "deploy_web" || action === "deploy_processing" ? "code" : "mixed",
|
|
1321
1350
|
status: "failed",
|
|
1322
1351
|
sourceRef: currentRef(options.tenantRoot),
|
|
1323
1352
|
commitSha: currentCommit(options.tenantRoot),
|
|
@@ -1328,6 +1357,17 @@ async function runProjectPlatformAction(action, options) {
|
|
|
1328
1357
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1329
1358
|
}).catch(() => void 0);
|
|
1330
1359
|
throw error;
|
|
1360
|
+
} finally {
|
|
1361
|
+
if (previousWorkflowAction === void 0) {
|
|
1362
|
+
delete process.env.TREESEED_WORKFLOW_ACTION;
|
|
1363
|
+
} else {
|
|
1364
|
+
process.env.TREESEED_WORKFLOW_ACTION = previousWorkflowAction;
|
|
1365
|
+
}
|
|
1366
|
+
if (previousWorkflowPlane === void 0) {
|
|
1367
|
+
delete process.env.TREESEED_WORKFLOW_PLANE;
|
|
1368
|
+
} else {
|
|
1369
|
+
process.env.TREESEED_WORKFLOW_PLANE = previousWorkflowPlane;
|
|
1370
|
+
}
|
|
1331
1371
|
}
|
|
1332
1372
|
}
|
|
1333
1373
|
export {
|
|
@@ -8,6 +8,9 @@ export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string
|
|
|
8
8
|
export declare function buildRailwayCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
9
9
|
[key: string]: string | undefined;
|
|
10
10
|
};
|
|
11
|
+
export declare function buildRailwayDeployCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
12
|
+
[key: string]: string | undefined;
|
|
13
|
+
};
|
|
11
14
|
export declare function isRailwayTransientFailure(result: any): boolean;
|
|
12
15
|
export declare function runRailway(args: any, { cwd, capture, allowFailure, input, env }?: {
|
|
13
16
|
capture?: boolean | undefined;
|
|
@@ -329,11 +332,31 @@ export declare function verifyRailwayManagedResources(tenantRoot: any, scope: an
|
|
|
329
332
|
ok: boolean;
|
|
330
333
|
checks: any[];
|
|
331
334
|
}>;
|
|
332
|
-
export declare function
|
|
335
|
+
export declare function shouldRunRailwayPredeployBuild(env?: NodeJS.ProcessEnv): boolean;
|
|
336
|
+
export declare function planRailwayServiceDeploy(service: any, { env, projectTokenMode }?: {
|
|
337
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
338
|
+
projectTokenMode?: boolean | undefined;
|
|
339
|
+
}): {
|
|
340
|
+
command: string;
|
|
341
|
+
args: string[];
|
|
342
|
+
cwd: any;
|
|
343
|
+
};
|
|
344
|
+
export declare function buildRailwayLinkCommandEnv(env?: NodeJS.ProcessEnv, service?: {}): any;
|
|
345
|
+
export declare function writeRailwayCliProjectConfig(service: any, { env, cwd }?: {
|
|
346
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
347
|
+
cwd?: any;
|
|
348
|
+
}): {
|
|
349
|
+
configPath: string;
|
|
350
|
+
projectPath: string;
|
|
351
|
+
projectId: string;
|
|
352
|
+
environmentId: string;
|
|
353
|
+
serviceId: string;
|
|
354
|
+
} | null;
|
|
355
|
+
export declare function planRailwayServiceLink(service: any, { env }?: {
|
|
333
356
|
env?: NodeJS.ProcessEnv | undefined;
|
|
334
357
|
}): {
|
|
335
358
|
command: string;
|
|
336
|
-
args:
|
|
359
|
+
args: string[];
|
|
337
360
|
cwd: any;
|
|
338
361
|
};
|
|
339
362
|
export declare function deployRailwayService(tenantRoot: any, service: any, { dryRun, write, prefix, env, }?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { relative, resolve } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, relative, resolve } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { loadCliDeployConfig } from "./runtime-tools.js";
|
|
5
5
|
import { createPersistentDeployTarget, resolveTreeseedResourceIdentity } from "./deploy.js";
|
|
@@ -221,10 +221,19 @@ function resolveRailwayAuthToken(env = process.env) {
|
|
|
221
221
|
function buildRailwayCommandEnv(env = process.env) {
|
|
222
222
|
const merged = { ...env };
|
|
223
223
|
const token = resolveRailwayAuthToken(merged);
|
|
224
|
+
const projectToken = configuredEnvValue(merged, "RAILWAY_TOKEN");
|
|
224
225
|
if (token) {
|
|
225
226
|
merged.RAILWAY_API_TOKEN = token;
|
|
226
227
|
} else {
|
|
227
|
-
|
|
228
|
+
merged.RAILWAY_API_TOKEN = void 0;
|
|
229
|
+
}
|
|
230
|
+
merged.RAILWAY_TOKEN = projectToken || void 0;
|
|
231
|
+
return merged;
|
|
232
|
+
}
|
|
233
|
+
function buildRailwayDeployCommandEnv(env = process.env) {
|
|
234
|
+
const merged = buildRailwayCommandEnv(env);
|
|
235
|
+
if (shouldAttachRailwayDeployLogs(merged)) {
|
|
236
|
+
merged.CI = "true";
|
|
228
237
|
}
|
|
229
238
|
return merged;
|
|
230
239
|
}
|
|
@@ -306,7 +315,11 @@ function isRailwayAlreadyExistsMessage(result) {
|
|
|
306
315
|
return /already exists|already taken|duplicate|has already been taken/iu.test(railwayMessage(result));
|
|
307
316
|
}
|
|
308
317
|
function isRailwayTransientFailure(result) {
|
|
309
|
-
|
|
318
|
+
const message = railwayMessage(result);
|
|
319
|
+
if (!message.trim() && result?.status === 1) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
return /timed out|failed to fetch|temporarily unavailable|econnreset|etimedout|failed to stream build logs|failed to retrieve build log/iu.test(message);
|
|
310
323
|
}
|
|
311
324
|
function sleepSync(milliseconds) {
|
|
312
325
|
if (!Number.isFinite(milliseconds) || milliseconds <= 0) {
|
|
@@ -1165,28 +1178,249 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1165
1178
|
};
|
|
1166
1179
|
}
|
|
1167
1180
|
function shouldAttachRailwayDeployLogs(env = process.env) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1181
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_ATTACH_LOGS");
|
|
1182
|
+
if (configured === "1" || configured === "true") {
|
|
1183
|
+
return true;
|
|
1184
|
+
}
|
|
1185
|
+
if (configured === "0" || configured === "false") {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
function shouldUseVerboseRailwayDeploy(env = process.env) {
|
|
1191
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_VERBOSE");
|
|
1192
|
+
if (configured === "1" || configured === "true") {
|
|
1193
|
+
return true;
|
|
1194
|
+
}
|
|
1195
|
+
if (configured === "0" || configured === "false") {
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
return shouldAttachRailwayDeployLogs(env);
|
|
1199
|
+
}
|
|
1200
|
+
function shouldIncludeRailwayIgnoredFiles(env = process.env) {
|
|
1201
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_INCLUDE_IGNORED");
|
|
1202
|
+
return configured === "1" || configured === "true";
|
|
1203
|
+
}
|
|
1204
|
+
function shouldRunRailwayPredeployBuild(env = process.env) {
|
|
1205
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_PREDEPLOY_BUILD");
|
|
1206
|
+
if (configured === "1" || configured === "true") {
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
if (configured === "0" || configured === "false") {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
return configuredEnvValue(env, "CI") !== "true";
|
|
1213
|
+
}
|
|
1214
|
+
function planRailwayServiceDeploy(service, { env = process.env, projectTokenMode = false } = {}) {
|
|
1215
|
+
const serviceSelector = service.serviceName ?? service.serviceId;
|
|
1216
|
+
const args = ["up"];
|
|
1217
|
+
if (serviceSelector) {
|
|
1218
|
+
args.push("--service", serviceSelector);
|
|
1219
|
+
}
|
|
1220
|
+
if (shouldIncludeRailwayIgnoredFiles(env)) {
|
|
1221
|
+
args.push("--no-gitignore");
|
|
1222
|
+
}
|
|
1223
|
+
args.push(shouldAttachRailwayDeployLogs(env) ? "--ci" : "--detach");
|
|
1224
|
+
if (shouldUseVerboseRailwayDeploy(env)) {
|
|
1225
|
+
args.push("--verbose");
|
|
1226
|
+
}
|
|
1227
|
+
if (!projectTokenMode && service.projectId) {
|
|
1228
|
+
args.push("--project", service.projectId);
|
|
1229
|
+
}
|
|
1230
|
+
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1231
|
+
if (!projectTokenMode && environmentName) {
|
|
1232
|
+
args.push("--environment", environmentName);
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
command: "railway",
|
|
1236
|
+
args,
|
|
1237
|
+
cwd: service.rootDir
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function planRailwayProjectEnvironmentLink(service) {
|
|
1241
|
+
const args = ["link"];
|
|
1242
|
+
if (service.projectId) {
|
|
1243
|
+
args.push("--project", service.projectId);
|
|
1244
|
+
}
|
|
1245
|
+
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1246
|
+
if (environmentName) {
|
|
1247
|
+
args.push("--environment", environmentName);
|
|
1248
|
+
}
|
|
1249
|
+
args.push("--json");
|
|
1250
|
+
return {
|
|
1251
|
+
command: "railway",
|
|
1252
|
+
args,
|
|
1253
|
+
cwd: service.rootDir
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
function buildRailwayCliContextEnv(env, service) {
|
|
1257
|
+
return {
|
|
1258
|
+
...env,
|
|
1259
|
+
RAILWAY_PROJECT_ID: configuredEnvValue(service, "projectId") || configuredEnvValue(env, "RAILWAY_PROJECT_ID") || void 0,
|
|
1260
|
+
RAILWAY_ENVIRONMENT_ID: configuredEnvValue(service, "environmentId") || configuredEnvValue(env, "RAILWAY_ENVIRONMENT_ID") || void 0,
|
|
1261
|
+
RAILWAY_SERVICE_ID: configuredEnvValue(service, "serviceId") || configuredEnvValue(env, "RAILWAY_SERVICE_ID") || void 0
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function buildRailwayLinkCommandEnv(env = process.env, service = {}) {
|
|
1265
|
+
return buildRailwayCliContextEnv({
|
|
1266
|
+
...buildRailwayDeployCommandEnv({ ...env, RAILWAY_TOKEN: void 0 }),
|
|
1267
|
+
RAILWAY_TOKEN: void 0
|
|
1268
|
+
}, service);
|
|
1269
|
+
}
|
|
1270
|
+
function railwayCliConfigPath(env = process.env) {
|
|
1271
|
+
const railwayHome = configuredEnvValue(env, "RAILWAY_HOME");
|
|
1272
|
+
if (railwayHome) {
|
|
1273
|
+
return resolve(railwayHome, "config.json");
|
|
1274
|
+
}
|
|
1275
|
+
const home = configuredEnvValue(env, "HOME");
|
|
1276
|
+
if (!home) {
|
|
1277
|
+
return "";
|
|
1278
|
+
}
|
|
1279
|
+
return resolve(home, ".railway", "config.json");
|
|
1280
|
+
}
|
|
1281
|
+
function readRailwayCliConfig(configPath) {
|
|
1282
|
+
if (!configPath || !existsSync(configPath)) {
|
|
1283
|
+
return {};
|
|
1284
|
+
}
|
|
1285
|
+
try {
|
|
1286
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1287
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1288
|
+
} catch {
|
|
1289
|
+
return {};
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
function writeRailwayCliProjectConfig(service, { env = process.env, cwd = service.rootDir } = {}) {
|
|
1293
|
+
const projectId = configuredEnvValue(service, "projectId");
|
|
1294
|
+
const environmentId = configuredEnvValue(service, "environmentId");
|
|
1295
|
+
const serviceId = configuredEnvValue(service, "serviceId");
|
|
1296
|
+
const projectPath = cwd ? resolve(cwd) : "";
|
|
1297
|
+
const configPath = railwayCliConfigPath(env);
|
|
1298
|
+
if (!configPath || !projectPath || !projectId || !environmentId || !serviceId) {
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
const existing = readRailwayCliConfig(configPath);
|
|
1302
|
+
const projects = existing.projects && typeof existing.projects === "object" ? existing.projects : {};
|
|
1303
|
+
const next = {
|
|
1304
|
+
...existing,
|
|
1305
|
+
projects: {
|
|
1306
|
+
...projects,
|
|
1307
|
+
[projectPath]: {
|
|
1308
|
+
projectPath,
|
|
1309
|
+
name: configuredEnvValue(service, "projectName") || configuredEnvValue(service, "serviceName") || projectId,
|
|
1310
|
+
project: projectId,
|
|
1311
|
+
environment: environmentId,
|
|
1312
|
+
environmentName: normalizeRailwayEnvironmentName(service.railwayEnvironment) || configuredEnvValue(service, "environmentName") || environmentId,
|
|
1313
|
+
service: serviceId
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
user: existing.user && typeof existing.user === "object" ? existing.user : {
|
|
1317
|
+
token: null,
|
|
1318
|
+
accessToken: null,
|
|
1319
|
+
refreshToken: null,
|
|
1320
|
+
tokenExpiresAt: null
|
|
1321
|
+
},
|
|
1322
|
+
linkedFunctions: existing.linkedFunctions ?? null
|
|
1323
|
+
};
|
|
1324
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
1325
|
+
writeFileSync(configPath, `${JSON.stringify(next, null, 2)}
|
|
1326
|
+
`, "utf8");
|
|
1327
|
+
return {
|
|
1328
|
+
configPath,
|
|
1329
|
+
projectPath,
|
|
1330
|
+
projectId,
|
|
1331
|
+
environmentId,
|
|
1332
|
+
serviceId
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function planRailwayServiceLink(service, { env = process.env } = {}) {
|
|
1336
|
+
const args = ["link"];
|
|
1177
1337
|
if (service.projectId) {
|
|
1178
1338
|
args.push("--project", service.projectId);
|
|
1179
1339
|
}
|
|
1340
|
+
const workspace = resolveRailwayWorkspace(env);
|
|
1341
|
+
if (workspace) {
|
|
1342
|
+
args.push("--workspace", workspace);
|
|
1343
|
+
}
|
|
1180
1344
|
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1181
1345
|
if (environmentName) {
|
|
1182
1346
|
args.push("--environment", environmentName);
|
|
1183
1347
|
}
|
|
1348
|
+
const serviceSelector = service.serviceName ?? service.serviceId;
|
|
1349
|
+
if (serviceSelector) {
|
|
1350
|
+
args.push("--service", serviceSelector);
|
|
1351
|
+
}
|
|
1352
|
+
args.push("--json");
|
|
1184
1353
|
return {
|
|
1185
1354
|
command: "railway",
|
|
1186
1355
|
args,
|
|
1187
1356
|
cwd: service.rootDir
|
|
1188
1357
|
};
|
|
1189
1358
|
}
|
|
1359
|
+
function railwayProjectTokenName(service) {
|
|
1360
|
+
const environment = normalizeRailwayEnvironmentName(service.railwayEnvironment) || "environment";
|
|
1361
|
+
const serviceName = String(service.serviceName ?? service.serviceId ?? service.key ?? "service").toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 48) || "service";
|
|
1362
|
+
return `treeseed-ci-${environment}-${serviceName}`;
|
|
1363
|
+
}
|
|
1364
|
+
async function createRailwayCliProjectToken(service, { env = process.env } = {}) {
|
|
1365
|
+
const projectId = configuredEnvValue(service, "projectId");
|
|
1366
|
+
const environmentId = configuredEnvValue(service, "environmentId");
|
|
1367
|
+
if (!projectId || !environmentId) {
|
|
1368
|
+
return "";
|
|
1369
|
+
}
|
|
1370
|
+
const name = railwayProjectTokenName(service);
|
|
1371
|
+
try {
|
|
1372
|
+
const existingPayload = await railwayGraphqlRequest({
|
|
1373
|
+
query: `
|
|
1374
|
+
query TreeseedProjectTokens($projectId: String!) {
|
|
1375
|
+
projectTokens(projectId: $projectId, first: 100) {
|
|
1376
|
+
edges {
|
|
1377
|
+
node {
|
|
1378
|
+
id
|
|
1379
|
+
name
|
|
1380
|
+
projectId
|
|
1381
|
+
environmentId
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
`.trim(),
|
|
1387
|
+
variables: { projectId },
|
|
1388
|
+
env
|
|
1389
|
+
});
|
|
1390
|
+
const existingTokens = railwayEdgeNodes(existingPayload.data?.projectTokens);
|
|
1391
|
+
for (const token2 of existingTokens) {
|
|
1392
|
+
if (token2?.name === name && token2?.projectId === projectId && token2?.environmentId === environmentId && token2?.id) {
|
|
1393
|
+
await railwayGraphqlRequest({
|
|
1394
|
+
query: `
|
|
1395
|
+
mutation TreeseedProjectTokenDelete($id: String!) {
|
|
1396
|
+
projectTokenDelete(id: $id)
|
|
1397
|
+
}
|
|
1398
|
+
`.trim(),
|
|
1399
|
+
variables: { id: token2.id },
|
|
1400
|
+
env
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
const payload = await railwayGraphqlRequest({
|
|
1407
|
+
query: `
|
|
1408
|
+
mutation TreeseedProjectTokenCreate($input: ProjectTokenCreateInput!) {
|
|
1409
|
+
projectTokenCreate(input: $input)
|
|
1410
|
+
}
|
|
1411
|
+
`.trim(),
|
|
1412
|
+
variables: {
|
|
1413
|
+
input: {
|
|
1414
|
+
projectId,
|
|
1415
|
+
environmentId,
|
|
1416
|
+
name
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
env
|
|
1420
|
+
});
|
|
1421
|
+
const token = payload.data?.projectTokenCreate;
|
|
1422
|
+
return typeof token === "string" && token.trim() ? token.trim() : "";
|
|
1423
|
+
}
|
|
1190
1424
|
async function resolveRailwayDeployProjectContext(service, { env = process.env } = {}) {
|
|
1191
1425
|
if (service.projectId) {
|
|
1192
1426
|
return service;
|
|
@@ -1302,6 +1536,12 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1302
1536
|
});
|
|
1303
1537
|
}
|
|
1304
1538
|
return {
|
|
1539
|
+
projectId: project.id,
|
|
1540
|
+
projectName: project.name ?? service.projectName ?? null,
|
|
1541
|
+
environmentId: environment.id,
|
|
1542
|
+
environmentName: environment.name ?? environmentName,
|
|
1543
|
+
serviceId: railwayService.id,
|
|
1544
|
+
serviceName: railwayService.name ?? service.serviceName ?? null,
|
|
1305
1545
|
instance: runtimeConfiguration?.instance ?? null,
|
|
1306
1546
|
updated: Boolean(runtimeConfiguration?.updated || volumeConfiguration?.updated || volumeConfiguration?.created),
|
|
1307
1547
|
volume: volumeConfiguration ? {
|
|
@@ -1408,8 +1648,12 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1408
1648
|
};
|
|
1409
1649
|
}
|
|
1410
1650
|
const deployService = await resolveRailwayDeployProjectContext(service, { env });
|
|
1411
|
-
const plan = planRailwayServiceDeploy(deployService, { env });
|
|
1412
1651
|
const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
|
|
1652
|
+
let railwayDeployEnv = buildRailwayDeployCommandEnv(commandEnv);
|
|
1653
|
+
const railway = resolveTreeseedToolCommand("railway", { env: commandEnv });
|
|
1654
|
+
if (!railway) {
|
|
1655
|
+
throw new Error("Railway CLI is unavailable.");
|
|
1656
|
+
}
|
|
1413
1657
|
const taskPrefix = prefix ?? {
|
|
1414
1658
|
scope: normalizeScope(deployService.scope ?? deployService.railwayEnvironment ?? "railway"),
|
|
1415
1659
|
system: deployService.key === "api" ? "api" : "agents",
|
|
@@ -1419,7 +1663,33 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1419
1663
|
const runtimeConfiguration = await syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, {
|
|
1420
1664
|
env: commandEnv
|
|
1421
1665
|
});
|
|
1422
|
-
|
|
1666
|
+
const cliDeployService = {
|
|
1667
|
+
...deployService,
|
|
1668
|
+
projectId: runtimeConfiguration?.projectId ?? deployService.projectId,
|
|
1669
|
+
projectName: runtimeConfiguration?.projectName ?? deployService.projectName,
|
|
1670
|
+
environmentId: runtimeConfiguration?.environmentId ?? deployService.environmentId,
|
|
1671
|
+
serviceId: runtimeConfiguration?.serviceId ?? deployService.serviceId,
|
|
1672
|
+
serviceName: runtimeConfiguration?.serviceName ?? deployService.serviceName,
|
|
1673
|
+
railwayEnvironment: runtimeConfiguration?.environmentName ?? runtimeConfiguration?.environmentId ?? deployService.railwayEnvironment
|
|
1674
|
+
};
|
|
1675
|
+
railwayDeployEnv = buildRailwayCliContextEnv(railwayDeployEnv, cliDeployService);
|
|
1676
|
+
const hasCommandApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
|
|
1677
|
+
let usesProjectToken = Boolean(configuredEnvValue(railwayDeployEnv, "RAILWAY_TOKEN"));
|
|
1678
|
+
if (usesProjectToken) {
|
|
1679
|
+
railwayDeployEnv = { ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0 };
|
|
1680
|
+
}
|
|
1681
|
+
if (!usesProjectToken && !hasCommandApiToken) {
|
|
1682
|
+
const projectToken = await createRailwayCliProjectToken(cliDeployService, { env: commandEnv });
|
|
1683
|
+
if (projectToken) {
|
|
1684
|
+
railwayDeployEnv = buildRailwayCliContextEnv({ ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0, RAILWAY_TOKEN: projectToken }, cliDeployService);
|
|
1685
|
+
usesProjectToken = true;
|
|
1686
|
+
} else if (configuredEnvValue(commandEnv, "CI") === "true") {
|
|
1687
|
+
throw new Error(`Railway CI deploy requires a project token for ${cliDeployService.serviceName ?? cliDeployService.key}. Automatic project token creation did not return a token.`);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const linkPlan = planRailwayServiceLink(cliDeployService, { env: commandEnv });
|
|
1691
|
+
const plan = planRailwayServiceDeploy(cliDeployService, { env, projectTokenMode: usesProjectToken });
|
|
1692
|
+
if (deployService.buildCommand && shouldRunRailwayPredeployBuild(commandEnv)) {
|
|
1423
1693
|
const buildResult = await runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
|
|
1424
1694
|
cwd: deployService.rootDir,
|
|
1425
1695
|
env: commandEnv,
|
|
@@ -1430,11 +1700,28 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1430
1700
|
throw new Error(`Railway ${deployService.key} build command failed.`);
|
|
1431
1701
|
}
|
|
1432
1702
|
}
|
|
1703
|
+
const hasRailwayApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
|
|
1704
|
+
const cliConfig = configuredEnvValue(commandEnv, "CI") === "true" ? writeRailwayCliProjectConfig(cliDeployService, { env: railwayDeployEnv, cwd: plan.cwd }) : null;
|
|
1705
|
+
const effectiveLinkPlan = hasRailwayApiToken ? linkPlan : usesProjectToken ? planRailwayProjectEnvironmentLink(cliDeployService) : linkPlan;
|
|
1706
|
+
const railwayLinkEnv = hasRailwayApiToken ? buildRailwayLinkCommandEnv(commandEnv, cliDeployService) : railwayDeployEnv;
|
|
1707
|
+
if (cliConfig) {
|
|
1708
|
+
write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][link] Wrote Railway CLI project context for ${cliConfig.projectPath}.`, "stdout") : null;
|
|
1709
|
+
} else {
|
|
1710
|
+
const linkResult = await runPrefixedCommand(railway.command, [...railway.argsPrefix, ...effectiveLinkPlan.args], {
|
|
1711
|
+
cwd: effectiveLinkPlan.cwd,
|
|
1712
|
+
env: railwayLinkEnv,
|
|
1713
|
+
write,
|
|
1714
|
+
prefix: { ...taskPrefix, stage: "link" }
|
|
1715
|
+
});
|
|
1716
|
+
if (linkResult.status !== 0) {
|
|
1717
|
+
throw new Error(linkResult.stderr?.trim() || linkResult.stdout?.trim() || `railway ${effectiveLinkPlan.args.join(" ")} failed with exit code ${linkResult.status ?? "unknown"} in ${effectiveLinkPlan.cwd}`);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1433
1720
|
let lastFailure = null;
|
|
1434
1721
|
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
1435
|
-
const result = await runPrefixedCommand(
|
|
1436
|
-
cwd:
|
|
1437
|
-
env:
|
|
1722
|
+
const result = await runPrefixedCommand(railway.command, [...railway.argsPrefix, ...plan.args], {
|
|
1723
|
+
cwd: plan.cwd,
|
|
1724
|
+
env: railwayDeployEnv,
|
|
1438
1725
|
write,
|
|
1439
1726
|
prefix: taskPrefix
|
|
1440
1727
|
});
|
|
@@ -1444,7 +1731,7 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1444
1731
|
}
|
|
1445
1732
|
lastFailure = result;
|
|
1446
1733
|
if (!isRailwayTransientFailure(result) || attempt === 5) {
|
|
1447
|
-
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${plan.args.join(" ")} failed`);
|
|
1734
|
+
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${plan.args.join(" ")} failed with exit code ${result.status ?? "unknown"} in ${plan.cwd}`);
|
|
1448
1735
|
}
|
|
1449
1736
|
const backoffMs = 5e3 * attempt;
|
|
1450
1737
|
const warning = `Railway deploy for ${deployService.serviceName ?? deployService.serviceId ?? deployService.key} hit a transient failure; retrying in ${Math.round(backoffMs / 1e3)}s...`;
|
|
@@ -1471,6 +1758,8 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1471
1758
|
}
|
|
1472
1759
|
export {
|
|
1473
1760
|
buildRailwayCommandEnv,
|
|
1761
|
+
buildRailwayDeployCommandEnv,
|
|
1762
|
+
buildRailwayLinkCommandEnv,
|
|
1474
1763
|
collectRailwayDeploymentStatusChecks,
|
|
1475
1764
|
configuredRailwayScheduledJobs,
|
|
1476
1765
|
configuredRailwayServices,
|
|
@@ -1485,14 +1774,17 @@ export {
|
|
|
1485
1774
|
isRailwayTransientFailure,
|
|
1486
1775
|
isUsableRailwayToken,
|
|
1487
1776
|
planRailwayServiceDeploy,
|
|
1777
|
+
planRailwayServiceLink,
|
|
1488
1778
|
railwayServiceRuntimeStartCommand,
|
|
1489
1779
|
resolveRailwayAuthToken,
|
|
1490
1780
|
resolveRailwayDeploymentProfile,
|
|
1491
1781
|
runRailway,
|
|
1492
1782
|
setRailwaySecretVariable,
|
|
1783
|
+
shouldRunRailwayPredeployBuild,
|
|
1493
1784
|
validateRailwayDeployPrerequisites,
|
|
1494
1785
|
validateRailwayServiceConfiguration,
|
|
1495
1786
|
verifyRailwayManagedResources,
|
|
1496
1787
|
verifyRailwayScheduledJobs,
|
|
1497
|
-
waitForRailwayManagedDeploymentsSettled
|
|
1788
|
+
waitForRailwayManagedDeploymentsSettled,
|
|
1789
|
+
writeRailwayCliProjectConfig
|
|
1498
1790
|
};
|
|
@@ -85,6 +85,7 @@ export type RepositorySaveResult = {
|
|
|
85
85
|
rootRepo: RepositorySaveReport;
|
|
86
86
|
waves: string[][];
|
|
87
87
|
plannedVersions: Record<string, string>;
|
|
88
|
+
workflowGates?: Array<Record<string, unknown>>;
|
|
88
89
|
};
|
|
89
90
|
export type RepositorySavePlanRepo = {
|
|
90
91
|
id: string;
|
|
@@ -150,6 +151,13 @@ export type RepositorySaveOptions = {
|
|
|
150
151
|
includeRoot?: boolean;
|
|
151
152
|
stablePackageRelease?: boolean;
|
|
152
153
|
onProgress?: (message: string, stream?: 'stdout' | 'stderr') => void;
|
|
154
|
+
onWaveSaved?: (wave: {
|
|
155
|
+
index: number;
|
|
156
|
+
nodes: RepositorySaveNode[];
|
|
157
|
+
reports: RepositorySaveReport[];
|
|
158
|
+
allReports: RepositorySaveReport[];
|
|
159
|
+
rootRepo: RepositorySaveReport | null;
|
|
160
|
+
}) => Promise<Array<Record<string, unknown>> | void> | Array<Record<string, unknown>> | void;
|
|
153
161
|
};
|
|
154
162
|
export declare function runStreamingCommand(node: Pick<RepositorySaveNode, 'name' | 'path'>, options: Pick<RepositorySaveOptions, 'onProgress'>, phase: string, command: string, args: string[], commandOptions?: {
|
|
155
163
|
cwd?: string;
|