@treeseed/sdk 0.6.7 → 0.6.9
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 +50 -23
- 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 +330 -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/prepare.js +14 -0
- package/dist/scripts/publish-package.js +5 -0
- package/dist/scripts/tenant-workflow-action.js +11 -2
- package/dist/verification.js +46 -13
- package/dist/workflow/operations.d.ts +381 -55
- package/dist/workflow/operations.js +725 -261
- 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 +59 -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,13 +708,68 @@ function workflowSessionSnapshot(session) {
|
|
|
555
708
|
function nextPendingJournalStep(journal) {
|
|
556
709
|
return journal.steps.find((step) => step.status === "pending") ?? null;
|
|
557
710
|
}
|
|
558
|
-
|
|
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
|
+
const releasePlan = stringRecord(journal.steps.find((step) => step.id === "release-plan")?.data);
|
|
756
|
+
const nextStep = nextPendingJournalStep(journal);
|
|
757
|
+
if (releaseRunHasCompletedMutation(journal)) {
|
|
758
|
+
if (nextStep?.id === "release-root" && releasePlanHead(releasePlan ?? {}, rootRepo.name) !== rootRepo.commitSha) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
return releasePlan ? releasePlanMatchesCurrentHeads(releasePlan, rootRepo, packageReports) : true;
|
|
764
|
+
}) ?? null;
|
|
765
|
+
}
|
|
766
|
+
async function executeJournalStep(root, runId, stepId, action, options = {}) {
|
|
559
767
|
const current = readWorkflowRunJournal(root, runId);
|
|
560
768
|
const step = current?.steps.find((entry) => entry.id === stepId) ?? null;
|
|
561
769
|
if (!current || !step) {
|
|
562
770
|
throw new Error(`Unknown workflow step "${stepId}" for run ${runId}.`);
|
|
563
771
|
}
|
|
564
|
-
if (step.status === "completed") {
|
|
772
|
+
if (step.status === "completed" && !options.rerunCompleted) {
|
|
565
773
|
return step.data ?? null;
|
|
566
774
|
}
|
|
567
775
|
const data = await Promise.resolve(action());
|
|
@@ -707,6 +915,149 @@ function validateStagingWorkflowContracts(root) {
|
|
|
707
915
|
});
|
|
708
916
|
}
|
|
709
917
|
}
|
|
918
|
+
function shouldSkipReleaseInstall() {
|
|
919
|
+
return process.env.TREESEED_GITHUB_AUTOMATION_MODE === "stub" || process.env.TREESEED_SAVE_NPM_INSTALL_MODE === "skip";
|
|
920
|
+
}
|
|
921
|
+
function runReleaseNpmInstall(repoDir, options = {}) {
|
|
922
|
+
if (shouldSkipReleaseInstall()) {
|
|
923
|
+
return { status: "skipped", reason: "stubbed" };
|
|
924
|
+
}
|
|
925
|
+
const args = repoDir === options.workspaceRoot ? ["install", "--package-lock-only", "--ignore-scripts"] : ["install", "--package-lock-only", "--ignore-scripts", "--workspaces=false"];
|
|
926
|
+
run("npm", args, { cwd: repoDir });
|
|
927
|
+
return { status: "completed", reason: null };
|
|
928
|
+
}
|
|
929
|
+
function pathIsWithin(parent, candidate) {
|
|
930
|
+
const path = relative(parent, candidate);
|
|
931
|
+
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
932
|
+
}
|
|
933
|
+
function assertNoInternalDevReferencesForRepo(root, repoDir, packageNames) {
|
|
934
|
+
const issues = collectInternalDevReferenceIssues(root, packageNames).filter((issue) => {
|
|
935
|
+
if (!pathIsWithin(repoDir, issue.filePath)) return false;
|
|
936
|
+
if (repoDir !== root) return true;
|
|
937
|
+
return !relative(root, issue.filePath).includes("/");
|
|
938
|
+
});
|
|
939
|
+
if (issues.length === 0) return;
|
|
940
|
+
const rendered = issues.map((issue) => `${issue.filePath}${issue.field ? ` ${issue.field}.${issue.dependencyName}` : ""}: ${issue.reason} ${issue.spec}`).join("\n");
|
|
941
|
+
throw new Error(`Stable release still contains internal Git/dev dependency references.
|
|
942
|
+
${rendered}`);
|
|
943
|
+
}
|
|
944
|
+
function collectActiveDevTagReferences(root) {
|
|
945
|
+
return collectInternalDevReferenceIssues(root).map((issue) => devTagFromDependencySpec(issue.spec) ?? (issue.spec.includes("-dev.") ? issue.spec : null)).filter((value) => Boolean(value));
|
|
946
|
+
}
|
|
947
|
+
function releasePlanVersionMap(plannedVersions) {
|
|
948
|
+
return new Map(
|
|
949
|
+
Object.entries(plannedVersions).filter(([name]) => name !== "@treeseed/market").map(([name, version]) => [name, String(version)])
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
function releasePlanPackageSelection(value) {
|
|
953
|
+
const record = value && typeof value === "object" ? value : {};
|
|
954
|
+
return {
|
|
955
|
+
changed: Array.isArray(record.changed) ? record.changed.map(String) : [],
|
|
956
|
+
dependents: Array.isArray(record.dependents) ? record.dependents.map(String) : [],
|
|
957
|
+
selected: Array.isArray(record.selected) ? record.selected.map(String) : []
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function buildReleasePlanSnapshot(input) {
|
|
961
|
+
const selectedPackageNames = new Set(input.packageSelection.selected);
|
|
962
|
+
const versionPlan = planWorkspaceReleaseBump(input.level, input.root, input.mode === "recursive-workspace" ? { selectedPackageNames } : {});
|
|
963
|
+
const rootVersion = planRootPackageVersion(input.root, input.level);
|
|
964
|
+
const plannedVersions = {
|
|
965
|
+
"@treeseed/market": rootVersion,
|
|
966
|
+
...Object.fromEntries(versionPlan.versions.entries())
|
|
967
|
+
};
|
|
968
|
+
const plannedDevReferenceRewrites = input.mode === "recursive-workspace" ? collectInternalDevReferenceIssues(input.root, selectedPackageNames) : [];
|
|
969
|
+
return {
|
|
970
|
+
mode: input.mode,
|
|
971
|
+
mergeStrategy: "merge-commit",
|
|
972
|
+
level: input.level,
|
|
973
|
+
rootVersion,
|
|
974
|
+
releaseTag: rootVersion,
|
|
975
|
+
stagingBranch: STAGING_BRANCH,
|
|
976
|
+
productionBranch: PRODUCTION_BRANCH,
|
|
977
|
+
packageSelection: input.packageSelection,
|
|
978
|
+
plannedVersions,
|
|
979
|
+
plannedDevReferenceRewrites,
|
|
980
|
+
plannedPublishWaits: input.packageSelection.selected.map((name) => ({
|
|
981
|
+
name,
|
|
982
|
+
workflow: "publish.yml",
|
|
983
|
+
branch: String(plannedVersions[name] ?? PRODUCTION_BRANCH),
|
|
984
|
+
status: "planned"
|
|
985
|
+
})),
|
|
986
|
+
touchedPackages: input.packageSelection.selected,
|
|
987
|
+
repos: input.packageReports,
|
|
988
|
+
rootRepo: input.rootRepo,
|
|
989
|
+
finalBranch: STAGING_BRANCH,
|
|
990
|
+
plannedSteps: [
|
|
991
|
+
{ id: "release-plan", description: "Record immutable release plan and target versions" },
|
|
992
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before stable release install" },
|
|
993
|
+
{ id: "prepare-release-metadata", description: "Rewrite package metadata and lockfiles to production dependency mode" },
|
|
994
|
+
...input.packageReports.filter((report) => selectedPackageNames.has(report.name)).map((report) => ({
|
|
995
|
+
id: `release-${report.name}`,
|
|
996
|
+
description: `Release ${report.name} from staging to main and tag ${plannedVersions[report.name] ?? "(planned)"}`
|
|
997
|
+
})),
|
|
998
|
+
{ id: "release-root", description: `Release market ${rootVersion}` },
|
|
999
|
+
{ id: "cleanup-dev-tags", description: "Clean replaced Treeseed dev tags after stable release" },
|
|
1000
|
+
{ id: "workspace-link", description: "Restore local workspace links after release syncs back to staging" }
|
|
1001
|
+
],
|
|
1002
|
+
blockers: input.blockers
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function collectReleasePlanBlockers(session, mode, selectedPackageNames) {
|
|
1006
|
+
const blockers = [];
|
|
1007
|
+
if (session.branchName !== STAGING_BRANCH) {
|
|
1008
|
+
blockers.push("Release must start from staging.");
|
|
1009
|
+
}
|
|
1010
|
+
if (session.rootRepo.dirty) {
|
|
1011
|
+
blockers.push("@treeseed/market has uncommitted changes.");
|
|
1012
|
+
}
|
|
1013
|
+
if (!session.rootRepo.hasOriginRemote) {
|
|
1014
|
+
blockers.push("@treeseed/market is missing origin remote.");
|
|
1015
|
+
}
|
|
1016
|
+
if (mode === "recursive-workspace") {
|
|
1017
|
+
for (const repo of session.packageRepos) {
|
|
1018
|
+
if (!selectedPackageNames.includes(repo.name)) continue;
|
|
1019
|
+
if (repo.detached) blockers.push(`${repo.name} is detached.`);
|
|
1020
|
+
if (repo.branchName !== STAGING_BRANCH) blockers.push(`${repo.name} is on ${repo.branchName ?? "(detached)"} instead of staging.`);
|
|
1021
|
+
if (repo.dirty) blockers.push(`${repo.name} has uncommitted changes.`);
|
|
1022
|
+
if (!repo.hasOriginRemote) blockers.push(`${repo.name} is missing origin remote.`);
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
validatePackageReleaseWorkflows(session.root, selectedPackageNames);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return blockers;
|
|
1031
|
+
}
|
|
1032
|
+
function assertReleaseGitHubAutomationReady(root, selectedPackageNames) {
|
|
1033
|
+
if (process.env.TREESEED_GITHUB_AUTOMATION_MODE === "stub") {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
createGitHubApiClient();
|
|
1037
|
+
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
1038
|
+
if (!selectedPackageNames.has(pkg.name)) continue;
|
|
1039
|
+
resolveGitHubRepositorySlug(pkg.dir);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function assertReleaseGitHubWorkflowSucceeded(packageName, workflow) {
|
|
1043
|
+
if (!workflow || workflow.status !== "completed") {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (workflow.conclusion === "success") {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const workflowName = typeof workflow.workflow === "string" ? workflow.workflow : "publish.yml";
|
|
1050
|
+
const repository = typeof workflow.repository === "string" ? workflow.repository : packageName;
|
|
1051
|
+
const url = typeof workflow.url === "string" && workflow.url ? `
|
|
1052
|
+
${workflow.url}` : "";
|
|
1053
|
+
const conclusion = typeof workflow.conclusion === "string" && workflow.conclusion ? workflow.conclusion : "unknown";
|
|
1054
|
+
workflowError("release", "github_workflow_failed", `${packageName} ${workflowName} completed with conclusion ${conclusion} in ${repository}.${url}`, {
|
|
1055
|
+
details: {
|
|
1056
|
+
packageName,
|
|
1057
|
+
workflow
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
710
1061
|
function assertSessionBranchSafety(operation, session, {
|
|
711
1062
|
requireCleanPackages = false,
|
|
712
1063
|
requireCurrentBranch = false,
|
|
@@ -1011,8 +1362,26 @@ function collectReleasePackageSelection(root) {
|
|
|
1011
1362
|
function hasStagedChanges(repoDir) {
|
|
1012
1363
|
return run("git", ["diff", "--cached", "--name-only"], { cwd: repoDir, capture: true }).trim().length > 0;
|
|
1013
1364
|
}
|
|
1014
|
-
async function workflowStatus(helpers) {
|
|
1015
|
-
return withContextEnv(helpers.context.env, () =>
|
|
1365
|
+
async function workflowStatus(helpers, input = {}) {
|
|
1366
|
+
return withContextEnv(helpers.context.env, async () => {
|
|
1367
|
+
const resolved = resolveTreeseedWorkflowPaths(helpers.cwd());
|
|
1368
|
+
if (resolved.tenantRoot) {
|
|
1369
|
+
try {
|
|
1370
|
+
await ensureTreeseedSecretSessionForConfig({
|
|
1371
|
+
tenantRoot: resolved.cwd,
|
|
1372
|
+
interactive: false,
|
|
1373
|
+
env: helpers.context.env,
|
|
1374
|
+
createIfMissing: false,
|
|
1375
|
+
allowMigration: false
|
|
1376
|
+
});
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return createStatusResult(helpers.cwd(), {
|
|
1381
|
+
...input,
|
|
1382
|
+
env: input.env ?? helpers.context.env
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1016
1385
|
}
|
|
1017
1386
|
async function workflowTasks(helpers) {
|
|
1018
1387
|
return withContextEnv(helpers.context.env, () => createTasksResult(helpers.cwd()));
|
|
@@ -1034,6 +1403,21 @@ async function workflowConfig(helpers, input = {}) {
|
|
|
1034
1403
|
const bootstrapSystemsInput = input.systems;
|
|
1035
1404
|
const skipUnavailable = input.skipUnavailable;
|
|
1036
1405
|
const bootstrapExecution = input.bootstrapExecution ?? "parallel";
|
|
1406
|
+
const dependencyInstall = await installTreeseedDependencies({
|
|
1407
|
+
tenantRoot,
|
|
1408
|
+
force: input.installMissingTooling === true,
|
|
1409
|
+
env: helpers.context.env,
|
|
1410
|
+
write: (line) => maybePrint(helpers.write, line)
|
|
1411
|
+
});
|
|
1412
|
+
if (!dependencyInstall.ok) {
|
|
1413
|
+
workflowError(
|
|
1414
|
+
"config",
|
|
1415
|
+
"validation_failed",
|
|
1416
|
+
`Treeseed dependency initialization failed:
|
|
1417
|
+
- ${formatTreeseedDependencyFailureDetails(dependencyInstall)}`,
|
|
1418
|
+
{ details: { dependencies: dependencyInstall } }
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1037
1421
|
const repairs = input.repair === false ? [] : resolveTreeseedWorkflowState(tenantRoot).deployConfigPresent ? applyTreeseedSafeRepairs(tenantRoot) : [];
|
|
1038
1422
|
const toolHealth = ensureTreeseedActVerificationTooling({
|
|
1039
1423
|
tenantRoot,
|
|
@@ -1367,6 +1751,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1367
1751
|
plannedSteps: [
|
|
1368
1752
|
{ id: "switch-root", description: `Switch market repo to ${branchName}` },
|
|
1369
1753
|
...packageReports.map((report) => ({ id: `switch-${report.name}`, description: `Mirror ${branchName} into ${report.name}` })),
|
|
1754
|
+
{ id: "workspace-link", description: "Apply local workspace links for integrated development" },
|
|
1370
1755
|
...preview ? [{ id: "preview", description: `Provision or refresh preview for ${branchName}` }] : []
|
|
1371
1756
|
]
|
|
1372
1757
|
},
|
|
@@ -1398,6 +1783,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1398
1783
|
branch: branchName,
|
|
1399
1784
|
resumable: true
|
|
1400
1785
|
})),
|
|
1786
|
+
{ id: "workspace-link", description: "Apply local workspace links", repoName: rootRepo.name, repoPath: rootRepo.path, branch: branchName, resumable: true },
|
|
1401
1787
|
...preview ? [{ id: "preview", description: `Provision or refresh preview ${branchName}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: branchName, resumable: true }] : []
|
|
1402
1788
|
],
|
|
1403
1789
|
helpers.context
|
|
@@ -1437,6 +1823,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1437
1823
|
report.commitSha = headCommit(pkg.dir);
|
|
1438
1824
|
report.dirty = hasMeaningfulChanges(pkg.dir);
|
|
1439
1825
|
}
|
|
1826
|
+
const workspaceLinks = await executeJournalStep(root, workflowRun.runId, "workspace-link", () => ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto"));
|
|
1440
1827
|
const stateAfterSwitch = resolveTreeseedWorkflowState(root);
|
|
1441
1828
|
if (preview) {
|
|
1442
1829
|
previewResult = await executeJournalStep(
|
|
@@ -1461,6 +1848,7 @@ async function workflowSwitch(helpers, input) {
|
|
|
1461
1848
|
lastDeploymentTimestamp: state.preview.lastDeploymentTimestamp
|
|
1462
1849
|
},
|
|
1463
1850
|
previewResult,
|
|
1851
|
+
workspaceLinks,
|
|
1464
1852
|
preconditions: {
|
|
1465
1853
|
cleanWorktreeRequired: true,
|
|
1466
1854
|
baseBranch: STAGING_BRANCH
|
|
@@ -1502,6 +1890,7 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1502
1890
|
workflowError("dev", "unsupported_transport", "Treeseed dev is not supported over the HTTP workflow API.");
|
|
1503
1891
|
}
|
|
1504
1892
|
const tenantRoot = resolveProjectRootOrThrow("dev", helpers.cwd());
|
|
1893
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(workspaceRoot(tenantRoot), helpers, input.workspaceLinks ?? "auto");
|
|
1505
1894
|
const readiness = ensureLocalReadinessOrThrow("dev", tenantRoot);
|
|
1506
1895
|
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "local", override: true });
|
|
1507
1896
|
assertTreeseedCommandEnvironment({ tenantRoot, scope: "local", purpose: "dev" });
|
|
@@ -1537,7 +1926,8 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1537
1926
|
apiBaseUrl: process.env.TREESEED_API_BASE_URL ?? "http://127.0.0.1:3000",
|
|
1538
1927
|
webUrl: "http://127.0.0.1:8787"
|
|
1539
1928
|
},
|
|
1540
|
-
readiness: readiness.readiness.local
|
|
1929
|
+
readiness: readiness.readiness.local,
|
|
1930
|
+
workspaceLinks
|
|
1541
1931
|
});
|
|
1542
1932
|
}
|
|
1543
1933
|
const result = spawnSync(process.execPath, args, {
|
|
@@ -1558,7 +1948,8 @@ async function workflowDev(helpers, input = {}) {
|
|
|
1558
1948
|
apiBaseUrl: process.env.TREESEED_API_BASE_URL ?? "http://127.0.0.1:3000",
|
|
1559
1949
|
webUrl: "http://127.0.0.1:8787"
|
|
1560
1950
|
},
|
|
1561
|
-
readiness: readiness.readiness.local
|
|
1951
|
+
readiness: readiness.readiness.local,
|
|
1952
|
+
workspaceLinks
|
|
1562
1953
|
});
|
|
1563
1954
|
});
|
|
1564
1955
|
} catch (error) {
|
|
@@ -1569,8 +1960,6 @@ async function workflowSave(helpers, input) {
|
|
|
1569
1960
|
try {
|
|
1570
1961
|
return await withContextEnv(helpers.context.env, async () => {
|
|
1571
1962
|
const tenantRoot = resolveProjectRootOrThrow("save", helpers.cwd());
|
|
1572
|
-
const message = ensureMessage("save", input.message, "a commit message");
|
|
1573
|
-
const optionsHotfix = input.hotfix === true;
|
|
1574
1963
|
const root = workspaceRoot(tenantRoot);
|
|
1575
1964
|
const session = resolveTreeseedWorkflowSession(root);
|
|
1576
1965
|
const gitRoot = session.gitRoot;
|
|
@@ -1580,6 +1969,12 @@ async function workflowSave(helpers, input) {
|
|
|
1580
1969
|
const recursiveWorkspace = session.mode === "recursive-workspace";
|
|
1581
1970
|
const mode = session.mode;
|
|
1582
1971
|
const executionMode = normalizeExecutionMode(input);
|
|
1972
|
+
const explicitResumeRunId = helpers.context.workflow?.resumeRunId ?? null;
|
|
1973
|
+
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId ? findAutoResumableSaveRun(root, branch) : null;
|
|
1974
|
+
const planAutoResumeRun = executionMode === "plan" ? findAutoResumableSaveRun(root, branch) : null;
|
|
1975
|
+
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
1976
|
+
const message = String(effectiveInput.message ?? "").trim();
|
|
1977
|
+
const optionsHotfix = effectiveInput.hotfix === true;
|
|
1583
1978
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope, override: true });
|
|
1584
1979
|
if (!branch) {
|
|
1585
1980
|
workflowError("save", "validation_failed", "Treeseed save requires an active git branch.");
|
|
@@ -1597,13 +1992,20 @@ async function workflowSave(helpers, input) {
|
|
|
1597
1992
|
if (branch === PRODUCTION_BRANCH && !optionsHotfix) {
|
|
1598
1993
|
blockers.push("Main saves require --hotfix.");
|
|
1599
1994
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1995
|
+
const repositoryPlan = planRepositorySave({
|
|
1996
|
+
root,
|
|
1997
|
+
gitRoot,
|
|
1998
|
+
branch,
|
|
1999
|
+
message,
|
|
2000
|
+
bump: effectiveInput.bump ?? "patch",
|
|
2001
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
2002
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
2003
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2004
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2005
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2006
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto"
|
|
2007
|
+
});
|
|
2008
|
+
const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
|
|
1607
2009
|
return buildWorkflowResult(
|
|
1608
2010
|
"save",
|
|
1609
2011
|
root,
|
|
@@ -1613,21 +2015,30 @@ async function workflowSave(helpers, input) {
|
|
|
1613
2015
|
scope,
|
|
1614
2016
|
hotfix: optionsHotfix,
|
|
1615
2017
|
message,
|
|
1616
|
-
repos:
|
|
1617
|
-
rootRepo,
|
|
2018
|
+
repos: repositoryPlan.repos,
|
|
2019
|
+
rootRepo: repositoryPlan.rootRepo,
|
|
1618
2020
|
blockers,
|
|
2021
|
+
autoResumeCandidate: planAutoResumeRun ? {
|
|
2022
|
+
runId: planAutoResumeRun.runId,
|
|
2023
|
+
branch: planAutoResumeRun.session.branchName,
|
|
2024
|
+
failure: planAutoResumeRun.failure
|
|
2025
|
+
} : null,
|
|
2026
|
+
workspaceLinks,
|
|
2027
|
+
repositoryPlan,
|
|
2028
|
+
waves: repositoryPlan.waves,
|
|
2029
|
+
plannedVersions: repositoryPlan.plannedVersions,
|
|
1619
2030
|
plannedSteps: [
|
|
1620
|
-
|
|
1621
|
-
...
|
|
1622
|
-
{ id: "
|
|
1623
|
-
{ id: "
|
|
1624
|
-
...beforeState.branchRole === "feature" && (
|
|
2031
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before deployment install and lockfile updates" },
|
|
2032
|
+
...repositoryPlan.plannedSteps,
|
|
2033
|
+
{ id: "lockfile-validation", description: "Validate refreshed package-lock.json files before any save commit is pushed" },
|
|
2034
|
+
{ id: "workspace-link", description: "Restore local workspace links after save" },
|
|
2035
|
+
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || beforeState.preview.enabled) ? [{ id: "preview", description: `Refresh preview deployment for ${branch}` }] : []
|
|
1625
2036
|
]
|
|
1626
2037
|
},
|
|
1627
2038
|
{
|
|
1628
2039
|
executionMode,
|
|
1629
2040
|
nextSteps: createNextSteps([
|
|
1630
|
-
{ operation: "save", reason: "Run without --plan to persist the workspace checkpoint.", input: { message, hotfix: optionsHotfix, preview:
|
|
2041
|
+
{ 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
2042
|
])
|
|
1632
2043
|
}
|
|
1633
2044
|
);
|
|
@@ -1646,44 +2057,28 @@ async function workflowSave(helpers, input) {
|
|
|
1646
2057
|
{
|
|
1647
2058
|
message,
|
|
1648
2059
|
hotfix: optionsHotfix,
|
|
1649
|
-
preview:
|
|
1650
|
-
refreshPreview:
|
|
1651
|
-
verify:
|
|
2060
|
+
preview: effectiveInput.preview === true,
|
|
2061
|
+
refreshPreview: effectiveInput.refreshPreview !== false,
|
|
2062
|
+
verify: effectiveInput.verify !== false,
|
|
2063
|
+
bump: effectiveInput.bump ?? "patch",
|
|
2064
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
2065
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
2066
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2067
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2068
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2069
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2070
|
+
workspaceLinks: effectiveInput.workspaceLinks ?? "auto"
|
|
1652
2071
|
},
|
|
1653
2072
|
[
|
|
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
2073
|
{
|
|
1679
|
-
id: "
|
|
1680
|
-
description:
|
|
2074
|
+
id: "save-repositories",
|
|
2075
|
+
description: "Save dependency-ordered repositories",
|
|
1681
2076
|
repoName: rootRepo.name,
|
|
1682
2077
|
repoPath: rootRepo.path,
|
|
1683
2078
|
branch,
|
|
1684
2079
|
resumable: true
|
|
1685
2080
|
},
|
|
1686
|
-
...beforeState.branchRole === "feature" && (
|
|
2081
|
+
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || effectiveInput.refreshPreview !== false && beforeState.preview.enabled) ? [{
|
|
1687
2082
|
id: "preview",
|
|
1688
2083
|
description: `Refresh preview ${branch}`,
|
|
1689
2084
|
repoName: rootRepo.name,
|
|
@@ -1692,105 +2087,66 @@ async function workflowSave(helpers, input) {
|
|
|
1692
2087
|
resumable: true
|
|
1693
2088
|
}] : []
|
|
1694
2089
|
],
|
|
1695
|
-
|
|
2090
|
+
autoResumeRun ? {
|
|
2091
|
+
...helpers.context,
|
|
2092
|
+
workflow: {
|
|
2093
|
+
...helpers.context.workflow ?? {},
|
|
2094
|
+
resumeRunId: autoResumeRun.runId
|
|
2095
|
+
}
|
|
2096
|
+
} : helpers.context
|
|
1696
2097
|
);
|
|
2098
|
+
if (autoResumeRun) {
|
|
2099
|
+
helpers.write(`[workflow][resume] Resuming interrupted save ${autoResumeRun.runId} on ${branch}.`);
|
|
2100
|
+
}
|
|
1697
2101
|
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) {
|
|
2102
|
+
const saveResult = await executeJournalStep(root, workflowRun.runId, "save-repositories", () => (async () => {
|
|
2103
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
1729
2104
|
try {
|
|
1730
|
-
await
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2105
|
+
return await runRepositorySaveOrchestrator({
|
|
2106
|
+
root,
|
|
2107
|
+
gitRoot,
|
|
2108
|
+
branch,
|
|
2109
|
+
message,
|
|
2110
|
+
bump: effectiveInput.bump ?? "patch",
|
|
2111
|
+
devVersionStrategy: effectiveInput.devVersionStrategy ?? "prerelease",
|
|
2112
|
+
devDependencyReferenceMode: effectiveInput.devDependencyReferenceMode ?? "git-tag",
|
|
2113
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2114
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2115
|
+
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "action-first"),
|
|
2116
|
+
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2117
|
+
workflowRunId: workflowRun.runId,
|
|
2118
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
1736
2119
|
});
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
"Treeseed save stopped while verifying the market workspace.",
|
|
1740
|
-
packageReports,
|
|
1741
|
-
rootRepo,
|
|
1742
|
-
null,
|
|
1743
|
-
error
|
|
1744
|
-
);
|
|
2120
|
+
} finally {
|
|
2121
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
1745
2122
|
}
|
|
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
|
-
}
|
|
2123
|
+
})());
|
|
2124
|
+
const savedPackageReports = saveResult?.repos ?? packageReports;
|
|
2125
|
+
const savedRootRepo = saveResult?.rootRepo ?? rootRepo;
|
|
2126
|
+
const head = savedRootRepo.commitSha ?? run("git", ["rev-parse", "HEAD"], { cwd: gitRoot, capture: true }).trim();
|
|
2127
|
+
const commitCreated = savedRootRepo.committed === true;
|
|
2128
|
+
const branchSync = {
|
|
2129
|
+
...savedRootRepo.publishWait ?? {},
|
|
2130
|
+
pushed: savedRootRepo.pushed === true
|
|
2131
|
+
};
|
|
2132
|
+
const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
|
|
2133
|
+
const commandReadiness = ensureTreeseedCommandReadiness(root);
|
|
2134
|
+
const lockfileValidation = {
|
|
2135
|
+
root: savedRootRepo.lockfileValidation,
|
|
2136
|
+
repos: savedPackageReports.map((repo) => ({
|
|
2137
|
+
name: repo.name,
|
|
2138
|
+
path: repo.path,
|
|
2139
|
+
lockfileValidation: repo.lockfileValidation
|
|
2140
|
+
}))
|
|
2141
|
+
};
|
|
1786
2142
|
let previewAction = { status: "skipped" };
|
|
1787
2143
|
if (beforeState.branchRole === "feature" && branch) {
|
|
1788
|
-
if (
|
|
2144
|
+
if (effectiveInput.preview === true) {
|
|
1789
2145
|
previewAction = {
|
|
1790
2146
|
status: beforeState.preview.enabled ? "refreshed" : "created",
|
|
1791
2147
|
details: await executeJournalStep(root, workflowRun.runId, "preview", () => deployBranchPreview(root, branch, helpers.context, { initialize: !beforeState.preview.enabled }))
|
|
1792
2148
|
};
|
|
1793
|
-
} else if (
|
|
2149
|
+
} else if (effectiveInput.refreshPreview !== false && beforeState.preview.enabled) {
|
|
1794
2150
|
previewAction = {
|
|
1795
2151
|
status: "refreshed",
|
|
1796
2152
|
details: await executeJournalStep(root, workflowRun.runId, "preview", () => deployBranchPreview(root, branch, helpers.context, { initialize: false }))
|
|
@@ -1798,20 +2154,28 @@ async function workflowSave(helpers, input) {
|
|
|
1798
2154
|
}
|
|
1799
2155
|
}
|
|
1800
2156
|
const payload = {
|
|
1801
|
-
mode,
|
|
2157
|
+
mode: saveResult?.mode ?? mode,
|
|
1802
2158
|
branch,
|
|
1803
2159
|
scope,
|
|
1804
2160
|
hotfix: optionsHotfix,
|
|
1805
2161
|
message,
|
|
2162
|
+
resumed: workflowRun.resumed,
|
|
2163
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2164
|
+
autoResumed: autoResumeRun != null,
|
|
1806
2165
|
commitSha: head,
|
|
1807
2166
|
commitCreated,
|
|
1808
|
-
noChanges: !
|
|
2167
|
+
noChanges: !commitCreated,
|
|
1809
2168
|
branchSync,
|
|
1810
|
-
repos:
|
|
1811
|
-
rootRepo,
|
|
2169
|
+
repos: savedPackageReports,
|
|
2170
|
+
rootRepo: savedRootRepo,
|
|
2171
|
+
waves: saveResult?.waves ?? [],
|
|
2172
|
+
plannedVersions: saveResult?.plannedVersions ?? {},
|
|
1812
2173
|
partialFailure: null,
|
|
1813
2174
|
previewAction,
|
|
1814
|
-
mergeConflict: null
|
|
2175
|
+
mergeConflict: null,
|
|
2176
|
+
workspaceLinks,
|
|
2177
|
+
commandReadiness,
|
|
2178
|
+
lockfileValidation
|
|
1815
2179
|
};
|
|
1816
2180
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
1817
2181
|
return buildWorkflowResult(
|
|
@@ -1826,7 +2190,9 @@ async function workflowSave(helpers, input) {
|
|
|
1826
2190
|
}
|
|
1827
2191
|
);
|
|
1828
2192
|
} catch (error) {
|
|
1829
|
-
const
|
|
2193
|
+
const saveError = repositorySaveErrorDetails(error);
|
|
2194
|
+
const savedPartialFailure = saveError.details?.partialFailure;
|
|
2195
|
+
const failingRepo = savedPartialFailure?.repos.find((report) => report.name === savedPartialFailure.failingRepo) ?? packageReports.find((report) => report.dirty && report.pushed !== true) ?? rootRepo;
|
|
1830
2196
|
const wrappedError = error instanceof TreeseedWorkflowError && error.details?.partialFailure != null ? error : new TreeseedWorkflowError(
|
|
1831
2197
|
"save",
|
|
1832
2198
|
error instanceof TreeseedWorkflowError ? error.code : "unsupported_state",
|
|
@@ -1834,7 +2200,8 @@ async function workflowSave(helpers, input) {
|
|
|
1834
2200
|
{
|
|
1835
2201
|
details: {
|
|
1836
2202
|
...error instanceof TreeseedWorkflowError ? error.details ?? {} : {},
|
|
1837
|
-
|
|
2203
|
+
...saveError.details ?? {},
|
|
2204
|
+
partialFailure: savedPartialFailure ?? {
|
|
1838
2205
|
message: "Treeseed save stopped before the workspace could finish syncing.",
|
|
1839
2206
|
failingRepo: failingRepo.name,
|
|
1840
2207
|
repos: packageReports,
|
|
@@ -1842,7 +2209,7 @@ async function workflowSave(helpers, input) {
|
|
|
1842
2209
|
error: error instanceof Error ? error.message : String(error)
|
|
1843
2210
|
}
|
|
1844
2211
|
},
|
|
1845
|
-
exitCode: error instanceof TreeseedWorkflowError ? error.exitCode :
|
|
2212
|
+
exitCode: error instanceof TreeseedWorkflowError ? error.exitCode : saveError.exitCode
|
|
1846
2213
|
}
|
|
1847
2214
|
);
|
|
1848
2215
|
failWorkflowRun(root, workflowRun.runId, wrappedError, {
|
|
@@ -1888,7 +2255,8 @@ async function workflowClose(helpers, input) {
|
|
|
1888
2255
|
...checkedOutWorkspacePackageRepos(root).map((pkg) => ({
|
|
1889
2256
|
id: `cleanup-${pkg.name}`,
|
|
1890
2257
|
description: `Archive and delete ${branchName ?? "(current task)"} in ${pkg.name}`
|
|
1891
|
-
}))
|
|
2258
|
+
})),
|
|
2259
|
+
{ id: "workspace-link", description: "Restore local workspace links on the final branch" }
|
|
1892
2260
|
]
|
|
1893
2261
|
},
|
|
1894
2262
|
{
|
|
@@ -1899,10 +2267,12 @@ async function workflowClose(helpers, input) {
|
|
|
1899
2267
|
}
|
|
1900
2268
|
);
|
|
1901
2269
|
}
|
|
2270
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1902
2271
|
const autoSave = await maybeAutoSaveCurrentTaskBranch(helpers, "close", {
|
|
1903
2272
|
message,
|
|
1904
2273
|
autoSave: input.autoSave
|
|
1905
2274
|
});
|
|
2275
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1906
2276
|
const activeSession = resolveTreeseedWorkflowSession(root);
|
|
1907
2277
|
const featureBranch = assertFeatureBranch(root);
|
|
1908
2278
|
const mode = activeSession.mode;
|
|
@@ -1967,6 +2337,7 @@ async function workflowClose(helpers, input) {
|
|
|
1967
2337
|
}));
|
|
1968
2338
|
Object.assign(report, cleanup);
|
|
1969
2339
|
}
|
|
2340
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1970
2341
|
const payload = {
|
|
1971
2342
|
mode,
|
|
1972
2343
|
branchName: featureBranch,
|
|
@@ -1979,7 +2350,8 @@ async function workflowClose(helpers, input) {
|
|
|
1979
2350
|
previewCleanup,
|
|
1980
2351
|
remoteDeleted: rootRepo.deletedRemote,
|
|
1981
2352
|
localDeleted: rootRepo.deletedLocal,
|
|
1982
|
-
finalBranch: currentBranch(repoDir) || STAGING_BRANCH
|
|
2353
|
+
finalBranch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2354
|
+
workspaceLinks
|
|
1983
2355
|
};
|
|
1984
2356
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
1985
2357
|
return buildWorkflowResult(
|
|
@@ -1994,6 +2366,7 @@ async function workflowClose(helpers, input) {
|
|
|
1994
2366
|
}
|
|
1995
2367
|
);
|
|
1996
2368
|
} catch (error) {
|
|
2369
|
+
ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
1997
2370
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
1998
2371
|
resumable: true,
|
|
1999
2372
|
runId: workflowRun.runId,
|
|
@@ -2042,14 +2415,17 @@ async function workflowStage(helpers, input) {
|
|
|
2042
2415
|
id: `merge-${pkg.name}`,
|
|
2043
2416
|
description: `Squash-merge ${initialSession.branchName ?? "(current task)"} into ${pkg.name} staging`
|
|
2044
2417
|
})),
|
|
2418
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before staging promotion" },
|
|
2045
2419
|
{ id: "merge-root", description: `Squash-merge ${initialSession.branchName ?? "(current task)"} into market staging` },
|
|
2420
|
+
{ id: "lockfile-validation", description: "Refresh and validate the merged root workspace lockfile before pushing staging" },
|
|
2046
2421
|
{ id: "wait-staging", description: "Wait for staging automation" },
|
|
2047
2422
|
{ id: "preview-cleanup", description: "Destroy preview resources" },
|
|
2048
2423
|
{ id: "cleanup-root", description: "Archive and delete the task branch from market" },
|
|
2049
2424
|
...checkedOutWorkspacePackageRepos(root).map((pkg) => ({
|
|
2050
2425
|
id: `cleanup-${pkg.name}`,
|
|
2051
2426
|
description: `Archive and delete the task branch from ${pkg.name}`
|
|
2052
|
-
}))
|
|
2427
|
+
})),
|
|
2428
|
+
{ id: "workspace-link", description: "Restore local workspace links on staging" }
|
|
2053
2429
|
]
|
|
2054
2430
|
},
|
|
2055
2431
|
{
|
|
@@ -2060,10 +2436,12 @@ async function workflowStage(helpers, input) {
|
|
|
2060
2436
|
}
|
|
2061
2437
|
);
|
|
2062
2438
|
}
|
|
2439
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2063
2440
|
const autoSave = await maybeAutoSaveCurrentTaskBranch(helpers, "stage", {
|
|
2064
2441
|
message,
|
|
2065
2442
|
autoSave: input.autoSave
|
|
2066
2443
|
});
|
|
2444
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2067
2445
|
const session = resolveTreeseedWorkflowSession(root);
|
|
2068
2446
|
const featureBranch = assertFeatureBranch(root);
|
|
2069
2447
|
const mode = session.mode;
|
|
@@ -2107,6 +2485,7 @@ async function workflowStage(helpers, input) {
|
|
|
2107
2485
|
helpers.context
|
|
2108
2486
|
);
|
|
2109
2487
|
try {
|
|
2488
|
+
unlinkWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2110
2489
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2111
2490
|
const report = findReportByName(packageReports, pkg.name);
|
|
2112
2491
|
if (!report) {
|
|
@@ -2134,14 +2513,21 @@ async function workflowStage(helpers, input) {
|
|
|
2134
2513
|
});
|
|
2135
2514
|
}
|
|
2136
2515
|
}
|
|
2516
|
+
let rootMerge = null;
|
|
2137
2517
|
try {
|
|
2138
|
-
|
|
2518
|
+
rootMerge = await executeJournalStep(root, workflowRun.runId, "merge-root", async () => {
|
|
2139
2519
|
assertCleanWorktree(root);
|
|
2140
2520
|
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
2141
2521
|
run("git", ["merge", "--squash", featureBranch], { cwd: repoDir });
|
|
2142
2522
|
if (mode === "recursive-workspace") {
|
|
2143
2523
|
syncAllCheckedOutPackageRepos(root, STAGING_BRANCH);
|
|
2144
2524
|
}
|
|
2525
|
+
const lockfileSafety = await refreshAndValidateRootWorkspaceLockfileForSave({
|
|
2526
|
+
root,
|
|
2527
|
+
gitRoot: repoDir,
|
|
2528
|
+
branch: STAGING_BRANCH,
|
|
2529
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
2530
|
+
});
|
|
2145
2531
|
if (hasStagedChanges(repoDir) || hasMeaningfulChanges(repoDir)) {
|
|
2146
2532
|
run("git", ["add", "-A"], { cwd: repoDir });
|
|
2147
2533
|
run("git", ["commit", "-m", message], { cwd: repoDir });
|
|
@@ -2150,7 +2536,9 @@ async function workflowStage(helpers, input) {
|
|
|
2150
2536
|
return {
|
|
2151
2537
|
commitSha: headCommit(repoDir),
|
|
2152
2538
|
branch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2153
|
-
committed: hasMeaningfulChanges(repoDir) ? false : true
|
|
2539
|
+
committed: hasMeaningfulChanges(repoDir) ? false : true,
|
|
2540
|
+
lockfileValidation: lockfileSafety.lockfileValidation,
|
|
2541
|
+
lockfileInstall: lockfileSafety.install
|
|
2154
2542
|
};
|
|
2155
2543
|
});
|
|
2156
2544
|
rootRepo.merged = true;
|
|
@@ -2196,6 +2584,7 @@ async function workflowStage(helpers, input) {
|
|
|
2196
2584
|
}));
|
|
2197
2585
|
Object.assign(report, cleanup);
|
|
2198
2586
|
}
|
|
2587
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2199
2588
|
const payload = {
|
|
2200
2589
|
mode,
|
|
2201
2590
|
branchName: featureBranch,
|
|
@@ -2209,9 +2598,12 @@ async function workflowStage(helpers, input) {
|
|
|
2209
2598
|
rootRepo,
|
|
2210
2599
|
stagingWait,
|
|
2211
2600
|
previewCleanup,
|
|
2601
|
+
lockfileValidation: rootMerge?.lockfileValidation ?? null,
|
|
2602
|
+
lockfileInstall: rootMerge?.lockfileInstall ?? null,
|
|
2212
2603
|
remoteDeleted: rootRepo.deletedRemote,
|
|
2213
2604
|
localDeleted: rootRepo.deletedLocal,
|
|
2214
|
-
finalBranch: currentBranch(repoDir) || STAGING_BRANCH
|
|
2605
|
+
finalBranch: currentBranch(repoDir) || STAGING_BRANCH,
|
|
2606
|
+
workspaceLinks
|
|
2215
2607
|
};
|
|
2216
2608
|
completeWorkflowRun(root, workflowRun.runId, payload);
|
|
2217
2609
|
return buildWorkflowResult(
|
|
@@ -2227,6 +2619,7 @@ async function workflowStage(helpers, input) {
|
|
|
2227
2619
|
}
|
|
2228
2620
|
);
|
|
2229
2621
|
} catch (error) {
|
|
2622
|
+
ensureWorkflowWorkspaceLinks(root, helpers, input.workspaceLinks ?? "auto");
|
|
2230
2623
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
2231
2624
|
resumable: true,
|
|
2232
2625
|
runId: workflowRun.runId,
|
|
@@ -2245,7 +2638,6 @@ async function workflowStage(helpers, input) {
|
|
|
2245
2638
|
async function workflowRelease(helpers, input) {
|
|
2246
2639
|
try {
|
|
2247
2640
|
return await withContextEnv(helpers.context.env, async () => {
|
|
2248
|
-
const level = input.bump ?? "patch";
|
|
2249
2641
|
const root = resolveProjectRootOrThrow("release", helpers.cwd());
|
|
2250
2642
|
const session = resolveTreeseedWorkflowSession(root);
|
|
2251
2643
|
const gitRoot = session.gitRoot;
|
|
@@ -2253,67 +2645,58 @@ async function workflowRelease(helpers, input) {
|
|
|
2253
2645
|
const executionMode = normalizeExecutionMode(input);
|
|
2254
2646
|
const rootRepo = createWorkspaceRootRepoReport(root);
|
|
2255
2647
|
const packageReports = createWorkspacePackageReports(root);
|
|
2648
|
+
const explicitResumeRunId = helpers.context.workflow?.resumeRunId ?? null;
|
|
2649
|
+
const autoResumeRun = executionMode === "execute" && !explicitResumeRunId ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
2650
|
+
const planAutoResumeRun = executionMode === "plan" ? findAutoResumableReleaseRun(root, session.branchName, rootRepo, packageReports) : null;
|
|
2651
|
+
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
2652
|
+
const level = effectiveInput.bump ?? "patch";
|
|
2653
|
+
const isResume = Boolean(explicitResumeRunId || autoResumeRun);
|
|
2256
2654
|
const packageSelection = session.packageSelection;
|
|
2257
2655
|
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());
|
|
2656
|
+
const blockers = isResume ? [] : collectReleasePlanBlockers(session, mode, packageSelection.selected);
|
|
2657
|
+
const plannedRelease = buildReleasePlanSnapshot({
|
|
2658
|
+
root,
|
|
2659
|
+
mode,
|
|
2660
|
+
level,
|
|
2661
|
+
packageSelection,
|
|
2662
|
+
packageReports,
|
|
2663
|
+
rootRepo,
|
|
2664
|
+
blockers
|
|
2665
|
+
});
|
|
2272
2666
|
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
|
-
);
|
|
2667
|
+
return buildWorkflowResult("release", root, {
|
|
2668
|
+
...plannedRelease,
|
|
2669
|
+
autoResumeCandidate: planAutoResumeRun ? {
|
|
2670
|
+
runId: planAutoResumeRun.runId,
|
|
2671
|
+
branch: planAutoResumeRun.session.branchName,
|
|
2672
|
+
failure: planAutoResumeRun.failure
|
|
2673
|
+
} : null
|
|
2674
|
+
}, {
|
|
2675
|
+
executionMode,
|
|
2676
|
+
nextSteps: createNextSteps([
|
|
2677
|
+
{ operation: "release", reason: planAutoResumeRun ? `Run without --plan to resume ${planAutoResumeRun.runId}.` : "Run without --plan to promote staging into production.", input: { bump: level } }
|
|
2678
|
+
])
|
|
2679
|
+
});
|
|
2302
2680
|
}
|
|
2303
2681
|
if (blockers.length > 0) {
|
|
2304
2682
|
workflowError("release", "validation_failed", blockers.join("\n"), {
|
|
2305
2683
|
details: { blockers }
|
|
2306
2684
|
});
|
|
2307
2685
|
}
|
|
2308
|
-
assertSessionBranchSafety("release", session);
|
|
2309
|
-
prepareReleaseBranches(root);
|
|
2310
|
-
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
2311
|
-
runWorkspaceSavePreflight({ cwd: root });
|
|
2312
2686
|
const workflowRun = acquireWorkflowRun(
|
|
2313
2687
|
"release",
|
|
2314
2688
|
session,
|
|
2315
|
-
{
|
|
2689
|
+
{
|
|
2690
|
+
bump: level,
|
|
2691
|
+
devTagCleanup: effectiveInput.devTagCleanup ?? "safe-after-release",
|
|
2692
|
+
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
2693
|
+
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
2694
|
+
workspaceLinks: effectiveInput.workspaceLinks ?? "auto"
|
|
2695
|
+
},
|
|
2316
2696
|
[
|
|
2697
|
+
{ id: "release-plan", description: "Record release plan", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2698
|
+
{ id: "workspace-unlink", description: "Remove local workspace links before release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2699
|
+
...mode === "recursive-workspace" ? [{ id: "prepare-release-metadata", description: "Rewrite stable release metadata", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }] : [],
|
|
2317
2700
|
...packageReports.filter((report) => selectedPackageNames.has(report.name)).map((report) => ({
|
|
2318
2701
|
id: `release-${report.name}`,
|
|
2319
2702
|
description: `Release ${report.name}`,
|
|
@@ -2322,26 +2705,48 @@ async function workflowRelease(helpers, input) {
|
|
|
2322
2705
|
branch: STAGING_BRANCH,
|
|
2323
2706
|
resumable: true
|
|
2324
2707
|
})),
|
|
2325
|
-
{ id: "release-root", description: "Release market repo", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }
|
|
2708
|
+
{ id: "release-root", description: "Release market repo", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
2709
|
+
...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
2710
|
],
|
|
2327
|
-
|
|
2711
|
+
autoResumeRun ? {
|
|
2712
|
+
...helpers.context,
|
|
2713
|
+
workflow: {
|
|
2714
|
+
...helpers.context.workflow ?? {},
|
|
2715
|
+
resumeRunId: autoResumeRun.runId
|
|
2716
|
+
}
|
|
2717
|
+
} : helpers.context
|
|
2328
2718
|
);
|
|
2719
|
+
if (autoResumeRun) {
|
|
2720
|
+
helpers.write(`[workflow][resume] Resuming interrupted release ${autoResumeRun.runId} on ${STAGING_BRANCH}.`);
|
|
2721
|
+
}
|
|
2329
2722
|
try {
|
|
2723
|
+
const releasePlan = await executeJournalStep(root, workflowRun.runId, "release-plan", () => plannedRelease);
|
|
2724
|
+
const effectivePackageSelection = releasePlanPackageSelection(releasePlan.packageSelection);
|
|
2725
|
+
const effectiveSelectedPackageNames = new Set(effectivePackageSelection.selected);
|
|
2726
|
+
const effectiveVersions = releasePlanVersionMap(releasePlan.plannedVersions);
|
|
2727
|
+
const rootVersion = String(releasePlan.rootVersion);
|
|
2728
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
2729
|
+
assertReleaseGitHubAutomationReady(root, effectiveSelectedPackageNames);
|
|
2730
|
+
if (!isResume) {
|
|
2731
|
+
assertSessionBranchSafety("release", session, { requireCleanPackages: true, requireCurrentBranch: true });
|
|
2732
|
+
assertCleanWorktree(root);
|
|
2733
|
+
}
|
|
2734
|
+
prepareReleaseBranches(root);
|
|
2735
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
2736
|
+
await executeJournalStep(root, workflowRun.runId, "workspace-unlink", () => unlinkWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto"));
|
|
2330
2737
|
if (mode === "root-only") {
|
|
2331
2738
|
const rootRelease2 = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
2332
|
-
|
|
2333
|
-
const rootVersion = bumpRootPackageJson(root, level);
|
|
2739
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2334
2740
|
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
2335
|
-
|
|
2336
|
-
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
2741
|
+
commitAllIfChanged(gitRoot, `release: ${level} bump`);
|
|
2337
2742
|
pushBranch(gitRoot, STAGING_BRANCH);
|
|
2338
2743
|
const released = mergeStagingIntoMain(root);
|
|
2339
|
-
|
|
2340
|
-
run("git", ["push", "origin", rootVersion], { cwd: gitRoot });
|
|
2744
|
+
const tag = ensureReleaseTag(gitRoot, rootVersion, released.commitSha);
|
|
2341
2745
|
syncBranchWithOrigin(gitRoot, STAGING_BRANCH);
|
|
2342
2746
|
return {
|
|
2343
2747
|
rootVersion,
|
|
2344
|
-
releasedCommit: released.commitSha
|
|
2748
|
+
releasedCommit: released.commitSha,
|
|
2749
|
+
tag
|
|
2345
2750
|
};
|
|
2346
2751
|
});
|
|
2347
2752
|
rootRepo.committed = true;
|
|
@@ -2350,22 +2755,27 @@ async function workflowRelease(helpers, input) {
|
|
|
2350
2755
|
rootRepo.branch = PRODUCTION_BRANCH;
|
|
2351
2756
|
rootRepo.commitSha = String(rootRelease2?.releasedCommit ?? headCommit(gitRoot));
|
|
2352
2757
|
rootRepo.tagName = String(rootRelease2?.rootVersion ?? "");
|
|
2758
|
+
const workspaceLinks2 = ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2353
2759
|
const payload2 = {
|
|
2354
2760
|
mode,
|
|
2355
2761
|
mergeStrategy: "merge-commit",
|
|
2356
2762
|
level,
|
|
2763
|
+
resumed: workflowRun.resumed,
|
|
2764
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2765
|
+
autoResumed: autoResumeRun != null,
|
|
2357
2766
|
rootVersion: String(rootRelease2?.rootVersion ?? ""),
|
|
2358
2767
|
releaseTag: String(rootRelease2?.rootVersion ?? ""),
|
|
2359
2768
|
releasedCommit: String(rootRelease2?.releasedCommit ?? rootRepo.commitSha ?? ""),
|
|
2360
2769
|
stagingBranch: STAGING_BRANCH,
|
|
2361
2770
|
productionBranch: PRODUCTION_BRANCH,
|
|
2362
|
-
touchedPackages: [
|
|
2771
|
+
touchedPackages: [],
|
|
2363
2772
|
packageSelection: { changed: [], dependents: [], selected: [] },
|
|
2364
2773
|
publishWait: [],
|
|
2365
2774
|
repos: [],
|
|
2366
2775
|
rootRepo,
|
|
2367
2776
|
finalBranch: currentBranch(gitRoot) || STAGING_BRANCH,
|
|
2368
|
-
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true }
|
|
2777
|
+
pushStatus: { stagingPushed: true, productionPushed: true, tagPushed: true },
|
|
2778
|
+
workspaceLinks: workspaceLinks2
|
|
2369
2779
|
};
|
|
2370
2780
|
completeWorkflowRun(root, workflowRun.runId, payload2);
|
|
2371
2781
|
return buildWorkflowResult("release", root, payload2, {
|
|
@@ -2375,18 +2785,39 @@ async function workflowRelease(helpers, input) {
|
|
|
2375
2785
|
])
|
|
2376
2786
|
});
|
|
2377
2787
|
}
|
|
2378
|
-
|
|
2379
|
-
validatePackageReleaseWorkflows(root, packageSelection.selected);
|
|
2788
|
+
validatePackageReleaseWorkflows(root, effectivePackageSelection.selected);
|
|
2380
2789
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2381
|
-
if (
|
|
2790
|
+
if (effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2382
2791
|
prepareReleaseBranches(pkg.dir);
|
|
2383
2792
|
}
|
|
2384
2793
|
}
|
|
2385
|
-
|
|
2794
|
+
const metadata = await executeJournalStep(root, workflowRun.runId, "prepare-release-metadata", () => {
|
|
2795
|
+
const releasedPackageDevTags2 = Object.fromEntries(
|
|
2796
|
+
checkedOutWorkspacePackageRepos(root).filter((pkg) => effectiveSelectedPackageNames.has(pkg.name)).map((pkg) => {
|
|
2797
|
+
const packageJson = JSON.parse(readFileSync(resolve(pkg.dir, "package.json"), "utf8"));
|
|
2798
|
+
return [pkg.name, String(packageJson.version ?? "")];
|
|
2799
|
+
}).filter(([, version]) => version.includes("-dev."))
|
|
2800
|
+
);
|
|
2801
|
+
const replacedDevReferences2 = rewriteProjectInternalDependenciesToStableVersions(root, effectiveVersions);
|
|
2802
|
+
applyStableWorkspaceVersionChanges(root, effectiveVersions);
|
|
2803
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2804
|
+
const releaseInstalls2 = [
|
|
2805
|
+
{ name: "@treeseed/market", ...runReleaseNpmInstall(root, { workspaceRoot: root }) }
|
|
2806
|
+
];
|
|
2807
|
+
assertNoInternalDevReferencesForRepo(root, root, effectiveSelectedPackageNames);
|
|
2808
|
+
return {
|
|
2809
|
+
releasedPackageDevTags: releasedPackageDevTags2,
|
|
2810
|
+
replacedDevReferences: replacedDevReferences2,
|
|
2811
|
+
releaseInstalls: releaseInstalls2
|
|
2812
|
+
};
|
|
2813
|
+
}, { rerunCompleted: workflowRun.resumed });
|
|
2814
|
+
const replacedDevReferences = Array.isArray(metadata?.replacedDevReferences) ? metadata.replacedDevReferences : [];
|
|
2815
|
+
const releaseInstalls = Array.isArray(metadata?.releaseInstalls) ? metadata.releaseInstalls : [];
|
|
2816
|
+
const releasedPackageDevTags = new Map(Object.entries(metadata?.releasedPackageDevTags ?? {}).map(([name, version]) => [name, String(version)]));
|
|
2386
2817
|
const publishWait = [];
|
|
2387
2818
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2388
2819
|
const report = findReportByName(packageReports, pkg.name);
|
|
2389
|
-
if (!report || !
|
|
2820
|
+
if (!report || !effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2390
2821
|
if (report) {
|
|
2391
2822
|
report.skippedReason = "unchanged";
|
|
2392
2823
|
}
|
|
@@ -2394,9 +2825,14 @@ async function workflowRelease(helpers, input) {
|
|
|
2394
2825
|
}
|
|
2395
2826
|
const releasedPackage = await executeJournalStep(root, workflowRun.runId, `release-${report.name}`, async () => {
|
|
2396
2827
|
checkoutBranch(pkg.dir, STAGING_BRANCH);
|
|
2828
|
+
releaseInstalls.push({
|
|
2829
|
+
name: pkg.name,
|
|
2830
|
+
...runReleaseNpmInstall(pkg.dir, { workspaceRoot: root })
|
|
2831
|
+
});
|
|
2832
|
+
assertNoInternalDevReferencesForRepo(root, pkg.dir, effectiveSelectedPackageNames);
|
|
2397
2833
|
if (hasMeaningfulChanges(pkg.dir)) {
|
|
2398
2834
|
run("git", ["add", "-A"], { cwd: pkg.dir });
|
|
2399
|
-
run("git", ["commit", "-m", `release: ${
|
|
2835
|
+
run("git", ["commit", "-m", `release: ${effectiveVersions.get(pkg.name)}`], { cwd: pkg.dir });
|
|
2400
2836
|
}
|
|
2401
2837
|
pushBranch(pkg.dir, STAGING_BRANCH);
|
|
2402
2838
|
const mergeResult = mergeBranchIntoTarget(pkg.dir, {
|
|
@@ -2405,18 +2841,19 @@ async function workflowRelease(helpers, input) {
|
|
|
2405
2841
|
message: `release: ${STAGING_BRANCH} -> ${PRODUCTION_BRANCH}`,
|
|
2406
2842
|
pushTarget: true
|
|
2407
2843
|
});
|
|
2408
|
-
const tagName = String(
|
|
2409
|
-
|
|
2410
|
-
run("git", ["push", "origin", tagName], { cwd: pkg.dir });
|
|
2844
|
+
const tagName = String(effectiveVersions.get(pkg.name));
|
|
2845
|
+
const tag = ensureReleaseTag(pkg.dir, tagName, mergeResult.commitSha);
|
|
2411
2846
|
const publish = await waitForGitHubWorkflowCompletion(pkg.dir, {
|
|
2412
2847
|
workflow: "publish.yml",
|
|
2413
2848
|
headSha: mergeResult.commitSha,
|
|
2414
|
-
branch:
|
|
2849
|
+
branch: tagName
|
|
2415
2850
|
});
|
|
2851
|
+
assertReleaseGitHubWorkflowSucceeded(pkg.name, publish);
|
|
2416
2852
|
syncBranchWithOrigin(pkg.dir, STAGING_BRANCH);
|
|
2417
2853
|
return {
|
|
2418
2854
|
commitSha: mergeResult.commitSha,
|
|
2419
2855
|
tagName,
|
|
2856
|
+
tag,
|
|
2420
2857
|
publish
|
|
2421
2858
|
};
|
|
2422
2859
|
});
|
|
@@ -2432,11 +2869,11 @@ async function workflowRelease(helpers, input) {
|
|
|
2432
2869
|
...releasedPackage?.publish ?? {}
|
|
2433
2870
|
});
|
|
2434
2871
|
}
|
|
2872
|
+
assertNoInternalDevReferences(root, effectiveSelectedPackageNames);
|
|
2435
2873
|
const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
2436
|
-
|
|
2874
|
+
setRootPackageJsonVersion(root, rootVersion);
|
|
2437
2875
|
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
2438
|
-
|
|
2439
|
-
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
2876
|
+
commitAllIfChanged(gitRoot, `release: ${level} bump`);
|
|
2440
2877
|
pushBranch(gitRoot, STAGING_BRANCH);
|
|
2441
2878
|
const released = mergeBranchIntoTarget(root, {
|
|
2442
2879
|
sourceBranch: STAGING_BRANCH,
|
|
@@ -2445,22 +2882,21 @@ async function workflowRelease(helpers, input) {
|
|
|
2445
2882
|
pushTarget: false
|
|
2446
2883
|
});
|
|
2447
2884
|
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2448
|
-
if (
|
|
2885
|
+
if (effectiveSelectedPackageNames.has(pkg.name)) {
|
|
2449
2886
|
syncBranchWithOrigin(pkg.dir, PRODUCTION_BRANCH);
|
|
2450
2887
|
}
|
|
2451
2888
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
}
|
|
2456
|
-
run("git", ["tag", "-a", rootVersion, "-m", `release: ${rootVersion}`], { cwd: gitRoot });
|
|
2889
|
+
commitAllIfChanged(gitRoot, "release: sync package main heads");
|
|
2890
|
+
const releasedCommit = headCommit(gitRoot);
|
|
2891
|
+
const tag = ensureReleaseTag(gitRoot, rootVersion, releasedCommit);
|
|
2457
2892
|
run("git", ["push", "origin", PRODUCTION_BRANCH], { cwd: gitRoot });
|
|
2458
|
-
run("git", ["push", "origin", rootVersion], { cwd: gitRoot });
|
|
2459
2893
|
syncAllCheckedOutPackageRepos(root, STAGING_BRANCH);
|
|
2460
2894
|
syncBranchWithOrigin(gitRoot, STAGING_BRANCH);
|
|
2461
2895
|
return {
|
|
2462
2896
|
rootVersion,
|
|
2463
|
-
releasedCommit
|
|
2897
|
+
releasedCommit,
|
|
2898
|
+
mergeCommit: released.commitSha,
|
|
2899
|
+
tag
|
|
2464
2900
|
};
|
|
2465
2901
|
});
|
|
2466
2902
|
rootRepo.committed = true;
|
|
@@ -2469,17 +2905,48 @@ async function workflowRelease(helpers, input) {
|
|
|
2469
2905
|
rootRepo.branch = PRODUCTION_BRANCH;
|
|
2470
2906
|
rootRepo.commitSha = String(rootRelease?.releasedCommit ?? headCommit(gitRoot));
|
|
2471
2907
|
rootRepo.tagName = String(rootRelease?.rootVersion ?? "");
|
|
2908
|
+
const devTagCleanupMode = effectiveInput.devTagCleanup ?? "safe-after-release";
|
|
2909
|
+
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", () => {
|
|
2910
|
+
const activeDevTags = collectActiveDevTagReferences(root);
|
|
2911
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
2912
|
+
for (const reference of replacedDevReferences) {
|
|
2913
|
+
const tagName = typeof reference.tagName === "string" ? reference.tagName : devTagFromDependencySpec(String(reference.from ?? ""));
|
|
2914
|
+
const packageName = typeof reference.packageName === "string" ? reference.packageName : null;
|
|
2915
|
+
if (!tagName || !packageName) continue;
|
|
2916
|
+
byPackage.set(packageName, [...byPackage.get(packageName) ?? [], tagName]);
|
|
2917
|
+
}
|
|
2918
|
+
for (const [packageName, tagName] of releasedPackageDevTags.entries()) {
|
|
2919
|
+
byPackage.set(packageName, [...byPackage.get(packageName) ?? [], tagName]);
|
|
2920
|
+
}
|
|
2921
|
+
const cleanupReports = [];
|
|
2922
|
+
for (const pkg of checkedOutWorkspacePackageRepos(root)) {
|
|
2923
|
+
const tagNames = byPackage.get(pkg.name) ?? [];
|
|
2924
|
+
if (tagNames.length === 0) continue;
|
|
2925
|
+
cleanupReports.push({
|
|
2926
|
+
name: pkg.name,
|
|
2927
|
+
...cleanupDevTags(pkg.dir, tagNames, activeDevTags)
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
return { status: "completed", repos: cleanupReports };
|
|
2931
|
+
});
|
|
2932
|
+
const workspaceLinks = ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2472
2933
|
const payload = {
|
|
2473
2934
|
mode,
|
|
2474
2935
|
mergeStrategy: "merge-commit",
|
|
2475
2936
|
level,
|
|
2937
|
+
resumed: workflowRun.resumed,
|
|
2938
|
+
resumedRunId: workflowRun.resumed ? workflowRun.runId : null,
|
|
2939
|
+
autoResumed: autoResumeRun != null,
|
|
2476
2940
|
rootVersion: String(rootRelease?.rootVersion ?? ""),
|
|
2477
2941
|
releaseTag: String(rootRelease?.rootVersion ?? ""),
|
|
2478
2942
|
releasedCommit: String(rootRelease?.releasedCommit ?? rootRepo.commitSha ?? ""),
|
|
2479
2943
|
stagingBranch: STAGING_BRANCH,
|
|
2480
2944
|
productionBranch: PRODUCTION_BRANCH,
|
|
2481
|
-
touchedPackages:
|
|
2482
|
-
packageSelection,
|
|
2945
|
+
touchedPackages: effectivePackageSelection.selected,
|
|
2946
|
+
packageSelection: effectivePackageSelection,
|
|
2947
|
+
replacedDevReferences,
|
|
2948
|
+
releaseInstalls,
|
|
2949
|
+
devTagCleanup,
|
|
2483
2950
|
publishWait,
|
|
2484
2951
|
repos: packageReports,
|
|
2485
2952
|
rootRepo,
|
|
@@ -2488,21 +2955,18 @@ async function workflowRelease(helpers, input) {
|
|
|
2488
2955
|
stagingPushed: true,
|
|
2489
2956
|
productionPushed: true,
|
|
2490
2957
|
tagPushed: true
|
|
2491
|
-
}
|
|
2958
|
+
},
|
|
2959
|
+
workspaceLinks
|
|
2492
2960
|
};
|
|
2493
2961
|
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
|
-
);
|
|
2962
|
+
return buildWorkflowResult("release", root, payload, {
|
|
2963
|
+
runId: workflowRun.runId,
|
|
2964
|
+
nextSteps: createNextSteps([
|
|
2965
|
+
{ operation: "status", reason: "Inspect release readiness and production state after the promotion." }
|
|
2966
|
+
])
|
|
2967
|
+
});
|
|
2505
2968
|
} catch (error) {
|
|
2969
|
+
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
2506
2970
|
failWorkflowRun(root, workflowRun.runId, error, {
|
|
2507
2971
|
resumable: true,
|
|
2508
2972
|
runId: workflowRun.runId,
|