9abce6e974
omp resolves the update target by querying https://registry.npmjs.org/ directly, but `bun install -g pkg@<version>` would then consult bun's on-disk manifest snapshot AND honour the user's npm-mirror configuration (corporate proxy, Taobao, …). Either source can lag the upstream registry by minutes-to-hours, in which case bun rejects the version with `No version matching "X" found for specifier "@oh-my-pi/pi-coding-agent" (but package exists)` even though the registry omp just queried is serving it. The bun install step now runs with both `--no-cache` (skip the manifest snapshot) and `--registry=https://registry.npmjs.org/` (pin the official catalog regardless of bunfig/.npmrc) so the install observes the same registry state the version check used. The registry URL is centralised in an `NPM_REGISTRY` constant shared by `getLatestRelease` and `buildBunInstallArgs`. Fixes #1686
103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import { afterEach, describe, expect, it } from "bun:test";
|
|
import * as fs from "node:fs/promises";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { buildBunInstallArgs, replaceBinaryForUpdate, resolveUpdateMethodForTest } from "../src/cli/update-cli";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
async function makeTempDir(): Promise<string> {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-update-test-"));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })));
|
|
});
|
|
describe("update-cli install target detection", () => {
|
|
it("uses bun update when prioritized omp is inside bun global bin", () => {
|
|
const method = resolveUpdateMethodForTest("/Users/test/.bun/bin/omp", "/Users/test/.bun/bin");
|
|
|
|
expect(method).toBe("bun");
|
|
});
|
|
|
|
it("uses binary update when prioritized omp is outside bun global bin", () => {
|
|
const method = resolveUpdateMethodForTest("/Users/test/.local/bin/omp", "/Users/test/.bun/bin");
|
|
|
|
expect(method).toBe("binary");
|
|
});
|
|
|
|
it("uses binary update when bun global bin cannot be resolved", () => {
|
|
const method = resolveUpdateMethodForTest("/Users/test/.local/bin/omp", undefined);
|
|
|
|
expect(method).toBe("binary");
|
|
});
|
|
});
|
|
|
|
describe("update-cli bun install command", () => {
|
|
it("pins the official npm registry and bypasses the manifest cache so a stale mirror or snapshot cannot mask a freshly published version", () => {
|
|
// Regression: omp queries https://registry.npmjs.org/<pkg>/latest directly.
|
|
// The install MUST hit the same registry, otherwise:
|
|
// - a lagging mirror (corp proxy, Taobao, …) rejects the version with
|
|
// `No version matching "X" (but package exists)`,
|
|
// - or bun's local manifest snapshot does the same when the user's bun
|
|
// is already pointed at the official registry but its cache predates
|
|
// the release.
|
|
// See https://github.com/can1357/oh-my-pi/issues/1686.
|
|
expect(buildBunInstallArgs("15.7.6")).toEqual([
|
|
"install",
|
|
"-g",
|
|
"--no-cache",
|
|
"--registry=https://registry.npmjs.org/",
|
|
"@oh-my-pi/pi-coding-agent@15.7.6",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("update-cli binary replacement", () => {
|
|
it("restores the previous binary when the replacement fails verification", async () => {
|
|
const dir = await makeTempDir();
|
|
const targetPath = path.join(dir, "omp");
|
|
const tempPath = `${targetPath}.new`;
|
|
const backupPath = `${targetPath}.bak`;
|
|
await Bun.write(targetPath, "old binary");
|
|
await Bun.write(tempPath, "broken binary");
|
|
|
|
await expect(
|
|
replaceBinaryForUpdate({
|
|
targetPath,
|
|
tempPath,
|
|
backupPath,
|
|
expectedVersion: "15.1.8",
|
|
verifyInstalledVersion: async () => ({ ok: false, path: targetPath }),
|
|
}),
|
|
).rejects.toThrow("restored previous omp binary");
|
|
|
|
expect(await Bun.file(targetPath).text()).toBe("old binary");
|
|
expect(await Bun.file(tempPath).exists()).toBe(false);
|
|
expect(await Bun.file(backupPath).exists()).toBe(false);
|
|
});
|
|
|
|
it("keeps the replacement only after it reports the expected version", async () => {
|
|
const dir = await makeTempDir();
|
|
const targetPath = path.join(dir, "omp");
|
|
const tempPath = `${targetPath}.new`;
|
|
const backupPath = `${targetPath}.bak`;
|
|
await Bun.write(targetPath, "old binary");
|
|
await Bun.write(tempPath, "new binary");
|
|
|
|
await replaceBinaryForUpdate({
|
|
targetPath,
|
|
tempPath,
|
|
backupPath,
|
|
expectedVersion: "15.1.8",
|
|
verifyInstalledVersion: async () => ({ ok: true, actual: "15.1.8", path: targetPath }),
|
|
});
|
|
|
|
expect(await Bun.file(targetPath).text()).toBe("new binary");
|
|
expect(await Bun.file(tempPath).exists()).toBe(false);
|
|
expect(await Bun.file(backupPath).exists()).toBe(false);
|
|
});
|
|
});
|