d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
266 lines
9.3 KiB
JavaScript
266 lines
9.3 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const crypto = require("node:crypto");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const test = require("node:test");
|
|
|
|
const installScript = fs.readFileSync(
|
|
path.join(__dirname, "..", "scripts", "install.js"),
|
|
"utf8",
|
|
);
|
|
const { installFailureHint, _internal } = require("../scripts/install");
|
|
const { _internal: glibcInternal } = require("../scripts/preflight-glibc");
|
|
|
|
function sha256(content) {
|
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
}
|
|
|
|
async function makeTempDir(t) {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "codewhale-install-test-"));
|
|
t.after(() => fs.promises.rm(dir, { force: true, recursive: true }));
|
|
return dir;
|
|
}
|
|
|
|
async function exists(file) {
|
|
return fs.promises.access(file).then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
}
|
|
|
|
async function withoutForcedDownload(callback) {
|
|
const previousTui = process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
|
const previousLegacy = process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
|
delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
|
delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
|
try {
|
|
return await callback();
|
|
} finally {
|
|
if (previousTui === undefined) {
|
|
delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
|
} else {
|
|
process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD = previousTui;
|
|
}
|
|
if (previousLegacy === undefined) {
|
|
delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
|
} else {
|
|
process.env.DEEPSEEK_FORCE_DOWNLOAD = previousLegacy;
|
|
}
|
|
}
|
|
}
|
|
|
|
test("install script checks Node support before loading helpers", () => {
|
|
const guardIndex = installScript.indexOf("assertSupportedNode();");
|
|
const firstRequireIndex = installScript.indexOf("require(");
|
|
|
|
assert.notEqual(guardIndex, -1);
|
|
assert.notEqual(firstRequireIndex, -1);
|
|
assert.ok(guardIndex < firstRequireIndex);
|
|
});
|
|
|
|
test("install script remains parseable before the Node support guard runs", () => {
|
|
assert.equal(installScript.includes("??"), false);
|
|
assert.equal(installScript.includes("?."), false);
|
|
});
|
|
|
|
test("install failure hint explains release base override for blocked GitHub downloads", () => {
|
|
const previous = process.env.DEEPSEEK_TUI_RELEASE_BASE_URL;
|
|
delete process.env.DEEPSEEK_TUI_RELEASE_BASE_URL;
|
|
try {
|
|
const error = Object.assign(
|
|
new Error(
|
|
"fetch https://github.com/Hmbown/CodeWhale/releases/download/v0.8.19/codewhale-artifacts-sha256.txt failed after 5 attempts:\ngetaddrinfo ENOTFOUND github.com",
|
|
),
|
|
{ code: "ENOTFOUND" },
|
|
);
|
|
|
|
const hint = installFailureHint(error);
|
|
|
|
assert.match(hint, /DEEPSEEK_TUI_RELEASE_BASE_URL/);
|
|
assert.match(hint, /codewhale-artifacts-sha256\.txt/);
|
|
assert.match(hint, /platform binaries/);
|
|
assert.match(hint, /#npm-binary-download-times-out/);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.DEEPSEEK_TUI_RELEASE_BASE_URL;
|
|
} else {
|
|
process.env.DEEPSEEK_TUI_RELEASE_BASE_URL = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
test("install failure hint checks configured release base when override is already set", () => {
|
|
const previous = process.env.DEEPSEEK_TUI_RELEASE_BASE_URL;
|
|
process.env.DEEPSEEK_TUI_RELEASE_BASE_URL = "https://mirror.example/deepseek/";
|
|
try {
|
|
const error = Object.assign(new Error("download stalled"), {
|
|
code: "EDOWNLOADTIMEOUT",
|
|
});
|
|
|
|
const hint = installFailureHint(error);
|
|
|
|
assert.match(hint, /is set to https:\/\/mirror\.example\/deepseek\//);
|
|
assert.match(hint, /codewhale-artifacts-sha256\.txt/);
|
|
assert.doesNotMatch(hint, /If GitHub is unavailable/);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.DEEPSEEK_TUI_RELEASE_BASE_URL;
|
|
} else {
|
|
process.env.DEEPSEEK_TUI_RELEASE_BASE_URL = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
test("glibc preflight message is CodeWhale-branded and actionable", () => {
|
|
const message = glibcInternal.glibcCompatibilityMessage([2, 39, 0], [2, 35, 0]);
|
|
|
|
assert.match(message, /Prebuilt CodeWhale Linux binaries require GLIBC_2\.39/);
|
|
assert.match(message, /this system has glibc 2\.35/);
|
|
assert.match(message, /cargo install codewhale-cli --locked/);
|
|
assert.match(message, /Linux x64 release asset is a static \(musl\) build/);
|
|
assert.match(message, /Linux arm64 asset is a GNU libc build/);
|
|
assert.match(message, /CODEWHALE_SKIP_GLIBC_CHECK=1/);
|
|
});
|
|
|
|
test("glibc preflight accepts canonical and legacy skip env vars", () => {
|
|
const previousCodewhale = process.env.CODEWHALE_SKIP_GLIBC_CHECK;
|
|
const previousTui = process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK;
|
|
const previousLegacy = process.env.DEEPSEEK_SKIP_GLIBC_CHECK;
|
|
delete process.env.CODEWHALE_SKIP_GLIBC_CHECK;
|
|
delete process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK;
|
|
delete process.env.DEEPSEEK_SKIP_GLIBC_CHECK;
|
|
try {
|
|
assert.equal(glibcInternal.skipGlibcCheck(), false);
|
|
process.env.CODEWHALE_SKIP_GLIBC_CHECK = "1";
|
|
assert.equal(glibcInternal.skipGlibcCheck(), true);
|
|
delete process.env.CODEWHALE_SKIP_GLIBC_CHECK;
|
|
process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK = "1";
|
|
assert.equal(glibcInternal.skipGlibcCheck(), true);
|
|
} finally {
|
|
if (previousCodewhale === undefined) {
|
|
delete process.env.CODEWHALE_SKIP_GLIBC_CHECK;
|
|
} else {
|
|
process.env.CODEWHALE_SKIP_GLIBC_CHECK = previousCodewhale;
|
|
}
|
|
if (previousTui === undefined) {
|
|
delete process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK;
|
|
} else {
|
|
process.env.DEEPSEEK_TUI_SKIP_GLIBC_CHECK = previousTui;
|
|
}
|
|
if (previousLegacy === undefined) {
|
|
delete process.env.DEEPSEEK_SKIP_GLIBC_CHECK;
|
|
} else {
|
|
process.env.DEEPSEEK_SKIP_GLIBC_CHECK = previousLegacy;
|
|
}
|
|
}
|
|
});
|
|
|
|
test("ensureBinary adopts a manually placed target binary after checksum validation", async (t) => {
|
|
const dir = await makeTempDir(t);
|
|
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
|
|
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
|
|
const version = "0.8.25";
|
|
const content = Buffer.from("manual codewhale binary");
|
|
let checksumLoads = 0;
|
|
|
|
await fs.promises.writeFile(target, content, { mode: 0o600 });
|
|
await fs.promises.writeFile(`${target}.version`, "0.8.24", "utf8");
|
|
|
|
const result = await withoutForcedDownload(() =>
|
|
_internal.ensureBinary(target, assetName, version, "Hmbown/CodeWhale", async () => {
|
|
checksumLoads += 1;
|
|
return new Map([[assetName, sha256(content)]]);
|
|
}),
|
|
);
|
|
|
|
assert.equal(result, target);
|
|
assert.equal(checksumLoads, 1);
|
|
assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
|
|
if (process.platform !== "win32") {
|
|
assert.notEqual((await fs.promises.stat(target)).mode & 0o111, 0);
|
|
}
|
|
});
|
|
|
|
test("ensureBinary adopts an official release-named binary placed in downloads", async (t) => {
|
|
const dir = await makeTempDir(t);
|
|
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
|
|
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
|
|
const assetPath = path.join(dir, assetName);
|
|
const version = "0.8.25";
|
|
const content = Buffer.from("official release binary");
|
|
|
|
await fs.promises.writeFile(assetPath, content);
|
|
|
|
const result = await withoutForcedDownload(() =>
|
|
_internal.ensureBinary(target, assetName, version, "Hmbown/CodeWhale", async () =>
|
|
new Map([[assetName, sha256(content)]]),
|
|
),
|
|
);
|
|
|
|
assert.equal(result, target);
|
|
assert.equal(await exists(target), true);
|
|
assert.equal(await exists(assetPath), false);
|
|
assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
|
|
});
|
|
|
|
test("manual binaries with mismatched checksums are not adopted", async (t) => {
|
|
const dir = await makeTempDir(t);
|
|
const target = path.join(dir, process.platform === "win32" ? "codewhale.exe" : "codewhale");
|
|
const assetName = process.platform === "win32" ? "codewhale-windows-x64.exe" : "codewhale-linux-x64";
|
|
const content = Buffer.from("wrong binary bytes");
|
|
|
|
await fs.promises.writeFile(target, content);
|
|
|
|
const adopted = await _internal.adoptExistingBinaryIfValid(
|
|
target,
|
|
assetName,
|
|
"0.8.25",
|
|
async () => new Map([[assetName, sha256(Buffer.from("different bytes"))]]),
|
|
`${target}.version`,
|
|
);
|
|
|
|
assert.equal(adopted, false);
|
|
assert.equal(await exists(`${target}.version`), false);
|
|
});
|
|
|
|
test("resolvePackageVersion honors codewhaleBinaryVersion precedence (#3769)", () => {
|
|
const { resolvePackageVersion } = _internal;
|
|
|
|
// codewhaleBinaryVersion wins over deepseekBinaryVersion and pkg.version.
|
|
assert.equal(
|
|
resolvePackageVersion(
|
|
{
|
|
codewhaleBinaryVersion: "1.2.3",
|
|
deepseekBinaryVersion: "0.0.1",
|
|
version: "9.9.9",
|
|
},
|
|
{},
|
|
),
|
|
"1.2.3",
|
|
);
|
|
|
|
// Falls back to deepseekBinaryVersion, then pkg.version.
|
|
assert.equal(
|
|
resolvePackageVersion({ deepseekBinaryVersion: "0.0.1", version: "9.9.9" }, {}),
|
|
"0.0.1",
|
|
);
|
|
assert.equal(resolvePackageVersion({ version: "9.9.9" }, {}), "9.9.9");
|
|
|
|
// Legacy env vars still take precedence over package fields, unchanged.
|
|
assert.equal(
|
|
resolvePackageVersion(
|
|
{ codewhaleBinaryVersion: "1.2.3", version: "9.9.9" },
|
|
{ DEEPSEEK_TUI_VERSION: "7.7.7" },
|
|
),
|
|
"7.7.7",
|
|
);
|
|
assert.equal(
|
|
resolvePackageVersion(
|
|
{ codewhaleBinaryVersion: "1.2.3" },
|
|
{ DEEPSEEK_VERSION: "8.8.8" },
|
|
),
|
|
"8.8.8",
|
|
);
|
|
});
|