Files
Recordly/electron/releaseDistributionGate.test.mjs
webadderall 48de72ccd7 Fix Recordly 2.0 part 2 follow-up review findings
Preserve existing PR history and integrate validated second-cycle fixes.
2026-11-08 11:03:10 +11:00

251 lines
7.8 KiB
JavaScript

import * as fs from "node:fs";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { runInNewContext } from "node:vm";
import ts from "typescript";
import { afterEach, describe, expect, it } from "vitest";
// js-yaml is included by the repository's existing Electron build toolchain.
const { load } = createRequire(import.meta.url)("js-yaml");
const workflow = load(
readFileSync(new URL("../.github/workflows/release.yml", import.meta.url), "utf8"),
);
function matchingStep(steps, command) {
return steps.findIndex((step) => step.run?.includes(command));
}
describe("macOS published distribution gate", () => {
for (const arch of ["x64", "arm64"]) {
it(`requires verified signing and notarization before ${arch} artifacts reach publication`, () => {
const jobName = `build-macos-${arch}`;
const job = workflow.jobs[jobName];
const steps = job.steps;
const build = matchingStep(steps, "electron-builder --mac");
const verify = matchingStep(steps, "npm run verify:macos-distribution");
const checksums = matchingStep(steps, "npm run checksums:release");
const upload = steps.findIndex((step) => step.with?.name === `macos-${arch}-release`);
expect(build).toBeGreaterThan(-1);
expect(steps[build].run).toContain("-c.mac.forceCodeSigning=true");
expect(steps[build].run).toContain("-c.mac.notarize=true");
expect(steps[build].run).toContain("--publish never");
expect(verify).toBeGreaterThan(build);
expect(verify).toBeLessThan(checksums);
expect(verify).toBeLessThan(upload);
expect(steps[verify].if).toBeUndefined();
expect(steps[verify]["continue-on-error"]).not.toBe(true);
expect(steps[verify].run).toContain(`--arch ${arch}`);
expect(steps[verify].run).toContain('--team-id "$APPLE_TEAM_ID"');
expect(job.env.APPLE_TEAM_ID).toContain("secrets.APPLE_TEAM_ID");
expect(steps[upload].with.path).toContain(
`release/macos-distribution-report-${arch}.json`,
);
const publish = workflow.jobs["publish-release-assets"];
expect(publish.needs).toContain(jobName);
expect(publish.if).toContain(`needs.${jobName}.result == 'success'`);
});
}
});
const smokeSource = readFileSync(
new URL("../scripts/smoke-packaged-binaries.mjs", import.meta.url),
"utf8",
);
const smokeProgram = ts.transpileModule(smokeSource, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
esModuleInterop: true,
},
}).outputText;
const smokeDirectories = [];
afterEach(() => {
for (const directory of smokeDirectories.splice(0)) {
fs.rmSync(directory, { recursive: true, force: true });
}
});
function smokeFixture(platform = "darwin") {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "recordly-packaged-smoke-"));
smokeDirectories.push(directory);
fs.writeFileSync(
path.join(directory, "package.json"),
JSON.stringify({ name: "recordly", productName: "Recordly" }),
);
return {
directory,
addArtifact(arch, packages, libraries) {
const app = path.join(
directory,
`artifact-${arch}`,
...(platform === "darwin" ? ["Recordly.app", "Contents"] : []),
);
const resources = path.join(app, platform === "darwin" ? "Resources" : "resources");
const unpacked = path.join(resources, "app.asar.unpacked");
const put = (file, value = "fixture") => {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, value, { mode: 0o755 });
};
put(
path.join(
app,
...(platform === "darwin"
? ["MacOS", "Recordly"]
: [platform === "win32" ? "Recordly.exe" : "recordly"]),
),
);
put(
path.join(resources, "licenses", "manifest.json"),
JSON.stringify({ platform, arch }),
);
put(
path.join(
unpacked,
"node_modules",
"ffmpeg-static",
platform === "win32" ? "ffmpeg.exe" : "ffmpeg",
),
);
for (const name of packages) {
const module = path.join(unpacked, "node_modules", name);
put(path.join(module, "sherpa-onnx.node"));
for (const library of libraries) put(path.join(module, library));
}
const helperNames =
platform === "darwin"
? [
"recordly-permission-drag-helper",
"recordly-screencapturekit-helper",
"recordly-window-list",
"recordly-system-cursors",
"recordly-native-cursor-monitor",
"whisper-cli",
"whisper-runtime.json",
]
: platform === "win32"
? [
"wgc-capture.exe",
"cursor-monitor.exe",
"recordly-gpu-export.exe",
"helpers-manifest.json",
"whisper-cli.exe",
"whisper-runtime.json",
"recordly-nvidia-cuda-compositor.exe",
]
: ["whisper-cli", "whisper-runtime.json"];
for (const helperArch of ["arm64", "x64"]) {
for (const helper of helperNames)
put(
path.join(
unpacked,
"electron",
"native",
"bin",
`${platform}-${helperArch}`,
helper,
),
);
}
},
run() {
const messages = [];
runInNewContext(smokeProgram, {
exports: {},
require(name) {
if (name === "node:fs") return fs;
if (name === "node:path") return path;
if (name === "node:child_process")
return { execFileSync: () => "ffmpeg version fixture\n" };
if (name === "./verify-distribution-notices.mjs")
return {
verifyPackagedDistributionNotices: (resources) =>
JSON.parse(
fs.readFileSync(
path.join(resources, "licenses", "manifest.json"),
"utf8",
),
),
};
throw new Error(`Unexpected smoke dependency: ${name}`);
},
process: {
cwd: () => directory,
platform,
arch: "arm64",
env: { RECORDLY_RELEASE_DIR: directory },
},
console: { log: (message) => messages.push(message) },
});
return messages;
},
};
}
describe("packaged caption runtime validation", () => {
it("checks each macOS artifact's target package when both architectures share an output directory", () => {
const fixture = smokeFixture();
for (const arch of ["arm64", "x64"])
fixture.addArtifact(
arch,
[`sherpa-onnx-darwin-${arch}`],
["libonnxruntime.dylib", "libsherpa-onnx-c-api.dylib"],
);
expect(fixture.run().at(-1)).toContain("packaged binary path smoke passed");
});
it("rejects a macOS x64 artifact containing only the build host's arm64 caption package", () => {
const fixture = smokeFixture();
fixture.addArtifact(
"arm64",
["sherpa-onnx-darwin-arm64"],
["libonnxruntime.dylib", "libsherpa-onnx-c-api.dylib"],
);
fixture.addArtifact(
"x64",
["sherpa-onnx-darwin-arm64"],
["libonnxruntime.dylib", "libsherpa-onnx-c-api.dylib"],
);
expect(() => fixture.run()).toThrow(/Parakeet.*missing/);
});
for (const [platform, packageName, core, api, provider] of [
[
"win32",
"sherpa-onnx-win-arm64",
"onnxruntime.dll",
"sherpa-onnx-c-api.dll",
"onnxruntime_providers_shared.dll",
],
[
"darwin",
"sherpa-onnx-darwin-arm64",
"libonnxruntime.1.23.2.dylib",
"libsherpa-onnx-c-api.dylib",
"libonnxruntime_providers_shared.dylib",
],
[
"linux",
"sherpa-onnx-linux-arm64",
"libonnxruntime.so.1.23.2",
"libsherpa-onnx-c-api.so",
"libonnxruntime_providers_shared.so",
],
]) {
it(`rejects a ${platform} provider library when the core ONNX Runtime library is missing`, () => {
const fixture = smokeFixture(platform);
fixture.addArtifact("arm64", [packageName], [provider, api]);
expect(() => fixture.run()).toThrow(/Parakeet library onnxruntime is missing/);
});
it(`accepts the ${platform} core library with a provider library beside it`, () => {
const fixture = smokeFixture(platform);
fixture.addArtifact("arm64", [packageName], [provider, core, api]);
expect(fixture.run().at(-1)).toContain("packaged binary path smoke passed");
});
}
});