Files
codebase-memory-mcp/pkg/npm/test/install-failure.test.js
T
Martin Vogel 10c7fc3206 fix(pkg): remove the archive size caps from the npm, PyPI and Go wrappers
v0.12.0 could not be installed through npm, pnpm, pip or the Go wrapper on
any platform (#2657): each wrapper refused an archive member above 256 MiB,
and every v0.12.0 binary is 331-335 MB. The v0.11.0 binaries were already
about 300 MB, so the Go wrapper, which has had the cap since v0.10.0, was
broken before; npm and PyPI gained it in v0.12.0. The archive is verified
against the release's checksums.txt before it is unpacked, so a byte cap
only ever refused what we ship.

- npm, PyPI, Go: delete every byte-size cap (compressed archive, per member,
  aggregate, checksum manifest, download size). The checksum, the exact
  member set, path and link refusal, the member count, HTTPS and the
  --version check stay.
- All three honour CBM_DOWNLOAD_URL like install.sh, for testing: https, or
  plain http to an exact loopback authority with no proxy and no redirect.
- Smoke: scripts/smoke-wrappers.sh installs the three wrappers from the
  checkout against the release fixture and runs the binary each one
  installed. smoke-test.sh Phase 13w runs it; SMOKE_REQUIRE_WRAPPERS=1 makes
  it required on the PR smoke lanes and in the release smoke, which gates
  publishing.

Other install paths found broken while checking every channel:
- Homebrew: the formula was still at 0.10.3 and installed the dynamic Linux
  build, which needs glibc 2.38 (Ubuntu 22.04, Debian 12 and RHEL 9 lack it).
  It now installs 0.12.0 with the static -portable Linux assets and tracks
  the newest release in the version contract like the other surfaces.
- Go: `go install .../pkg/go/cmd/codebase-memory-mcp@vX.Y.Z` never worked
  (no pkg/go/v* tags, so Go fetched the whole repository, over its 500 MB
  module limit), and @latest built main's wrapper, whose version constant
  lags each release. The release now also tags pkg/go/vX.Y.Z, and the
  wrapper installs its own module version (the constant only for local and
  untagged builds).
- npm: a stable release also moves the `next` dist-tag to itself; `next`
  still pointed to 0.9.1-rc.1.
- Docs: winget and Chocolatey were listed as install channels but are not
  published; they are removed until they are. Windows arm64 ships and is
  now listed.

Fixes #2657.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-16 18:09:06 +02:00

110 lines
3.6 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
test('postinstall exits nonzero when installation fails', async () => {
const installPath = path.join(__dirname, '..', 'install.js');
const source = fs.readFileSync(installPath, 'utf8');
const output = { stdout: '', stderr: '' };
const observed = { exitCode: undefined };
const fakeProcess = {
// Force a deterministic failure before any filesystem or network access.
platform: 'unsupported-test-platform',
arch: 'x64',
env: {},
pid: 12345,
stdout: { write: (text) => { output.stdout += String(text); } },
stderr: { write: (text) => { output.stderr += String(text); } },
exit: (code) => { observed.exitCode = code; },
exitCode: undefined,
};
const sandbox = {
Buffer,
URL,
__dirname: path.dirname(installPath),
__filename: installPath,
clearTimeout,
console,
exports: {},
module: { exports: {} },
process: fakeProcess,
require: (specifier) => {
if (specifier === './package.json') return { version: '0.0.0-test' };
return require(specifier);
},
setTimeout,
};
vm.runInNewContext(source, sandbox, { filename: installPath });
await new Promise((resolve) => setImmediate(resolve));
const exitCode = observed.exitCode ?? fakeProcess.exitCode ?? 0;
assert.match(output.stderr, /install failed/);
assert.notEqual(
exitCode,
0,
'npm postinstall reported success after a deterministic installation failure',
);
});
test('postinstall refuses a non-loopback http CBM_DOWNLOAD_URL before any download', async () => {
const installPath = path.join(__dirname, '..', 'install.js');
const source = fs.readFileSync(installPath, 'utf8');
// A fresh package directory: no cached binary, and nothing may be created.
const packageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cbm-npm-override-test-'));
const output = { stdout: '', stderr: '' };
const observed = { exitCode: undefined };
const fakeProcess = {
platform: 'linux',
arch: 'x64',
env: { CBM_DOWNLOAD_URL: 'http://example.com/releases' },
pid: 12345,
stdout: { write: (text) => { output.stdout += String(text); } },
stderr: { write: (text) => { output.stderr += String(text); } },
exit: (code) => { observed.exitCode = code; },
exitCode: undefined,
};
const refusingTransport = {
get(url) { throw new Error(`unexpected request to ${url}`); },
};
try {
const sandbox = {
Buffer,
URL,
__dirname: packageDir,
__filename: path.join(packageDir, 'install.js'),
clearTimeout,
console,
exports: {},
module: { exports: {} },
process: fakeProcess,
require: (specifier) => {
if (specifier === './package.json') return { version: '0.0.0-test' };
if (specifier === 'https' || specifier === 'http') return refusingTransport;
return require(specifier);
},
setTimeout,
};
vm.runInNewContext(source, sandbox, { filename: installPath });
await new Promise((resolve) => setImmediate(resolve));
assert.match(
output.stderr,
/install failed .*Refusing non-HTTPS download URL \(CBM_DOWNLOAD_URL\): http:\/\/example\.com\/releases/,
);
assert.equal(observed.exitCode, 1);
assert.doesNotMatch(output.stdout, /downloading/);
assert.deepEqual(fs.readdirSync(packageDir), []);
} finally {
fs.rmSync(packageDir, { recursive: true, force: true });
}
});