fix(scripts): make package artifacts pnpm launch work on Windows

Fix Windows package artifact script invocation under pnpm.
This commit is contained in:
ARYAN 2026-05-26 03:16:53 -07:00 committed by GitHub
parent 56985b7e09
commit 2a6fb19ba4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 226 additions and 125 deletions

View file

@ -28,6 +28,20 @@ export const NPM_ARTIFACT_PACKAGES = [{ name: PUBLIC_NPM_PACKAGE_NAME, packageRo
export const CLI_PYTHON_ASSET_MANIFEST = 'manifest.json'; export const CLI_PYTHON_ASSET_MANIFEST = 'manifest.json';
function pnpmCommand(args) {
if (process.platform === 'win32') {
return {
command: 'cmd.exe',
args: ['/d', '/s', '/c', 'pnpm', ...args],
};
}
return {
command: 'pnpm',
args,
};
}
function scriptRootDir() { function scriptRootDir() {
return resolve(dirname(fileURLToPath(import.meta.url)), '..'); return resolve(dirname(fileURLToPath(import.meta.url)), '..');
} }
@ -70,8 +84,7 @@ export function packageArtifactLayout(rootDir = scriptRootDir(), version = publi
export function buildArtifactCommands(layout) { export function buildArtifactCommands(layout) {
return [ return [
{ {
command: 'pnpm', ...pnpmCommand(['--filter', PUBLIC_NPM_PACKAGE_NAME, 'run', 'build']),
args: ['--filter', PUBLIC_NPM_PACKAGE_NAME, 'run', 'build'],
cwd: layout.rootDir, cwd: layout.rootDir,
}, },
{ {
@ -80,8 +93,7 @@ export function buildArtifactCommands(layout) {
cwd: layout.rootDir, cwd: layout.rootDir,
}, },
{ {
command: 'pnpm', ...pnpmCommand(['pack', '--out', layout.cliTarball]),
args: ['pack', '--out', layout.cliTarball],
cwd: join(layout.rootDir, 'packages', 'cli'), cwd: join(layout.rootDir, 'packages', 'cli'),
}, },
]; ];
@ -460,11 +472,19 @@ import { createRequire } from 'node:module';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
import { join } from 'node:path'; import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite'; import { DatabaseSync } from 'node:sqlite';
import { setTimeout as delay } from 'node:timers/promises';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
const require = createRequire(import.meta.url); const require = createRequire(import.meta.url);
function pnpmCommand(args) {
if (process.platform === 'win32') {
return { command: 'cmd.exe', args: ['/d', '/s', '/c', 'pnpm', ...args] };
}
return { command: 'pnpm', args };
}
async function run(command, args, options = {}) { async function run(command, args, options = {}) {
process.stdout.write('$ ' + command + ' ' + args.join(' ') + '\\n'); process.stdout.write('$ ' + command + ' ' + args.join(' ') + '\\n');
try { try {
@ -551,6 +571,21 @@ function requireIncludes(values, expected, label) {
assert.ok(values.includes(expected), label + ' did not include ' + expected + ': ' + values.join(', ')); assert.ok(values.includes(expected), label + ' did not include ' + expected + ': ' + values.join(', '));
} }
async function rmWithRetry(path) {
for (let attempt = 0; ; attempt += 1) {
try {
await rm(path, { recursive: true, force: true });
return;
} catch (error) {
const code = typeof error?.code === 'string' ? error.code : '';
if (attempt >= 4 || !['EBUSY', 'ENOTEMPTY', 'EPERM'].includes(code)) {
throw error;
}
await delay(500);
}
}
}
async function writeSqliteWarehouse(projectDir) { async function writeSqliteWarehouse(projectDir) {
const database = new DatabaseSync(join(projectDir, 'warehouse.db')); const database = new DatabaseSync(join(projectDir, 'warehouse.db'));
try { try {
@ -575,50 +610,58 @@ let daemonStarted = false;
try { try {
const projectDir = join(root, 'project'); const projectDir = join(root, 'project');
const version = await run('pnpm', ['exec', 'ktx', '--version']); const version = await run(...Object.values(pnpmCommand(['exec', 'ktx', '--version'])));
requireSuccess('ktx public package version', version); requireSuccess('ktx public package version', version);
requireOutput('ktx public package version', version, await installedPackageVersionPattern()); requireOutput('ktx public package version', version, await installedPackageVersionPattern());
const runtimeStatusBefore = parseJsonResultWithExitCode( const runtimeStatusBefore = parseJsonResultWithExitCode(
'ktx admin runtime status missing', 'ktx admin runtime status missing',
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status', '--json']), await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'status', '--json']))),
1, 1,
); );
assert.equal(runtimeStatusBefore.kind, 'missing'); assert.equal(runtimeStatusBefore.kind, 'missing');
assert.equal(runtimeStatusBefore.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT); assert.equal(runtimeStatusBefore.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT);
process.stdout.write('ktx managed runtime starts missing in isolated root\\n'); process.stdout.write('ktx managed runtime starts missing in isolated root\\n');
const init = await run('pnpm', [ const init = await run(
'exec', ...Object.values(
'ktx', pnpmCommand([
'setup', 'exec',
'--project-dir', 'ktx',
projectDir, 'setup',
'--no-input', '--project-dir',
'--yes', projectDir,
'--skip-llm', '--no-input',
'--skip-embeddings', '--yes',
'--skip-databases', '--skip-llm',
'--skip-sources', '--skip-embeddings',
'--skip-agents', '--skip-databases',
]); '--skip-sources',
'--skip-agents',
]),
),
);
requireSuccess('ktx setup', init); requireSuccess('ktx setup', init);
const emptyProjectDir = join(root, 'empty-project'); const emptyProjectDir = join(root, 'empty-project');
const emptyInit = await run('pnpm', [ const emptyInit = await run(
'exec', ...Object.values(
'ktx', pnpmCommand([
'setup', 'exec',
'--project-dir', 'ktx',
emptyProjectDir, 'setup',
'--no-input', '--project-dir',
'--yes', emptyProjectDir,
'--skip-llm', '--no-input',
'--skip-embeddings', '--yes',
'--skip-databases', '--skip-llm',
'--skip-sources', '--skip-embeddings',
'--skip-agents', '--skip-databases',
]); '--skip-sources',
'--skip-agents',
]),
),
);
requireSuccess('ktx setup empty project', emptyInit); requireSuccess('ktx setup empty project', emptyInit);
await writeFile( await writeFile(
join(projectDir, 'ktx.yaml'), join(projectDir, 'ktx.yaml'),
@ -658,17 +701,21 @@ try {
'utf-8', 'utf-8',
); );
const wikiSearch = await run('pnpm', [ const wikiSearch = await run(
'exec', ...Object.values(
'ktx', pnpmCommand([
'wiki', 'exec',
'revenue', 'ktx',
'--json', 'wiki',
'--limit', 'revenue',
'5', '--json',
'--project-dir', '--limit',
projectDir, '5',
]); '--project-dir',
projectDir,
]),
),
);
const wikiSearchJson = parseJsonResult('ktx wiki search', wikiSearch); const wikiSearchJson = parseJsonResult('ktx wiki search', wikiSearch);
assert.equal(wikiSearchJson.kind, 'list'); assert.equal(wikiSearchJson.kind, 'list');
assert.equal(wikiSearchJson.data.items.length, 1); assert.equal(wikiSearchJson.data.items.length, 1);
@ -700,17 +747,21 @@ try {
await mkdir(join(projectDir, 'semantic-layer', 'warehouse'), { recursive: true }); await mkdir(join(projectDir, 'semantic-layer', 'warehouse'), { recursive: true });
await writeFile(join(projectDir, 'semantic-layer', 'warehouse', 'orders.yaml'), slYaml, 'utf-8'); await writeFile(join(projectDir, 'semantic-layer', 'warehouse', 'orders.yaml'), slYaml, 'utf-8');
const slSearch = await run('pnpm', [ const slSearch = await run(
'exec', ...Object.values(
'ktx', pnpmCommand([
'sl', 'exec',
'orders', 'ktx',
'--json', 'sl',
'--connection-id', 'orders',
'warehouse', '--json',
'--project-dir', '--connection-id',
projectDir, 'warehouse',
]); '--project-dir',
projectDir,
]),
),
);
const slSearchJson = parseJsonResult('ktx sl search', slSearch); const slSearchJson = parseJsonResult('ktx sl search', slSearch);
assert.equal(slSearchJson.kind, 'list'); assert.equal(slSearchJson.kind, 'list');
assert.equal(slSearchJson.data.items.length, 1); assert.equal(slSearchJson.data.items.length, 1);
@ -720,17 +771,25 @@ try {
requireIncludes(slSearchJson.data.items[0].matchReasons, 'lexical', 'sl search match reasons'); requireIncludes(slSearchJson.data.items[0].matchReasons, 'lexical', 'sl search match reasons');
process.stdout.write('ktx sl search hybrid metadata verified\\n'); process.stdout.write('ktx sl search hybrid metadata verified\\n');
const slQuery = await run('pnpm', ['exec', 'ktx', 'sl', 'query', const slQuery = await run(
'--connection-id', ...Object.values(
'warehouse', pnpmCommand([
'--measure', 'exec',
'orders.order_count', 'ktx',
'--format', 'sl',
'json', 'query',
'--yes', '--connection-id',
'--project-dir', 'warehouse',
projectDir, '--measure',
]); 'orders.order_count',
'--format',
'json',
'--yes',
'--project-dir',
projectDir,
]),
),
);
requireSuccessWithStderr( requireSuccessWithStderr(
'ktx sl query first managed runtime install', 'ktx sl query first managed runtime install',
slQuery, slQuery,
@ -741,27 +800,35 @@ try {
const runtimeStatusAfter = parseJsonResult( const runtimeStatusAfter = parseJsonResult(
'ktx admin runtime status ready', 'ktx admin runtime status ready',
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status', '--json']), await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'status', '--json']))),
); );
assert.equal(runtimeStatusAfter.kind, 'ready'); assert.equal(runtimeStatusAfter.kind, 'ready');
assert.deepEqual(runtimeStatusAfter.manifest.features, ['core']); assert.deepEqual(runtimeStatusAfter.manifest.features, ['core']);
assert.equal(runtimeStatusAfter.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT); assert.equal(runtimeStatusAfter.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT);
process.stdout.write('ktx managed runtime lazy install verified\\n'); process.stdout.write('ktx managed runtime lazy install verified\\n');
const sqliteSlQuery = await run('pnpm', ['exec', 'ktx', 'sl', 'query', const sqliteSlQuery = await run(
'--connection-id', ...Object.values(
'warehouse', pnpmCommand([
'--measure', 'exec',
'orders.order_count', 'ktx',
'--format', 'sl',
'json', 'query',
'--execute', '--connection-id',
'--max-rows', 'warehouse',
'100', '--measure',
'--yes', 'orders.order_count',
'--project-dir', '--format',
projectDir, 'json',
]); '--execute',
'--max-rows',
'100',
'--yes',
'--project-dir',
projectDir,
]),
),
);
requireSuccess('ktx sl query sqlite execute', sqliteSlQuery); requireSuccess('ktx sl query sqlite execute', sqliteSlQuery);
requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/); requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/);
requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/); requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/);
@ -769,36 +836,35 @@ try {
requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/); requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/);
process.stdout.write('ktx sl query sqlite execute verified\\n'); process.stdout.write('ktx sl query sqlite execute verified\\n');
const runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status']); const runtimeDoctor = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'status'])));
requireSuccess('ktx admin runtime status', runtimeDoctor); requireSuccess('ktx admin runtime status', runtimeDoctor);
requireOutput('ktx admin runtime status', runtimeDoctor, /KTX Python runtime/); requireOutput('ktx admin runtime status', runtimeDoctor, /KTX Python runtime/);
requireOutput('ktx admin runtime status', runtimeDoctor, /status: ready/); requireOutput('ktx admin runtime status', runtimeDoctor, /status: ready/);
process.stdout.write('ktx admin runtime status verified\\n'); process.stdout.write('ktx admin runtime status verified\\n');
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']); const runtimeStart = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'start'])));
requireSuccess('ktx admin runtime start', runtimeStart); requireSuccess('ktx admin runtime start', runtimeStart);
daemonStarted = true; daemonStarted = true;
requireOutput('ktx admin runtime start', runtimeStart, /Started KTX 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, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
requireOutput('ktx admin runtime start', runtimeStart, /features: core/); requireOutput('ktx admin runtime start', runtimeStart, /features: core/);
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']); const runtimeStartReuse = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'start'])));
requireSuccess('ktx admin runtime start reuse', runtimeStartReuse); requireSuccess('ktx admin runtime start reuse', runtimeStartReuse);
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /Using existing KTX daemon/); requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /Using existing KTX daemon/);
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /features: core/); requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /features: core/);
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop']); const runtimeStop = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'stop'])));
requireSuccess('ktx admin runtime stop', runtimeStop); requireSuccess('ktx admin runtime stop', runtimeStop);
daemonStarted = false; daemonStarted = false;
requireOutput('ktx admin runtime stop', runtimeStop, /Stopped KTX daemon/); requireOutput('ktx admin runtime stop', runtimeStop, /Stopped KTX daemon/);
process.stdout.write('ktx admin runtime daemon lifecycle verified\\n'); process.stdout.write('ktx admin runtime daemon lifecycle verified\\n');
const structuralScan = await run('pnpm', ['exec', 'ktx', 'ingest', 'warehouse', const structuralScan = await run(
'--project-dir', ...Object.values(
projectDir, pnpmCommand(['exec', 'ktx', 'ingest', 'warehouse', '--project-dir', projectDir, '--fast', '--no-input']),
'--fast', ),
'--no-input', );
]);
requireSuccessWithProjectStderr('ktx ingest fast', structuralScan, projectDir); requireSuccessWithProjectStderr('ktx ingest fast', structuralScan, projectDir);
requireOutput('ktx ingest fast', structuralScan, /Ingest finished/); requireOutput('ktx ingest fast', structuralScan, /Ingest finished/);
requireOutput('ktx ingest fast', structuralScan, /Database schema/); requireOutput('ktx ingest fast', structuralScan, /Database schema/);
@ -806,12 +872,11 @@ try {
await access(join(projectDir, 'semantic-layer', 'warehouse', '_schema', 'public.yaml')); await access(join(projectDir, 'semantic-layer', 'warehouse', '_schema', 'public.yaml'));
process.stdout.write('ktx ingest fast verified\\n'); process.stdout.write('ktx ingest fast verified\\n');
const enrichedScan = await run('pnpm', ['exec', 'ktx', 'ingest', 'warehouse', const enrichedScan = await run(
'--project-dir', ...Object.values(
projectDir, pnpmCommand(['exec', 'ktx', 'ingest', 'warehouse', '--project-dir', projectDir, '--deep', '--no-input']),
'--deep', ),
'--no-input', );
]);
requireExitCodeWithProjectStderr('ktx ingest deep readiness guard', enrichedScan, projectDir, 1); requireExitCodeWithProjectStderr('ktx ingest deep readiness guard', enrichedScan, projectDir, 1);
requireOutput('ktx ingest deep readiness guard', enrichedScan, /Ingest finished with partial failures/); requireOutput('ktx ingest deep readiness guard', enrichedScan, /Ingest finished with partial failures/);
requireOutput('ktx ingest deep readiness guard', enrichedScan, /requires deep ingest readiness/); requireOutput('ktx ingest deep readiness guard', enrichedScan, /requires deep ingest readiness/);
@ -821,14 +886,15 @@ try {
process.stdout.write('ktx ingest state verified\\n'); process.stdout.write('ktx ingest state verified\\n');
} finally { } finally {
if (daemonStarted) { if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop']); await run(...Object.values(pnpmCommand(['exec', 'ktx', 'admin', 'runtime', 'stop'])));
await delay(500);
} }
if (previousRuntimeRoot === undefined) { if (previousRuntimeRoot === undefined) {
delete process.env.KTX_RUNTIME_ROOT; delete process.env.KTX_RUNTIME_ROOT;
} else { } else {
process.env.KTX_RUNTIME_ROOT = previousRuntimeRoot; process.env.KTX_RUNTIME_ROOT = previousRuntimeRoot;
} }
await rm(root, { recursive: true, force: true }); await rmWithRetry(root);
} }
`; `;
} }
@ -844,6 +910,13 @@ import { promisify } from 'node:util';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
function pnpmCommand(args) {
if (process.platform === 'win32') {
return { command: 'cmd.exe', args: ['/d', '/s', '/c', 'pnpm', ...args] };
}
return { command: 'pnpm', args };
}
async function run(command, args, options = {}) { async function run(command, args, options = {}) {
process.stdout.write('$ ' + command + ' ' + args.join(' ') + '\\n'); process.stdout.write('$ ' + command + ' ' + args.join(' ') + '\\n');
try { try {
@ -880,17 +953,17 @@ try {
const packageJson = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf8')); const packageJson = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf8'));
assert.deepEqual(Object.keys(packageJson.dependencies), ['@kaelio/ktx']); assert.deepEqual(Object.keys(packageJson.dependencies), ['@kaelio/ktx']);
const help = await run('pnpm', ['exec', 'ktx', '--help']); const help = await run(...Object.values(pnpmCommand(['exec', 'ktx', '--help'])));
requireSuccess('ktx --help', help); requireSuccess('ktx --help', help);
requireStdout('ktx --help', help, /Usage: ktx/); requireStdout('ktx --help', help, /Usage: ktx/);
requireStdout('ktx --help', help, /setup/); requireStdout('ktx --help', help, /setup/);
const setupHelp = await run('pnpm', ['exec', 'ktx', 'setup', '--help']); const setupHelp = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'setup', '--help'])));
requireSuccess('ktx setup --help', setupHelp); requireSuccess('ktx setup --help', setupHelp);
requireStdout('ktx setup --help', setupHelp, /Usage: ktx setup/); requireStdout('ktx setup --help', setupHelp, /Usage: ktx setup/);
requireStdout('ktx setup --help', setupHelp, /--no-input/); requireStdout('ktx setup --help', setupHelp, /--no-input/);
const doctor = await run('pnpm', ['exec', 'ktx', 'status', '--verbose', '--no-input']); const doctor = await run(...Object.values(pnpmCommand(['exec', 'ktx', 'status', '--verbose', '--no-input'])));
assert.ok([0, 1].includes(doctor.code), 'ktx status setup exit code must be 0 or 1'); assert.ok([0, 1].includes(doctor.code), 'ktx status setup exit code must be 0 or 1');
requireStdout('ktx status setup', doctor, /KTX status/); requireStdout('ktx status setup', doctor, /KTX status/);
requireStdout('ktx status setup', doctor, /No project here yet\\./); requireStdout('ktx status setup', doctor, /No project here yet\\./);
@ -949,10 +1022,19 @@ async function verifyNpmArtifacts(layout, tmpRoot) {
await writeFile(join(projectDir, 'verify-installed-cli.mjs'), npmRuntimeSmokeSource()); await writeFile(join(projectDir, 'verify-installed-cli.mjs'), npmRuntimeSmokeSource());
await writeFile(join(projectDir, 'verify-installed-cli-commands.mjs'), npmCliSmokeSource()); await writeFile(join(projectDir, 'verify-installed-cli-commands.mjs'), npmCliSmokeSource());
await runCommand('pnpm', ['install'], { cwd: projectDir }); {
await runCommand('pnpm', ['rebuild', 'better-sqlite3'], { cwd: projectDir }); const pnpmInstall = pnpmCommand(['install']);
await runCommand(pnpmInstall.command, pnpmInstall.args, { cwd: projectDir });
}
{
const pnpmRebuild = pnpmCommand(['rebuild', 'better-sqlite3']);
await runCommand(pnpmRebuild.command, pnpmRebuild.args, { cwd: projectDir });
}
await runCommand('node', ['verify-npm.mjs'], { cwd: projectDir }); await runCommand('node', ['verify-npm.mjs'], { cwd: projectDir });
await runCommand('pnpm', ['exec', 'ktx', '--version'], { cwd: projectDir }); {
const pnpmExecVersion = pnpmCommand(['exec', 'ktx', '--version']);
await runCommand(pnpmExecVersion.command, pnpmExecVersion.args, { cwd: projectDir });
}
await runCommand('node', ['verify-installed-cli.mjs'], { cwd: projectDir }); await runCommand('node', ['verify-installed-cli.mjs'], { cwd: projectDir });
await runCommand('node', ['verify-installed-cli-commands.mjs'], { cwd: projectDir }); await runCommand('node', ['verify-installed-cli-commands.mjs'], { cwd: projectDir });
} }
@ -968,7 +1050,10 @@ async function verifyNpmCliArtifacts(layout, tmpRoot) {
await writeFile(join(projectDir, 'pnpm-workspace.yaml'), npmSmokePnpmWorkspaceYaml()); await writeFile(join(projectDir, 'pnpm-workspace.yaml'), npmSmokePnpmWorkspaceYaml());
await writeFile(join(projectDir, 'verify-installed-cli-commands.mjs'), npmCliSmokeSource()); await writeFile(join(projectDir, 'verify-installed-cli-commands.mjs'), npmCliSmokeSource());
await runCommand('pnpm', ['install'], { cwd: projectDir }); {
const pnpmInstall = pnpmCommand(['install']);
await runCommand(pnpmInstall.command, pnpmInstall.args, { cwd: projectDir });
}
await runCommand('node', ['verify-installed-cli-commands.mjs'], { cwd: projectDir }); await runCommand('node', ['verify-installed-cli-commands.mjs'], { cwd: projectDir });
} }

View file

@ -97,12 +97,12 @@ describe('packageArtifactLayout', () => {
it('uses stable artifact paths under ktx/dist/artifacts', () => { it('uses stable artifact paths under ktx/dist/artifacts', () => {
const layout = packageArtifactLayout('/repo/ktx', PUBLIC_NPM_PACKAGE_VERSION); const layout = packageArtifactLayout('/repo/ktx', PUBLIC_NPM_PACKAGE_VERSION);
assert.equal(layout.artifactDir, '/repo/ktx/dist/artifacts'); assert.equal(layout.artifactDir, join('/repo/ktx', 'dist', 'artifacts'));
assert.equal(layout.npmDir, '/repo/ktx/dist/artifacts/npm'); assert.equal(layout.npmDir, join('/repo/ktx', 'dist', 'artifacts', 'npm'));
assert.equal(layout.pythonDir, '/repo/ktx/dist/artifacts/python'); assert.equal(layout.pythonDir, join('/repo/ktx', 'dist', 'artifacts', 'python'));
assert.equal( assert.equal(
layout.cliTarball, layout.cliTarball,
`/repo/ktx/dist/artifacts/npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`, join('/repo/ktx', 'dist', 'artifacts', 'npm', `kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`),
); );
assert.deepEqual(Object.keys(layout.npmTarballs), ['@kaelio/ktx']); assert.deepEqual(Object.keys(layout.npmTarballs), ['@kaelio/ktx']);
}); });
@ -112,17 +112,21 @@ describe('buildArtifactCommands', () => {
it('builds the CLI package, then the runtime wheel, then packs the npm tarball directly', () => { it('builds the CLI package, then the runtime wheel, then packs the npm tarball directly', () => {
const layout = packageArtifactLayout('/repo/ktx', PUBLIC_NPM_PACKAGE_VERSION); const layout = packageArtifactLayout('/repo/ktx', PUBLIC_NPM_PACKAGE_VERSION);
const commands = buildArtifactCommands(layout); const commands = buildArtifactCommands(layout);
const expectedBuildCommand =
process.platform === 'win32'
? ['cmd.exe', ['/d', '/s', '/c', 'pnpm', '--filter', '@kaelio/ktx', 'run', 'build'], layout.rootDir]
: ['pnpm', ['--filter', '@kaelio/ktx', 'run', 'build'], layout.rootDir];
const expectedPackCommand =
process.platform === 'win32'
? ['cmd.exe', ['/d', '/s', '/c', 'pnpm', 'pack', '--out', layout.cliTarball], join('/repo/ktx', 'packages', 'cli')]
: ['pnpm', ['pack', '--out', layout.cliTarball], join('/repo/ktx', 'packages', 'cli')];
assert.deepEqual( assert.deepEqual(
commands.map((command) => [command.command, command.args, command.cwd]), commands.map((command) => [command.command, command.args, command.cwd]),
[ [
['pnpm', ['--filter', '@kaelio/ktx', 'run', 'build'], '/repo/ktx'], expectedBuildCommand,
[process.execPath, ['scripts/build-python-runtime-wheel.mjs'], '/repo/ktx'], [process.execPath, ['scripts/build-python-runtime-wheel.mjs'], layout.rootDir],
[ expectedPackCommand,
'pnpm',
['pack', '--out', `/repo/ktx/dist/artifacts/npm/kaelio-ktx-${PUBLIC_NPM_PACKAGE_VERSION}.tgz`],
'/repo/ktx/packages/cli',
],
], ],
); );
}); });
@ -476,18 +480,27 @@ describe('verification snippets', () => {
it('runs installed CLI commands through the public package runtime', () => { it('runs installed CLI commands through the public package runtime', () => {
const source = npmRuntimeSmokeSource(); const source = npmRuntimeSmokeSource();
assert.match(source, /function pnpmCommand\(args\)/);
assert.match(source, /process\.platform === 'win32'/);
assert.match(source, /command: 'cmd\.exe'/);
assert.match(source, /args: \['\/d', '\/s', '\/c', 'pnpm', \.\.\.args\]/);
assert.match(source, /import \{ setTimeout as delay \} from 'node:timers\/promises';/);
assert.match(source, /async function rmWithRetry\(path\)/);
assert.match(source, /await delay\(500\)/);
assert.match(source, /await rmWithRetry\(root\)/);
assert.match(source, /ktx public package version/); assert.match(source, /ktx public package version/);
assert.match(source, /installedPackageVersionPattern/); assert.match(source, /installedPackageVersionPattern/);
assert.doesNotMatch(source, /@kaelio\\\/ktx 0\\\.1\\\.0/); assert.doesNotMatch(source, /@kaelio\\\/ktx 0\\\.1\\\.0/);
assert.match(source, /'ktx', 'sl', 'query'/); assert.match(source, /pnpmCommand\(\[\s*'exec',\s*'ktx',\s*'sl',\s*'query'/);
assert.doesNotMatch(source, /@ktx\/context/); assert.doesNotMatch(source, /@ktx\/context/);
assert.doesNotMatch(source, /@modelcontextprotocol/); assert.doesNotMatch(source, /@modelcontextprotocol/);
assert.doesNotMatch(source, /startSemanticDaemon/); assert.doesNotMatch(source, /startSemanticDaemon/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'setup'/); assert.doesNotMatch(source, /run\('pnpm',/);
assert.match(source, /pnpmCommand\(\[\s*'exec',\s*'ktx',\s*'setup'/);
assert.match(source, /wiki', 'global', 'revenue\.md'/); assert.match(source, /wiki', 'global', 'revenue\.md'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'wiki',\s*'revenue'/); assert.match(source, /pnpmCommand\(\[\s*'exec',\s*'ktx',\s*'wiki',\s*'revenue'/);
assert.match(source, /semantic-layer', 'warehouse', 'orders\.yaml'/); assert.match(source, /semantic-layer', 'warehouse', 'orders\.yaml'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'sl',\s*'orders'/); assert.match(source, /pnpmCommand\(\[\s*'exec',\s*'ktx',\s*'sl',\s*'orders'/);
assert.match(source, /orders\.order_count/); assert.match(source, /orders\.order_count/);
assert.match(source, /node:sqlite/); assert.match(source, /node:sqlite/);
assert.match(source, /driver: sqlite/); assert.match(source, /driver: sqlite/);
@ -516,7 +529,7 @@ describe('verification snippets', () => {
assert.match(source, /ktx admin runtime stop/); assert.match(source, /ktx admin runtime stop/);
assert.doesNotMatch(source, /ktx admin runtime prune/); assert.doesNotMatch(source, /ktx admin runtime prune/);
assert.doesNotMatch(source, /staleRuntimeDir/); assert.doesNotMatch(source, /staleRuntimeDir/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'ingest',\s*'warehouse'/); assert.match(source, /pnpmCommand\(\['exec', 'ktx', 'ingest', 'warehouse'/);
assert.match(source, /'--deep'/); assert.match(source, /'--deep'/);
assert.doesNotMatch(source, /'--enrich'/); assert.doesNotMatch(source, /'--enrich'/);
assert.match(source, /ktx ingest fast verified/); assert.match(source, /ktx ingest fast verified/);
@ -534,8 +547,11 @@ describe('verification snippets', () => {
it('exercises supported public package CLI commands', () => { it('exercises supported public package CLI commands', () => {
const source = npmCliSmokeSource(); const source = npmCliSmokeSource();
assert.match(source, /pnpm', \['exec', 'ktx', '--help'\]/); assert.match(source, /function pnpmCommand\(args\)/);
assert.match(source, /pnpm', \['exec', 'ktx', 'setup', '--help'\]/); assert.match(source, /process\.platform === 'win32'/);
assert.doesNotMatch(source, /run\('pnpm',/);
assert.match(source, /pnpmCommand\(\['exec', 'ktx', '--help'\]\)/);
assert.match(source, /pnpmCommand\(\['exec', 'ktx', 'setup', '--help'\]\)/);
assert.match(source, /Usage: ktx setup/); assert.match(source, /Usage: ktx setup/);
assert.doesNotMatch(source, new RegExp(["'demo'", "'--mode'", "'deterministic'"].join(', '))); assert.doesNotMatch(source, new RegExp(["'demo'", "'--mode'", "'deterministic'"].join(', ')));
assert.match(source, /'status', '--verbose', '--no-input'/); assert.match(source, /'status', '--verbose', '--no-input'/);