@treeseed/sdk 0.12.5 → 0.12.7
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.
|
@@ -221,6 +221,7 @@ function beamPackageAdapter(root, dir) {
|
|
|
221
221
|
const versionSourceRel = typeof manifest?.versionSource === "string" && manifest.versionSource.trim() ? manifest.versionSource.trim() : existsSync(resolve(dir, "apps/api/mix.exs")) ? "apps/api/mix.exs" : "mix.exs";
|
|
222
222
|
const versionSource = resolve(dir, versionSourceRel);
|
|
223
223
|
const image = typeof manifest?.image === "string" && manifest.image.trim() ? manifest.image.trim() : id === "treedx" ? "treeseed/treedx" : null;
|
|
224
|
+
const dockerArtifacts = manifestDockerArtifacts(manifest?.artifacts);
|
|
224
225
|
const repository = stringValue(manifest?.repository) ?? (id === "treedx" ? "treeseed-ai/treedx" : null);
|
|
225
226
|
const hostedVerifyWorkflow = stringValue(manifest?.hostedVerifyWorkflow) ?? stringValue(stringRecord(manifest?.releaseGate).workflow) ?? (existsSync(resolve(dir, ".github/workflows/release-gate.yml")) ? "release-gate.yml" : null);
|
|
226
227
|
const projectArchitecture = normalizeTreeseedPackageProjectArchitecture(manifest?.projectArchitecture, id);
|
|
@@ -246,7 +247,16 @@ function beamPackageAdapter(root, dir) {
|
|
|
246
247
|
local: commandFromScript(dir, local, "local"),
|
|
247
248
|
release: commandFromScript(dir, releaseGate, "release")
|
|
248
249
|
},
|
|
249
|
-
artifacts:
|
|
250
|
+
artifacts: dockerArtifacts.length > 0 ? dockerArtifacts.map((artifact) => ({
|
|
251
|
+
provider: "docker",
|
|
252
|
+
name: artifact.name,
|
|
253
|
+
tags: version ? [version, shaTag] : [shaTag],
|
|
254
|
+
dockerfile: artifact.dockerfile ?? "Dockerfile",
|
|
255
|
+
context: artifact.context ?? ".",
|
|
256
|
+
target: artifact.target,
|
|
257
|
+
role: artifact.role ?? id,
|
|
258
|
+
architectures: artifact.architectures.length > 0 ? artifact.architectures : stringArray(stringRecord(manifest?.dockerImages).architectures)
|
|
259
|
+
})) : image ? [{
|
|
250
260
|
provider: "docker",
|
|
251
261
|
name: image,
|
|
252
262
|
tags: version ? [version, shaTag] : [shaTag],
|
|
@@ -256,7 +266,7 @@ function beamPackageAdapter(root, dir) {
|
|
|
256
266
|
role: id,
|
|
257
267
|
architectures: stringArray(stringRecord(manifest?.dockerImages).architectures)
|
|
258
268
|
}] : [],
|
|
259
|
-
releaseChecks: image ? [{ kind: "docker-manifest", name: "Docker image manifest", detail: `${image}:${version ?? "<version>"}` }] : [],
|
|
269
|
+
releaseChecks: dockerArtifacts.length > 0 ? dockerArtifacts.map((artifact) => ({ kind: "docker-manifest", name: `${artifact.name} Docker image manifest`, detail: `${artifact.name}:${version ?? "<version>"}` })) : image ? [{ kind: "docker-manifest", name: "Docker image manifest", detail: `${image}:${version ?? "<version>"}` }] : [],
|
|
260
270
|
metadata: {
|
|
261
271
|
hasCargo: existsSync(resolve(dir, "Cargo.toml")),
|
|
262
272
|
hasDockerfile: existsSync(resolve(dir, "Dockerfile")),
|
|
@@ -16,7 +16,7 @@ function internalDependencyFields(packageJson) {
|
|
|
16
16
|
return INTERNAL_DEPENDENCY_FIELDS.filter((field) => packageJson[field] && typeof packageJson[field] === "object" && !Array.isArray(packageJson[field]));
|
|
17
17
|
}
|
|
18
18
|
function isPrereleaseVersion(version) {
|
|
19
|
-
return
|
|
19
|
+
return /^[~^]?\d+\.\d+\.\d+-[0-9A-Za-z.-]+$/u.test(String(version).trim());
|
|
20
20
|
}
|
|
21
21
|
function isStableVersion(version) {
|
|
22
22
|
return /^\d+\.\d+\.\d+$/u.test(String(version).trim());
|
|
@@ -136,13 +136,22 @@ function updateInternalDependencySpecs(packageJson, references) {
|
|
|
136
136
|
return changed;
|
|
137
137
|
}
|
|
138
138
|
function installableInternalDependencyVersions(root = workspaceRoot(), versions) {
|
|
139
|
-
const
|
|
139
|
+
const installablePackages = /* @__PURE__ */ new Set();
|
|
140
140
|
for (const adapter of discoverTreeseedPackageAdapters(root)) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
if (adapter.publishTarget === "npm" || publicNpmPackageManifest(adapter.dir)) {
|
|
142
|
+
installablePackages.add(adapter.id);
|
|
143
|
+
installablePackages.add(adapter.name);
|
|
144
|
+
}
|
|
144
145
|
}
|
|
145
|
-
return new Map([...versions.entries()].filter(([packageName]) =>
|
|
146
|
+
return new Map([...versions.entries()].filter(([packageName]) => installablePackages.has(packageName)));
|
|
147
|
+
}
|
|
148
|
+
function publicNpmPackageManifest(packageRoot) {
|
|
149
|
+
const packageJsonPath = resolve(packageRoot, "package.json");
|
|
150
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
151
|
+
const packageJson = readJson(packageJsonPath);
|
|
152
|
+
if (packageJson.private === true) return false;
|
|
153
|
+
const publishConfig = packageJson.publishConfig;
|
|
154
|
+
return Boolean(publishConfig && typeof publishConfig === "object" && publishConfig.access === "public");
|
|
146
155
|
}
|
|
147
156
|
function rewriteInternalDependenciesToStableVersions(root = workspaceRoot(), versions) {
|
|
148
157
|
const rewrites = [];
|
|
@@ -83,6 +83,27 @@ function packageUnitFromAdapter(adapter) {
|
|
|
83
83
|
githubEnvironments: Array.isArray(adapter.metadata.githubEnvironments) ? adapter.metadata.githubEnvironments : []
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
const PRODUCTION_ONLY_GITHUB_SECRET_NAMES = /* @__PURE__ */ new Set([
|
|
87
|
+
"NPM_TOKEN",
|
|
88
|
+
"PYPI_API_TOKEN",
|
|
89
|
+
"CARGO_REGISTRY_TOKEN",
|
|
90
|
+
"HEX_API_KEY"
|
|
91
|
+
]);
|
|
92
|
+
function packageRequiredSecretsForGitHubEnvironment(adapter, environmentName) {
|
|
93
|
+
const requiredSecrets = Array.isArray(adapter.metadata.requiredSecrets) ? adapter.metadata.requiredSecrets : [];
|
|
94
|
+
const isProduction = environmentName === "production";
|
|
95
|
+
return requiredSecrets.filter((secretName) => {
|
|
96
|
+
if (typeof secretName !== "string" || !secretName.trim()) return false;
|
|
97
|
+
return isProduction || !PRODUCTION_ONLY_GITHUB_SECRET_NAMES.has(secretName);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function packageRequiredVariablesForGitHubEnvironment(adapter, _environmentName) {
|
|
101
|
+
return (Array.isArray(adapter.metadata.requiredVariables) ? adapter.metadata.requiredVariables : []).filter((variableName) => typeof variableName === "string" && Boolean(variableName.trim()));
|
|
102
|
+
}
|
|
103
|
+
function packageUnitRequiredSecretsForGitHubEnvironment(pkg, environmentName) {
|
|
104
|
+
const isProduction = environmentName === "production";
|
|
105
|
+
return pkg.requiredSecrets.filter((secretName) => isProduction || !PRODUCTION_ONLY_GITHUB_SECRET_NAMES.has(secretName));
|
|
106
|
+
}
|
|
86
107
|
function templateReleaseTag(manifest) {
|
|
87
108
|
return manifest.version ? `${manifest.release.tagPrefix}${manifest.id}/v${manifest.version}` : null;
|
|
88
109
|
}
|
|
@@ -299,8 +320,7 @@ function packageResources(adapter, environment) {
|
|
|
299
320
|
},
|
|
300
321
|
source: { type: "package-adapter", id: packageId }
|
|
301
322
|
});
|
|
302
|
-
for (const secretName of
|
|
303
|
-
if (typeof secretName !== "string" || !secretName.trim()) continue;
|
|
323
|
+
for (const secretName of packageRequiredSecretsForGitHubEnvironment(adapter, environmentName)) {
|
|
304
324
|
resources.push({
|
|
305
325
|
id: `github-secret-binding:${packageId}:${environmentName}:${secretName}`,
|
|
306
326
|
kind: "github-secret-binding",
|
|
@@ -321,8 +341,7 @@ function packageResources(adapter, environment) {
|
|
|
321
341
|
source: { type: "package-adapter", id: packageId }
|
|
322
342
|
});
|
|
323
343
|
}
|
|
324
|
-
for (const variableName of
|
|
325
|
-
if (typeof variableName !== "string" || !variableName.trim()) continue;
|
|
344
|
+
for (const variableName of packageRequiredVariablesForGitHubEnvironment(adapter, environmentName)) {
|
|
326
345
|
resources.push({
|
|
327
346
|
id: `github-variable-binding:${packageId}:${environmentName}:${variableName}`,
|
|
328
347
|
kind: "github-variable-binding",
|
|
@@ -731,7 +750,7 @@ function releaseGateResources(packages, templates, environment) {
|
|
|
731
750
|
};
|
|
732
751
|
const publishGateKind = pkg.releaseCapability === "npm" ? "release-gate:npm-publish" : pkg.releaseCapability === "image" ? "release-gate:image-publish" : null;
|
|
733
752
|
const imageCredentialDependencies = publishGateKind === "release-gate:image-publish" && pkg.githubEnvironments.includes(hostedEnvironment) ? [
|
|
734
|
-
...pkg.
|
|
753
|
+
...packageUnitRequiredSecretsForGitHubEnvironment(pkg, hostedEnvironment).map((secretName) => `github-secret-binding:${pkg.id}:${hostedEnvironment}:${secretName}`),
|
|
735
754
|
...pkg.requiredVariables.map((variableName) => `github-variable-binding:${pkg.id}:${hostedEnvironment}:${variableName}`)
|
|
736
755
|
] : [];
|
|
737
756
|
const publishDependencies = publishGateKind === "release-gate:image-publish" ? [verifyGate.id, ...imageCredentialDependencies] : [verifyGate.id];
|
|
@@ -12,7 +12,7 @@ import type { TreeseedCloseInput, TreeseedCiInput, TreeseedConfigInput, Treeseed
|
|
|
12
12
|
type WorkflowWrite = NonNullable<TreeseedWorkflowContext['write']>;
|
|
13
13
|
type WorkflowStatePayload = ReturnType<typeof resolveTreeseedWorkflowState>;
|
|
14
14
|
type ReleaseCandidateMode = TreeseedReleaseCandidateMode;
|
|
15
|
-
export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable';
|
|
15
|
+
export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable' | 'hosted_reconcile_failed' | 'hosted_live_verification_failed';
|
|
16
16
|
export declare class TreeseedWorkflowError extends Error {
|
|
17
17
|
code: TreeseedWorkflowErrorCode;
|
|
18
18
|
operation: TreeseedWorkflowOperationId;
|
|
@@ -89,6 +89,16 @@ export declare function workflowReleaseCandidate(helpers: WorkflowOperationHelpe
|
|
|
89
89
|
finalState?: WorkflowStatePayload;
|
|
90
90
|
timing?: TreeseedWorkflowTiming;
|
|
91
91
|
}>>;
|
|
92
|
+
type PublishedArtifactCheck = {
|
|
93
|
+
id: string;
|
|
94
|
+
kind: 'npm' | 'docker' | 'pypi' | 'crates' | 'hex';
|
|
95
|
+
name: string;
|
|
96
|
+
version: string;
|
|
97
|
+
url: string;
|
|
98
|
+
ok: boolean;
|
|
99
|
+
status?: number | null;
|
|
100
|
+
message?: string;
|
|
101
|
+
};
|
|
92
102
|
export declare function reconcileTreeseedBranchPreview(input: {
|
|
93
103
|
root: string;
|
|
94
104
|
branch: string;
|
|
@@ -1001,29 +1011,20 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1001
1011
|
version: string | null;
|
|
1002
1012
|
result: {
|
|
1003
1013
|
status: string;
|
|
1004
|
-
|
|
1005
|
-
command: string;
|
|
1006
|
-
} | {
|
|
1007
|
-
status: string;
|
|
1008
|
-
reason: string;
|
|
1009
|
-
adapter: string;
|
|
1010
|
-
command?: undefined;
|
|
1011
|
-
} | {
|
|
1012
|
-
status: string;
|
|
1013
|
-
reason: null;
|
|
1014
|
-
adapter: string;
|
|
1015
|
-
command?: undefined;
|
|
1014
|
+
reason?: undefined;
|
|
1016
1015
|
} | {
|
|
1017
1016
|
status: string;
|
|
1018
1017
|
reason: string;
|
|
1019
1018
|
};
|
|
1020
1019
|
}[];
|
|
1021
|
-
|
|
1020
|
+
rootPackageLock: {
|
|
1022
1021
|
status: string;
|
|
1023
1022
|
reason: string;
|
|
1023
|
+
path?: undefined;
|
|
1024
1024
|
} | {
|
|
1025
1025
|
status: string;
|
|
1026
|
-
|
|
1026
|
+
path: string;
|
|
1027
|
+
reason?: undefined;
|
|
1027
1028
|
};
|
|
1028
1029
|
workspaceUnlink: import("../operations/services/workspace-dependency-mode.js").WorkspaceDependencyModeReport;
|
|
1029
1030
|
};
|
|
@@ -1044,6 +1045,13 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1044
1045
|
changelogUpdated: boolean;
|
|
1045
1046
|
entry: string;
|
|
1046
1047
|
};
|
|
1048
|
+
rootInstall: {
|
|
1049
|
+
status: string;
|
|
1050
|
+
reason: string;
|
|
1051
|
+
} | {
|
|
1052
|
+
status: string;
|
|
1053
|
+
reason: null;
|
|
1054
|
+
};
|
|
1047
1055
|
commit: {
|
|
1048
1056
|
committed: boolean;
|
|
1049
1057
|
commitSha: string;
|
|
@@ -1077,6 +1085,62 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1077
1085
|
timeoutSeconds: number | null;
|
|
1078
1086
|
cached: boolean;
|
|
1079
1087
|
}[];
|
|
1088
|
+
publishedArtifacts: {
|
|
1089
|
+
checks: PublishedArtifactCheck[];
|
|
1090
|
+
};
|
|
1091
|
+
productionHosting: {
|
|
1092
|
+
status: "skipped";
|
|
1093
|
+
reason: string;
|
|
1094
|
+
environment: "staging" | "prod";
|
|
1095
|
+
selectedApps: string[];
|
|
1096
|
+
selectedResources: {
|
|
1097
|
+
id: string;
|
|
1098
|
+
host: string;
|
|
1099
|
+
serviceType: string;
|
|
1100
|
+
placement: import("../hosting/contracts.js").TreeseedServicePlacement;
|
|
1101
|
+
serviceName: string | null;
|
|
1102
|
+
}[];
|
|
1103
|
+
reconcile?: undefined;
|
|
1104
|
+
postApplyStatus?: undefined;
|
|
1105
|
+
liveVerification?: undefined;
|
|
1106
|
+
} | {
|
|
1107
|
+
status: "reconciled";
|
|
1108
|
+
environment: "staging" | "prod";
|
|
1109
|
+
selectedApps: string[];
|
|
1110
|
+
selectedResources: {
|
|
1111
|
+
id: string;
|
|
1112
|
+
host: string;
|
|
1113
|
+
serviceType: string;
|
|
1114
|
+
placement: import("../hosting/contracts.js").TreeseedServicePlacement;
|
|
1115
|
+
serviceName: string | null;
|
|
1116
|
+
}[];
|
|
1117
|
+
reconcile: {
|
|
1118
|
+
target: TreeseedReconcileTarget;
|
|
1119
|
+
units: import("../reconcile/contracts.js").TreeseedDesiredUnit[];
|
|
1120
|
+
plans: import("../reconcile/contracts.js").TreeseedReconcilePlan[];
|
|
1121
|
+
results: TreeseedReconcileResult[];
|
|
1122
|
+
state: import("../reconcile/contracts.js").TreeseedReconcileStateRecord;
|
|
1123
|
+
timings: import("../timing.js").TreeseedTimingEntry[];
|
|
1124
|
+
};
|
|
1125
|
+
postApplyStatus: {
|
|
1126
|
+
target: TreeseedReconcileTarget;
|
|
1127
|
+
ready: boolean;
|
|
1128
|
+
blockers: string[];
|
|
1129
|
+
warnings: string[];
|
|
1130
|
+
units: {
|
|
1131
|
+
unitId: string;
|
|
1132
|
+
unitType: import("../reconcile/contracts.js").TreeseedReconcileUnitType;
|
|
1133
|
+
provider: string;
|
|
1134
|
+
status: import("../reconcile/contracts.js").TreeseedReconcileStatusKind;
|
|
1135
|
+
exists: boolean;
|
|
1136
|
+
locators: Record<string, string | null>;
|
|
1137
|
+
warnings: string[];
|
|
1138
|
+
verification: import("../reconcile/contracts.js").TreeseedUnitVerificationResult | null;
|
|
1139
|
+
}[];
|
|
1140
|
+
};
|
|
1141
|
+
liveVerification: import("../workflow-support.js").TreeseedLiveHostedServiceCheckReport;
|
|
1142
|
+
reason?: undefined;
|
|
1143
|
+
};
|
|
1080
1144
|
backMerge: {
|
|
1081
1145
|
packages: {
|
|
1082
1146
|
status: string;
|
|
@@ -114,8 +114,7 @@ import {
|
|
|
114
114
|
import { discoverTreeseedPackageAdapters } from "../operations/services/package-adapters.js";
|
|
115
115
|
import {
|
|
116
116
|
collectInternalDevReferenceIssues,
|
|
117
|
-
|
|
118
|
-
normalizeGitRemoteForManifest
|
|
117
|
+
rewriteProjectInternalDependenciesToStableVersions
|
|
119
118
|
} from "../operations/services/package-reference-policy.js";
|
|
120
119
|
import {
|
|
121
120
|
ensureLocalWorkspaceLinks,
|
|
@@ -525,7 +524,7 @@ function selectorFromWorkflowHostingGraph(graph) {
|
|
|
525
524
|
}))]
|
|
526
525
|
};
|
|
527
526
|
}
|
|
528
|
-
async function reconcileSaveHostedEnvironment(root, environment, helpers, workflowRunId) {
|
|
527
|
+
async function reconcileSaveHostedEnvironment(root, environment, helpers, workflowRunId, operation = "save") {
|
|
529
528
|
const graph = compileTreeseedHostingGraph({ tenantRoot: root, environment });
|
|
530
529
|
const selector = selectorFromWorkflowHostingGraph(graph);
|
|
531
530
|
if (process.env.TREESEED_WORKFLOW_HOSTED_RECONCILE_MODE === "skip") {
|
|
@@ -548,24 +547,26 @@ async function reconcileSaveHostedEnvironment(root, environment, helpers, workfl
|
|
|
548
547
|
...helpers.context.env,
|
|
549
548
|
...collectTreeseedConfigSeedValues(root, environment, helpers.context.env)
|
|
550
549
|
};
|
|
551
|
-
|
|
550
|
+
const reconcileSession = /* @__PURE__ */ new Map([["workflowRunId", workflowRunId]]);
|
|
551
|
+
helpers.write(`[${operation}][workflow] Reconciling ${environment} hosted deployments for ${graph.units.length} selected resources.`);
|
|
552
552
|
const reconcile = await reconcileTreeseedTarget({
|
|
553
553
|
tenantRoot: root,
|
|
554
554
|
target,
|
|
555
555
|
env,
|
|
556
556
|
selector,
|
|
557
557
|
dryRun: false,
|
|
558
|
-
write: (line) => helpers.write(`[
|
|
559
|
-
session:
|
|
558
|
+
write: (line) => helpers.write(`[${operation}][reconcile] ${line}`, "stderr"),
|
|
559
|
+
session: reconcileSession
|
|
560
560
|
});
|
|
561
561
|
const status = await collectTreeseedReconcileStatus({
|
|
562
562
|
tenantRoot: root,
|
|
563
563
|
target,
|
|
564
564
|
env,
|
|
565
|
-
selector
|
|
565
|
+
selector,
|
|
566
|
+
session: reconcileSession
|
|
566
567
|
});
|
|
567
568
|
if (!status.ready) {
|
|
568
|
-
workflowError(
|
|
569
|
+
workflowError(operation, "hosted_reconcile_failed", `Hosted reconciliation for ${environment} did not verify:
|
|
569
570
|
${status.blockers.join("\n")}`, {
|
|
570
571
|
details: { environment, selector, status, reconcile }
|
|
571
572
|
});
|
|
@@ -583,7 +584,7 @@ ${status.blockers.join("\n")}`, {
|
|
|
583
584
|
...live.liveObservation.issues
|
|
584
585
|
];
|
|
585
586
|
if (liveFailures.length > 0) {
|
|
586
|
-
workflowError(
|
|
587
|
+
workflowError(operation, "hosted_live_verification_failed", `Hosted live verification for ${environment} failed:
|
|
587
588
|
${liveFailures.join("\n")}`, {
|
|
588
589
|
details: { environment, selector, live, reconcile }
|
|
589
590
|
});
|
|
@@ -972,9 +973,31 @@ function writeJsonFile(path, value) {
|
|
|
972
973
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
973
974
|
`, "utf8");
|
|
974
975
|
}
|
|
976
|
+
function updatePackageLockRootVersion(root, version) {
|
|
977
|
+
const packageLockPath = resolve(root, "package-lock.json");
|
|
978
|
+
if (!existsSync(packageLockPath)) return { status: "skipped", reason: "no package-lock.json" };
|
|
979
|
+
const packageLock = JSON.parse(readFileSync(packageLockPath, "utf8"));
|
|
980
|
+
let changed = false;
|
|
981
|
+
if (packageLock.version !== version) {
|
|
982
|
+
packageLock.version = version;
|
|
983
|
+
changed = true;
|
|
984
|
+
}
|
|
985
|
+
const packages = packageLock.packages;
|
|
986
|
+
if (packages && typeof packages === "object" && !Array.isArray(packages)) {
|
|
987
|
+
const rootPackage = packages[""];
|
|
988
|
+
if (rootPackage && typeof rootPackage === "object" && !Array.isArray(rootPackage)) {
|
|
989
|
+
if (rootPackage.version !== version) {
|
|
990
|
+
rootPackage.version = version;
|
|
991
|
+
changed = true;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (changed) {
|
|
996
|
+
writeJsonFile(packageLockPath, packageLock);
|
|
997
|
+
}
|
|
998
|
+
return { status: changed ? "updated" : "unchanged", path: "package-lock.json" };
|
|
999
|
+
}
|
|
975
1000
|
function applyStableWorkspaceVersionChanges(root, versions) {
|
|
976
|
-
const dependencyVersions = installableInternalDependencyVersions(root, versions);
|
|
977
|
-
const stableGitReferences = stablePackageGitReferences(root, dependencyVersions);
|
|
978
1001
|
for (const target of [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))]) {
|
|
979
1002
|
const packageJsonPath = resolve(target.dir, "package.json");
|
|
980
1003
|
if (!existsSync(packageJsonPath)) continue;
|
|
@@ -988,9 +1011,9 @@ function applyStableWorkspaceVersionChanges(root, versions) {
|
|
|
988
1011
|
for (const field of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
|
|
989
1012
|
const values = packageJson[field];
|
|
990
1013
|
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
991
|
-
for (const [dependencyName, version] of
|
|
1014
|
+
for (const [dependencyName, version] of versions.entries()) {
|
|
992
1015
|
if (!(dependencyName in values)) continue;
|
|
993
|
-
const dependencySpec =
|
|
1016
|
+
const dependencySpec = version;
|
|
994
1017
|
if (String(values[dependencyName]) === dependencySpec) continue;
|
|
995
1018
|
values[dependencyName] = dependencySpec;
|
|
996
1019
|
changed = true;
|
|
@@ -1000,20 +1023,7 @@ function applyStableWorkspaceVersionChanges(root, versions) {
|
|
|
1000
1023
|
writeJsonFile(packageJsonPath, packageJson);
|
|
1001
1024
|
}
|
|
1002
1025
|
}
|
|
1003
|
-
|
|
1004
|
-
function stablePackageGitReferences(root, versions) {
|
|
1005
|
-
return new Map(workspacePackages(root).map((pkg) => {
|
|
1006
|
-
const version = versions.get(pkg.name);
|
|
1007
|
-
if (!version) return null;
|
|
1008
|
-
let remote = null;
|
|
1009
|
-
try {
|
|
1010
|
-
remote = originRemoteUrl(pkg.dir);
|
|
1011
|
-
} catch {
|
|
1012
|
-
remote = null;
|
|
1013
|
-
}
|
|
1014
|
-
const manifestRemote = normalizeGitRemoteForManifest(remote ?? "", "preserve-origin");
|
|
1015
|
-
return manifestRemote ? [pkg.name, `${manifestRemote}#${version}`] : null;
|
|
1016
|
-
}).filter((entry) => Boolean(entry)));
|
|
1026
|
+
rewriteProjectInternalDependenciesToStableVersions(root, versions);
|
|
1017
1027
|
}
|
|
1018
1028
|
function gitObjectCommit(repoDir, ref) {
|
|
1019
1029
|
try {
|
|
@@ -1855,12 +1865,7 @@ function failWorkflowRun(root, runId, error, recovery) {
|
|
|
1855
1865
|
releaseWorkflowLock(root, runId);
|
|
1856
1866
|
}
|
|
1857
1867
|
function validatePackageReleaseWorkflows(root, packageNames) {
|
|
1858
|
-
const
|
|
1859
|
-
const missing = checkedOutWorkspacePackageRepos(root).filter((pkg) => packageNames.includes(pkg.name)).filter((pkg) => {
|
|
1860
|
-
const adapter = adapterById.get(pkg.name);
|
|
1861
|
-
const workflow = adapter?.kind === "beam-elixir-rust" && typeof adapter.metadata.hostedVerifyWorkflow === "string" ? String(adapter.metadata.hostedVerifyWorkflow) : ".github/workflows/publish.yml";
|
|
1862
|
-
return !existsSync(resolve(pkg.dir, workflow));
|
|
1863
|
-
}).map((pkg) => pkg.name);
|
|
1868
|
+
const missing = checkedOutWorkspacePackageRepos(root).filter((pkg) => packageNames.includes(pkg.name)).filter((pkg) => !existsSync(resolve(pkg.dir, ".github/workflows/publish.yml"))).map((pkg) => pkg.name);
|
|
1864
1869
|
if (missing.length > 0) {
|
|
1865
1870
|
workflowError("release", "workflow_contract_missing", `Treeseed release requires .github/workflows/publish.yml in: ${missing.join(", ")}.`, {
|
|
1866
1871
|
details: {
|
|
@@ -1870,9 +1875,9 @@ function validatePackageReleaseWorkflows(root, packageNames) {
|
|
|
1870
1875
|
}
|
|
1871
1876
|
}
|
|
1872
1877
|
function releaseWorkflowForPackage(root, packageName) {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
return
|
|
1878
|
+
void root;
|
|
1879
|
+
void packageName;
|
|
1880
|
+
return "publish.yml";
|
|
1876
1881
|
}
|
|
1877
1882
|
function prepareAdapterReleaseMetadata(root, pkg, version) {
|
|
1878
1883
|
const adapter = discoverTreeseedPackageAdapters(root).find((entry) => entry.id === pkg.name || entry.name === pkg.name);
|
|
@@ -1970,8 +1975,10 @@ function assertNoInternalDevReferencesForRepo(root, repoDir, packageNames) {
|
|
|
1970
1975
|
${rendered}`);
|
|
1971
1976
|
}
|
|
1972
1977
|
function backMergeProductionIntoStaging(repoDir, repoName, message) {
|
|
1973
|
-
syncBranchWithOrigin(repoDir, PRODUCTION_BRANCH);
|
|
1974
1978
|
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
1979
|
+
if (!remoteBranchExists(repoDir, PRODUCTION_BRANCH)) {
|
|
1980
|
+
throw new Error(`Remote branch "origin/${PRODUCTION_BRANCH}" does not exist.`);
|
|
1981
|
+
}
|
|
1975
1982
|
checkoutBranch(repoDir, STAGING_BRANCH);
|
|
1976
1983
|
try {
|
|
1977
1984
|
runGit(["merge-base", "--is-ancestor", `origin/${PRODUCTION_BRANCH}`, "HEAD"], { cwd: repoDir, capture: true });
|
|
@@ -2435,6 +2442,180 @@ ${workflow.url}` : "";
|
|
|
2435
2442
|
}
|
|
2436
2443
|
});
|
|
2437
2444
|
}
|
|
2445
|
+
function npmRegistryPackageUrl(packageName) {
|
|
2446
|
+
return `https://registry.npmjs.org/${packageName.replace("/", "%2f")}`;
|
|
2447
|
+
}
|
|
2448
|
+
async function fetchJsonForArtifact(url) {
|
|
2449
|
+
const controller = new AbortController();
|
|
2450
|
+
const timeout = setTimeout(() => controller.abort(), 2e4);
|
|
2451
|
+
try {
|
|
2452
|
+
const response = await fetch(url, {
|
|
2453
|
+
headers: { accept: "application/json" },
|
|
2454
|
+
signal: controller.signal
|
|
2455
|
+
});
|
|
2456
|
+
let json = null;
|
|
2457
|
+
try {
|
|
2458
|
+
json = await response.json();
|
|
2459
|
+
} catch {
|
|
2460
|
+
json = null;
|
|
2461
|
+
}
|
|
2462
|
+
return { ok: response.ok, status: response.status, json };
|
|
2463
|
+
} finally {
|
|
2464
|
+
clearTimeout(timeout);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function hasObjectKey(value, key) {
|
|
2468
|
+
return Boolean(value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key));
|
|
2469
|
+
}
|
|
2470
|
+
async function verifyNpmArtifact(packageName, version) {
|
|
2471
|
+
const url = npmRegistryPackageUrl(packageName);
|
|
2472
|
+
try {
|
|
2473
|
+
const response = await fetchJsonForArtifact(url);
|
|
2474
|
+
const versions = stringRecord(response.json)?.versions;
|
|
2475
|
+
const ok = response.ok && hasObjectKey(versions, version);
|
|
2476
|
+
return {
|
|
2477
|
+
id: `npm:${packageName}:${version}`,
|
|
2478
|
+
kind: "npm",
|
|
2479
|
+
name: packageName,
|
|
2480
|
+
version,
|
|
2481
|
+
url,
|
|
2482
|
+
ok,
|
|
2483
|
+
status: response.status,
|
|
2484
|
+
...ok ? {} : { message: `${packageName}@${version} was not found in npm registry metadata.` }
|
|
2485
|
+
};
|
|
2486
|
+
} catch (error) {
|
|
2487
|
+
return {
|
|
2488
|
+
id: `npm:${packageName}:${version}`,
|
|
2489
|
+
kind: "npm",
|
|
2490
|
+
name: packageName,
|
|
2491
|
+
version,
|
|
2492
|
+
url,
|
|
2493
|
+
ok: false,
|
|
2494
|
+
status: null,
|
|
2495
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
async function verifyDockerHubArtifact(image, version) {
|
|
2500
|
+
const [namespace, repository] = image.split("/");
|
|
2501
|
+
const url = `https://hub.docker.com/v2/repositories/${namespace}/${repository}/tags/${version}`;
|
|
2502
|
+
try {
|
|
2503
|
+
const response = await fetchJsonForArtifact(url);
|
|
2504
|
+
const images = Array.isArray(stringRecord(response.json)?.images) ? stringRecord(response.json)?.images : [];
|
|
2505
|
+
const architectures = new Set(images.map((entry) => stringRecord(entry)).map((entry) => typeof entry?.architecture === "string" ? entry.architecture : null).filter((entry) => Boolean(entry)));
|
|
2506
|
+
const ok = response.ok && architectures.has("amd64") && architectures.has("arm64");
|
|
2507
|
+
return {
|
|
2508
|
+
id: `docker:${image}:${version}`,
|
|
2509
|
+
kind: "docker",
|
|
2510
|
+
name: image,
|
|
2511
|
+
version,
|
|
2512
|
+
url,
|
|
2513
|
+
ok,
|
|
2514
|
+
status: response.status,
|
|
2515
|
+
...ok ? {} : { message: `${image}:${version} was not found on Docker Hub with amd64 and arm64 images.` }
|
|
2516
|
+
};
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
return {
|
|
2519
|
+
id: `docker:${image}:${version}`,
|
|
2520
|
+
kind: "docker",
|
|
2521
|
+
name: image,
|
|
2522
|
+
version,
|
|
2523
|
+
url,
|
|
2524
|
+
ok: false,
|
|
2525
|
+
status: null,
|
|
2526
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
async function verifySimpleRegistryArtifact(input) {
|
|
2531
|
+
try {
|
|
2532
|
+
const response = await fetchJsonForArtifact(input.url);
|
|
2533
|
+
return {
|
|
2534
|
+
id: `${input.kind}:${input.name}:${input.version}`,
|
|
2535
|
+
kind: input.kind,
|
|
2536
|
+
name: input.name,
|
|
2537
|
+
version: input.version,
|
|
2538
|
+
url: input.url,
|
|
2539
|
+
ok: response.ok,
|
|
2540
|
+
status: response.status,
|
|
2541
|
+
...response.ok ? {} : { message: `${input.name} ${input.version} was not found in ${input.kind}.` }
|
|
2542
|
+
};
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
return {
|
|
2545
|
+
id: `${input.kind}:${input.name}:${input.version}`,
|
|
2546
|
+
kind: input.kind,
|
|
2547
|
+
name: input.name,
|
|
2548
|
+
version: input.version,
|
|
2549
|
+
url: input.url,
|
|
2550
|
+
ok: false,
|
|
2551
|
+
status: null,
|
|
2552
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
async function collectPublishedReleaseArtifactChecks(selectedVersions) {
|
|
2557
|
+
const checks = [];
|
|
2558
|
+
const npmPackages = ["@treeseed/sdk", "@treeseed/ui", "@treeseed/core", "@treeseed/admin", "@treeseed/cli", "@treeseed/agent"];
|
|
2559
|
+
for (const packageName of npmPackages) {
|
|
2560
|
+
const version = selectedVersions.get(packageName);
|
|
2561
|
+
if (version) checks.push(await verifyNpmArtifact(packageName, version));
|
|
2562
|
+
}
|
|
2563
|
+
const agentVersion = selectedVersions.get("@treeseed/agent");
|
|
2564
|
+
if (agentVersion) {
|
|
2565
|
+
for (const image of ["treeseed/agent-manager", "treeseed/agent-runner"]) {
|
|
2566
|
+
checks.push(await verifyDockerHubArtifact(image, agentVersion));
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
const apiVersion = selectedVersions.get("@treeseed/api");
|
|
2570
|
+
if (apiVersion) {
|
|
2571
|
+
for (const image of ["treeseed/api", "treeseed/op-runner"]) {
|
|
2572
|
+
checks.push(await verifyDockerHubArtifact(image, apiVersion));
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
const treedxVersion = selectedVersions.get("treedx") ?? selectedVersions.get("@treeseed/treedx");
|
|
2576
|
+
if (treedxVersion) {
|
|
2577
|
+
checks.push(await verifyNpmArtifact("@treeseed/treedx", treedxVersion));
|
|
2578
|
+
checks.push(await verifySimpleRegistryArtifact({
|
|
2579
|
+
kind: "pypi",
|
|
2580
|
+
name: "treedx",
|
|
2581
|
+
version: treedxVersion,
|
|
2582
|
+
url: `https://pypi.org/pypi/treedx/${treedxVersion}/json`
|
|
2583
|
+
}));
|
|
2584
|
+
checks.push(await verifySimpleRegistryArtifact({
|
|
2585
|
+
kind: "crates",
|
|
2586
|
+
name: "treedx",
|
|
2587
|
+
version: treedxVersion,
|
|
2588
|
+
url: `https://crates.io/api/v1/crates/treedx/${treedxVersion}`
|
|
2589
|
+
}));
|
|
2590
|
+
checks.push(await verifySimpleRegistryArtifact({
|
|
2591
|
+
kind: "hex",
|
|
2592
|
+
name: "treedx",
|
|
2593
|
+
version: treedxVersion,
|
|
2594
|
+
url: `https://hex.pm/api/packages/treedx/releases/${treedxVersion}`
|
|
2595
|
+
}));
|
|
2596
|
+
for (const image of ["treeseed/treedx", "treeseed/treedx-profiler"]) {
|
|
2597
|
+
checks.push(await verifyDockerHubArtifact(image, treedxVersion));
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return checks;
|
|
2601
|
+
}
|
|
2602
|
+
async function verifyPublishedReleaseArtifacts(selectedVersions) {
|
|
2603
|
+
let checks = await collectPublishedReleaseArtifactChecks(selectedVersions);
|
|
2604
|
+
const deadline = Date.now() + 5 * 60 * 1e3;
|
|
2605
|
+
while (checks.some((check) => !check.ok) && Date.now() < deadline) {
|
|
2606
|
+
await sleep(15e3);
|
|
2607
|
+
checks = await collectPublishedReleaseArtifactChecks(selectedVersions);
|
|
2608
|
+
}
|
|
2609
|
+
const failures = checks.filter((check) => !check.ok);
|
|
2610
|
+
if (failures.length > 0) {
|
|
2611
|
+
const rendered = failures.map((check) => `${check.id}: ${check.message ?? `registry returned ${check.status ?? "unknown"}`} (${check.url})`).join("\n");
|
|
2612
|
+
workflowError("release", "validation_failed", `Published release artifact verification failed.
|
|
2613
|
+
${rendered}`, {
|
|
2614
|
+
details: { checks }
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
return { checks };
|
|
2618
|
+
}
|
|
2438
2619
|
function assertSessionBranchSafety(operation, session, {
|
|
2439
2620
|
requireCleanPackages = false,
|
|
2440
2621
|
requireCurrentBranch = false,
|
|
@@ -5128,6 +5309,8 @@ ${blockers.join("\n")}`, {
|
|
|
5128
5309
|
}),
|
|
5129
5310
|
{ id: "release-root", description: `Release market ${plannedRelease.rootVersion}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
5130
5311
|
{ id: "publish-wait", description: "Wait for production release workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5312
|
+
{ id: "verify-published-artifacts", description: "Verify immutable registry artifacts exist after publish workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5313
|
+
{ id: "production-hosting", description: "Reconcile and live-verify production hosted resources after publish", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5131
5314
|
{ id: "release-back-merge", description: "Back-merge production release history into staging", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
5132
5315
|
{ id: "workspace-link", description: "Restore local workspace links after release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }
|
|
5133
5316
|
],
|
|
@@ -5167,10 +5350,10 @@ ${blockers.join("\n")}`, {
|
|
|
5167
5350
|
const adapterMetadata = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => ({
|
|
5168
5351
|
name: pkg.name,
|
|
5169
5352
|
version: selectedVersions.get(pkg.name) ?? null,
|
|
5170
|
-
result: selectedVersions.has(pkg.name) ?
|
|
5353
|
+
result: selectedVersions.has(pkg.name) ? { status: "pending-package-release-step" } : { status: "skipped", reason: "no planned version" }
|
|
5171
5354
|
}));
|
|
5172
|
-
const
|
|
5173
|
-
const remainingDevReferences = collectInternalDevReferenceIssues(root, selectedPackageSet).filter((issue) => issue.reason
|
|
5355
|
+
const rootPackageLock = updatePackageLockRootVersion(root, plannedRelease.rootVersion);
|
|
5356
|
+
const remainingDevReferences = collectInternalDevReferenceIssues(root, selectedPackageSet).filter((issue) => !issue.reason.startsWith("lockfile-"));
|
|
5174
5357
|
if (remainingDevReferences.length > 0) {
|
|
5175
5358
|
const rendered = remainingDevReferences.map((issue) => `${issue.repoName}: ${issue.filePath} ${issue.dependencyName ?? ""} ${issue.reason} ${issue.spec}`).join("\n");
|
|
5176
5359
|
throw new Error(`Stable release metadata still contains development references.
|
|
@@ -5179,7 +5362,7 @@ ${rendered}`);
|
|
|
5179
5362
|
return {
|
|
5180
5363
|
versions: Object.fromEntries(allVersions.entries()),
|
|
5181
5364
|
adapterMetadata,
|
|
5182
|
-
|
|
5365
|
+
rootPackageLock,
|
|
5183
5366
|
workspaceUnlink
|
|
5184
5367
|
};
|
|
5185
5368
|
});
|
|
@@ -5187,7 +5370,8 @@ ${rendered}`);
|
|
|
5187
5370
|
for (const pkg of checkedOutWorkspacePackageRepos(root).filter((entry) => selectedPackageSet.has(entry.name))) {
|
|
5188
5371
|
const version = selectedVersions.get(pkg.name);
|
|
5189
5372
|
if (!version) continue;
|
|
5190
|
-
const packageRelease = await executeJournalStep(root, workflowRun.runId, `release-${pkg.name}`, () => {
|
|
5373
|
+
const packageRelease = await executeJournalStep(root, workflowRun.runId, `release-${pkg.name}`, async () => {
|
|
5374
|
+
const metadata = prepareAdapterReleaseMetadata(root, pkg, version);
|
|
5191
5375
|
const changelog = updateReleaseChangelog(pkg.dir, {
|
|
5192
5376
|
version,
|
|
5193
5377
|
sourceRef: `origin/${PRODUCTION_BRANCH}`,
|
|
@@ -5204,19 +5388,36 @@ ${rendered}`);
|
|
|
5204
5388
|
pushBranch(pkg.dir, STAGING_BRANCH);
|
|
5205
5389
|
const promotion = promoteCommitToProductionBranch(pkg.dir, commit.commitSha);
|
|
5206
5390
|
const tag = ensureReleaseTag(pkg.dir, version, commit.commitSha, `release: ${pkg.name} ${version}`);
|
|
5391
|
+
const publishGate = {
|
|
5392
|
+
name: pkg.name,
|
|
5393
|
+
repoPath: pkg.dir,
|
|
5394
|
+
workflow: releaseWorkflowForPackage(root, pkg.name),
|
|
5395
|
+
branch: version,
|
|
5396
|
+
headSha: commit.commitSha
|
|
5397
|
+
};
|
|
5398
|
+
const publishWait2 = await waitForWorkflowGates("release", [publishGate], ciMode, {
|
|
5399
|
+
root,
|
|
5400
|
+
runId: workflowRun.runId,
|
|
5401
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5402
|
+
});
|
|
5403
|
+
const publishedArtifacts2 = await verifyPublishedReleaseArtifacts(/* @__PURE__ */ new Map([[pkg.name, version]]));
|
|
5207
5404
|
return {
|
|
5208
5405
|
name: pkg.name,
|
|
5209
5406
|
path: relative(root, pkg.dir),
|
|
5210
5407
|
version,
|
|
5211
5408
|
changelog,
|
|
5409
|
+
metadata,
|
|
5212
5410
|
commit,
|
|
5213
5411
|
promotion,
|
|
5214
|
-
tag
|
|
5412
|
+
tag,
|
|
5413
|
+
publishWait: publishWait2,
|
|
5414
|
+
publishedArtifacts: publishedArtifacts2
|
|
5215
5415
|
};
|
|
5216
5416
|
});
|
|
5217
5417
|
packageReleases.push(packageRelease);
|
|
5218
5418
|
}
|
|
5219
5419
|
const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
5420
|
+
const rootInstall = runReleaseNpmInstall(root, { workspaceRoot: root });
|
|
5220
5421
|
const changelog = updateReleaseChangelog(repoRoot(root), {
|
|
5221
5422
|
version: plannedRelease.rootVersion,
|
|
5222
5423
|
sourceRef: `origin/${PRODUCTION_BRANCH}`,
|
|
@@ -5240,6 +5441,7 @@ ${rendered}`);
|
|
|
5240
5441
|
version: plannedRelease.rootVersion,
|
|
5241
5442
|
releaseTag: plannedRelease.releaseTag,
|
|
5242
5443
|
changelog,
|
|
5444
|
+
rootInstall,
|
|
5243
5445
|
commit,
|
|
5244
5446
|
promotion,
|
|
5245
5447
|
tag
|
|
@@ -5257,20 +5459,15 @@ ${rendered}`);
|
|
|
5257
5459
|
environment: "prod",
|
|
5258
5460
|
action_kind: "deploy_web"
|
|
5259
5461
|
}
|
|
5260
|
-
})
|
|
5261
|
-
...packageReleases.map((entry) => ({
|
|
5262
|
-
name: String(entry.name),
|
|
5263
|
-
repoPath: resolve(root, String(entry.path)),
|
|
5264
|
-
workflow: releaseWorkflowForPackage(root, String(entry.name)),
|
|
5265
|
-
branch: String(entry.version),
|
|
5266
|
-
headSha: String(entry.commit.commitSha ?? "")
|
|
5267
|
-
}))
|
|
5462
|
+
})
|
|
5268
5463
|
].filter((gate) => gate.headSha);
|
|
5269
5464
|
const publishWait = await executeJournalStep(root, workflowRun.runId, "publish-wait", () => waitForWorkflowGates("release", publishGates, ciMode, {
|
|
5270
5465
|
root,
|
|
5271
5466
|
runId: workflowRun.runId,
|
|
5272
5467
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5273
5468
|
}).then((workflowGates) => ({ workflowGates })));
|
|
5469
|
+
const publishedArtifacts = await executeJournalStep(root, workflowRun.runId, "verify-published-artifacts", () => verifyPublishedReleaseArtifacts(selectedVersions));
|
|
5470
|
+
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release"));
|
|
5274
5471
|
const backMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => {
|
|
5275
5472
|
const packageBackMerges = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => backMergeProductionIntoStaging(pkg.dir, pkg.name, releaseAdminMessage({
|
|
5276
5473
|
subject: `release: back-merge ${PRODUCTION_BRANCH} into ${STAGING_BRANCH}`,
|
|
@@ -5296,6 +5493,8 @@ ${rendered}`);
|
|
|
5296
5493
|
packageReleases,
|
|
5297
5494
|
rootRelease,
|
|
5298
5495
|
publishWait: publishWait.workflowGates,
|
|
5496
|
+
publishedArtifacts,
|
|
5497
|
+
productionHosting,
|
|
5299
5498
|
backMerge,
|
|
5300
5499
|
workspaceLinks,
|
|
5301
5500
|
releasedCommit: String(rootRelease.commit.commitSha ?? ""),
|