refactor(release): single source of truth for package version

Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.

Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.

- update-public-release-version.mjs rewrites both Python pyproject.toml
  files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
  normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
  @semantic-release/git assets so the release commit back to main
  carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
  replaced with "?? getKtxCliPackageInfo().version", and
  createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
  always reflects the most recent release; no sentinel pin to
  maintain.

Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
This commit is contained in:
Andrey Avtomonov 2026-05-21 14:23:28 +02:00
parent 36805c4533
commit 99f7a31919
22 changed files with 168 additions and 54 deletions

View file

@ -105,27 +105,36 @@ prior rc lineage is consumed cleanly.
## Release metadata
semantic-release calls `scripts/update-public-release-version.mjs` during the
prepare step before the exec publish command runs. That script updates the
following files **inside the CI runner only**:
prepare step before the exec publish command runs. That script rewrites the
following files in lockstep:
- `package.json` with the semantic-release version.
- `release-policy.json` with `publicNpmPackageVersion`, npm publish settings,
and the published package smoke-test version.
- `package.json` and `packages/cli/package.json` with the semantic-release
version.
- `python/ktx-daemon/pyproject.toml` and `python/ktx-sl/pyproject.toml` with
the same version, normalized for PEP 440 (e.g. `0.1.0-rc.2`
`0.1.0rc2`). Branch-prefixed npm releases skip this step because they are
not published to PyPI.
- `release-policy.json` with the npm publish settings, release mode, and
published package smoke-test version. The package version itself is not
stored here; it is derived from `packages/cli/package.json.version` by
`scripts/public-npm-release-metadata.mjs`, so there is only one place that
owns the version.
The artifact packaging, readiness, and smoke-test scripts read
`publicNpmPackageVersion` from `release-policy.json` within the same CI run.
Nothing reads these files at runtime — the daemon and CLI rely on the
published `package.json` (for the installed `@kaelio/ktx` package) or
`packages/cli/package.json` (for dev-tree runs from this repo, which always
report `0.0.0-private`). Because the metadata mutation never has to survive
the run, no commit is pushed back to `main`. The git tag plus the published
npm artifact carry the version forward.
The `@semantic-release/git` plugin then commits these files back to `main`
with the release tag (see `scripts/semantic-release-config.cjs`). As a
result, the dev tree always reflects the most recently published version —
there is no sentinel pin. `ktx --version` and the bundled Python wheel both
report whatever was last released.
The bundled Python runtime wheel also derives its version from
`publicNpmPackageVersion`. Stable npm versions are reused as-is, and rc
versions are normalized to Python's version format. For example,
`0.1.0-rc.2` becomes `0.1.0rc2` in the `kaelio-ktx` wheel filename and wheel
metadata.
At runtime the CLI reads its version from its own `package.json` via
`getKtxCliPackageInfo()` (`packages/cli/src/cli-runtime.ts`); the Python
daemon reads its version from installed-package metadata via
`importlib.metadata.version()` (`python/ktx-daemon/src/ktx_daemon/__init__.py`).
The bundled Python runtime wheel also derives its version from the same
single source. Stable npm versions are reused as-is, and rc versions are
normalized to Python's version format. For example, `0.1.0-rc.2` becomes
`0.1.0rc2` in the `kaelio-ktx` wheel filename and wheel metadata.
## npm authentication

View file

@ -1,7 +1,7 @@
{
"name": "@kaelio/ktx",
"version": "0.4.1",
"description": "Standalone ktx context layer for database agents",
"description": "Standalone ktx context layer for data agents",
"type": "module",
"engines": {
"node": ">=22.0.0"
@ -30,7 +30,7 @@
"scripts": {
"assets:demo": "node scripts/build-demo-assets.mjs",
"build": "tsc -p tsconfig.json && node scripts/copy-runtime-assets.mjs && node ../../scripts/prepare-cli-bin.mjs",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('node_modules/.cache/tsc.tsbuildinfo', { force: true })\"",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
"docs:commands": "pnpm run build && node dist/print-command-tree.js",
"smoke": "vitest run src/standalone-smoke.test.ts src/example-smoke.test.ts --testTimeout 30000",
"test": "vitest run --exclude src/standalone-smoke.test.ts --exclude src/example-smoke.test.ts --exclude src/setup-databases.test.ts --exclude src/scan.test.ts --exclude src/commands/connection-metabase-setup.test.ts --exclude src/setup-models.test.ts --exclude src/setup-sources.test.ts --exclude src/setup.test.ts --exclude src/connection.test.ts --exclude src/setup-embeddings.test.ts --exclude src/ingest.test.ts --exclude src/commands/connection-mapping.test.ts --exclude src/ingest-viz.test.ts --exclude src/demo.test.ts --exclude src/setup-project.test.ts --exclude src/sl.test.ts --exclude src/local-scan-connectors.test.ts --exclude src/commands/connection-notion.test.ts --exclude src/context/scan/local-scan.test.ts --exclude src/context/mcp/local-project-ports.test.ts --exclude src/context/ingest/local-stage-ingest.test.ts --exclude src/context/sl/pglite-sl-search-prototype.test.ts --exclude src/context/core/git.service.test.ts --exclude src/context/ingest/local-adapters.test.ts --exclude src/context/ingest/local-bundle-ingest.test.ts --exclude src/context/ingest/local-metabase-ingest.test.ts --exclude src/context/sl/local-sl.test.ts --exclude src/context/search/pglite-owner-process.test.ts --exclude src/context/scan/local-enrichment-artifacts.test.ts --exclude src/context/search/pglite-spike.test.ts --exclude src/context/wiki/local-knowledge.test.ts --exclude src/context/sl/local-query.test.ts --exclude src/context/scan/relationship-review-decisions.test.ts --exclude src/context/scan/relationship-profiling.test.ts",

View file

@ -16,11 +16,11 @@ export function createKtxMcpServer(deps: KtxMcpServerDeps): KtxMcpServerDeps['se
}
export function createDefaultKtxMcpServer(
deps: Omit<KtxMcpServerDeps, 'server'> & { name?: string; version?: string },
deps: Omit<KtxMcpServerDeps, 'server'> & { name?: string; version: string },
): McpServer {
const server = new McpServer({
name: deps.name ?? 'ktx',
version: deps.version ?? '0.0.0-private',
version: deps.version,
});
createKtxMcpServer({
server: server as KtxMcpServerLike,

View file

@ -8,6 +8,7 @@ import type { MemoryFlowEvent, MemoryFlowReplayInput } from './context/ingest/me
import { renderMemoryFlowReplay } from './context/ingest/memory-flow/render.js';
import type { KtxSqlQueryExecutorPort } from './context/connections/query-executor.js';
import { loadKtxProject, type KtxLocalProject } from './context/project/project.js';
import { getKtxCliPackageInfo } from './cli-runtime.js';
import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
import { readIngestReportSnapshotFile } from './ingest-report-file.js';
@ -677,7 +678,7 @@ export async function runKtxIngest(
const resolution = await resolveProjectEmbeddingProvider(project, {
mode: 'ensure',
installPolicy: args.runtimeInstallPolicy ?? 'never',
cliVersion: args.cliVersion ?? '0.0.0-private',
cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version,
io,
});
const embeddingProvider =

View file

@ -4,7 +4,7 @@ import { loadKtxProject } from './context/project/project.js';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import type { KtxCliIo } from './cli-runtime.js';
import { getKtxCliPackageInfo, type KtxCliIo } from './cli-runtime.js';
import { createKtxMcpServerFactory } from './mcp-server-factory.js';
const DEFAULT_ALLOWED_HOSTS = ['localhost', '127.0.0.1', '::1'] as const;
@ -178,7 +178,7 @@ export async function runKtxMcpHttpServer(options: RunKtxMcpHttpServerOptions):
(await createKtxMcpServerFactory({
project: project!,
projectDir: options.projectDir,
cliVersion: options.cliVersion ?? '0.0.0-private',
cliVersion: options.cliVersion ?? getKtxCliPackageInfo().version,
io: options.io,
}));
const sessions = new Map<string, StreamableHTTPServerTransport>();

View file

@ -3,7 +3,7 @@ import type { Readable, Writable } from 'node:stream';
import { loadKtxProject } from './context/project/project.js';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import type { KtxCliIo } from './cli-runtime.js';
import { getKtxCliPackageInfo, type KtxCliIo } from './cli-runtime.js';
import { createKtxMcpServerFactory } from './mcp-server-factory.js';
export interface RunKtxMcpStdioServerOptions {
@ -30,7 +30,7 @@ export async function runKtxMcpStdioServer(options: RunKtxMcpStdioServerOptions)
(await createKtxMcpServerFactory({
project: project!,
projectDir: options.projectDir,
cliVersion: options.cliVersion ?? '0.0.0-private',
cliVersion: options.cliVersion ?? getKtxCliPackageInfo().version,
io: protocolIo,
}));
const stdin = options.stdin ?? process.stdin;

View file

@ -1,3 +1,4 @@
import { getKtxCliPackageInfo } from './cli-runtime.js';
import { loadKtxProject, type KtxLocalProject } from './context/project/project.js';
import type { KtxProjectConnectionConfig } from './context/project/config.js';
import type { KtxProgressPort } from './context/scan/types.js';
@ -878,7 +879,7 @@ export async function runKtxPublicIngest(
for (const feature of requirements.features) {
try {
await ensureRuntime({
cliVersion: args.cliVersion ?? '0.0.0-private',
cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version,
installPolicy: args.runtimeInstallPolicy ?? 'prompt',
io,
feature,

View file

@ -1,6 +1,7 @@
import type { KtxProgressPort, KtxScanMode, KtxScanReport, KtxScanWarning } from './context/scan/types.js';
import { runLocalScan } from './context/scan/local-scan.js';
import { loadKtxProject } from './context/project/project.js';
import { getKtxCliPackageInfo } from './cli-runtime.js';
import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
import type { KtxCliIo } from './index.js';
import { createKtxCliLocalIngestAdapters } from './local-adapters.js';
@ -314,7 +315,7 @@ export async function runKtxScan(args: KtxScanArgs, io: KtxCliIo = process, deps
const resolution = await resolveProjectEmbeddingProvider(project, {
mode: 'ensure',
installPolicy: args.runtimeInstallPolicy ?? 'never',
cliVersion: args.cliVersion ?? '0.0.0-private',
cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version,
io,
});
const embeddingProvider =

View file

@ -1,6 +1,6 @@
[project]
name = "ktx-daemon"
version = "0.0.0+private"
version = "0.4.1"
description = "Portable compute package for KTX semantic-layer operations"
readme = "README.md"
requires-python = ">=3.13"

View file

@ -1,6 +1,6 @@
[project]
name = "ktx-sl"
version = "0.0.0+private"
version = "0.4.1"
description = "Agent-first semantic layer engine with aggregate locality"
readme = "README.md"
requires-python = ">=3.13"

View file

@ -1,6 +1,5 @@
{
"schemaVersion": 1,
"publicNpmPackageVersion": "0.4.1",
"releaseMode": "npm-public-release-ready",
"npm": {
"publish": true,

View file

@ -52,7 +52,10 @@ describe('runtimeWheelPyproject', () => {
const pyproject = runtimeWheelPyproject();
assert.match(pyproject, /name = "kaelio-ktx"/);
assert.match(pyproject, new RegExp(`version = "${RUNTIME_WHEEL_PACKAGE_VERSION.replace(/\./g, '\\.')}"`));
assert.match(
pyproject,
new RegExp(`version = "${RUNTIME_WHEEL_PACKAGE_VERSION.replace(/[.+]/g, (char) => `\\${char}`)}"`),
);
assert.match(pyproject, /ktx-daemon = "ktx_daemon\.__main__:main"/);
assert.match(pyproject, /packages = \["semantic_layer", "ktx_daemon"\]/);
assert.match(pyproject, /\[project\.optional-dependencies\]/);

View file

@ -57,11 +57,11 @@ describe('publicKtxTarballName', () => {
});
describe('expectedPublicKtxVersionPattern', () => {
it('matches the public package version and rejects the private workspace version', () => {
it('matches the public package version and rejects other versions', () => {
const pattern = expectedPublicKtxVersionPattern();
assert.match(`@kaelio/ktx ${PUBLIC_NPM_PACKAGE_VERSION}\n`, pattern);
assert.doesNotMatch('@kaelio/ktx 0.0.0-private\n', pattern);
assert.doesNotMatch('@kaelio/ktx 9.9.9-other\n', pattern);
});
});

View file

@ -34,7 +34,6 @@ async function writeJson(path, value) {
async function writeReleaseMetadataInputs(root) {
await writeJson(join(root, 'release-policy.json'), {
schemaVersion: 1,
publicNpmPackageVersion: PUBLIC_NPM_PACKAGE_VERSION,
releaseMode: 'ci-artifact-only',
npm: {
publish: false,

View file

@ -25,6 +25,10 @@ export function releasePolicyPath(rootDir = scriptRootDir()) {
return join(rootDir, 'release-policy.json');
}
export function cliPackageJsonPath(rootDir = scriptRootDir()) {
return join(rootDir, 'packages', 'cli', 'package.json');
}
function readJsonSync(path) {
return JSON.parse(readFileSync(path, 'utf8'));
}
@ -70,20 +74,24 @@ export function assertPublicNpmReleaseTag(tag) {
throw new Error(`Invalid public npm release tag: ${tag}`);
}
function readCliPackageVersion(rootDir = scriptRootDir()) {
const packageJson = readJsonSync(cliPackageJsonPath(rootDir));
return assertPublicNpmPackageVersion(packageJson.version);
}
export function readPublicNpmReleaseMetadata(rootDir = scriptRootDir()) {
const policy = readJsonSync(releasePolicyPath(rootDir));
const version = assertPublicNpmPackageVersion(policy.publicNpmPackageVersion);
const tag = assertPublicNpmReleaseTag(policy.npm?.tag);
return {
packageName: PUBLIC_NPM_PACKAGE_NAME,
version,
version: readCliPackageVersion(rootDir),
tag,
};
}
export function publicNpmPackageVersion(rootDir = scriptRootDir()) {
return readPublicNpmReleaseMetadata(rootDir).version;
return readCliPackageVersion(rootDir);
}
export const PUBLIC_NPM_PACKAGE_VERSION = publicNpmPackageVersion();

View file

@ -5,7 +5,7 @@ import { dirname, join, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { packageArtifactLayout, packageReleaseMetadata, verifyArtifactManifest } from './package-artifacts.mjs';
import { assertPublicNpmPackageVersion, publicNpmPackageVersion } from './public-npm-release-metadata.mjs';
import { publicNpmPackageVersion } from './public-npm-release-metadata.mjs';
import { readPublishedPackageSmokeConfig } from './published-package-smoke-config.mjs';
function scriptRootDir() {
@ -138,8 +138,6 @@ export function validateReleasePolicy(policy) {
throw new Error(`Unsupported release policy schemaVersion: ${policy.schemaVersion}`);
}
assertSupportedReleaseMode(policy.releaseMode);
assertString(policy.publicNpmPackageVersion, 'Release policy publicNpmPackageVersion');
assertPublicNpmPackageVersion(policy.publicNpmPackageVersion);
assertPlainObject(policy.npm, 'Release policy npm');
assertPlainObject(policy.python, 'Release policy python');
assertPlainObject(policy.publishedPackageSmoke, 'Release policy publishedPackageSmoke');

View file

@ -52,7 +52,6 @@ function releasePolicy(overrides = {}) {
return {
schemaVersion: 1,
publicNpmPackageVersion: PUBLIC_NPM_PACKAGE_VERSION,
releaseMode: 'ci-artifact-only',
npm: {
publish: false,

View file

@ -166,7 +166,13 @@ function createReleaseConfig(env = process.env) {
[
'@semantic-release/git',
{
assets: ['package.json', 'release-policy.json', 'packages/cli/package.json'],
assets: [
'package.json',
'release-policy.json',
'packages/cli/package.json',
'python/ktx-daemon/pyproject.toml',
'python/ktx-sl/pyproject.toml',
],
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
},
],

View file

@ -88,7 +88,13 @@ describe('semantic-release config', () => {
const config = createReleaseConfig({ KTX_RELEASE_KIND: kind, GITHUB_REF_NAME: 'main' });
const git = gitPluginOptions(config);
assert.ok(git, `${kind}: @semantic-release/git plugin must be configured`);
assert.deepEqual(git.assets, ['package.json', 'release-policy.json', 'packages/cli/package.json']);
assert.deepEqual(git.assets, [
'package.json',
'release-policy.json',
'packages/cli/package.json',
'python/ktx-daemon/pyproject.toml',
'python/ktx-sl/pyproject.toml',
]);
assert.match(git.message, /^chore\(release\): \$\{nextRelease\.version\} \[skip ci\]/);
}
});

View file

@ -8,6 +8,7 @@ import {
PUBLIC_NPM_PACKAGE_NAME,
assertPublicNpmPackageVersion,
assertPublicNpmReleaseTag,
publicNpmPackageVersionToPythonVersion,
releasePolicyPath,
} from './public-npm-release-metadata.mjs';
@ -23,6 +24,42 @@ async function writeJson(path, value) {
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
}
function pyprojectWithProjectVersion(source, version) {
const lines = source.split('\n');
let inProject = false;
let replaced = false;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
const sectionMatch = /^\s*\[([^\]]+)\]\s*$/.exec(line);
if (sectionMatch) {
inProject = sectionMatch[1] === 'project';
continue;
}
if (inProject && /^\s*version\s*=\s*"[^"]*"\s*$/.test(line)) {
lines[index] = `version = "${version}"`;
replaced = true;
break;
}
}
if (!replaced) {
throw new Error('No [project].version assignment found in pyproject.toml');
}
return lines.join('\n');
}
async function rewritePyprojectVersion(path, version) {
const source = await readFile(path, 'utf8');
await writeFile(path, pyprojectWithProjectVersion(source, version));
}
function safePythonVersionFor(version) {
try {
return publicNpmPackageVersionToPythonVersion(version);
} catch {
return null;
}
}
export async function updatePublicReleaseVersion(rootDir, version, tag) {
const safeVersion = assertPublicNpmPackageVersion(version);
const safeTag = assertPublicNpmReleaseTag(tag);
@ -37,9 +74,15 @@ export async function updatePublicReleaseVersion(rootDir, version, tag) {
cliPackageJson.version = safeVersion;
await writeJson(cliPackageJsonPath, cliPackageJson);
const pythonVersion = safePythonVersionFor(safeVersion);
if (pythonVersion !== null) {
await rewritePyprojectVersion(join(rootDir, 'python', 'ktx-daemon', 'pyproject.toml'), pythonVersion);
await rewritePyprojectVersion(join(rootDir, 'python', 'ktx-sl', 'pyproject.toml'), pythonVersion);
}
const policyPath = releasePolicyPath(rootDir);
const policy = await readJson(policyPath);
policy.publicNpmPackageVersion = safeVersion;
delete policy.publicNpmPackageVersion;
policy.releaseMode = 'npm-public-release-ready';
policy.requiredBeforePublishing = [];
policy.npm = {
@ -60,6 +103,7 @@ export async function updatePublicReleaseVersion(rootDir, version, tag) {
return {
version: safeVersion,
tag: safeTag,
pythonVersion,
};
}

View file

@ -11,23 +11,51 @@ async function writeJson(path, value) {
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
}
async function writeText(path, value) {
await mkdir(join(path, '..'), { recursive: true });
await writeFile(path, value);
}
async function readJson(path) {
return JSON.parse(await readFile(path, 'utf8'));
}
async function readText(path) {
return readFile(path, 'utf8');
}
const DAEMON_PYPROJECT = `[project]
name = "ktx-daemon"
version = "0.4.0"
description = "Portable compute package for KTX semantic-layer operations"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
`;
const SL_PYPROJECT = `[project]
name = "ktx-sl"
version = "0.4.0"
description = "Agent-first semantic layer engine with aggregate locality"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
`;
async function writeReleaseFixture(root) {
await writeJson(join(root, 'package.json'), {
name: 'ktx-workspace',
version: '0.0.0-private',
version: '0.4.0',
private: true,
});
await writeJson(join(root, 'packages', 'cli', 'package.json'), {
name: '@kaelio/ktx',
version: '0.0.0-private',
version: '0.4.0',
});
await writeJson(join(root, 'release-policy.json'), {
schemaVersion: 1,
publicNpmPackageVersion: '0.1.0-rc.1',
releaseMode: 'ci-artifact-only',
npm: {
publish: false,
@ -43,7 +71,7 @@ async function writeReleaseFixture(root) {
},
publishedPackageSmoke: {
packageName: '@kaelio/ktx',
version: '0.1.0-rc.1',
version: 'latest',
registry: null,
},
runtimeInstaller: {
@ -53,6 +81,8 @@ async function writeReleaseFixture(root) {
},
requiredBeforePublishing: ['Choose public release version.'],
});
await writeText(join(root, 'python', 'ktx-daemon', 'pyproject.toml'), DAEMON_PYPROJECT);
await writeText(join(root, 'python', 'ktx-sl', 'pyproject.toml'), SL_PYPROJECT);
}
describe('updatePublicReleaseVersion', () => {
@ -65,9 +95,10 @@ describe('updatePublicReleaseVersion', () => {
assert.equal((await readJson(join(root, 'package.json'))).version, '0.1.0-rc.2');
assert.equal((await readJson(join(root, 'packages', 'cli', 'package.json'))).version, '0.1.0-rc.2');
assert.match(await readText(join(root, 'python', 'ktx-daemon', 'pyproject.toml')), /^version = "0\.1\.0rc2"$/m);
assert.match(await readText(join(root, 'python', 'ktx-sl', 'pyproject.toml')), /^version = "0\.1\.0rc2"$/m);
assert.deepEqual(await readJson(join(root, 'release-policy.json')), {
schemaVersion: 1,
publicNpmPackageVersion: '0.1.0-rc.2',
releaseMode: 'npm-public-release-ready',
npm: {
publish: true,
@ -110,8 +141,17 @@ describe('updatePublicReleaseVersion', () => {
(await readJson(join(root, 'packages', 'cli', 'package.json'))).version,
'0.1.0-feature-foo.0',
);
assert.match(
await readText(join(root, 'python', 'ktx-daemon', 'pyproject.toml')),
/^version = "0\.4\.0"$/m,
);
assert.match(
await readText(join(root, 'python', 'ktx-sl', 'pyproject.toml')),
/^version = "0\.4\.0"$/m,
);
const policy = await readJson(join(root, 'release-policy.json'));
assert.equal(policy.publicNpmPackageVersion, '0.1.0-feature-foo.0');
assert.equal(policy.publicNpmPackageVersion, undefined);
assert.equal(policy.publishedPackageSmoke.version, '0.1.0-feature-foo.0');
assert.equal(policy.npm.tag, 'branch-feature-foo');
} finally {
await rm(root, { recursive: true, force: true });

4
uv.lock generated
View file

@ -440,7 +440,7 @@ wheels = [
[[package]]
name = "ktx-daemon"
version = "0.0.0+private"
version = "0.4.1"
source = { editable = "python/ktx-daemon" }
dependencies = [
{ name = "fastapi" },
@ -495,7 +495,7 @@ dev = [
[[package]]
name = "ktx-sl"
version = "0.0.0+private"
version = "0.4.1"
source = { editable = "python/ktx-sl" }
dependencies = [
{ name = "pydantic" },