mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-22 08:38:08 +02:00
fix(cli): remove top-level scan command
This commit is contained in:
parent
601591bfbf
commit
011d694ed3
10 changed files with 182 additions and 359 deletions
|
|
@ -3,7 +3,6 @@ import type { KtxCliDeps, KtxCliIo, KtxCliPackageInfo } from './cli-runtime.js';
|
|||
import { registerConnectionCommands } from './commands/connection-commands.js';
|
||||
import { registerIngestCommands } from './commands/ingest-commands.js';
|
||||
import { registerWikiCommands } from './commands/knowledge-commands.js';
|
||||
import { registerScanCommands } from './commands/scan-commands.js';
|
||||
import { registerSetupCommands } from './commands/setup-commands.js';
|
||||
import { registerSlCommands } from './commands/sl-commands.js';
|
||||
import { registerStatusCommands } from './commands/status-commands.js';
|
||||
|
|
@ -53,7 +52,8 @@ type CommandPathNode = CommandWithGlobalOptions & {
|
|||
parent?: CommandPathNode | null;
|
||||
};
|
||||
|
||||
const PROJECT_AWARE_ROOT_COMMANDS = new Set(['setup', 'connection', 'ingest', 'wiki', 'sl', 'status', 'scan']);
|
||||
const PROJECT_AWARE_ROOT_COMMANDS = new Set(['setup', 'connection', 'ingest', 'wiki', 'sl', 'status']);
|
||||
const REMOVED_ROOT_COMMANDS = new Set(['scan']);
|
||||
|
||||
export interface CommandWithGlobalOptions {
|
||||
opts: () => object;
|
||||
|
|
@ -313,7 +313,6 @@ export function buildKtxProgram(options: BuildKtxProgramOptions): Command {
|
|||
runIngestWithProgress: async (ingestArgs, ingestIo, ingestDeps, defaultRunIngest) =>
|
||||
await (ingestDeps.ingest ?? defaultRunIngest)(ingestArgs, ingestIo),
|
||||
});
|
||||
registerScanCommands(program, context);
|
||||
registerWikiCommands(program, context);
|
||||
registerSlCommands(program, context);
|
||||
registerStatusCommands(program, context);
|
||||
|
|
@ -367,6 +366,11 @@ export async function runCommanderKtxCli(
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (REMOVED_ROOT_COMMANDS.has(argv[0] ?? '')) {
|
||||
io.stderr.write(`error: unknown command '${argv[0]}'\n`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
await profileSpan('commander:parseAsync', () => program.parseAsync(argv, { from: 'user' }));
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import type { KtxIngestArgs } from './ingest.js';
|
|||
import type { KtxKnowledgeArgs } from './knowledge.js';
|
||||
import type { KtxPublicIngestArgs } from './public-ingest.js';
|
||||
import type { KtxRuntimeArgs } from './runtime.js';
|
||||
import type { KtxScanArgs } from './scan.js';
|
||||
import type { KtxSetupArgs } from './setup.js';
|
||||
import type { KtxSlArgs } from './sl.js';
|
||||
import { profileMark, profileSpan } from './startup-profile.js';
|
||||
|
|
@ -33,7 +32,6 @@ export interface KtxCliDeps {
|
|||
ingest?: (args: KtxIngestArgs, io: KtxCliIo) => Promise<number>;
|
||||
publicIngest?: (args: KtxPublicIngestArgs, io: KtxCliIo) => Promise<number>;
|
||||
runtime?: (args: KtxRuntimeArgs, io: KtxCliIo) => Promise<number>;
|
||||
scan?: (args: KtxScanArgs, io: KtxCliIo) => Promise<number>;
|
||||
knowledge?: (args: KtxKnowledgeArgs, io: KtxCliIo) => Promise<number>;
|
||||
sl?: (args: KtxSlArgs, io: KtxCliIo) => Promise<number>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
import { type Command, InvalidArgumentError } from '@commander-js/extra-typings';
|
||||
import { type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
|
||||
import { runtimeInstallPolicyFromFlags } from '../managed-python-command.js';
|
||||
import type { KtxScanArgs } from '../scan.js';
|
||||
import { profileMark } from '../startup-profile.js';
|
||||
|
||||
profileMark('module:commands/scan-commands');
|
||||
|
||||
async function runScanArgs(context: KtxCliCommandContext, args: KtxScanArgs): Promise<void> {
|
||||
const runner = context.deps.scan ?? (await import('../scan.js')).runKtxScan;
|
||||
context.setExitCode(await runner(args, context.io));
|
||||
}
|
||||
|
||||
type KtxScanModeOption = Extract<KtxScanArgs, { command: 'run' }>['mode'];
|
||||
|
||||
const REMOVED_SCAN_SUBCOMMAND_NAMES = new Set([
|
||||
'status',
|
||||
'report',
|
||||
'relationships',
|
||||
'relationship-apply',
|
||||
'relationship-feedback',
|
||||
'relationship-calibration',
|
||||
'relationship-thresholds',
|
||||
]);
|
||||
|
||||
function parseScanModeOption(value: string): KtxScanModeOption {
|
||||
if (value === 'structural' || value === 'enriched' || value === 'relationships') {
|
||||
return value;
|
||||
}
|
||||
throw new InvalidArgumentError('Allowed choices are structural, enriched, relationships');
|
||||
}
|
||||
|
||||
function parseConnectionId(value: string): string {
|
||||
if (REMOVED_SCAN_SUBCOMMAND_NAMES.has(value)) {
|
||||
throw new InvalidArgumentError(`"${value}" is not a scan connection id`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function registerScanCommands(program: Command, context: KtxCliCommandContext): void {
|
||||
program
|
||||
.command('scan', { hidden: true })
|
||||
.description('Run a standalone connection scan')
|
||||
.argument('<connectionId>', 'KTX connection id to scan', parseConnectionId)
|
||||
.option(
|
||||
'--mode <mode>',
|
||||
'Scan mode: structural, enriched, relationships (default: structural)',
|
||||
parseScanModeOption,
|
||||
)
|
||||
.option('--dry-run', 'Run without writing scan results', false)
|
||||
.option('--database-introspection-url <url>', 'Daemon URL for live-database introspection')
|
||||
.option('--yes', 'Install the managed Python runtime without prompting when required', false)
|
||||
.option('--no-input', 'Disable interactive managed runtime installation')
|
||||
.showHelpAfterError()
|
||||
.addHelpText(
|
||||
'after',
|
||||
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
|
||||
)
|
||||
.hook('preAction', (_thisCommand, actionCommand) => {
|
||||
context.writeDebug?.('scan', actionCommand);
|
||||
})
|
||||
.action(async (connectionId: string, options, command) => {
|
||||
const mode = options.mode ?? 'structural';
|
||||
await runScanArgs(context, {
|
||||
command: 'run',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
connectionId,
|
||||
mode,
|
||||
detectRelationships: mode === 'relationships',
|
||||
dryRun: options.dryRun === true,
|
||||
databaseIntrospectionUrl: options.databaseIntrospectionUrl,
|
||||
cliVersion: context.packageInfo.version,
|
||||
runtimeInstallPolicy: runtimeInstallPolicyFromFlags(options),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -129,17 +129,12 @@ describe('dev Commander tree', () => {
|
|||
argv: ['dev', 'runtime', '--help'],
|
||||
expected: ['Usage: ktx dev runtime', 'install', 'start', 'stop', 'status'],
|
||||
},
|
||||
{
|
||||
argv: ['scan', '--help'],
|
||||
expected: ['Usage: ktx scan [options] <connectionId>', '--mode <mode>', 'structural', 'relationships', '--dry-run'],
|
||||
},
|
||||
])('prints generated nested help for $argv', async ({ argv, expected }) => {
|
||||
const io = makeIo();
|
||||
const doctor = vi.fn(async () => 0);
|
||||
const ingest = vi.fn(async () => 0);
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(runKtxCli(argv, io.io, { doctor, ingest, scan })).resolves.toBe(0);
|
||||
await expect(runKtxCli(argv, io.io, { doctor, ingest })).resolves.toBe(0);
|
||||
|
||||
for (const text of expected) {
|
||||
expect(io.stdout()).toContain(text);
|
||||
|
|
@ -151,7 +146,6 @@ describe('dev Commander tree', () => {
|
|||
expect(io.stderr()).toBe('');
|
||||
expect(doctor).not.toHaveBeenCalled();
|
||||
expect(ingest).not.toHaveBeenCalled();
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps legacy adapter-backed ingest run callable but hidden from ingest help', async () => {
|
||||
|
|
@ -175,100 +169,20 @@ describe('dev Commander tree', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('dispatches top-level scan through Commander with injected dependencies', async () => {
|
||||
const scanIo = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(
|
||||
runKtxCli(['scan', 'warehouse', '--project-dir', '/tmp/project', '--dry-run'], scanIo.io, { scan }),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(scan).toHaveBeenCalledWith(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: '/tmp/project',
|
||||
connectionId: 'warehouse',
|
||||
mode: 'structural',
|
||||
detectRelationships: false,
|
||||
dryRun: true,
|
||||
databaseIntrospectionUrl: undefined,
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
},
|
||||
scanIo.io,
|
||||
);
|
||||
expect(scanIo.stderr()).toBe('Project: /tmp/project\n');
|
||||
});
|
||||
|
||||
it('dispatches top-level scan --mode relationships through Commander', async () => {
|
||||
const io = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(
|
||||
runKtxCli(['scan', 'warehouse', '--project-dir', '/tmp/project', '--mode', 'relationships'], io.io, {
|
||||
scan,
|
||||
}),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(scan).toHaveBeenCalledWith(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: '/tmp/project',
|
||||
connectionId: 'warehouse',
|
||||
mode: 'relationships',
|
||||
detectRelationships: true,
|
||||
dryRun: false,
|
||||
databaseIntrospectionUrl: undefined,
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
},
|
||||
io.io,
|
||||
);
|
||||
expect(io.stderr()).toBe('Project: /tmp/project\n');
|
||||
});
|
||||
|
||||
it.each(['--enrich', '--detect-relationships'])('rejects removed scan shorthand option %s', async (option) => {
|
||||
const io = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(runKtxCli(['scan', 'warehouse', option], io.io, { scan })).resolves.toBe(1);
|
||||
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toContain(`unknown option '${option}'`);
|
||||
});
|
||||
|
||||
it('rejects scan without a connection id', async () => {
|
||||
const io = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(runKtxCli(['scan', '--dry-run'], io.io, { scan })).resolves.toBe(1);
|
||||
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toMatch(/missing required argument/i);
|
||||
});
|
||||
|
||||
it('rejects invalid scan modes before dispatch', async () => {
|
||||
const io = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
|
||||
await expect(runKtxCli(['scan', 'warehouse', '--mode', 'deep'], io.io, { scan })).resolves.toBe(1);
|
||||
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toContain("argument 'deep' is invalid");
|
||||
expect(io.stderr()).toContain('Allowed choices are structural, enriched, relationships');
|
||||
});
|
||||
|
||||
it.each([
|
||||
['scan', 'report', 'scan-run-1'],
|
||||
['scan', 'relationships', 'scan-run-1'],
|
||||
])('rejects removed scan subcommand %s %s', async (command, subcommand, runId) => {
|
||||
{ argv: ['scan'] },
|
||||
{ argv: ['scan', '--help'] },
|
||||
{ argv: ['scan', 'warehouse'] },
|
||||
{ argv: ['scan', 'warehouse', '--project-dir', '/tmp/project', '--dry-run'] },
|
||||
{ argv: ['scan', 'warehouse', '--project-dir', '/tmp/project', '--mode', 'relationships'] },
|
||||
])('rejects removed top-level scan command $argv', async ({ argv }) => {
|
||||
const io = makeIo();
|
||||
const scan = vi.fn(async () => 0);
|
||||
const ingest = vi.fn(async () => 0);
|
||||
|
||||
await expect(runKtxCli([command, subcommand, runId], io.io, { scan })).resolves.toBe(1);
|
||||
await expect(runKtxCli(argv, io.io, { ingest })).resolves.toBe(1);
|
||||
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toMatch(/too many arguments|unknown command|error:/);
|
||||
expect(ingest).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toMatch(/unknown command|error:/);
|
||||
});
|
||||
|
||||
it('dispatches top-level ingest run through the low-level ingest Commander registration', async () => {
|
||||
|
|
|
|||
|
|
@ -1565,63 +1565,20 @@ describe('runKtxCli', () => {
|
|||
expect(testIo.stderr()).toContain('[debug] dispatch=connection');
|
||||
});
|
||||
|
||||
it('routes scan through the top-level command with top-level project-dir', async () => {
|
||||
it.each([
|
||||
{ argv: ['scan'] },
|
||||
{ argv: ['scan', '--help'] },
|
||||
{ argv: ['scan', 'warehouse'] },
|
||||
{ argv: ['scan', 'warehouse', '--project-dir', '/tmp/project'] },
|
||||
{ argv: ['scan', 'warehouse', '--mode', 'relationships'] },
|
||||
])('rejects removed top-level scan command $argv', async ({ argv }) => {
|
||||
const testIo = makeIo();
|
||||
const scan = vi.fn().mockResolvedValue(0);
|
||||
const ingest = vi.fn().mockResolvedValue(0);
|
||||
|
||||
await expect(runKtxCli(['--project-dir', tempDir, 'scan', 'warehouse'], testIo.io, { scan })).resolves.toBe(
|
||||
0,
|
||||
);
|
||||
await expect(runKtxCli(argv, testIo.io, { ingest })).resolves.toBe(1);
|
||||
|
||||
expect(scan).toHaveBeenCalledWith(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
connectionId: 'warehouse',
|
||||
mode: 'structural',
|
||||
detectRelationships: false,
|
||||
dryRun: false,
|
||||
databaseIntrospectionUrl: undefined,
|
||||
cliVersion: '0.0.0-private',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
},
|
||||
testIo.io,
|
||||
);
|
||||
});
|
||||
|
||||
it('routes scan managed runtime install policies', async () => {
|
||||
const autoIo = makeIo();
|
||||
const neverIo = makeIo();
|
||||
const conflictIo = makeIo();
|
||||
const scan = vi.fn().mockResolvedValue(0);
|
||||
|
||||
await expect(runKtxCli(['--project-dir', tempDir, 'scan', 'warehouse', '--yes'], autoIo.io, { scan }))
|
||||
.resolves.toBe(0);
|
||||
await expect(runKtxCli(['--project-dir', tempDir, 'scan', 'warehouse', '--no-input'], neverIo.io, { scan }))
|
||||
.resolves.toBe(0);
|
||||
await expect(
|
||||
runKtxCli(['--project-dir', tempDir, 'scan', 'warehouse', '--yes', '--no-input'], conflictIo.io, {
|
||||
scan,
|
||||
}),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(scan).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
command: 'run',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
}),
|
||||
autoIo.io,
|
||||
);
|
||||
expect(scan).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
command: 'run',
|
||||
runtimeInstallPolicy: 'never',
|
||||
}),
|
||||
neverIo.io,
|
||||
);
|
||||
expect(conflictIo.stderr()).toContain('Choose only one runtime install mode: --yes or --no-input');
|
||||
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
||||
expect(ingest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects removed public serve command options before dispatch', async () => {
|
||||
|
|
@ -1669,27 +1626,17 @@ describe('runKtxCli', () => {
|
|||
it('rejects removed dev command groups without invoking execution', async () => {
|
||||
for (const command of ['scan', 'ingest', 'mapping']) {
|
||||
const testIo = makeIo();
|
||||
const scan = vi.fn().mockResolvedValue(0);
|
||||
const ingest = vi.fn().mockResolvedValue(0);
|
||||
const sl = vi.fn().mockResolvedValue(0);
|
||||
|
||||
await expect(runKtxCli(['dev', command], testIo.io, { scan, sl })).resolves.toBe(1);
|
||||
await expect(runKtxCli(['dev', command], testIo.io, { ingest, sl })).resolves.toBe(1);
|
||||
|
||||
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
expect(ingest).not.toHaveBeenCalled();
|
||||
expect(sl).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects removed scan subcommands without invoking scan execution', async () => {
|
||||
const testIo = makeIo();
|
||||
const scan = vi.fn().mockResolvedValue(0);
|
||||
|
||||
await expect(runKtxCli(['scan', 'report'], testIo.io, { scan })).resolves.toBe(1);
|
||||
|
||||
expect(testIo.stderr()).toMatch(/too many arguments|unknown command|error:/);
|
||||
expect(scan).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects removed reserved dev subcommands', async () => {
|
||||
const testIo = makeIo();
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ describe('project directory defaults', () => {
|
|||
const connection = vi.fn(async () => 0);
|
||||
const doctor = vi.fn(async () => 0);
|
||||
const ingest = vi.fn(async () => 0);
|
||||
const scan = vi.fn(async () => 0);
|
||||
const publicIngest = vi.fn(async () => 0);
|
||||
const setup = vi.fn(async () => 0);
|
||||
const deps: KtxCliDeps = { connection, doctor, ingest, scan, setup };
|
||||
const deps: KtxCliDeps = { connection, doctor, ingest, publicIngest, setup };
|
||||
|
||||
const cases: Array<{
|
||||
argv: string[];
|
||||
|
|
@ -68,9 +68,9 @@ describe('project directory defaults', () => {
|
|||
expectedStderr: 'Project: /tmp/ktx-env-project\n',
|
||||
},
|
||||
{
|
||||
argv: ['scan', 'warehouse'],
|
||||
spy: scan,
|
||||
expected: { command: 'run', projectDir: '/tmp/ktx-env-project', connectionId: 'warehouse' },
|
||||
argv: ['ingest', 'warehouse', '--no-input'],
|
||||
spy: publicIngest,
|
||||
expected: { command: 'run', projectDir: '/tmp/ktx-env-project', targetConnectionId: 'warehouse' },
|
||||
expectedStderr: 'Project: /tmp/ktx-env-project\n',
|
||||
},
|
||||
];
|
||||
|
|
@ -86,13 +86,15 @@ describe('project directory defaults', () => {
|
|||
it('lets explicit global --project-dir override KTX_PROJECT_DIR before and after nested commands', async () => {
|
||||
process.env.KTX_PROJECT_DIR = '/tmp/ktx-env-project';
|
||||
|
||||
const scan = vi.fn(async () => 0);
|
||||
const publicIngest = vi.fn(async () => 0);
|
||||
const ingest = vi.fn(async () => 0);
|
||||
const scanIo = makeIo();
|
||||
const publicIngestIo = makeIo();
|
||||
const ingestIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(['--project-dir', '/tmp/ktx-explicit-project', 'scan', 'warehouse'], scanIo.io, { scan }),
|
||||
runKtxCli(['--project-dir', '/tmp/ktx-explicit-project', 'ingest', 'warehouse', '--no-input'], publicIngestIo.io, {
|
||||
publicIngest,
|
||||
}),
|
||||
).resolves.toBe(0);
|
||||
await expect(
|
||||
runKtxCli(['ingest', 'status', 'run-1', '--project-dir=/tmp/ktx-explicit-project'], ingestIo.io, {
|
||||
|
|
@ -100,15 +102,15 @@ describe('project directory defaults', () => {
|
|||
}),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(scan).toHaveBeenCalledWith(
|
||||
expect(publicIngest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ command: 'run', projectDir: '/tmp/ktx-explicit-project' }),
|
||||
scanIo.io,
|
||||
publicIngestIo.io,
|
||||
);
|
||||
expect(ingest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ command: 'status', projectDir: '/tmp/ktx-explicit-project' }),
|
||||
ingestIo.io,
|
||||
);
|
||||
expect(scanIo.stderr()).toBe('Project: /tmp/ktx-explicit-project\n');
|
||||
expect(publicIngestIo.stderr()).toBe('Project: /tmp/ktx-explicit-project\n');
|
||||
expect(ingestIo.stderr()).toBe('Project: /tmp/ktx-explicit-project\n');
|
||||
});
|
||||
|
||||
|
|
@ -126,18 +128,18 @@ describe('project directory defaults', () => {
|
|||
await writeFile(join(projectDir, 'ktx.yaml'), 'project: warehouse\n', 'utf-8');
|
||||
const expectedProjectDir = await realpath(projectDir);
|
||||
|
||||
const scan = vi.fn(async () => 0);
|
||||
const publicIngest = vi.fn(async () => 0);
|
||||
const testIo = makeIo();
|
||||
|
||||
try {
|
||||
process.chdir(nestedDir);
|
||||
await expect(runKtxCli(['scan', 'warehouse'], testIo.io, { scan })).resolves.toBe(0);
|
||||
await expect(runKtxCli(['ingest', 'warehouse', '--no-input'], testIo.io, { publicIngest })).resolves.toBe(0);
|
||||
} finally {
|
||||
process.chdir(originalCwd);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
expect(scan).toHaveBeenCalledWith(
|
||||
expect(publicIngest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ command: 'run', projectDir: expectedProjectDir }),
|
||||
testIo.io,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ describe('standalone built ktx CLI smoke', () => {
|
|||
expect([0, 1]).toContain(result.code);
|
||||
});
|
||||
|
||||
it('runs structural and enriched scans through the built binary with manifest artifacts', async () => {
|
||||
const projectDir = join(tempDir, 'scan-project');
|
||||
it('runs fast public database ingest through the built binary with manifest artifacts', async () => {
|
||||
const projectDir = join(tempDir, 'database-ingest-project');
|
||||
const init = await runSetupNewProject(projectDir);
|
||||
expectProjectStderr(init, projectDir);
|
||||
|
||||
|
|
@ -219,43 +219,19 @@ describe('standalone built ktx CLI smoke', () => {
|
|||
expect(connectionTest.stdout).toContain('Driver: sqlite');
|
||||
expect(connectionTest.stdout).toContain('Tables: 2');
|
||||
|
||||
const structural = await runBuiltCli(['scan', 'warehouse', '--project-dir', projectDir]);
|
||||
expectProjectStderr(structural, projectDir);
|
||||
expect(structural.stdout).toContain('Status: done');
|
||||
expect(structural.stdout).toContain('Mode: structural');
|
||||
expect(structural.stdout).toContain('Schema shards: 1');
|
||||
const ingest = await runBuiltCli(['ingest', 'warehouse', '--project-dir', projectDir, '--fast', '--no-input']);
|
||||
expectProjectStderr(ingest, projectDir);
|
||||
expect(ingest.stdout).toContain('Ingest finished');
|
||||
expect(ingest.stdout).toContain('warehouse');
|
||||
expect(ingest.stdout).toContain('Database schema');
|
||||
expect(ingest.stdout).toContain('warehouse done');
|
||||
expect(ingest.stdout).not.toContain('KTX scan completed');
|
||||
|
||||
const structuralManifest = await readFile(
|
||||
join(projectDir, 'semantic-layer/warehouse/_schema/public.yaml'),
|
||||
'utf-8',
|
||||
);
|
||||
expect(structuralManifest).toContain('customers:');
|
||||
expect(structuralManifest).toContain('orders:');
|
||||
expect(structuralManifest).toContain('source: formal');
|
||||
expect(structuralManifest).not.toContain('ai:');
|
||||
|
||||
const providerlessEnriched = await runBuiltCli([
|
||||
'scan',
|
||||
'warehouse',
|
||||
'--project-dir',
|
||||
projectDir,
|
||||
'--mode',
|
||||
'enriched',
|
||||
]);
|
||||
expectProjectStderr(providerlessEnriched, projectDir);
|
||||
expect(providerlessEnriched.stdout).toContain('Mode: enriched');
|
||||
expect(providerlessEnriched.stdout).toContain('Relationships');
|
||||
expect(providerlessEnriched.stdout).toContain('Accepted: 1');
|
||||
expect(providerlessEnriched.stdout).toContain('scan_enrichment_backend_not_configured');
|
||||
expect(providerlessEnriched.stdout).toContain('Enrichment artifacts: 3');
|
||||
await writeSqliteScanConfig(projectDir, dbPath, true);
|
||||
const enriched = await runBuiltCli(['scan', 'warehouse', '--project-dir', projectDir, '--mode', 'enriched']);
|
||||
expectProjectStderr(enriched, projectDir);
|
||||
expect(enriched.stdout).toContain('Mode: enriched');
|
||||
expect(enriched.stdout).toContain('Enrichment artifacts:');
|
||||
|
||||
const enrichedManifest = await readFile(join(projectDir, 'semantic-layer/warehouse/_schema/public.yaml'), 'utf-8');
|
||||
expect(enrichedManifest).toContain('Deterministic description');
|
||||
const manifest = await readFile(join(projectDir, 'semantic-layer/warehouse/_schema/public.yaml'), 'utf-8');
|
||||
expect(manifest).toContain('customers:');
|
||||
expect(manifest).toContain('orders:');
|
||||
expect(manifest).toContain('source: formal');
|
||||
expect(manifest).not.toContain('ai:');
|
||||
}, 30_000);
|
||||
|
||||
it('parses gateway LLM config and OpenAI enrichment embeddings used by standalone scans without network calls', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue