198 lines
5.8 KiB
JavaScript
198 lines
5.8 KiB
JavaScript
const { createHash } = require("node:crypto");
|
|
const {
|
|
chmod,
|
|
mkdir,
|
|
readFile,
|
|
rename,
|
|
rm,
|
|
stat,
|
|
writeFile,
|
|
} = require("node:fs/promises");
|
|
const { existsSync } = require("node:fs");
|
|
const { join } = require("node:path");
|
|
const { spawnSync } = require("node:child_process");
|
|
const tar = require("tar");
|
|
const { sha256File } = require("./file-hash.cjs");
|
|
const {
|
|
opencodeVersion,
|
|
prepareBundledOpenCodeConfig,
|
|
} = require("./opencode-config.cjs");
|
|
const { npmInvocation } = require("./npm-invocation.cjs");
|
|
|
|
const architectureNames = {
|
|
1: "x64",
|
|
3: "arm64",
|
|
};
|
|
const platformNames = {
|
|
darwin: "darwin",
|
|
linux: "linux",
|
|
win32: "windows",
|
|
};
|
|
|
|
function sha512Integrity(contents) {
|
|
return `sha512-${createHash("sha512").update(contents).digest("base64")}`;
|
|
}
|
|
|
|
async function lockedIntegrity(projectDir, packageName) {
|
|
const lock = JSON.parse(
|
|
await readFile(join(projectDir, "package-lock.json"), "utf8"),
|
|
);
|
|
const entry = lock.packages?.[`node_modules/${packageName}`];
|
|
if (
|
|
entry?.version !== opencodeVersion ||
|
|
typeof entry.integrity !== "string"
|
|
) {
|
|
throw new Error(
|
|
`Missing locked ${packageName}@${opencodeVersion} integrity`,
|
|
);
|
|
}
|
|
return entry.integrity;
|
|
}
|
|
|
|
async function downloadPackage(projectDir, packageName, integrity) {
|
|
const cacheDirectory = join(projectDir, ".runtime-resources", "cache");
|
|
await mkdir(cacheDirectory, { recursive: true });
|
|
const archivePath = join(
|
|
cacheDirectory,
|
|
`${packageName}-${opencodeVersion}.tgz`,
|
|
);
|
|
if (existsSync(archivePath)) {
|
|
const cached = await readFile(archivePath);
|
|
if (sha512Integrity(cached) === integrity) {
|
|
return archivePath;
|
|
}
|
|
await rm(archivePath, { force: true });
|
|
}
|
|
|
|
const npm = npmInvocation(projectDir);
|
|
const result = spawnSync(
|
|
npm.command,
|
|
[
|
|
...npm.prefixArgs,
|
|
"pack",
|
|
`${packageName}@${opencodeVersion}`,
|
|
"--ignore-scripts",
|
|
"--json",
|
|
"--pack-destination",
|
|
cacheDirectory,
|
|
],
|
|
{
|
|
cwd: projectDir,
|
|
encoding: "utf8",
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
shell: false,
|
|
windowsHide: true,
|
|
},
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
`Unable to fetch ${packageName}: ${
|
|
result.error?.message ||
|
|
result.stderr ||
|
|
result.stdout ||
|
|
`npm exited with ${result.status}`
|
|
}`,
|
|
);
|
|
}
|
|
const output = JSON.parse(result.stdout);
|
|
const downloadedPath = join(cacheDirectory, output[0].filename);
|
|
const contents = await readFile(downloadedPath);
|
|
if (sha512Integrity(contents) !== integrity) {
|
|
await rm(downloadedPath, { force: true });
|
|
throw new Error(`Integrity verification failed for ${packageName}`);
|
|
}
|
|
if (downloadedPath !== archivePath) {
|
|
await rm(archivePath, { force: true });
|
|
await rename(downloadedPath, archivePath);
|
|
}
|
|
return archivePath;
|
|
}
|
|
|
|
module.exports = async function prepareBundledRuntimes(context) {
|
|
const platform = context.electronPlatformName;
|
|
const architecture = architectureNames[context.arch];
|
|
const packagePlatform = platformNames[platform];
|
|
if (!architecture || !packagePlatform) {
|
|
throw new Error(
|
|
`Bundled OpenCode does not support ${platform}/${context.arch}`,
|
|
);
|
|
}
|
|
|
|
const suffix =
|
|
architecture === "x64" ? `${architecture}-baseline` : architecture;
|
|
const packageName = `opencode-${packagePlatform}-${suffix}`;
|
|
const projectDir = context.packager.projectDir;
|
|
await prepareBundledOpenCodeConfig(projectDir);
|
|
const projectPackage = JSON.parse(
|
|
await readFile(join(projectDir, "package.json"), "utf8"),
|
|
);
|
|
await writeFile(
|
|
join(projectDir, "out", "main", "package.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
name: "@deepseek-ai/dsh-llm",
|
|
version: projectPackage.dependencies["@deepseek-ai/dsh-llm"],
|
|
private: true,
|
|
type: "module",
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf8",
|
|
);
|
|
const integrity = await lockedIntegrity(projectDir, packageName);
|
|
const targetDirectory = join(projectDir, ".runtime-resources", architecture);
|
|
const readyPath = join(targetDirectory, ".ready.json");
|
|
const executable = platform === "win32" ? "opencode.exe" : "opencode";
|
|
const preparedPath = join(targetDirectory, executable);
|
|
const identity = {
|
|
packageName,
|
|
version: opencodeVersion,
|
|
integrity,
|
|
};
|
|
try {
|
|
const ready = JSON.parse(await readFile(readyPath, "utf8"));
|
|
if (
|
|
ready.packageName === identity.packageName &&
|
|
ready.version === identity.version &&
|
|
ready.integrity === identity.integrity &&
|
|
typeof ready.executableSha256 === "string" &&
|
|
(platform === "win32" ||
|
|
((await stat(preparedPath)).mode & 0o111) !== 0) &&
|
|
(await sha256File(preparedPath)) === ready.executableSha256
|
|
) {
|
|
return;
|
|
}
|
|
} catch {
|
|
// Rebuild an incomplete or stale runtime cache.
|
|
}
|
|
|
|
const archivePath = await downloadPackage(projectDir, packageName, integrity);
|
|
const stagingDirectory = `${targetDirectory}.staging-${process.pid}`;
|
|
await rm(stagingDirectory, { recursive: true, force: true });
|
|
await mkdir(stagingDirectory, { recursive: true });
|
|
try {
|
|
await tar.x({
|
|
file: archivePath,
|
|
cwd: stagingDirectory,
|
|
strip: 1,
|
|
});
|
|
const sourcePath = join(stagingDirectory, "bin", executable);
|
|
const stagingExecutable = join(stagingDirectory, executable);
|
|
await rename(sourcePath, stagingExecutable);
|
|
if (platform !== "win32") {
|
|
await chmod(stagingExecutable, 0o755);
|
|
}
|
|
const executableSha256 = await sha256File(stagingExecutable);
|
|
await writeFile(
|
|
join(stagingDirectory, ".ready.json"),
|
|
JSON.stringify({ ...identity, executableSha256 }),
|
|
"utf8",
|
|
);
|
|
await rm(targetDirectory, { recursive: true, force: true });
|
|
await rename(stagingDirectory, targetDirectory);
|
|
} finally {
|
|
await rm(stagingDirectory, { recursive: true, force: true });
|
|
}
|
|
};
|