@treeseed/sdk 0.8.2 → 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 +140 -26
- 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
|
@@ -209,7 +209,8 @@ function readPackageScript(root, packageDir, scriptName) {
|
|
|
209
209
|
function ensureWorkflowWorkspacePackageArtifacts(root, helpers) {
|
|
210
210
|
const packages = [
|
|
211
211
|
{ name: "@treeseed/sdk", dir: "packages/sdk", artifacts: ["dist/workflow-support.js", "dist/plugin-default.js", "dist/platform/env.yaml"] },
|
|
212
|
-
{ name: "@treeseed/
|
|
212
|
+
{ name: "@treeseed/agent", dir: "packages/agent", artifacts: ["dist/api/index.js", "dist/services/worker.js"] },
|
|
213
|
+
{ name: "@treeseed/core", dir: "packages/core", artifacts: ["dist/plugin-default.js"] },
|
|
213
214
|
{ name: "@treeseed/cli", dir: "packages/cli", artifacts: ["dist/cli/main.js"] }
|
|
214
215
|
];
|
|
215
216
|
for (const entry of packages) {
|
|
@@ -301,6 +302,10 @@ function normalizeCiMode(mode, operation) {
|
|
|
301
302
|
if (mode === "hosted" || mode === "off") return mode;
|
|
302
303
|
return operation === "save" ? "off" : "hosted";
|
|
303
304
|
}
|
|
305
|
+
function normalizeSaveCiMode(mode, branch) {
|
|
306
|
+
if (mode === "hosted" || mode === "off") return mode;
|
|
307
|
+
return branch === STAGING_BRANCH ? "hosted" : "off";
|
|
308
|
+
}
|
|
304
309
|
function normalizeSaveVerifyMode(mode) {
|
|
305
310
|
switch (mode) {
|
|
306
311
|
case "skip":
|
|
@@ -319,8 +324,8 @@ function normalizeSaveVerifyMode(mode) {
|
|
|
319
324
|
return "skip";
|
|
320
325
|
}
|
|
321
326
|
}
|
|
322
|
-
function shouldUseHostedSaveCi(input) {
|
|
323
|
-
return
|
|
327
|
+
function shouldUseHostedSaveCi(input, branch) {
|
|
328
|
+
return normalizeSaveCiMode(input.ciMode, branch) === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both" || input.verifyDeployedResources === true;
|
|
324
329
|
}
|
|
325
330
|
function worktreePayload(root, requestedMode) {
|
|
326
331
|
const metadata = managedWorkflowWorktreeMetadata(root);
|
|
@@ -425,11 +430,11 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
|
|
|
425
430
|
}
|
|
426
431
|
return results;
|
|
427
432
|
}
|
|
428
|
-
const
|
|
429
|
-
function
|
|
433
|
+
const HOSTED_DEPLOY_GATE_TIMEOUT_SECONDS = 45 * 60;
|
|
434
|
+
function hostedDeployGate(gate) {
|
|
430
435
|
return {
|
|
431
436
|
...gate,
|
|
432
|
-
timeoutSeconds: gate.timeoutSeconds ??
|
|
437
|
+
timeoutSeconds: gate.timeoutSeconds ?? HOSTED_DEPLOY_GATE_TIMEOUT_SECONDS
|
|
433
438
|
};
|
|
434
439
|
}
|
|
435
440
|
function recordHostedDeploymentStatesFromRootGates(root, rootRelease, workflowGates) {
|
|
@@ -441,7 +446,7 @@ function recordHostedDeploymentStatesFromRootGates(root, rootRelease, workflowGa
|
|
|
441
446
|
{ scope: "staging", branch: STAGING_BRANCH, commit: releaseRecord.stagingCommit },
|
|
442
447
|
{ scope: "prod", branch: releaseTag ?? PRODUCTION_BRANCH, commit: releaseRecord.releasedCommit }
|
|
443
448
|
]) {
|
|
444
|
-
const gate = gates.find((candidate) => candidate.workflow === "deploy.yml" && candidate.branch === target.branch && candidate.status === "completed" && candidate.conclusion === "success");
|
|
449
|
+
const gate = gates.find((candidate) => (candidate.workflow === "deploy-web.yml" || candidate.workflow === "deploy-processing.yml") && candidate.branch === target.branch && candidate.status === "completed" && candidate.conclusion === "success");
|
|
445
450
|
const timestamp = typeof gate?.updatedAt === "string" && gate.updatedAt.trim() ? gate.updatedAt : null;
|
|
446
451
|
if (!gate || !timestamp) {
|
|
447
452
|
continue;
|
|
@@ -478,7 +483,7 @@ function ensureTreeseedCommandReadiness(root) {
|
|
|
478
483
|
{ id: "sdk", path: resolve(root, "node_modules/@treeseed/sdk/package.json") },
|
|
479
484
|
{ id: "sdk-workflow-support", path: resolve(root, "node_modules/@treeseed/sdk/dist/workflow-support.js") },
|
|
480
485
|
{ id: "core", path: resolve(root, "node_modules/@treeseed/core/package.json") },
|
|
481
|
-
{ id: "
|
|
486
|
+
{ id: "agent-api", path: resolve(root, "node_modules/@treeseed/agent/dist/api/index.js") },
|
|
482
487
|
{ id: "cli", path: resolve(root, "node_modules/@treeseed/cli/package.json") },
|
|
483
488
|
{ id: "cli-entrypoint", path: resolve(root, "node_modules/@treeseed/cli/dist/cli/main.js") },
|
|
484
489
|
{ id: "trsd-bin", path: resolve(root, "node_modules/.bin/trsd") }
|
|
@@ -838,7 +843,7 @@ function defaultCiWorkflows(kind, branch) {
|
|
|
838
843
|
return ["verify.yml"];
|
|
839
844
|
}
|
|
840
845
|
if (branch === STAGING_BRANCH || branch === PRODUCTION_BRANCH) {
|
|
841
|
-
return ["deploy.yml"];
|
|
846
|
+
return ["deploy-web.yml", "deploy-processing.yml"];
|
|
842
847
|
}
|
|
843
848
|
return ["verify.yml"];
|
|
844
849
|
}
|
|
@@ -1147,8 +1152,41 @@ function nextPendingJournalStep(journal) {
|
|
|
1147
1152
|
}
|
|
1148
1153
|
function findAutoResumableSaveRun(root, branch) {
|
|
1149
1154
|
if (!branch) return null;
|
|
1155
|
+
if (branch === STAGING_BRANCH && (hasMeaningfulChanges(repoRoot(root)) || checkedOutWorkspacePackageRepos(root).some((repo) => hasMeaningfulChanges(repo.dir)))) {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1150
1158
|
return listInterruptedWorkflowRuns(root).find((journal) => journal.command === "save" && journal.resumable && journal.session.branchName === branch) ?? null;
|
|
1151
1159
|
}
|
|
1160
|
+
function gatesForSavedPackageReports(reports) {
|
|
1161
|
+
return reports.filter((repo) => repo.pushed && repo.commitSha && repo.branch).map((repo) => ({
|
|
1162
|
+
name: repo.name,
|
|
1163
|
+
repoPath: repo.path,
|
|
1164
|
+
workflow: "verify.yml",
|
|
1165
|
+
branch: String(repo.branch),
|
|
1166
|
+
headSha: String(repo.commitSha)
|
|
1167
|
+
}));
|
|
1168
|
+
}
|
|
1169
|
+
function gateForSavedRootReport(report, branch, scope) {
|
|
1170
|
+
if (!branch || scope === "local" || !report.pushed || !report.commitSha) {
|
|
1171
|
+
return [];
|
|
1172
|
+
}
|
|
1173
|
+
if (branch === STAGING_BRANCH) {
|
|
1174
|
+
return [hostedDeployGate({
|
|
1175
|
+
name: report.name,
|
|
1176
|
+
repoPath: report.path,
|
|
1177
|
+
workflow: "deploy.yml",
|
|
1178
|
+
branch,
|
|
1179
|
+
headSha: report.commitSha
|
|
1180
|
+
})];
|
|
1181
|
+
}
|
|
1182
|
+
return [{
|
|
1183
|
+
name: report.name,
|
|
1184
|
+
repoPath: report.path,
|
|
1185
|
+
workflow: "verify.yml",
|
|
1186
|
+
branch,
|
|
1187
|
+
headSha: report.commitSha
|
|
1188
|
+
}];
|
|
1189
|
+
}
|
|
1152
1190
|
function findAutoResumableTaskRun(root, command, branch) {
|
|
1153
1191
|
if (!branch) return null;
|
|
1154
1192
|
return listInterruptedWorkflowRuns(root).find((journal) => journal.command === command && journal.resumable && journal.session.branchName === branch) ?? null;
|
|
@@ -1304,12 +1342,29 @@ function prepareFreshReleaseRun(root, branch, rootRepo, packageReports) {
|
|
|
1304
1342
|
}
|
|
1305
1343
|
return { archived, blockers };
|
|
1306
1344
|
}
|
|
1307
|
-
function findAutoResumableReleaseRun(root, branch, rootRepo, packageReports) {
|
|
1345
|
+
function findAutoResumableReleaseRun(root, branch, rootRepo, packageReports, options = {}) {
|
|
1308
1346
|
if (branch !== STAGING_BRANCH) return null;
|
|
1347
|
+
const currentHeads = Object.fromEntries([
|
|
1348
|
+
[rootRepo.name, rootRepo.commitSha ?? null],
|
|
1349
|
+
...packageReports.map((report) => [report.name, report.commitSha ?? null])
|
|
1350
|
+
]);
|
|
1309
1351
|
return listInterruptedWorkflowRuns(root).find((journal) => {
|
|
1310
1352
|
if (journal.command !== "release" || !journal.resumable || journal.session.branchName !== STAGING_BRANCH) {
|
|
1311
1353
|
return false;
|
|
1312
1354
|
}
|
|
1355
|
+
const classification = classifyWorkflowRunJournal(journal, {
|
|
1356
|
+
currentBranch: branch,
|
|
1357
|
+
currentHeads
|
|
1358
|
+
});
|
|
1359
|
+
if (classification.state !== "resumable") {
|
|
1360
|
+
if (options.archiveStale && classification.state === "stale") {
|
|
1361
|
+
archiveWorkflowRun(root, journal.runId, {
|
|
1362
|
+
...classification,
|
|
1363
|
+
reasons: ["release auto-resume skipped stale failed release", ...classification.reasons]
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1313
1368
|
const releasePlan = stringRecord(journal.steps.find((step) => step.id === "release-plan")?.data);
|
|
1314
1369
|
const nextStep = nextPendingJournalStep(journal);
|
|
1315
1370
|
if (releaseRunHasCompletedMutation(journal)) {
|
|
@@ -1459,7 +1514,7 @@ function validateStagingWorkflowContracts(root) {
|
|
|
1459
1514
|
return;
|
|
1460
1515
|
}
|
|
1461
1516
|
const missing = [];
|
|
1462
|
-
for (const fileName of ["verify.yml", "deploy.yml"]) {
|
|
1517
|
+
for (const fileName of ["verify.yml", "deploy-web.yml", "deploy-processing.yml"]) {
|
|
1463
1518
|
if (!existsSync(resolve(root, ".github", "workflows", fileName))) {
|
|
1464
1519
|
missing.push(fileName);
|
|
1465
1520
|
}
|
|
@@ -2846,7 +2901,7 @@ async function workflowSave(helpers, input) {
|
|
|
2846
2901
|
failure: planAutoResumeRun.failure
|
|
2847
2902
|
} : null,
|
|
2848
2903
|
workspaceLinks,
|
|
2849
|
-
ciMode:
|
|
2904
|
+
ciMode: normalizeSaveCiMode(effectiveInput.ciMode, branch),
|
|
2850
2905
|
verifyMode: effectiveInput.verifyMode ?? "fast",
|
|
2851
2906
|
...worktreePayload(root, effectiveInput.worktreeMode),
|
|
2852
2907
|
repositoryPlan,
|
|
@@ -2856,6 +2911,7 @@ async function workflowSave(helpers, input) {
|
|
|
2856
2911
|
{ id: "workspace-unlink", description: "Remove local workspace links before deployment install and lockfile updates" },
|
|
2857
2912
|
...repositoryPlan.plannedSteps,
|
|
2858
2913
|
{ id: "lockfile-validation", description: "Validate refreshed package-lock.json files before any save commit is pushed" },
|
|
2914
|
+
...shouldUseHostedSaveCi(effectiveInput, branch) ? [{ id: "hosted-ci", description: `Wait for hosted save workflows on ${branch}` }] : [],
|
|
2859
2915
|
...branch === STAGING_BRANCH ? [{ id: "release-candidate", description: "Run release-candidate readiness checks for the saved staging state" }] : [],
|
|
2860
2916
|
{ id: "workspace-link", description: "Restore local workspace links after save" },
|
|
2861
2917
|
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || previewInitialized) ? [{ id: "preview", description: `Refresh preview deployment for ${branch}` }] : []
|
|
@@ -2892,7 +2948,7 @@ async function workflowSave(helpers, input) {
|
|
|
2892
2948
|
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2893
2949
|
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2894
2950
|
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "fast"),
|
|
2895
|
-
ciMode: effectiveInput.ciMode ?? "auto",
|
|
2951
|
+
ciMode: effectiveInput.ciMode ?? (branch === STAGING_BRANCH ? "hosted" : "auto"),
|
|
2896
2952
|
worktreeMode: effectiveInput.worktreeMode ?? "auto",
|
|
2897
2953
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2898
2954
|
workspaceLinks: effectiveInput.workspaceLinks ?? "auto",
|
|
@@ -2907,7 +2963,7 @@ async function workflowSave(helpers, input) {
|
|
|
2907
2963
|
branch,
|
|
2908
2964
|
resumable: true
|
|
2909
2965
|
},
|
|
2910
|
-
...shouldUseHostedSaveCi(effectiveInput) ? [{
|
|
2966
|
+
...shouldUseHostedSaveCi(effectiveInput, branch) ? [{
|
|
2911
2967
|
id: "hosted-ci",
|
|
2912
2968
|
description: `Wait for hosted save workflows on ${branch}`,
|
|
2913
2969
|
repoName: rootRepo.name,
|
|
@@ -2962,7 +3018,29 @@ async function workflowSave(helpers, input) {
|
|
|
2962
3018
|
verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
|
|
2963
3019
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2964
3020
|
workflowRunId: workflowRun.runId,
|
|
2965
|
-
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3021
|
+
onProgress: (line, stream) => helpers.write(line, stream),
|
|
3022
|
+
onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
|
|
3023
|
+
const packageReportsForWave = reports.filter((repo, index) => nodes[index]?.kind === "package");
|
|
3024
|
+
const rootReportForWave = nodes.some((node) => node.kind === "project") ? waveRootRepo : null;
|
|
3025
|
+
const gates = [
|
|
3026
|
+
...gatesForSavedPackageReports(packageReportsForWave),
|
|
3027
|
+
...rootReportForWave ? gateForSavedRootReport(rootReportForWave, branch, scope) : []
|
|
3028
|
+
];
|
|
3029
|
+
if (gates.length === 0) {
|
|
3030
|
+
return [];
|
|
3031
|
+
}
|
|
3032
|
+
const packageNames = packageReportsForWave.map((repo) => repo.name).join(", ");
|
|
3033
|
+
if (packageNames) {
|
|
3034
|
+
helpers.write(`[save][workflow] Waiting for hosted package gates before saving dependents: ${packageNames}.`);
|
|
3035
|
+
} else if (rootReportForWave) {
|
|
3036
|
+
helpers.write("[save][workflow] Waiting for hosted market deploy gate.");
|
|
3037
|
+
}
|
|
3038
|
+
return waitForWorkflowGates("save", gates, "hosted", {
|
|
3039
|
+
root,
|
|
3040
|
+
runId: workflowRun.runId,
|
|
3041
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3042
|
+
});
|
|
3043
|
+
} : void 0
|
|
2966
3044
|
});
|
|
2967
3045
|
} finally {
|
|
2968
3046
|
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
@@ -2987,23 +3065,26 @@ async function workflowSave(helpers, input) {
|
|
|
2987
3065
|
lockfileValidation: repo.lockfileValidation
|
|
2988
3066
|
}))
|
|
2989
3067
|
};
|
|
2990
|
-
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", () => {
|
|
3068
|
+
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", () => {
|
|
3069
|
+
if (branch === STAGING_BRANCH) {
|
|
3070
|
+
return { workflowGates: saveResult?.workflowGates ?? [] };
|
|
3071
|
+
}
|
|
2991
3072
|
helpers.write("[save][workflow] Waiting for hosted save workflow gates.");
|
|
2992
3073
|
return waitForWorkflowGates("save", [
|
|
2993
|
-
...savedRootRepo.pushed && savedRootRepo.commitSha && branch ? [{
|
|
3074
|
+
...branch !== STAGING_BRANCH && savedRootRepo.pushed && savedRootRepo.commitSha && branch ? [{
|
|
2994
3075
|
name: savedRootRepo.name,
|
|
2995
3076
|
repoPath: savedRootRepo.path,
|
|
2996
3077
|
workflow: "verify.yml",
|
|
2997
3078
|
branch,
|
|
2998
3079
|
headSha: savedRootRepo.commitSha
|
|
2999
3080
|
}] : [],
|
|
3000
|
-
...effectiveInput.verifyDeployedResources === true && scope !== "local" && savedRootRepo.pushed && savedRootRepo.commitSha && branch ? [{
|
|
3081
|
+
...(branch === STAGING_BRANCH || effectiveInput.verifyDeployedResources === true) && scope !== "local" && savedRootRepo.pushed && savedRootRepo.commitSha && branch ? [hostedDeployGate({
|
|
3001
3082
|
name: savedRootRepo.name,
|
|
3002
3083
|
repoPath: savedRootRepo.path,
|
|
3003
3084
|
workflow: "deploy.yml",
|
|
3004
3085
|
branch,
|
|
3005
3086
|
headSha: savedRootRepo.commitSha
|
|
3006
|
-
}] : [],
|
|
3087
|
+
})] : [],
|
|
3007
3088
|
...savedPackageReports.filter((repo) => repo.pushed && repo.commitSha && repo.branch).map((repo) => ({
|
|
3008
3089
|
name: repo.name,
|
|
3009
3090
|
repoPath: repo.path,
|
|
@@ -3075,7 +3156,7 @@ async function workflowSave(helpers, input) {
|
|
|
3075
3156
|
workspaceLinks,
|
|
3076
3157
|
commandReadiness,
|
|
3077
3158
|
lockfileValidation,
|
|
3078
|
-
ciMode:
|
|
3159
|
+
ciMode: normalizeSaveCiMode(effectiveInput.ciMode, branch),
|
|
3079
3160
|
verifyMode: effectiveInput.verifyMode ?? "fast",
|
|
3080
3161
|
workflowGates: saveWorkflowGates?.workflowGates ?? [],
|
|
3081
3162
|
releaseCandidate,
|
|
@@ -3541,13 +3622,13 @@ async function workflowStage(helpers, input) {
|
|
|
3541
3622
|
});
|
|
3542
3623
|
}
|
|
3543
3624
|
const stageWorkflowGateResult = !waitForStaging ? (skipJournalStep(root, workflowRun.runId, "wait-staging", { status: "skipped", reason: "disabled" }), { status: "skipped", reason: "disabled" }) : await executeJournalStep(root, workflowRun.runId, "wait-staging", () => waitForWorkflowGates("stage", [
|
|
3544
|
-
{
|
|
3625
|
+
hostedDeployGate({
|
|
3545
3626
|
name: rootRepo.name,
|
|
3546
3627
|
repoPath: rootRepo.path,
|
|
3547
3628
|
workflow: "deploy.yml",
|
|
3548
3629
|
branch: STAGING_BRANCH,
|
|
3549
3630
|
headSha: rootRepo.commitSha
|
|
3550
|
-
},
|
|
3631
|
+
}),
|
|
3551
3632
|
...packageReports.filter((report) => report.merged && report.commitSha).map((report) => ({
|
|
3552
3633
|
name: report.name,
|
|
3553
3634
|
repoPath: report.path,
|
|
@@ -3714,7 +3795,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3714
3795
|
const explicitResumeRunId = helpers.context.workflow?.resumeRunId ?? null;
|
|
3715
3796
|
const freshRelease = input.fresh === true && !explicitResumeRunId;
|
|
3716
3797
|
const freshPreparation = freshRelease && executionMode === "execute" ? prepareFreshReleaseRun(root, session.branchName, rootRepo, packageReports) : { archived: [], blockers: [] };
|
|
3717
|
-
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId && !freshRelease ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
3798
|
+
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId && !freshRelease ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports, { archiveStale: true }) : null;
|
|
3718
3799
|
const planAutoResumeRun = executionMode === "plan" && input.fresh !== true ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
3719
3800
|
const effectiveInput = autoResumeRun ? {
|
|
3720
3801
|
...autoResumeRun.input,
|
|
@@ -3774,6 +3855,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3774
3855
|
},
|
|
3775
3856
|
[
|
|
3776
3857
|
{ id: "release-plan", description: "Record release plan", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
3858
|
+
{ id: "release-staging-gates", description: "Verify current staging GitHub Actions gates", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
3777
3859
|
{ id: "release-candidate", description: "Run release-candidate readiness checks", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
3778
3860
|
{ id: "workspace-unlink", description: "Remove local workspace links before release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
3779
3861
|
...mode === "recursive-workspace" ? [{ id: "prepare-release-metadata", description: "Rewrite stable release metadata", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }] : [],
|
|
@@ -3814,6 +3896,30 @@ async function workflowRelease(helpers, input) {
|
|
|
3814
3896
|
const rootVersion = String(releasePlan.rootVersion);
|
|
3815
3897
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
3816
3898
|
assertReleaseGitHubAutomationReady(root, effectiveSelectedPackageNames, ciMode);
|
|
3899
|
+
const stagingGateResult = resumeAtRootGates ? completedJournalStepData(root, workflowRun.runId, "release-staging-gates") : await executeJournalStep(root, workflowRun.runId, "release-staging-gates", () => {
|
|
3900
|
+
helpers.write("[release][workflow] Verifying current staging gates before production release.");
|
|
3901
|
+
const packageGates = checkedOutWorkspacePackageRepos(root).filter((pkg) => effectiveSelectedPackageNames.has(pkg.name)).map((pkg) => ({
|
|
3902
|
+
name: pkg.name,
|
|
3903
|
+
repoPath: pkg.dir,
|
|
3904
|
+
workflow: "verify.yml",
|
|
3905
|
+
branch: STAGING_BRANCH,
|
|
3906
|
+
headSha: headCommit(pkg.dir)
|
|
3907
|
+
}));
|
|
3908
|
+
return waitForWorkflowGates("release", [
|
|
3909
|
+
hostedDeployGate({
|
|
3910
|
+
name: rootRepo.name,
|
|
3911
|
+
repoPath: rootRepo.path,
|
|
3912
|
+
workflow: "deploy.yml",
|
|
3913
|
+
branch: STAGING_BRANCH,
|
|
3914
|
+
headSha: headCommit(gitRoot)
|
|
3915
|
+
}),
|
|
3916
|
+
...packageGates
|
|
3917
|
+
], ciMode, {
|
|
3918
|
+
root,
|
|
3919
|
+
runId: workflowRun.runId,
|
|
3920
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3921
|
+
}).then((workflowGates) => ({ workflowGates }));
|
|
3922
|
+
});
|
|
3817
3923
|
const releaseCandidate = resumeAtRootGates ? completedJournalStepData(root, workflowRun.runId, "release-candidate") : await executeJournalStep(root, workflowRun.runId, "release-candidate", () => runReleaseCandidateForPlan("release", root, releasePlan, { allowReuse: true }));
|
|
3818
3924
|
if (!resumeAtRootGates && !isResume) {
|
|
3819
3925
|
assertSessionBranchSafety("release", session, { requireCleanPackages: true, requireCurrentBranch: true });
|
|
@@ -3884,7 +3990,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3884
3990
|
rootRepo.commitSha = String(rootRelease2?.releasedCommit ?? headCommit(gitRoot));
|
|
3885
3991
|
rootRepo.tagName = String(rootRelease2?.rootVersion ?? "");
|
|
3886
3992
|
const rootWorkflowGateResult2 = await executeJournalStep(root, workflowRun.runId, "release-root-gates", () => waitForWorkflowGates("release", [
|
|
3887
|
-
|
|
3993
|
+
hostedDeployGate({
|
|
3888
3994
|
name: rootRepo.name,
|
|
3889
3995
|
repoPath: rootRepo.path,
|
|
3890
3996
|
workflow: "deploy.yml",
|
|
@@ -3897,6 +4003,7 @@ async function workflowRelease(helpers, input) {
|
|
|
3897
4003
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3898
4004
|
}).then((workflowGates) => ({ workflowGates })));
|
|
3899
4005
|
const hostedDeploymentState2 = recordHostedDeploymentStatesFromRootGates(root, rootRelease2, rootWorkflowGateResult2?.workflowGates);
|
|
4006
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
3900
4007
|
const hostingAudit2 = await runReadOnlyHostingAuditForWorkflow("release", root, helpers, "prod", {
|
|
3901
4008
|
enabled: true,
|
|
3902
4009
|
strict: false
|
|
@@ -3926,13 +4033,17 @@ async function workflowRelease(helpers, input) {
|
|
|
3926
4033
|
repos: [],
|
|
3927
4034
|
rootRepo,
|
|
3928
4035
|
releaseCandidate,
|
|
4036
|
+
stagingWorkflowGates: stagingGateResult?.workflowGates ?? [],
|
|
3929
4037
|
releaseBackMerge: releaseBackMerge2,
|
|
3930
4038
|
hostedDeploymentState: hostedDeploymentState2,
|
|
3931
4039
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
3932
4040
|
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true },
|
|
3933
4041
|
workspaceLinks: workspaceLinks2,
|
|
3934
4042
|
ciMode,
|
|
3935
|
-
workflowGates:
|
|
4043
|
+
workflowGates: [
|
|
4044
|
+
...Array.isArray(stagingGateResult?.workflowGates) ? stagingGateResult.workflowGates : [],
|
|
4045
|
+
...Array.isArray(rootWorkflowGateResult2?.workflowGates) ? rootWorkflowGateResult2.workflowGates : []
|
|
4046
|
+
],
|
|
3936
4047
|
hostingAudit: hostingAudit2,
|
|
3937
4048
|
...worktreePayload(root, effectiveInput.worktreeMode)
|
|
3938
4049
|
};
|
|
@@ -4190,7 +4301,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4190
4301
|
rootRepo.commitSha = String(rootRelease?.releasedCommit ?? headCommit(gitRoot));
|
|
4191
4302
|
rootRepo.tagName = String(rootRelease?.rootVersion ?? "");
|
|
4192
4303
|
const rootWorkflowGateResult = await executeJournalStep(root, workflowRun.runId, "release-root-gates", () => waitForWorkflowGates("release", [
|
|
4193
|
-
|
|
4304
|
+
hostedDeployGate({
|
|
4194
4305
|
name: rootRepo.name,
|
|
4195
4306
|
repoPath: rootRepo.path,
|
|
4196
4307
|
workflow: "deploy.yml",
|
|
@@ -4203,6 +4314,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4203
4314
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
4204
4315
|
}).then((workflowGates) => ({ workflowGates })));
|
|
4205
4316
|
const hostedDeploymentState = recordHostedDeploymentStatesFromRootGates(root, rootRelease, rootWorkflowGateResult?.workflowGates);
|
|
4317
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
4206
4318
|
const hostingAudit = await runReadOnlyHostingAuditForWorkflow("release", root, helpers, "prod", {
|
|
4207
4319
|
enabled: true,
|
|
4208
4320
|
strict: false
|
|
@@ -4245,6 +4357,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4245
4357
|
repos: packageReports,
|
|
4246
4358
|
rootRepo,
|
|
4247
4359
|
releaseCandidate,
|
|
4360
|
+
stagingWorkflowGates: stagingGateResult?.workflowGates ?? [],
|
|
4248
4361
|
releaseBackMerge,
|
|
4249
4362
|
hostedDeploymentState,
|
|
4250
4363
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
@@ -4256,6 +4369,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4256
4369
|
workspaceLinks,
|
|
4257
4370
|
ciMode,
|
|
4258
4371
|
workflowGates: [
|
|
4372
|
+
...Array.isArray(stagingGateResult?.workflowGates) ? stagingGateResult.workflowGates : [],
|
|
4259
4373
|
...packageReports.flatMap((report) => report.workflowGates),
|
|
4260
4374
|
...Array.isArray(rootWorkflowGateResult?.workflowGates) ? rootWorkflowGateResult.workflowGates : []
|
|
4261
4375
|
],
|
package/dist/workflow/runs.js
CHANGED
|
@@ -257,6 +257,28 @@ function expectedPackageHeadAfterReleaseGate(journal, packageName) {
|
|
|
257
257
|
if (typeof data?.commitSha === "string") return data.commitSha;
|
|
258
258
|
return null;
|
|
259
259
|
}
|
|
260
|
+
function savePartialFailureData(journal) {
|
|
261
|
+
const details = stringRecord(journal.failure?.details);
|
|
262
|
+
return stringRecord(details?.partialFailure);
|
|
263
|
+
}
|
|
264
|
+
function collectSaveExpectedHeads(journal) {
|
|
265
|
+
const heads = {};
|
|
266
|
+
const saveData = stringRecord(journal.steps.find((step) => step.id === "save-repositories")?.data);
|
|
267
|
+
const partialFailure = savePartialFailureData(journal);
|
|
268
|
+
const source = saveData ?? partialFailure;
|
|
269
|
+
const rootRepo = stringRecord(source?.rootRepo);
|
|
270
|
+
if (typeof rootRepo?.commitSha === "string") {
|
|
271
|
+
heads["@treeseed/market"] = rootRepo.commitSha;
|
|
272
|
+
}
|
|
273
|
+
const repos = Array.isArray(source?.repos) ? source.repos : [];
|
|
274
|
+
for (const entry of repos) {
|
|
275
|
+
const repo = stringRecord(entry);
|
|
276
|
+
if (typeof repo?.name === "string" && typeof repo.commitSha === "string") {
|
|
277
|
+
heads[repo.name] = repo.commitSha;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return heads;
|
|
281
|
+
}
|
|
260
282
|
function classifyWorkflowRunJournal(journal, options = {}) {
|
|
261
283
|
const reasons = [];
|
|
262
284
|
const now = options.now ?? nowIso();
|
|
@@ -326,6 +348,15 @@ function classifyWorkflowRunJournal(journal, options = {}) {
|
|
|
326
348
|
}
|
|
327
349
|
}
|
|
328
350
|
}
|
|
351
|
+
if (journal.command === "save" && options.currentHeads) {
|
|
352
|
+
const expectedHeads = collectSaveExpectedHeads(journal);
|
|
353
|
+
for (const [name, expectedHead] of Object.entries(expectedHeads)) {
|
|
354
|
+
const currentHead = options.currentHeads[name];
|
|
355
|
+
if (currentHead && expectedHead && currentHead !== expectedHead) {
|
|
356
|
+
reasons.push(`${name} head changed from ${expectedHead} to ${currentHead}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
329
360
|
return {
|
|
330
361
|
state: reasons.length > 0 ? "stale" : "resumable",
|
|
331
362
|
reasons: reasons.length > 0 ? reasons : releaseGateOnlyCompletion ? ["release commits already exist; remaining release gates can be rechecked"] : ["workflow run can be resumed"],
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
name: Treeseed Processing Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
environment:
|
|
7
|
+
required: true
|
|
8
|
+
type: string
|
|
9
|
+
action_kind:
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
project_id:
|
|
13
|
+
required: false
|
|
14
|
+
type: string
|
|
15
|
+
preview_id:
|
|
16
|
+
required: false
|
|
17
|
+
type: string
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
inputs:
|
|
20
|
+
environment:
|
|
21
|
+
required: true
|
|
22
|
+
default: staging
|
|
23
|
+
type: choice
|
|
24
|
+
options:
|
|
25
|
+
- staging
|
|
26
|
+
- prod
|
|
27
|
+
action_kind:
|
|
28
|
+
required: true
|
|
29
|
+
default: deploy_processing
|
|
30
|
+
type: choice
|
|
31
|
+
options:
|
|
32
|
+
- deploy_processing
|
|
33
|
+
- monitor
|
|
34
|
+
project_id:
|
|
35
|
+
required: false
|
|
36
|
+
type: string
|
|
37
|
+
preview_id:
|
|
38
|
+
required: false
|
|
39
|
+
type: string
|
|
40
|
+
|
|
41
|
+
jobs:
|
|
42
|
+
__WORKING_DIRECTORY_BLOCK__ processing:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
environment: ${{ inputs.environment == 'prod' && 'production' || 'staging' }}
|
|
47
|
+
env:
|
|
48
|
+
TREESEED_BOOTSTRAP_MODE: auto
|
|
49
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
50
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
|
|
51
|
+
RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
|
|
52
|
+
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
|
53
|
+
TREESEED_RAILWAY_WORKSPACE: ${{ vars.TREESEED_RAILWAY_WORKSPACE }}
|
|
54
|
+
TREESEED_API_BASE_URL: ${{ vars.TREESEED_API_BASE_URL }}
|
|
55
|
+
TREESEED_API_AUTH_SECRET: ${{ secrets.TREESEED_API_AUTH_SECRET }}
|
|
56
|
+
TREESEED_API_D1_DATABASE_ID: ${{ vars.TREESEED_API_D1_DATABASE_ID }}
|
|
57
|
+
TREESEED_API_WEB_SERVICE_ID: ${{ vars.TREESEED_API_WEB_SERVICE_ID }}
|
|
58
|
+
TREESEED_API_WEB_SERVICE_SECRET: ${{ secrets.TREESEED_API_WEB_SERVICE_SECRET }}
|
|
59
|
+
TREESEED_API_WEB_ASSERTION_SECRET: ${{ secrets.TREESEED_API_WEB_ASSERTION_SECRET }}
|
|
60
|
+
TREESEED_API_BOOTSTRAP_ADMIN_ALLOWLIST: ${{ vars.TREESEED_API_BOOTSTRAP_ADMIN_ALLOWLIST }}
|
|
61
|
+
TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
|
|
62
|
+
TREESEED_PROJECT_RUNNER_TOKEN: ${{ secrets.TREESEED_PROJECT_RUNNER_TOKEN }}
|
|
63
|
+
TREESEED_WORKER_POOL_SCALER: ${{ vars.TREESEED_WORKER_POOL_SCALER }}
|
|
64
|
+
TREESEED_AGENT_POOL_MIN_WORKERS: ${{ vars.TREESEED_AGENT_POOL_MIN_WORKERS }}
|
|
65
|
+
TREESEED_AGENT_POOL_MAX_WORKERS: ${{ vars.TREESEED_AGENT_POOL_MAX_WORKERS }}
|
|
66
|
+
TREESEED_AGENT_POOL_TARGET_QUEUE_DEPTH: ${{ vars.TREESEED_AGENT_POOL_TARGET_QUEUE_DEPTH }}
|
|
67
|
+
TREESEED_AGENT_POOL_COOLDOWN_SECONDS: ${{ vars.TREESEED_AGENT_POOL_COOLDOWN_SECONDS }}
|
|
68
|
+
TREESEED_WORKDAY_TIMEZONE: ${{ vars.TREESEED_WORKDAY_TIMEZONE }}
|
|
69
|
+
TREESEED_WORKDAY_WINDOWS_JSON: ${{ vars.TREESEED_WORKDAY_WINDOWS_JSON }}
|
|
70
|
+
TREESEED_WORKDAY_TASK_CREDIT_BUDGET: ${{ vars.TREESEED_WORKDAY_TASK_CREDIT_BUDGET }}
|
|
71
|
+
TREESEED_MANAGER_MAX_QUEUED_TASKS: ${{ vars.TREESEED_MANAGER_MAX_QUEUED_TASKS }}
|
|
72
|
+
TREESEED_MANAGER_MAX_QUEUED_CREDITS: ${{ vars.TREESEED_MANAGER_MAX_QUEUED_CREDITS }}
|
|
73
|
+
TREESEED_MANAGER_PRIORITY_MODELS: ${{ vars.TREESEED_MANAGER_PRIORITY_MODELS }}
|
|
74
|
+
TREESEED_TASK_CREDIT_WEIGHTS_JSON: ${{ vars.TREESEED_TASK_CREDIT_WEIGHTS_JSON }}
|
|
75
|
+
TREESEED_RAILWAY_PROJECT_ID: ${{ vars.TREESEED_RAILWAY_PROJECT_ID }}
|
|
76
|
+
TREESEED_RAILWAY_ENVIRONMENT_ID: ${{ vars.TREESEED_RAILWAY_ENVIRONMENT_ID }}
|
|
77
|
+
TREESEED_RAILWAY_WORKER_SERVICE_ID: ${{ vars.TREESEED_RAILWAY_WORKER_SERVICE_ID }}
|
|
78
|
+
TREESEED_CAPACITY_PROVIDER_ID: ${{ vars.TREESEED_CAPACITY_PROVIDER_ID }}
|
|
79
|
+
TREESEED_CAPACITY_PROVIDER_TEAM_ID: ${{ vars.TREESEED_CAPACITY_PROVIDER_TEAM_ID }}
|
|
80
|
+
TREESEED_CAPACITY_PROVIDER_SERVICE_BASE_URL: ${{ vars.TREESEED_CAPACITY_PROVIDER_SERVICE_BASE_URL }}
|
|
81
|
+
TREESEED_PROCESSING_DRAIN: ${{ vars.TREESEED_PROCESSING_DRAIN }}
|
|
82
|
+
TREESEED_WORKFLOW_ACTION: ${{ inputs.action_kind }}
|
|
83
|
+
TREESEED_WORKFLOW_ENVIRONMENT: ${{ inputs.environment }}
|
|
84
|
+
TREESEED_WORKFLOW_PLANE: processing
|
|
85
|
+
TREESEED_WORKFLOW_PROJECT: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
|
|
86
|
+
TREESEED_WORKFLOW_PREVIEW_ID: ${{ inputs.preview_id }}
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
with:
|
|
90
|
+
submodules: recursive
|
|
91
|
+
|
|
92
|
+
- uses: actions/setup-node@v4
|
|
93
|
+
with:
|
|
94
|
+
node-version: 22
|
|
95
|
+
cache: npm
|
|
96
|
+
cache-dependency-path: __CACHE_DEPENDENCY_PATH__
|
|
97
|
+
|
|
98
|
+
- run: npm ci --ignore-scripts
|
|
99
|
+
|
|
100
|
+
- name: Build package artifacts
|
|
101
|
+
shell: bash
|
|
102
|
+
run: |
|
|
103
|
+
set -euo pipefail
|
|
104
|
+
for dir in packages/sdk packages/agent packages/cli; do
|
|
105
|
+
if test -f "${dir}/package.json"; then npm --prefix "${dir}" run build:dist; fi
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
- name: Run processing workflow action
|
|
109
|
+
shell: bash
|
|
110
|
+
run: |
|
|
111
|
+
set -euo pipefail
|
|
112
|
+
EXTRA_ARGS=()
|
|
113
|
+
if [[ -n "${TREESEED_WORKFLOW_PROJECT:-}" ]]; then EXTRA_ARGS+=(--project-id "${TREESEED_WORKFLOW_PROJECT}"); fi
|
|
114
|
+
if [[ -n "${TREESEED_WORKFLOW_PREVIEW_ID:-}" ]]; then EXTRA_ARGS+=(--preview-id "${TREESEED_WORKFLOW_PREVIEW_ID}"); fi
|
|
115
|
+
node ./packages/sdk/scripts/run-ts.mjs ./packages/sdk/scripts/tenant-workflow-action.ts --action "${TREESEED_WORKFLOW_ACTION}" --environment "${TREESEED_WORKFLOW_ENVIRONMENT}" "${EXTRA_ARGS[@]}"
|