mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-07-10 01:39:12 +00:00
223 lines
7.4 KiB
JavaScript
223 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
/**
|
|
* postinstall script for @ali/open-code-review
|
|
* Downloads the pre-built Go binary matching the current platform.
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { execSync } = require("child_process");
|
|
const https = require("https");
|
|
const crypto = require("crypto");
|
|
|
|
// ── Configuration ────────────────────────────────────────────────────────────
|
|
const REPO_BASE = "https://code.alibaba-inc.com/open-code-review/internal-release/raw/master";
|
|
const BINARY_NAME = "opencodereview";
|
|
|
|
// Resolve paths relative to package root (parent of scripts/)
|
|
const binDir = path.join(__dirname, "..", "bin");
|
|
const binaryDest = path.join(binDir, BINARY_NAME);
|
|
|
|
// ── Logging ──────────────────────────────────────────────────────────────────
|
|
function info(msg) {
|
|
console.log(`[INFO] ${msg}`);
|
|
}
|
|
|
|
function warn(msg) {
|
|
console.warn(`[WARN] ${msg}`);
|
|
}
|
|
|
|
function error(msg) {
|
|
console.error(`[ERROR] ${msg}`);
|
|
}
|
|
|
|
// ── Detect OS & Arch ─────────────────────────────────────────────────────────
|
|
function detectPlatform() {
|
|
const os = process.platform;
|
|
let arch = process.arch;
|
|
|
|
// Map Node.js arch to Go arch naming used in release artifacts
|
|
switch (arch) {
|
|
case "x64":
|
|
arch = "amd64";
|
|
break;
|
|
case "arm64":
|
|
arch = "arm64";
|
|
break;
|
|
default:
|
|
throw new Error(
|
|
`Unsupported architecture: ${arch}. Supported: amd64 (x64), arm64`
|
|
);
|
|
}
|
|
|
|
if (os !== "linux" && os !== "darwin") {
|
|
throw new Error(
|
|
`Unsupported operating system: ${os}. Supported: linux, darwin`
|
|
);
|
|
}
|
|
|
|
return { os, arch };
|
|
}
|
|
|
|
// ── Resolve version ──────────────────────────────────────────────────────────
|
|
async function resolveVersion() {
|
|
const envVersion = process.env.OCR_VERSION;
|
|
if (envVersion) {
|
|
const v = envVersion.startsWith("v") ? envVersion : `v${envVersion}`;
|
|
info(`Using pinned version from OCR_VERSION: ${v}`);
|
|
return v;
|
|
}
|
|
|
|
try {
|
|
info("Fetching latest version...");
|
|
const body = await downloadText(`${REPO_BASE}/VERSION`);
|
|
const lines = body.trim().split("\n").map(l => l.trim()).filter(Boolean);
|
|
if (lines.length > 0) {
|
|
const version = lines[lines.length - 1];
|
|
info(`Latest version: ${version}`);
|
|
return version;
|
|
}
|
|
} catch (e) {
|
|
warn(`Failed to fetch VERSION endpoint: ${e.message}`);
|
|
}
|
|
|
|
throw new Error(
|
|
"Cannot determine version. Set OCR_VERSION env variable to pin a specific version."
|
|
);
|
|
}
|
|
|
|
// ── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
function downloadText(url) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
// Follow redirect manually
|
|
downloadText(res.headers.location).then(resolve).catch(reject);
|
|
return;
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
return;
|
|
}
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => resolve(data));
|
|
}).on("error", reject);
|
|
});
|
|
}
|
|
|
|
function downloadBinary(url, destPath) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
downloadBinary(res.headers.location, destPath).then(resolve).catch(reject);
|
|
return;
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
|
|
return;
|
|
}
|
|
|
|
const fileStream = fs.createWriteStream(destPath);
|
|
let downloadedBytes = 0;
|
|
|
|
res.on("data", (chunk) => {
|
|
downloadedBytes += chunk.length;
|
|
});
|
|
|
|
res.pipe(fileStream);
|
|
|
|
fileStream.on("finish", () => {
|
|
fileStream.close(() => resolve());
|
|
});
|
|
|
|
fileStream.on("error", (err) => {
|
|
fs.unlink(destPath, () => {}); // clean up partial download
|
|
reject(err);
|
|
});
|
|
}).on("error", reject);
|
|
});
|
|
}
|
|
|
|
// ── Checksum verification ────────────────────────────────────────────────────
|
|
async function verifyChecksum(binaryFilePath, expectedSha) {
|
|
return new Promise((resolve, reject) => {
|
|
const hash = crypto.createHash("sha256");
|
|
const stream = fs.createReadStream(binaryFilePath);
|
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
stream.on("error", reject);
|
|
});
|
|
}
|
|
|
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
async function main() {
|
|
info("OpenCodeReview Installer");
|
|
info("=========================");
|
|
|
|
const { os, arch } = detectPlatform();
|
|
info(`Detected platform: ${os}/${arch}`);
|
|
|
|
const version = await resolveVersion();
|
|
|
|
// Ensure bin directory exists
|
|
if (!fs.existsSync(binDir)) {
|
|
fs.mkdirSync(binDir, { recursive: true });
|
|
}
|
|
|
|
// Download URL: bin/{VERSION}/{NAME}-{OS}-{ARCH}
|
|
const versionNum = version.replace(/^v/, "");
|
|
const remoteFileName = `${BINARY_NAME}-${os}-${arch}`;
|
|
const downloadUrl = `${REPO_BASE}/bin/v${versionNum}/${remoteFileName}`;
|
|
info(`Downloading ${downloadUrl} ...`);
|
|
|
|
await downloadBinary(downloadUrl, binaryDest);
|
|
fs.chmodSync(binaryDest, 0o755);
|
|
|
|
// Try to verify checksum
|
|
try {
|
|
info("Fetching checksum file...");
|
|
const shaContent = await downloadText(`${REPO_BASE}/bin/v${versionNum}/sha256sum.txt`);
|
|
for (const line of shaContent.split("\n")) {
|
|
const trimmed = line.trim();
|
|
// Match by OS+arch since local name lacks version prefix
|
|
if (trimmed.endsWith(`-${os}-${arch}`)) {
|
|
const expectedSha = trimmed.split(/\s+/)[0];
|
|
if (expectedSha) {
|
|
const actualSha = await verifyChecksum(binaryDest, expectedSha);
|
|
if (actualSha !== expectedSha.toLowerCase()) {
|
|
throw new Error(
|
|
`Checksum mismatch! Expected: ${expectedSha}, Got: ${actualSha}`
|
|
);
|
|
}
|
|
info("Checksum verified.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e.message.includes("HTTP 404") || e.message.includes("checksum")) {
|
|
warn("Checksum file not found; skipping integrity check.");
|
|
} else if (e.message.includes("Mismatch")) {
|
|
throw e;
|
|
} else {
|
|
warn(`Could not verify checksum: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
info(`Installed: ${binaryDest}`);
|
|
info("");
|
|
info("OpenCodeReview is ready!");
|
|
info("");
|
|
info("Quick start:");
|
|
info(" ocr version Show version info");
|
|
info(" ocr config set Configure your LLM provider");
|
|
info(" ocr review Start a code review");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
error(err.message);
|
|
process.exit(1);
|
|
});
|