diff --git a/packages/cli/src/public-ingest.test.ts b/packages/cli/src/public-ingest.test.ts index d2357f23..6d55f9b2 100644 --- a/packages/cli/src/public-ingest.test.ts +++ b/packages/cli/src/public-ingest.test.ts @@ -523,6 +523,81 @@ describe('runKtxPublicIngest', () => { expect(io.stdout()).not.toContain('live-database'); }); + it('suppresses lower-level source report output during direct public source ingest', async () => { + const io = makeIo(); + const project = projectWithConnections({ + docs: { driver: 'notion' }, + }); + const runIngest = vi.fn(async (_args, ingestIo) => { + ingestIo.stdout.write('Report: report-docs-1\n'); + ingestIo.stdout.write('Adapter: notion\n'); + ingestIo.stdout.write('Saved memory: 2 wiki, 0 SL\n'); + return 0; + }); + + await expect( + runKtxPublicIngest( + { + command: 'run', + projectDir: '/tmp/project', + targetConnectionId: 'docs', + all: false, + json: false, + inputMode: 'disabled', + }, + io.io, + { loadProject: vi.fn(async () => project), runIngest }, + ), + ).resolves.toBe(0); + + expect(io.stdout()).toContain('Ingest finished'); + expect(io.stdout()).toContain('docs'); + expect(io.stdout()).toContain('Source ingest'); + expect(io.stdout()).not.toContain('Report: report-docs-1'); + expect(io.stdout()).not.toContain('Adapter:'); + expect(io.stdout()).not.toContain('notion\n'); + expect(io.stderr()).toBe(''); + }); + + it('suppresses historic-sql report output during direct public query-history ingest', 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.stdout.write('Report: report-query-history-1\n'); + ingestIo.stdout.write('Adapter: historic-sql\n'); + ingestIo.stdout.write('Saved memory: 1 wiki, 1 SL\n'); + return 0; + }); + + 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(0); + + expect(io.stdout()).toContain('Schema ingest runs before query history for warehouse.'); + expect(io.stdout()).toContain('Ingest finished'); + expect(io.stdout()).toContain('warehouse'); + expect(io.stdout()).toContain('done'); + expect(io.stdout()).not.toContain('Report: report-query-history-1'); + expect(io.stdout()).not.toContain('Adapter:'); + expect(io.stdout()).not.toContain('historic-sql'); + expect(io.stderr()).toBe(''); + }); + it('delegates interactive TTY public ingest to the foreground context-build view', async () => { const io = makeIo({ isTTY: true, interactive: true }); const project = projectWithConnections({ warehouse: { driver: 'postgres' } }); diff --git a/packages/cli/src/public-ingest.ts b/packages/cli/src/public-ingest.ts index 4e8b5b17..c81cdeb4 100644 --- a/packages/cli/src/public-ingest.ts +++ b/packages/cli/src/public-ingest.ts @@ -584,11 +584,25 @@ function createCapturedPublicIngestIo(): CapturedPublicIngestIo { }; } +const INTERNAL_STATUS_LINE_RE = + /^(Report|Run|Job|Status|Adapter|Connection|Sync|Diff|Work units|Saved memory|Provenance rows):\s*/; + +function publicIngestOutputLine(line: string): string { + return line + .replace(/\blive-database\b/g, 'database schema') + .replace(/\bhistoric-sql\b/g, 'query history') + .replace(/\bhistoric SQL\b/gi, 'query history'); +} + function firstCapturedFailureLine(output: string): string | undefined { return output .split(/\r?\n/) .map((line) => line.trim()) - .find((line) => line.length > 0 && !line.startsWith('KTX scan completed')); + .filter((line) => line.length > 0) + .filter((line) => !line.startsWith('KTX scan completed')) + .filter((line) => !INTERNAL_STATUS_LINE_RE.test(line)) + .map(publicIngestOutputLine) + .find((line) => line.length > 0); } export async function executePublicIngestTarget( @@ -656,9 +670,19 @@ export async function executePublicIngestTarget( ...(target.queryHistory.windowDays !== undefined ? { windowDays: target.queryHistory.windowDays } : {}), }, }; - const qhExitCode = await runIngest(ingestArgs, io); + const capturedIngestIo = deps.ingestProgress ? null : createCapturedPublicIngestIo(); + const ingestIo = capturedIngestIo ?? io; + const qhExitCode = deps.ingestProgress + ? await runIngest(ingestArgs, ingestIo, { progress: deps.ingestProgress }) + : await runIngest(ingestArgs, ingestIo); if (qhExitCode !== 0) { - return markTargetResult(target, args, 'failed', 'query-history'); + return markTargetResult( + target, + args, + 'failed', + 'query-history', + capturedIngestIo ? firstCapturedFailureLine(capturedIngestIo.capturedOutput()) : undefined, + ); } } @@ -677,10 +701,18 @@ export async function executePublicIngestTarget( allowImplicitAdapter: true, }; const runIngest = deps.runIngest ?? runKtxIngest; + const capturedIngestIo = deps.ingestProgress ? null : createCapturedPublicIngestIo(); + const ingestIo = capturedIngestIo ?? io; const exitCode = deps.ingestProgress - ? await runIngest(ingestArgs, io, { progress: deps.ingestProgress }) - : await runIngest(ingestArgs, io); - return markTargetResult(target, args, exitCode === 0 ? 'done' : 'failed'); + ? await runIngest(ingestArgs, ingestIo, { progress: deps.ingestProgress }) + : await runIngest(ingestArgs, ingestIo); + return markTargetResult( + target, + args, + exitCode === 0 ? 'done' : 'failed', + 'source-ingest', + capturedIngestIo ? firstCapturedFailureLine(capturedIngestIo.capturedOutput()) : undefined, + ); } export async function runKtxPublicIngest(