@treeseed/sdk 0.12.5 → 0.12.6
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;
|
|
@@ -1003,27 +1013,41 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1003
1013
|
status: string;
|
|
1004
1014
|
adapter: string;
|
|
1005
1015
|
command: string;
|
|
1016
|
+
packageLock?: undefined;
|
|
1017
|
+
reason?: undefined;
|
|
1006
1018
|
} | {
|
|
1007
1019
|
status: string;
|
|
1008
|
-
reason: string;
|
|
1009
1020
|
adapter: string;
|
|
1021
|
+
packageLock: {
|
|
1022
|
+
status: string;
|
|
1023
|
+
reason: string;
|
|
1024
|
+
path?: undefined;
|
|
1025
|
+
} | {
|
|
1026
|
+
status: string;
|
|
1027
|
+
path: string;
|
|
1028
|
+
reason?: undefined;
|
|
1029
|
+
};
|
|
1010
1030
|
command?: undefined;
|
|
1031
|
+
reason?: undefined;
|
|
1011
1032
|
} | {
|
|
1012
1033
|
status: string;
|
|
1013
|
-
reason: null;
|
|
1014
1034
|
adapter: string;
|
|
1035
|
+
reason: string;
|
|
1015
1036
|
command?: undefined;
|
|
1037
|
+
packageLock?: undefined;
|
|
1016
1038
|
} | {
|
|
1017
1039
|
status: string;
|
|
1018
1040
|
reason: string;
|
|
1019
1041
|
};
|
|
1020
1042
|
}[];
|
|
1021
|
-
|
|
1043
|
+
rootPackageLock: {
|
|
1022
1044
|
status: string;
|
|
1023
1045
|
reason: string;
|
|
1046
|
+
path?: undefined;
|
|
1024
1047
|
} | {
|
|
1025
1048
|
status: string;
|
|
1026
|
-
|
|
1049
|
+
path: string;
|
|
1050
|
+
reason?: undefined;
|
|
1027
1051
|
};
|
|
1028
1052
|
workspaceUnlink: import("../operations/services/workspace-dependency-mode.js").WorkspaceDependencyModeReport;
|
|
1029
1053
|
};
|
|
@@ -1077,6 +1101,62 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1077
1101
|
timeoutSeconds: number | null;
|
|
1078
1102
|
cached: boolean;
|
|
1079
1103
|
}[];
|
|
1104
|
+
publishedArtifacts: {
|
|
1105
|
+
checks: PublishedArtifactCheck[];
|
|
1106
|
+
};
|
|
1107
|
+
productionHosting: {
|
|
1108
|
+
status: "skipped";
|
|
1109
|
+
reason: string;
|
|
1110
|
+
environment: "staging" | "prod";
|
|
1111
|
+
selectedApps: string[];
|
|
1112
|
+
selectedResources: {
|
|
1113
|
+
id: string;
|
|
1114
|
+
host: string;
|
|
1115
|
+
serviceType: string;
|
|
1116
|
+
placement: import("../hosting/contracts.js").TreeseedServicePlacement;
|
|
1117
|
+
serviceName: string | null;
|
|
1118
|
+
}[];
|
|
1119
|
+
reconcile?: undefined;
|
|
1120
|
+
postApplyStatus?: undefined;
|
|
1121
|
+
liveVerification?: undefined;
|
|
1122
|
+
} | {
|
|
1123
|
+
status: "reconciled";
|
|
1124
|
+
environment: "staging" | "prod";
|
|
1125
|
+
selectedApps: string[];
|
|
1126
|
+
selectedResources: {
|
|
1127
|
+
id: string;
|
|
1128
|
+
host: string;
|
|
1129
|
+
serviceType: string;
|
|
1130
|
+
placement: import("../hosting/contracts.js").TreeseedServicePlacement;
|
|
1131
|
+
serviceName: string | null;
|
|
1132
|
+
}[];
|
|
1133
|
+
reconcile: {
|
|
1134
|
+
target: TreeseedReconcileTarget;
|
|
1135
|
+
units: import("../reconcile/contracts.js").TreeseedDesiredUnit[];
|
|
1136
|
+
plans: import("../reconcile/contracts.js").TreeseedReconcilePlan[];
|
|
1137
|
+
results: TreeseedReconcileResult[];
|
|
1138
|
+
state: import("../reconcile/contracts.js").TreeseedReconcileStateRecord;
|
|
1139
|
+
timings: import("../timing.js").TreeseedTimingEntry[];
|
|
1140
|
+
};
|
|
1141
|
+
postApplyStatus: {
|
|
1142
|
+
target: TreeseedReconcileTarget;
|
|
1143
|
+
ready: boolean;
|
|
1144
|
+
blockers: string[];
|
|
1145
|
+
warnings: string[];
|
|
1146
|
+
units: {
|
|
1147
|
+
unitId: string;
|
|
1148
|
+
unitType: import("../reconcile/contracts.js").TreeseedReconcileUnitType;
|
|
1149
|
+
provider: string;
|
|
1150
|
+
status: import("../reconcile/contracts.js").TreeseedReconcileStatusKind;
|
|
1151
|
+
exists: boolean;
|
|
1152
|
+
locators: Record<string, string | null>;
|
|
1153
|
+
warnings: string[];
|
|
1154
|
+
verification: import("../reconcile/contracts.js").TreeseedUnitVerificationResult | null;
|
|
1155
|
+
}[];
|
|
1156
|
+
};
|
|
1157
|
+
liveVerification: import("../workflow-support.js").TreeseedLiveHostedServiceCheckReport;
|
|
1158
|
+
reason?: undefined;
|
|
1159
|
+
};
|
|
1080
1160
|
backMerge: {
|
|
1081
1161
|
packages: {
|
|
1082
1162
|
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);
|
|
@@ -1882,9 +1887,9 @@ function prepareAdapterReleaseMetadata(root, pkg, version) {
|
|
|
1882
1887
|
}
|
|
1883
1888
|
if (existsSync(resolve(pkg.dir, "package.json"))) {
|
|
1884
1889
|
return {
|
|
1885
|
-
status: "
|
|
1890
|
+
status: "updated",
|
|
1886
1891
|
adapter: adapter?.id ?? pkg.name,
|
|
1887
|
-
|
|
1892
|
+
packageLock: updatePackageLockRootVersion(pkg.dir, version)
|
|
1888
1893
|
};
|
|
1889
1894
|
}
|
|
1890
1895
|
return { status: "skipped", adapter: adapter?.id ?? pkg.name, reason: "no package metadata updater" };
|
|
@@ -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
|
],
|
|
@@ -5169,8 +5352,8 @@ ${blockers.join("\n")}`, {
|
|
|
5169
5352
|
version: selectedVersions.get(pkg.name) ?? null,
|
|
5170
5353
|
result: selectedVersions.has(pkg.name) ? prepareAdapterReleaseMetadata(root, pkg, selectedVersions.get(pkg.name)) : { 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 !== "lockfile-git-release-ref");
|
|
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,7 @@ ${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 () => {
|
|
5191
5374
|
const changelog = updateReleaseChangelog(pkg.dir, {
|
|
5192
5375
|
version,
|
|
5193
5376
|
sourceRef: `origin/${PRODUCTION_BRANCH}`,
|
|
@@ -5204,6 +5387,19 @@ ${rendered}`);
|
|
|
5204
5387
|
pushBranch(pkg.dir, STAGING_BRANCH);
|
|
5205
5388
|
const promotion = promoteCommitToProductionBranch(pkg.dir, commit.commitSha);
|
|
5206
5389
|
const tag = ensureReleaseTag(pkg.dir, version, commit.commitSha, `release: ${pkg.name} ${version}`);
|
|
5390
|
+
const publishGate = {
|
|
5391
|
+
name: pkg.name,
|
|
5392
|
+
repoPath: pkg.dir,
|
|
5393
|
+
workflow: releaseWorkflowForPackage(root, pkg.name),
|
|
5394
|
+
branch: version,
|
|
5395
|
+
headSha: commit.commitSha
|
|
5396
|
+
};
|
|
5397
|
+
const publishWait2 = await waitForWorkflowGates("release", [publishGate], ciMode, {
|
|
5398
|
+
root,
|
|
5399
|
+
runId: workflowRun.runId,
|
|
5400
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5401
|
+
});
|
|
5402
|
+
const publishedArtifacts2 = await verifyPublishedReleaseArtifacts(/* @__PURE__ */ new Map([[pkg.name, version]]));
|
|
5207
5403
|
return {
|
|
5208
5404
|
name: pkg.name,
|
|
5209
5405
|
path: relative(root, pkg.dir),
|
|
@@ -5211,7 +5407,9 @@ ${rendered}`);
|
|
|
5211
5407
|
changelog,
|
|
5212
5408
|
commit,
|
|
5213
5409
|
promotion,
|
|
5214
|
-
tag
|
|
5410
|
+
tag,
|
|
5411
|
+
publishWait: publishWait2,
|
|
5412
|
+
publishedArtifacts: publishedArtifacts2
|
|
5215
5413
|
};
|
|
5216
5414
|
});
|
|
5217
5415
|
packageReleases.push(packageRelease);
|
|
@@ -5257,20 +5455,15 @@ ${rendered}`);
|
|
|
5257
5455
|
environment: "prod",
|
|
5258
5456
|
action_kind: "deploy_web"
|
|
5259
5457
|
}
|
|
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
|
-
}))
|
|
5458
|
+
})
|
|
5268
5459
|
].filter((gate) => gate.headSha);
|
|
5269
5460
|
const publishWait = await executeJournalStep(root, workflowRun.runId, "publish-wait", () => waitForWorkflowGates("release", publishGates, ciMode, {
|
|
5270
5461
|
root,
|
|
5271
5462
|
runId: workflowRun.runId,
|
|
5272
5463
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5273
5464
|
}).then((workflowGates) => ({ workflowGates })));
|
|
5465
|
+
const publishedArtifacts = await executeJournalStep(root, workflowRun.runId, "verify-published-artifacts", () => verifyPublishedReleaseArtifacts(selectedVersions));
|
|
5466
|
+
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release"));
|
|
5274
5467
|
const backMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => {
|
|
5275
5468
|
const packageBackMerges = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => backMergeProductionIntoStaging(pkg.dir, pkg.name, releaseAdminMessage({
|
|
5276
5469
|
subject: `release: back-merge ${PRODUCTION_BRANCH} into ${STAGING_BRANCH}`,
|
|
@@ -5296,6 +5489,8 @@ ${rendered}`);
|
|
|
5296
5489
|
packageReleases,
|
|
5297
5490
|
rootRelease,
|
|
5298
5491
|
publishWait: publishWait.workflowGates,
|
|
5492
|
+
publishedArtifacts,
|
|
5493
|
+
productionHosting,
|
|
5299
5494
|
backMerge,
|
|
5300
5495
|
workspaceLinks,
|
|
5301
5496
|
releasedCommit: String(rootRelease.commit.commitSha ?? ""),
|