release: prepare ForgeFlow 0.10.1
ForgeFlow quality gate / quality (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-07-30 00:49:58 +02:00
parent 6ef4620388
commit 60e8aa8fc2
15 changed files with 173 additions and 82 deletions
+44 -3
View File
@@ -17,6 +17,9 @@ app.setPath("userData", userDataPath);
app.whenReady().then(async () => {
try {
const configureAccess = process.argv.includes("--configure-access");
const reconcile = process.argv.includes("--reconcile");
const summaryOnly = process.argv.includes("--summary");
const inventoryOnly = process.argv.includes("--inventory-only");
const repositoryFilter = new Set(String(process.argv.find((value) => value.startsWith("--repository=")) || "")
.slice("--repository=".length).toLowerCase().split(",").map((value) => value.trim()).filter(Boolean));
const store = new ConfigStore(userDataPath);
@@ -29,14 +32,28 @@ app.whenReady().then(async () => {
const reports = [];
for (const server of store.data.servers || []) {
const report = await deployments.scanServerInventory(server.id, repositories);
let reconciliation = null;
if (reconcile) {
for (let attempt = 1; attempt <= 3 && !reconciliation; attempt += 1) {
const preview = await deployments.planServerInventoryReconciliation(server.id, repositories, { autoLink: true });
try {
reconciliation = await deployments.reconcileServerInventory(server.id, repositories, { autoLink: true, expectedPlanId: preview.plan.id });
} catch (error) {
if (error.code !== "RECONCILIATION_PLAN_STALE" || attempt === 3) throw error;
}
}
}
const access = [];
const seenProfiles = new Set();
for (const repository of repositories) {
for (const repository of inventoryOnly || configureAccess ? [] : repositories) {
for (const profile of repository.deploymentProfiles || store.getDeploymentProfiles(repository.fullName) || []) {
if (profile.serverId !== server.id || profile.deploymentMode !== "server-git" || seenProfiles.has(profile.id)) continue;
seenProfiles.add(profile.id);
try {
access.push(await deployments.verifyServerGitProfile({ repository, profileId: profile.id }));
let verification = await deployments.verifyServerGitProfile({ repository, profileId: profile.id });
await deployments.refreshProfileState(repository.fullName, profile.id, verification.branchSha);
verification = await deployments.verifyServerGitProfile({ repository, profileId: profile.id });
access.push(verification);
} catch (error) {
access.push({ repository: repository.fullName, profileId: profile.id, readiness: "Verification incomplete", ready: false, error: error.message });
}
@@ -59,6 +76,13 @@ app.whenReady().then(async () => {
}
reports.push({
server: server.name,
reconciliation: reconciliation ? {
adopted: reconciliation.adopted,
refreshed: reconciliation.refreshed,
retired: reconciliation.retired,
staleProfiles: reconciliation.staleProfiles,
recoverySnapshot: reconciliation.recoverySnapshot,
} : null,
capabilities: report.capabilities,
warnings: (report.warnings || []).map((warning) => String(warning).slice(0, 300)),
summary: {
@@ -67,6 +91,12 @@ app.whenReady().then(async () => {
linked: report.linked,
needsReview: report.needsReview,
},
reviewBreakdown: report.workloads.filter((workload) => !workload.reviewDecision && (workload.classification?.type === "stale-link" || (workload.runtime?.running && workload.classification?.type === "duplicate") || (workload.runtime?.running && !["system-container", "external-container", "temporary-runtime", "backup", "release-folder", "historical-compose", "manually-excluded"].includes(workload.classification?.type) && ["suggested", "ambiguous", "unmatched"].includes(workload.status)))).reduce((counts, workload) => {
const key = `${workload.classification?.type || "unknown"}:${workload.status || "unknown"}`;
counts[key] = (counts[key] || 0) + 1;
return counts;
}, {}),
reviewSamples: report.workloads.filter((workload) => !workload.reviewDecision && (workload.classification?.type === "stale-link" || (workload.runtime?.running && workload.classification?.type === "duplicate") || (workload.runtime?.running && !["system-container", "external-container", "temporary-runtime", "backup", "release-folder", "historical-compose", "manually-excluded"].includes(workload.classification?.type) && ["suggested", "ambiguous", "unmatched"].includes(workload.status)))).slice(0, 30).map((workload) => ({ name: workload.displayName, type: workload.classification?.type, status: workload.status, running: workload.runtime?.running, folder: workload.remoteFolderCandidate, containers: (workload.containers || []).map((container) => container.name) })),
access,
workloads: report.workloads.filter((workload) => workload.link || (workload.runtime?.running && workload.status !== "unmatched")).map((workload) => ({
name: workload.displayName,
@@ -79,7 +109,18 @@ app.whenReady().then(async () => {
})),
});
}
console.log(JSON.stringify(reports, null, 2));
const output = summaryOnly ? reports.map((report) => ({
server: report.server,
capabilities: report.capabilities,
warnings: report.warnings,
summary: report.summary,
reviewBreakdown: Object.fromEntries(Object.entries(report.reviewBreakdown || {}).sort(([left], [right]) => left.localeCompare(right))),
reviewSamples: report.reviewSamples,
reconciliation: report.reconciliation,
access: report.access.map((item) => ({ repository: item.repository, profileId: item.profileId || null, ready: item.ready, readiness: item.readiness || item.action || null, remoteSha: item.remoteSha || item.branchSha || null, liveSha: item.liveSha || null, error: item.error || null })),
review: report.workloads.filter((item) => !item.repository && item.running).map((item) => ({ name: item.name, confidence: item.confidence, folder: item.folder })),
})) : reports;
console.log(JSON.stringify(output, null, 2));
} catch (error) {
console.error(error?.stack || error?.message || String(error));
process.exitCode = 1;
+7 -2
View File
@@ -80,6 +80,7 @@ const required = [
"docs/RELEASE_NOTES_0.9.4.md",
"docs/RELEASE_NOTES_0.9.5.md",
"docs/RELEASE_NOTES_0.10.0.md",
"docs/RELEASE_NOTES_0.10.1.md",
"docs/UPDATING.md",
"docs/DIAGNOSTICS.md",
"docs/DEPLOYMENT_SETUP.md",
@@ -118,9 +119,9 @@ for (const file of required) await access(path.join(root, file));
const packageJson = JSON.parse(
await readFile(path.join(root, "package.json"), "utf8"),
);
if (packageJson.version !== "0.10.0")
if (packageJson.version !== "0.10.1")
throw new Error(
`Expected package version 0.10.0, got ${packageJson.version}.`,
`Expected package version 0.10.1, got ${packageJson.version}.`,
);
const sourceManifest = await readFile(
path.join(root, "SOURCE_MANIFEST.txt"),
@@ -434,6 +435,10 @@ for (const phrase of [
]) {
if (!release0100.includes(phrase)) throw new Error(`0.10.0 release notes are missing: ${phrase}`);
}
const release0101 = await readFile(path.join(root, "docs/RELEASE_NOTES_0.10.1.md"), "utf8");
for (const phrase of ["certificate-free updates", "case-insensitive", "read-only deploy keys", "SHA-256"]) {
if (!release0101.includes(phrase)) throw new Error(`0.10.1 release notes are missing: ${phrase}`);
}
const configSource = await readFile(path.join(root, "src/main/config-store.cjs"), "utf8");
for (const mode of ["server-git", "push-bundle", "monitor-only"]) {
if (!configSource.includes(mode)) throw new Error(`Deployment configuration is missing mode: ${mode}`);