Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions scripts/stage-whisper-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vite-plus/test";

import {
assertPinnedWhisperServerSource,
resolveArchiveExtractionPlan,
resolvePrebuiltArtifact,
runtimeExecutableName,
WHISPER_CPP_COMMIT,
Expand Down Expand Up @@ -45,4 +46,46 @@ describe("stage-whisper-runtime", () => {
expect(runtimeExecutableName("linux")).toBe("whisper-server");
expect(runtimeExecutableName("win")).toBe("whisper-server.exe");
});

it("uses Windows-safe extraction for local tarballs and zip prebuilts", () => {
const sourcePlan = resolveArchiveExtractionPlan(
String.raw`C:\\runner\\whisper.cpp.tar.gz`,
String.raw`C:\\runner\\source`,
"win32",
);
expect(sourcePlan.command).toBe("tar");
expect(sourcePlan.args).toEqual([
"--force-local",
"-xzf",
String.raw`C:\\runner\\whisper.cpp.tar.gz`,
"-C",
String.raw`C:\\runner\\source`,
]);

const prebuiltPlan = resolveArchiveExtractionPlan(
String.raw`C:\\runner\\whisper-bin-x64.zip`,
String.raw`C:\\runner\\prebuilt`,
"win32",
);
expect(prebuiltPlan.command).toBe("powershell.exe");
expect(prebuiltPlan.args).toContain(
"Expand-Archive -LiteralPath $env:SCIENT_WHISPER_ARCHIVE -DestinationPath $env:SCIENT_WHISPER_DESTINATION -Force",
);
expect(prebuiltPlan.env).toEqual({
SCIENT_WHISPER_ARCHIVE: String.raw`C:\\runner\\whisper-bin-x64.zip`,
SCIENT_WHISPER_DESTINATION: String.raw`C:\\runner\\prebuilt`,
});
});

it("keeps POSIX archive extraction on tar", () => {
const plan = resolveArchiveExtractionPlan(
"/tmp/whisper-bin-x64.zip",
"/tmp/prebuilt",
"darwin",
);
expect(plan).toEqual({
args: ["-xf", "/tmp/whisper-bin-x64.zip", "-C", "/tmp/prebuilt"],
command: "tar",
});
});
});
79 changes: 76 additions & 3 deletions scripts/stage-whisper-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,16 @@ interface StageOptions {
readonly verbose: boolean;
}

function run(command: string, args: readonly string[], options: { readonly cwd?: string } = {}) {
interface RunOptions {
readonly cwd?: string;
readonly env?: NodeJS.ProcessEnv;
}

function run(command: string, args: readonly string[], options: RunOptions = {}) {
return new Promise<void>((resolvePromise, reject) => {
const child = NodeChildProcess.spawn(command, [...args], {
...(options.cwd ? { cwd: options.cwd } : {}),
...(options.env ? { env: options.env } : {}),
shell: false,
stdio: "inherit",
});
Expand Down Expand Up @@ -145,8 +151,75 @@ export function runtimeExecutableName(platform: WhisperRuntimePlatform): string
return platform === "win" ? "whisper-server.exe" : "whisper-server";
}

type ArchiveFormat = "tar.gz" | "zip";
type ArchiveHostPlatform = "win32" | "darwin" | "linux";

interface ArchiveExtractionPlan {
readonly args: readonly string[];
readonly command: string;
readonly env?: Readonly<Record<string, string>>;
}

function archiveFormat(fileName: string): ArchiveFormat {
if (fileName.endsWith(".tar.gz")) return "tar.gz";
if (fileName.endsWith(".zip")) return "zip";
throw new Error(`Unsupported whisper runtime archive: ${fileName}`);
}

export function resolveArchiveExtractionPlan(
archive: string,
destination: string,
hostPlatform: ArchiveHostPlatform,
): ArchiveExtractionPlan {
const format = archiveFormat(archive);
if (format === "zip" && hostPlatform === "win32") {
return {
args: [
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-Command",
"Expand-Archive -LiteralPath $env:SCIENT_WHISPER_ARCHIVE -DestinationPath $env:SCIENT_WHISPER_DESTINATION -Force",
],
command: "powershell.exe",
env: {
SCIENT_WHISPER_ARCHIVE: archive,
SCIENT_WHISPER_DESTINATION: destination,
},
};
}

return {
args: [
...(hostPlatform === "win32" ? ["--force-local"] : []),
...(format === "tar.gz" ? ["-xzf"] : ["-xf"]),
archive,
"-C",
destination,
],
command: "tar",
};
}

async function extractArchive(
archive: string,
destination: string,
hostPlatform: ArchiveHostPlatform = process.platform === "win32"
? "win32"
: process.platform === "darwin"
? "darwin"
: "linux",
): Promise<void> {
const plan = resolveArchiveExtractionPlan(archive, destination, hostPlatform);
if (plan.env) {
await run(plan.command, plan.args, { env: { ...process.env, ...plan.env } });
return;
}
await run(plan.command, plan.args);
}

async function extractSource(archive: string, workspace: string): Promise<string> {
await run("tar", ["-xzf", archive, "-C", workspace]);
await extractArchive(archive, workspace);
const sourceDirectory = NodePath.join(workspace, `whisper.cpp-${WHISPER_CPP_COMMIT}`);
const serverSource = await NodeFSP.readFile(
NodePath.join(sourceDirectory, "examples/server/server.cpp"),
Expand Down Expand Up @@ -205,7 +278,7 @@ async function stagePrebuiltRuntime(
): Promise<void> {
const extracted = NodePath.join(workspace, "prebuilt");
await NodeFSP.mkdir(extracted);
await run("tar", ["-xf", archive, "-C", extracted]);
await extractArchive(archive, extracted);
if (platform === "win") {
const releaseDirectory = NodePath.join(extracted, "Release");
for (const file of await NodeFSP.readdir(releaseDirectory)) {
Expand Down
Loading