mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-07-09 17:28:58 +00:00
Ship Go binaries inside per-platform npm packages (@alibaba-group/ocr-{os}-{arch})
so npm install resolves the correct binary via optionalDependencies + os/cpu fields.
This removes the need for a postinstall download from GitHub Releases, which is
extremely slow for users behind restricted networks (e.g. China mainland).
The postinstall download is retained as a fallback for --no-optional installs.
243 lines
6.1 KiB
JavaScript
243 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const os = require("os");
|
|
const https = require("https");
|
|
const { spawnSync } = require("child_process");
|
|
|
|
const {
|
|
IS_WINDOWS,
|
|
BINARY_NAME,
|
|
detectPlatform,
|
|
loadPackageJson,
|
|
buildUrl,
|
|
downloadText,
|
|
downloadBinary,
|
|
computeChecksum,
|
|
} = require("./install.js");
|
|
const packageRoot = path.join(__dirname, "..");
|
|
const binDir = path.join(packageRoot, "bin");
|
|
const binaryPath = path.join(binDir, BINARY_NAME);
|
|
const stateDir = path.join(os.homedir(), ".opencodereview");
|
|
const tsFile = path.join(stateDir, "last-update-check");
|
|
const lockFile = path.join(stateDir, "update.lock");
|
|
|
|
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
|
|
function touchTimestamp() {
|
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
const now = new Date();
|
|
try {
|
|
fs.utimesSync(tsFile, now, now);
|
|
} catch (_) {
|
|
fs.writeFileSync(tsFile, now.toISOString());
|
|
}
|
|
}
|
|
|
|
function acquireLock() {
|
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
try {
|
|
fs.writeFileSync(lockFile, String(process.pid), { flag: "wx" });
|
|
return true;
|
|
} catch (e) {
|
|
if (e.code !== "EEXIST") return false;
|
|
try {
|
|
const pid = parseInt(fs.readFileSync(lockFile, "utf8").trim(), 10);
|
|
process.kill(pid, 0);
|
|
return false;
|
|
} catch (_) {
|
|
try {
|
|
fs.unlinkSync(lockFile);
|
|
fs.writeFileSync(lockFile, String(process.pid), { flag: "wx" });
|
|
return true;
|
|
} catch (_2) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function releaseLock() {
|
|
try {
|
|
fs.unlinkSync(lockFile);
|
|
} catch (_) {}
|
|
}
|
|
|
|
function getInstalledVersion() {
|
|
try {
|
|
const result = spawnSync(binaryPath, ["version"], {
|
|
encoding: "utf8",
|
|
timeout: 3000,
|
|
});
|
|
const match = (result.stdout || "").match(/v(\d+\.\d+(?:\.\d+)?)/);
|
|
return match ? match[1] : null;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function fetchLatestVersion(pkg) {
|
|
const registry = (pkg.publishConfig && pkg.publishConfig.registry) || DEFAULT_REGISTRY;
|
|
const pkgName = pkg.name;
|
|
if (!pkgName) return Promise.resolve(null);
|
|
const encodedName = pkgName.replace(/\//g, "%2F");
|
|
const url = `${registry.replace(/\/$/, "")}/${encodedName}/latest`;
|
|
if (!url.startsWith("https://")) return Promise.resolve(null);
|
|
|
|
return new Promise((resolve) => {
|
|
const options = {
|
|
headers: { "User-Agent": "ocr-updater", Accept: "application/json" },
|
|
timeout: 15000,
|
|
};
|
|
const req = https
|
|
.get(url, options, (res) => {
|
|
if (res.statusCode !== 200) {
|
|
res.resume();
|
|
resolve(null);
|
|
return;
|
|
}
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => {
|
|
try {
|
|
const json = JSON.parse(data);
|
|
resolve(json.version || null);
|
|
} catch (_) {
|
|
resolve(null);
|
|
}
|
|
});
|
|
res.on("error", () => resolve(null));
|
|
})
|
|
.on("error", () => resolve(null));
|
|
req.on("timeout", () => {
|
|
req.destroy();
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
function semverGt(a, b) {
|
|
const pa = a.split(".").map(Number);
|
|
const pb = b.split(".").map(Number);
|
|
for (let i = 0; i < 3; i++) {
|
|
if ((pa[i] || 0) > (pb[i] || 0)) return true;
|
|
if ((pa[i] || 0) < (pb[i] || 0)) return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function cleanupTemp() {
|
|
try {
|
|
const files = fs.readdirSync(binDir);
|
|
for (const f of files) {
|
|
if (f.startsWith(".opencodereview.tmp.")) {
|
|
fs.unlinkSync(path.join(binDir, f));
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
async function main() {
|
|
touchTimestamp();
|
|
|
|
if (!acquireLock()) return;
|
|
|
|
cleanupTemp();
|
|
|
|
try {
|
|
const { resolveNativeBinary } = require("./platform");
|
|
const resolved = resolveNativeBinary();
|
|
if (resolved && resolved.fromPlatformPkg) {
|
|
info("Binary managed by platform package, skipping auto-update.");
|
|
return;
|
|
}
|
|
const installedVersion = getInstalledVersion();
|
|
if (!installedVersion) return;
|
|
|
|
const pkg = loadPackageJson();
|
|
const latestVersion = await fetchLatestVersion(pkg);
|
|
if (!latestVersion) return;
|
|
|
|
if (!semverGt(latestVersion, installedVersion)) return;
|
|
|
|
const { os: platform, arch } = detectPlatform();
|
|
const config = pkg.ocrConfig;
|
|
|
|
const vars = { version: latestVersion, os: platform, arch };
|
|
let downloadUrl = buildUrl(config.urlPattern, vars);
|
|
if (IS_WINDOWS) {
|
|
downloadUrl += ".exe";
|
|
}
|
|
|
|
const tempPath = path.join(binDir, `.opencodereview.tmp.${process.pid}`);
|
|
await downloadBinary(downloadUrl, tempPath);
|
|
if (!IS_WINDOWS) {
|
|
fs.chmodSync(tempPath, 0o755);
|
|
}
|
|
|
|
if (config.checksumPattern) {
|
|
const checksumUrl = buildUrl(config.checksumPattern, vars);
|
|
let shaContent;
|
|
try {
|
|
shaContent = await downloadText(checksumUrl);
|
|
} catch (_) {
|
|
fs.unlinkSync(tempPath);
|
|
return;
|
|
}
|
|
let actualSha;
|
|
try {
|
|
actualSha = await computeChecksum(tempPath);
|
|
} catch (_) {
|
|
fs.unlinkSync(tempPath);
|
|
return;
|
|
}
|
|
|
|
let verified = false;
|
|
for (const line of shaContent.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.includes(`-${platform}-${arch}`)) {
|
|
const expectedSha = trimmed.split(/\s+/)[0].toLowerCase();
|
|
if (expectedSha && actualSha !== expectedSha) {
|
|
fs.unlinkSync(tempPath);
|
|
return;
|
|
}
|
|
verified = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!verified) {
|
|
fs.unlinkSync(tempPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (IS_WINDOWS) {
|
|
const oldPath = binaryPath + ".old";
|
|
try { fs.unlinkSync(oldPath); } catch (_) {}
|
|
try {
|
|
fs.renameSync(binaryPath, oldPath);
|
|
} catch (e) {
|
|
if (fs.existsSync(binaryPath)) {
|
|
throw e;
|
|
}
|
|
}
|
|
try {
|
|
fs.renameSync(tempPath, binaryPath);
|
|
} catch (e) {
|
|
try { fs.renameSync(oldPath, binaryPath); } catch (_) {}
|
|
throw e;
|
|
}
|
|
try { fs.unlinkSync(oldPath); } catch (_) {}
|
|
} else {
|
|
fs.renameSync(tempPath, binaryPath);
|
|
}
|
|
} catch (_) {
|
|
cleanupTemp();
|
|
} finally {
|
|
releaseLock();
|
|
}
|
|
}
|
|
|
|
main().catch(() => {});
|