mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
refactor(release): drop release-policy.json runtime dep and next branch (#180)
* chore: standardize daemon naming on "KTX daemon"
Replace inconsistent names ("KTX Python daemon", "KTX local embeddings
daemon", "KTX managed daemon", "Python daemon") with the single name
"KTX daemon" in CLI output, errors, command descriptions, test
assertions, smoke scripts, docs, AGENTS.md, issue templates, and
codecov flags. The daemon is a portable compute server with endpoints
for SQL analysis, semantic layer, LookML, database introspection, and
embeddings; the previous labels misrepresented it as embeddings-only or
exposed implementation details ("Python", "managed").
The "KTX Python runtime" concept (installed interpreter + packages) is
deliberately left as-is — it is a separate concept from the daemon
process.
* refactor(release): drop release-policy.json runtime dep and next branch
Strips the release-policy.json fallback from release-version.ts so the CLI
reads its version straight from packages/cli/package.json. dev → 0.0.0-private,
installed @kaelio/ktx → the real semver baked into the published package.json.
KtxCliPackageInfo collapses to { name, version, contextPackageName }; /health
no longer depends on version files surviving past a CI run.
Replaces the dual-branch (main + next) semantic-release model with a single-
branch model on main. rcs and stables interleave on the same branch via
{ name: 'main', prerelease: 'rc', channel: 'next' } / ['main']. Drops
@semantic-release/git and @semantic-release/changelog (nothing is committed
back to the repo on any channel) and the workflow's "Prepare next prerelease
branch" step plus the KTX_PRERELEASE_BRANCH plumbing. The git tag plus the
published npm artifact carry the version forward.
Updates docs/release.md, removes the two now-unused devDeps, regenerates
pnpm-lock.yaml. 611/611 @ktx/cli tests, 173/173 script tests, type-check,
biome, knip all clean.
* fix(release): don't throw on non-main branches at config-load time
knip loads .releaserc.cjs on every PR run, where GITHUB_REF_NAME is the
merge ref (e.g. 180/merge). The previous version of releaseBranches threw
immediately when the branch wasn't main, which made knip fail to evaluate
the config and then mis-flag @semantic-release/exec as an unused dep.
semantic-release already refuses to publish when the current branch doesn't
match a configured release branch, so the explicit throw was redundant.
Drop it (and the unused currentBranch helper) and replace the
"rejects releases from non-main" assertion with one that exercises a CI-
shaped GITHUB_REF_NAME and confirms the config loads.
This commit is contained in:
parent
da6d05ed55
commit
a11b9e9757
43 changed files with 142 additions and 387 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -44,7 +44,7 @@ body:
|
|||
- Connector (SQL Server)
|
||||
- Connector (SQLite)
|
||||
- Python semantic layer
|
||||
- Python daemon
|
||||
- KTX daemon
|
||||
- Docs
|
||||
- Other
|
||||
validations:
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -24,7 +24,7 @@ body:
|
|||
- Connectors
|
||||
- Context engine
|
||||
- Python semantic layer
|
||||
- Python daemon
|
||||
- KTX daemon
|
||||
- Docs
|
||||
- Other
|
||||
validations:
|
||||
|
|
|
|||
45
.github/workflows/release.yml
vendored
45
.github/workflows/release.yml
vendored
|
|
@ -69,27 +69,6 @@ jobs:
|
|||
- name: Install Python dependencies
|
||||
run: uv sync --all-packages
|
||||
|
||||
- name: Prepare next prerelease branch
|
||||
if: ${{ inputs.release_kind == 'rc' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
source_sha="$(git rev-parse HEAD)"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
if git ls-remote --exit-code --heads origin "${KTX_PRERELEASE_BRANCH}" >/dev/null 2>&1; then
|
||||
git fetch origin "${KTX_PRERELEASE_BRANCH}"
|
||||
git checkout -B "${KTX_PRERELEASE_BRANCH}" "origin/${KTX_PRERELEASE_BRANCH}"
|
||||
git merge --no-edit "${source_sha}"
|
||||
else
|
||||
git checkout -B "${KTX_PRERELEASE_BRANCH}" "${source_sha}"
|
||||
fi
|
||||
|
||||
git push --set-upstream origin "HEAD:${KTX_PRERELEASE_BRANCH}"
|
||||
env:
|
||||
KTX_PRERELEASE_BRANCH: next
|
||||
|
||||
- name: Prepare npm package root for release verification
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
|
@ -112,36 +91,16 @@ jobs:
|
|||
|
||||
- name: Dry-run semantic release
|
||||
if: ${{ !inputs.publish_live }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${KTX_RELEASE_KIND}" = "rc" ]; then
|
||||
export GITHUB_REF="refs/heads/${KTX_PRERELEASE_BRANCH}"
|
||||
export GITHUB_REF_NAME="${KTX_PRERELEASE_BRANCH}"
|
||||
export GITHUB_SHA="$(git rev-parse HEAD)"
|
||||
fi
|
||||
|
||||
pnpm run semantic-release:dry-run
|
||||
run: pnpm run semantic-release:dry-run
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KTX_RELEASE_KIND: ${{ inputs.release_kind }}
|
||||
KTX_PRERELEASE_BRANCH: next
|
||||
FORCE_RELEASE: ${{ inputs.force_release }}
|
||||
|
||||
- name: Create semantic release
|
||||
if: ${{ inputs.publish_live }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${KTX_RELEASE_KIND}" = "rc" ]; then
|
||||
export GITHUB_REF="refs/heads/${KTX_PRERELEASE_BRANCH}"
|
||||
export GITHUB_REF_NAME="${KTX_PRERELEASE_BRANCH}"
|
||||
export GITHUB_SHA="$(git rev-parse HEAD)"
|
||||
fi
|
||||
|
||||
pnpm run semantic-release
|
||||
run: pnpm run semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
KTX_RELEASE_KIND: ${{ inputs.release_kind }}
|
||||
KTX_PRERELEASE_BRANCH: next
|
||||
FORCE_RELEASE: ${{ inputs.force_release }}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ KTX is a pnpm + uv workspace.
|
|||
- LLM package: `packages/llm`
|
||||
- Database connectors: `packages/connector-*`
|
||||
- Python semantic layer: `python/ktx-sl`
|
||||
- Python daemon: `python/ktx-daemon`
|
||||
- KTX daemon: `python/ktx-daemon`
|
||||
- Examples and fixtures: `examples/`
|
||||
- Workspace scripts: `scripts/`
|
||||
- Local agent skills and internal planning docs are private overlays. Do not
|
||||
|
|
@ -134,7 +134,7 @@ shared contracts or package exports are affected.
|
|||
test file
|
||||
- TypeScript dead-code tooling/config changes: `pnpm run dead-code`
|
||||
- Python semantic layer: `uv run pytest python/ktx-sl/tests -q`
|
||||
- Python daemon: `uv run pytest python/ktx-daemon/tests -q`
|
||||
- KTX daemon: `uv run pytest python/ktx-daemon/tests -q`
|
||||
- Python files: also run `uv run pre-commit run --files [FILES]` when
|
||||
pre-commit is configured
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ component_management:
|
|||
paths:
|
||||
- python/ktx-sl/semantic_layer/**
|
||||
- component_id: py_daemon
|
||||
name: Python daemon
|
||||
name: KTX daemon
|
||||
paths:
|
||||
- python/ktx-daemon/src/ktx_daemon/**
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ Success requires (canonical shape - supersedes the example in the docs):
|
|||
|
||||
Do **not** run `--deep` ingest in this flow - that requires LLM time and is out of scope.
|
||||
|
||||
### Optional: directly probe the embeddings daemon
|
||||
### Optional: directly probe the KTX daemon
|
||||
|
||||
If the user asks for stronger verification that `sentence-transformers` is actually serving (not just that setup said "ok"), do all of:
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ If the user asks for stronger verification that `sentence-transformers` is actua
|
|||
3. `curl -sS http://127.0.0.1:<port>/health` → expect HTTP 200 with `{"status":"healthy",…}`.
|
||||
4. `curl -sS -X POST http://127.0.0.1:<port>/embeddings/compute -H 'content-type: application/json' -d '{"text":"hello"}'` → expect `{"embedding": [...384 floats...]}`.
|
||||
|
||||
Discover the port from setup's log line `Started KTX local embeddings daemon: http://127.0.0.1:<port>` or from the daemon's OpenAPI at `GET /openapi.json`. Note: the routes are `/health` and `/embeddings/compute` - not `/healthz` or `/embeddings`.
|
||||
Discover the port from setup's log line `Started KTX daemon: http://127.0.0.1:<port>` or from the daemon's OpenAPI at `GET /openapi.json`. Note: the routes are `/health` and `/embeddings/compute` - not `/healthz` or `/embeddings`.
|
||||
|
||||
## Phase 6 - Final report
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ KTX SETUP COMPLETE
|
|||
Project: <path>
|
||||
LLM: <backend> / <model>
|
||||
Embeddings: <backend> / <model>
|
||||
Runtime: managed Python ✓ (if sentence-transformers daemon was started)
|
||||
Runtime: managed Python ✓ (if the KTX daemon was started)
|
||||
|
||||
Connections:
|
||||
- <name> (<driver>) status=ok schemas=[…] tables=<N>
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ directory. Use it from any directory to generate editor or agent schema files.
|
|||
| Subcommand | Description |
|
||||
|-----------|-------------|
|
||||
| `install` | Install the bundled Python runtime wheel into the managed runtime |
|
||||
| `start` | Start the KTX-managed Python HTTP daemon |
|
||||
| `stop` | Stop the KTX-managed Python HTTP daemon |
|
||||
| `start` | Start the KTX daemon |
|
||||
| `stop` | Stop the KTX daemon |
|
||||
| `status` | Show managed Python runtime status and readiness checks |
|
||||
|
||||
## `admin runtime` Options
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ documentation, connector coverage, and examples.
|
|||
| Context engine | `packages/context`, including project config, ingest orchestration, and semantic search |
|
||||
| Connectors | `packages/connector-*`, plus connector-specific tests and integration docs |
|
||||
| Python semantic layer | `python/ktx-sl` for planning and SQL generation |
|
||||
| Python daemon | `python/ktx-daemon` for the portable runtime API |
|
||||
| KTX daemon | `python/ktx-daemon` for the portable runtime API |
|
||||
| Documentation | `docs-site/content/docs` for public docs and `docs-site/tests` for docs behavior |
|
||||
|
||||
## Development setup
|
||||
|
|
|
|||
|
|
@ -2,25 +2,24 @@
|
|||
|
||||
This runbook covers the maintainer workflow for publishing `@kaelio/ktx` to
|
||||
npm through GitHub Actions. The workflow uses semantic-release to choose the
|
||||
next version, update release metadata, publish the package, create the GitHub
|
||||
release, and commit prerelease files back to the `next` branch.
|
||||
next version, update release metadata in the CI workspace, publish the
|
||||
package, and create the GitHub release. No files are ever committed back to
|
||||
the repository — the git tag and the published npm artifact are the source of
|
||||
truth for any released version.
|
||||
|
||||
## Release channels
|
||||
|
||||
KTX has two npm release channels:
|
||||
`main` is the bleeding-edge branch. Every release runs from `main`; the
|
||||
dispatcher chooses the channel:
|
||||
|
||||
- `rc` publishes prereleases such as `0.1.0-rc.2` to the npm `next` tag.
|
||||
- `stable` publishes normal releases such as `0.1.0` to the npm `latest` tag.
|
||||
- `rc` publishes prereleases such as `0.3.0-rc.1` to the npm `next` tag.
|
||||
- `stable` publishes normal releases such as `0.3.0` to the npm `latest` tag.
|
||||
|
||||
Run rc releases from the source branch you want to publish. The workflow
|
||||
creates or updates the `next` prerelease branch from that source branch before
|
||||
running semantic-release, because semantic-release requires a dedicated
|
||||
prerelease branch in addition to the stable `main` branch. You can publish an
|
||||
rc from `main` when you want to validate the current stable branch before a
|
||||
stable release.
|
||||
Tag history on `main` interleaves rc and stable tags
|
||||
(`v0.2.0 → v0.3.0-rc.1 → v0.3.0-rc.2 → v0.3.0 → …`). semantic-release uses the
|
||||
prerelease tags to graduate to the next stable cleanly.
|
||||
|
||||
Run stable releases only from `main`. The workflow rejects stable releases from
|
||||
other branches.
|
||||
The workflow rejects releases from any branch other than `main`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
|
@ -59,16 +58,15 @@ publishing to npm.
|
|||
|
||||
1. Open **Actions** in GitHub.
|
||||
2. Select **KTX Release**.
|
||||
3. Select the branch to release from.
|
||||
3. Select `main`.
|
||||
4. Set **release_kind** to `rc` or `stable`.
|
||||
5. Set **publish_live** to `false`.
|
||||
6. Optional: Set **force_release** to `true` when you need a patch release even
|
||||
if semantic-release doesn't find a releasable commit.
|
||||
7. Run the workflow.
|
||||
|
||||
The dry-run uses the same semantic-release configuration as a live release. For
|
||||
rc releases, it can create or update the `next` branch. It doesn't publish to
|
||||
npm and doesn't commit release files.
|
||||
The dry-run uses the same semantic-release configuration as a live release. It
|
||||
doesn't publish to npm and doesn't push any tags.
|
||||
|
||||
## Publish an rc release
|
||||
|
||||
|
|
@ -77,16 +75,15 @@ promoting to `latest`.
|
|||
|
||||
1. Open **Actions** in GitHub.
|
||||
2. Select **KTX Release**.
|
||||
3. Select the source branch to release from, including `main` when needed.
|
||||
3. Select `main`.
|
||||
4. Set **release_kind** to `rc`.
|
||||
5. Leave **publish_live** set to `true`.
|
||||
6. Optional: Set **force_release** to `true`.
|
||||
7. Run the workflow.
|
||||
|
||||
The workflow merges the selected source branch into `next`, publishes
|
||||
`@kaelio/ktx` with `--access public --tag next`, runs the published package
|
||||
smoke test, creates a GitHub release, and commits `CHANGELOG.md`,
|
||||
`package.json`, and `release-policy.json` on `next`.
|
||||
The workflow publishes `@kaelio/ktx` with `--access public --tag next`, runs
|
||||
the published package smoke test, creates a GitHub release, and pushes the
|
||||
`vX.Y.Z-rc.N` tag.
|
||||
|
||||
## Publish a stable release
|
||||
|
||||
|
|
@ -101,26 +98,28 @@ Publish a stable release from `main` after you have validated an rc package.
|
|||
7. Run the workflow.
|
||||
|
||||
The workflow publishes `@kaelio/ktx` with `--access public --tag latest`, runs
|
||||
the published package smoke test, and creates a GitHub release. Stable releases
|
||||
don't commit release metadata back to `main`, because `main` is protected and
|
||||
requires changes through pull requests.
|
||||
the published package smoke test, creates a GitHub release, and pushes the
|
||||
`vX.Y.Z` tag. semantic-release graduates from the most recent rc tag, so the
|
||||
prior rc lineage is consumed cleanly.
|
||||
|
||||
## Release metadata
|
||||
|
||||
semantic-release calls `scripts/update-public-release-version.mjs` during the
|
||||
prepare step before `@semantic-release/npm` publishes the package. That script
|
||||
updates:
|
||||
prepare step before the exec publish command runs. That script updates the
|
||||
following files **inside the CI runner only**:
|
||||
|
||||
- `package.json` with the semantic-release version.
|
||||
- `release-policy.json` with `publicNpmPackageVersion`, npm publish settings,
|
||||
and the published package smoke-test version.
|
||||
|
||||
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 semantic-release npm plugin publishes the generated
|
||||
`dist/public-npm-package` tree and writes the release tarball under
|
||||
`dist/artifacts/npm`. Stable releases use the updated metadata during the
|
||||
workflow run, but that generated metadata isn't committed back to `main`.
|
||||
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 bundled Python runtime wheel also derives its version from
|
||||
`publicNpmPackageVersion`. Stable npm versions are reused as-is, and rc
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ warehouse credential.
|
|||
|
||||
`postgres-historic/` is a manual Docker-backed smoke for Postgres
|
||||
query-history ingest via `pg_stat_statements`. It verifies setup, staged
|
||||
query-history artifacts, managed daemon batch SQL analysis, bounded pattern
|
||||
query-history artifacts, KTX daemon batch SQL analysis, bounded pattern
|
||||
WorkUnit shards, and no-WorkUnit idempotency for unchanged bucketed table
|
||||
inputs and pattern shards.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ generated local project.
|
|||
The managed Python runtime smoke requires `uv` on `PATH`, isolates
|
||||
`KTX_RUNTIME_ROOT`, verifies `ktx admin runtime status`, runs `ktx sl query --yes` to
|
||||
install the core runtime from the bundled wheel, checks `ktx admin runtime status`,
|
||||
starts and reuses the managed daemon, and stops it.
|
||||
starts and reuses the KTX daemon, and stops it.
|
||||
|
||||
The artifact manifest contains the public `@kaelio/ktx` npm tarball and the
|
||||
bundled `kaelio-ktx` runtime wheel. The smoke does not install standalone
|
||||
|
|
|
|||
|
|
@ -48,10 +48,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/exec": "^7.1.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^12.0.8",
|
||||
"@semantic-release/npm": "^13.1.5",
|
||||
"@semantic-release/release-notes-generator": "^14.1.1",
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ describe('admin reindex Commander routing', () => {
|
|||
force: true,
|
||||
json: true,
|
||||
output: 'plain',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
},
|
||||
io.io,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ function stubPackageInfo(): KtxCliPackageInfo {
|
|||
return {
|
||||
name: '@ktx/cli',
|
||||
version: '0.0.0-test',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.0.0-test',
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,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';
|
||||
import { assertCliVersion } from './release-version.js';
|
||||
|
||||
profileMark('module:cli-runtime');
|
||||
|
||||
|
|
@ -20,8 +20,6 @@ const requirePackageJson = createRequire(import.meta.url);
|
|||
export interface KtxCliPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
packageVersion: string;
|
||||
runtimeVersion: string;
|
||||
contextPackageName: '@ktx/context';
|
||||
}
|
||||
|
||||
|
|
@ -66,16 +64,9 @@ 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: runtimeVersion,
|
||||
packageVersion: packageJson.version,
|
||||
runtimeVersion,
|
||||
version: assertCliVersion(packageJson.version, `${packageJson.name}/package.json`),
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function registerRuntimeCommands(program: Command, context: KtxCliCommand
|
|||
|
||||
runtime
|
||||
.command('start')
|
||||
.description('Start the KTX-managed Python HTTP daemon')
|
||||
.description('Start the KTX daemon')
|
||||
.addOption(createRuntimeFeatureOption())
|
||||
.option('--force', 'Restart even when a matching daemon is already running', false)
|
||||
.action(async (options: { feature: RuntimeFeature; force?: boolean }, command: CommandWithGlobalOptions) => {
|
||||
|
|
@ -53,7 +53,7 @@ export function registerRuntimeCommands(program: Command, context: KtxCliCommand
|
|||
|
||||
runtime
|
||||
.command('stop')
|
||||
.description('Stop the KTX-managed Python HTTP daemon')
|
||||
.description('Stop the KTX daemon')
|
||||
.option('--all', 'Stop all KTX daemon processes recorded or discoverable on this machine', false)
|
||||
.action(async (options: { all?: boolean }, command: CommandWithGlobalOptions) => {
|
||||
await runRuntimeArgs(context, {
|
||||
|
|
|
|||
|
|
@ -706,7 +706,7 @@ function failedStepDetail(result: KtxPublicIngestTargetResult): string | null {
|
|||
const INTERNAL_FAILURE_LINE_RE =
|
||||
/^(Report|Run|Job|Status|Adapter|Connection|Sync|Mode|Dry run|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
|
||||
const ACTIONABLE_FAILURE_LINE_RE =
|
||||
/^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX managed daemon|Error:|Failed\b|Could not\b|Cannot\b)/;
|
||||
/^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX daemon HTTP|Error:|Failed\b|Could not\b|Cannot\b)/;
|
||||
|
||||
function trimErrorPrefix(line: string): string {
|
||||
return line.replace(/^Error:\s*/, '');
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ describe('getKtxCliPackageInfo', () => {
|
|||
it('identifies the CLI package and its context dependency', () => {
|
||||
expect(getKtxCliPackageInfo()).toEqual({
|
||||
name: '@ktx/cli',
|
||||
version: '0.1.0-rc.1',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.1.0-rc.1',
|
||||
version: '0.0.0-private',
|
||||
contextPackageName: '@ktx/context',
|
||||
});
|
||||
});
|
||||
|
|
@ -70,8 +68,6 @@ describe('getKtxCliPackageInfo', () => {
|
|||
).toEqual({
|
||||
name: '@kaelio/ktx',
|
||||
version: '0.1.0',
|
||||
packageVersion: '0.1.0',
|
||||
runtimeVersion: '0.1.0',
|
||||
contextPackageName: '@ktx/context',
|
||||
});
|
||||
});
|
||||
|
|
@ -118,7 +114,7 @@ describe('runKtxCli', () => {
|
|||
|
||||
await expect(runKtxCli(['--version'], testIo.io)).resolves.toBe(0);
|
||||
|
||||
expect(testIo.stdout()).toBe('@ktx/cli 0.1.0-rc.1\n');
|
||||
expect(testIo.stdout()).toBe('@ktx/cli 0.0.0-private\n');
|
||||
expect(testIo.stderr()).toBe('');
|
||||
});
|
||||
|
||||
|
|
@ -282,7 +278,7 @@ describe('runKtxCli', () => {
|
|||
expect(unknownIo.stderr()).toContain("unknown option '--query'");
|
||||
});
|
||||
|
||||
it('routes runtime management commands with the release runtime version', async () => {
|
||||
it('routes runtime management commands with the CLI package version', async () => {
|
||||
const runtime = vi.fn(async () => 0);
|
||||
const installIo = makeIo();
|
||||
const startIo = makeIo();
|
||||
|
|
@ -308,7 +304,7 @@ describe('runKtxCli', () => {
|
|||
1,
|
||||
{
|
||||
command: 'install',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
feature: 'local-embeddings',
|
||||
force: true,
|
||||
},
|
||||
|
|
@ -318,7 +314,7 @@ describe('runKtxCli', () => {
|
|||
2,
|
||||
{
|
||||
command: 'start',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
projectDir: expect.any(String),
|
||||
feature: 'local-embeddings',
|
||||
force: true,
|
||||
|
|
@ -329,7 +325,7 @@ describe('runKtxCli', () => {
|
|||
3,
|
||||
{
|
||||
command: 'stop',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
projectDir: expect.any(String),
|
||||
all: false,
|
||||
},
|
||||
|
|
@ -339,7 +335,7 @@ describe('runKtxCli', () => {
|
|||
4,
|
||||
{
|
||||
command: 'stop',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
projectDir: expect.any(String),
|
||||
all: true,
|
||||
},
|
||||
|
|
@ -349,7 +345,7 @@ describe('runKtxCli', () => {
|
|||
5,
|
||||
{
|
||||
command: 'status',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
json: true,
|
||||
},
|
||||
statusIo.io,
|
||||
|
|
@ -422,7 +418,7 @@ describe('runKtxCli', () => {
|
|||
expect.objectContaining({
|
||||
command: 'query',
|
||||
projectDir: tempDir,
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
query: expect.objectContaining({ measures: ['orders.order_count'], dimensions: [] }),
|
||||
}),
|
||||
|
|
@ -437,7 +433,7 @@ describe('runKtxCli', () => {
|
|||
).resolves.toBe(0);
|
||||
expect(sl).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
}),
|
||||
autoIo.io,
|
||||
|
|
@ -453,7 +449,7 @@ describe('runKtxCli', () => {
|
|||
).resolves.toBe(0);
|
||||
expect(sl).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'never',
|
||||
}),
|
||||
noInputIo.io,
|
||||
|
|
@ -589,7 +585,7 @@ describe('runKtxCli', () => {
|
|||
skipAgents: false,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: false,
|
||||
databaseSchemas: [],
|
||||
|
|
@ -719,7 +715,7 @@ describe('runKtxCli', () => {
|
|||
inputMode: 'disabled',
|
||||
depth: 'fast',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -746,7 +742,7 @@ describe('runKtxCli', () => {
|
|||
inputMode: 'auto',
|
||||
depth: 'deep',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -823,7 +819,7 @@ describe('runKtxCli', () => {
|
|||
json: false,
|
||||
inputMode: 'disabled',
|
||||
queryHistory: 'default',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
testIo.io,
|
||||
|
|
@ -1128,7 +1124,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
|
|
@ -1167,7 +1163,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
|
|
@ -1204,7 +1200,7 @@ describe('runKtxCli', () => {
|
|||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
llmBackend: 'claude-code',
|
||||
llmModel: 'opus',
|
||||
skipLlm: false,
|
||||
|
|
@ -1312,7 +1308,7 @@ describe('runKtxCli', () => {
|
|||
projectDir: '/tmp/project',
|
||||
inputMode: 'disabled',
|
||||
yes: true,
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
skipLlm: true,
|
||||
skipEmbeddings: true,
|
||||
databaseDrivers: ['postgres'],
|
||||
|
|
@ -1653,7 +1649,7 @@ describe('runKtxCli', () => {
|
|||
queryFile: '/tmp/query.json',
|
||||
execute: false,
|
||||
format: 'json',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
},
|
||||
autoIo.io,
|
||||
|
|
@ -1667,7 +1663,7 @@ describe('runKtxCli', () => {
|
|||
queryFile: '/tmp/query.json',
|
||||
execute: false,
|
||||
format: 'json',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'never',
|
||||
},
|
||||
neverIo.io,
|
||||
|
|
|
|||
|
|
@ -1334,7 +1334,7 @@ describe('runKtxIngest', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('passes managed daemon options to adapters and pull-config options when no explicit daemon URL is set', async () => {
|
||||
it('passes KTX daemon options to adapters and pull-config options when no explicit daemon URL is set', async () => {
|
||||
const projectDir = join(tempDir, 'managed-daemon-ingest-project');
|
||||
await initKtxProject({ projectDir });
|
||||
await writeWarehouseConfig(projectDir);
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ describe('managedLocalEmbeddingProjectConfig', () => {
|
|||
});
|
||||
|
||||
describe('managedLocalEmbeddingHealthConfig', () => {
|
||||
it('uses the active managed daemon URL for the immediate health check', () => {
|
||||
it('uses the active KTX daemon URL for the immediate health check', () => {
|
||||
expect(
|
||||
managedLocalEmbeddingHealthConfig({
|
||||
baseUrl: 'http://127.0.0.1:61234',
|
||||
|
|
@ -134,7 +134,7 @@ describe('managedLocalEmbeddingHealthConfig', () => {
|
|||
});
|
||||
|
||||
describe('ensureManagedLocalEmbeddingsDaemon', () => {
|
||||
it('ensures the local-embeddings feature and starts the managed daemon', async () => {
|
||||
it('ensures the local-embeddings feature and starts the KTX daemon', async () => {
|
||||
const io = makeIo();
|
||||
const ensureRuntime = vi.fn(async () => runtime());
|
||||
const startDaemon = vi.fn(async () => daemonResult('started'));
|
||||
|
|
@ -169,7 +169,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
|
|||
features: ['local-embeddings'],
|
||||
force: false,
|
||||
});
|
||||
expect(io.stderr()).toContain('Started KTX local embeddings daemon: http://127.0.0.1:61234');
|
||||
expect(io.stderr()).toContain('Started KTX daemon: http://127.0.0.1:61234');
|
||||
});
|
||||
|
||||
it('reuses an already running daemon without reporting a new start', async () => {
|
||||
|
|
@ -184,6 +184,6 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
|
|||
startDaemon: vi.fn(async () => daemonResult('reused')),
|
||||
});
|
||||
|
||||
expect(io.stderr()).toContain('Using KTX local embeddings daemon: http://127.0.0.1:61234');
|
||||
expect(io.stderr()).toContain('Using KTX daemon: http://127.0.0.1:61234');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export async function ensureManagedLocalEmbeddingsDaemon(
|
|||
});
|
||||
|
||||
const verb = daemon.status === 'started' ? 'Started' : 'Using';
|
||||
options.io.stderr.write(`${verb} KTX local embeddings daemon: ${daemon.baseUrl}\n`);
|
||||
options.io.stderr.write(`${verb} KTX daemon: ${daemon.baseUrl}\n`);
|
||||
|
||||
return {
|
||||
baseUrl: daemon.baseUrl,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ function daemonOptionsBase(root: string) {
|
|||
} as const;
|
||||
}
|
||||
|
||||
describe('managed Python daemon lifecycle', () => {
|
||||
describe('KTX daemon lifecycle', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ async function waitForHealth(input: {
|
|||
return;
|
||||
}
|
||||
lastDetail = finalHealth.detail;
|
||||
throw new Error(`KTX Python daemon failed to start: ${lastDetail}. stderr: ${input.state.stderrLog}`);
|
||||
throw new Error(`KTX daemon failed to start: ${lastDetail}. stderr: ${input.state.stderrLog}`);
|
||||
}
|
||||
|
||||
async function removeState(layout: ManagedPythonDaemonLayout): Promise<void> {
|
||||
|
|
@ -705,7 +705,7 @@ export async function startManagedPythonDaemon(
|
|||
);
|
||||
child.unref();
|
||||
if (!child.pid) {
|
||||
throw new Error(`KTX Python daemon did not report a pid. stderr: ${layout.daemonStderrPath}`);
|
||||
throw new Error(`KTX daemon did not report a pid. stderr: ${layout.daemonStderrPath}`);
|
||||
}
|
||||
const state: ManagedPythonDaemonState = {
|
||||
schemaVersion: 1,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ describe('createManagedPythonDaemonBaseUrlResolver', () => {
|
|||
features: ['core'],
|
||||
force: false,
|
||||
});
|
||||
expect(testIo.stderr()).toContain('Started KTX Python daemon: http://127.0.0.1:61234');
|
||||
expect(testIo.stderr()).toContain('Started KTX daemon: http://127.0.0.1:61234');
|
||||
});
|
||||
|
||||
it('reports daemon reuse without reinstalling after the first resolved URL', async () => {
|
||||
|
|
@ -86,7 +86,7 @@ describe('createManagedPythonDaemonBaseUrlResolver', () => {
|
|||
|
||||
expect(ensureRuntime).toHaveBeenCalledTimes(1);
|
||||
expect(startDaemon).toHaveBeenCalledTimes(1);
|
||||
expect(testIo.stderr()).toContain('Using existing KTX Python daemon: http://127.0.0.1:61234');
|
||||
expect(testIo.stderr()).toContain('Using existing KTX daemon: http://127.0.0.1:61234');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -104,8 +104,8 @@ describe('createManagedDaemonHttpJsonRunner', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('managed daemon ingest ports', () => {
|
||||
it('creates a Looker table parser backed by the managed daemon runner', async () => {
|
||||
describe('KTX daemon ingest ports', () => {
|
||||
it('creates a Looker table parser backed by the KTX daemon runner', async () => {
|
||||
const requestJson = vi.fn(async () => ({
|
||||
results: {
|
||||
'model.explore': {
|
||||
|
|
@ -135,7 +135,7 @@ describe('managed daemon ingest ports', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('creates a SQL analysis port backed by the managed daemon runner', async () => {
|
||||
it('creates a SQL analysis port backed by the KTX daemon runner', async () => {
|
||||
const requestJson = vi.fn(async () => ({
|
||||
fingerprint: 'select-orders',
|
||||
normalized_sql: 'SELECT * FROM public.orders WHERE id = ?',
|
||||
|
|
@ -157,7 +157,7 @@ describe('managed daemon ingest ports', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('routes SQL batch analysis through the managed daemon runner', async () => {
|
||||
it('routes SQL batch analysis through the KTX daemon runner', async () => {
|
||||
const requestJson = vi.fn(async () => ({
|
||||
results: {
|
||||
orders: {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function normalizedBaseUrl(baseUrl: string): string {
|
|||
function parseJsonObject(raw: string, path: string): Record<string, unknown> {
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(`KTX managed daemon HTTP ${path} returned non-object JSON`);
|
||||
throw new Error(`KTX daemon HTTP ${path} returned non-object JSON`);
|
||||
}
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ export async function postManagedDaemonJson(
|
|||
const text = Buffer.concat(chunks).toString('utf8');
|
||||
const statusCode = response.statusCode ?? 0;
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
reject(new Error(`KTX managed daemon HTTP ${path} failed with ${statusCode}: ${text}`));
|
||||
reject(new Error(`KTX daemon HTTP ${path} failed with ${statusCode}: ${text}`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
@ -142,7 +142,7 @@ export function createManagedPythonDaemonBaseUrlResolver(
|
|||
force: false,
|
||||
});
|
||||
const verb = daemon.status === 'started' ? 'Started' : 'Using existing';
|
||||
options.io.stderr.write(`${verb} KTX Python daemon: ${daemon.baseUrl}\n`);
|
||||
options.io.stderr.write(`${verb} KTX daemon: ${daemon.baseUrl}\n`);
|
||||
cachedBaseUrl = daemon.baseUrl;
|
||||
return cachedBaseUrl;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ function stubPackageInfo(): KtxCliPackageInfo {
|
|||
return {
|
||||
name: '@ktx/cli',
|
||||
version: '0.0.0-docs',
|
||||
packageVersion: '0.0.0-private',
|
||||
runtimeVersion: '0.0.0-docs',
|
||||
contextPackageName: '@ktx/context',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ function createCapturedPublicIngestIo(): CapturedPublicIngestIo {
|
|||
const INTERNAL_STATUS_LINE_RE =
|
||||
/^(Report|Run|Job|Status|Adapter|Connection|Sync|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
|
||||
const ACTIONABLE_FAILURE_LINE_RE =
|
||||
/^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX managed daemon|Error:|Failed\b|Could not\b|Cannot\b)/;
|
||||
/^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX daemon HTTP|Error:|Failed\b|Could not\b|Cannot\b)/;
|
||||
const RUNTIME_BACKED_RETRY_LINE_RE = /^Then retry the runtime-backed KTX command\.?$/;
|
||||
|
||||
function trimErrorPrefix(line: string): string {
|
||||
|
|
|
|||
|
|
@ -1,55 +1,9 @@
|
|||
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 {
|
||||
export function assertCliVersion(value: unknown, source: string): string {
|
||||
if (typeof value !== 'string' || !semverPattern.test(value)) {
|
||||
throw new Error(`Invalid KTX release version in ${source}`);
|
||||
throw new Error(`Invalid KTX CLI 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export function resolveProjectRuntimeRequirements(
|
|||
requirements.push({
|
||||
feature: 'core',
|
||||
reason: 'database-introspection',
|
||||
detail: 'Database introspection fallback uses the Python daemon.',
|
||||
detail: 'Database introspection fallback uses the KTX daemon.',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ describe('runKtxRuntime', () => {
|
|||
expect(io.stderr()).toBe('');
|
||||
});
|
||||
|
||||
it('starts the managed Python daemon and prints the base URL', async () => {
|
||||
it('starts the KTX daemon and prints the base URL', async () => {
|
||||
const io = makeIo();
|
||||
const deps: KtxRuntimeDeps = {
|
||||
startDaemon: vi.fn(async (): Promise<ManagedPythonDaemonStartResult> => ({
|
||||
|
|
@ -160,14 +160,14 @@ describe('runKtxRuntime', () => {
|
|||
features: ['local-embeddings'],
|
||||
force: true,
|
||||
});
|
||||
expect(io.stdout()).toContain('Started KTX Python daemon');
|
||||
expect(io.stdout()).toContain('Started KTX daemon');
|
||||
expect(io.stdout()).toContain('url: http://127.0.0.1:61234');
|
||||
expect(io.stdout()).toContain('pid: 4242');
|
||||
expect(io.stdout()).toContain('features: core, local-embeddings');
|
||||
expect(io.stdout()).toContain('stderr: /work/proj/.ktx/runtime/daemon.stderr.log');
|
||||
});
|
||||
|
||||
it('stops the managed Python daemon', async () => {
|
||||
it('stops the KTX daemon', async () => {
|
||||
const io = makeIo();
|
||||
const deps: KtxRuntimeDeps = {
|
||||
stopDaemon: vi.fn(async (): Promise<ManagedPythonDaemonStopResult> => ({
|
||||
|
|
@ -208,11 +208,11 @@ describe('runKtxRuntime', () => {
|
|||
).resolves.toBe(0);
|
||||
|
||||
expect(deps.stopDaemon).toHaveBeenCalledWith({ cliVersion: '0.2.0', projectDir: '/work/proj' });
|
||||
expect(io.stdout()).toContain('Stopped KTX Python daemon');
|
||||
expect(io.stdout()).toContain('Stopped KTX daemon');
|
||||
expect(io.stdout()).toContain('pid: 4242');
|
||||
});
|
||||
|
||||
it('stops all discovered Python daemons and reports the summary', async () => {
|
||||
it('stops all discovered KTX daemons and reports the summary', async () => {
|
||||
const io = makeIo();
|
||||
const deps: KtxRuntimeDeps = {
|
||||
stopAllDaemons: vi.fn(async (): Promise<ManagedPythonDaemonStopAllResult> => ({
|
||||
|
|
@ -231,7 +231,7 @@ describe('runKtxRuntime', () => {
|
|||
).resolves.toBe(0);
|
||||
|
||||
expect(deps.stopAllDaemons).toHaveBeenCalledWith({ cliVersion: '0.2.0', projectDir: '/work/proj' });
|
||||
expect(io.stdout()).toContain('Stopped 2 KTX Python daemons');
|
||||
expect(io.stdout()).toContain('Stopped 2 KTX daemons');
|
||||
expect(io.stdout()).toContain('pid: 4242 source: state url: http://127.0.0.1:61234');
|
||||
expect(io.stdout()).toContain('pid: 5252 source: process url: http://127.0.0.1:8765');
|
||||
});
|
||||
|
|
@ -259,7 +259,7 @@ describe('runKtxRuntime', () => {
|
|||
runKtxRuntime({ command: 'stop', cliVersion: '0.2.0', projectDir: '/work/proj', all: true }, io.io, deps),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(io.stderr()).toContain('Stopped 0 KTX Python daemons; failed 1');
|
||||
expect(io.stderr()).toContain('Stopped 0 KTX daemons; failed 1');
|
||||
expect(io.stderr()).toContain('pid: 4242 source: state url: http://127.0.0.1:61234');
|
||||
expect(io.stderr()).toContain('process scan: ps failed');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ function writeInstallResult(io: KtxCliIo, result: ManagedPythonRuntimeInstallRes
|
|||
|
||||
function writeDaemonStart(io: KtxCliIo, result: ManagedPythonDaemonStartResult): void {
|
||||
const verb = result.status === 'reused' ? 'Using existing' : 'Started';
|
||||
io.stdout.write(`${verb} KTX Python daemon\n`);
|
||||
io.stdout.write(`${verb} KTX daemon\n`);
|
||||
io.stdout.write(`url: ${result.baseUrl}\n`);
|
||||
io.stdout.write(`pid: ${result.state.pid}\n`);
|
||||
io.stdout.write(`version: ${result.state.version}\n`);
|
||||
|
|
@ -68,10 +68,10 @@ function writeDaemonStart(io: KtxCliIo, result: ManagedPythonDaemonStartResult):
|
|||
|
||||
function writeDaemonStop(io: KtxCliIo, result: ManagedPythonDaemonStopResult): void {
|
||||
if (result.status === 'already-stopped') {
|
||||
io.stdout.write('KTX Python daemon already stopped\n');
|
||||
io.stdout.write('KTX daemon already stopped\n');
|
||||
return;
|
||||
}
|
||||
io.stdout.write('Stopped KTX Python daemon\n');
|
||||
io.stdout.write('Stopped KTX daemon\n');
|
||||
io.stdout.write(`pid: ${result.state?.pid ?? 'unknown'}\n`);
|
||||
io.stdout.write(`state: ${result.layout.daemonStatePath}\n`);
|
||||
}
|
||||
|
|
@ -94,11 +94,11 @@ function writeDaemonStopAll(io: KtxCliIo, result: ManagedPythonDaemonStopAllResu
|
|||
result.failed.length === 0 &&
|
||||
result.scanErrors.length === 0
|
||||
) {
|
||||
io.stdout.write('No KTX Python daemons found\n');
|
||||
io.stdout.write('No KTX daemons found\n');
|
||||
return 0;
|
||||
}
|
||||
if (failed === 0) {
|
||||
io.stdout.write(`Stopped ${result.stopped.length} KTX Python daemons\n`);
|
||||
io.stdout.write(`Stopped ${result.stopped.length} KTX daemons\n`);
|
||||
if (result.stale.length > 0) {
|
||||
io.stdout.write(`Cleaned ${result.stale.length} stale daemon states\n`);
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ function writeDaemonStopAll(io: KtxCliIo, result: ManagedPythonDaemonStopAllResu
|
|||
return 0;
|
||||
}
|
||||
io.stderr.write(
|
||||
`Stopped ${result.stopped.length} KTX Python daemons; failed ${result.failed.length}${
|
||||
`Stopped ${result.stopped.length} KTX daemons; failed ${result.failed.length}${
|
||||
result.stale.length > 0 ? `; cleaned stale ${result.stale.length}` : ''
|
||||
}\n`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ describe('runKtxScan', () => {
|
|||
expect(io.stdout()).not.toContain('/~');
|
||||
});
|
||||
|
||||
it('passes managed daemon options to local ingest adapters when no explicit daemon URL is set', async () => {
|
||||
it('passes KTX daemon options to local ingest adapters when no explicit daemon URL is set', async () => {
|
||||
await initKtxProject({ projectDir: tempDir });
|
||||
const createLocalIngestAdapters = vi.fn(() => []);
|
||||
const runLocalScan = vi.fn(
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ describe('setup embeddings step', () => {
|
|||
);
|
||||
|
||||
expect(result.status).toBe('failed');
|
||||
expect(io.stderr()).toContain('Recent local embeddings daemon stderr:');
|
||||
expect(io.stderr()).toContain('Recent KTX daemon stderr:');
|
||||
expect(io.stderr()).toContain('daemon traceback line 6');
|
||||
expect(io.stderr()).toContain('daemon traceback line 45');
|
||||
expect(io.stderr()).not.toContain('daemon traceback line 5');
|
||||
|
|
@ -391,7 +391,7 @@ describe('setup embeddings step', () => {
|
|||
);
|
||||
|
||||
expect(result.status).toBe('failed');
|
||||
expect(io.stderr()).not.toContain('Recent local embeddings daemon stderr:');
|
||||
expect(io.stderr()).not.toContain('Recent KTX daemon stderr:');
|
||||
});
|
||||
|
||||
it('uses fixed OpenAI defaults and only asks for credentials when OpenAI is selected', async () => {
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ function localEmbeddingSetupMessage(message: string, stderrTail: string[] = []):
|
|||
'The first run may download Python packages and the all-MiniLM-L6-v2 model.',
|
||||
];
|
||||
if (stderrTail.length > 0) {
|
||||
lines.push('Recent local embeddings daemon stderr:', ...stderrTail);
|
||||
lines.push('Recent KTX daemon stderr:', ...stderrTail);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
expect(io.stderr()).toContain('ktx admin runtime install --yes');
|
||||
});
|
||||
|
||||
it('starts the managed local embeddings daemon for configured sentence-transformers embeddings', async () => {
|
||||
it('starts the KTX daemon for configured sentence-transformers embeddings', async () => {
|
||||
const io = makeIo();
|
||||
const ensureLocalEmbeddings = vi.fn(async () => ({
|
||||
baseUrl: 'http://127.0.0.1:61234',
|
||||
|
|
|
|||
93
pnpm-lock.yaml
generated
93
pnpm-lock.yaml
generated
|
|
@ -15,18 +15,12 @@ importers:
|
|||
'@biomejs/biome':
|
||||
specifier: ^2.4.15
|
||||
version: 2.4.15
|
||||
'@semantic-release/changelog':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3(semantic-release@25.0.3(typescript@6.0.3))
|
||||
'@semantic-release/commit-analyzer':
|
||||
specifier: ^13.0.1
|
||||
version: 13.0.1(semantic-release@25.0.3(typescript@6.0.3))
|
||||
'@semantic-release/exec':
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(semantic-release@25.0.3(typescript@6.0.3))
|
||||
'@semantic-release/git':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(semantic-release@25.0.3(typescript@6.0.3))
|
||||
'@semantic-release/github':
|
||||
specifier: ^12.0.8
|
||||
version: 12.0.8(semantic-release@25.0.3(typescript@6.0.3))
|
||||
|
|
@ -2326,22 +2320,12 @@ packages:
|
|||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
'@semantic-release/changelog@6.0.3':
|
||||
resolution: {integrity: sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==}
|
||||
engines: {node: '>=14.17'}
|
||||
peerDependencies:
|
||||
semantic-release: '>=18.0.0'
|
||||
|
||||
'@semantic-release/commit-analyzer@13.0.1':
|
||||
resolution: {integrity: sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==}
|
||||
engines: {node: '>=20.8.1'}
|
||||
peerDependencies:
|
||||
semantic-release: '>=20.1.0'
|
||||
|
||||
'@semantic-release/error@3.0.0':
|
||||
resolution: {integrity: sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
|
||||
'@semantic-release/error@4.0.0':
|
||||
resolution: {integrity: sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -2352,12 +2336,6 @@ packages:
|
|||
peerDependencies:
|
||||
semantic-release: '>=24.1.0'
|
||||
|
||||
'@semantic-release/git@10.0.1':
|
||||
resolution: {integrity: sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==}
|
||||
engines: {node: '>=14.17'}
|
||||
peerDependencies:
|
||||
semantic-release: '>=18.0.0'
|
||||
|
||||
'@semantic-release/github@12.0.8':
|
||||
resolution: {integrity: sha512-tej5AAgK5X9wHRoDmYhecMXEHEkFeGOY1XsEblKxu8pIQwahzf1STYyr7iPU6Lpbg6C5I3N2w/ocXrBo+L7jhw==}
|
||||
engines: {node: ^22.14.0 || >= 24.10.0}
|
||||
|
|
@ -3623,10 +3601,6 @@ packages:
|
|||
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
execa@5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
|
@ -4106,10 +4080,6 @@ packages:
|
|||
resolution: {integrity: sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
|
@ -4490,9 +4460,6 @@ packages:
|
|||
lodash.uniqby@4.7.0:
|
||||
resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==}
|
||||
|
||||
lodash@4.18.1:
|
||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||
|
||||
logform@2.7.0:
|
||||
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
|
@ -4909,10 +4876,6 @@ packages:
|
|||
resolution: {integrity: sha512-z9nC87iaZXXySbWWtTHfCFJyFvKaUAW6lODhikG7ILSbVgmwuFjUqkgnheHvAUcGedO29e2QGBRXMUD64aurqQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
npm-run-path@4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
|
@ -5085,10 +5048,6 @@ packages:
|
|||
resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-reduce@2.1.0:
|
||||
resolution: {integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-reduce@3.0.0:
|
||||
resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -5707,10 +5666,6 @@ packages:
|
|||
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
strip-final-newline@2.0.0:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -8558,14 +8513,6 @@ snapshots:
|
|||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@semantic-release/changelog@6.0.3(semantic-release@25.0.3(typescript@6.0.3))':
|
||||
dependencies:
|
||||
'@semantic-release/error': 3.0.0
|
||||
aggregate-error: 3.1.0
|
||||
fs-extra: 11.3.5
|
||||
lodash: 4.18.1
|
||||
semantic-release: 25.0.3(typescript@6.0.3)
|
||||
|
||||
'@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.3(typescript@6.0.3))':
|
||||
dependencies:
|
||||
conventional-changelog-angular: 8.3.1
|
||||
|
|
@ -8580,8 +8527,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@semantic-release/error@3.0.0': {}
|
||||
|
||||
'@semantic-release/error@4.0.0': {}
|
||||
|
||||
'@semantic-release/exec@7.1.0(semantic-release@25.0.3(typescript@6.0.3))':
|
||||
|
|
@ -8596,20 +8541,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@semantic-release/git@10.0.1(semantic-release@25.0.3(typescript@6.0.3))':
|
||||
dependencies:
|
||||
'@semantic-release/error': 3.0.0
|
||||
aggregate-error: 3.1.0
|
||||
debug: 4.4.3
|
||||
dir-glob: 3.0.1
|
||||
execa: 5.1.1
|
||||
lodash: 4.18.1
|
||||
micromatch: 4.0.8
|
||||
p-reduce: 2.1.0
|
||||
semantic-release: 25.0.3(typescript@6.0.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@semantic-release/github@12.0.8(semantic-release@25.0.3(typescript@6.0.3))':
|
||||
dependencies:
|
||||
'@octokit/core': 7.0.6
|
||||
|
|
@ -10034,18 +9965,6 @@ snapshots:
|
|||
dependencies:
|
||||
eventsource-parser: 3.0.8
|
||||
|
||||
execa@5.1.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 6.0.1
|
||||
human-signals: 2.1.0
|
||||
is-stream: 2.0.1
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 4.0.1
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
execa@8.0.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
|
@ -10642,8 +10561,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
|
||||
human-signals@8.0.1: {}
|
||||
|
|
@ -10983,8 +10900,6 @@ snapshots:
|
|||
|
||||
lodash.uniqby@4.7.0: {}
|
||||
|
||||
lodash@4.18.1: {}
|
||||
|
||||
logform@2.7.0:
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
|
|
@ -11671,10 +11586,6 @@ snapshots:
|
|||
|
||||
normalize-url@9.0.0: {}
|
||||
|
||||
npm-run-path@4.0.1:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
|
|
@ -11818,8 +11729,6 @@ snapshots:
|
|||
|
||||
p-map@7.0.4: {}
|
||||
|
||||
p-reduce@2.1.0: {}
|
||||
|
||||
p-reduce@3.0.0: {}
|
||||
|
||||
p-timeout@6.1.4: {}
|
||||
|
|
@ -12619,8 +12528,6 @@ snapshots:
|
|||
|
||||
strip-bom@3.0.0: {}
|
||||
|
||||
strip-final-newline@2.0.0: {}
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-final-newline@4.0.0: {}
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ export async function runLocalEmbeddingsRuntimeSmoke(options = {}) {
|
|||
900_000,
|
||||
);
|
||||
validateEmbeddingResponse(embeddingResponse, 384);
|
||||
process.stdout.write('KTX local embeddings daemon computed a 384-dimensional embedding\n');
|
||||
process.stdout.write('KTX daemon computed a 384-dimensional embedding\n');
|
||||
|
||||
const setup = await run(commands[5].command, commands[5].args, {
|
||||
cwd: installDir,
|
||||
|
|
@ -369,7 +369,7 @@ export async function runLocalEmbeddingsRuntimeSmoke(options = {}) {
|
|||
});
|
||||
requireSuccess(commands[6].label, stop);
|
||||
daemonStarted = false;
|
||||
requireOutput(commands[6].label, stop, /Stopped KTX Python daemon/);
|
||||
requireOutput(commands[6].label, stop, /Stopped KTX daemon/);
|
||||
|
||||
process.stdout.write('KTX local embeddings runtime smoke verified\n');
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -129,13 +129,13 @@ describe('localEmbeddingsSmokeCommands', () => {
|
|||
describe('parseDaemonBaseUrl', () => {
|
||||
it('extracts the daemon URL from runtime start output', () => {
|
||||
assert.equal(
|
||||
parseDaemonBaseUrl('Started KTX Python daemon\nurl: http://127.0.0.1:61234\nfeatures: local-embeddings\n'),
|
||||
parseDaemonBaseUrl('Started KTX daemon\nurl: http://127.0.0.1:61234\nfeatures: local-embeddings\n'),
|
||||
'http://127.0.0.1:61234',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects output without a daemon URL', () => {
|
||||
assert.throws(() => parseDaemonBaseUrl('Started KTX Python daemon\n'), /Daemon URL was not printed/);
|
||||
assert.throws(() => parseDaemonBaseUrl('Started KTX daemon\n'), /Daemon URL was not printed/);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -804,19 +804,19 @@ try {
|
|||
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']);
|
||||
requireSuccess('ktx admin runtime start', runtimeStart);
|
||||
daemonStarted = true;
|
||||
requireOutput('ktx admin runtime start', runtimeStart, /Started KTX Python daemon/);
|
||||
requireOutput('ktx admin runtime start', runtimeStart, /Started KTX daemon/);
|
||||
requireOutput('ktx admin runtime start', runtimeStart, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
|
||||
requireOutput('ktx admin runtime start', runtimeStart, /features: core/);
|
||||
|
||||
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']);
|
||||
requireSuccess('ktx admin runtime start reuse', runtimeStartReuse);
|
||||
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /Using existing KTX Python daemon/);
|
||||
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /Using existing KTX daemon/);
|
||||
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /features: core/);
|
||||
|
||||
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop']);
|
||||
requireSuccess('ktx admin runtime stop', runtimeStop);
|
||||
daemonStarted = false;
|
||||
requireOutput('ktx admin runtime stop', runtimeStop, /Stopped KTX Python daemon/);
|
||||
requireOutput('ktx admin runtime stop', runtimeStop, /Stopped KTX daemon/);
|
||||
process.stdout.write('ktx admin runtime daemon lifecycle verified\\n');
|
||||
|
||||
const structuralScan = await run('pnpm', ['exec', 'ktx', 'ingest', 'warehouse',
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ describe('verification snippets', () => {
|
|||
assert.match(source, /status: ready/);
|
||||
assert.match(source, /ktx admin runtime start/);
|
||||
assert.match(source, /ktx admin runtime start reuse/);
|
||||
assert.match(source, /Using existing KTX Python daemon/);
|
||||
assert.match(source, /Using existing KTX daemon/);
|
||||
assert.match(source, /ktx admin runtime stop/);
|
||||
assert.doesNotMatch(source, /ktx admin runtime prune/);
|
||||
assert.doesNotMatch(source, /staleRuntimeDir/);
|
||||
|
|
|
|||
|
|
@ -21,15 +21,14 @@ describe('release workflow', () => {
|
|||
assert.doesNotMatch(workflow, /Prepare first stable release floor/);
|
||||
assert.doesNotMatch(workflow, /git tag v0\.0\.0/);
|
||||
assert.doesNotMatch(workflow, /KTX_STABLE_RELEASE_FLOOR_TAG/);
|
||||
assert.match(workflow, /Prepare next prerelease branch/);
|
||||
assert.match(workflow, /git checkout -B "\$\{KTX_PRERELEASE_BRANCH\}"/);
|
||||
assert.doesNotMatch(workflow, /Prepare next prerelease branch/);
|
||||
assert.doesNotMatch(workflow, /KTX_PRERELEASE_BRANCH/);
|
||||
assert.doesNotMatch(workflow, /GITHUB_REF="refs\/heads\//);
|
||||
assert.match(workflow, /Prepare npm package root for release verification/);
|
||||
assert.match(workflow, /dist\/public-npm-package\/package\.json/);
|
||||
assert.match(workflow, /GITHUB_REF="refs\/heads\/\$\{KTX_PRERELEASE_BRANCH\}"/);
|
||||
assert.match(workflow, /pnpm run semantic-release:dry-run/);
|
||||
assert.match(workflow, /pnpm run semantic-release$/m);
|
||||
assert.match(workflow, /KTX_RELEASE_KIND: \$\{\{ inputs.release_kind \}\}/);
|
||||
assert.match(workflow, /KTX_PRERELEASE_BRANCH: next/);
|
||||
assert.match(workflow, /FORCE_RELEASE: \$\{\{ inputs.force_release \}\}/);
|
||||
assert.doesNotMatch(workflow, /NODE_AUTH_TOKEN/);
|
||||
assert.doesNotMatch(workflow, /^ push:/m);
|
||||
|
|
|
|||
|
|
@ -74,54 +74,22 @@ const releaseNoteTypes = [
|
|||
{ type: 'major', section: 'BREAKING CHANGES', hidden: false },
|
||||
];
|
||||
|
||||
function currentBranch(env) {
|
||||
return env.GITHUB_REF_NAME || env.INPUT_BRANCH || 'main';
|
||||
}
|
||||
|
||||
function releaseKind(env) {
|
||||
return env.KTX_RELEASE_KIND || env.INPUT_RELEASE_KIND || 'rc';
|
||||
}
|
||||
|
||||
function prereleaseBranch(env) {
|
||||
return env.KTX_PRERELEASE_BRANCH || env.INPUT_PRERELEASE_BRANCH || 'next';
|
||||
}
|
||||
|
||||
function releaseTag(kind) {
|
||||
return kind === 'rc' ? 'next' : 'latest';
|
||||
}
|
||||
|
||||
function releaseChangelogPlugins(kind) {
|
||||
return kind === 'rc' ? ['@semantic-release/changelog'] : [];
|
||||
}
|
||||
|
||||
function releaseGitPlugins(kind) {
|
||||
if (kind !== 'rc') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'@semantic-release/git',
|
||||
{
|
||||
assets: ['CHANGELOG.md', 'package.json', 'release-policy.json'],
|
||||
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function releaseBranches(env = process.env) {
|
||||
const branch = currentBranch(env);
|
||||
const kind = releaseKind(env);
|
||||
|
||||
if (kind === 'rc') {
|
||||
return ['main', { name: prereleaseBranch(env), prerelease: 'rc', channel: 'next' }];
|
||||
return [{ name: 'main', prerelease: 'rc', channel: 'next' }];
|
||||
}
|
||||
|
||||
if (kind === 'stable') {
|
||||
if (branch !== 'main') {
|
||||
throw new Error(`Stable KTX releases must run from main, got ${branch}`);
|
||||
}
|
||||
return ['main'];
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +125,6 @@ function createReleaseConfig(env = process.env) {
|
|||
},
|
||||
},
|
||||
],
|
||||
...releaseChangelogPlugins(kind),
|
||||
[
|
||||
'@semantic-release/exec',
|
||||
{
|
||||
|
|
@ -172,7 +139,6 @@ function createReleaseConfig(env = process.env) {
|
|||
].join(' && '),
|
||||
},
|
||||
],
|
||||
...releaseGitPlugins(kind),
|
||||
[
|
||||
'@semantic-release/github',
|
||||
{
|
||||
|
|
@ -188,7 +154,6 @@ function createReleaseConfig(env = process.env) {
|
|||
|
||||
module.exports = {
|
||||
createReleaseConfig,
|
||||
prereleaseBranch,
|
||||
releaseBranches,
|
||||
releaseKind,
|
||||
releaseTag,
|
||||
|
|
|
|||
|
|
@ -9,21 +9,16 @@ function releaseExecOptions(config) {
|
|||
return config.plugins.find((plugin) => Array.isArray(plugin) && plugin[0] === '@semantic-release/exec' && plugin[1].prepareCmd)[1];
|
||||
}
|
||||
|
||||
function releaseExecIndex(config) {
|
||||
return config.plugins.findIndex((plugin) => Array.isArray(plugin) && plugin[0] === '@semantic-release/exec' && plugin[1].prepareCmd);
|
||||
}
|
||||
|
||||
function pluginNames(config) {
|
||||
return config.plugins.map((plugin) => (Array.isArray(plugin) ? plugin[0] : plugin));
|
||||
}
|
||||
|
||||
describe('semantic-release config', () => {
|
||||
it('configures rc releases on a dedicated next prerelease branch', () => {
|
||||
it('configures rc releases as a prerelease on main', () => {
|
||||
assert.equal(releaseKind({ KTX_RELEASE_KIND: 'rc' }), 'rc');
|
||||
assert.equal(releaseTag('rc'), 'next');
|
||||
assert.deepEqual(releaseBranches({ KTX_RELEASE_KIND: 'rc', GITHUB_REF_NAME: 'main' }), [
|
||||
'main',
|
||||
{ name: 'next', prerelease: 'rc', channel: 'next' },
|
||||
{ name: 'main', prerelease: 'rc', channel: 'next' },
|
||||
]);
|
||||
|
||||
const config = createReleaseConfig({ KTX_RELEASE_KIND: 'rc', GITHUB_REF_NAME: 'main' });
|
||||
|
|
@ -42,14 +37,6 @@ describe('semantic-release config', () => {
|
|||
);
|
||||
assert.match(releaseExecOptions(config).publishCmd, /pnpm run release:published-smoke/);
|
||||
assert.doesNotMatch(JSON.stringify(config.plugins), /release:npm-publish/);
|
||||
const releaseFilePluginNames = pluginNames(config).filter(
|
||||
(plugin) => plugin === '@semantic-release/changelog' || plugin === '@semantic-release/git',
|
||||
);
|
||||
assert.deepEqual(releaseFilePluginNames, ['@semantic-release/changelog', '@semantic-release/git']);
|
||||
|
||||
const names = pluginNames(config);
|
||||
assert.ok(names.indexOf('@semantic-release/changelog') < releaseExecIndex(config));
|
||||
assert.ok(names.indexOf('@semantic-release/git') > releaseExecIndex(config));
|
||||
});
|
||||
|
||||
it('configures stable releases only from main with latest tag', () => {
|
||||
|
|
@ -69,18 +56,22 @@ describe('semantic-release config', () => {
|
|||
assert.equal(config.plugins.includes('./scripts/semantic-release-version-policy.cjs'), false);
|
||||
});
|
||||
|
||||
it('does not commit release files back to protected main during stable releases', () => {
|
||||
const config = createReleaseConfig({ KTX_RELEASE_KIND: 'stable', GITHUB_REF_NAME: 'main' });
|
||||
|
||||
assert.equal(pluginNames(config).includes('@semantic-release/git'), false);
|
||||
assert.equal(pluginNames(config).includes('@semantic-release/changelog'), false);
|
||||
it('never commits release files back to the repo', () => {
|
||||
for (const kind of ['rc', 'stable']) {
|
||||
const config = createReleaseConfig({ KTX_RELEASE_KIND: kind, GITHUB_REF_NAME: 'main' });
|
||||
assert.equal(pluginNames(config).includes('@semantic-release/git'), false, `${kind}: @semantic-release/git`);
|
||||
assert.equal(pluginNames(config).includes('@semantic-release/changelog'), false, `${kind}: @semantic-release/changelog`);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects stable releases from non-main branches', () => {
|
||||
assert.throws(
|
||||
() => releaseBranches({ KTX_RELEASE_KIND: 'stable', GITHUB_REF_NAME: 'feature/release-test' }),
|
||||
/Stable KTX releases must run from main, got feature\/release-test/,
|
||||
);
|
||||
it('produces a loadable config regardless of GITHUB_REF_NAME', () => {
|
||||
// Knip and other tooling load .releaserc.cjs on PR runners where
|
||||
// GITHUB_REF_NAME is the merge ref. semantic-release itself enforces the
|
||||
// main-only rule by refusing to publish when the current branch does not
|
||||
// match a configured release branch, so the config must not throw at load.
|
||||
for (const kind of ['rc', 'stable']) {
|
||||
assert.doesNotThrow(() => releaseBranches({ KTX_RELEASE_KIND: kind, GITHUB_REF_NAME: '180/merge' }));
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the force-release patch escape hatch', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue