test: derive release versions in tests instead of hardcoding 0.1.0-rc.1

After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.

Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
This commit is contained in:
Andrey Avtomonov 2026-05-21 00:54:53 +02:00
parent d781a885b1
commit 9b729c64ef
7 changed files with 80 additions and 54 deletions

View file

@ -1,8 +1,13 @@
import { createRequire } from 'node:module';
import type { ReindexSummary } from '@ktx/context/index-sync';
import { describe, expect, it, vi } from 'vitest';
import { renderReindexJson, renderReindexPlain, reindexHasErrors } from './admin-reindex.js';
import { runKtxCli } from './index.js';
const cliVersion = (createRequire(import.meta.url)('@ktx/cli/package.json') as { version: string })
.version;
function makeIo(options: { stdoutIsTTY?: boolean } = {}) {
let stdout = '';
let stderr = '';
@ -137,7 +142,7 @@ describe('admin reindex Commander routing', () => {
force: true,
json: true,
output: 'plain',
cliVersion: '0.0.0-private',
cliVersion,
},
io.io,
);

View file

@ -19,6 +19,9 @@ import {
const require = createRequire(import.meta.url);
const cliPackageJson = require('@ktx/cli/package.json') as { name: string; version: string };
const cliVersion = cliPackageJson.version;
function makeIo(options: { stdoutIsTty?: boolean } = {}) {
let stdout = '';
let stderr = '';
@ -45,7 +48,7 @@ describe('getKtxCliPackageInfo', () => {
it('identifies the CLI package and its context dependency', () => {
expect(getKtxCliPackageInfo()).toEqual({
name: '@ktx/cli',
version: '0.0.0-private',
version: cliVersion,
contextPackageName: '@ktx/context',
});
});
@ -55,8 +58,9 @@ describe('getKtxCliPackageInfo', () => {
expect(packageJson).toMatchObject({
name: '@ktx/cli',
version: '0.0.0-private',
version: cliVersion,
});
expect(cliVersion).toMatch(/^\d+\.\d+\.\d+/);
});
it('normalizes public package metadata from package.json contents', () => {
@ -114,7 +118,7 @@ describe('runKtxCli', () => {
await expect(runKtxCli(['--version'], testIo.io)).resolves.toBe(0);
expect(testIo.stdout()).toBe('@ktx/cli 0.0.0-private\n');
expect(testIo.stdout()).toBe(`@ktx/cli ${cliVersion}\n`);
expect(testIo.stderr()).toBe('');
});
@ -304,7 +308,7 @@ describe('runKtxCli', () => {
1,
{
command: 'install',
cliVersion: '0.0.0-private',
cliVersion,
feature: 'local-embeddings',
force: true,
},
@ -314,7 +318,7 @@ describe('runKtxCli', () => {
2,
{
command: 'start',
cliVersion: '0.0.0-private',
cliVersion,
projectDir: expect.any(String),
feature: 'local-embeddings',
force: true,
@ -325,7 +329,7 @@ describe('runKtxCli', () => {
3,
{
command: 'stop',
cliVersion: '0.0.0-private',
cliVersion,
projectDir: expect.any(String),
all: false,
},
@ -335,7 +339,7 @@ describe('runKtxCli', () => {
4,
{
command: 'stop',
cliVersion: '0.0.0-private',
cliVersion,
projectDir: expect.any(String),
all: true,
},
@ -345,7 +349,7 @@ describe('runKtxCli', () => {
5,
{
command: 'status',
cliVersion: '0.0.0-private',
cliVersion,
json: true,
},
statusIo.io,
@ -418,7 +422,7 @@ describe('runKtxCli', () => {
expect.objectContaining({
command: 'query',
projectDir: tempDir,
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'prompt',
query: expect.objectContaining({ measures: ['orders.order_count'], dimensions: [] }),
}),
@ -433,7 +437,7 @@ describe('runKtxCli', () => {
).resolves.toBe(0);
expect(sl).toHaveBeenLastCalledWith(
expect.objectContaining({
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'auto',
}),
autoIo.io,
@ -449,7 +453,7 @@ describe('runKtxCli', () => {
).resolves.toBe(0);
expect(sl).toHaveBeenLastCalledWith(
expect.objectContaining({
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'never',
}),
noInputIo.io,
@ -585,7 +589,7 @@ describe('runKtxCli', () => {
skipAgents: false,
inputMode: 'auto',
yes: false,
cliVersion: '0.0.0-private',
cliVersion,
skipLlm: false,
skipEmbeddings: false,
databaseSchemas: [],
@ -715,7 +719,7 @@ describe('runKtxCli', () => {
inputMode: 'disabled',
depth: 'fast',
queryHistory: 'default',
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'never',
},
testIo.io,
@ -742,7 +746,7 @@ describe('runKtxCli', () => {
inputMode: 'auto',
depth: 'deep',
queryHistory: 'default',
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'prompt',
},
testIo.io,
@ -819,7 +823,7 @@ describe('runKtxCli', () => {
json: false,
inputMode: 'disabled',
queryHistory: 'default',
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'never',
},
testIo.io,
@ -1124,7 +1128,7 @@ describe('runKtxCli', () => {
command: 'run',
projectDir: tempDir,
inputMode: 'disabled',
cliVersion: '0.0.0-private',
cliVersion,
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
llmModel: 'claude-sonnet-4-6',
skipLlm: false,
@ -1163,7 +1167,7 @@ describe('runKtxCli', () => {
command: 'run',
projectDir: tempDir,
inputMode: 'disabled',
cliVersion: '0.0.0-private',
cliVersion,
llmBackend: 'vertex',
vertexProject: 'local-gcp-project',
vertexLocation: 'us-east5',
@ -1200,7 +1204,7 @@ describe('runKtxCli', () => {
command: 'run',
projectDir: tempDir,
inputMode: 'disabled',
cliVersion: '0.0.0-private',
cliVersion,
llmBackend: 'claude-code',
llmModel: 'opus',
skipLlm: false,
@ -1308,7 +1312,7 @@ describe('runKtxCli', () => {
projectDir: '/tmp/project',
inputMode: 'disabled',
yes: true,
cliVersion: '0.0.0-private',
cliVersion,
skipLlm: true,
skipEmbeddings: true,
databaseDrivers: ['postgres'],
@ -1649,7 +1653,7 @@ describe('runKtxCli', () => {
queryFile: '/tmp/query.json',
execute: false,
format: 'json',
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'auto',
},
autoIo.io,
@ -1663,7 +1667,7 @@ describe('runKtxCli', () => {
queryFile: '/tmp/query.json',
execute: false,
format: 'json',
cliVersion: '0.0.0-private',
cliVersion,
runtimeInstallPolicy: 'never',
},
neverIo.io,

View file

@ -139,12 +139,15 @@ async function writeWorkspaceFixture(root) {
}
describe('publicNpmPackageLayout', () => {
it('uses the first public npm release version for the tarball name', () => {
it('uses the public npm release version for the tarball name', () => {
const layout = publicNpmPackageLayout('/repo/ktx');
assert.equal(PUBLIC_NPM_PACKAGE_VERSION, '0.1.0-rc.1');
assert.equal(publicNpmPackageTarballName(), 'kaelio-ktx-0.1.0-rc.1.tgz');
assert.equal(layout.tarballPath, '/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0-rc.1.tgz');
assert.match(PUBLIC_NPM_PACKAGE_VERSION, /^\d+\.\d+\.\d+/);
assert.equal(publicNpmPackageTarballName(), `kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`);
assert.equal(
layout.tarballPath,
`/repo/ktx/dist/artifacts/npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`,
);
});
});
@ -211,7 +214,7 @@ describe('publicNpmPackageJson', () => {
);
assert.equal(packageJson.name, PUBLIC_NPM_PACKAGE_NAME);
assert.equal(packageJson.version, '0.1.0-rc.1');
assert.equal(packageJson.version, PUBLIC_NPM_PACKAGE_VERSION);
assert.equal(packageJson.private, false);
assert.deepEqual(packageJson.bin, { ktx: './dist/bin.js' });
assert.deepEqual(packageJson.dependencies, { commander: '14.0.3' });
@ -275,7 +278,7 @@ describe('publicNpmPackCommand', () => {
'--config.node-linker=hoisted',
'pack',
'--out',
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0-rc.1.tgz',
`/repo/ktx/dist/artifacts/npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`,
],
cwd: '/repo/ktx/dist/public-npm-package',
});

View file

@ -52,7 +52,7 @@ describe('runtimeWheelPyproject', () => {
const pyproject = runtimeWheelPyproject();
assert.match(pyproject, /name = "kaelio-ktx"/);
assert.match(pyproject, /version = "0\.1\.0rc1"/);
assert.match(pyproject, new RegExp(`version = "${RUNTIME_WHEEL_PACKAGE_VERSION.replace(/\./g, '\\.')}"`));
assert.match(pyproject, /ktx-daemon = "ktx_daemon\.__main__:main"/);
assert.match(pyproject, /packages = \["semantic_layer", "ktx_daemon"\]/);
assert.match(pyproject, /\[project\.optional-dependencies\]/);
@ -110,6 +110,6 @@ describe('runtimeWheelBuildCommand', () => {
cwd: '/repo/ktx',
});
assert.equal(RUNTIME_WHEEL_DISTRIBUTION_NAME, 'kaelio-ktx');
assert.equal(RUNTIME_WHEEL_PACKAGE_VERSION, '0.1.0rc1');
assert.match(RUNTIME_WHEEL_PACKAGE_VERSION, /^\d+\.\d+\.\d+/);
});
});

View file

@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { describe, it } from 'node:test';
import { PUBLIC_NPM_PACKAGE_VERSION } from './build-public-npm-package.mjs';
import {
buildLocalEmbeddingsSmokeEnv,
expectedPublicKtxVersionPattern,
@ -12,6 +13,9 @@ import {
validateEmbeddingResponse,
} from './local-embeddings-runtime-smoke.mjs';
const PUBLIC_TARBALL_NAME = `kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`;
const OTHER_PUBLIC_TARBALL_NAME = 'kaelio-ktx-9.9.9.tgz';
describe('localEmbeddingsSmokeOptIn', () => {
it('skips unless the smoke is explicitly enabled', () => {
assert.deepEqual(localEmbeddingsSmokeOptIn({}, []), {
@ -35,10 +39,7 @@ describe('localEmbeddingsSmokeOptIn', () => {
describe('publicKtxTarballName', () => {
it('selects the public @kaelio/ktx tarball name', () => {
assert.equal(
publicKtxTarballName(['kaelio-ktx-0.1.0-rc.1.tgz', 'ignore-me.tgz']),
'kaelio-ktx-0.1.0-rc.1.tgz',
);
assert.equal(publicKtxTarballName([PUBLIC_TARBALL_NAME, 'ignore-me.tgz']), PUBLIC_TARBALL_NAME);
});
it('fails when the public package tarball is missing', () => {
@ -50,7 +51,7 @@ describe('publicKtxTarballName', () => {
it('fails when multiple public package tarballs are present', () => {
assert.throws(
() => publicKtxTarballName(['kaelio-ktx-0.1.0-rc.1.tgz', 'kaelio-ktx-0.2.0.tgz']),
() => publicKtxTarballName([PUBLIC_TARBALL_NAME, OTHER_PUBLIC_TARBALL_NAME]),
/Expected exactly one @kaelio\/ktx tarball/,
);
});
@ -60,7 +61,7 @@ describe('expectedPublicKtxVersionPattern', () => {
it('matches the public package version and rejects the private workspace version', () => {
const pattern = expectedPublicKtxVersionPattern();
assert.match('@kaelio/ktx 0.1.0-rc.1\n', pattern);
assert.match(`@kaelio/ktx ${PUBLIC_NPM_PACKAGE_VERSION}\n`, pattern);
assert.doesNotMatch('@kaelio/ktx 0.0.0-private\n', pattern);
});
});

View file

@ -72,6 +72,10 @@ async function writeReleaseMetadataInputs(root) {
}
}
function runtimeWheelFilename(version = RUNTIME_WHEEL_PACKAGE_VERSION) {
return `kaelio_ktx-${version}-py3-none-any.whl`;
}
async function writeUploadableArtifactFixtures(layout) {
await mkdir(layout.npmDir, { recursive: true });
await mkdir(layout.pythonDir, { recursive: true });
@ -82,7 +86,7 @@ async function writeUploadableArtifactFixtures(layout) {
`${packageInfo.name}-tarball`,
]),
[
join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
join(layout.pythonDir, runtimeWheelFilename()),
'kaelio-ktx-runtime-wheel',
],
]);
@ -99,7 +103,10 @@ describe('packageArtifactLayout', () => {
assert.equal(layout.artifactDir, '/repo/ktx/dist/artifacts');
assert.equal(layout.npmDir, '/repo/ktx/dist/artifacts/npm');
assert.equal(layout.pythonDir, '/repo/ktx/dist/artifacts/python');
assert.equal(layout.cliTarball, '/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0-rc.1.tgz');
assert.equal(
layout.cliTarball,
`/repo/ktx/dist/artifacts/npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`,
);
assert.deepEqual(Object.keys(layout.npmTarballs), ['@kaelio/ktx']);
});
});
@ -131,7 +138,7 @@ describe('packageReleaseMetadata', () => {
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageRoot: 'packages/cli',
packageVersion: '0.1.0-rc.1',
packageVersion: PUBLIC_NPM_PACKAGE_VERSION,
private: false,
releaseMode: 'ci-artifact-only',
},
@ -139,7 +146,7 @@ describe('packageReleaseMetadata', () => {
ecosystem: 'python',
packageName: 'kaelio-ktx',
packageRoot: 'python/runtime-wheel',
packageVersion: '0.1.0rc1',
packageVersion: RUNTIME_WHEEL_PACKAGE_VERSION,
private: false,
releaseMode: 'ci-artifact-only',
},
@ -154,10 +161,10 @@ describe('findPythonArtifacts', () => {
it('finds the bundled runtime wheel only', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
await writeFile(join(root, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'), '');
await writeFile(join(root, runtimeWheelFilename()), '');
assert.deepEqual(await findPythonArtifacts(root), {
runtimeWheel: join(root, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
runtimeWheel: join(root, runtimeWheelFilename()),
});
} finally {
await rm(root, { recursive: true, force: true });
@ -197,7 +204,7 @@ describe('artifact manifest', () => {
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageRoot: 'packages/cli',
packageVersion: '0.1.0-rc.1',
packageVersion: PUBLIC_NPM_PACKAGE_VERSION,
private: false,
releaseMode: 'ci-artifact-only',
},
@ -210,7 +217,7 @@ describe('artifact manifest', () => {
ecosystem: 'python',
packageName: 'kaelio-ktx',
packageRoot: 'python/runtime-wheel',
packageVersion: '0.1.0rc1',
packageVersion: RUNTIME_WHEEL_PACKAGE_VERSION,
private: false,
releaseMode: 'ci-artifact-only',
},
@ -232,8 +239,8 @@ describe('artifact manifest', () => {
artifactKind: 'tarball',
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageVersion: '0.1.0-rc.1',
path: 'npm/kaelio-ktx-0.1.0-rc.1.tgz',
packageVersion: PUBLIC_NPM_PACKAGE_VERSION,
path: `npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`,
},
],
);
@ -252,13 +259,15 @@ describe('artifact manifest', () => {
artifactKind: 'wheel',
ecosystem: 'python',
packageName: 'kaelio-ktx',
packageVersion: '0.1.0rc1',
path: 'python/kaelio_ktx-0.1.0rc1-py3-none-any.whl',
packageVersion: RUNTIME_WHEEL_PACKAGE_VERSION,
path: `python/${runtimeWheelFilename()}`,
},
],
);
const npmEntry = manifest.files.find((file) => file.path === 'npm/kaelio-ktx-0.1.0-rc.1.tgz');
const npmEntry = manifest.files.find(
(file) => file.path === `npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`,
);
assert.ok(npmEntry);
assert.equal(npmEntry.bytes, Buffer.byteLength('@kaelio/ktx-tarball'));
assert.equal(npmEntry.sha256, createHash('sha256').update('@kaelio/ktx-tarball').digest('hex'));
@ -362,17 +371,17 @@ describe('copyRuntimeWheelAssets', () => {
try {
await mkdir(layout.pythonDir, { recursive: true });
await writeFile(
join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
join(layout.pythonDir, runtimeWheelFilename()),
'kaelio-ktx-runtime-wheel',
);
const assets = await copyRuntimeWheelAssets(layout, {
runtimeWheel: join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
runtimeWheel: join(layout.pythonDir, runtimeWheelFilename()),
});
assert.equal(
assets.wheelPath,
join(root, 'packages', 'cli', 'assets', 'python', 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
join(root, 'packages', 'cli', 'assets', 'python', runtimeWheelFilename()),
);
assert.equal(
assets.manifestPath,
@ -385,7 +394,7 @@ describe('copyRuntimeWheelAssets', () => {
normalizedName: RUNTIME_WHEEL_NORMALIZED_NAME,
version: RUNTIME_WHEEL_PACKAGE_VERSION,
wheel: {
file: 'kaelio_ktx-0.1.0rc1-py3-none-any.whl',
file: runtimeWheelFilename(),
sha256: createHash('sha256')
.update('kaelio-ktx-runtime-wheel')
.digest('hex'),

View file

@ -11,6 +11,7 @@ import {
writeArtifactManifest,
} from './package-artifacts.mjs';
import { PUBLIC_NPM_PACKAGE_VERSION } from './build-public-npm-package.mjs';
import { RUNTIME_WHEEL_PACKAGE_VERSION } from './build-python-runtime-wheel.mjs';
import { readReleasePolicy, releasePolicyPath, releaseReadinessReport } from './release-readiness.mjs';
async function writeJson(path, value) {
@ -37,7 +38,10 @@ async function writeUploadableArtifactFixtures(layout) {
layout.npmTarballs[packageInfo.name],
`${packageInfo.name}-tarball`,
]),
[join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'), 'kaelio-ktx-runtime-wheel'],
[
join(layout.pythonDir, `kaelio_ktx-${RUNTIME_WHEEL_PACKAGE_VERSION}-py3-none-any.whl`),
'kaelio-ktx-runtime-wheel',
],
]);
for (const [path, contents] of fileContents) {