mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-09 15:33:31 +00:00
251 lines
7.8 KiB
JavaScript
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");
|
|
});
|
|
}
|
|
});
|