@treeseed/sdk 0.10.27 → 0.11.0
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/README.md +207 -6
- package/dist/capacity-provider.d.ts +3 -1
- package/dist/capacity-provider.js +25 -5
- package/dist/control-plane.d.ts +1 -0
- package/dist/control-plane.js +38 -13
- package/dist/db/market-schema.d.ts +8860 -6172
- package/dist/db/market-schema.js +108 -0
- package/dist/db/node-sqlite.js +7 -2
- package/dist/hosting/apps.d.ts +12 -0
- package/dist/hosting/apps.js +107 -0
- package/dist/hosting/builtins.d.ts +25 -0
- package/dist/hosting/builtins.js +791 -0
- package/dist/hosting/contracts.d.ts +207 -0
- package/dist/hosting/contracts.js +0 -0
- package/dist/hosting/graph.d.ts +192 -0
- package/dist/hosting/graph.js +1106 -0
- package/dist/hosting/index.d.ts +4 -0
- package/dist/hosting/index.js +4 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +71 -7
- package/dist/managed-dependencies.js +1 -2
- package/dist/market-client.d.ts +63 -3
- package/dist/market-client.js +83 -11
- package/dist/operations/services/bootstrap-runner.d.ts +3 -1
- package/dist/operations/services/bootstrap-runner.js +22 -2
- package/dist/operations/services/config-runtime.d.ts +10 -5
- package/dist/operations/services/config-runtime.js +209 -66
- package/dist/operations/services/deploy.d.ts +70 -7
- package/dist/operations/services/deploy.js +579 -64
- package/dist/operations/services/deployment-readiness.d.ts +30 -0
- package/dist/operations/services/deployment-readiness.js +175 -0
- package/dist/operations/services/git-workflow.d.ts +2 -1
- package/dist/operations/services/git-workflow.js +9 -3
- package/dist/operations/services/github-actions-verification.d.ts +1 -0
- package/dist/operations/services/github-actions-verification.js +1 -0
- package/dist/operations/services/github-api.js +1 -1
- package/dist/operations/services/github-automation.d.ts +1 -1
- package/dist/operations/services/github-automation.js +4 -3
- package/dist/operations/services/github-credentials.d.ts +13 -0
- package/dist/operations/services/github-credentials.js +58 -0
- package/dist/operations/services/hosted-service-checks.d.ts +63 -0
- package/dist/operations/services/hosted-service-checks.js +327 -0
- package/dist/operations/services/hub-provider-launch.js +3 -3
- package/dist/operations/services/live-hosted-service-checks.d.ts +25 -0
- package/dist/operations/services/live-hosted-service-checks.js +350 -0
- package/dist/operations/services/managed-host-security.js +1 -1
- package/dist/operations/services/operations-runner-smoke.d.ts +30 -0
- package/dist/operations/services/operations-runner-smoke.js +180 -0
- package/dist/operations/services/package-adapters.d.ts +95 -0
- package/dist/operations/services/package-adapters.js +288 -0
- package/dist/operations/services/package-reference-policy.d.ts +1 -0
- package/dist/operations/services/package-reference-policy.js +15 -2
- package/dist/operations/services/project-platform.d.ts +80 -22
- package/dist/operations/services/project-platform.js +49 -8
- package/dist/operations/services/project-web-monitor.js +26 -4
- package/dist/operations/services/railway-api.d.ts +88 -5
- package/dist/operations/services/railway-api.js +626 -35
- package/dist/operations/services/railway-deploy.d.ts +46 -40
- package/dist/operations/services/railway-deploy.js +261 -293
- package/dist/operations/services/release-candidate.d.ts +19 -0
- package/dist/operations/services/release-candidate.js +375 -38
- package/dist/operations/services/repository-save-orchestrator.d.ts +3 -1
- package/dist/operations/services/repository-save-orchestrator.js +279 -66
- package/dist/operations/services/runtime-tools.d.ts +1 -0
- package/dist/operations/services/runtime-tools.js +10 -9
- package/dist/operations/services/template-registry.js +14 -7
- package/dist/operations/services/verification-cache.d.ts +25 -0
- package/dist/operations/services/verification-cache.js +71 -0
- package/dist/operations/services/workspace-dependency-mode.js +9 -1
- package/dist/operations/services/workspace-save.js +1 -1
- package/dist/operations/services/workspace-tools.js +2 -1
- package/dist/platform/contracts.d.ts +32 -1
- package/dist/platform/deploy-config.js +73 -8
- package/dist/platform/env.yaml +163 -35
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +74 -5
- package/dist/platform/plugin.d.ts +9 -0
- package/dist/platform-operation-store.js +2 -2
- package/dist/platform-operations.js +1 -1
- package/dist/reconcile/bootstrap-systems.js +2 -2
- package/dist/reconcile/builtin-adapters.js +372 -189
- package/dist/reconcile/contracts.d.ts +9 -5
- package/dist/reconcile/desired-state.d.ts +1 -0
- package/dist/reconcile/desired-state.js +5 -5
- package/dist/reconcile/engine.d.ts +5 -2
- package/dist/reconcile/engine.js +53 -32
- package/dist/reconcile/index.d.ts +2 -0
- package/dist/reconcile/index.js +2 -0
- package/dist/reconcile/live-acceptance.d.ts +79 -0
- package/dist/reconcile/live-acceptance.js +1615 -0
- package/dist/reconcile/platform.d.ts +104 -0
- package/dist/reconcile/platform.js +100 -0
- package/dist/reconcile/state.js +4 -4
- package/dist/reconcile/units.js +2 -2
- package/dist/scripts/deployment-readiness.js +20 -0
- package/dist/scripts/generate-treedx-openapi-types.js +186 -0
- package/dist/scripts/operations-runner-smoke.js +16 -0
- package/dist/scripts/release-verify.js +4 -1
- package/dist/scripts/template-catalog.test.js +7 -7
- package/dist/scripts/tenant-workflow-action.js +10 -1
- package/dist/sdk-types.d.ts +172 -5
- package/dist/sdk-types.js +28 -3
- package/dist/sdk.d.ts +35 -24
- package/dist/sdk.js +186 -17
- package/dist/template-launch-requirements.js +9 -0
- package/dist/treedx/adapters.d.ts +6 -0
- package/dist/treedx/adapters.js +36 -0
- package/dist/treedx/client.d.ts +222 -0
- package/dist/treedx/client.js +871 -0
- package/dist/treedx/errors.d.ts +13 -0
- package/dist/treedx/errors.js +17 -0
- package/dist/treedx/federated-client.d.ts +27 -0
- package/dist/treedx/federated-client.js +158 -0
- package/dist/treedx/generated/openapi-types.d.ts +3558 -0
- package/dist/treedx/generated/openapi-types.js +0 -0
- package/dist/treedx/graph-adapter.d.ts +33 -0
- package/dist/treedx/graph-adapter.js +156 -0
- package/dist/treedx/index.d.ts +14 -0
- package/dist/treedx/index.js +48 -0
- package/dist/treedx/market-integration.d.ts +27 -0
- package/dist/treedx/market-integration.js +131 -0
- package/dist/treedx/ports.d.ts +166 -0
- package/dist/treedx/ports.js +231 -0
- package/dist/treedx/query-adapter.d.ts +19 -0
- package/dist/treedx/query-adapter.js +62 -0
- package/dist/treedx/registry-client.d.ts +11 -0
- package/dist/treedx/registry-client.js +19 -0
- package/dist/treedx/repository-adapter.d.ts +45 -0
- package/dist/treedx/repository-adapter.js +308 -0
- package/dist/treedx/sdk-integration.d.ts +27 -0
- package/dist/treedx/sdk-integration.js +63 -0
- package/dist/treedx/types.d.ts +1084 -0
- package/dist/treedx/types.js +8 -0
- package/dist/treedx/workspace-adapter.d.ts +27 -0
- package/dist/treedx/workspace-adapter.js +65 -0
- package/dist/treedx-backends.d.ts +218 -0
- package/dist/treedx-backends.js +632 -0
- package/dist/treedx-client.d.ts +86 -0
- package/dist/treedx-client.js +175 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +497 -138
- package/dist/workflow/operations.d.ts +119 -13
- package/dist/workflow/operations.js +309 -53
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.js +43 -26
- package/dist/workflow-support.d.ts +11 -3
- package/dist/workflow-support.js +67 -3
- package/dist/workflow.d.ts +5 -0
- package/drizzle/market/0004_treedx_market_integration.sql +99 -0
- package/package.json +34 -3
- package/templates/github/deploy-web.workflow.yml +39 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +0 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +0 -35
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +0 -4
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +0 -65
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +0 -22
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +0 -17
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +0 -26
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +0 -74
- package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +0 -9
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +0 -103
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
inspectTreeseedKeyAgentStatus,
|
|
19
19
|
loadTreeseedMachineConfig,
|
|
20
20
|
resolveTreeseedLaunchEnvironment,
|
|
21
|
+
resolveTreeseedMachineEnvironmentValues,
|
|
21
22
|
resolveTreeseedRemoteSession,
|
|
22
23
|
rotateTreeseedMachineKey,
|
|
23
24
|
setTreeseedRemoteSession,
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
syncBranchWithOrigin
|
|
70
71
|
} from "../operations/services/git-workflow.js";
|
|
71
72
|
import { resolveGitHubRepositorySlug } from "../operations/services/github-automation.js";
|
|
73
|
+
import { resolveGitHubCredentialForRepository } from "../operations/services/github-credentials.js";
|
|
72
74
|
import { dispatchGitHubWorkflowRun } from "../operations/services/github-api.js";
|
|
73
75
|
import {
|
|
74
76
|
formatGitHubActionsGateFailure,
|
|
@@ -106,12 +108,14 @@ import {
|
|
|
106
108
|
repositorySaveErrorDetails,
|
|
107
109
|
runRepositorySaveOrchestrator
|
|
108
110
|
} from "../operations/services/repository-save-orchestrator.js";
|
|
111
|
+
import { discoverTreeseedPackageAdapters } from "../operations/services/package-adapters.js";
|
|
109
112
|
import {
|
|
110
113
|
assertNoInternalDevReferences,
|
|
111
114
|
cleanupStaleTreeseedDevTags,
|
|
112
115
|
collectTreeseedDevTagCleanupPlan,
|
|
113
116
|
collectInternalDevReferenceIssues,
|
|
114
117
|
devTagFromDependencySpec,
|
|
118
|
+
installableInternalDependencyVersions,
|
|
115
119
|
normalizeGitRemoteForManifest,
|
|
116
120
|
rewriteProjectInternalDependenciesToStableVersions
|
|
117
121
|
} from "../operations/services/package-reference-policy.js";
|
|
@@ -130,6 +134,10 @@ import {
|
|
|
130
134
|
workspaceRoot
|
|
131
135
|
} from "../operations/services/workspace-tools.js";
|
|
132
136
|
import { runTreeseedHostingAudit } from "../operations/services/hosting-audit.js";
|
|
137
|
+
import { collectTreeseedHostedServiceChecks } from "../operations/services/hosted-service-checks.js";
|
|
138
|
+
import { collectTreeseedDeploymentReadiness } from "../operations/services/deployment-readiness.js";
|
|
139
|
+
import { collectTreeseedLiveHostedServiceChecks } from "../operations/services/live-hosted-service-checks.js";
|
|
140
|
+
import { discoverTreeseedApplications } from "../hosting/apps.js";
|
|
133
141
|
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
134
142
|
import { createTreeseedReconcileRegistry, deriveTreeseedDesiredUnits, filterTreeseedDesiredUnitsByBootstrapSystems, planTreeseedReconciliation, resolveTreeseedBootstrapSelection, reconcileTreeseedTarget } from "../reconcile/index.js";
|
|
135
143
|
import {
|
|
@@ -214,8 +222,10 @@ function readPackageScript(root, packageDir, scriptName) {
|
|
|
214
222
|
function ensureWorkflowWorkspacePackageArtifacts(root, helpers) {
|
|
215
223
|
const packages = [
|
|
216
224
|
{ name: "@treeseed/sdk", dir: "packages/sdk", artifacts: ["dist/workflow-support.js", "dist/plugin-default.js", "dist/platform/env.yaml"] },
|
|
225
|
+
{ name: "@treeseed/ui", dir: "packages/ui", artifacts: ["dist/index.js"] },
|
|
217
226
|
{ name: "@treeseed/agent", dir: "packages/agent", artifacts: ["dist/api/index.js", "dist/services/worker.js"] },
|
|
218
227
|
{ name: "@treeseed/core", dir: "packages/core", artifacts: ["dist/plugin-default.js"] },
|
|
228
|
+
{ name: "@treeseed/admin", dir: "packages/admin", artifacts: ["dist/plugin.js"] },
|
|
219
229
|
{ name: "@treeseed/cli", dir: "packages/cli", artifacts: ["dist/cli/main.js"] }
|
|
220
230
|
];
|
|
221
231
|
for (const entry of packages) {
|
|
@@ -307,9 +317,14 @@ function normalizeCiMode(mode, operation) {
|
|
|
307
317
|
if (mode === "hosted" || mode === "off") return mode;
|
|
308
318
|
return operation === "save" ? "off" : "hosted";
|
|
309
319
|
}
|
|
310
|
-
function
|
|
320
|
+
function normalizeSaveLane(lane) {
|
|
321
|
+
const value = lane ?? process.env.TREESEED_SAVE_LANE;
|
|
322
|
+
return value === "promotion" ? "promotion" : "fast";
|
|
323
|
+
}
|
|
324
|
+
function normalizeSaveCiMode(mode, branch, lane = "fast") {
|
|
311
325
|
if (mode === "hosted" || mode === "off") return mode;
|
|
312
|
-
return branch === STAGING_BRANCH ? "hosted" : "off";
|
|
326
|
+
if (lane === "promotion") return branch === STAGING_BRANCH ? "hosted" : "off";
|
|
327
|
+
return "off";
|
|
313
328
|
}
|
|
314
329
|
function normalizeSaveVerifyMode(mode) {
|
|
315
330
|
switch (mode) {
|
|
@@ -329,8 +344,16 @@ function normalizeSaveVerifyMode(mode) {
|
|
|
329
344
|
return "skip";
|
|
330
345
|
}
|
|
331
346
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
347
|
+
function normalizeReleaseCandidateMode(mode, operation, lane = "fast") {
|
|
348
|
+
const value = mode ?? process.env.TREESEED_RELEASE_CANDIDATE_MODE;
|
|
349
|
+
if (value === "hybrid" || value === "strict" || value === "skip") {
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
if (operation === "save" && lane === "promotion") return "strict";
|
|
353
|
+
return operation === "save" ? "hybrid" : "strict";
|
|
354
|
+
}
|
|
355
|
+
function shouldUseHostedSaveCi(input, branch, lane = normalizeSaveLane(input.lane)) {
|
|
356
|
+
return normalizeSaveCiMode(input.ciMode, branch, lane) === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both" || input.verifyDeployedResources === true;
|
|
334
357
|
}
|
|
335
358
|
function worktreePayload(root, requestedMode) {
|
|
336
359
|
const metadata = managedWorkflowWorktreeMetadata(root);
|
|
@@ -412,6 +435,7 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
|
|
|
412
435
|
}
|
|
413
436
|
const result = await waitForGitHubActionsGate(gate, {
|
|
414
437
|
operation,
|
|
438
|
+
env: githubWorkflowGateEnv(options.root, gate),
|
|
415
439
|
onProgress: options.onProgress
|
|
416
440
|
});
|
|
417
441
|
const normalized = {
|
|
@@ -435,6 +459,23 @@ async function waitForWorkflowGates(operation, gates, ciMode, options = {}) {
|
|
|
435
459
|
}
|
|
436
460
|
return results;
|
|
437
461
|
}
|
|
462
|
+
function githubWorkflowGateEnv(root, gate) {
|
|
463
|
+
if (!root) return process.env;
|
|
464
|
+
try {
|
|
465
|
+
const repository = gate.repository ?? resolveGitHubRepositorySlug(gate.repoPath);
|
|
466
|
+
const scope = gate.branch === PRODUCTION_BRANCH ? "prod" : "staging";
|
|
467
|
+
const values = resolveTreeseedMachineEnvironmentValues(root, scope);
|
|
468
|
+
const credential = resolveGitHubCredentialForRepository(repository, { values, env: process.env });
|
|
469
|
+
if (!credential.token) return process.env;
|
|
470
|
+
return {
|
|
471
|
+
...process.env,
|
|
472
|
+
GH_TOKEN: credential.token,
|
|
473
|
+
GITHUB_TOKEN: credential.token
|
|
474
|
+
};
|
|
475
|
+
} catch {
|
|
476
|
+
return process.env;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
438
479
|
const HOSTED_DEPLOY_GATE_TIMEOUT_SECONDS = 45 * 60;
|
|
439
480
|
function hostedDeployGate(gate) {
|
|
440
481
|
return {
|
|
@@ -642,24 +683,134 @@ function buildWorkflowResult(operation, cwd, payload, options = {}) {
|
|
|
642
683
|
errors: options.errors ?? []
|
|
643
684
|
};
|
|
644
685
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
686
|
+
function parseGitStatusChangedPaths(status) {
|
|
687
|
+
return status.split("\n").map((line) => line.trimEnd()).filter(Boolean).map((line) => {
|
|
688
|
+
const value = line.slice(3).trim();
|
|
689
|
+
return value.includes(" -> ") ? value.split(" -> ").at(-1).trim() : value;
|
|
690
|
+
}).filter(Boolean);
|
|
691
|
+
}
|
|
692
|
+
function availableWorkflowAppIds(root) {
|
|
693
|
+
try {
|
|
694
|
+
const ids = discoverTreeseedApplications(root).map((app) => app.id);
|
|
695
|
+
return ids.length > 0 ? ids : ["web"];
|
|
696
|
+
} catch {
|
|
697
|
+
return ["web"];
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function selectWorkflowApplications(root, input = {}) {
|
|
701
|
+
const available = availableWorkflowAppIds(root);
|
|
702
|
+
const availableSet = new Set(available);
|
|
703
|
+
const selected = /* @__PURE__ */ new Set();
|
|
704
|
+
const reasons = [];
|
|
705
|
+
const add = (appId, reason) => {
|
|
706
|
+
if (!availableSet.has(appId)) return;
|
|
707
|
+
selected.add(appId);
|
|
708
|
+
reasons.push({ appId, reason });
|
|
709
|
+
};
|
|
710
|
+
const packages = [
|
|
711
|
+
...input.packageSelection?.selected ?? [],
|
|
712
|
+
...input.packageSelection?.changed ?? [],
|
|
713
|
+
...input.packageSelection?.dependents ?? []
|
|
714
|
+
];
|
|
715
|
+
const appByPackage = new Map(discoverTreeseedApplications(root).filter((app) => app.relativeRoot.startsWith("packages/")).map((app) => [`@treeseed/${app.relativeRoot.slice("packages/".length).split("/")[0]}`, app.id]));
|
|
716
|
+
for (const packageName of packages) {
|
|
717
|
+
if (packageName === "@treeseed/api" || packageName === "@treeseed/treedx" || packageName === "@treeseed/agent") {
|
|
718
|
+
add("api", `${packageName} changed`);
|
|
719
|
+
} else if (packageName === "@treeseed/core" || packageName === "@treeseed/ui" || packageName === "@treeseed/admin") {
|
|
720
|
+
add("web", `${packageName} changed`);
|
|
721
|
+
} else if (packageName === "@treeseed/sdk" || packageName === "@treeseed/cli") {
|
|
722
|
+
add("web", `${packageName} is shared`);
|
|
723
|
+
add("api", `${packageName} is shared`);
|
|
724
|
+
}
|
|
725
|
+
const packageAppId = appByPackage.get(packageName);
|
|
726
|
+
if (packageAppId) add(packageAppId, `${packageName} owns ${packageAppId}`);
|
|
727
|
+
}
|
|
728
|
+
const changedPaths = input.changedPaths ?? parseGitStatusChangedPaths(gitStatusPorcelain(root));
|
|
729
|
+
const appByPackagePath = new Map(discoverTreeseedApplications(root).filter((app) => app.relativeRoot.startsWith("packages/")).map((app) => [app.relativeRoot, app.id]));
|
|
730
|
+
for (const file of changedPaths) {
|
|
731
|
+
if (file.startsWith("packages/api/") || file === "packages/api") {
|
|
732
|
+
add("api", `${file} is API-owned`);
|
|
733
|
+
} else if (file.startsWith("packages/treedx/") || file === "packages/treedx") {
|
|
734
|
+
add("api", `${file} is TreeDX implementation`);
|
|
735
|
+
} else if (file.startsWith("packages/core/") || file.startsWith("packages/ui/") || file.startsWith("packages/admin/") || file.startsWith("src/") || file.startsWith("content/") || file.startsWith("public/") || file === "treeseed.site.yaml") {
|
|
736
|
+
add("web", `${file} is web-owned`);
|
|
737
|
+
} else if (file.startsWith("packages/sdk/") || file.startsWith("packages/cli/") || file === "package.json" || file === "package-lock.json" || file.startsWith(".github/")) {
|
|
738
|
+
add("web", `${file} is shared workflow/config`);
|
|
739
|
+
add("api", `${file} is shared workflow/config`);
|
|
740
|
+
}
|
|
741
|
+
for (const [packageRoot, appId] of appByPackagePath) {
|
|
742
|
+
if (file === packageRoot || file.startsWith(`${packageRoot}/`)) {
|
|
743
|
+
add(appId, `${file} is ${appId}-owned`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
const source = packages.length > 0 ? "package-selection" : changedPaths.length > 0 ? "changed-paths" : "default";
|
|
748
|
+
const finalSelected = selected.size > 0 ? available.filter((appId) => selected.has(appId)) : available;
|
|
749
|
+
return {
|
|
750
|
+
selected: finalSelected,
|
|
751
|
+
skipped: available.filter((appId) => !finalSelected.includes(appId)).map((appId) => ({ appId, reason: "No changed files or selected packages target this application." })),
|
|
752
|
+
reasons,
|
|
753
|
+
source
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function singleSelectedWorkflowAppId(selection) {
|
|
757
|
+
return selection.selected.length === 1 ? selection.selected[0] : void 0;
|
|
758
|
+
}
|
|
759
|
+
async function runWorkflowHostedResourceVerification(operation, root, helpers, environment, options = { enabled: true }) {
|
|
760
|
+
if (!options.enabled) return null;
|
|
761
|
+
const target = environment === "prod" ? "prod" : environment === "local" ? "local" : "staging";
|
|
762
|
+
const readiness = collectTreeseedDeploymentReadiness({
|
|
763
|
+
tenantRoot: root,
|
|
764
|
+
environment: target,
|
|
765
|
+
appId: options.appId
|
|
766
|
+
});
|
|
767
|
+
if (options.strict && !readiness.ok) {
|
|
768
|
+
const failures = readiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`);
|
|
769
|
+
workflowError(operation, "validation_failed", `Deployment readiness failed for ${target}:
|
|
770
|
+
${failures.join("\n")}`, {
|
|
771
|
+
details: { readiness }
|
|
772
|
+
});
|
|
648
773
|
}
|
|
649
|
-
|
|
650
|
-
const report = await runTreeseedHostingAudit({
|
|
774
|
+
const hostingAudit = await runTreeseedHostingAudit({
|
|
651
775
|
tenantRoot: root,
|
|
652
776
|
environment,
|
|
653
777
|
repair: false,
|
|
778
|
+
hostKinds: options.appId === "api" ? ["repository"] : void 0,
|
|
654
779
|
env: helpers.context.env,
|
|
655
780
|
write: (line) => helpers.write(line)
|
|
656
781
|
});
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
782
|
+
const hostedServices = options.live ? await collectTreeseedLiveHostedServiceChecks({
|
|
783
|
+
tenantRoot: root,
|
|
784
|
+
target,
|
|
785
|
+
strict: options.strict === true,
|
|
786
|
+
requireLiveRailway: options.strict === true,
|
|
787
|
+
requireLiveHttp: options.strict === true,
|
|
788
|
+
appId: options.appId,
|
|
789
|
+
retry: { attempts: 12, intervalMs: 1e4 },
|
|
790
|
+
env: helpers.context.env
|
|
791
|
+
}) : collectTreeseedHostedServiceChecks({
|
|
792
|
+
tenantRoot: root,
|
|
793
|
+
target,
|
|
794
|
+
appId: options.appId
|
|
795
|
+
});
|
|
796
|
+
if (options.strict && !hostingAudit.ok) {
|
|
797
|
+
workflowError(operation, "validation_failed", `Hosting audit failed for ${hostingAudit.environment}: ${hostingAudit.blockers.join("\n")}`, {
|
|
798
|
+
details: { hostingAudit, hostedServices, readiness }
|
|
660
799
|
});
|
|
661
800
|
}
|
|
662
|
-
|
|
801
|
+
if (options.strict && hostedServices.summary.failed > 0) {
|
|
802
|
+
const failures = hostedServices.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.issues.join("; ") || check.description}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`);
|
|
803
|
+
workflowError(operation, "validation_failed", `Hosted service checks failed for ${hostedServices.target}:
|
|
804
|
+
${failures.join("\n")}`, {
|
|
805
|
+
details: { hostingAudit, hostedServices, readiness }
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
hostingAudit,
|
|
810
|
+
hostedServices,
|
|
811
|
+
readiness,
|
|
812
|
+
live: options.live === true
|
|
813
|
+
};
|
|
663
814
|
}
|
|
664
815
|
function normalizeExecutionMode(input) {
|
|
665
816
|
return input?.plan === true || input?.dryRun === true ? "plan" : "execute";
|
|
@@ -710,7 +861,8 @@ function writeJsonFile(path, value) {
|
|
|
710
861
|
`, "utf8");
|
|
711
862
|
}
|
|
712
863
|
function applyStableWorkspaceVersionChanges(root, versions) {
|
|
713
|
-
const
|
|
864
|
+
const dependencyVersions = installableInternalDependencyVersions(root, versions);
|
|
865
|
+
const stableGitReferences = stablePackageGitReferences(root, dependencyVersions);
|
|
714
866
|
for (const target of [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))]) {
|
|
715
867
|
const packageJsonPath = resolve(target.dir, "package.json");
|
|
716
868
|
if (!existsSync(packageJsonPath)) continue;
|
|
@@ -724,7 +876,7 @@ function applyStableWorkspaceVersionChanges(root, versions) {
|
|
|
724
876
|
for (const field of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
|
|
725
877
|
const values = packageJson[field];
|
|
726
878
|
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
727
|
-
for (const [dependencyName, version] of
|
|
879
|
+
for (const [dependencyName, version] of dependencyVersions.entries()) {
|
|
728
880
|
if (!(dependencyName in values)) continue;
|
|
729
881
|
const dependencySpec = stableGitReferences.get(dependencyName) ?? version;
|
|
730
882
|
if (String(values[dependencyName]) === dependencySpec) continue;
|
|
@@ -1064,7 +1216,7 @@ async function connectTreeseedMarketProject(helpers, tenantRoot, input, context)
|
|
|
1064
1216
|
principal: activeRemoteSession?.principal ?? null
|
|
1065
1217
|
});
|
|
1066
1218
|
}
|
|
1067
|
-
const runnerHostId = `
|
|
1219
|
+
const runnerHostId = `operations-runner:${projectId}`;
|
|
1068
1220
|
if (connectionResult.runnerToken) {
|
|
1069
1221
|
setTreeseedRemoteSession(tenantRoot, {
|
|
1070
1222
|
hostId: runnerHostId,
|
|
@@ -1182,14 +1334,34 @@ function findAutoResumableSaveRun(root, branch) {
|
|
|
1182
1334
|
}
|
|
1183
1335
|
return listInterruptedWorkflowRuns(root).find((journal) => journal.command === "save" && journal.resumable && journal.session.branchName === branch) ?? null;
|
|
1184
1336
|
}
|
|
1185
|
-
function
|
|
1186
|
-
return
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1337
|
+
function workflowFileExists(repoPath, workflow) {
|
|
1338
|
+
return existsSync(resolve(repoPath, ".github", "workflows", workflow));
|
|
1339
|
+
}
|
|
1340
|
+
function hostedWorkflowForSavedRepository(root, repo) {
|
|
1341
|
+
const adapterByPath = new Map(discoverTreeseedPackageAdapters(root).map((adapter) => [resolve(adapter.dir), adapter]));
|
|
1342
|
+
const adapterWorkflow = packageHostedVerifyWorkflow(adapterByPath.get(resolve(repo.path)));
|
|
1343
|
+
if (adapterWorkflow) return adapterWorkflow;
|
|
1344
|
+
if (repo.branch === STAGING_BRANCH && existsSync(resolve(repo.path, "treeseed.site.yaml")) && workflowFileExists(repo.path, "deploy.yml")) {
|
|
1345
|
+
return "deploy.yml";
|
|
1346
|
+
}
|
|
1347
|
+
return "verify.yml";
|
|
1348
|
+
}
|
|
1349
|
+
function gatesForSavedRepositoryReports(root, reports) {
|
|
1350
|
+
return reports.filter((repo) => repo.pushed && repo.commitSha && repo.branch && (repo.committed || repo.tagName)).map((repo) => {
|
|
1351
|
+
const workflow = hostedWorkflowForSavedRepository(root, repo);
|
|
1352
|
+
const gate = {
|
|
1353
|
+
name: repo.name,
|
|
1354
|
+
repoPath: repo.path,
|
|
1355
|
+
workflow,
|
|
1356
|
+
branch: String(repo.branch),
|
|
1357
|
+
headSha: String(repo.commitSha)
|
|
1358
|
+
};
|
|
1359
|
+
return /^deploy(?:[-.]|$)/u.test(workflow) ? hostedDeployGate(gate) : gate;
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
function packageHostedVerifyWorkflow(adapter) {
|
|
1363
|
+
const workflow = adapter?.metadata?.hostedVerifyWorkflow;
|
|
1364
|
+
return typeof workflow === "string" && workflow.trim() ? workflow.trim().replace(/^\.github\/workflows\//u, "") : null;
|
|
1193
1365
|
}
|
|
1194
1366
|
function gateForSavedRootReport(report, branch, scope) {
|
|
1195
1367
|
if (!branch || scope === "local" || !report.pushed || !report.commitSha) {
|
|
@@ -1779,13 +1951,15 @@ async function runReleaseCandidateForPlan(operation, root, plannedRelease, optio
|
|
|
1779
1951
|
root,
|
|
1780
1952
|
plannedVersions: { ...stableDependencyVersions, ...plannedVersions },
|
|
1781
1953
|
selectedPackageNames: packageSelection.selected,
|
|
1782
|
-
allowReuse: options.allowReuse
|
|
1954
|
+
allowReuse: options.allowReuse,
|
|
1955
|
+
mode: options.mode ?? normalizeReleaseCandidateMode(void 0, operation)
|
|
1783
1956
|
});
|
|
1784
1957
|
assertReleaseCandidatePassed(operation, report);
|
|
1785
1958
|
return report;
|
|
1786
1959
|
}
|
|
1787
1960
|
function buildReleasePlanSnapshot(input) {
|
|
1788
1961
|
const selectedPackageNames = new Set(input.packageSelection.selected);
|
|
1962
|
+
const applicationSelection = selectWorkflowApplications(input.root, { packageSelection: input.packageSelection });
|
|
1789
1963
|
const versionPlan = planWorkspaceReleaseBump(input.level, input.root, input.mode === "recursive-workspace" ? { selectedPackageNames, repairVersionLine: input.repairVersionLine === true, targetVersionLine: input.targetVersionLine } : {});
|
|
1790
1964
|
const plannedSelected = [...versionPlan.selected].filter((name) => versionPlan.versions.has(name));
|
|
1791
1965
|
const plannedChanged = input.repairVersionLine === true ? plannedSelected : Array.from(new Set(input.packageSelection.changed.filter((name) => plannedSelected.includes(name))));
|
|
@@ -1821,6 +1995,7 @@ function buildReleasePlanSnapshot(input) {
|
|
|
1821
1995
|
packageSelection: plannedPackageSelection,
|
|
1822
1996
|
plannedVersions,
|
|
1823
1997
|
stableDependencyVersions,
|
|
1998
|
+
applicationSelection,
|
|
1824
1999
|
plannedDevReferenceRewrites,
|
|
1825
2000
|
plannedPublishWaits: plannedPackageSelection.selected.map((name) => ({
|
|
1826
2001
|
name,
|
|
@@ -2554,6 +2729,7 @@ async function workflowConfig(helpers, input = {}) {
|
|
|
2554
2729
|
sync,
|
|
2555
2730
|
env: helpers.context.env,
|
|
2556
2731
|
checkConnections: bootstrapOnly || sync !== "none" || scopes.some((scope) => scope !== "local"),
|
|
2732
|
+
initializePersistent: bootstrapOnly,
|
|
2557
2733
|
systems: bootstrapSystemsInput,
|
|
2558
2734
|
skipUnavailable,
|
|
2559
2735
|
bootstrapExecution,
|
|
@@ -2916,6 +3092,9 @@ async function workflowSave(helpers, input) {
|
|
|
2916
3092
|
const planAutoResumeRun = executionMode === "plan" ? findAutoResumableSaveRun(root, branch) : null;
|
|
2917
3093
|
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
2918
3094
|
const message = String(effectiveInput.message ?? "").trim();
|
|
3095
|
+
const saveLane = normalizeSaveLane(effectiveInput.lane);
|
|
3096
|
+
const saveCiMode = normalizeSaveCiMode(effectiveInput.ciMode, branch, saveLane);
|
|
3097
|
+
const releaseCandidateMode = normalizeReleaseCandidateMode(effectiveInput.releaseCandidate, "save", saveLane);
|
|
2919
3098
|
const optionsHotfix = effectiveInput.hotfix === true;
|
|
2920
3099
|
const previewInitialized = branchPreviewInitialized(root, branch);
|
|
2921
3100
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope, override: true });
|
|
@@ -2948,6 +3127,7 @@ async function workflowSave(helpers, input) {
|
|
|
2948
3127
|
verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
|
|
2949
3128
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto"
|
|
2950
3129
|
});
|
|
3130
|
+
const applicationSelection = selectWorkflowApplications(root);
|
|
2951
3131
|
const workspaceLinks = inspectWorkspaceDependencyMode(root, { mode: effectiveInput.workspaceLinks ?? "auto", env: helpers.context.env });
|
|
2952
3132
|
return buildWorkflowResult(
|
|
2953
3133
|
"save",
|
|
@@ -2967,8 +3147,11 @@ async function workflowSave(helpers, input) {
|
|
|
2967
3147
|
failure: planAutoResumeRun.failure
|
|
2968
3148
|
} : null,
|
|
2969
3149
|
workspaceLinks,
|
|
2970
|
-
ciMode:
|
|
3150
|
+
ciMode: saveCiMode,
|
|
3151
|
+
lane: saveLane,
|
|
2971
3152
|
verifyMode: effectiveInput.verifyMode ?? "fast",
|
|
3153
|
+
releaseCandidateMode,
|
|
3154
|
+
applicationSelection,
|
|
2972
3155
|
...worktreePayload(root, effectiveInput.worktreeMode),
|
|
2973
3156
|
repositoryPlan,
|
|
2974
3157
|
waves: repositoryPlan.waves,
|
|
@@ -2977,8 +3160,8 @@ async function workflowSave(helpers, input) {
|
|
|
2977
3160
|
{ id: "workspace-unlink", description: "Remove local workspace links before deployment install and lockfile updates" },
|
|
2978
3161
|
...repositoryPlan.plannedSteps,
|
|
2979
3162
|
{ id: "lockfile-validation", description: "Validate refreshed package-lock.json files before any save commit is pushed" },
|
|
2980
|
-
...shouldUseHostedSaveCi(effectiveInput, branch) ? [{ id: "hosted-ci", description: `Wait for hosted save workflows on ${branch}` }] : [],
|
|
2981
|
-
...branch === STAGING_BRANCH ? [{ id: "release-candidate", description:
|
|
3163
|
+
...shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? [{ id: "hosted-ci", description: `Wait for hosted save workflows on ${branch}` }] : [],
|
|
3164
|
+
...branch === STAGING_BRANCH ? [{ id: "release-candidate", description: `Run ${releaseCandidateMode} release-candidate readiness checks for the saved staging state` }] : [],
|
|
2982
3165
|
{ id: "workspace-link", description: "Restore local workspace links after save" },
|
|
2983
3166
|
...beforeState.branchRole === "feature" && (effectiveInput.preview === true || previewInitialized) ? [{ id: "preview", description: `Refresh preview deployment for ${branch}` }] : []
|
|
2984
3167
|
]
|
|
@@ -3014,10 +3197,12 @@ async function workflowSave(helpers, input) {
|
|
|
3014
3197
|
gitDependencyProtocol: effectiveInput.gitDependencyProtocol ?? "preserve-origin",
|
|
3015
3198
|
gitRemoteWriteMode: effectiveInput.gitRemoteWriteMode ?? "ssh-pushurl",
|
|
3016
3199
|
verifyMode: effectiveInput.verifyMode ?? (effectiveInput.verify === false ? "skip" : "fast"),
|
|
3017
|
-
ciMode:
|
|
3200
|
+
ciMode: saveCiMode,
|
|
3201
|
+
lane: saveLane,
|
|
3018
3202
|
worktreeMode: effectiveInput.worktreeMode ?? "auto",
|
|
3019
3203
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
3020
3204
|
workspaceLinks: effectiveInput.workspaceLinks ?? "auto",
|
|
3205
|
+
releaseCandidate: releaseCandidateMode,
|
|
3021
3206
|
verifyDeployedResources: effectiveInput.verifyDeployedResources === true
|
|
3022
3207
|
},
|
|
3023
3208
|
[
|
|
@@ -3029,7 +3214,7 @@ async function workflowSave(helpers, input) {
|
|
|
3029
3214
|
branch,
|
|
3030
3215
|
resumable: true
|
|
3031
3216
|
},
|
|
3032
|
-
...shouldUseHostedSaveCi(effectiveInput, branch) ? [{
|
|
3217
|
+
...shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? [{
|
|
3033
3218
|
id: "hosted-ci",
|
|
3034
3219
|
description: `Wait for hosted save workflows on ${branch}`,
|
|
3035
3220
|
repoName: rootRepo.name,
|
|
@@ -3085,19 +3270,19 @@ async function workflowSave(helpers, input) {
|
|
|
3085
3270
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
3086
3271
|
workflowRunId: workflowRun.runId,
|
|
3087
3272
|
onProgress: (line, stream) => helpers.write(line, stream),
|
|
3088
|
-
onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
|
|
3089
|
-
const
|
|
3090
|
-
const rootReportForWave = nodes.some((node) => node.
|
|
3273
|
+
onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
|
|
3274
|
+
const nonRootReportsForWave = reports.filter((repo, index) => nodes[index]?.id !== ".");
|
|
3275
|
+
const rootReportForWave = nodes.some((node) => node.id === ".") ? waveRootRepo : null;
|
|
3091
3276
|
const gates = [
|
|
3092
|
-
...
|
|
3277
|
+
...gatesForSavedRepositoryReports(root, nonRootReportsForWave),
|
|
3093
3278
|
...rootReportForWave ? gateForSavedRootReport(rootReportForWave, branch, scope) : []
|
|
3094
3279
|
];
|
|
3095
3280
|
if (gates.length === 0) {
|
|
3096
3281
|
return [];
|
|
3097
3282
|
}
|
|
3098
|
-
const
|
|
3099
|
-
if (
|
|
3100
|
-
helpers.write(`[save][workflow] Waiting for hosted
|
|
3283
|
+
const repositoryNames = gates.map((gate) => gate.name).join(", ");
|
|
3284
|
+
if (nonRootReportsForWave.length > 0) {
|
|
3285
|
+
helpers.write(`[save][workflow] Waiting for hosted repository gates before saving dependents: ${repositoryNames}.`);
|
|
3101
3286
|
} else if (rootReportForWave) {
|
|
3102
3287
|
helpers.write("[save][workflow] Waiting for hosted market deploy gate.");
|
|
3103
3288
|
}
|
|
@@ -3131,7 +3316,7 @@ async function workflowSave(helpers, input) {
|
|
|
3131
3316
|
lockfileValidation: repo.lockfileValidation
|
|
3132
3317
|
}))
|
|
3133
3318
|
};
|
|
3134
|
-
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
|
|
3319
|
+
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
|
|
3135
3320
|
if (branch === STAGING_BRANCH) {
|
|
3136
3321
|
const workflowGates = saveResult?.workflowGates ?? [];
|
|
3137
3322
|
if (effectiveInput.verifyDeployedResources !== true || scope === "local" || !savedRootRepo.commitSha) {
|
|
@@ -3200,7 +3385,7 @@ async function workflowSave(helpers, input) {
|
|
|
3200
3385
|
}).then((workflowGates) => ({ workflowGates }));
|
|
3201
3386
|
}) : { workflowGates: [] };
|
|
3202
3387
|
const releaseCandidate = branch === STAGING_BRANCH ? await executeJournalStep(root, workflowRun.runId, "release-candidate", () => {
|
|
3203
|
-
helpers.write(
|
|
3388
|
+
helpers.write(`[save][workflow] Running staging release-candidate readiness checks (${releaseCandidateMode}).`);
|
|
3204
3389
|
const releaseSession = resolveTreeseedWorkflowSession(root);
|
|
3205
3390
|
const stagingReleasePlan = buildReleasePlanSnapshot({
|
|
3206
3391
|
root,
|
|
@@ -3211,7 +3396,7 @@ async function workflowSave(helpers, input) {
|
|
|
3211
3396
|
rootRepo: savedRootRepo,
|
|
3212
3397
|
blockers: []
|
|
3213
3398
|
});
|
|
3214
|
-
return runReleaseCandidateForPlan("save", root, stagingReleasePlan);
|
|
3399
|
+
return runReleaseCandidateForPlan("save", root, stagingReleasePlan, { mode: releaseCandidateMode });
|
|
3215
3400
|
}) : null;
|
|
3216
3401
|
let previewAction = { status: "skipped" };
|
|
3217
3402
|
if (beforeState.branchRole === "feature" && branch) {
|
|
@@ -3227,12 +3412,22 @@ async function workflowSave(helpers, input) {
|
|
|
3227
3412
|
};
|
|
3228
3413
|
}
|
|
3229
3414
|
}
|
|
3230
|
-
const
|
|
3415
|
+
const applicationSelection = selectWorkflowApplications(root, {
|
|
3416
|
+
packageSelection: {
|
|
3417
|
+
selected: savedPackageReports.map((report) => report.name)
|
|
3418
|
+
}
|
|
3419
|
+
});
|
|
3420
|
+
const hostingAudit = await runWorkflowHostedResourceVerification(
|
|
3231
3421
|
"save",
|
|
3232
3422
|
root,
|
|
3233
3423
|
helpers,
|
|
3234
3424
|
scope === "prod" ? "prod" : scope === "local" ? "local" : "staging",
|
|
3235
|
-
{
|
|
3425
|
+
{
|
|
3426
|
+
enabled: effectiveInput.verifyDeployedResources === true,
|
|
3427
|
+
strict: true,
|
|
3428
|
+
live: effectiveInput.verifyDeployedResources === true,
|
|
3429
|
+
appId: singleSelectedWorkflowAppId(applicationSelection)
|
|
3430
|
+
}
|
|
3236
3431
|
);
|
|
3237
3432
|
const payload = {
|
|
3238
3433
|
mode: saveResult?.mode ?? mode,
|
|
@@ -3257,8 +3452,11 @@ async function workflowSave(helpers, input) {
|
|
|
3257
3452
|
workspaceLinks,
|
|
3258
3453
|
commandReadiness,
|
|
3259
3454
|
lockfileValidation,
|
|
3260
|
-
ciMode:
|
|
3455
|
+
ciMode: saveCiMode,
|
|
3456
|
+
lane: saveLane,
|
|
3261
3457
|
verifyMode: effectiveInput.verifyMode ?? "fast",
|
|
3458
|
+
releaseCandidateMode,
|
|
3459
|
+
applicationSelection,
|
|
3262
3460
|
workflowGates: saveWorkflowGates?.workflowGates ?? [],
|
|
3263
3461
|
releaseCandidate,
|
|
3264
3462
|
hostingAudit,
|
|
@@ -3529,6 +3727,13 @@ async function workflowStage(helpers, input) {
|
|
|
3529
3727
|
} catch (error) {
|
|
3530
3728
|
blockers.push(error instanceof Error ? error.message : String(error));
|
|
3531
3729
|
}
|
|
3730
|
+
const applicationSelection = selectWorkflowApplications(root, { packageSelection: initialSession.packageSelection });
|
|
3731
|
+
const readiness = collectTreeseedDeploymentReadiness({
|
|
3732
|
+
tenantRoot: root,
|
|
3733
|
+
environment: "staging",
|
|
3734
|
+
appId: singleSelectedWorkflowAppId(applicationSelection)
|
|
3735
|
+
});
|
|
3736
|
+
blockers.push(...readiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`));
|
|
3532
3737
|
return buildWorkflowResult(
|
|
3533
3738
|
"stage",
|
|
3534
3739
|
root,
|
|
@@ -3539,6 +3744,7 @@ async function workflowStage(helpers, input) {
|
|
|
3539
3744
|
mergeStrategy: "squash",
|
|
3540
3745
|
message,
|
|
3541
3746
|
ciMode,
|
|
3747
|
+
applicationSelection,
|
|
3542
3748
|
autoResumeCandidate: planAutoResumeRun ? {
|
|
3543
3749
|
runId: planAutoResumeRun.runId,
|
|
3544
3750
|
branch: planAutoResumeRun.session.branchName,
|
|
@@ -3547,6 +3753,7 @@ async function workflowStage(helpers, input) {
|
|
|
3547
3753
|
...worktreePayload(root, effectiveInput.worktreeMode),
|
|
3548
3754
|
autoSaveRequired: initialSession.rootRepo.dirty || initialSession.packageRepos.some((repo) => repo.dirty),
|
|
3549
3755
|
blockers,
|
|
3756
|
+
readiness,
|
|
3550
3757
|
rootRepo: createWorkspaceRootRepoReport(root),
|
|
3551
3758
|
repos: createWorkspacePackageReports(root),
|
|
3552
3759
|
plannedSteps: [
|
|
@@ -3593,6 +3800,16 @@ async function workflowStage(helpers, input) {
|
|
|
3593
3800
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
3594
3801
|
validateStagingWorkflowContracts(root);
|
|
3595
3802
|
runWorkspaceSavePreflight({ cwd: root });
|
|
3803
|
+
const stagingReadiness = collectTreeseedDeploymentReadiness({ tenantRoot: root, environment: "staging" });
|
|
3804
|
+
if (!stagingReadiness.ok) {
|
|
3805
|
+
workflowError(
|
|
3806
|
+
"stage",
|
|
3807
|
+
"validation_failed",
|
|
3808
|
+
`Deployment readiness failed for staging:
|
|
3809
|
+
${stagingReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`).join("\n")}`,
|
|
3810
|
+
{ details: { readiness: stagingReadiness } }
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3596
3813
|
const repoDir = session.gitRoot;
|
|
3597
3814
|
const rootRepo = createWorkspaceRootRepoReport(root);
|
|
3598
3815
|
const packageReports = createWorkspacePackageReports(root);
|
|
@@ -3759,12 +3976,18 @@ async function workflowStage(helpers, input) {
|
|
|
3759
3976
|
blockers: []
|
|
3760
3977
|
});
|
|
3761
3978
|
const releaseCandidate = await executeJournalStep(root, workflowRun.runId, "release-candidate", () => runReleaseCandidateForPlan("stage", root, stageReleasePlan));
|
|
3762
|
-
const
|
|
3979
|
+
const applicationSelection = selectWorkflowApplications(root, { packageSelection: session.packageSelection });
|
|
3980
|
+
const hostingAudit = await runWorkflowHostedResourceVerification(
|
|
3763
3981
|
"stage",
|
|
3764
3982
|
root,
|
|
3765
3983
|
helpers,
|
|
3766
3984
|
"staging",
|
|
3767
|
-
{
|
|
3985
|
+
{
|
|
3986
|
+
enabled: effectiveInput.verifyDeployedResources === true,
|
|
3987
|
+
strict: true,
|
|
3988
|
+
live: effectiveInput.verifyDeployedResources === true,
|
|
3989
|
+
appId: singleSelectedWorkflowAppId(applicationSelection)
|
|
3990
|
+
}
|
|
3768
3991
|
);
|
|
3769
3992
|
const previewCleanup = effectiveInput.deletePreview === false ? (skipJournalStep(root, workflowRun.runId, "preview-cleanup", { performed: false }), { performed: false }) : await executeJournalStep(root, workflowRun.runId, "preview-cleanup", () => destroyPreviewIfPresent(root, featureBranch));
|
|
3770
3993
|
const rootCleanup = await executeJournalStep(root, workflowRun.runId, "cleanup-root", () => {
|
|
@@ -3813,6 +4036,7 @@ async function workflowStage(helpers, input) {
|
|
|
3813
4036
|
rootRepo,
|
|
3814
4037
|
stagingWait,
|
|
3815
4038
|
releaseCandidate,
|
|
4039
|
+
applicationSelection,
|
|
3816
4040
|
previewCleanup,
|
|
3817
4041
|
lockfileValidation: rootMerge?.lockfileValidation ?? null,
|
|
3818
4042
|
lockfileInstall: rootMerge?.lockfileInstall ?? null,
|
|
@@ -3923,10 +4147,19 @@ async function workflowRelease(helpers, input) {
|
|
|
3923
4147
|
level,
|
|
3924
4148
|
repairVersionLine: effectiveInput.repairVersionLine === true
|
|
3925
4149
|
});
|
|
4150
|
+
const plannedReadiness = collectTreeseedDeploymentReadiness({
|
|
4151
|
+
tenantRoot: root,
|
|
4152
|
+
environment: "prod",
|
|
4153
|
+
appId: singleSelectedWorkflowAppId(plannedRelease.applicationSelection)
|
|
4154
|
+
});
|
|
4155
|
+
if (!isResume) {
|
|
4156
|
+
blockers.push(...plannedReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`));
|
|
4157
|
+
}
|
|
3926
4158
|
plannedRelease.blockers = blockers;
|
|
3927
4159
|
if (executionMode === "plan") {
|
|
3928
4160
|
return buildWorkflowResult("release", root, {
|
|
3929
4161
|
...plannedRelease,
|
|
4162
|
+
readiness: plannedReadiness,
|
|
3930
4163
|
ciMode,
|
|
3931
4164
|
fresh: input.fresh === true,
|
|
3932
4165
|
freshArchivedRuns: [],
|
|
@@ -4011,6 +4244,16 @@ async function workflowRelease(helpers, input) {
|
|
|
4011
4244
|
const rootVersion = String(releasePlan.rootVersion);
|
|
4012
4245
|
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
4013
4246
|
assertReleaseGitHubAutomationReady(root, effectiveSelectedPackageNames, ciMode);
|
|
4247
|
+
const productionReadiness = collectTreeseedDeploymentReadiness({ tenantRoot: root, environment: "prod" });
|
|
4248
|
+
if (!productionReadiness.ok) {
|
|
4249
|
+
workflowError(
|
|
4250
|
+
"release",
|
|
4251
|
+
"validation_failed",
|
|
4252
|
+
`Deployment readiness failed for prod:
|
|
4253
|
+
${productionReadiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`).join("\n")}`,
|
|
4254
|
+
{ details: { readiness: productionReadiness } }
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4014
4257
|
const stagingGateResult = resumeAtRootGates ? completedJournalStepData(root, workflowRun.runId, "release-staging-gates") : await executeJournalStep(root, workflowRun.runId, "release-staging-gates", () => {
|
|
4015
4258
|
helpers.write("[release][workflow] Verifying current staging gates before production release.");
|
|
4016
4259
|
const packageGates = checkedOutWorkspacePackageRepos(root).filter((pkg) => effectiveSelectedPackageNames.has(pkg.name)).map((pkg) => ({
|
|
@@ -4119,9 +4362,12 @@ async function workflowRelease(helpers, input) {
|
|
|
4119
4362
|
}).then((workflowGates) => ({ workflowGates })));
|
|
4120
4363
|
const hostedDeploymentState2 = recordHostedDeploymentStatesFromRootGates(root, rootRelease2, rootWorkflowGateResult2?.workflowGates);
|
|
4121
4364
|
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
4122
|
-
const
|
|
4365
|
+
const applicationSelection2 = selectWorkflowApplications(root, { changedPaths: ["treeseed.site.yaml"] });
|
|
4366
|
+
const hostingAudit2 = await runWorkflowHostedResourceVerification("release", root, helpers, "prod", {
|
|
4123
4367
|
enabled: true,
|
|
4124
|
-
strict:
|
|
4368
|
+
strict: effectiveInput.verifyDeployedResources === true,
|
|
4369
|
+
live: effectiveInput.verifyDeployedResources === true,
|
|
4370
|
+
appId: singleSelectedWorkflowAppId(applicationSelection2)
|
|
4125
4371
|
});
|
|
4126
4372
|
const releaseBackMerge2 = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, false, {
|
|
4127
4373
|
version: rootVersion,
|
|
@@ -4144,6 +4390,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4144
4390
|
productionBranch: PRODUCTION_BRANCH,
|
|
4145
4391
|
touchedPackages: [],
|
|
4146
4392
|
packageSelection: { changed: [], dependents: [], selected: [] },
|
|
4393
|
+
applicationSelection: applicationSelection2,
|
|
4147
4394
|
publishWait: [],
|
|
4148
4395
|
repos: [],
|
|
4149
4396
|
rootRepo,
|
|
@@ -4249,7 +4496,8 @@ async function workflowRelease(helpers, input) {
|
|
|
4249
4496
|
changelog,
|
|
4250
4497
|
extraLines: [`Package: ${pkg.name}`]
|
|
4251
4498
|
}),
|
|
4252
|
-
pushTarget: true
|
|
4499
|
+
pushTarget: true,
|
|
4500
|
+
allowUnrelatedHistories: true
|
|
4253
4501
|
});
|
|
4254
4502
|
const tag = ensureReleaseTag(pkg.dir, tagName, mergeResult.commitSha, releaseAdminMessage({
|
|
4255
4503
|
subject: `release: ${tagName}`,
|
|
@@ -4430,9 +4678,12 @@ async function workflowRelease(helpers, input) {
|
|
|
4430
4678
|
}).then((workflowGates) => ({ workflowGates })));
|
|
4431
4679
|
const hostedDeploymentState = recordHostedDeploymentStatesFromRootGates(root, rootRelease, rootWorkflowGateResult?.workflowGates);
|
|
4432
4680
|
ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto");
|
|
4433
|
-
const
|
|
4681
|
+
const applicationSelection = releasePlan.applicationSelection;
|
|
4682
|
+
const hostingAudit = await runWorkflowHostedResourceVerification("release", root, helpers, "prod", {
|
|
4434
4683
|
enabled: true,
|
|
4435
|
-
strict:
|
|
4684
|
+
strict: effectiveInput.verifyDeployedResources === true,
|
|
4685
|
+
live: effectiveInput.verifyDeployedResources === true,
|
|
4686
|
+
appId: singleSelectedWorkflowAppId(applicationSelection)
|
|
4436
4687
|
});
|
|
4437
4688
|
const releaseBackMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => backMergeRootProductionIntoStaging(root, true, {
|
|
4438
4689
|
version: rootVersion,
|
|
@@ -4465,6 +4716,7 @@ async function workflowRelease(helpers, input) {
|
|
|
4465
4716
|
productionBranch: PRODUCTION_BRANCH,
|
|
4466
4717
|
touchedPackages: effectivePackageSelection.selected,
|
|
4467
4718
|
packageSelection: effectivePackageSelection,
|
|
4719
|
+
applicationSelection,
|
|
4468
4720
|
replacedDevReferences,
|
|
4469
4721
|
releaseInstalls,
|
|
4470
4722
|
devTagCleanup,
|
|
@@ -4720,6 +4972,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4720
4972
|
const dryRun = executionMode === "plan";
|
|
4721
4973
|
const force = input.force === true;
|
|
4722
4974
|
const deleteData = input.deleteData === true;
|
|
4975
|
+
const sweepTreeseed = input.sweepTreeseed === true;
|
|
4723
4976
|
const destroyRemote = input.destroyRemote !== false;
|
|
4724
4977
|
const destroyLocal = input.destroyLocal !== false;
|
|
4725
4978
|
const removeBuildArtifacts = input.removeBuildArtifacts === true;
|
|
@@ -4733,6 +4986,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4733
4986
|
dryRun,
|
|
4734
4987
|
force,
|
|
4735
4988
|
deleteData,
|
|
4989
|
+
sweepTreeseed,
|
|
4736
4990
|
destroyRemote,
|
|
4737
4991
|
destroyLocal,
|
|
4738
4992
|
removeBuildArtifacts,
|
|
@@ -4743,12 +4997,13 @@ async function workflowDestroy(helpers, input) {
|
|
|
4743
4997
|
},
|
|
4744
4998
|
plannedSteps: [
|
|
4745
4999
|
...destroyRemote ? [{ id: "destroy-remote", description: `Destroy remote ${scope} resources` }] : [],
|
|
5000
|
+
...sweepTreeseed ? [{ id: "sweep-treeseed-resources", description: "Sweep TreeSeed-owned provider resources across persistent environments" }] : [],
|
|
4746
5001
|
...destroyLocal ? [{ id: "cleanup-local", description: `Clean local ${scope} state${removeBuildArtifacts ? " and build artifacts" : ""}` }] : []
|
|
4747
5002
|
],
|
|
4748
5003
|
remoteResult: null
|
|
4749
5004
|
};
|
|
4750
5005
|
if (executionMode === "plan") {
|
|
4751
|
-
const plannedRemoteResult = destroyRemote ? await destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: true, force, deleteData, target }) : null;
|
|
5006
|
+
const plannedRemoteResult = destroyRemote ? await destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: true, force, deleteData, sweepTreeseed, target }) : null;
|
|
4752
5007
|
return buildWorkflowResult(
|
|
4753
5008
|
"destroy",
|
|
4754
5009
|
tenantRoot,
|
|
@@ -4759,7 +5014,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4759
5014
|
{
|
|
4760
5015
|
executionMode,
|
|
4761
5016
|
nextSteps: createNextSteps([
|
|
4762
|
-
{ operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, deleteData, removeBuildArtifacts } },
|
|
5017
|
+
{ operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, deleteData, sweepTreeseed, removeBuildArtifacts } },
|
|
4763
5018
|
{ operation: "status", reason: "Confirm the current environment state before making destructive changes." }
|
|
4764
5019
|
])
|
|
4765
5020
|
}
|
|
@@ -4772,6 +5027,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4772
5027
|
environment: scope,
|
|
4773
5028
|
force,
|
|
4774
5029
|
deleteData,
|
|
5030
|
+
sweepTreeseed,
|
|
4775
5031
|
destroyRemote,
|
|
4776
5032
|
destroyLocal,
|
|
4777
5033
|
removeBuildArtifacts
|
|
@@ -4801,7 +5057,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4801
5057
|
if (!confirmed) {
|
|
4802
5058
|
workflowError("destroy", "confirmation_required", `Destroy confirmation required. Re-run with confirm="${expectedConfirmation}".`);
|
|
4803
5059
|
}
|
|
4804
|
-
const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () => destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: false, force, deleteData, target })) : null;
|
|
5060
|
+
const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () => destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: false, force, deleteData, sweepTreeseed, target })) : null;
|
|
4805
5061
|
if (!destroyRemote) {
|
|
4806
5062
|
skipJournalStep(root, workflowRun.runId, "destroy-remote", { skippedReason: "destroyRemote=false" });
|
|
4807
5063
|
}
|