ktx/scripts/refresh-uv-manifest.test.mjs
Andrey Avtomonov feb0818444
feat(cli): self-provision pinned uv and defer MCP Python runtime install (#297)
Fixes a production crash-loop (PostHog issue 019eb68e): ktx mcp start
--foreground on a uv-less container eagerly installed the managed Python
runtime at boot, failed, and was restarted by its supervisor every ~62s
(122 exceptions from one install).

- MCP server factory now wires a lazy semantic-layer compute port that
  defers the runtime install to the first call, mirroring the already-lazy
  SQL-analysis port; the server boots and serves non-Python tools without
  the runtime.
- ktx no longer requires uv on PATH: it downloads its own pinned,
  sha256-verified uv build under the runtime root (KTX_RUNTIME_ROOT aware),
  always musl-static on Linux. PATH uv is never consulted.
- uv is acquired before the version dir is wiped, so a failed download
  cannot destroy an existing runtime.
- Acquisition failures (offline, intercepted download, unsupported
  platform) throw KtxExpectedError and stay out of Error Tracking; a
  missing binary inside a checksum-verified archive remains a plain Error.
- scripts/refresh-uv-manifest.mjs regenerates the pinned manifest
  (packages/cli/src/managed-uv-release.ts) on uv bumps.
- Setup consent prompt now discloses the uv download; docs updated.
2026-06-12 16:31:06 +00:00

69 lines
2.4 KiB
JavaScript

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { refreshUvManifest, renderUvReleaseModule } from './refresh-uv-manifest.mjs';
const ALL_KEYS = ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-arm64', 'win32-x64'];
function artifactsFixture() {
return Object.fromEntries(
ALL_KEYS.map((key, index) => [key, { file: `uv-${key}.tar.gz`, sha256: String(index).repeat(64).slice(0, 64) }]),
);
}
describe('renderUvReleaseModule', () => {
it('renders a typed module with every platform key and the pinned version', () => {
const module = renderUvReleaseModule('1.2.3', artifactsFixture());
assert.match(module, /MANAGED_UV_VERSION = '1\.2\.3'/);
assert.match(module, /Generated by scripts\/refresh-uv-manifest\.mjs/);
for (const key of ALL_KEYS) {
assert.match(module, new RegExp(`'${key}': \\{ file: 'uv-${key}\\.tar\\.gz', sha256: '[a-f0-9]{64}' \\}`));
}
});
it('rejects an incomplete platform map', () => {
const artifacts = artifactsFixture();
delete artifacts['win32-arm64'];
assert.throws(() => renderUvReleaseModule('1.2.3', artifacts), /Missing artifact entry for win32-arm64/);
});
});
describe('refreshUvManifest', () => {
it('fetches per-artifact sha256 files and writes the module', async () => {
const written = [];
const fetched = [];
const fetchStub = async (url) => {
fetched.push(url);
return {
ok: true,
text: async () => `${'a'.repeat(64)} *${url.split('/').at(-1).replace('.sha256', '')}\n`,
};
};
const result = await refreshUvManifest({
version: '1.2.3',
fetch: fetchStub,
writeFile: (path, contents) => written.push({ path, contents }),
log: () => {},
outputPath: '/tmp/managed-uv-release.ts',
});
assert.equal(result.version, '1.2.3');
assert.equal(fetched.length, ALL_KEYS.length);
assert.ok(fetched.every((url) => url.endsWith('.sha256') && url.includes('/download/1.2.3/')));
assert.equal(written.length, 1);
assert.match(written[0].contents, /MANAGED_UV_VERSION = '1\.2\.3'/);
});
it('rejects malformed sha256 file contents', async () => {
await assert.rejects(
refreshUvManifest({
version: '1.2.3',
fetch: async () => ({ ok: true, text: async () => '<html>proxy login</html>' }),
writeFile: () => {},
log: () => {},
}),
/Unexpected sha256 file contents/,
);
});
});