@treeseed/sdk 0.6.7 → 0.6.8
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/copilot.d.ts +15 -0
- package/dist/copilot.js +75 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/managed-dependencies.d.ts +56 -0
- package/dist/managed-dependencies.js +668 -0
- package/dist/operations/providers/default.js +30 -1
- package/dist/operations/services/commit-message-provider.d.ts +33 -0
- package/dist/operations/services/commit-message-provider.js +319 -0
- package/dist/operations/services/config-runtime.js +41 -20
- package/dist/operations/services/git-remote-policy.d.ts +9 -0
- package/dist/operations/services/git-remote-policy.js +55 -0
- package/dist/operations/services/git-workflow.js +22 -3
- package/dist/operations/services/github-api.js +9 -4
- package/dist/operations/services/knowledge-coop-launch.js +4 -0
- package/dist/operations/services/local-dev.js +7 -2
- package/dist/operations/services/package-reference-policy.d.ts +70 -0
- package/dist/operations/services/package-reference-policy.js +314 -0
- package/dist/operations/services/project-platform.d.ts +4 -0
- package/dist/operations/services/project-platform.js +28 -4
- package/dist/operations/services/railway-deploy.d.ts +4 -1
- package/dist/operations/services/railway-deploy.js +76 -38
- package/dist/operations/services/repository-save-orchestrator.d.ts +172 -0
- package/dist/operations/services/repository-save-orchestrator.js +1462 -0
- package/dist/operations/services/workspace-dependency-mode.d.ts +70 -0
- package/dist/operations/services/workspace-dependency-mode.js +404 -0
- package/dist/operations/services/workspace-preflight.js +5 -0
- package/dist/operations/services/workspace-save.js +10 -6
- package/dist/operations-registry.js +5 -0
- package/dist/operations-types.d.ts +1 -0
- package/dist/platform/books-data.js +4 -1
- package/dist/platform/env.yaml +6 -3
- package/dist/reconcile/builtin-adapters.js +37 -7
- package/dist/scripts/cleanup-markdown.js +4 -0
- package/dist/scripts/publish-package.js +5 -0
- package/dist/scripts/tenant-workflow-action.js +11 -2
- package/dist/verification.js +24 -12
- package/dist/workflow/operations.d.ts +381 -55
- package/dist/workflow/operations.js +718 -258
- package/dist/workflow-state.d.ts +40 -1
- package/dist/workflow-state.js +220 -17
- package/dist/workflow-support.d.ts +3 -0
- package/dist/workflow-support.js +34 -0
- package/dist/workflow.d.ts +19 -3
- package/dist/workflow.js +3 -3
- package/dist/wrangler-d1.js +6 -1
- package/package.json +17 -1
- package/templates/github/deploy.workflow.yml +24 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import {
|
|
5
5
|
applyTreeseedEnvironmentToProcess,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
setTreeseedRemoteSession,
|
|
24
24
|
writeTreeseedMachineConfig
|
|
25
25
|
} from "../operations/services/config-runtime.js";
|
|
26
|
+
import { formatTreeseedDependencyFailureDetails, installTreeseedDependencies } from "../managed-dependencies.js";
|
|
26
27
|
import { ControlPlaneClient } from "../control-plane-client.js";
|
|
27
28
|
import { exportTreeseedCodebase } from "../operations/services/export-runtime.js";
|
|
28
29
|
import {
|
|
@@ -64,13 +65,12 @@ import {
|
|
|
64
65
|
syncBranchWithOrigin,
|
|
65
66
|
waitForStagingAutomation
|
|
66
67
|
} from "../operations/services/git-workflow.js";
|
|
67
|
-
import { waitForGitHubWorkflowCompletion } from "../operations/services/github-automation.js";
|
|
68
|
+
import { getGitHubAutomationMode, resolveGitHubRepositorySlug, waitForGitHubWorkflowCompletion } from "../operations/services/github-automation.js";
|
|
69
|
+
import { createGitHubApiClient } from "../operations/services/github-api.js";
|
|
68
70
|
import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "../operations/services/runtime-tools.js";
|
|
69
71
|
import { runTenantDeployPreflight, runWorkspaceSavePreflight } from "../operations/services/save-deploy-preflight.js";
|
|
70
72
|
import { collectCliPreflight } from "../operations/services/workspace-preflight.js";
|
|
71
73
|
import {
|
|
72
|
-
applyWorkspaceVersionChanges,
|
|
73
|
-
assertWorkspaceVersionConsistency,
|
|
74
74
|
collectMergeConflictReport,
|
|
75
75
|
currentBranch,
|
|
76
76
|
formatMergeConflictReport,
|
|
@@ -81,11 +81,30 @@ import {
|
|
|
81
81
|
planWorkspaceReleaseBump,
|
|
82
82
|
repoRoot
|
|
83
83
|
} from "../operations/services/workspace-save.js";
|
|
84
|
+
import {
|
|
85
|
+
planRepositorySave,
|
|
86
|
+
refreshAndValidateRootWorkspaceLockfileForSave,
|
|
87
|
+
repositorySaveErrorDetails,
|
|
88
|
+
runRepositorySaveOrchestrator
|
|
89
|
+
} from "../operations/services/repository-save-orchestrator.js";
|
|
90
|
+
import {
|
|
91
|
+
assertNoInternalDevReferences,
|
|
92
|
+
cleanupDevTags,
|
|
93
|
+
collectInternalDevReferenceIssues,
|
|
94
|
+
devTagFromDependencySpec,
|
|
95
|
+
rewriteProjectInternalDependenciesToStableVersions
|
|
96
|
+
} from "../operations/services/package-reference-policy.js";
|
|
97
|
+
import {
|
|
98
|
+
ensureLocalWorkspaceLinks,
|
|
99
|
+
inspectWorkspaceDependencyMode,
|
|
100
|
+
unlinkLocalWorkspaceLinks
|
|
101
|
+
} from "../operations/services/workspace-dependency-mode.js";
|
|
84
102
|
import {
|
|
85
103
|
changedWorkspacePackages,
|
|
86
104
|
publishableWorkspacePackages,
|
|
87
105
|
run,
|
|
88
106
|
sortWorkspacePackages,
|
|
107
|
+
workspacePackages,
|
|
89
108
|
workspaceRoot
|
|
90
109
|
} from "../operations/services/workspace-tools.js";
|
|
91
110
|
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
@@ -129,6 +148,63 @@ function defaultWrite(output, stream = "stdout") {
|
|
|
129
148
|
(stream === "stderr" ? process.stderr : process.stdout).write(`${output}
|
|
130
149
|
`);
|
|
131
150
|
}
|
|
151
|
+
function shouldManageWorkspaceLinks(mode, env = process.env) {
|
|
152
|
+
if (mode === "off") return false;
|
|
153
|
+
const envMode = String(env?.TREESEED_WORKSPACE_LINKS ?? "auto").trim().toLowerCase();
|
|
154
|
+
return envMode !== "off" && envMode !== "false" && envMode !== "0";
|
|
155
|
+
}
|
|
156
|
+
function ensureWorkflowWorkspaceLinks(root, helpers, mode = "auto") {
|
|
157
|
+
if (!shouldManageWorkspaceLinks(mode, helpers.context.env)) {
|
|
158
|
+
return inspectWorkspaceDependencyMode(root, { mode: "off", env: helpers.context.env });
|
|
159
|
+
}
|
|
160
|
+
const report = ensureLocalWorkspaceLinks(root, { mode, env: helpers.context.env });
|
|
161
|
+
if (report.created.length > 0) {
|
|
162
|
+
helpers.write(`[workspace][link] Linked ${report.created.length} local workspace package paths.`);
|
|
163
|
+
}
|
|
164
|
+
return report;
|
|
165
|
+
}
|
|
166
|
+
function unlinkWorkflowWorkspaceLinks(root, helpers, mode = "auto") {
|
|
167
|
+
if (!shouldManageWorkspaceLinks(mode, helpers.context.env)) {
|
|
168
|
+
return inspectWorkspaceDependencyMode(root, { mode: "off", env: helpers.context.env });
|
|
169
|
+
}
|
|
170
|
+
const report = unlinkLocalWorkspaceLinks(root, { mode, env: helpers.context.env });
|
|
171
|
+
if (report.removed.length > 0) {
|
|
172
|
+
helpers.write(`[workspace][unlink] Removed ${report.removed.length} local workspace package links for deployment install.`);
|
|
173
|
+
}
|
|
174
|
+
return report;
|
|
175
|
+
}
|
|
176
|
+
function ensureTreeseedCommandReadiness(root) {
|
|
177
|
+
if (getGitHubAutomationMode() === "stub") {
|
|
178
|
+
return {
|
|
179
|
+
status: "skipped",
|
|
180
|
+
reason: "stubbed",
|
|
181
|
+
checks: [],
|
|
182
|
+
missing: []
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const checks = [
|
|
186
|
+
{ id: "sdk", path: resolve(root, "node_modules/@treeseed/sdk/package.json") },
|
|
187
|
+
{ id: "sdk-workflow-support", path: resolve(root, "node_modules/@treeseed/sdk/dist/workflow-support.js") },
|
|
188
|
+
{ id: "core", path: resolve(root, "node_modules/@treeseed/core/package.json") },
|
|
189
|
+
{ id: "core-api", path: resolve(root, "node_modules/@treeseed/core/dist/api.js") },
|
|
190
|
+
{ id: "cli", path: resolve(root, "node_modules/@treeseed/cli/package.json") },
|
|
191
|
+
{ id: "cli-entrypoint", path: resolve(root, "node_modules/@treeseed/cli/dist/cli/main.js") },
|
|
192
|
+
{ id: "trsd-bin", path: resolve(root, "node_modules/.bin/trsd") }
|
|
193
|
+
];
|
|
194
|
+
const missing = checks.filter((check) => !existsSync(check.path));
|
|
195
|
+
const report = {
|
|
196
|
+
status: missing.length === 0 ? "passed" : "failed",
|
|
197
|
+
checks: checks.map((check) => ({ ...check, exists: existsSync(check.path) })),
|
|
198
|
+
missing
|
|
199
|
+
};
|
|
200
|
+
if (missing.length > 0) {
|
|
201
|
+
workflowError("save", "validation_failed", `Treeseed save restored workspace links, but command readiness failed.
|
|
202
|
+
${missing.map((check) => `${check.id}: ${check.path}`).join("\n")}`, {
|
|
203
|
+
details: report
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return report;
|
|
207
|
+
}
|
|
132
208
|
function workflowError(operation, code, message, options = {}) {
|
|
133
209
|
throw new TreeseedWorkflowError(operation, code, message, options);
|
|
134
210
|
}
|
|
@@ -137,9 +213,9 @@ function ageDays(lastCommitDate) {
|
|
|
137
213
|
if (!Number.isFinite(timestamp)) return null;
|
|
138
214
|
return Math.max(0, Math.floor((Date.now() - timestamp) / 864e5));
|
|
139
215
|
}
|
|
140
|
-
function withContextEnv(env, action) {
|
|
216
|
+
async function withContextEnv(env, action) {
|
|
141
217
|
if (!env) {
|
|
142
|
-
return action();
|
|
218
|
+
return await action();
|
|
143
219
|
}
|
|
144
220
|
const previous = /* @__PURE__ */ new Map();
|
|
145
221
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -151,7 +227,7 @@ function withContextEnv(env, action) {
|
|
|
151
227
|
}
|
|
152
228
|
}
|
|
153
229
|
try {
|
|
154
|
-
return action();
|
|
230
|
+
return await action();
|
|
155
231
|
} finally {
|
|
156
232
|
for (const [key, value] of previous.entries()) {
|
|
157
233
|
if (value === void 0) {
|
|
@@ -293,19 +369,96 @@ function ensureLocalReadinessOrThrow(operation, tenantRoot) {
|
|
|
293
369
|
}
|
|
294
370
|
return state;
|
|
295
371
|
}
|
|
296
|
-
function
|
|
372
|
+
function planRootPackageVersion(root, level) {
|
|
373
|
+
const packageJsonPath = resolve(root, "package.json");
|
|
374
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
375
|
+
return incrementVersion(String(packageJson.version ?? "0.0.0"), level);
|
|
376
|
+
}
|
|
377
|
+
function setRootPackageJsonVersion(root, version) {
|
|
297
378
|
const packageJsonPath = resolve(root, "package.json");
|
|
298
379
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
299
|
-
packageJson.version =
|
|
380
|
+
packageJson.version = version;
|
|
300
381
|
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
301
382
|
`, "utf8");
|
|
302
383
|
return String(packageJson.version);
|
|
303
384
|
}
|
|
385
|
+
function writeJsonFile(path, value) {
|
|
386
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
387
|
+
`, "utf8");
|
|
388
|
+
}
|
|
389
|
+
function applyStableWorkspaceVersionChanges(root, versions) {
|
|
390
|
+
for (const pkg of workspacePackages(root)) {
|
|
391
|
+
const packageJsonPath = resolve(pkg.dir, "package.json");
|
|
392
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
393
|
+
let changed = false;
|
|
394
|
+
const plannedVersion = versions.get(pkg.name);
|
|
395
|
+
if (plannedVersion && packageJson.version !== plannedVersion) {
|
|
396
|
+
packageJson.version = plannedVersion;
|
|
397
|
+
changed = true;
|
|
398
|
+
}
|
|
399
|
+
for (const field of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
|
|
400
|
+
const values = packageJson[field];
|
|
401
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
402
|
+
for (const [dependencyName, version] of versions.entries()) {
|
|
403
|
+
if (!(dependencyName in values)) continue;
|
|
404
|
+
if (String(values[dependencyName]) === version) continue;
|
|
405
|
+
values[dependencyName] = version;
|
|
406
|
+
changed = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (changed) {
|
|
410
|
+
writeJsonFile(packageJsonPath, packageJson);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function gitObjectCommit(repoDir, ref) {
|
|
415
|
+
try {
|
|
416
|
+
return run("git", ["rev-list", "-n", "1", ref], { cwd: repoDir, capture: true }).trim() || null;
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function remoteTagCommit(repoDir, tagName) {
|
|
422
|
+
const output = run("git", ["ls-remote", "origin", `refs/tags/${tagName}`, `refs/tags/${tagName}^{}`], { cwd: repoDir, capture: true }).trim();
|
|
423
|
+
if (!output) return null;
|
|
424
|
+
const peeled = output.split("\n").find((line) => line.endsWith(`refs/tags/${tagName}^{}`));
|
|
425
|
+
const direct = output.split("\n").find((line) => line.endsWith(`refs/tags/${tagName}`));
|
|
426
|
+
return (peeled ?? direct)?.split(/\s+/u)[0] ?? null;
|
|
427
|
+
}
|
|
428
|
+
function ensureReleaseTag(repoDir, tagName, commitSha) {
|
|
429
|
+
const localCommit = gitObjectCommit(repoDir, tagName);
|
|
430
|
+
if (localCommit && localCommit !== commitSha) {
|
|
431
|
+
throw new Error(`Release tag ${tagName} already exists locally at ${localCommit}, expected ${commitSha}.`);
|
|
432
|
+
}
|
|
433
|
+
if (!localCommit) {
|
|
434
|
+
run("git", ["tag", "-a", tagName, commitSha, "-m", `release: ${tagName}`], { cwd: repoDir });
|
|
435
|
+
}
|
|
436
|
+
const remoteCommit = remoteTagCommit(repoDir, tagName);
|
|
437
|
+
if (remoteCommit && remoteCommit !== commitSha) {
|
|
438
|
+
throw new Error(`Release tag ${tagName} already exists on origin at ${remoteCommit}, expected ${commitSha}.`);
|
|
439
|
+
}
|
|
440
|
+
if (!remoteCommit) {
|
|
441
|
+
run("git", ["push", "origin", tagName], { cwd: repoDir });
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
tagName,
|
|
445
|
+
local: localCommit ? "existing" : "created",
|
|
446
|
+
remote: remoteCommit ? "existing" : "pushed"
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function commitAllIfChanged(repoDir, message) {
|
|
450
|
+
run("git", ["add", "-A"], { cwd: repoDir });
|
|
451
|
+
if (!hasMeaningfulChanges(repoDir)) {
|
|
452
|
+
return { committed: false, commitSha: headCommit(repoDir) };
|
|
453
|
+
}
|
|
454
|
+
run("git", ["commit", "-m", message], { cwd: repoDir });
|
|
455
|
+
return { committed: true, commitSha: headCommit(repoDir) };
|
|
456
|
+
}
|
|
304
457
|
function createNextSteps(steps) {
|
|
305
458
|
return steps.map(renderWorkflowStep);
|
|
306
459
|
}
|
|
307
|
-
function createStatusResult(cwd) {
|
|
308
|
-
const state = resolveTreeseedWorkflowState(cwd);
|
|
460
|
+
function createStatusResult(cwd, options = {}) {
|
|
461
|
+
const state = resolveTreeseedWorkflowState(cwd, options);
|
|
309
462
|
return buildWorkflowResult("status", cwd, state, {
|
|
310
463
|
nextSteps: createNextSteps(state.recommendations),
|
|
311
464
|
includeFinalState: false
|
|
@@ -555,6 +708,57 @@ function workflowSessionSnapshot(session) {
|
|
|
555
708
|
function nextPendingJournalStep(journal) {
|
|
556
709
|
return journal.steps.find((step) => step.status === "pending") ?? null;
|
|
557
710
|
}
|
|
711
|
+
function findAutoResumableSaveRun(root, branch) {
|
|
712
|
+
if (!branch) return null;
|
|
713
|
+
return listInterruptedWorkflowRuns(root).find((journal) => journal.command === "save" && journal.resumable && journal.session.branchName === branch) ?? null;
|
|
714
|
+
}
|
|
715
|
+
function stringRecord(value) {
|
|
716
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
717
|
+
}
|
|
718
|
+
function releasePlanHead(plan, repoName) {
|
|
719
|
+
if (repoName === "@treeseed/market") {
|
|
720
|
+
const rootRepo = stringRecord(plan.rootRepo);
|
|
721
|
+
return typeof rootRepo?.commitSha === "string" ? rootRepo.commitSha : null;
|
|
722
|
+
}
|
|
723
|
+
const repos = Array.isArray(plan.repos) ? plan.repos : [];
|
|
724
|
+
for (const repo of repos) {
|
|
725
|
+
const record = stringRecord(repo);
|
|
726
|
+
if (record?.name === repoName) {
|
|
727
|
+
return typeof record.commitSha === "string" ? record.commitSha : null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
function releasePlanMatchesCurrentHeads(plan, rootRepo, packageReports) {
|
|
733
|
+
if (releasePlanHead(plan, rootRepo.name) !== rootRepo.commitSha) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
const packageSelection = stringRecord(plan.packageSelection);
|
|
737
|
+
const selected = Array.isArray(packageSelection?.selected) ? packageSelection.selected.filter((name) => typeof name === "string") : packageReports.map((report) => report.name);
|
|
738
|
+
for (const name of selected) {
|
|
739
|
+
const current = packageReports.find((report) => report.name === name);
|
|
740
|
+
if (!current || releasePlanHead(plan, name) !== current.commitSha) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
function releaseRunHasCompletedMutation(journal) {
|
|
747
|
+
return journal.steps.some((step) => step.status === "completed" && step.id !== "release-plan" && step.id !== "workspace-unlink");
|
|
748
|
+
}
|
|
749
|
+
function findAutoResumableReleaseRun(root, branch, rootRepo, packageReports) {
|
|
750
|
+
if (branch !== STAGING_BRANCH) return null;
|
|
751
|
+
return listInterruptedWorkflowRuns(root).find((journal) => {
|
|
752
|
+
if (journal.command !== "release" || !journal.resumable || journal.session.branchName !== STAGING_BRANCH) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
if (releaseRunHasCompletedMutation(journal)) {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
const releasePlan = stringRecord(journal.steps.find((step) => step.id === "release-plan")?.data);
|
|
759
|
+
return releasePlan ? releasePlanMatchesCurrentHeads(releasePlan, rootRepo, packageReports) : true;
|
|
760
|
+
}) ?? null;
|
|
761
|
+
}
|
|
558
762
|
async function executeJournalStep(root, runId, stepId, action) {
|
|
559
763
|
const current = readWorkflowRunJournal(root, runId);
|
|
560
764
|
const step = current?.steps.find((entry) => entry.id === stepId) ?? null;
|
|
@@ -707,6 +911,149 @@ function validateStagingWorkflowContracts(root) {
|
|
|
707
911
|
});
|
|
708
912
|
}
|
|
709
913
|
}
|
|
914
|
+
function shouldSkipReleaseInstall() {
|
|
915
|
+
return process.env.TREESEED_GITHUB_AUTOMATION_MODE === "stub" || process.env.TREESEED_SAVE_NPM_INSTALL_MODE === "skip";
|
|
916
|
+
}
|
|
917
|
+
function runReleaseNpmInstall(repoDir, options = {}) {
|
|
918
|
+
if (shouldSkipReleaseInstall()) {
|
|
919
|
+
return { status: "skipped", reason: "stubbed" };
|
|
920
|
+
}
|
|
921
|
+
const args = repoDir === options.workspaceRoot ? ["install"] : ["install", "--workspaces=false"];
|
|
922
|
+
run("npm", args, { cwd: repoDir });
|
|
923
|
+
return { status: "completed", reason: null };
|
|
924
|
+
}
|
|
925
|
+
function pathIsWithin(parent, candidate) {
|
|
926
|
+
const path = relative(parent, candidate);
|
|
927
|
+
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
928
|
+
}
|
|
929
|
+
function assertNoInternalDevReferencesForRepo(root, repoDir, packageNames) {
|
|
930
|
+
const issues = collectInternalDevReferenceIssues(root, packageNames).filter((issue) => {
|
|
931
|
+
if (!pathIsWithin(repoDir, issue.filePath)) return false;
|
|
932
|
+
if (repoDir !== root) return true;
|
|
933
|
+
return !relative(root, issue.filePath).includes("/");
|
|
934
|
+
});
|
|
935
|
+
if (issues.length === 0) return;
|
|
936
|
+
const rendered = issues.map((issue) => `${issue.filePath}${issue.field ? ` ${issue.field}.${issue.dependencyName}` : ""}: ${issue.reason} ${issue.spec}`).join("\n");
|
|
937
|
+
throw new Error(`Stable release still contains internal Git/dev dependency references.
|
|
938
|
+
${rendered}`);
|
|
939
|
+
}
|
|
940
|
+
function collectActiveDevTagReferences(root) {
|
|
941
|
+
return collectInternalDevReferenceIssues(root).map((issue) => devTagFromDependencySpec(issue.spec) ?? (issue.spec.includes("-dev.") ? issue.spec : null)).filter((value) => Boolean(value));
|
|
942
|
+
}
|
|
943
|
+
function releasePlanVersionMap(plannedVersions) {
|
|
944
|
+
return new Map(
|
|
945
|
+
Object.entries(plannedVersions).filter(([name]) => name !== "@treeseed/market").map(([name, version]) => [name, String(version)])
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
function releasePlanPackageSelection(value) {
|
|
949
|
+
const record = value && typeof value === "object" ? value : {};
|
|
950
|
+
return {
|
|
951
|
+
changed: Array.isArray(record.changed) ? record.changed.map(String) : [],
|
|
952
|
+
dependents: Array.isArray(record.dependents) ? record.dependents.map(String) : [],
|
|
953
|
+
selected: Array.isArray(record.selected) ? record.selected.map(String) : []
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function buildReleasePlanSnapshot(input) {
|
|
957
|
+
const selectedPackageNames = new Set(input.packageSelection.selected);
|
|
958
|
+
const versionPlan = planWorkspaceReleaseBump(input.level, input.root, input.mode === "recursive-workspace" ? { selectedPackageNames } : {});
|
|
959
|
+
const rootVersion = planRootPackageVersion(input.root, input.level);
|
|
960
|
+
const plannedVersions = {
|
|
961
|
+
"@treeseed/market": rootVersion,
|
|
962
|
+
...Object.fromEntries(versionPlan.versions.entries())
|
|
963
|
+
};
|
|
964
|
+
const plannedDevReferenceRewrites = input.mode === "recursive-workspace" ? collectInternalDevReferenceIssues(input.root, selectedPackageNames) : [];
|
|
965
|
+
return {
|
|
966
|
+
mode: input.mode,
|
|
967
|
+
mergeStrategy: "merge-commit",
|
|
968
|
+
level: input.level,
|
|
969
|
+
rootVersion,
|
|
970
|
+
releaseTag: rootVersion,
|
|
971
|
+
stagingBranch: STAGING_BRANCH,
|
|
972
|
+
productionBranch: PRODUCTION_BRANCH,
|
|
973
|
+
packageSelection: input.packageSelection,
|
|
974
|
+
plannedVersions,
|
|
975
|
+
plannedDevReferenceRewrites,
|
|
976
|
+
plannedPublishWaits: input.packageSelection.selected.map((name) => ({
|
|
977
|
+
name,
|
|
978
|
+
workflow: "publish.yml",
|
|
979
|
+
branch: PRODUCTION_BRANCH,
|
|
980
|
+
status: "planned"
|
|
981
|
+
})),
|
|
982
|
+
touchedPackages: input.packageSelection.selected,
|
|
983
|
+
repos: input.packageReports,
|
|
984
|
+
rootRepo: input.rootRepo,
|
|
985
|
+
finalBranch: STAGING_BRANCH,
|
|
986
|
+
plannedSteps: [
|
|
987
|
+
{ id: "release-plan", description: "Record immutable release plan and target versions" },
|
|
988
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before stable release install" },
|
|
989
|
+
{ id: "prepare-release-metadata", description: "Rewrite package metadata and lockfiles to production dependency mode" },
|
|
990
|
+
...input.packageReports.filter((report) => selectedPackageNames.has(report.name)).map((report) => ({
|
|
991
|
+
id: `release-${report.name}`,
|
|
992
|
+
description: `Release ${report.name} from staging to main and tag ${plannedVersions[report.name] ?? "(planned)"}`
|
|
993
|
+
})),
|
|
994
|
+
{ id: "release-root", description: `Release market ${rootVersion}` },
|
|
995
|
+
{ id: "cleanup-dev-tags", description: "Clean replaced Treeseed dev tags after stable release" },
|
|
996
|
+
{ id: "workspace-link", description: "Restore local workspace links after release syncs back to staging" }
|
|
997
|
+
],
|
|
998
|
+
blockers: input.blockers
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function collectReleasePlanBlockers(session, mode, selectedPackageNames) {
|
|
1002
|
+
const blockers = [];
|
|
1003
|
+
if (session.branchName !== STAGING_BRANCH) {
|
|
1004
|
+
blockers.push("Release must start from staging.");
|
|
1005
|
+
}
|
|
1006
|
+
if (session.rootRepo.dirty) {
|
|
1007
|
+
blockers.push("@treeseed/market has uncommitted changes.");
|
|
1008
|
+
}
|
|
1009
|
+
if (!session.rootRepo.hasOriginRemote) {
|
|
1010
|
+
blockers.push("@treeseed/market is missing origin remote.");
|
|
1011
|
+
}
|
|
1012
|
+
if (mode === "recursive-workspace") {
|
|
1013
|
+
for (const repo of session.packageRepos) {
|
|
1014
|
+
if (!selectedPackageNames.includes(repo.name)) continue;
|
|
1015
|
+
if (repo.detached) blockers.push(`${repo.name} is detached.`);
|
|
1016
|
+
if (repo.branchName !== STAGING_BRANCH) blockers.push(`${repo.name} is on ${repo.branchName ?? "(detached)"} instead of staging.`);
|
|
1017
|
+
if (repo.dirty) blockers.push(`${repo.name} has uncommitted changes.`);
|
|
1018
|
+
if (!repo.hasOriginRemote) blockers.push(`${repo.name} is missing origin remote.`);
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
validatePackageReleaseWorkflows(session.root, selectedPackageNames);
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return blockers;
|
|
1027
|
+
}
|
|
1028
|
+
function assertReleaseGitHubAutomationReady(root, selectedPackageNames) {
|
|
1029
|
+
if (process.env.TREESEED_GITHUB_AUTOMATION_MODE === "stub") {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
createGitHubApiClient();
|
|
1033
|
+
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
1034
|
+
if (!selectedPackageNames.has(pkg.name)) continue;
|
|
1035
|
+
resolveGitHubRepositorySlug(pkg.dir);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function assertReleaseGitHubWorkflowSucceeded(packageName, workflow) {
|
|
1039
|
+
if (!workflow || workflow.status !== "completed") {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (workflow.conclusion === "success") {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const workflowName = typeof workflow.workflow === "string" ? workflow.workflow : "publish.yml";
|
|
1046
|
+
const repository = typeof workflow.repository === "string" ? workflow.repository : packageName;
|
|
1047
|
+
const url = typeof workflow.url === "string" && workflow.url ? `
|
|
1048
|
+
${workflow.url}` : "";
|
|
1049
|
+
const conclusion = typeof workflow.conclusion === "string" && workflow.conclusion ? workflow.conclusion : "unknown";
|
|
1050
|
+
workflowError("release", "github_workflow_failed", `${packageName} ${workflowName} completed with conclusion ${conclusion} in ${repository}.${url}`, {
|
|
1051
|
+
details: {
|
|
1052
|
+
packageName,
|
|
1053
|
+
workflow
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
710
1057
|
function assertSessionBranchSafety(operation, session, {
|
|
711
1058
|
requireCleanPackages = false,
|
|
712
1059
|
requireCurrentBranch = false,
|
|
@@ -1011,8 +1358,26 @@ function collectReleasePackageSelection(root) {
|
|
|
1011
1358
|
function hasStagedChanges(repoDir) {
|
|
1012
1359
|
return run("git", ["diff", "--cached", "--name-only"], { cwd: repoDir, capture: true }).trim().length > 0;
|
|
1013
1360
|
}
|
|
1014
|
-
async function workflowStatus(helpers) {
|
|
1015
|
-
return withContextEnv(helpers.context.env, () =>
|
|
1361
|
+
async function workflowStatus(helpers, input = {}) {
|
|
1362
|
+
return withContextEnv(helpers.context.env, async () => {
|
|
1363
|
+
const resolved = resolveTreeseedWorkflowPaths(helpers.cwd());
|
|
1364
|
+
if (resolved.tenantRoot) {
|
|
1365
|
+
try {
|
|
1366
|
+
await ensureTreeseedSecretSessionForConfig({
|
|
1367
|
+
tenantRoot: resolved.cwd,
|
|
1368
|
+
interactive: false,
|
|
1369
|
+
env: helpers.context.env,
|
|
1370
|
+
createIfMissing: false,
|
|
1371
|
+
allowMigration: false
|
|
1372
|
+
});
|
|
1373
|
+
} catch {
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return createStatusResult(helpers.cwd(), {
|
|
1377
|
+
...input,
|
|
1378
|
+
env: input.env ?? helpers.context.env
|
|
1379
|
+
});
|
|
1380
|
+
});
|
|
1016
1381
|
}
|
|
1017
1382
|
async function workflowTasks(helpers) {
|
|
1018
1383
|
return withContextEnv(helpers.context.env, () => createTasksResult(helpers.cwd()));
|
|
@@ -1034,6 +1399,21 @@ async function workflowConfig(helpers, input = {}) {
|
|
|
1034
1399
|
const bootstrapSystemsInput = input.systems;
|
|
1035
1400
|
const skipUnavailable = input.skipUnavailable;
|
|
1036
1401
|
const bootstrapExecution = input.bootstrapExecution ?? "parallel";
|
|
1402
|
+
const dependencyInstall = await installTreeseedDependencies({
|
|
1403
|
+
tenantRoot,
|
|
1404
|
+
force: input.installMissingTooling === true,
|
|
1405
|
+
env: helpers.context.env,
|
|
1406
|
+
write: (line) => maybePrint(helpers.write, line)
|
|
1407
|
+
});
|
|
1408
|
+
if (!dependencyInstall.ok) {
|
|
1409
|
+
workflowError(
|
|
1410
|
+
"config",
|
|
1411
|
+
"validation_failed",
|
|
1412
|
+
`Treeseed dependency initialization failed:
|
|
1413
|
+
- ${formatTreeseedDependencyFailureDetails(dependencyInstall)}`,
|
|
1414
|
+
{ details: { dependencies: dependencyInstall } }
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1037
1417
|
const repairs = input.repair === false ? [] : resolveTreeseedWorkflowState(tenantRoot).deployConfigPresent ? applyTreeseedSafeRepairs(tenantRoot) : [];
|
|
1038
1418
|
const toolHealth = ensureTreeseedActVerificationTooling({
|
|
1039
1419
|
tenantRoot,
|
|
@@ -1367,6 +1747,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1367
1747
|
plannedSteps: [
|
|
1368
1748
|
{ id: "switch-root", description: `Switch market repo to ${branchName}` },
|
|
1369
1749
|
...packageReports.map((report) => ({ id: `switch-${report.name}`, description: `Mirror ${branchName} into ${report.name}` })),
|
|
1750
|
+
{ id: "workspace-link", description: "Apply local workspace links for integrated development" },
|
|
1370
1751
|
...preview ? [{ id: "preview", description: `Provision or refresh preview for ${branchName}` }] : []
|
|
1371
1752
|
]
|
|
1372
1753
|
},
|
|
@@ -1398,6 +1779,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1398
1779
|
branch: branchName,
|
|
1399
1780
|
resumable: true
|
|
1400
1781
|
})),
|
|
1782
|
+
{ id: "workspace-link", description: "Apply local workspace links", repoName: rootRepo.name, repoPath: rootRepo.path, branch: branchName, resumable: true },
|
|
1401
1783
|
...preview ? [{ id: "preview", description: `Provision or refresh preview ${branchName}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: branchName, resumable: true }] : []
|
|
1402
1784
|
],
|
|
1403
1785
|
helpers.context
|
|
@@ -1437,6 +1819,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1437
1819
|
report.commitSha = headCommit(pkg.dir);
|
|
1438
1820
|
report.dirty = hasMeaningfulChanges(pkg.dir);
|
|
1439
1821
|
}
|
|
1822
|
+
const workspaceLinks = await executeJournalStep(root, workflowRun.runId, "workspace-link", () => ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto"));
|
|
1440
1823
|
const stateAfterSwitch = resolveTreeseedWorkflowState(root);
|
|
1441
1824
|
if (preview) {
|
|
1442
1825
|
previewResult = await executeJournalStep(
|
|
@@ -1461,6 +1844,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1461
1844
|
lastDeploymentTimestamp: state.preview.lastDeploymentTimestamp
|
|
1462
1845
|
},
|
|
1463
1846
|
previewResult,
|
|
1847
|
+
workspaceLinks,
|
|
1464
1848
|
preconditions: {
|
|
1465
1849
|
cleanWorktreeRequired: true,
|
|
1466
1850
|
baseBranch: STAGING_BRANCH
|
|
@@ -1502,6 +1886,7 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1502
1886
|
workflowError("dev", "unsupported_transport", "Treeseed dev is not supported over the HTTP workflow API.");
|
|
1503
1887
|
}
|
|
1504
1888
|
const tenantRoot = resolveProjectRootOrThrow("dev", helpers.cwd());
|
|
1889
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(workspaceRoot(tenantRoot), helpers, input.workspaceLinks ?? "auto");
|
|
1505
1890
|
const readiness = ensureLocalReadinessOrThrow("dev", tenantRoot);
|
|
1506
1891
|
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "local", override: true });
|
|
1507
1892
|
assertTreeseedCommandEnvironment({ tenantRoot, scope: "local", purpose: "dev" });
|
|
@@ -1537,7 +1922,8 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1537
1922
|
apiBaseUrl: process.env.TREESEED_API_BASE_URL ?? "http://127.0.0.1:3000",
|
|
1538
1923
|
webUrl: "http://127.0.0.1:8787"
|
|
1539
1924
|
},
|
|
1540
|
-
readiness: readiness.readiness.local
|
|
1925
|
+
readiness: readiness.readiness.local,
|
|
1926
|
+
workspaceLinks
|
|
1541
1927
|
});
|
|
1542
1928
|
}
|
|
1543
1929
|
const result = spawnSync(process.execPath, args, {
|
|
@@ -1558,7 +1944,8 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1558
1944
|
apiBaseUrl: process.env.TREESEED_API_BASE_URL ?? "http://127.0.0.1:3000",
|
|
1559
1945
|
webUrl: "http://127.0.0.1:8787"
|
|
1560
1946
|
},
|
|
1561
|
-
readiness: readiness.readiness.local
|
|
1947
|
+
readiness: readiness.readiness.local,
|
|
1948
|
+
workspaceLinks
|
|
1562
1949
|
});
|
|
1563
1950
|
});
|
|
1564
1951
|
} catch (error) {
|
|
@@ -1569,8 +1956,6 @@ async function workflowSave(helpers, input) {
|
|
|
1569
1956
|
try {
|
|
1570
1957
|
return await withContextEnv(helpers.context.env, async () => {
|
|
1571
1958
|
const tenantRoot = resolveProjectRootOrThrow("save", helpers.cwd());
|
|
1572
|
-
const message = ensureMessage("save", input.message, "a commit message");
|
|
1573
|
-
const optionsHotfix = input.hotfix === true;
|
|
1574
1959
|
const root = workspaceRoot(tenantRoot);
|
|
1575
1960
|
const session = resolveTreeseedWorkflowSession(root);
|
|
1576
1961
|
const gitRoot = session.gitRoot;
|
|
@@ -1580,6 +1965,12 @@ async function workflowSave(helpers, input) {
|
|
|
1580
1965
|
const recursiveWorkspace = session.mode === "recursive-workspace";
|
|
1581
1966
|
const mode = session.mode;
|
|
1582
1967
|
const executionMode = normalizeExecutionMode(input);
|
|
1968
|
+
const explicitResumeRunId = helpers.context.workflow?.resumeRunId ?? null;
|
|
1969
|
+
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId ? findAutoResumableSaveRun(root, branch) : null;
|
|
1970
|
+
const planAutoResumeRun = executionMode === "plan" ? findAutoResumableSaveRun(root, branch) : null;
|
|
1971
|
+
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
1972
|
+
const message = String(effectiveInput.message ?? "").trim();
|
|
1973
|
+
const optionsHotfix = effectiveInput.hotfix === true;
|
|
1583
1974
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope, override: true });
|
|
1584
1975
|
if (!branch) {
|
|
1585
1976
|
workflowError("save", "validation_failed", "Treeseed save requires an active git branch.");
|
|
@@ -1597,13 +1988,20 @@ async function workflowSave(helpers, input) {
|
|
|
1597
1988
|
if (branch === PRODUCTION_BRANCH && !optionsHotfix) {
|
|
1598
1989
|
blockers.push("Main saves require --hotfix.");
|
|
1599
1990
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1991
|
+
const repositoryPlan = planRepositorySave({
|
|
1992
|
+
root,
|
|
1993
|
+
gitRoot,
|
|
1994
|
+
branch,
|
|
1995
|
+
message,
|
|
1996
|
+
bump: effectiveInput.bump ?? "patch",
|
|
1997
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
1998
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
1999
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2000
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2001
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2002
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto"
|
|
2003
|
+
});
|
|
2004
|
+
const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
|
|
1607
2005
|
return buildWorkflowResult(
|
|
1608
2006
|
"save",
|
|
1609
2007
|
root,
|
|
@@ -1613,21 +2011,30 @@ async function workflowSave(helpers, input) {
|
|
|
1613
2011
|
scope,
|
|
1614
2012
|
hotfix: optionsHotfix,
|
|
1615
2013
|
message,
|
|
1616
|
-
repos:
|
|
1617
|
-
rootRepo,
|
|
2014
|
+
repos: repositoryPlan.repos,
|
|
2015
|
+
rootRepo: repositoryPlan.rootRepo,
|
|
1618
2016
|
blockers,
|
|
2017
|
+
autoResumeCandidate: planAutoResumeRun ? {
|
|
2018
|
+
runId: planAutoResumeRun.runId,
|
|
2019
|
+
branch: planAutoResumeRun.session.branchName,
|
|
2020
|
+
failure: planAutoResumeRun.failure
|
|
2021
|
+
} : null,
|
|
2022
|
+
workspaceLinks,
|
|
2023
|
+
repositoryPlan,
|
|
2024
|
+
waves: repositoryPlan.waves,
|
|
2025
|
+
plannedVersions: repositoryPlan.plannedVersions,
|
|
1619
2026
|
plannedSteps: [
|
|
1620
|
-
|
|
1621
|
-
...
|
|
1622
|
-
{ id: "
|
|
1623
|
-
{ id: "
|
|
1624
|
-
...beforeState.branchRole === "feature" && (
|
|
2027
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before deployment install and lockfile updates" },
|
|
2028
|
+
...repositoryPlan.plannedSteps,
|
|
2029
|
+
{ id: "lockfile-validation", description: "Validate refreshed package-lock.json files before any save commit is pushed" },
|
|
2030
|
+
{ id: "workspace-link", description: "Restore local workspace links after save" },
|
|
2031
|
+
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || beforeState.preview.enabled) ? [{ id: "preview", description: `Refresh preview deployment for ${branch}` }] : []
|
|
1625
2032
|
]
|
|
1626
2033
|
},
|
|
1627
2034
|
{
|
|
1628
2035
|
executionMode,
|
|
1629
2036
|
nextSteps: createNextSteps([
|
|
1630
|
-
{ operation: "save", reason: "Run without --plan to persist the workspace checkpoint.", input: { message, hotfix: optionsHotfix, preview:
|
|
2037
|
+
{ operation: "save", reason: planAutoResumeRun ? `Run without --plan to resume ${planAutoResumeRun.runId}.` : "Run without --plan to persist the workspace checkpoint.", input: { message, hotfix: optionsHotfix, preview: effectiveInput.preview === true } }
|
|
1631
2038
|
])
|
|
1632
2039
|
}
|
|
1633
2040
|
);
|
|
@@ -1646,44 +2053,28 @@ async function workflowSave(helpers, input) {
|
|
|
1646
2053
|
{
|
|
1647
2054
|
message,
|
|
1648
2055
|
hotfix: optionsHotfix,
|
|
1649
|
-
preview:
|
|
1650
|
-
refreshPreview:
|
|
1651
|
-
verify:
|
|
2056
|
+
preview: effectiveInput.preview === true,
|
|
2057
|
+
refreshPreview: effectiveInput.refreshPreview !== false,
|
|
2058
|
+
verify: effectiveInput.verify !== false,
|
|
2059
|
+
bump: effectiveInput.bump ?? "patch",
|
|
2060
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
2061
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
2062
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2063
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2064
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2065
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2066
|
+
workspaceLinks: effectiveInput.workspaceLinks ?? "auto"
|
|
1652
2067
|
},
|
|
1653
2068
|
[
|
|
1654
|
-
...packageReports.map((report) => ({
|
|
1655
|
-
id: `save-${report.name}`,
|
|
1656
|
-
description: `Save ${report.name}`,
|
|
1657
|
-
repoName: report.name,
|
|
1658
|
-
repoPath: report.path,
|
|
1659
|
-
branch,
|
|
1660
|
-
resumable: true
|
|
1661
|
-
})),
|
|
1662
|
-
...input.verify !== false ? [{
|
|
1663
|
-
id: "verify-root",
|
|
1664
|
-
description: "Verify market workspace",
|
|
1665
|
-
repoName: rootRepo.name,
|
|
1666
|
-
repoPath: rootRepo.path,
|
|
1667
|
-
branch,
|
|
1668
|
-
resumable: true
|
|
1669
|
-
}] : [],
|
|
1670
|
-
{
|
|
1671
|
-
id: "commit-root",
|
|
1672
|
-
description: "Commit market workspace changes",
|
|
1673
|
-
repoName: rootRepo.name,
|
|
1674
|
-
repoPath: rootRepo.path,
|
|
1675
|
-
branch,
|
|
1676
|
-
resumable: true
|
|
1677
|
-
},
|
|
1678
2069
|
{
|
|
1679
|
-
id: "
|
|
1680
|
-
description:
|
|
2070
|
+
id: "save-repositories",
|
|
2071
|
+
description: "Save dependency-ordered repositories",
|
|
1681
2072
|
repoName: rootRepo.name,
|
|
1682
2073
|
repoPath: rootRepo.path,
|
|
1683
2074
|
branch,
|
|
1684
2075
|
resumable: true
|
|
1685
2076
|
},
|
|
1686
|
-
...beforeState.branchRole === "feature" && (
|
|
2077
|
+
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || effectiveInput.refreshPreview !== false && beforeState.preview.enabled) ? [{
|
|
1687
2078
|
id: "preview",
|
|
1688
2079
|
description: `Refresh preview ${branch}`,
|
|
1689
2080
|
repoName: rootRepo.name,
|
|
@@ -1692,105 +2083,66 @@ async function workflowSave(helpers, input) {
|
|
|
1692
2083
|
resumable: true
|
|
1693
2084
|
}] : []
|
|
1694
2085
|
],
|
|
1695
|
-
|
|
2086
|
+
autoResumeRun ? {
|
|
2087
|
+
...helpers.context,
|
|
2088
|
+
workflow: {
|
|
2089
|
+
...helpers.context.workflow ?? {},
|
|
2090
|
+
resumeRunId: autoResumeRun.runId
|
|
2091
|
+
}
|
|
2092
|
+
} : helpers.context
|
|
1696
2093
|
);
|
|
2094
|
+
if (autoResumeRun) {
|
|
2095
|
+
helpers.write(`[workflow][resume] Resuming interrupted save ${autoResumeRun.runId} on ${branch}.`);
|
|
2096
|
+
}
|
|
1697
2097
|
try {
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
1701
|
-
const report = findReportByName(packageReports, pkg.name);
|
|
1702
|
-
if (!report) {
|
|
1703
|
-
continue;
|
|
1704
|
-
}
|
|
1705
|
-
try {
|
|
1706
|
-
const step = readWorkflowRunJournal(root, workflowRun.runId)?.steps.find((entry) => entry.id === `save-${report.name}`) ?? null;
|
|
1707
|
-
const resumePendingSync = workflowRun.resumed && step?.status === "pending" && branchNeedsSync(report.path, branch);
|
|
1708
|
-
if (!report.dirty && !resumePendingSync) {
|
|
1709
|
-
report.skippedReason = "clean";
|
|
1710
|
-
skipJournalStep(root, workflowRun.runId, `save-${report.name}`, {
|
|
1711
|
-
skippedReason: "clean"
|
|
1712
|
-
});
|
|
1713
|
-
continue;
|
|
1714
|
-
}
|
|
1715
|
-
const savedReport = await executeJournalStep(root, workflowRun.runId, `save-${report.name}`, () => savePackageRepo(report, message, branch, input.verify !== false));
|
|
1716
|
-
Object.assign(report, savedReport);
|
|
1717
|
-
} catch (error) {
|
|
1718
|
-
createSaveFailure(
|
|
1719
|
-
`Treeseed save stopped while saving workspace package ${pkg.name}.`,
|
|
1720
|
-
packageReports,
|
|
1721
|
-
rootRepo,
|
|
1722
|
-
report,
|
|
1723
|
-
error
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
if (input.verify !== false) {
|
|
2098
|
+
const saveResult = await executeJournalStep(root, workflowRun.runId, "save-repositories", () => (async () => {
|
|
2099
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
1729
2100
|
try {
|
|
1730
|
-
await
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2101
|
+
return await runRepositorySaveOrchestrator({
|
|
2102
|
+
root,
|
|
2103
|
+
gitRoot,
|
|
2104
|
+
branch,
|
|
2105
|
+
message,
|
|
2106
|
+
bump: effectiveInput.bump ?? "patch",
|
|
2107
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
2108
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
2109
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2110
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2111
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2112
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2113
|
+
workflowRunId: workflowRun.runId,
|
|
2114
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
1736
2115
|
});
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
"Treeseed save stopped while verifying the market workspace.",
|
|
1740
|
-
packageReports,
|
|
1741
|
-
rootRepo,
|
|
1742
|
-
null,
|
|
1743
|
-
error
|
|
1744
|
-
);
|
|
2116
|
+
} finally {
|
|
2117
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
1745
2118
|
}
|
|
1746
|
-
}
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1766
|
-
rootRepo.commitSha = head;
|
|
1767
|
-
let branchSync;
|
|
1768
|
-
try {
|
|
1769
|
-
branchSync = await executeJournalStep(root, workflowRun.runId, "sync-root", () => syncCurrentBranchToOrigin("save", gitRoot, branch));
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
createSaveFailure(
|
|
1772
|
-
"Treeseed save stopped while syncing the market repository.",
|
|
1773
|
-
packageReports,
|
|
1774
|
-
rootRepo,
|
|
1775
|
-
rootRepo,
|
|
1776
|
-
error
|
|
1777
|
-
);
|
|
1778
|
-
}
|
|
1779
|
-
rootRepo.pushed = branchSync.pushed === true;
|
|
1780
|
-
if (input.verify !== false) {
|
|
1781
|
-
rootRepo.verified = true;
|
|
1782
|
-
}
|
|
1783
|
-
if (!hadMeaningfulChanges) {
|
|
1784
|
-
rootRepo.skippedReason = "clean";
|
|
1785
|
-
}
|
|
2119
|
+
})());
|
|
2120
|
+
const savedPackageReports = saveResult?.repos ?? packageReports;
|
|
2121
|
+
const savedRootRepo = saveResult?.rootRepo ?? rootRepo;
|
|
2122
|
+
const head = savedRootRepo.commitSha ?? run("git", ["rev-parse", "HEAD"], { cwd: gitRoot, capture: true }).trim();
|
|
2123
|
+
const commitCreated = savedRootRepo.committed === true;
|
|
2124
|
+
const branchSync = {
|
|
2125
|
+
...savedRootRepo.publishWait ?? {},
|
|
2126
|
+
pushed: savedRootRepo.pushed === true
|
|
2127
|
+
};
|
|
2128
|
+
const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
|
|
2129
|
+
const commandReadiness = ensureTreeseedCommandReadiness(root);
|
|
2130
|
+
const lockfileValidation = {
|
|
2131
|
+
root: savedRootRepo.lockfileValidation,
|
|
2132
|
+
repos: savedPackageReports.map((repo) => ({
|
|
2133
|
+
name: repo.name,
|
|
2134
|
+
path: repo.path,
|
|
2135
|
+
lockfileValidation: repo.lockfileValidation
|
|
2136
|
+
}))
|
|
2137
|
+
};
|
|
1786
2138
|
let previewAction = { status: "skipped" };
|
|
1787
2139
|
if (beforeState.branchRole === "feature" && branch) {
|
|
1788
|
-
if (
|
|
2140
|
+
if (effectiveInput.preview === true) {
|
|
1789
2141
|
previewAction = {
|
|
1790
2142
|
status: beforeState.preview.enabled ? "refreshed" : "created",
|
|
1791
2143
|
details: await executeJournalStep(root, workflowRun.runId, "preview", () => deployBranchPreview(root, branch, helpers.context, { initialize: !beforeState.preview.enabled }))
|
|
1792
2144
|
};
|
|
1793
|
-
} else if (
|
|
2145
|
+
} else if (effectiveInput.refreshPreview !== false && beforeState.preview.enabled) {
|
|
1794
2146
|
previewAction = {
|
|
1795
2147
|
status: "refreshed",
|
|
1796
2148
|
details: await executeJournalStep(root, workflowRun.runId, "preview", () => deployBranchPreview(root, branch, helpers.context, { initialize: false }))
|
|
@@ -1798,20 +2150,28 @@ async function workflowSave(helpers, input) {
|
|
|
1798
2150
|
}
|
|
1799
2151
|
}
|
|
1800
2152
|
const payload = {
|
|
1801
|
-
mode,
|
|
2153
|
+
mode: saveResult?.mode ?? mode,
|
|
1802
2154
|
branch,
|
|
1803
2155
|
scope,
|
|
1804
2156
|
hotfix: optionsHotfix,
|
|
1805
2157
|
message,
|
|
2158
|
+
resumed: workflowRun.resumed,
|
|
2159
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2160
|
+
autoResumed: autoResumeRun != null,
|
|
1806
2161
|
commitSha: head,
|
|
1807
2162
|
commitCreated,
|
|
1808
|
-
noChanges: !
|
|
2163
|
+
noChanges: !commitCreated,
|
|
1809
2164
|
branchSync,
|
|
1810
|
-
repos:
|
|
1811
|
-
rootRepo,
|
|
2165
|
+
repos: savedPackageReports,
|
|
2166
|
+
rootRepo: savedRootRepo,
|
|
2167
|
+
waves: saveResult?.waves ?? [],
|
|
2168
|
+
plannedVersions: saveResult?.plannedVersions ?? {},
|
|
1812
2169
|
partialFailure: null,
|
|
1813
2170
|
previewAction,
|
|
1814
|
-
mergeConflict: null
|
|
2171
|
+
mergeConflict: null,
|
|
2172
|
+
workspaceLinks,
|
|
2173
|
+
commandReadiness,
|
|
2174
|
+
lockfileValidation
|
|
1815
2175
|
};
|
|
1816
2176
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
1817
2177
|
return buildWorkflowResult(
|
|
@@ -1826,7 +2186,9 @@ async function workflowSave(helpers, input) {
|
|
|
1826
2186
|
}
|
|
1827
2187
|
);
|
|
1828
2188
|
} catch (error) {
|
|
1829
|
-
const
|
|
2189
|
+
const saveError = repositorySaveErrorDetails(error);
|
|
2190
|
+
const savedPartialFailure = saveError.details?.partialFailure;
|
|
2191
|
+
const failingRepo = savedPartialFailure?.repos.find((report) => report.name === savedPartialFailure.failingRepo) ?? packageReports.find((report) => report.dirty && report.pushed !== true) ?? rootRepo;
|
|
1830
2192
|
const wrappedError = error instanceof TreeseedWorkflowError && error.details?.partialFailure != null ? error : new TreeseedWorkflowError(
|
|
1831
2193
|
"save",
|
|
1832
2194
|
error instanceof TreeseedWorkflowError ? error.code : "unsupported_state",
|
|
@@ -1834,7 +2196,8 @@ async function workflowSave(helpers, input) {
|
|
|
1834
2196
|
{
|
|
1835
2197
|
details: {
|
|
1836
2198
|
...error instanceof TreeseedWorkflowError ? error.details ?? {} : {},
|
|
1837
|
-
|
|
2199
|
+
...saveError.details ?? {},
|
|
2200
|
+
partialFailure: savedPartialFailure ?? {
|
|
1838
2201
|
message: "Treeseed save stopped before the workspace could finish syncing.",
|
|
1839
2202
|
failingRepo: failingRepo.name,
|
|
1840
2203
|
repos: packageReports,
|
|
@@ -1842,7 +2205,7 @@ async function workflowSave(helpers, input) {
|
|
|
1842
2205
|
error: error instanceof Error ? error.message : String(error)
|
|
1843
2206
|
}
|
|
1844
2207
|
},
|
|
1845
|
-
exitCode: error instanceof TreeseedWorkflowError ? error.exitCode :
|
|
2208
|
+
exitCode: error instanceof TreeseedWorkflowError ? error.exitCode : saveError.exitCode
|
|
1846
2209
|
}
|
|
1847
2210
|
);
|
|
1848
2211
|
failWorkflowRun(root, workflowRun.runId, wrappedError, {
|
|
@@ -1888,7 +2251,8 @@ async function workflowClose(helpers, input) {
|
|
|
1888
2251
|
...checkedOutWorkspacePackageRepos(root).map((pkg) => ({
|
|
1889
2252
|
id: `cleanup-${pkg.name}`,
|
|
1890
2253
|
description: `Archive and delete ${branchName ?? "(current task)"} in ${pkg.name}`
|
|
1891
|
-
}))
|
|
2254
|
+
})),
|
|
2255
|
+
{ id: "workspace-link", description: "Restore local workspace links on the final branch" }
|
|
1892
2256
|
]
|
|
1893
2257
|
},
|
|
1894
2258
|
{
|
|
@@ -1899,10 +2263,12 @@ async function workflowClose(helpers, input) {
|
|
|
1899
2263
|
}
|
|
1900
2264
|
);
|
|
1901
2265
|
}
|
|
2266
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1902
2267
|
const autoSave = await maybeAutoSaveCurrentTaskBranch(helpers, "close", {
|
|
1903
2268
|
message,
|
|
1904
2269
|
autoSave: input.autoSave
|
|
1905
2270
|
});
|
|
2271
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1906
2272
|
const activeSession = resolveTreeseedWorkflowSession(root);
|
|
1907
2273
|
const featureBranch = assertFeatureBranch(root);
|
|
1908
2274
|
const mode = activeSession.mode;
|
|
@@ -1967,6 +2333,7 @@ async function workflowClose(helpers, input) {
|
|
|
1967
2333
|
}));
|
|
1968
2334
|
Object.assign(report, cleanup);
|
|
1969
2335
|
}
|
|
2336
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1970
2337
|
const payload = {
|
|
1971
2338
|
mode,
|
|
1972
2339
|
branchName: featureBranch,
|
|
@@ -1979,7 +2346,8 @@ async function workflowClose(helpers, input) {
|
|
|
1979
2346
|
previewCleanup,
|
|
1980
2347
|
remoteDeleted: rootRepo.deletedRemote,
|
|
1981
2348
|
localDeleted: rootRepo.deletedLocal,
|
|
1982
|
-
finalBranch: currentBranch(repoDir) || STAGING_BRANCH
|
|
2349
|
+
finalBranch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2350
|
+
workspaceLinks
|
|
1983
2351
|
};
|
|
1984
2352
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
1985
2353
|
return buildWorkflowResult(
|
|
@@ -1994,6 +2362,7 @@ async function workflowClose(helpers, input) {
|
|
|
1994
2362
|
}
|
|
1995
2363
|
);
|
|
1996
2364
|
} catch (error) {
|
|
2365
|
+
ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1997
2366
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
1998
2367
|
resumable: true,
|
|
1999
2368
|
runId: workflowRun.runId,
|
|
@@ -2042,14 +2411,17 @@ async function workflowStage(helpers, input) {
|
|
|
2042
2411
|
id: `merge-${pkg.name}`,
|
|
2043
2412
|
description: `Squash-merge ${initialSession.branchName ?? "(current task)"} into ${pkg.name} staging`
|
|
2044
2413
|
})),
|
|
2414
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before staging promotion" },
|
|
2045
2415
|
{ id: "merge-root", description: `Squash-merge ${initialSession.branchName ?? "(current task)"} into market staging` },
|
|
2416
|
+
{ id: "lockfile-validation", description: "Refresh and validate the merged root workspace lockfile before pushing staging" },
|
|
2046
2417
|
{ id: "wait-staging", description: "Wait for staging automation" },
|
|
2047
2418
|
{ id: "preview-cleanup", description: "Destroy preview resources" },
|
|
2048
2419
|
{ id: "cleanup-root", description: "Archive and delete the task branch from market" },
|
|
2049
2420
|
...checkedOutWorkspacePackageRepos(root).map((pkg) => ({
|
|
2050
2421
|
id: `cleanup-${pkg.name}`,
|
|
2051
2422
|
description: `Archive and delete the task branch from ${pkg.name}`
|
|
2052
|
-
}))
|
|
2423
|
+
})),
|
|
2424
|
+
{ id: "workspace-link", description: "Restore local workspace links on staging" }
|
|
2053
2425
|
]
|
|
2054
2426
|
},
|
|
2055
2427
|
{
|
|
@@ -2060,10 +2432,12 @@ async function workflowStage(helpers, input) {
|
|
|
2060
2432
|
}
|
|
2061
2433
|
);
|
|
2062
2434
|
}
|
|
2435
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2063
2436
|
const autoSave = await maybeAutoSaveCurrentTaskBranch(helpers, "stage", {
|
|
2064
2437
|
message,
|
|
2065
2438
|
autoSave: input.autoSave
|
|
2066
2439
|
});
|
|
2440
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2067
2441
|
const session = resolveTreeseedWorkflowSession(root);
|
|
2068
2442
|
const featureBranch = assertFeatureBranch(root);
|
|
2069
2443
|
const mode = session.mode;
|
|
@@ -2107,6 +2481,7 @@ async function workflowStage(helpers, input) {
|
|
|
2107
2481
|
helpers.context
|
|
2108
2482
|
);
|
|
2109
2483
|
try {
|
|
2484
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2110
2485
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2111
2486
|
const report = findReportByName(packageReports, pkg.name);
|
|
2112
2487
|
if (!report) {
|
|
@@ -2134,14 +2509,21 @@ async function workflowStage(helpers, input) {
|
|
|
2134
2509
|
});
|
|
2135
2510
|
}
|
|
2136
2511
|
}
|
|
2512
|
+
let rootMerge = null;
|
|
2137
2513
|
try {
|
|
2138
|
-
|
|
2514
|
+
rootMerge = await executeJournalStep(root, workflowRun.runId, "merge-root", async () => {
|
|
2139
2515
|
assertCleanWorktree(root);
|
|
2140
2516
|
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
2141
2517
|
run("git", ["merge", "--squash", featureBranch], { cwd: repoDir });
|
|
2142
2518
|
if (mode === "recursive-workspace") {
|
|
2143
2519
|
syncAllCheckedOutPackageRepos(root, STAGING_BRANCH);
|
|
2144
2520
|
}
|
|
2521
|
+
const lockfileSafety = await refreshAndValidateRootWorkspaceLockfileForSave({
|
|
2522
|
+
root,
|
|
2523
|
+
gitRoot: repoDir,
|
|
2524
|
+
branch: STAGING_BRANCH,
|
|
2525
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
2526
|
+
});
|
|
2145
2527
|
if (hasStagedChanges(repoDir) || hasMeaningfulChanges(repoDir)) {
|
|
2146
2528
|
run("git", ["add", "-A"], { cwd: repoDir });
|
|
2147
2529
|
run("git", ["commit", "-m", message], { cwd: repoDir });
|
|
@@ -2150,7 +2532,9 @@ async function workflowStage(helpers, input) {
|
|
|
2150
2532
|
return {
|
|
2151
2533
|
commitSha: headCommit(repoDir),
|
|
2152
2534
|
branch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2153
|
-
committed: hasMeaningfulChanges(repoDir) ? false : true
|
|
2535
|
+
committed: hasMeaningfulChanges(repoDir) ? false : true,
|
|
2536
|
+
lockfileValidation: lockfileSafety.lockfileValidation,
|
|
2537
|
+
lockfileInstall: lockfileSafety.install
|
|
2154
2538
|
};
|
|
2155
2539
|
});
|
|
2156
2540
|
rootRepo.merged = true;
|
|
@@ -2196,6 +2580,7 @@ async function workflowStage(helpers, input) {
|
|
|
2196
2580
|
}));
|
|
2197
2581
|
Object.assign(report, cleanup);
|
|
2198
2582
|
}
|
|
2583
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2199
2584
|
const payload = {
|
|
2200
2585
|
mode,
|
|
2201
2586
|
branchName: featureBranch,
|
|
@@ -2209,9 +2594,12 @@ async function workflowStage(helpers, input) {
|
|
|
2209
2594
|
rootRepo,
|
|
2210
2595
|
stagingWait,
|
|
2211
2596
|
previewCleanup,
|
|
2597
|
+
lockfileValidation: rootMerge?.lockfileValidation ?? null,
|
|
2598
|
+
lockfileInstall: rootMerge?.lockfileInstall ?? null,
|
|
2212
2599
|
remoteDeleted: rootRepo.deletedRemote,
|
|
2213
2600
|
localDeleted: rootRepo.deletedLocal,
|
|
2214
|
-
finalBranch: currentBranch(repoDir) || STAGING_BRANCH
|
|
2601
|
+
finalBranch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2602
|
+
workspaceLinks
|
|
2215
2603
|
};
|
|
2216
2604
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
2217
2605
|
return buildWorkflowResult(
|
|
@@ -2227,6 +2615,7 @@ async function workflowStage(helpers, input) {
|
|
|
2227
2615
|
}
|
|
2228
2616
|
);
|
|
2229
2617
|
} catch (error) {
|
|
2618
|
+
ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2230
2619
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
2231
2620
|
resumable: true,
|
|
2232
2621
|
runId: workflowRun.runId,
|
|
@@ -2245,7 +2634,6 @@ async function workflowStage(helpers, input) {
|
|
|
2245
2634
|
async function workflowRelease(helpers, input) {
|
|
2246
2635
|
try {
|
|
2247
2636
|
return await withContextEnv(helpers.context.env, async () => {
|
|
2248
|
-
const level = input.bump ?? "patch";
|
|
2249
2637
|
const root = resolveProjectRootOrThrow("release", helpers.cwd());
|
|
2250
2638
|
const session = resolveTreeseedWorkflowSession(root);
|
|
2251
2639
|
const gitRoot = session.gitRoot;
|
|
@@ -2253,67 +2641,58 @@ async function workflowRelease(helpers, input) {
|
|
|
2253
2641
|
const executionMode = normalizeExecutionMode(input);
|
|
2254
2642
|
const rootRepo = createWorkspaceRootRepoReport(root);
|
|
2255
2643
|
const packageReports = createWorkspacePackageReports(root);
|
|
2644
|
+
const explicitResumeRunId = helpers.context.workflow?.resumeRunId ?? null;
|
|
2645
|
+
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
2646
|
+
const planAutoResumeRun = executionMode === "plan" ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
2647
|
+
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
2648
|
+
const level = effectiveInput.bump ?? "patch";
|
|
2649
|
+
const isResume = Boolean(explicitResumeRunId || autoResumeRun);
|
|
2256
2650
|
const packageSelection = session.packageSelection;
|
|
2257
2651
|
const selectedPackageNames = new Set(packageSelection.selected);
|
|
2258
|
-
const blockers = [];
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
const versionPlan = planWorkspaceReleaseBump(level, root, mode === "recursive-workspace" ? { selectedPackageNames } : {});
|
|
2271
|
-
const plannedVersions = Object.fromEntries(versionPlan.versions.entries());
|
|
2652
|
+
const blockers = isResume ? [] : collectReleasePlanBlockers(session, mode, packageSelection.selected);
|
|
2653
|
+
const plannedRelease = buildReleasePlanSnapshot({
|
|
2654
|
+
root,
|
|
2655
|
+
mode,
|
|
2656
|
+
level,
|
|
2657
|
+
packageSelection,
|
|
2658
|
+
packageReports,
|
|
2659
|
+
rootRepo,
|
|
2660
|
+
blockers
|
|
2661
|
+
});
|
|
2272
2662
|
if (executionMode === "plan") {
|
|
2273
|
-
return buildWorkflowResult(
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
plannedSteps: [
|
|
2287
|
-
...packageReports.filter((report) => selectedPackageNames.has(report.name)).map((report) => ({
|
|
2288
|
-
id: `release-${report.name}`,
|
|
2289
|
-
description: `Release ${report.name} from staging to main and tag ${plannedVersions[report.name] ?? "(planned)"}`
|
|
2290
|
-
})),
|
|
2291
|
-
{ id: "release-root", description: `Release market ${plannedVersions["@treeseed/market"] ?? "(planned)"}` }
|
|
2292
|
-
],
|
|
2293
|
-
blockers
|
|
2294
|
-
},
|
|
2295
|
-
{
|
|
2296
|
-
executionMode,
|
|
2297
|
-
nextSteps: createNextSteps([
|
|
2298
|
-
{ operation: "release", reason: "Run without --plan to promote staging into production.", input: { bump: level } }
|
|
2299
|
-
])
|
|
2300
|
-
}
|
|
2301
|
-
);
|
|
2663
|
+
return buildWorkflowResult("release", root, {
|
|
2664
|
+
...plannedRelease,
|
|
2665
|
+
autoResumeCandidate: planAutoResumeRun ? {
|
|
2666
|
+
runId: planAutoResumeRun.runId,
|
|
2667
|
+
branch: planAutoResumeRun.session.branchName,
|
|
2668
|
+
failure: planAutoResumeRun.failure
|
|
2669
|
+
} : null
|
|
2670
|
+
}, {
|
|
2671
|
+
executionMode,
|
|
2672
|
+
nextSteps: createNextSteps([
|
|
2673
|
+
{ operation: "release", reason: planAutoResumeRun ? `Run without --plan to resume ${planAutoResumeRun.runId}.` : "Run without --plan to promote staging into production.", input: { bump: level } }
|
|
2674
|
+
])
|
|
2675
|
+
});
|
|
2302
2676
|
}
|
|
2303
2677
|
if (blockers.length > 0) {
|
|
2304
2678
|
workflowError("release", "validation_failed", blockers.join("\n"), {
|
|
2305
2679
|
details: { blockers }
|
|
2306
2680
|
});
|
|
2307
2681
|
}
|
|
2308
|
-
assertSessionBranchSafety("release", session);
|
|
2309
|
-
prepareReleaseBranches(root);
|
|
2310
|
-
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
2311
|
-
runWorkspaceSavePreflight({ cwd: root });
|
|
2312
2682
|
const workflowRun = acquireWorkflowRun(
|
|
2313
2683
|
"release",
|
|
2314
2684
|
session,
|
|
2315
|
-
{
|
|
2685
|
+
{
|
|
2686
|
+
bump: level,
|
|
2687
|
+
devTagCleanup: effectiveInput.devTagCleanup ?? "safe-after-release",
|
|
2688
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2689
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2690
|
+
workspaceLinks: effectiveInput.workspaceLinks ?? "auto"
|
|
2691
|
+
},
|
|
2316
2692
|
[
|
|
2693
|
+
{ id: "release-plan", description: "Record release plan", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2694
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2695
|
+
...mode === "recursive-workspace" ? [{ id: "prepare-release-metadata", description: "Rewrite stable release metadata", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }] : [],
|
|
2317
2696
|
...packageReports.filter((report) => selectedPackageNames.has(report.name)).map((report) => ({
|
|
2318
2697
|
id: `release-${report.name}`,
|
|
2319
2698
|
description: `Release ${report.name}`,
|
|
@@ -2322,26 +2701,48 @@ async function workflowRelease(helpers, input) {
|
|
|
2322
2701
|
branch: STAGING_BRANCH,
|
|
2323
2702
|
resumable: true
|
|
2324
2703
|
})),
|
|
2325
|
-
{ id: "release-root", description: "Release market repo", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }
|
|
2704
|
+
{ id: "release-root", description: "Release market repo", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2705
|
+
...mode === "recursive-workspace" ? [{ id: "cleanup-dev-tags", description: "Clean replaced dev package tags", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }] : []
|
|
2326
2706
|
],
|
|
2327
|
-
|
|
2707
|
+
autoResumeRun ? {
|
|
2708
|
+
...helpers.context,
|
|
2709
|
+
workflow: {
|
|
2710
|
+
...helpers.context.workflow ?? {},
|
|
2711
|
+
resumeRunId: autoResumeRun.runId
|
|
2712
|
+
}
|
|
2713
|
+
} : helpers.context
|
|
2328
2714
|
);
|
|
2715
|
+
if (autoResumeRun) {
|
|
2716
|
+
helpers.write(`[workflow][resume] Resuming interrupted release ${autoResumeRun.runId} on ${STAGING_BRANCH}.`);
|
|
2717
|
+
}
|
|
2329
2718
|
try {
|
|
2719
|
+
const releasePlan = await executeJournalStep(root, workflowRun.runId, "release-plan", () => plannedRelease);
|
|
2720
|
+
const effectivePackageSelection = releasePlanPackageSelection(releasePlan.packageSelection);
|
|
2721
|
+
const effectiveSelectedPackageNames = new Set(effectivePackageSelection.selected);
|
|
2722
|
+
const effectiveVersions = releasePlanVersionMap(releasePlan.plannedVersions);
|
|
2723
|
+
const rootVersion = String(releasePlan.rootVersion);
|
|
2724
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
2725
|
+
assertReleaseGitHubAutomationReady(root, effectiveSelectedPackageNames);
|
|
2726
|
+
if (!isResume) {
|
|
2727
|
+
assertSessionBranchSafety("release", session, { requireCleanPackages: true, requireCurrentBranch: true });
|
|
2728
|
+
assertCleanWorktree(root);
|
|
2729
|
+
}
|
|
2730
|
+
prepareReleaseBranches(root);
|
|
2731
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
2732
|
+
await executeJournalStep(root, workflowRun.runId, "workspace-unlink", () => unlinkWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto"));
|
|
2330
2733
|
if (mode === "root-only") {
|
|
2331
2734
|
const rootRelease2 = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
2332
|
-
|
|
2333
|
-
const rootVersion = bumpRootPackageJson(root, level);
|
|
2735
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2334
2736
|
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
2335
|
-
|
|
2336
|
-
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
2737
|
+
commitAllIfChanged(gitRoot, `release: ${level} bump`);
|
|
2337
2738
|
pushBranch(gitRoot, STAGING_BRANCH);
|
|
2338
2739
|
const released = mergeStagingIntoMain(root);
|
|
2339
|
-
|
|
2340
|
-
run("git", ["push", "origin", rootVersion], { cwd: gitRoot });
|
|
2740
|
+
const tag = ensureReleaseTag(gitRoot, rootVersion, released.commitSha);
|
|
2341
2741
|
syncBranchWithOrigin(gitRoot, STAGING_BRANCH);
|
|
2342
2742
|
return {
|
|
2343
2743
|
rootVersion,
|
|
2344
|
-
releasedCommit: released.commitSha
|
|
2744
|
+
releasedCommit: released.commitSha,
|
|
2745
|
+
tag
|
|
2345
2746
|
};
|
|
2346
2747
|
});
|
|
2347
2748
|
rootRepo.committed = true;
|
|
@@ -2350,22 +2751,27 @@ async function workflowRelease(helpers, input) {
|
|
|
2350
2751
|
rootRepo.branch = PRODUCTION_BRANCH;
|
|
2351
2752
|
rootRepo.commitSha = String(rootRelease2?.releasedCommit ?? headCommit(gitRoot));
|
|
2352
2753
|
rootRepo.tagName = String(rootRelease2?.rootVersion ?? "");
|
|
2754
|
+
const workspaceLinks2 = ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2353
2755
|
const payload2 = {
|
|
2354
2756
|
mode,
|
|
2355
2757
|
mergeStrategy: "merge-commit",
|
|
2356
2758
|
level,
|
|
2759
|
+
resumed: workflowRun.resumed,
|
|
2760
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2761
|
+
autoResumed: autoResumeRun != null,
|
|
2357
2762
|
rootVersion: String(rootRelease2?.rootVersion ?? ""),
|
|
2358
2763
|
releaseTag: String(rootRelease2?.rootVersion ?? ""),
|
|
2359
2764
|
releasedCommit: String(rootRelease2?.releasedCommit ?? rootRepo.commitSha ?? ""),
|
|
2360
2765
|
stagingBranch: STAGING_BRANCH,
|
|
2361
2766
|
productionBranch: PRODUCTION_BRANCH,
|
|
2362
|
-
touchedPackages: [
|
|
2767
|
+
touchedPackages: [],
|
|
2363
2768
|
packageSelection: { changed: [], dependents: [], selected: [] },
|
|
2364
2769
|
publishWait: [],
|
|
2365
2770
|
repos: [],
|
|
2366
2771
|
rootRepo,
|
|
2367
2772
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
2368
|
-
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true }
|
|
2773
|
+
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true },
|
|
2774
|
+
workspaceLinks: workspaceLinks2
|
|
2369
2775
|
};
|
|
2370
2776
|
completeWorkflowRun(root, workflowRun.runId, payload2);
|
|
2371
2777
|
return buildWorkflowResult("release", root, payload2, {
|
|
@@ -2375,18 +2781,39 @@ async function workflowRelease(helpers, input) {
|
|
|
2375
2781
|
])
|
|
2376
2782
|
});
|
|
2377
2783
|
}
|
|
2378
|
-
|
|
2379
|
-
validatePackageReleaseWorkflows(root, packageSelection.selected);
|
|
2784
|
+
validatePackageReleaseWorkflows(root, effectivePackageSelection.selected);
|
|
2380
2785
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2381
|
-
if (
|
|
2786
|
+
if (effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2382
2787
|
prepareReleaseBranches(pkg.dir);
|
|
2383
2788
|
}
|
|
2384
2789
|
}
|
|
2385
|
-
|
|
2790
|
+
const metadata = await executeJournalStep(root, workflowRun.runId, "prepare-release-metadata", () => {
|
|
2791
|
+
const releasedPackageDevTags2 = Object.fromEntries(
|
|
2792
|
+
checkedOutWorkspacePackageRepos(root).filter((pkg) => effectiveSelectedPackageNames.has(pkg.name)).map((pkg) => {
|
|
2793
|
+
const packageJson = JSON.parse(readFileSync(resolve(pkg.dir, "package.json"), "utf8"));
|
|
2794
|
+
return [pkg.name, String(packageJson.version ?? "")];
|
|
2795
|
+
}).filter(([, version]) => version.includes("-dev."))
|
|
2796
|
+
);
|
|
2797
|
+
const replacedDevReferences2 = rewriteProjectInternalDependenciesToStableVersions(root, effectiveVersions);
|
|
2798
|
+
applyStableWorkspaceVersionChanges(root, effectiveVersions);
|
|
2799
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2800
|
+
const releaseInstalls2 = [
|
|
2801
|
+
{ name: "@treeseed/market", ...runReleaseNpmInstall(root, { workspaceRoot: root }) }
|
|
2802
|
+
];
|
|
2803
|
+
assertNoInternalDevReferencesForRepo(root, root, effectiveSelectedPackageNames);
|
|
2804
|
+
return {
|
|
2805
|
+
releasedPackageDevTags: releasedPackageDevTags2,
|
|
2806
|
+
replacedDevReferences: replacedDevReferences2,
|
|
2807
|
+
releaseInstalls: releaseInstalls2
|
|
2808
|
+
};
|
|
2809
|
+
});
|
|
2810
|
+
const replacedDevReferences = Array.isArray(metadata?.replacedDevReferences) ? metadata.replacedDevReferences : [];
|
|
2811
|
+
const releaseInstalls = Array.isArray(metadata?.releaseInstalls) ? metadata.releaseInstalls : [];
|
|
2812
|
+
const releasedPackageDevTags = new Map(Object.entries(metadata?.releasedPackageDevTags ?? {}).map(([name, version]) => [name, String(version)]));
|
|
2386
2813
|
const publishWait = [];
|
|
2387
2814
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2388
2815
|
const report = findReportByName(packageReports, pkg.name);
|
|
2389
|
-
if (!report || !
|
|
2816
|
+
if (!report || !effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2390
2817
|
if (report) {
|
|
2391
2818
|
report.skippedReason = "unchanged";
|
|
2392
2819
|
}
|
|
@@ -2394,9 +2821,14 @@ async function workflowRelease(helpers, input) {
|
|
|
2394
2821
|
}
|
|
2395
2822
|
const releasedPackage = await executeJournalStep(root, workflowRun.runId, `release-${report.name}`, async () => {
|
|
2396
2823
|
checkoutBranch(pkg.dir, STAGING_BRANCH);
|
|
2824
|
+
releaseInstalls.push({
|
|
2825
|
+
name: pkg.name,
|
|
2826
|
+
...runReleaseNpmInstall(pkg.dir, { workspaceRoot: root })
|
|
2827
|
+
});
|
|
2828
|
+
assertNoInternalDevReferencesForRepo(root, pkg.dir, effectiveSelectedPackageNames);
|
|
2397
2829
|
if (hasMeaningfulChanges(pkg.dir)) {
|
|
2398
2830
|
run("git", ["add", "-A"], { cwd: pkg.dir });
|
|
2399
|
-
run("git", ["commit", "-m", `release: ${
|
|
2831
|
+
run("git", ["commit", "-m", `release: ${effectiveVersions.get(pkg.name)}`], { cwd: pkg.dir });
|
|
2400
2832
|
}
|
|
2401
2833
|
pushBranch(pkg.dir, STAGING_BRANCH);
|
|
2402
2834
|
const mergeResult = mergeBranchIntoTarget(pkg.dir, {
|
|
@@ -2405,18 +2837,19 @@ async function workflowRelease(helpers, input) {
|
|
|
2405
2837
|
message: `release: ${STAGING_BRANCH} -> ${PRODUCTION_BRANCH}`,
|
|
2406
2838
|
pushTarget: true
|
|
2407
2839
|
});
|
|
2408
|
-
const tagName = String(
|
|
2409
|
-
|
|
2410
|
-
run("git", ["push", "origin", tagName], { cwd: pkg.dir });
|
|
2840
|
+
const tagName = String(effectiveVersions.get(pkg.name));
|
|
2841
|
+
const tag = ensureReleaseTag(pkg.dir, tagName, mergeResult.commitSha);
|
|
2411
2842
|
const publish = await waitForGitHubWorkflowCompletion(pkg.dir, {
|
|
2412
2843
|
workflow: "publish.yml",
|
|
2413
2844
|
headSha: mergeResult.commitSha,
|
|
2414
2845
|
branch: PRODUCTION_BRANCH
|
|
2415
2846
|
});
|
|
2847
|
+
assertReleaseGitHubWorkflowSucceeded(pkg.name, publish);
|
|
2416
2848
|
syncBranchWithOrigin(pkg.dir, STAGING_BRANCH);
|
|
2417
2849
|
return {
|
|
2418
2850
|
commitSha: mergeResult.commitSha,
|
|
2419
2851
|
tagName,
|
|
2852
|
+
tag,
|
|
2420
2853
|
publish
|
|
2421
2854
|
};
|
|
2422
2855
|
});
|
|
@@ -2432,11 +2865,11 @@ async function workflowRelease(helpers, input) {
|
|
|
2432
2865
|
...releasedPackage?.publish ?? {}
|
|
2433
2866
|
});
|
|
2434
2867
|
}
|
|
2868
|
+
assertNoInternalDevReferences(root, effectiveSelectedPackageNames);
|
|
2435
2869
|
const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
2436
|
-
|
|
2870
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2437
2871
|
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
2438
|
-
|
|
2439
|
-
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
2872
|
+
commitAllIfChanged(gitRoot, `release: ${level} bump`);
|
|
2440
2873
|
pushBranch(gitRoot, STAGING_BRANCH);
|
|
2441
2874
|
const released = mergeBranchIntoTarget(root, {
|
|
2442
2875
|
sourceBranch: STAGING_BRANCH,
|
|
@@ -2445,22 +2878,21 @@ async function workflowRelease(helpers, input) {
|
|
|
2445
2878
|
pushTarget: false
|
|
2446
2879
|
});
|
|
2447
2880
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2448
|
-
if (
|
|
2881
|
+
if (effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2449
2882
|
syncBranchWithOrigin(pkg.dir, PRODUCTION_BRANCH);
|
|
2450
2883
|
}
|
|
2451
2884
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
}
|
|
2456
|
-
run("git", ["tag", "-a", rootVersion, "-m", `release: ${rootVersion}`], { cwd: gitRoot });
|
|
2885
|
+
commitAllIfChanged(gitRoot, "release: sync package main heads");
|
|
2886
|
+
const releasedCommit = headCommit(gitRoot);
|
|
2887
|
+
const tag = ensureReleaseTag(gitRoot, rootVersion, releasedCommit);
|
|
2457
2888
|
run("git", ["push", "origin", PRODUCTION_BRANCH], { cwd: gitRoot });
|
|
2458
|
-
run("git", ["push", "origin", rootVersion], { cwd: gitRoot });
|
|
2459
2889
|
syncAllCheckedOutPackageRepos(root, STAGING_BRANCH);
|
|
2460
2890
|
syncBranchWithOrigin(gitRoot, STAGING_BRANCH);
|
|
2461
2891
|
return {
|
|
2462
2892
|
rootVersion,
|
|
2463
|
-
releasedCommit
|
|
2893
|
+
releasedCommit,
|
|
2894
|
+
mergeCommit: released.commitSha,
|
|
2895
|
+
tag
|
|
2464
2896
|
};
|
|
2465
2897
|
});
|
|
2466
2898
|
rootRepo.committed = true;
|
|
@@ -2469,17 +2901,48 @@ async function workflowRelease(helpers, input) {
|
|
|
2469
2901
|
rootRepo.branch = PRODUCTION_BRANCH;
|
|
2470
2902
|
rootRepo.commitSha = String(rootRelease?.releasedCommit ?? headCommit(gitRoot));
|
|
2471
2903
|
rootRepo.tagName = String(rootRelease?.rootVersion ?? "");
|
|
2904
|
+
const devTagCleanupMode = effectiveInput.devTagCleanup ?? "safe-after-release";
|
|
2905
|
+
const devTagCleanup = devTagCleanupMode === "off" ? (skipJournalStep(root, workflowRun.runId, "cleanup-dev-tags", { status: "skipped", reason: "disabled" }), { status: "skipped", reason: "disabled" }) : await executeJournalStep(root, workflowRun.runId, "cleanup-dev-tags", () => {
|
|
2906
|
+
const activeDevTags = collectActiveDevTagReferences(root);
|
|
2907
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
2908
|
+
for (const reference of replacedDevReferences) {
|
|
2909
|
+
const tagName = typeof reference.tagName === "string" ? reference.tagName : devTagFromDependencySpec(String(reference.from ?? ""));
|
|
2910
|
+
const packageName = typeof reference.packageName === "string" ? reference.packageName : null;
|
|
2911
|
+
if (!tagName || !packageName) continue;
|
|
2912
|
+
byPackage.set(packageName, [...byPackage.get(packageName) ?? [], tagName]);
|
|
2913
|
+
}
|
|
2914
|
+
for (const [packageName, tagName] of releasedPackageDevTags.entries()) {
|
|
2915
|
+
byPackage.set(packageName, [...byPackage.get(packageName) ?? [], tagName]);
|
|
2916
|
+
}
|
|
2917
|
+
const cleanupReports = [];
|
|
2918
|
+
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2919
|
+
const tagNames = byPackage.get(pkg.name) ?? [];
|
|
2920
|
+
if (tagNames.length === 0) continue;
|
|
2921
|
+
cleanupReports.push({
|
|
2922
|
+
name: pkg.name,
|
|
2923
|
+
...cleanupDevTags(pkg.dir, tagNames, activeDevTags)
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
return { status: "completed", repos: cleanupReports };
|
|
2927
|
+
});
|
|
2928
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2472
2929
|
const payload = {
|
|
2473
2930
|
mode,
|
|
2474
2931
|
mergeStrategy: "merge-commit",
|
|
2475
2932
|
level,
|
|
2933
|
+
resumed: workflowRun.resumed,
|
|
2934
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2935
|
+
autoResumed: autoResumeRun != null,
|
|
2476
2936
|
rootVersion: String(rootRelease?.rootVersion ?? ""),
|
|
2477
2937
|
releaseTag: String(rootRelease?.rootVersion ?? ""),
|
|
2478
2938
|
releasedCommit: String(rootRelease?.releasedCommit ?? rootRepo.commitSha ?? ""),
|
|
2479
2939
|
stagingBranch: STAGING_BRANCH,
|
|
2480
2940
|
productionBranch: PRODUCTION_BRANCH,
|
|
2481
|
-
touchedPackages:
|
|
2482
|
-
packageSelection,
|
|
2941
|
+
touchedPackages: effectivePackageSelection.selected,
|
|
2942
|
+
packageSelection: effectivePackageSelection,
|
|
2943
|
+
replacedDevReferences,
|
|
2944
|
+
releaseInstalls,
|
|
2945
|
+
devTagCleanup,
|
|
2483
2946
|
publishWait,
|
|
2484
2947
|
repos: packageReports,
|
|
2485
2948
|
rootRepo,
|
|
@@ -2488,21 +2951,18 @@ async function workflowRelease(helpers, input) {
|
|
|
2488
2951
|
stagingPushed: true,
|
|
2489
2952
|
productionPushed: true,
|
|
2490
2953
|
tagPushed: true
|
|
2491
|
-
}
|
|
2954
|
+
},
|
|
2955
|
+
workspaceLinks
|
|
2492
2956
|
};
|
|
2493
2957
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
2494
|
-
return buildWorkflowResult(
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
nextSteps: createNextSteps([
|
|
2501
|
-
{ operation: "status", reason: "Inspect release readiness and production state after the promotion." }
|
|
2502
|
-
])
|
|
2503
|
-
}
|
|
2504
|
-
);
|
|
2958
|
+
return buildWorkflowResult("release", root, payload, {
|
|
2959
|
+
runId: workflowRun.runId,
|
|
2960
|
+
nextSteps: createNextSteps([
|
|
2961
|
+
{ operation: "status", reason: "Inspect release readiness and production state after the promotion." }
|
|
2962
|
+
])
|
|
2963
|
+
});
|
|
2505
2964
|
} catch (error) {
|
|
2965
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2506
2966
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
2507
2967
|
resumable: true,
|
|
2508
2968
|
runId: workflowRun.runId,
|