import assert from 'node:assert/strict'; import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, it } from 'node:test'; import { updatePublicReleaseVersion } from './update-public-release-version.mjs'; async function writeJson(path, value) { await mkdir(join(path, '..'), { recursive: true }); 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.4.0', private: true, }); await writeJson(join(root, 'packages', 'cli', 'package.json'), { name: '@kaelio/ktx', version: '0.4.0', }); await writeJson(join(root, 'release-policy.json'), { schemaVersion: 1, releaseMode: 'ci-artifact-only', npm: { publish: false, registry: null, access: 'public', tag: 'next', packages: ['@kaelio/ktx'], }, python: { publish: false, repository: null, packages: ['kaelio-ktx'], }, publishedPackageSmoke: { packageName: '@kaelio/ktx', version: 'latest', registry: null, }, runtimeInstaller: { uvStrategy: 'path-prerequisite', bootstrapUv: false, missingUvBehavior: 'focused-error', }, 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', () => { it('updates package and release policy metadata for rc releases', async () => { const root = await mkdtemp(join(tmpdir(), 'ktx-release-version-test-')); try { await writeReleaseFixture(root); await updatePublicReleaseVersion(root, '0.1.0-rc.2', 'next'); 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, releaseMode: 'npm-public-release-ready', npm: { publish: true, registry: null, access: 'public', tag: 'next', packages: ['@kaelio/ktx'], }, python: { publish: false, repository: null, packages: ['kaelio-ktx'], }, publishedPackageSmoke: { packageName: '@kaelio/ktx', version: '0.1.0-rc.2', registry: null, }, runtimeInstaller: { uvStrategy: 'path-prerequisite', bootstrapUv: false, missingUvBehavior: 'focused-error', }, requiredBeforePublishing: [], }); } finally { await rm(root, { recursive: true, force: true }); } }); it('accepts branch-prefixed npm release tags produced by branch RC publishes', async () => { const root = await mkdtemp(join(tmpdir(), 'ktx-release-version-branch-test-')); try { await writeReleaseFixture(root); await updatePublicReleaseVersion(root, '0.1.0-feature-foo.0', 'branch-feature-foo'); assert.equal((await readJson(join(root, 'package.json'))).version, '0.1.0-feature-foo.0'); assert.equal( (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, 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 }); } }); it('rejects invalid versions and tags', async () => { const root = await mkdtemp(join(tmpdir(), 'ktx-release-version-invalid-test-')); try { await writeReleaseFixture(root); await assert.rejects(() => updatePublicReleaseVersion(root, 'not a version', 'next'), /Invalid public npm package version/); await assert.rejects(() => updatePublicReleaseVersion(root, '0.2.0', 'canary'), /Invalid public npm release tag/); } finally { await rm(root, { recursive: true, force: true }); } }); });