mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-01 08:59:39 +02:00
fix(cli): align Notion setup credential to --source-auth-token-ref (#236)
Notion's setup path read --source-api-key-ref while writing the auth_token_ref config field, so --source-auth-token-ref was silently dropped. Align Notion to the flag=field convention every other connector follows: it now reads --source-auth-token-ref, and --source-api-key-ref becomes Metabase-only. Also add validation rejecting any credential-ref flag not applicable to the chosen --source, with a pointer to the correct flag, closing the silent-drop class for all connectors. Update CLI-reference docs, the ktx skill Notion example, and tests. Fixes KLO-724.
This commit is contained in:
parent
8ebc4ce107
commit
637891f030
5 changed files with 137 additions and 12 deletions
|
|
@ -308,9 +308,14 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
.addOption(new Option('--source-git-url <url>', 'Git URL for dbt, MetricFlow, or LookML').hideHelp())
|
||||
.addOption(new Option('--source-branch <branch>', 'Git branch for source setup').hideHelp())
|
||||
.addOption(new Option('--source-subpath <path>', 'Repo subpath for source setup').hideHelp())
|
||||
.addOption(new Option('--source-auth-token-ref <ref>', 'env: or file: credential ref for source repo auth').hideHelp())
|
||||
.addOption(
|
||||
new Option(
|
||||
'--source-auth-token-ref <ref>',
|
||||
'env: or file: credential ref for source repo auth or Notion integration token',
|
||||
).hideHelp(),
|
||||
)
|
||||
.addOption(new Option('--source-url <url>', 'Source service URL for Metabase or Looker').hideHelp())
|
||||
.addOption(new Option('--source-api-key-ref <ref>', 'env: or file: API key ref for Metabase or Notion').hideHelp())
|
||||
.addOption(new Option('--source-api-key-ref <ref>', 'env: or file: API key ref for Metabase').hideHelp())
|
||||
.addOption(new Option('--source-client-id <id>', 'Looker client id').hideHelp())
|
||||
.addOption(new Option('--source-client-secret-ref <ref>', 'env: or file: Looker client secret ref').hideHelp())
|
||||
.addOption(new Option('--source-warehouse-connection-id <id>', 'Mapped warehouse connection id').hideHelp())
|
||||
|
|
|
|||
|
|
@ -217,6 +217,39 @@ function credentialRef(value: string | undefined, label: string): string {
|
|||
return ref;
|
||||
}
|
||||
|
||||
type SourceCredentialFlag = {
|
||||
field: 'sourceAuthTokenRef' | 'sourceApiKeyRef' | 'sourceClientSecretRef';
|
||||
flag: string;
|
||||
};
|
||||
|
||||
// Each connector reads exactly one credential ref; the flag name mirrors the
|
||||
// ktx.yaml field it writes (auth_token_ref / api_key_ref / client_secret_ref).
|
||||
const SOURCE_CREDENTIAL_FLAG: Record<KtxSetupSourceType, SourceCredentialFlag> = {
|
||||
dbt: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
||||
metricflow: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
||||
lookml: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
||||
notion: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
||||
metabase: { field: 'sourceApiKeyRef', flag: '--source-api-key-ref' },
|
||||
looker: { field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
|
||||
};
|
||||
|
||||
const ALL_SOURCE_CREDENTIAL_FLAGS: SourceCredentialFlag[] = [
|
||||
{ field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
|
||||
{ field: 'sourceApiKeyRef', flag: '--source-api-key-ref' },
|
||||
{ field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
|
||||
];
|
||||
|
||||
// Reject a credential ref flag the chosen source does not read, so a wrong flag
|
||||
// fails loudly instead of being silently dropped (KLO-724).
|
||||
function assertSourceCredentialFlags(source: KtxSetupSourceType, args: KtxSetupSourcesArgs): void {
|
||||
const allowed = SOURCE_CREDENTIAL_FLAG[source];
|
||||
for (const { field, flag } of ALL_SOURCE_CREDENTIAL_FLAGS) {
|
||||
if (args[field] && field !== allowed.field) {
|
||||
throw new Error(`${flag} does not apply to --source ${source}; use ${allowed.flag}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function chooseSourceCredentialRef(input: {
|
||||
prompts: KtxSetupSourcesPromptAdapter;
|
||||
projectDir: string;
|
||||
|
|
@ -515,7 +548,7 @@ function buildNotionConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionC
|
|||
}
|
||||
return {
|
||||
driver: 'notion',
|
||||
auth_token_ref: credentialRef(args.sourceApiKeyRef, 'Notion token ref'),
|
||||
auth_token_ref: credentialRef(args.sourceAuthTokenRef, 'Notion token ref'),
|
||||
crawl_mode: crawlMode,
|
||||
...(rootPageIds.length > 0 ? { root_page_ids: rootPageIds } : {}),
|
||||
root_database_ids: [],
|
||||
|
|
@ -1295,10 +1328,10 @@ async function promptForInteractiveSource(
|
|||
label: 'Notion integration token',
|
||||
envName: 'NOTION_TOKEN',
|
||||
secretFileName: `${currentState.sourceConnectionId ?? 'notion-main'}-token`,
|
||||
existingRef: currentState.sourceApiKeyRef,
|
||||
existingRef: currentState.sourceAuthTokenRef,
|
||||
});
|
||||
if (ref === 'back') return 'back';
|
||||
currentState.sourceApiKeyRef = ref;
|
||||
currentState.sourceAuthTokenRef = ref;
|
||||
return 'next';
|
||||
},
|
||||
async (currentState) => {
|
||||
|
|
@ -1326,7 +1359,7 @@ async function promptForInteractiveSource(
|
|||
connectionId,
|
||||
connection: {
|
||||
driver: 'notion',
|
||||
auth_token_ref: credentialRef(currentState.sourceApiKeyRef, 'Notion token ref'),
|
||||
auth_token_ref: credentialRef(currentState.sourceAuthTokenRef, 'Notion token ref'),
|
||||
crawl_mode: 'selected_roots',
|
||||
root_page_ids: currentState.notionRootPageIds ?? [],
|
||||
root_database_ids: [],
|
||||
|
|
@ -1516,7 +1549,7 @@ function sourceArgsFromExistingConnection(input: {
|
|||
return sourceArgs;
|
||||
}
|
||||
|
||||
sourceArgs.sourceApiKeyRef = stringField(input.connection.auth_token_ref);
|
||||
sourceArgs.sourceAuthTokenRef = stringField(input.connection.auth_token_ref);
|
||||
sourceArgs.notionCrawlMode =
|
||||
input.connection.crawl_mode === 'all_accessible' ? 'all_accessible' : 'selected_roots';
|
||||
if (Array.isArray(input.connection.root_page_ids)) {
|
||||
|
|
@ -1817,6 +1850,10 @@ export async function runKtxSetupSourcesStep(
|
|||
return { status: 'skipped', projectDir: args.projectDir };
|
||||
}
|
||||
|
||||
if (args.source) {
|
||||
assertSourceCredentialFlags(args.source, args);
|
||||
}
|
||||
|
||||
const prompts = deps.prompts ?? createPromptAdapter();
|
||||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
if (!hasPrimarySource(project.config)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue