feat: add safe project upgrades and production performance fixes
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
interface UpdateCache {
|
||||
checkedAt: number;
|
||||
latest: string;
|
||||
}
|
||||
|
||||
export function currentCliVersion(): string {
|
||||
try {
|
||||
return JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf8")).version;
|
||||
} catch {
|
||||
return "0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
const left = a.split("-")[0]!.split(".").map(Number);
|
||||
const right = b.split("-")[0]!.split(".").map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const difference = (left[i] || 0) - (right[i] || 0);
|
||||
if (difference) return difference > 0 ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function cachePath(): string {
|
||||
return process.env.WRNEXUS_UPDATE_CACHE ?? join(homedir(), ".wrnexus", "update-check.json");
|
||||
}
|
||||
|
||||
function cachedLatest(now: number): string | null {
|
||||
try {
|
||||
const cached = JSON.parse(readFileSync(cachePath(), "utf8")) as UpdateCache;
|
||||
return now - cached.checkedAt < CHECK_INTERVAL_MS ? cached.latest : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function registryLatest(): string | null {
|
||||
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
const result = spawnSync(npm, ["view", "@wrnexus/cli", "version"], {
|
||||
encoding: "utf8",
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
const version = (result.stdout ?? "").trim();
|
||||
return result.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
||||
}
|
||||
|
||||
/** Check at most once per day and print a non-blocking upgrade suggestion. */
|
||||
export function notifyIfUpdateAvailable(current: string, command?: string): void {
|
||||
if (
|
||||
process.env.WRNEXUS_NO_UPDATE_CHECK === "1" ||
|
||||
command === "update" ||
|
||||
command === "upgrade"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
let latest = cachedLatest(now);
|
||||
if (!latest) {
|
||||
latest = registryLatest();
|
||||
if (!latest) return;
|
||||
try {
|
||||
const file = cachePath();
|
||||
if (!existsSync(dirname(file))) mkdirSync(dirname(file), { recursive: true });
|
||||
writeFileSync(file, JSON.stringify({ checkedAt: now, latest }) + "\n", "utf8");
|
||||
} catch {
|
||||
// A read-only home directory must never break the CLI.
|
||||
}
|
||||
}
|
||||
if (compareVersions(latest, current) > 0) {
|
||||
console.log(
|
||||
`\n Update available: WRNexusJS ${current} → ${latest}\n` +
|
||||
` Run: wrnexus update --latest\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user