fix(cli): build runtime assets during dev setup (#121)

This commit is contained in:
Andrey Avtomonov 2026-05-17 01:04:44 +02:00 committed by GitHub
parent c7e6b5001d
commit de72a10ffb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 85 additions and 7 deletions

View file

@ -11,6 +11,7 @@
}, },
"scripts": { "scripts": {
"artifacts:build": "node scripts/package-artifacts.mjs build", "artifacts:build": "node scripts/package-artifacts.mjs build",
"artifacts:build-runtime": "node scripts/package-artifacts.mjs build-runtime",
"artifacts:check": "node scripts/package-artifacts.mjs check", "artifacts:check": "node scripts/package-artifacts.mjs check",
"artifacts:live-db-smoke": "node scripts/installed-live-database-smoke.mjs", "artifacts:live-db-smoke": "node scripts/installed-live-database-smoke.mjs",
"artifacts:verify": "node scripts/package-artifacts.mjs verify", "artifacts:verify": "node scripts/package-artifacts.mjs verify",

View file

@ -829,6 +829,43 @@ describe('runKtxPublicIngest', () => {
expect(io.stdout()).not.toContain('historic-sql'); expect(io.stdout()).not.toContain('historic-sql');
}); });
it('prints the runtime artifact build hint for missing query-history runtime assets', async () => {
const io = makeIo();
const project = deepReadyProject({
warehouse: { driver: 'postgres', context: { depth: 'deep' } },
});
const runScan = vi.fn(async () => 0);
const runIngest = vi.fn(async (_args, ingestIo) => {
ingestIo.stderr.write('Missing bundled Python runtime manifest: /repo/packages/cli/assets/python/manifest.json\n');
ingestIo.stderr.write('In a source checkout, build the local runtime assets with: pnpm run artifacts:build\n');
ingestIo.stderr.write('Then retry the runtime-backed KTX command.\n');
return 1;
});
await expect(
runKtxPublicIngest(
{
command: 'run',
projectDir: '/tmp/project',
targetConnectionId: 'warehouse',
all: false,
json: false,
inputMode: 'disabled',
queryHistory: 'enabled',
},
io.io,
{ loadProject: vi.fn(async () => project), runScan, runIngest },
),
).resolves.toBe(1);
expect(io.stdout()).toContain('Missing bundled Python runtime manifest');
expect(io.stdout()).toContain(
'In a source checkout, build the local runtime assets with: pnpm run artifacts:build',
);
expect(io.stdout()).toContain('Retry: ktx ingest warehouse --project-dir /tmp/project --deep --query-history');
expect(io.stdout()).not.toContain('Then retry the runtime-backed KTX command');
});
it('fails deep-readiness targets before work starts while continuing independent --all targets', async () => { it('fails deep-readiness targets before work starts while continuing independent --all targets', async () => {
const io = makeIo(); const io = makeIo();
const project = projectWithConnections({ const project = projectWithConnections({

View file

@ -663,16 +663,35 @@ function createCapturedPublicIngestIo(): CapturedPublicIngestIo {
const INTERNAL_STATUS_LINE_RE = const INTERNAL_STATUS_LINE_RE =
/^(Report|Run|Job|Status|Adapter|Connection|Sync|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/; /^(Report|Run|Job|Status|Adapter|Connection|Sync|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
const ACTIONABLE_FAILURE_LINE_RE =
/^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX managed daemon|Error:|Failed\b|Could not\b|Cannot\b)/;
const RUNTIME_BACKED_RETRY_LINE_RE = /^Then retry the runtime-backed KTX command\.?$/;
function firstCapturedFailureLine(output: string): string | undefined { function capturedFailureMessage(output: string): string | undefined {
return output const lines = output
.split(/\r?\n/) .split(/\r?\n/)
.map((line) => line.trim()) .map((line) => line.trim())
.filter((line) => line.length > 0) .filter((line) => line.length > 0)
.filter((line) => !line.startsWith('KTX scan completed')) .filter((line) => !line.startsWith('KTX scan completed'))
.filter((line) => !INTERNAL_STATUS_LINE_RE.test(line)) .filter((line) => !INTERNAL_STATUS_LINE_RE.test(line))
.map(publicIngestOutputLine) .map(publicIngestOutputLine);
.find((line) => line.length > 0);
const actionableIndex = lines.findIndex((line) => ACTIONABLE_FAILURE_LINE_RE.test(line));
if (actionableIndex < 0) {
return lines.find((line) => line.length > 0);
}
const firstLine = lines[actionableIndex];
if (!firstLine?.startsWith('Missing bundled Python runtime manifest')) {
return firstLine;
}
const followupLines = lines
.slice(actionableIndex + 1)
.filter((line) => !RUNTIME_BACKED_RETRY_LINE_RE.test(line))
.filter((line) => !/\bRetry:\s/.test(line))
.filter((line) => line.startsWith('In a source checkout, build the local runtime assets with:'));
return [firstLine, ...followupLines].join('\n');
} }
export async function executePublicIngestTarget( export async function executePublicIngestTarget(
@ -737,7 +756,7 @@ export async function executePublicIngestTarget(
args, args,
'failed', 'failed',
'database-schema', 'database-schema',
capturedScanIo ? firstCapturedFailureLine(capturedScanIo.capturedOutput()) : undefined, capturedScanIo ? capturedFailureMessage(capturedScanIo.capturedOutput()) : undefined,
); );
} }
deps.onPhaseEnd?.('database-schema', 'done'); deps.onPhaseEnd?.('database-schema', 'done');
@ -779,7 +798,7 @@ export async function executePublicIngestTarget(
args, args,
'failed', 'failed',
'query-history', 'query-history',
capturedIngestIo ? firstCapturedFailureLine(capturedIngestIo.capturedOutput()) : undefined, capturedIngestIo ? capturedFailureMessage(capturedIngestIo.capturedOutput()) : undefined,
); );
} }
deps.onPhaseEnd?.('query-history', 'done'); deps.onPhaseEnd?.('query-history', 'done');
@ -819,7 +838,7 @@ export async function executePublicIngestTarget(
args, args,
exitCode === 0 ? 'done' : 'failed', exitCode === 0 ? 'done' : 'failed',
'source-ingest', 'source-ingest',
capturedIngestIo ? firstCapturedFailureLine(capturedIngestIo.capturedOutput()) : undefined, capturedIngestIo ? capturedFailureMessage(capturedIngestIo.capturedOutput()) : undefined,
); );
} }

View file

@ -942,6 +942,16 @@ async function buildArtifacts(layout) {
await assertPathExists(artifactManifestPath(layout), 'artifact manifest'); await assertPathExists(artifactManifestPath(layout), 'artifact manifest');
} }
async function buildRuntimeWheelAssets(layout) {
await rm(layout.pythonDir, { recursive: true, force: true });
await mkdir(layout.pythonDir, { recursive: true });
const [, wheelCommand] = buildArtifactCommands(layout);
await runCommand(wheelCommand.command, wheelCommand.args, { cwd: wheelCommand.cwd });
const pythonArtifacts = await findPythonArtifacts(layout.pythonDir);
await copyRuntimeWheelAssets(layout, pythonArtifacts);
}
async function verifyNpmArtifacts(layout, tmpRoot) { async function verifyNpmArtifacts(layout, tmpRoot) {
for (const packageInfo of NPM_ARTIFACT_PACKAGES) { for (const packageInfo of NPM_ARTIFACT_PACKAGES) {
await assertPathExists(layout.npmTarballs[packageInfo.name], `${packageInfo.name} tarball`); await assertPathExists(layout.npmTarballs[packageInfo.name], `${packageInfo.name} tarball`);
@ -1011,6 +1021,10 @@ async function main() {
await buildArtifacts(layout); await buildArtifacts(layout);
return; return;
} }
if (command === 'build-runtime') {
await buildRuntimeWheelAssets(layout);
return;
}
if (command === 'verify') { if (command === 'verify') {
await verifyArtifacts(layout); await verifyArtifacts(layout);
return; return;

View file

@ -41,6 +41,12 @@ export async function runSetupDev(options = {}) {
args: ['run', 'build'], args: ['run', 'build'],
retry: 'pnpm run build', retry: 'pnpm run build',
}, },
{
name: 'runtime wheel assets',
command: 'pnpm',
args: ['run', 'artifacts:build-runtime'],
retry: 'pnpm run artifacts:build-runtime',
},
{ {
name: 'doctor setup', name: 'doctor setup',
command: process.execPath, command: process.execPath,

View file

@ -22,6 +22,7 @@ test('runSetupDev runs phased setup without global linking', async () => {
['pnpm', ['install', '--frozen-lockfile']], ['pnpm', ['install', '--frozen-lockfile']],
['pnpm', ['run', 'native:rebuild']], ['pnpm', ['run', 'native:rebuild']],
['pnpm', ['run', 'build']], ['pnpm', ['run', 'build']],
['pnpm', ['run', 'artifacts:build-runtime']],
[process.execPath, ['packages/cli/dist/bin.js', 'status', '--no-input']], [process.execPath, ['packages/cli/dist/bin.js', 'status', '--no-input']],
], ],
); );