mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-08-21 14:44:56 +00:00
fix(npm): improve binary staging for zero-downtime auto-upgrade
Replace the simple binary cache with a proper staged binary mechanism: - Add version.json metadata tracking (version, timestamp, platform) - Add platform validation to prevent cross-platform misuse - Add file integrity checks (exists, non-empty) in resolveStagedBinary() - Notify users via dim stderr message when running from staged binary - Refresh staged binary after successful upgrade for next-time protection - Reuse STATE_DIR from platform.js to eliminate path duplication - Always write version.json to avoid orphan binaries on disk Resolution priority: platform package > staged binary > legacy path.
This commit is contained in:
parent
730f2243f6
commit
c2312f9744
3 changed files with 94 additions and 28 deletions
|
|
@ -17,6 +17,13 @@ if (!resolved) {
|
|||
}
|
||||
const binaryPath = resolved.path;
|
||||
|
||||
if (resolved.fromStaged) {
|
||||
const ver = resolved.stagedVersion ? ` (v${resolved.stagedVersion})` : "";
|
||||
process.stderr.write(
|
||||
`\x1b[2m[ocr] Using staged binary${ver}; run npm i -g @alibaba-group/open-code-review to reinstall.\x1b[0m\n`
|
||||
);
|
||||
}
|
||||
|
||||
const hintFile = path.join(os.homedir(), ".opencodereview", "update-available");
|
||||
try {
|
||||
const hint = JSON.parse(fs.readFileSync(hintFile, "utf8"));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ const os = require("os");
|
|||
const IS_WINDOWS = process.platform === "win32";
|
||||
const BINARY_FILENAME = IS_WINDOWS ? "opencodereview.exe" : "opencodereview";
|
||||
|
||||
const STATE_DIR = path.join(os.homedir(), ".opencodereview");
|
||||
const STAGED_BIN_DIR = path.join(STATE_DIR, "staged");
|
||||
const VERSION_JSON_PATH = path.join(STAGED_BIN_DIR, "version.json");
|
||||
|
||||
const PLATFORM_PKG = {
|
||||
"darwin-arm64": "@alibaba-group/ocr-darwin-arm64",
|
||||
"darwin-x64": "@alibaba-group/ocr-darwin-x64",
|
||||
|
|
@ -34,6 +38,41 @@ function getPlatformPackageName() {
|
|||
return PLATFORM_PKG[key] || null;
|
||||
}
|
||||
|
||||
function resolveStagedBinary() {
|
||||
try {
|
||||
const raw = fs.readFileSync(VERSION_JSON_PATH, "utf8");
|
||||
const meta = JSON.parse(raw);
|
||||
if (!meta.version || !meta.stagedAt || !meta.platform) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentPlatform = `${process.platform}-${process.arch}`;
|
||||
if (meta.platform !== currentPlatform) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const binPath = path.join(STAGED_BIN_DIR, BINARY_FILENAME);
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(binPath);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
if (!stat.isFile() || stat.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
path: binPath,
|
||||
fromPlatformPkg: false,
|
||||
fromStaged: true,
|
||||
stagedVersion: meta.version,
|
||||
};
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveNativeBinary() {
|
||||
const pkgName = getPlatformPackageName();
|
||||
if (pkgName) {
|
||||
|
|
@ -41,7 +80,7 @@ function resolveNativeBinary() {
|
|||
const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
|
||||
const binPath = path.join(pkgDir, "bin", BINARY_FILENAME);
|
||||
if (fs.existsSync(binPath)) {
|
||||
return { path: binPath, fromPlatformPkg: true, fromCache: false };
|
||||
return { path: binPath, fromPlatformPkg: true, fromStaged: false };
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code !== "MODULE_NOT_FOUND") {
|
||||
|
|
@ -50,16 +89,14 @@ function resolveNativeBinary() {
|
|||
}
|
||||
}
|
||||
|
||||
const legacyPath = path.join(__dirname, "..", "bin", BINARY_FILENAME);
|
||||
if (fs.existsSync(legacyPath)) {
|
||||
return { path: legacyPath, fromPlatformPkg: false, fromCache: false };
|
||||
const staged = resolveStagedBinary();
|
||||
if (staged) {
|
||||
return staged;
|
||||
}
|
||||
|
||||
const cachePath = path.join(
|
||||
os.homedir(), ".opencodereview", "bin", BINARY_FILENAME
|
||||
);
|
||||
if (fs.existsSync(cachePath)) {
|
||||
return { path: cachePath, fromPlatformPkg: false, fromCache: true };
|
||||
const legacyPath = path.join(__dirname, "..", "bin", BINARY_FILENAME);
|
||||
if (fs.existsSync(legacyPath)) {
|
||||
return { path: legacyPath, fromPlatformPkg: false, fromStaged: false };
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -69,6 +106,10 @@ module.exports = {
|
|||
IS_WINDOWS,
|
||||
BINARY_FILENAME,
|
||||
PLATFORM_PKG,
|
||||
STATE_DIR,
|
||||
STAGED_BIN_DIR,
|
||||
VERSION_JSON_PATH,
|
||||
getPlatformPackageName,
|
||||
resolveStagedBinary,
|
||||
resolveNativeBinary,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,23 +3,27 @@
|
|||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const https = require("https");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
const { resolveNativeBinary } = require("./platform");
|
||||
const {
|
||||
resolveNativeBinary,
|
||||
IS_WINDOWS,
|
||||
BINARY_FILENAME,
|
||||
STATE_DIR,
|
||||
STAGED_BIN_DIR,
|
||||
VERSION_JSON_PATH,
|
||||
} = require("./platform");
|
||||
const { loadPackageJson } = require("./install.js");
|
||||
|
||||
const stateDir = path.join(os.homedir(), ".opencodereview");
|
||||
const tsFile = path.join(stateDir, "last-update-check");
|
||||
const lockFile = path.join(stateDir, "update.lock");
|
||||
const hintFile = path.join(stateDir, "update-available");
|
||||
const tsFile = path.join(STATE_DIR, "last-update-check");
|
||||
const lockFile = path.join(STATE_DIR, "update.lock");
|
||||
const hintFile = path.join(STATE_DIR, "update-available");
|
||||
|
||||
const CACHE_BIN_DIR = path.join(stateDir, "bin");
|
||||
const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
||||
|
||||
function touchTimestamp() {
|
||||
fs.mkdirSync(stateDir, { recursive: true });
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
const now = new Date();
|
||||
try {
|
||||
fs.utimesSync(tsFile, now, now);
|
||||
|
|
@ -29,7 +33,7 @@ function touchTimestamp() {
|
|||
}
|
||||
|
||||
function acquireLock() {
|
||||
fs.mkdirSync(stateDir, { recursive: true });
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
try {
|
||||
fs.writeFileSync(lockFile, String(process.pid), { flag: "wx" });
|
||||
return true;
|
||||
|
|
@ -137,16 +141,27 @@ function removeHint() {
|
|||
} catch (_) {}
|
||||
}
|
||||
|
||||
function cacheBinary(srcPath) {
|
||||
function writeVersionJson(version) {
|
||||
const meta = {
|
||||
version,
|
||||
stagedAt: new Date().toISOString(),
|
||||
platform: `${process.platform}-${process.arch}`,
|
||||
};
|
||||
fs.writeFileSync(VERSION_JSON_PATH, JSON.stringify(meta, null, 2));
|
||||
}
|
||||
|
||||
function stageBinary(srcPath) {
|
||||
try {
|
||||
fs.mkdirSync(CACHE_BIN_DIR, { recursive: true });
|
||||
const dest = path.join(CACHE_BIN_DIR, path.basename(srcPath));
|
||||
fs.mkdirSync(STAGED_BIN_DIR, { recursive: true });
|
||||
const dest = path.join(STAGED_BIN_DIR, BINARY_FILENAME);
|
||||
const tmp = dest + ".tmp";
|
||||
fs.copyFileSync(srcPath, tmp);
|
||||
fs.renameSync(tmp, dest);
|
||||
if (process.platform !== "win32") {
|
||||
fs.chmodSync(dest, 0o755);
|
||||
if (!IS_WINDOWS) {
|
||||
fs.chmodSync(tmp, 0o755);
|
||||
}
|
||||
fs.renameSync(tmp, dest);
|
||||
|
||||
writeVersionJson(getInstalledVersion(dest) || "unknown");
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
|
|
@ -173,10 +188,12 @@ async function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
cacheBinary(resolved.path);
|
||||
// Stage current binary before npm i -g to cover the gap window
|
||||
if (!resolved.fromStaged) {
|
||||
stageBinary(resolved.path);
|
||||
}
|
||||
|
||||
const pkgName = pkg.name;
|
||||
const IS_WINDOWS = process.platform === "win32";
|
||||
const result = spawnSync("npm", ["i", "-g", `${pkgName}@${latestVersion}`], {
|
||||
encoding: "utf8",
|
||||
timeout: 120000,
|
||||
|
|
@ -185,9 +202,10 @@ async function main() {
|
|||
|
||||
if (result.status === 0) {
|
||||
removeHint();
|
||||
// Refresh staged binary with the newly installed version
|
||||
const newResolved = resolveNativeBinary();
|
||||
if (newResolved && !newResolved.fromCache) {
|
||||
cacheBinary(newResolved.path);
|
||||
if (newResolved && !newResolved.fromStaged) {
|
||||
stageBinary(newResolved.path);
|
||||
}
|
||||
} else {
|
||||
writeHint(latestVersion, pkgName);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue