mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
fix(scripts): make package artifacts pnpm launch work on Windows
Fix Windows package artifact script invocation under pnpm.
This commit is contained in:
parent
56985b7e09
commit
2a6fb19ba4
2 changed files with 226 additions and 125 deletions
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'/);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue