diff --git a/bin/ocr.js b/bin/ocr.js index a1e66437..33902c42 100755 --- a/bin/ocr.js +++ b/bin/ocr.js @@ -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")); diff --git a/scripts/platform.js b/scripts/platform.js index 3972669c..406d7261 100644 --- a/scripts/platform.js +++ b/scripts/platform.js @@ -2,10 +2,15 @@ const path = require("path"); const fs = require("fs"); +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", @@ -33,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) { @@ -40,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 }; + return { path: binPath, fromPlatformPkg: true, fromStaged: false }; } } catch (err) { if (err.code !== "MODULE_NOT_FOUND") { @@ -49,9 +89,14 @@ function resolveNativeBinary() { } } + const staged = resolveStagedBinary(); + if (staged) { + return staged; + } + const legacyPath = path.join(__dirname, "..", "bin", BINARY_FILENAME); if (fs.existsSync(legacyPath)) { - return { path: legacyPath, fromPlatformPkg: false }; + return { path: legacyPath, fromPlatformPkg: false, fromStaged: false }; } return null; @@ -61,6 +106,10 @@ module.exports = { IS_WINDOWS, BINARY_FILENAME, PLATFORM_PKG, + STATE_DIR, + STAGED_BIN_DIR, + VERSION_JSON_PATH, getPlatformPackageName, + resolveStagedBinary, resolveNativeBinary, }; diff --git a/scripts/update.js b/scripts/update.js index ba6fc249..a3e2f5a5 100644 --- a/scripts/update.js +++ b/scripts/update.js @@ -3,22 +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 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); @@ -28,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; @@ -136,6 +141,30 @@ function removeHint() { } catch (_) {} } +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(STAGED_BIN_DIR, { recursive: true }); + const dest = path.join(STAGED_BIN_DIR, BINARY_FILENAME); + const tmp = dest + ".tmp"; + fs.copyFileSync(srcPath, tmp); + if (!IS_WINDOWS) { + fs.chmodSync(tmp, 0o755); + } + fs.renameSync(tmp, dest); + + writeVersionJson(getInstalledVersion(dest) || "unknown"); + } catch (_) {} +} + async function main() { touchTimestamp(); @@ -159,8 +188,12 @@ async function main() { return; } + // 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, @@ -169,6 +202,11 @@ async function main() { if (result.status === 0) { removeHint(); + // Refresh staged binary with the newly installed version + const newResolved = resolveNativeBinary(); + if (newResolved && !newResolved.fromStaged) { + stageBinary(newResolved.path); + } } else { writeHint(latestVersion, pkgName); }