mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
fix: derive runtime versions from release metadata
This commit is contained in:
parent
1c30abc51d
commit
8aea27bfbe
18 changed files with 231 additions and 50 deletions
|
|
@ -100,6 +100,12 @@ The artifact packaging and readiness scripts read `publicNpmPackageVersion`
|
|||
from `release-policy.json`, so manual version edits in build scripts aren't
|
||||
needed for rc releases.
|
||||
|
||||
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.
|
||||
|
||||
## npm authentication
|
||||
|
||||
The release workflow publishes through npm Trusted Publishing. It doesn't use
|
||||
|
|
|
|||
|
|
@ -11,7 +11,13 @@ function stubIo(): KtxCliIo {
|
|||
}
|
||||
|
||||
function stubPackageInfo(): KtxCliPackageInfo {
|
||||
return { name: '@ktx/cli', version: '0.0.0-test', contextPackageName: '@ktx/context' };
|
||||
return {
|
||||
name: '@ktx/cli',
|
||||
version: '0.0.0-test',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.0.0-test',
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
||||
describe('buildKtxProgram', () => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { KtxSlArgs } from './sl.js';
|
|||
import type { KtxSqlArgs } from './sql.js';
|
||||
import { profileMark, profileSpan } from './startup-profile.js';
|
||||
import type { KtxTextIngestArgs } from './text-ingest.js';
|
||||
import { resolveKtxRuntimeVersion } from './release-version.js';
|
||||
|
||||
profileMark('module:cli-runtime');
|
||||
|
||||
|
|
@ -18,6 +19,8 @@ const requirePackageJson = createRequire(import.meta.url);
|
|||
export interface KtxCliPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
packageVersion: string;
|
||||
runtimeVersion: string;
|
||||
contextPackageName: '@ktx/context';
|
||||
}
|
||||
|
||||
|
|
@ -61,9 +64,16 @@ export function packageInfoFromJson(packageJson: unknown): KtxCliPackageInfo {
|
|||
throw new Error('Invalid KTX CLI package metadata');
|
||||
}
|
||||
|
||||
const runtimeVersion = resolveKtxRuntimeVersion({
|
||||
packageName: packageJson.name,
|
||||
packageVersion: packageJson.version,
|
||||
});
|
||||
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
version: runtimeVersion,
|
||||
packageVersion: packageJson.version,
|
||||
runtimeVersion,
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@ describe('getKtxCliPackageInfo', () => {
|
|||
it('identifies the CLI package and its context dependency', () => {
|
||||
expect(getKtxCliPackageInfo()).toEqual({
|
||||
name: '@ktx/cli',
|
||||
version: '0.0.0-private',
|
||||
version: '0.1.0-rc.1',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.1.0-rc.1',
|
||||
contextPackageName: '@ktx/context',
|
||||
});
|
||||
});
|
||||
|
|
@ -68,6 +70,8 @@ describe('getKtxCliPackageInfo', () => {
|
|||
).toEqual({
|
||||
name: '@kaelio/ktx',
|
||||
version: '0.1.0',
|
||||
packageVersion: '0.1.0',
|
||||
runtimeVersion: '0.1.0',
|
||||
contextPackageName: '@ktx/context',
|
||||
});
|
||||
});
|
||||
|
|
@ -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 0.1.0-rc.1\n');
|
||||
expect(testIo.stderr()).toBe('');
|
||||
});
|
||||
|
||||
|
|
@ -252,7 +256,7 @@ describe('runKtxCli', () => {
|
|||
expect(listIo.stderr()).toContain("unknown option '--query'");
|
||||
});
|
||||
|
||||
it('routes runtime management commands with the CLI package version', async () => {
|
||||
it('routes runtime management commands with the release runtime version', async () => {
|
||||
const runtime = vi.fn(async () => 0);
|
||||
const installIo = makeIo();
|
||||
const startIo = makeIo();
|
||||
|
|
@ -278,7 +282,7 @@ describe('runKtxCli', () => {
|
|||
1,
|
||||
{
|
||||
command: 'install',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
feature: 'local-embeddings',
|
||||
force: true,
|
||||
},
|
||||
|
|
@ -288,7 +292,7 @@ describe('runKtxCli', () => {
|
|||
2,
|
||||
{
|
||||
command: 'start',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
projectDir: expect.any(String),
|
||||
feature: 'local-embeddings',
|
||||
force: true,
|
||||
|
|
@ -299,7 +303,7 @@ describe('runKtxCli', () => {
|
|||
3,
|
||||
{
|
||||
command: 'stop',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
projectDir: expect.any(String),
|
||||
all: false,
|
||||
},
|
||||
|
|
@ -309,7 +313,7 @@ describe('runKtxCli', () => {
|
|||
4,
|
||||
{
|
||||
command: 'stop',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
projectDir: expect.any(String),
|
||||
all: true,
|
||||
},
|
||||
|
|
@ -319,7 +323,7 @@ describe('runKtxCli', () => {
|
|||
5,
|
||||
{
|
||||
command: 'status',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
json: true,
|
||||
},
|
||||
statusIo.io,
|
||||
|
|
@ -392,7 +396,7 @@ describe('runKtxCli', () => {
|
|||
expect.objectContaining({
|
||||
command: 'query',
|
||||
projectDir: tempDir,
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
query: expect.objectContaining({ measures: ['orders.order_count'], dimensions: [] }),
|
||||
}),
|
||||
|
|
@ -407,7 +411,7 @@ describe('runKtxCli', () => {
|
|||
).resolves.toBe(0);
|
||||
expect(sl).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
}),
|
||||
autoIo.io,
|
||||
|
|
@ -423,7 +427,7 @@ describe('runKtxCli', () => {
|
|||
).resolves.toBe(0);
|
||||
expect(sl).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'never',
|
||||
}),
|
||||
noInputIo.io,
|
||||
|
|
@ -562,7 +566,7 @@ describe('runKtxCli', () => {
|
|||
skipAgents: false,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: false,
|
||||
databaseSchemas: [],
|
||||
|
|
@ -692,7 +696,7 @@ describe('runKtxCli', () => {
|
|||
inputMode: 'disabled',
|
||||
depth: 'fast',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -719,7 +723,7 @@ describe('runKtxCli', () => {
|
|||
inputMode: 'auto',
|
||||
depth: 'deep',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -796,7 +800,7 @@ describe('runKtxCli', () => {
|
|||
json: false,
|
||||
inputMode: 'disabled',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -1074,7 +1078,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
|
|
@ -1113,7 +1117,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
|
|
@ -1150,7 +1154,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
llmBackend: 'claude-code',
|
||||
llmModel: 'opus',
|
||||
skipLlm: false,
|
||||
|
|
@ -1258,7 +1262,7 @@ describe('runKtxCli', () => {
|
|||
projectDir: '/tmp/project',
|
||||
inputMode: 'disabled',
|
||||
yes: true,
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
skipLlm: true,
|
||||
skipEmbeddings: true,
|
||||
databaseDrivers: ['postgres'],
|
||||
|
|
@ -1576,7 +1580,7 @@ describe('runKtxCli', () => {
|
|||
queryFile: '/tmp/query.json',
|
||||
execute: false,
|
||||
format: 'json',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
},
|
||||
autoIo.io,
|
||||
|
|
@ -1590,7 +1594,7 @@ describe('runKtxCli', () => {
|
|||
queryFile: '/tmp/query.json',
|
||||
execute: false,
|
||||
format: 'json',
|
||||
cliVersion: '0.0.0-private',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
neverIo.io,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,13 @@ function silentIo(): KtxCliIo {
|
|||
}
|
||||
|
||||
function stubPackageInfo(): KtxCliPackageInfo {
|
||||
return { name: '@ktx/cli', version: '0.0.0-docs', contextPackageName: '@ktx/context' };
|
||||
return {
|
||||
name: '@ktx/cli',
|
||||
version: '0.0.0-docs',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.0.0-docs',
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
||||
export function renderKtxCommandTree(): string {
|
||||
|
|
|
|||
55
packages/cli/src/release-version.ts
Normal file
55
packages/cli/src/release-version.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join, parse } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const semverPattern =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function assertReleaseVersion(value: unknown, source: string): string {
|
||||
if (typeof value !== 'string' || !semverPattern.test(value)) {
|
||||
throw new Error(`Invalid KTX release version in ${source}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function findReleasePolicyPath(startDir: string): string | undefined {
|
||||
let current = startDir;
|
||||
const root = parse(current).root;
|
||||
while (true) {
|
||||
const candidate = join(current, 'release-policy.json');
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
if (current === root) {
|
||||
return undefined;
|
||||
}
|
||||
current = dirname(current);
|
||||
}
|
||||
}
|
||||
|
||||
function readSourceReleaseVersion(startDir = dirname(fileURLToPath(import.meta.url))): string | undefined {
|
||||
const policyPath = findReleasePolicyPath(startDir);
|
||||
if (!policyPath) {
|
||||
return undefined;
|
||||
}
|
||||
const policy = JSON.parse(readFileSync(policyPath, 'utf8')) as unknown;
|
||||
if (!isPlainObject(policy)) {
|
||||
throw new Error(`Invalid KTX release policy: ${policyPath}`);
|
||||
}
|
||||
return assertReleaseVersion(policy.publicNpmPackageVersion, policyPath);
|
||||
}
|
||||
|
||||
export function resolveKtxRuntimeVersion(input: {
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
startDir?: string;
|
||||
}): string {
|
||||
if (input.packageName === '@kaelio/ktx') {
|
||||
return assertReleaseVersion(input.packageVersion, `${input.packageName}/package.json`);
|
||||
}
|
||||
return readSourceReleaseVersion(input.startDir) ?? input.packageVersion;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "ktx-daemon"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0+private"
|
||||
description = "Portable compute package for KTX semantic-layer operations"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
"""Portable compute package for KTX."""
|
||||
|
||||
PACKAGE_NAME = "ktx-daemon"
|
||||
VERSION = "0.1.0"
|
||||
from collections.abc import Callable
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
__all__ = ["PACKAGE_NAME", "VERSION"]
|
||||
PACKAGE_NAME = "ktx-daemon"
|
||||
RUNTIME_DISTRIBUTION_NAME = "kaelio-ktx"
|
||||
|
||||
|
||||
def resolve_package_version(
|
||||
version_loader: Callable[[str], str] = version,
|
||||
) -> str:
|
||||
for distribution_name in (RUNTIME_DISTRIBUTION_NAME, PACKAGE_NAME):
|
||||
try:
|
||||
return version_loader(distribution_name)
|
||||
except PackageNotFoundError:
|
||||
continue
|
||||
return "0.0.0+local"
|
||||
|
||||
|
||||
VERSION = resolve_package_version()
|
||||
|
||||
__all__ = [
|
||||
"PACKAGE_NAME",
|
||||
"RUNTIME_DISTRIBUTION_NAME",
|
||||
"VERSION",
|
||||
"resolve_package_version",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from typing import Any
|
|||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import Response
|
||||
|
||||
from ktx_daemon import VERSION
|
||||
from ktx_daemon.code_execution import (
|
||||
ExecuteCodeRequest,
|
||||
ExecuteCodeResponse,
|
||||
|
|
@ -84,7 +85,7 @@ def create_app(
|
|||
app = FastAPI(
|
||||
title="KTX Daemon",
|
||||
description="Stateless portable compute server for KTX.",
|
||||
version="0.1.0",
|
||||
version=VERSION,
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
from ktx_daemon import PACKAGE_NAME, VERSION
|
||||
from ktx_daemon import PACKAGE_NAME, VERSION, resolve_package_version
|
||||
|
||||
|
||||
def test_package_metadata() -> None:
|
||||
assert PACKAGE_NAME == "ktx-daemon"
|
||||
assert VERSION == "0.1.0"
|
||||
assert VERSION == resolve_package_version()
|
||||
|
||||
|
||||
def test_package_version_prefers_bundled_runtime_distribution() -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
def fake_version(distribution_name: str) -> str:
|
||||
calls.append(distribution_name)
|
||||
if distribution_name == "kaelio-ktx":
|
||||
return "0.1.0rc1"
|
||||
raise AssertionError(f"unexpected distribution lookup: {distribution_name}")
|
||||
|
||||
assert resolve_package_version(version_loader=fake_version) == "0.1.0rc1"
|
||||
assert calls == ["kaelio-ktx"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "ktx-sl"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0+private"
|
||||
description = "Agent-first semantic layer engine with aggregate locality"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import { dirname, join, resolve } from 'node:path';
|
|||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import { publicPythonRuntimePackageVersion } from './public-npm-release-metadata.mjs';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export const RUNTIME_WHEEL_DISTRIBUTION_NAME = 'kaelio-ktx';
|
||||
export const RUNTIME_WHEEL_NORMALIZED_NAME = 'kaelio_ktx';
|
||||
export const RUNTIME_WHEEL_PACKAGE_VERSION = '0.1.0';
|
||||
export const RUNTIME_WHEEL_PACKAGE_VERSION = publicPythonRuntimePackageVersion();
|
||||
|
||||
function scriptRootDir() {
|
||||
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ describe('runtimeWheelLayout', () => {
|
|||
});
|
||||
|
||||
describe('runtimeWheelPyproject', () => {
|
||||
it('describes one kaelio-ktx wheel with lazy local embeddings', () => {
|
||||
it('describes one kaelio-ktx wheel with the release-derived Python version and lazy local embeddings', () => {
|
||||
const pyproject = runtimeWheelPyproject();
|
||||
|
||||
assert.match(pyproject, /name = "kaelio-ktx"/);
|
||||
assert.match(pyproject, /version = "0\.1\.0"/);
|
||||
assert.match(pyproject, /version = "0\.1\.0rc1"/);
|
||||
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.0');
|
||||
assert.equal(RUNTIME_WHEEL_PACKAGE_VERSION, '0.1.0rc1');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ async function writeUploadableArtifactFixtures(layout) {
|
|||
`${packageInfo.name}-tarball`,
|
||||
]),
|
||||
[
|
||||
join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
|
||||
join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
|
||||
'kaelio-ktx-runtime-wheel',
|
||||
],
|
||||
]);
|
||||
|
|
@ -139,7 +139,7 @@ describe('packageReleaseMetadata', () => {
|
|||
ecosystem: 'python',
|
||||
packageName: 'kaelio-ktx',
|
||||
packageRoot: 'python/runtime-wheel',
|
||||
packageVersion: '0.1.0',
|
||||
packageVersion: '0.1.0rc1',
|
||||
private: false,
|
||||
releaseMode: 'ci-artifact-only',
|
||||
},
|
||||
|
|
@ -154,10 +154,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.0-py3-none-any.whl'), '');
|
||||
await writeFile(join(root, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'), '');
|
||||
|
||||
assert.deepEqual(await findPythonArtifacts(root), {
|
||||
runtimeWheel: join(root, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
|
||||
runtimeWheel: join(root, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
|
||||
});
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
|
|
@ -210,7 +210,7 @@ describe('artifact manifest', () => {
|
|||
ecosystem: 'python',
|
||||
packageName: 'kaelio-ktx',
|
||||
packageRoot: 'python/runtime-wheel',
|
||||
packageVersion: '0.1.0',
|
||||
packageVersion: '0.1.0rc1',
|
||||
private: false,
|
||||
releaseMode: 'ci-artifact-only',
|
||||
},
|
||||
|
|
@ -252,8 +252,8 @@ describe('artifact manifest', () => {
|
|||
artifactKind: 'wheel',
|
||||
ecosystem: 'python',
|
||||
packageName: 'kaelio-ktx',
|
||||
packageVersion: '0.1.0',
|
||||
path: 'python/kaelio_ktx-0.1.0-py3-none-any.whl',
|
||||
packageVersion: '0.1.0rc1',
|
||||
path: 'python/kaelio_ktx-0.1.0rc1-py3-none-any.whl',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -362,17 +362,17 @@ describe('copyRuntimeWheelAssets', () => {
|
|||
try {
|
||||
await mkdir(layout.pythonDir, { recursive: true });
|
||||
await writeFile(
|
||||
join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
|
||||
join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
|
||||
'kaelio-ktx-runtime-wheel',
|
||||
);
|
||||
|
||||
const assets = await copyRuntimeWheelAssets(layout, {
|
||||
runtimeWheel: join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
|
||||
runtimeWheel: join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
assets.wheelPath,
|
||||
join(root, 'packages', 'cli', 'assets', 'python', 'kaelio_ktx-0.1.0-py3-none-any.whl'),
|
||||
join(root, 'packages', 'cli', 'assets', 'python', 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'),
|
||||
);
|
||||
assert.equal(
|
||||
assets.manifestPath,
|
||||
|
|
@ -385,7 +385,7 @@ describe('copyRuntimeWheelAssets', () => {
|
|||
normalizedName: RUNTIME_WHEEL_NORMALIZED_NAME,
|
||||
version: RUNTIME_WHEEL_PACKAGE_VERSION,
|
||||
wheel: {
|
||||
file: 'kaelio_ktx-0.1.0-py3-none-any.whl',
|
||||
file: 'kaelio_ktx-0.1.0rc1-py3-none-any.whl',
|
||||
sha256: createHash('sha256')
|
||||
.update('kaelio-ktx-runtime-wheel')
|
||||
.digest('hex'),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export const PUBLIC_NPM_RELEASE_TAGS = new Set(['latest', 'next']);
|
|||
|
||||
const SEMVER_PATTERN =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
||||
const SEMVER_PARTS_PATTERN =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
||||
|
||||
function scriptRootDir() {
|
||||
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
|
@ -29,6 +31,30 @@ export function assertPublicNpmPackageVersion(version) {
|
|||
return version;
|
||||
}
|
||||
|
||||
export function publicNpmPackageVersionToPythonVersion(version) {
|
||||
const safeVersion = assertPublicNpmPackageVersion(version);
|
||||
const match = SEMVER_PARTS_PATTERN.exec(safeVersion);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid public npm package version: ${version}`);
|
||||
}
|
||||
|
||||
const [, major, minor, patch, prerelease, buildMetadata] = match;
|
||||
if (buildMetadata) {
|
||||
throw new Error(`Unsupported public npm build metadata for Python runtime version: ${safeVersion}`);
|
||||
}
|
||||
|
||||
const baseVersion = `${major}.${minor}.${patch}`;
|
||||
if (!prerelease) {
|
||||
return baseVersion;
|
||||
}
|
||||
|
||||
const rcMatch = /^rc\.([1-9]\d*|0)$/.exec(prerelease);
|
||||
if (!rcMatch) {
|
||||
throw new Error(`Unsupported public npm prerelease for Python runtime version: ${safeVersion}`);
|
||||
}
|
||||
return `${baseVersion}rc${rcMatch[1]}`;
|
||||
}
|
||||
|
||||
export function assertPublicNpmReleaseTag(tag) {
|
||||
if (!PUBLIC_NPM_RELEASE_TAGS.has(tag)) {
|
||||
throw new Error(`Invalid public npm release tag: ${tag}`);
|
||||
|
|
@ -51,3 +77,7 @@ export function readPublicNpmReleaseMetadata(rootDir = scriptRootDir()) {
|
|||
export function publicNpmPackageVersion(rootDir = scriptRootDir()) {
|
||||
return readPublicNpmReleaseMetadata(rootDir).version;
|
||||
}
|
||||
|
||||
export function publicPythonRuntimePackageVersion(rootDir = scriptRootDir()) {
|
||||
return publicNpmPackageVersionToPythonVersion(publicNpmPackageVersion(rootDir));
|
||||
}
|
||||
|
|
|
|||
26
scripts/public-npm-release-metadata.test.mjs
Normal file
26
scripts/public-npm-release-metadata.test.mjs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import { publicNpmPackageVersionToPythonVersion } from './public-npm-release-metadata.mjs';
|
||||
|
||||
describe('publicNpmPackageVersionToPythonVersion', () => {
|
||||
it('keeps stable public npm versions unchanged for Python wheels', () => {
|
||||
assert.equal(publicNpmPackageVersionToPythonVersion('1.2.3'), '1.2.3');
|
||||
});
|
||||
|
||||
it('converts semantic-release rc versions to PEP 440 rc versions', () => {
|
||||
assert.equal(publicNpmPackageVersionToPythonVersion('0.1.0-rc.1'), '0.1.0rc1');
|
||||
assert.equal(publicNpmPackageVersionToPythonVersion('2.0.0-rc.12'), '2.0.0rc12');
|
||||
});
|
||||
|
||||
it('rejects unsupported prerelease and build metadata forms', () => {
|
||||
assert.throws(
|
||||
() => publicNpmPackageVersionToPythonVersion('1.2.3-beta.1'),
|
||||
/Unsupported public npm prerelease for Python runtime version/,
|
||||
);
|
||||
assert.throws(
|
||||
() => publicNpmPackageVersionToPythonVersion('1.2.3+build.1'),
|
||||
/Unsupported public npm build metadata for Python runtime version/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -37,7 +37,7 @@ async function writeUploadableArtifactFixtures(layout) {
|
|||
layout.npmTarballs[packageInfo.name],
|
||||
`${packageInfo.name}-tarball`,
|
||||
]),
|
||||
[join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'), 'kaelio-ktx-runtime-wheel'],
|
||||
[join(layout.pythonDir, 'kaelio_ktx-0.1.0rc1-py3-none-any.whl'), 'kaelio-ktx-runtime-wheel'],
|
||||
]);
|
||||
|
||||
for (const [path, contents] of fileContents) {
|
||||
|
|
|
|||
4
uv.lock
generated
4
uv.lock
generated
|
|
@ -440,7 +440,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ktx-daemon"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0+private"
|
||||
source = { editable = "python/ktx-daemon" }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
|
|
@ -495,7 +495,7 @@ dev = [
|
|||
|
||||
[[package]]
|
||||
name = "ktx-sl"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0+private"
|
||||
source = { editable = "python/ktx-sl" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue