chore(windows): track bundled helper manifests

This commit is contained in:
webadderall
2026-03-28 16:17:29 +11:00
parent aec45368c6
commit 58153679dd
4 changed files with 260 additions and 0 deletions
@@ -0,0 +1,21 @@
{
"version": 1,
"platform": "win32",
"arch": "x64",
"helpers": {
"wgc-capture": {
"binaryName": "wgc-capture.exe",
"binarySha256": "72d8f0703a5360a0e911a95bf8839354c877df5fa51294af75ded83a17c0c32e",
"sourceDir": "electron/native/wgc-capture",
"sourceFingerprint": "f30dee6b4956a5b57f4fc4d8d586f3470c793c8f7e3242f4160394debbb3f3be",
"updatedAt": "2026-03-28T05:17:10.449Z"
},
"cursor-monitor": {
"binaryName": "cursor-monitor.exe",
"binarySha256": "916ed064c12a3b861d7721df8b9209d10ec0fdb8078564a2ba3ea8cf4f4354c4",
"sourceDir": "electron/native/cursor-monitor",
"sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e",
"updatedAt": "2026-03-28T05:17:17.697Z"
}
}
}
+25
View File
@@ -2,6 +2,12 @@ import { execSync } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
import {
formatNativeHelperManifestWarning,
updateNativeHelperManifest,
verifyNativeHelperManifest,
} from "./native-helper-manifest.mjs";
const projectRoot = process.cwd();
const sourceDir = path.join(projectRoot, "electron", "native", "cursor-monitor");
const buildDir = path.join(sourceDir, "build");
@@ -13,6 +19,7 @@ const bundledDir = path.join(
process.arch === "arm64" ? "win32-arm64" : "win32-x64",
);
const bundledExePath = path.join(bundledDir, "cursor-monitor.exe");
const helperId = "cursor-monitor";
if (process.platform !== "win32") {
console.log("[build-cursor-monitor] Skipping: host platform is not Windows.");
@@ -79,6 +86,16 @@ function findCmake() {
const cmake = findCmake();
if (!cmake) {
if (existsSync(bundledExePath)) {
const verification = verifyNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "cursor-monitor.exe",
});
if (!verification.ok) {
console.warn(formatNativeHelperManifestWarning("build-cursor-monitor", verification));
}
console.log(`[build-cursor-monitor] Using bundled helper: ${bundledExePath}`);
process.exit(0);
}
@@ -139,6 +156,14 @@ if (existsSync(exePath)) {
mkdirSync(bundledDir, { recursive: true });
copyFileSync(exePath, bundledExePath);
console.log(`[build-cursor-monitor] Staged bundled helper: ${bundledExePath}`);
const manifestPath = updateNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "cursor-monitor.exe",
});
console.log(`[build-cursor-monitor] Updated helper manifest: ${manifestPath}`);
} else {
console.error("[build-cursor-monitor] Expected exe not found at", exePath);
process.exit(1);
+25
View File
@@ -2,6 +2,12 @@ import { execSync } from "node:child_process";
import { copyFileSync, mkdirSync, existsSync, rmSync } from "node:fs";
import path from "node:path";
import {
formatNativeHelperManifestWarning,
updateNativeHelperManifest,
verifyNativeHelperManifest,
} from "./native-helper-manifest.mjs";
const projectRoot = process.cwd();
const sourceDir = path.join(projectRoot, "electron", "native", "wgc-capture");
const buildDir = path.join(sourceDir, "build");
@@ -13,6 +19,7 @@ const bundledDir = path.join(
process.arch === "arm64" ? "win32-arm64" : "win32-x64",
);
const bundledExePath = path.join(bundledDir, "wgc-capture.exe");
const helperId = "wgc-capture";
if (process.platform !== 'win32') {
console.log('[build-windows-capture] Skipping native Windows capture build: host platform is not Windows.');
@@ -79,6 +86,16 @@ function findCmake() {
const cmake = findCmake();
if (!cmake) {
if (existsSync(bundledExePath)) {
const verification = verifyNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "wgc-capture.exe",
});
if (!verification.ok) {
console.warn(formatNativeHelperManifestWarning("build-windows-capture", verification));
}
console.log(`[build-windows-capture] Using bundled helper: ${bundledExePath}`);
process.exit(0);
}
@@ -139,6 +156,14 @@ if (existsSync(exePath)) {
mkdirSync(bundledDir, { recursive: true });
copyFileSync(exePath, bundledExePath);
console.log(`[build-windows-capture] Staged bundled helper: ${bundledExePath}`);
const manifestPath = updateNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath: bundledExePath,
binaryName: "wgc-capture.exe",
});
console.log(`[build-windows-capture] Updated helper manifest: ${manifestPath}`);
} else {
console.error('[build-windows-capture] Expected exe not found at', exePath);
process.exit(1);
+189
View File
@@ -0,0 +1,189 @@
import { createHash } from "node:crypto";
import {
existsSync,
mkdirSync,
readFileSync,
readdirSync,
statSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
const MANIFEST_FILE_NAME = "helpers-manifest.json";
function getPlatformArchDir(platform, arch) {
if (platform !== "win32") {
throw new Error(`Unsupported native helper manifest platform: ${platform}`);
}
return arch === "arm64" ? "win32-arm64" : "win32-x64";
}
function hashBuffer(buffer) {
return createHash("sha256").update(buffer).digest("hex");
}
function collectSourceFiles(sourceDir, rootDir = sourceDir) {
const entries = readdirSync(sourceDir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (entry.name === "build" || entry.name === "bin") {
continue;
}
const absolutePath = path.join(sourceDir, entry.name);
if (entry.isDirectory()) {
files.push(...collectSourceFiles(absolutePath, rootDir));
continue;
}
if (!entry.isFile()) {
continue;
}
files.push(path.relative(rootDir, absolutePath).replaceAll("\\", "/"));
}
files.sort((left, right) => left.localeCompare(right));
return files;
}
function hashSourceTree(sourceDir) {
const hash = createHash("sha256");
for (const relativePath of collectSourceFiles(sourceDir)) {
const absolutePath = path.join(sourceDir, relativePath);
hash.update(relativePath);
hash.update("\n");
hash.update(readFileSync(absolutePath));
hash.update("\n");
}
return hash.digest("hex");
}
function hashFile(filePath) {
return hashBuffer(readFileSync(filePath));
}
export function getNativeHelperManifestPath({ projectRoot, platform = process.platform, arch = process.arch }) {
return path.join(
projectRoot,
"electron",
"native",
"bin",
getPlatformArchDir(platform, arch),
MANIFEST_FILE_NAME,
);
}
function readManifest(manifestPath, platform, arch) {
if (!existsSync(manifestPath)) {
return {
version: 1,
platform,
arch,
helpers: {},
};
}
const manifestContent = JSON.parse(readFileSync(manifestPath, "utf8"));
return {
version: 1,
platform,
arch,
helpers: {},
...manifestContent,
};
}
export function updateNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath,
binaryName,
platform = process.platform,
arch = process.arch,
}) {
const manifestPath = getNativeHelperManifestPath({ projectRoot, platform, arch });
const manifestDir = path.dirname(manifestPath);
mkdirSync(manifestDir, { recursive: true });
const manifest = readManifest(manifestPath, platform, arch);
manifest.helpers[helperId] = {
binaryName,
binarySha256: hashFile(binaryPath),
sourceDir: path.relative(projectRoot, sourceDir).replaceAll("\\", "/"),
sourceFingerprint: hashSourceTree(sourceDir),
updatedAt: new Date().toISOString(),
};
writeFileSync(`${manifestPath}`, `${JSON.stringify(manifest, null, 2)}\n`);
return manifestPath;
}
export function verifyNativeHelperManifest({
projectRoot,
helperId,
sourceDir,
binaryPath,
binaryName,
platform = process.platform,
arch = process.arch,
}) {
const manifestPath = getNativeHelperManifestPath({ projectRoot, platform, arch });
if (!existsSync(manifestPath)) {
return {
ok: false,
manifestPath,
reasons: ["manifest missing"],
};
}
if (!existsSync(binaryPath)) {
return {
ok: false,
manifestPath,
reasons: ["bundled helper missing"],
};
}
const manifest = readManifest(manifestPath, platform, arch);
const helperManifest = manifest.helpers?.[helperId];
if (!helperManifest) {
return {
ok: false,
manifestPath,
reasons: [`${helperId} entry missing`],
};
}
const reasons = [];
if (helperManifest.binaryName !== binaryName) {
reasons.push(`expected binary ${binaryName}, found ${helperManifest.binaryName ?? "unknown"}`);
}
const expectedBinaryHash = helperManifest.binarySha256;
const actualBinaryHash = hashFile(binaryPath);
if (expectedBinaryHash !== actualBinaryHash) {
reasons.push("binary hash mismatch");
}
const expectedSourceFingerprint = helperManifest.sourceFingerprint;
const actualSourceFingerprint = hashSourceTree(sourceDir);
if (expectedSourceFingerprint !== actualSourceFingerprint) {
reasons.push("source fingerprint mismatch");
}
return {
ok: reasons.length === 0,
manifestPath,
reasons,
};
}
export function formatNativeHelperManifestWarning(helperLabel, verificationResult) {
const reasonText = verificationResult.reasons.join(", ");
return `[${helperLabel}] Bundled helper provenance check failed (${reasonText}). Rebuild the helper to refresh ${path.basename(verificationResult.manifestPath)}.`;
}