mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
* docs: add npm managed python runtime design * build: add bundled python runtime wheel builder * build: make local embedding dependencies optional * build: bundle python runtime wheel in cli artifacts * build: track bundled python runtime release artifact * test: verify bundled python runtime wheel * docs: add plan for bundled python runtime wheel * test: cover managed python runtime lifecycle * feat: add managed python runtime installer * feat: add runtime command runner * feat: expose runtime management commands * test: verify managed python runtime commands * docs: add plan for managed python runtime installer * feat: add managed python command helper * feat: use managed runtime for sl query compute * feat: route sl query managed runtime policy * docs: add plan for managed runtime sl query integration * feat: add managed runtime daemon metadata * feat: manage python daemon lifecycle * feat: add runtime daemon start stop commands * fix: verify managed runtime daemon lifecycle * docs: add plan for managed runtime daemon lifecycle * feat: add managed local embeddings config marker * feat: add managed local embeddings daemon helper * feat: use managed runtime for local embedding setup * feat: pass managed runtime policy through setup * docs: add plan for managed local embeddings runtime * feat: read CLI package metadata dynamically * feat: assemble public kaelio ktx npm package * feat: release one public kaelio ktx npm artifact * test: cover public kaelio ktx package invocations * chore: verify public kaelio ktx package artifacts * docs: add plan for public kaelio ktx npm package * test: verify managed runtime in public package smoke * test: finalize managed runtime release smoke * docs: add plan for managed runtime release smoke * test: specify local embeddings release smoke * feat: add local embeddings runtime smoke * chore: register local embeddings smoke * fix: verify local embeddings smoke * fix: restore artifact smoke python env helper * docs: add plan for managed local embeddings release smoke * refactor: share managed runtime install policy parsing * feat: use managed runtime for agent semantic queries * feat: use managed runtime for MCP semantic compute * docs: add plan for managed agent and MCP semantic runtime * feat(cli): add managed daemon HTTP helpers * feat(cli): route local adapters through managed daemon * feat(cli): use managed daemon for ingest helpers * feat(cli): pass managed daemon options to scan * feat(context): pass MCP ingest pull config options * feat(cli): pass managed daemon options to serve ingest * test: verify managed local ingest daemon runtime * docs: add plan for managed local ingest daemon runtime * docs: align managed runtime examples * docs: add plan for managed runtime docs cleanup * test: cover published package runtime smoke commands * test: validate published package smoke outputs * docs: add plan for published package runtime smoke * build: stamp public npm package version * release: add npm public release policy * release: add guarded npm publish script * release: document public npm release handoff * docs: add plan for public npm release handoff * test: cover managed runtime prune in package smoke * docs: document managed runtime prune * docs: add plan for managed runtime prune smoke and docs * chore: encode uv runtime prerequisite policy * fix: clarify missing uv runtime error * docs: document uv runtime prerequisite * docs: add plan for uv runtime prerequisite contract * refactor: limit release artifacts to public package runtime * chore: align release policy with bundled runtime wheel * docs: describe single public runtime artifact surface * test: verify single public runtime artifact contract * docs: add plan for single public runtime artifact cleanup * fix: align local embeddings smoke with public version * docs: add plan for local embeddings smoke public version * release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag Publish target moves to the pre-release version 0.1.0-rc.0 under the next dist-tag so npm install @kaelio/ktx (which resolves to latest) does not pick up the soft-launch build. Users opt in via @kaelio/ktx@next. * Fix release script boundary checks * Remove PostHog from public package bundle
1332 lines
39 KiB
Markdown
1332 lines
39 KiB
Markdown
# Public NPM Release Handoff Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Turn the remaining npm-managed Python runtime release gap into a
|
|
guarded public `@kaelio/ktx` npm release handoff for version `0.1.0`.
|
|
|
|
**Architecture:** Keep one public npm package and keep Python packages
|
|
unpublished. The public package builder stamps the assembled `@kaelio/ktx`
|
|
package as `0.1.0`, release readiness accepts a publish-ready policy only when
|
|
all blocking decisions are encoded, and a new publish script performs a dry-run
|
|
by default before any live registry publish.
|
|
|
|
**Tech Stack:** Node 22 ESM scripts, `node:test`, pnpm 10 publish, JSON release
|
|
policy, GitHub Actions workflow validation.
|
|
|
|
---
|
|
|
|
## Spec trace and current state
|
|
|
|
This plan follows
|
|
`docs/superpowers/specs/2026-05-11-npm-managed-python-runtime-design.md`.
|
|
|
|
The existing plan files that reference that spec are:
|
|
|
|
- `docs/superpowers/plans/2026-05-11-bundled-python-runtime-wheel.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-agent-mcp-semantic-runtime.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-local-embeddings-release-smoke.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-local-embeddings-runtime.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-local-ingest-daemon-runtime.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-command-integration.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-daemon-lifecycle.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-installer.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-release-smoke.md`
|
|
- `docs/superpowers/plans/2026-05-11-managed-runtime-docs-and-postgres-smoke-cleanup.md`
|
|
- `docs/superpowers/plans/2026-05-11-public-kaelio-ktx-npm-package.md`
|
|
- `docs/superpowers/plans/2026-05-11-published-package-managed-runtime-smoke.md`
|
|
|
|
All twelve are implemented in the current tree: their referenced source and
|
|
test files exist, and the runtime command, daemon, package artifact,
|
|
published-package smoke, local-embedding smoke, and README markers are present.
|
|
|
|
The remaining release gap is explicit in `release-policy.json`: the repository
|
|
still uses `ci-artifact-only`, `npm.publish` is `false`, and the README states
|
|
that registry publishing is disabled. This plan changes that to a guarded
|
|
handoff for the first public npm release while leaving Python registry
|
|
publication disabled because the spec says KTX-owned Python code ships inside
|
|
the npm package as a bundled wheel for this release.
|
|
|
|
## File structure
|
|
|
|
- Modify `scripts/build-public-npm-package.mjs`: make the assembled public npm
|
|
package version and tarball name `0.1.0` instead of `0.0.0-private`.
|
|
- Modify `scripts/build-public-npm-package.test.mjs`: cover public version
|
|
stamping and the versioned tarball path.
|
|
- Modify `scripts/package-artifacts.mjs`: make artifact metadata report
|
|
`@kaelio/ktx` as version `0.1.0`.
|
|
- Modify `scripts/package-artifacts.test.mjs`: update artifact manifest,
|
|
metadata, runtime smoke, and demo smoke expectations for the public tarball.
|
|
- Modify `scripts/local-embeddings-runtime-smoke.test.mjs`: update public
|
|
tarball selection coverage for `kaelio-ktx-0.1.0.tgz`.
|
|
- Modify `scripts/release-readiness.mjs`: add the
|
|
`npm-public-release-ready` release mode and policy validation.
|
|
- Modify `scripts/release-readiness.test.mjs`: cover the publish-ready policy
|
|
and validation failures.
|
|
- Modify `release-policy.json`: encode the first public npm release handoff.
|
|
- Create `scripts/publish-public-npm-package.mjs`: verify readiness and run
|
|
`pnpm publish` in dry-run mode by default.
|
|
- Create `scripts/publish-public-npm-package.test.mjs`: cover publish command
|
|
construction and policy gating.
|
|
- Modify `package.json`: add `release:npm-publish`.
|
|
- Create `.github/workflows/release.yml`: add a manual dry-run/live publish
|
|
workflow for the public npm tarball.
|
|
- Create `scripts/release-workflow.test.mjs`: validate that the release
|
|
workflow is manual, uses pnpm, runs readiness checks, and gates live publish.
|
|
- Modify `README.md`: replace the disabled publishing note with the guarded
|
|
handoff commands.
|
|
|
|
### Task 1: Stamp public npm artifacts as `0.1.0`
|
|
|
|
**Files:**
|
|
|
|
- Modify: `scripts/build-public-npm-package.mjs`
|
|
- Modify: `scripts/build-public-npm-package.test.mjs`
|
|
- Modify: `scripts/package-artifacts.mjs`
|
|
- Modify: `scripts/package-artifacts.test.mjs`
|
|
- Modify: `scripts/local-embeddings-runtime-smoke.test.mjs`
|
|
|
|
- [ ] **Step 1: Write failing public version tests**
|
|
|
|
In `scripts/build-public-npm-package.test.mjs`, extend the import from
|
|
`./build-public-npm-package.mjs` so it includes `PUBLIC_NPM_PACKAGE_VERSION`
|
|
and `publicNpmPackageTarballName`:
|
|
|
|
```js
|
|
import {
|
|
PUBLIC_BUNDLED_WORKSPACE_PACKAGES,
|
|
PUBLIC_NPM_PACKAGE_NAME,
|
|
PUBLIC_NPM_PACKAGE_VERSION,
|
|
collectPublicDependencies,
|
|
createPublicNpmPackageTree,
|
|
publicNpmPackageJson,
|
|
publicNpmPackageLayout,
|
|
publicNpmPackageTarballName,
|
|
publicNpmPackCommand,
|
|
} from './build-public-npm-package.mjs';
|
|
```
|
|
|
|
Replace the `publicNpmPackageLayout` test expectation with:
|
|
|
|
```js
|
|
describe('publicNpmPackageLayout', () => {
|
|
it('uses the first public npm release version for the tarball name', () => {
|
|
const layout = publicNpmPackageLayout('/repo/ktx');
|
|
|
|
assert.equal(PUBLIC_NPM_PACKAGE_VERSION, '0.1.0');
|
|
assert.equal(publicNpmPackageTarballName(), 'kaelio-ktx-0.1.0.tgz');
|
|
assert.equal(layout.tarballPath, '/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz');
|
|
});
|
|
});
|
|
```
|
|
|
|
In the `publicNpmPackageJson` test, add this assertion after the package name
|
|
assertion:
|
|
|
|
```js
|
|
assert.equal(packageJson.version, '0.1.0');
|
|
```
|
|
|
|
In the `publicNpmPackCommand` test, replace the tarball assertion block with:
|
|
|
|
```js
|
|
assert.deepEqual(publicNpmPackCommand(layout), {
|
|
command: 'pnpm',
|
|
args: [
|
|
'--config.node-linker=hoisted',
|
|
'pack',
|
|
'--out',
|
|
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz',
|
|
],
|
|
cwd: '/repo/ktx/dist/public-npm-package',
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run public package tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/build-public-npm-package.test.mjs
|
|
```
|
|
|
|
Expected: FAIL. The failure mentions at least one stale
|
|
`kaelio-ktx-0.0.0-private.tgz` or `0.0.0-private` public package version
|
|
expectation.
|
|
|
|
- [ ] **Step 3: Implement public version stamping**
|
|
|
|
In `scripts/build-public-npm-package.mjs`, replace the current public version
|
|
constants and layout helper with:
|
|
|
|
```js
|
|
export const PUBLIC_NPM_PACKAGE_NAME = '@kaelio/ktx';
|
|
export const PUBLIC_NPM_PACKAGE_VERSION = '0.1.0';
|
|
|
|
export function publicNpmPackageTarballName(version = PUBLIC_NPM_PACKAGE_VERSION) {
|
|
return `kaelio-ktx-${version}.tgz`;
|
|
}
|
|
```
|
|
|
|
Replace `publicNpmPackageLayout` with:
|
|
|
|
```js
|
|
export function publicNpmPackageLayout(rootDir = scriptRootDir(), version = PUBLIC_NPM_PACKAGE_VERSION) {
|
|
return {
|
|
rootDir,
|
|
packageVersion: version,
|
|
cliPackageRoot: join(rootDir, 'packages', 'cli'),
|
|
packRoot: join(rootDir, 'dist', 'public-npm-package'),
|
|
npmDir: join(rootDir, 'dist', 'artifacts', 'npm'),
|
|
tarballPath: join(rootDir, 'dist', 'artifacts', 'npm', publicNpmPackageTarballName(version)),
|
|
};
|
|
}
|
|
```
|
|
|
|
Change `publicNpmPackageJson` so it accepts the public version explicitly:
|
|
|
|
```js
|
|
export function publicNpmPackageJson(cliPackageJson, dependencies, version = PUBLIC_NPM_PACKAGE_VERSION) {
|
|
return {
|
|
name: PUBLIC_NPM_PACKAGE_NAME,
|
|
version,
|
|
description: 'Standalone KTX context layer for database agents',
|
|
private: false,
|
|
type: 'module',
|
|
engines: cliPackageJson.engines ?? { node: '>=22.0.0' },
|
|
bin: { ktx: './dist/bin.js' },
|
|
main: cliPackageJson.main ?? 'dist/index.js',
|
|
types: cliPackageJson.types ?? 'dist/index.d.ts',
|
|
exports: cliPackageJson.exports ?? {
|
|
'.': {
|
|
types: './dist/index.d.ts',
|
|
import: './dist/index.js',
|
|
default: './dist/index.js',
|
|
},
|
|
'./package.json': './package.json',
|
|
},
|
|
files: ['dist', 'assets'],
|
|
dependencies,
|
|
bundledDependencies: PUBLIC_BUNDLED_WORKSPACE_PACKAGES,
|
|
license: cliPackageJson.license ?? 'Apache-2.0',
|
|
repository: {
|
|
type: 'git',
|
|
url: 'git+https://github.com/kaelio/ktx.git',
|
|
},
|
|
bugs: {
|
|
url: 'https://github.com/kaelio/ktx/issues',
|
|
},
|
|
homepage: 'https://github.com/kaelio/ktx#readme',
|
|
};
|
|
}
|
|
```
|
|
|
|
In `copyCliPackage`, pass the layout version:
|
|
|
|
```js
|
|
await writeJson(
|
|
join(layout.packRoot, 'package.json'),
|
|
publicNpmPackageJson(cliPackageJson, dependencies, layout.packageVersion),
|
|
);
|
|
```
|
|
|
|
In `createPublicNpmPackageTree`, return the versioned package JSON:
|
|
|
|
```js
|
|
return {
|
|
layout,
|
|
packageJson: publicNpmPackageJson(cliPackageJson, dependencies, layout.packageVersion),
|
|
bundledPackages: PUBLIC_BUNDLED_WORKSPACE_PACKAGES,
|
|
};
|
|
```
|
|
|
|
- [ ] **Step 4: Run public package tests to verify pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/build-public-npm-package.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Write failing artifact metadata tests**
|
|
|
|
In `scripts/package-artifacts.test.mjs`, replace expectations that use the
|
|
public npm tarball or package version:
|
|
|
|
```js
|
|
assert.equal(layout.cliTarball, '/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz');
|
|
```
|
|
|
|
```js
|
|
{
|
|
ecosystem: 'npm',
|
|
packageName: '@kaelio/ktx',
|
|
packageRoot: 'packages/cli',
|
|
packageVersion: '0.1.0',
|
|
private: false,
|
|
releaseMode: 'ci-artifact-only',
|
|
}
|
|
```
|
|
|
|
```js
|
|
{
|
|
ecosystem: 'npm',
|
|
packageName: '@kaelio/ktx',
|
|
packageVersion: '0.1.0',
|
|
path: 'npm/kaelio-ktx-0.1.0.tgz',
|
|
bytes: Buffer.byteLength('@kaelio/ktx-tarball'),
|
|
sha256: createHash('sha256').update('@kaelio/ktx-tarball').digest('hex'),
|
|
}
|
|
```
|
|
|
|
In the runtime smoke source expectation, replace:
|
|
|
|
```js
|
|
requireOutput('ktx public package version', version, /@kaelio\/ktx 0\.1\.0/);
|
|
```
|
|
|
|
In `scripts/local-embeddings-runtime-smoke.test.mjs`, replace the public
|
|
tarball selection assertion with:
|
|
|
|
```js
|
|
assert.equal(
|
|
publicKtxTarballName(['kaelio-ktx-0.1.0.tgz', 'ignore-me.tgz']),
|
|
'kaelio-ktx-0.1.0.tgz',
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 6: Run artifact tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/package-artifacts.test.mjs scripts/local-embeddings-runtime-smoke.test.mjs
|
|
```
|
|
|
|
Expected: FAIL. The failure mentions stale artifact metadata or tarball
|
|
expectations for `0.0.0-private`.
|
|
|
|
- [ ] **Step 7: Implement artifact metadata versioning**
|
|
|
|
In `scripts/package-artifacts.mjs`, change the build-public import to:
|
|
|
|
```js
|
|
import {
|
|
PUBLIC_NPM_PACKAGE_NAME,
|
|
PUBLIC_NPM_PACKAGE_VERSION,
|
|
publicNpmPackageTarballName,
|
|
} from './build-public-npm-package.mjs';
|
|
```
|
|
|
|
Replace `npmPackageTarballName` with:
|
|
|
|
```js
|
|
function npmPackageTarballName(packageName) {
|
|
if (packageName === PUBLIC_NPM_PACKAGE_NAME) {
|
|
return publicNpmPackageTarballName(PUBLIC_NPM_PACKAGE_VERSION);
|
|
}
|
|
return `${packageName.replace('@ktx/', 'ktx-')}-${PACKAGE_VERSION}.tgz`;
|
|
}
|
|
```
|
|
|
|
In `readNpmPackageMetadata`, return the public package version for
|
|
`@kaelio/ktx`:
|
|
|
|
```js
|
|
const isPublicKtxPackage = packageInfo.name === PUBLIC_NPM_PACKAGE_NAME;
|
|
return releaseMetadataEntry({
|
|
ecosystem: 'npm',
|
|
packageName: packageInfo.name,
|
|
packageRoot: packageInfo.packageRoot,
|
|
packageVersion: isPublicKtxPackage ? PUBLIC_NPM_PACKAGE_VERSION : packageJson.version,
|
|
privatePackage: isPublicKtxPackage ? false : packageJson.private === true,
|
|
});
|
|
```
|
|
|
|
In `npmRuntimeSmokeSource`, replace the version output regex with:
|
|
|
|
```js
|
|
requireOutput('ktx public package version', version, /@kaelio\/ktx 0\.1\.0/);
|
|
```
|
|
|
|
- [ ] **Step 8: Run artifact tests to verify pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/package-artifacts.test.mjs scripts/local-embeddings-runtime-smoke.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 9: Commit public version stamping**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add scripts/build-public-npm-package.mjs scripts/build-public-npm-package.test.mjs scripts/package-artifacts.mjs scripts/package-artifacts.test.mjs scripts/local-embeddings-runtime-smoke.test.mjs
|
|
git commit -m "build: stamp public npm package version"
|
|
```
|
|
|
|
Expected: commit created.
|
|
|
|
### Task 2: Add publish-ready release policy validation
|
|
|
|
**Files:**
|
|
|
|
- Modify: `scripts/release-readiness.mjs`
|
|
- Modify: `scripts/release-readiness.test.mjs`
|
|
- Modify: `release-policy.json`
|
|
|
|
- [ ] **Step 1: Write failing release readiness tests**
|
|
|
|
In `scripts/release-readiness.test.mjs`, add `PUBLIC_NPM_PACKAGE_VERSION` to
|
|
the imports from `./build-public-npm-package.mjs`:
|
|
|
|
```js
|
|
import { PUBLIC_NPM_PACKAGE_VERSION } from './build-public-npm-package.mjs';
|
|
```
|
|
|
|
Update `releasePolicy()` so the default npm block includes publish settings:
|
|
|
|
```js
|
|
npm: {
|
|
publish: false,
|
|
registry: null,
|
|
access: 'public',
|
|
tag: 'latest',
|
|
packages: ['@kaelio/ktx'],
|
|
...npmOverrides,
|
|
},
|
|
```
|
|
|
|
In each existing `releaseReadinessReport` expected object for
|
|
`ci-artifact-only` and `published-package-smoke-required`, add:
|
|
|
|
```js
|
|
npmPublish: null,
|
|
```
|
|
|
|
Place it after `publishedPackageSmokeGate` and before
|
|
`blockedPublishingDecisions`.
|
|
|
|
In `writeReleaseMetadataInputs`, keep internal workspace package versions
|
|
private. The public package version comes from artifact metadata:
|
|
|
|
```js
|
|
version: '0.0.0-private',
|
|
private: true,
|
|
```
|
|
|
|
Add this test after the existing
|
|
`reports required published package smoke when release mode requires it` test:
|
|
|
|
```js
|
|
it('accepts the npm public release ready policy', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'ktx-npm-public-ready-test-'));
|
|
try {
|
|
await writeReadyFixture(root, {
|
|
policy: releasePolicy({
|
|
releaseMode: 'npm-public-release-ready',
|
|
npm: {
|
|
publish: true,
|
|
registry: null,
|
|
access: 'public',
|
|
tag: 'latest',
|
|
},
|
|
publishedPackageSmoke: {
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
registry: null,
|
|
},
|
|
requiredBeforePublishing: [],
|
|
}),
|
|
});
|
|
|
|
const report = await releaseReadinessReport(root);
|
|
|
|
assert.deepEqual(report, {
|
|
schemaVersion: 1,
|
|
releaseMode: 'npm-public-release-ready',
|
|
sourceRevision: 'abc123',
|
|
npmPublishEnabled: true,
|
|
pythonPublishEnabled: false,
|
|
packageNames: ['@kaelio/ktx', 'ktx-sl', 'ktx-daemon', 'kaelio-ktx'],
|
|
publishedPackageSmokeGate: {
|
|
status: 'required',
|
|
script: 'pnpm run release:published-smoke',
|
|
reason: 'Run the published package smoke after the npm package is published.',
|
|
configSource: 'release-policy',
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
registry: null,
|
|
},
|
|
npmPublish: {
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
access: 'public',
|
|
tag: 'latest',
|
|
registry: null,
|
|
},
|
|
blockedPublishingDecisions: [],
|
|
});
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
```
|
|
|
|
Add this validation test:
|
|
|
|
```js
|
|
it('rejects npm public release ready mode when npm publish is disabled', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'ktx-npm-public-ready-disabled-test-'));
|
|
try {
|
|
await writeReadyFixture(root, {
|
|
policy: releasePolicy({
|
|
releaseMode: 'npm-public-release-ready',
|
|
npm: {
|
|
publish: false,
|
|
registry: null,
|
|
access: 'public',
|
|
tag: 'latest',
|
|
},
|
|
publishedPackageSmoke: {
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
registry: null,
|
|
},
|
|
requiredBeforePublishing: [],
|
|
}),
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => releaseReadinessReport(root),
|
|
/npm-public-release-ready policy requires npm.publish true/,
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
```
|
|
|
|
Add this validation test:
|
|
|
|
```js
|
|
it('rejects npm public release ready mode when Python publishing is enabled', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'ktx-npm-public-ready-python-test-'));
|
|
try {
|
|
await writeReadyFixture(root, {
|
|
policy: releasePolicy({
|
|
releaseMode: 'npm-public-release-ready',
|
|
npm: {
|
|
publish: true,
|
|
registry: null,
|
|
access: 'public',
|
|
tag: 'latest',
|
|
},
|
|
python: {
|
|
publish: true,
|
|
repository: 'pypi',
|
|
},
|
|
publishedPackageSmoke: {
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
registry: null,
|
|
},
|
|
requiredBeforePublishing: [],
|
|
}),
|
|
});
|
|
|
|
await assert.rejects(
|
|
() => releaseReadinessReport(root),
|
|
/npm-public-release-ready policy keeps python.publish false/,
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run release readiness tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/release-readiness.test.mjs
|
|
```
|
|
|
|
Expected: FAIL with `Unsupported release policy releaseMode:
|
|
npm-public-release-ready` or missing `npm.access` validation.
|
|
|
|
- [ ] **Step 3: Implement publish-ready policy validation**
|
|
|
|
In `scripts/release-readiness.mjs`, import the public package version:
|
|
|
|
```js
|
|
import { PUBLIC_NPM_PACKAGE_VERSION } from './build-public-npm-package.mjs';
|
|
```
|
|
|
|
Add the release mode constant and include it in `SUPPORTED_RELEASE_MODES`:
|
|
|
|
```js
|
|
const NPM_PUBLIC_RELEASE_READY_MODE = 'npm-public-release-ready';
|
|
const SUPPORTED_RELEASE_MODES = new Set([
|
|
CI_ARTIFACT_ONLY_RELEASE_MODE,
|
|
PUBLISHED_PACKAGE_SMOKE_REQUIRED_RELEASE_MODE,
|
|
NPM_PUBLIC_RELEASE_READY_MODE,
|
|
]);
|
|
```
|
|
|
|
Add string validators for the npm publish settings:
|
|
|
|
```js
|
|
function assertNpmAccess(value) {
|
|
if (value !== 'public') {
|
|
throw new Error('Release policy npm.access must be public');
|
|
}
|
|
}
|
|
|
|
function assertNpmTag(value) {
|
|
assertString(value, 'Release policy npm.tag');
|
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value)) {
|
|
throw new Error(`Invalid Release policy npm.tag: ${value}`);
|
|
}
|
|
}
|
|
```
|
|
|
|
In `validateReleasePolicy`, validate the new npm fields after
|
|
`assertNullableString(policy.npm.registry, 'Release policy npm.registry');`:
|
|
|
|
```js
|
|
assertNpmAccess(policy.npm.access);
|
|
assertNpmTag(policy.npm.tag);
|
|
```
|
|
|
|
Replace `assertRequiredBeforePublishing` with:
|
|
|
|
```js
|
|
function assertRequiredBeforePublishing(policy) {
|
|
assertStringArray(policy.requiredBeforePublishing, 'Release policy requiredBeforePublishing');
|
|
|
|
if (policy.releaseMode === CI_ARTIFACT_ONLY_RELEASE_MODE && policy.requiredBeforePublishing.length === 0) {
|
|
throw new Error('Release policy requiredBeforePublishing must list the remaining publishing decisions');
|
|
}
|
|
|
|
if (
|
|
(policy.releaseMode === PUBLISHED_PACKAGE_SMOKE_REQUIRED_RELEASE_MODE ||
|
|
policy.releaseMode === NPM_PUBLIC_RELEASE_READY_MODE) &&
|
|
policy.requiredBeforePublishing.length > 0
|
|
) {
|
|
throw new Error(`${policy.releaseMode} release mode requires requiredBeforePublishing to be empty`);
|
|
}
|
|
}
|
|
```
|
|
|
|
Replace `publishedPackageSmokeGate` with:
|
|
|
|
```js
|
|
function publishedPackageSmokeGate(policy) {
|
|
const config = readPublishedPackageSmokeConfig({}, [], policy.publishedPackageSmoke);
|
|
|
|
if (
|
|
(policy.releaseMode === PUBLISHED_PACKAGE_SMOKE_REQUIRED_RELEASE_MODE ||
|
|
policy.releaseMode === NPM_PUBLIC_RELEASE_READY_MODE) &&
|
|
!config.enabled
|
|
) {
|
|
throw new Error(`${policy.releaseMode} release mode requires release-policy.json publishedPackageSmoke.packageName`);
|
|
}
|
|
|
|
const base =
|
|
policy.releaseMode === CI_ARTIFACT_ONLY_RELEASE_MODE
|
|
? {
|
|
status: 'not_required',
|
|
reason: 'Published package smoke remains pending until release-policy.json enables npm registry publishing.',
|
|
}
|
|
: policy.releaseMode === NPM_PUBLIC_RELEASE_READY_MODE
|
|
? {
|
|
status: 'required',
|
|
reason: 'Run the published package smoke after the npm package is published.',
|
|
}
|
|
: {
|
|
status: 'required',
|
|
reason: 'Run the published package smoke before accepting the hybrid-search release.',
|
|
};
|
|
|
|
return {
|
|
...base,
|
|
script: 'pnpm run release:published-smoke',
|
|
configSource: config.enabled ? config.configSource : null,
|
|
packageName: config.enabled ? config.packageName : null,
|
|
version: config.enabled ? config.packageVersion : policy.publishedPackageSmoke.version,
|
|
registry: config.enabled ? (config.registry ?? null) : policy.publishedPackageSmoke.registry,
|
|
};
|
|
}
|
|
```
|
|
|
|
Add this function below `assertNonPublishingArtifactPolicy`:
|
|
|
|
```js
|
|
function assertNpmPublicReleaseReadyPolicy(policy, metadata) {
|
|
if (policy.npm.publish !== true) {
|
|
throw new Error('npm-public-release-ready policy requires npm.publish true');
|
|
}
|
|
if (policy.python.publish !== false) {
|
|
throw new Error('npm-public-release-ready policy keeps python.publish false');
|
|
}
|
|
if (policy.python.repository !== null) {
|
|
throw new Error('npm-public-release-ready policy keeps python.repository null');
|
|
}
|
|
|
|
assertSameMembers(policy.npm.packages, ['@kaelio/ktx'], 'Release policy npm.packages');
|
|
assertSameMembers(policy.python.packages, metadataNames(metadata, 'python'), 'Release policy python.packages');
|
|
|
|
const npmMetadata = metadata.find((entry) => entry.ecosystem === 'npm' && entry.packageName === '@kaelio/ktx');
|
|
if (!npmMetadata) {
|
|
throw new Error('npm-public-release-ready policy requires @kaelio/ktx artifact metadata');
|
|
}
|
|
if (npmMetadata.private !== false) {
|
|
throw new Error('npm-public-release-ready policy requires @kaelio/ktx to be publishable');
|
|
}
|
|
if (npmMetadata.packageVersion !== PUBLIC_NPM_PACKAGE_VERSION) {
|
|
throw new Error(
|
|
`npm-public-release-ready policy expected @kaelio/ktx ${PUBLIC_NPM_PACKAGE_VERSION}, got ${npmMetadata.packageVersion}`,
|
|
);
|
|
}
|
|
if (policy.publishedPackageSmoke.packageName !== '@kaelio/ktx') {
|
|
throw new Error('npm-public-release-ready policy requires publishedPackageSmoke.packageName @kaelio/ktx');
|
|
}
|
|
if (policy.publishedPackageSmoke.version !== PUBLIC_NPM_PACKAGE_VERSION) {
|
|
throw new Error(
|
|
`npm-public-release-ready policy requires publishedPackageSmoke.version ${PUBLIC_NPM_PACKAGE_VERSION}`,
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
Inside `assertNonPublishingArtifactPolicy`, replace the npm package version
|
|
suffix check with public-package-aware validation:
|
|
|
|
```js
|
|
if (isPublicKtxPackage) {
|
|
if (entry.packageVersion !== PUBLIC_NPM_PACKAGE_VERSION) {
|
|
throw new Error(
|
|
`${policyLabel} npm package @kaelio/ktx must use public version ${PUBLIC_NPM_PACKAGE_VERSION}`,
|
|
);
|
|
}
|
|
} else if (!entry.packageVersion.endsWith('-private')) {
|
|
throw new Error(`${policyLabel} npm package ${entry.packageName} must use a private version suffix`);
|
|
}
|
|
```
|
|
|
|
In `releaseReadinessReport`, replace the unconditional
|
|
`assertNonPublishingArtifactPolicy(policy, metadata);` call with:
|
|
|
|
```js
|
|
if (policy.releaseMode === NPM_PUBLIC_RELEASE_READY_MODE) {
|
|
assertNpmPublicReleaseReadyPolicy(policy, metadata);
|
|
} else {
|
|
assertNonPublishingArtifactPolicy(policy, metadata);
|
|
}
|
|
```
|
|
|
|
Add `npmPublish` to the returned report:
|
|
|
|
```js
|
|
npmPublish:
|
|
policy.releaseMode === NPM_PUBLIC_RELEASE_READY_MODE
|
|
? {
|
|
packageName: '@kaelio/ktx',
|
|
version: PUBLIC_NPM_PACKAGE_VERSION,
|
|
access: policy.npm.access,
|
|
tag: policy.npm.tag,
|
|
registry: policy.npm.registry,
|
|
}
|
|
: null,
|
|
```
|
|
|
|
Update the text output so it prints the npm publish target when present:
|
|
|
|
```js
|
|
if (report.npmPublish) {
|
|
process.stdout.write(
|
|
`NPM publish target: ${report.npmPublish.packageName}@${report.npmPublish.version} (${report.npmPublish.tag})\n`,
|
|
);
|
|
} else {
|
|
process.stdout.write('Registry publishing remains disabled by release-policy.json.\n');
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Update release policy**
|
|
|
|
Replace `release-policy.json` with:
|
|
|
|
```json
|
|
{
|
|
"schemaVersion": 1,
|
|
"releaseMode": "npm-public-release-ready",
|
|
"npm": {
|
|
"publish": true,
|
|
"registry": null,
|
|
"access": "public",
|
|
"tag": "latest",
|
|
"packages": ["@kaelio/ktx"]
|
|
},
|
|
"python": {
|
|
"publish": false,
|
|
"repository": null,
|
|
"packages": ["ktx-sl", "ktx-daemon", "kaelio-ktx"]
|
|
},
|
|
"publishedPackageSmoke": {
|
|
"packageName": "@kaelio/ktx",
|
|
"version": "0.1.0",
|
|
"registry": null
|
|
},
|
|
"requiredBeforePublishing": []
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 5: Run release readiness tests to verify pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/release-readiness.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 6: Commit release policy validation**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add scripts/release-readiness.mjs scripts/release-readiness.test.mjs release-policy.json
|
|
git commit -m "release: add npm public release policy"
|
|
```
|
|
|
|
Expected: commit created.
|
|
|
|
### Task 3: Add guarded npm publish script
|
|
|
|
**Files:**
|
|
|
|
- Create: `scripts/publish-public-npm-package.test.mjs`
|
|
- Create: `scripts/publish-public-npm-package.mjs`
|
|
- Modify: `package.json`
|
|
|
|
- [ ] **Step 1: Write failing publish script tests**
|
|
|
|
Create `scripts/publish-public-npm-package.test.mjs` with:
|
|
|
|
```js
|
|
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { describe, it } from 'node:test';
|
|
|
|
import {
|
|
buildNpmPublishCommand,
|
|
requireNpmPublicReleaseReady,
|
|
resolvePublishMode,
|
|
} from './publish-public-npm-package.mjs';
|
|
|
|
const readyReport = {
|
|
releaseMode: 'npm-public-release-ready',
|
|
npmPublishEnabled: true,
|
|
npmPublish: {
|
|
packageName: '@kaelio/ktx',
|
|
version: '0.1.0',
|
|
access: 'public',
|
|
tag: 'latest',
|
|
registry: null,
|
|
},
|
|
};
|
|
|
|
describe('resolvePublishMode', () => {
|
|
it('dry-runs by default', () => {
|
|
assert.deepEqual(resolvePublishMode([]), { live: false });
|
|
});
|
|
|
|
it('requires an explicit flag for live publish', () => {
|
|
assert.deepEqual(resolvePublishMode(['--publish']), { live: true });
|
|
});
|
|
});
|
|
|
|
describe('requireNpmPublicReleaseReady', () => {
|
|
it('accepts the npm public release ready report', () => {
|
|
assert.equal(requireNpmPublicReleaseReady(readyReport), readyReport.npmPublish);
|
|
});
|
|
|
|
it('rejects artifact-only reports', () => {
|
|
assert.throws(
|
|
() =>
|
|
requireNpmPublicReleaseReady({
|
|
releaseMode: 'ci-artifact-only',
|
|
npmPublishEnabled: false,
|
|
npmPublish: null,
|
|
}),
|
|
/release-policy.json must use npm-public-release-ready before publishing/,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('buildNpmPublishCommand', () => {
|
|
it('builds a dry-run pnpm publish command by default', () => {
|
|
assert.deepEqual(buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', readyReport.npmPublish, { live: false }), {
|
|
command: 'pnpm',
|
|
args: [
|
|
'publish',
|
|
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz',
|
|
'--access',
|
|
'public',
|
|
'--tag',
|
|
'latest',
|
|
'--dry-run',
|
|
],
|
|
env: {},
|
|
});
|
|
});
|
|
|
|
it('omits dry-run only for explicit live publish', () => {
|
|
assert.deepEqual(buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', readyReport.npmPublish, { live: true }).args, [
|
|
'publish',
|
|
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz',
|
|
'--access',
|
|
'public',
|
|
'--tag',
|
|
'latest',
|
|
]);
|
|
});
|
|
|
|
it('uses npm_config_registry when a registry is configured', () => {
|
|
const publish = {
|
|
...readyReport.npmPublish,
|
|
registry: 'https://registry.npmjs.org/',
|
|
};
|
|
|
|
assert.deepEqual(
|
|
buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', publish, { live: false }).env,
|
|
{ npm_config_registry: 'https://registry.npmjs.org/' },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('package script', () => {
|
|
it('registers release:npm-publish', async () => {
|
|
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
|
|
assert.equal(packageJson.scripts['release:npm-publish'], 'node scripts/publish-public-npm-package.mjs');
|
|
});
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run publish script tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/publish-public-npm-package.test.mjs
|
|
```
|
|
|
|
Expected: FAIL with `Cannot find module` for
|
|
`scripts/publish-public-npm-package.mjs`.
|
|
|
|
- [ ] **Step 3: Implement publish script**
|
|
|
|
Create `scripts/publish-public-npm-package.mjs` with:
|
|
|
|
```js
|
|
#!/usr/bin/env node
|
|
|
|
import { execFile } from 'node:child_process';
|
|
import { access } from 'node:fs/promises';
|
|
import { promisify } from 'node:util';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
import { packageArtifactLayout } from './package-artifacts.mjs';
|
|
import { releaseReadinessReport } from './release-readiness.mjs';
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
export function resolvePublishMode(args = process.argv.slice(2)) {
|
|
return { live: args.includes('--publish') };
|
|
}
|
|
|
|
export function requireNpmPublicReleaseReady(report) {
|
|
if (report.releaseMode !== 'npm-public-release-ready' || report.npmPublishEnabled !== true || !report.npmPublish) {
|
|
throw new Error('release-policy.json must use npm-public-release-ready before publishing');
|
|
}
|
|
return report.npmPublish;
|
|
}
|
|
|
|
export function buildNpmPublishCommand(tarballPath, publish, mode) {
|
|
return {
|
|
command: 'pnpm',
|
|
args: [
|
|
'publish',
|
|
tarballPath,
|
|
'--access',
|
|
publish.access,
|
|
'--tag',
|
|
publish.tag,
|
|
...(mode.live ? [] : ['--dry-run']),
|
|
],
|
|
env: publish.registry ? { npm_config_registry: publish.registry } : {},
|
|
};
|
|
}
|
|
|
|
async function assertFileExists(path) {
|
|
try {
|
|
await access(path);
|
|
} catch {
|
|
throw new Error(`Missing npm tarball: ${path}. Run pnpm run artifacts:check first.`);
|
|
}
|
|
}
|
|
|
|
async function runPublishCommand(command) {
|
|
process.stdout.write(`$ ${command.command} ${command.args.join(' ')}\n`);
|
|
await execFileAsync(command.command, command.args, {
|
|
env: { ...process.env, ...command.env },
|
|
encoding: 'utf8',
|
|
maxBuffer: 1024 * 1024 * 20,
|
|
});
|
|
}
|
|
|
|
export async function publishPublicNpmPackage(options = {}) {
|
|
const rootDir = options.rootDir;
|
|
const mode = options.mode ?? resolvePublishMode(options.args);
|
|
const report = await releaseReadinessReport(rootDir);
|
|
const publish = requireNpmPublicReleaseReady(report);
|
|
const layout = packageArtifactLayout(rootDir);
|
|
const tarballPath = layout.cliTarball;
|
|
|
|
await assertFileExists(tarballPath);
|
|
const command = buildNpmPublishCommand(tarballPath, publish, mode);
|
|
await runPublishCommand(command);
|
|
|
|
process.stdout.write(
|
|
mode.live
|
|
? `Published ${publish.packageName}@${publish.version} with tag ${publish.tag}\n`
|
|
: `Dry-run verified ${publish.packageName}@${publish.version} with tag ${publish.tag}\n`,
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
await publishPublicNpmPackage({ args: process.argv.slice(2) });
|
|
}
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
try {
|
|
await main();
|
|
} catch (error) {
|
|
process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 4: Add the package script**
|
|
|
|
In root `package.json`, add this script after `release:local-embeddings-smoke`:
|
|
|
|
```json
|
|
"release:npm-publish": "node scripts/publish-public-npm-package.mjs",
|
|
```
|
|
|
|
- [ ] **Step 5: Run publish script tests to verify pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/publish-public-npm-package.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 6: Run a dry-run publish after artifacts are built**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm run artifacts:check
|
|
pnpm run release:npm-publish
|
|
```
|
|
|
|
Expected: PASS. The publish command includes `--dry-run`, and the final line is:
|
|
|
|
```text
|
|
Dry-run verified @kaelio/ktx@0.1.0 with tag latest
|
|
```
|
|
|
|
- [ ] **Step 7: Commit publish script**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add scripts/publish-public-npm-package.mjs scripts/publish-public-npm-package.test.mjs package.json
|
|
git commit -m "release: add guarded npm publish script"
|
|
```
|
|
|
|
Expected: commit created.
|
|
|
|
### Task 4: Add manual release workflow and docs
|
|
|
|
**Files:**
|
|
|
|
- Create: `.github/workflows/release.yml`
|
|
- Create: `scripts/release-workflow.test.mjs`
|
|
- Modify: `README.md`
|
|
|
|
- [ ] **Step 1: Write failing workflow tests**
|
|
|
|
Create `scripts/release-workflow.test.mjs` with:
|
|
|
|
```js
|
|
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { describe, it } from 'node:test';
|
|
|
|
describe('release workflow', () => {
|
|
it('publishes only from manual dispatch with an explicit live input', async () => {
|
|
const workflow = await readFile(new URL('../.github/workflows/release.yml', import.meta.url), 'utf8');
|
|
|
|
assert.match(workflow, /^name: KTX Release$/m);
|
|
assert.match(workflow, /^ workflow_dispatch:$/m);
|
|
assert.match(workflow, /publish_live:/);
|
|
assert.match(workflow, /default: false/);
|
|
assert.match(workflow, /pnpm run artifacts:check/);
|
|
assert.match(workflow, /pnpm run release:readiness/);
|
|
assert.match(workflow, /pnpm run release:npm-publish$/m);
|
|
assert.match(workflow, /pnpm run release:npm-publish -- --publish/);
|
|
assert.match(workflow, /NODE_AUTH_TOKEN: \$\{\{ secrets.NPM_TOKEN \}\}/);
|
|
assert.doesNotMatch(workflow, /^ push:/m);
|
|
assert.doesNotMatch(workflow, /^ pull_request:/m);
|
|
});
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run workflow tests to verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/release-workflow.test.mjs
|
|
```
|
|
|
|
Expected: FAIL because `.github/workflows/release.yml` does not exist.
|
|
|
|
- [ ] **Step 3: Add the release workflow**
|
|
|
|
Create `.github/workflows/release.yml` with:
|
|
|
|
```yaml
|
|
name: KTX Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
publish_live:
|
|
description: "Publish @kaelio/ktx to npm instead of running a dry-run"
|
|
required: true
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ktx-release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
npm-public-release:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
|
with:
|
|
run_install: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
with:
|
|
node-version: "24"
|
|
cache: "pnpm"
|
|
cache-dependency-path: "pnpm-lock.yaml"
|
|
|
|
- name: Install TypeScript dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.13"
|
|
|
|
- name: Setup uv
|
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
|
|
- name: Install Python dependencies
|
|
run: uv sync --all-packages
|
|
|
|
- name: Build and verify artifacts
|
|
run: pnpm run artifacts:check
|
|
|
|
- name: Check release readiness
|
|
run: pnpm run release:readiness
|
|
|
|
- name: Dry-run npm publish
|
|
if: ${{ !inputs.publish_live }}
|
|
run: pnpm run release:npm-publish
|
|
|
|
- name: Publish npm package
|
|
if: ${{ inputs.publish_live }}
|
|
run: pnpm run release:npm-publish -- --publish
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
```
|
|
|
|
- [ ] **Step 4: Update release docs**
|
|
|
|
In `README.md`, replace the current `## Release status` section with:
|
|
|
|
```markdown
|
|
## Release status
|
|
|
|
This repository builds one public npm artifact named `@kaelio/ktx`. The first
|
|
public npm handoff is policy-gated through `release-policy.json`, which keeps
|
|
Python package publishing disabled because KTX-owned Python code ships inside
|
|
the npm package as a bundled wheel.
|
|
|
|
Build local package artifacts and verify the guarded dry-run publish path with:
|
|
|
|
```bash
|
|
source .venv/bin/activate
|
|
pnpm run artifacts:check
|
|
pnpm run release:readiness
|
|
pnpm run release:npm-publish
|
|
```
|
|
|
|
Run the live npm publish only from the manual `KTX Release` workflow with the
|
|
`publish_live` input enabled after the `NPM_TOKEN` secret is configured.
|
|
```
|
|
|
|
- [ ] **Step 5: Run workflow and README checks**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/release-workflow.test.mjs scripts/examples-docs.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 6: Commit workflow and docs**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add .github/workflows/release.yml scripts/release-workflow.test.mjs README.md
|
|
git commit -m "release: document public npm release handoff"
|
|
```
|
|
|
|
Expected: commit created.
|
|
|
|
### Task 5: Final verification
|
|
|
|
**Files:**
|
|
|
|
- Verify: `scripts/*.test.mjs`
|
|
- Verify: `packages/cli/src/*`
|
|
- Verify: `README.md`
|
|
- Verify: `release-policy.json`
|
|
|
|
- [ ] **Step 1: Run focused script tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test scripts/build-public-npm-package.test.mjs scripts/package-artifacts.test.mjs scripts/local-embeddings-runtime-smoke.test.mjs scripts/release-readiness.test.mjs scripts/publish-public-npm-package.test.mjs scripts/published-package-smoke.test.mjs scripts/release-workflow.test.mjs scripts/examples-docs.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 2: Run workspace type and package checks**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm run type-check
|
|
pnpm run artifacts:check
|
|
```
|
|
|
|
Expected: PASS. The artifact build creates
|
|
`dist/artifacts/npm/kaelio-ktx-0.1.0.tgz`.
|
|
|
|
- [ ] **Step 3: Run release readiness and dry-run publish**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm run release:readiness
|
|
pnpm run release:npm-publish
|
|
```
|
|
|
|
Expected: PASS. `release:readiness` prints `KTX release mode:
|
|
npm-public-release-ready`, and `release:npm-publish` prints `Dry-run verified
|
|
@kaelio/ktx@0.1.0 with tag latest`.
|
|
|
|
- [ ] **Step 4: Run pre-commit for changed files**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pre-commit run --files scripts/build-public-npm-package.mjs scripts/build-public-npm-package.test.mjs scripts/package-artifacts.mjs scripts/package-artifacts.test.mjs scripts/local-embeddings-runtime-smoke.test.mjs scripts/release-readiness.mjs scripts/release-readiness.test.mjs scripts/publish-public-npm-package.mjs scripts/publish-public-npm-package.test.mjs scripts/release-workflow.test.mjs release-policy.json package.json README.md .github/workflows/release.yml
|
|
```
|
|
|
|
Expected: PASS. If pre-commit is unavailable because the local `uv` version or
|
|
pre-commit environment is missing, report that explicitly and keep the script
|
|
tests, `pnpm run type-check`, `pnpm run artifacts:check`, `pnpm run
|
|
release:readiness`, and `pnpm run release:npm-publish` results as the closest
|
|
checks.
|
|
|
|
- [ ] **Step 5: Confirm the worktree is clean**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git status --short
|
|
```
|
|
|
|
Expected: no output. If there are uncommitted tracked changes, inspect them and
|
|
commit only files from this plan with the exact task commit commands above.
|
|
|
|
## Success criteria
|
|
|
|
- `@kaelio/ktx` artifact metadata and tarball names use version `0.1.0`.
|
|
- `release-policy.json` encodes `npm-public-release-ready`,
|
|
`npm.publish: true`, and `python.publish: false`.
|
|
- `pnpm run release:npm-publish` performs a dry-run by default.
|
|
- Live npm publishing requires `pnpm run release:npm-publish -- --publish` or
|
|
the manual `KTX Release` workflow with `publish_live` enabled.
|
|
- Published-package smoke remains the post-publication proof for `npx
|
|
@kaelio/ktx`, local `npx ktx`, and global `ktx` invocation modes.
|
|
- No Python package publication is added for this release.
|
|
|
|
## Self-review
|
|
|
|
- Spec coverage: this plan covers the remaining public npm handoff gap while
|
|
preserving the bundled Python wheel model and single npm package surface.
|
|
- Placeholder scan: no open placeholders or deferred implementation notes are
|
|
present.
|
|
- Type consistency: the release mode name is consistently
|
|
`npm-public-release-ready`; the public npm version is consistently `0.1.0`;
|
|
the publish script consumes the `npmPublish` report shape produced by
|
|
`release-readiness.mjs`.
|