@duckcodeailabs/dql-cli 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps-api.d.ts +11 -3
- package/dist/apps-api.d.ts.map +1 -1
- package/dist/apps-api.js +91 -75
- package/dist/apps-api.js.map +1 -1
- package/dist/assets/dql-notebook/assets/index-4DbeLWHA.js +5876 -0
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/local-runtime.d.ts +17 -1
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +355 -27
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/package.json +10 -10
- package/dist/assets/dql-notebook/assets/index-2Tszpipm.js +0 -5914
package/dist/local-runtime.js
CHANGED
|
@@ -10,13 +10,13 @@ import { buildMixedSourceWarehouseFallbackSql, findMentionedNotebookDataset, pla
|
|
|
10
10
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11
11
|
import OpenAI from "openai";
|
|
12
12
|
import { buildExecutionPlan, createWelcomeNotebook, deserializeNotebook, getConnectorFormSchemas, hasSemanticRefs, hasStandaloneSemanticRef, resolveSemanticRefs, } from '@duckcodeailabs/dql-notebook';
|
|
13
|
-
import { loadSemanticLayerFromDir, normalizeDqlArtifactReference, serializeMetricDefinitionToYaml, resolveSemanticLayerAsync, getDialect, Parser, NodeKind, blockParameterDefinitions, buildLineageGraph, buildManifest, collectInputFiles, findAppDocuments, findDashboardsForApp, isBlockIdRef, loadAppDocument, loadDashboardDocument, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryBusiness360, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, domainFolderSlug, previewModelingChange, applyModelingChange, previewDbtSourcePatch, applyDbtSourcePatch, loadDomainPackageRegistry, loadDbtNodeAuthoringDetail, relationshipValidationProofFingerprint, discoverDbtDomains, renderDomainDeclaration, ProjectSnapshotService, analyzeSqlReferences, } from '@duckcodeailabs/dql-core';
|
|
13
|
+
import { loadSemanticLayerFromDir, normalizeDqlArtifactReference, serializeMetricDefinitionToYaml, resolveSemanticLayerAsync, resolveRepoSource, getDialect, Parser, NodeKind, blockParameterDefinitions, buildLineageGraph, buildManifest, collectInputFiles, findAppDocuments, findDashboardsForApp, isBlockIdRef, loadAppDocument, loadDashboardDocument, analyzeImpact, buildTrustChain, detectDomainFlows, getDomainTrustOverview, queryLineage, queryBusiness360, queryCompleteLineagePaths, LineageGraph, canonicalize, canonicalizeNotebook, diffDQL, diffNotebook, domainFolderSlug, previewModelingChange, applyModelingChange, previewDbtSourcePatch, applyDbtSourcePatch, loadDomainPackageRegistry, loadDbtNodeAuthoringDetail, relationshipValidationProofFingerprint, discoverDbtDomains, renderDomainDeclaration, ProjectSnapshotService, analyzeSqlReferences, } from '@duckcodeailabs/dql-core';
|
|
14
14
|
import { load as loadYaml } from 'js-yaml';
|
|
15
15
|
import { listBlockTemplates } from './block-templates.js';
|
|
16
16
|
import { getRunner as getLLMRunner } from './llm/index.js';
|
|
17
17
|
import { createDqlAgentProviderRunner } from './llm/providers/dql-agent-provider.js';
|
|
18
18
|
import { listRemoteMcpSettings, saveRemoteMcpSettings } from './llm/mcp-config.js';
|
|
19
|
-
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildLocalContextPack, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureMetadataCatalogFresh, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, FileAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, synthesizeAnswer, streamOrGenerate, narrateResult, normalizeAnthropicBaseUrl, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, reindexProject, invalidateAgentProjectState, resolveDomainContextEnvelope, defaultKgPath,
|
|
19
|
+
import { ClaudeProvider, ConversationStore, advanceThreadState, buildConversationSnapshot, recallRelevantTurns, GeminiProvider, MemoryStore, OllamaProvider, OpenAIProvider, buildBlockBusinessFingerprint, buildBlockSqlFingerprints, buildLocalContextPack, defaultConversationPath, defaultMemoryPath, ensureDefaultMemoryFiles, ensureMetadataCatalogFresh, propose, proposePlan, recordCorrectionTrace, emitCorrectionEvalCase, mineJoinPatterns, reviewHint, AgentRunEngine, FileAgentRunStore, defaultAgentRunGates, createLlmAgentRunPlanner, createHybridRouter, computeResultStats, buildDeterministicDashboardStory, synthesizeAnswer, streamOrGenerate, narrateResult, normalizeAnthropicBaseUrl, buildProposePreview, buildFromPrompt, defaultAgentRunStorePath, resolveLocalOwner, resolveProposeConfig, recordQueryRun, recordRuntimeSchemaSnapshot, latestRuntimeSchemaSnapshotForProject, loadSkills, migrateLegacySkills, configuredSkillsPath, skillsDir, draftDomainSkillBootstrap, buildDomainSkillBootstrapPrompt, mergeDomainSkillBootstrapEnrichment, writeSkill, deleteSkill, deriveGeneratedDraftSlug, reindexProject, invalidateAgentProjectState, resolveDomainContextEnvelope, defaultKgPath, planAppFromPrompt, KGStore, planResearch, loadSemanticMetrics, cascadeTraceToEvidenceRouteSteps, createCascadeAnswerResult, createCascadeTrace, routeReasoningEffort, routeForCascadeAnswerTier, clampReasoningEffort, bumpReasoningEffort, resolveThinkingMode, coerceThinkingMode, probeLocalOllamaEmbeddings, upsertGeneratedDqlArtifactDraft, } from '@duckcodeailabs/dql-agent';
|
|
20
20
|
import { gatherProposeEnrichment } from './propose-enrich.js';
|
|
21
21
|
import { handleAppsApi, proposeAppAiBuild, recommendVisualization } from './apps-api.js';
|
|
22
22
|
import { getActiveProvider, getEffectiveProviderConfig, isProviderSettingsId, listProviderSettings, saveProviderSettings, } from './settings/provider-settings.js';
|
|
@@ -389,13 +389,19 @@ export async function startLocalServer(opts) {
|
|
|
389
389
|
const gitRoot = await resolveGitRoot(projectRoot);
|
|
390
390
|
if (gitRoot)
|
|
391
391
|
ensureLocalRuntimeGitignore(projectRoot);
|
|
392
|
-
let connection = rawConnection ? normalizeProjectConnection(rawConnection, projectRoot) : null;
|
|
393
392
|
let projectConfig = loadProjectConfig(projectRoot);
|
|
393
|
+
const configuredConnection = rawConnection
|
|
394
|
+
? normalizeProjectConnection(rawConnection, projectRoot)
|
|
395
|
+
: projectConfig.defaultConnection
|
|
396
|
+
? normalizeProjectConnection(projectConfig.defaultConnection, projectRoot)
|
|
397
|
+
: null;
|
|
398
|
+
let connection = configuredConnection ?? resolveDbtProfileRuntimeConnection(projectRoot, projectConfig);
|
|
394
399
|
const projectSnapshots = new ProjectSnapshotService();
|
|
400
|
+
const dashboardRunEvidence = new Map();
|
|
395
401
|
const dbtNodeDetailCache = new Map();
|
|
396
402
|
const onboardingJobs = new Map();
|
|
397
403
|
const projectSnapshot = () => {
|
|
398
|
-
const dbtManifestPath = resolveDbtManifestPath(projectRoot) ?? undefined;
|
|
404
|
+
const dbtManifestPath = resolveDbtManifestPath(projectRoot, projectConfig) ?? undefined;
|
|
399
405
|
const inputs = collectInputFiles({ projectRoot, dbtManifestPath });
|
|
400
406
|
const signatureHash = createHash('sha256');
|
|
401
407
|
for (const input of inputs.sort()) {
|
|
@@ -421,14 +427,26 @@ export async function startLocalServer(opts) {
|
|
|
421
427
|
};
|
|
422
428
|
};
|
|
423
429
|
const onboardingDbtPaths = (body = {}) => {
|
|
424
|
-
const
|
|
430
|
+
const repoUrl = typeof body.repoUrl === 'string' && body.repoUrl.trim() ? body.repoUrl.trim() : undefined;
|
|
431
|
+
const branch = typeof body.branch === 'string' && body.branch.trim() ? body.branch.trim() : undefined;
|
|
432
|
+
const subPath = typeof body.subPath === 'string' && body.subPath.trim() ? body.subPath.trim() : undefined;
|
|
433
|
+
const repo = repoUrl ? resolveRepoSource({
|
|
434
|
+
provider: 'dbt', source: repoUrl.includes('gitlab') ? 'gitlab' : 'github', repoUrl, branch, subPath,
|
|
435
|
+
}, projectRoot) : undefined;
|
|
436
|
+
const projectDirInput = repo?.localPath ?? (typeof body.projectDir === 'string' ? body.projectDir : projectConfig.dbt?.projectDir ?? '.');
|
|
425
437
|
const dbtProjectDir = resolve(projectRoot, projectDirInput);
|
|
426
438
|
const manifestInput = typeof body.manifestPath === 'string' ? body.manifestPath : projectConfig.dbt?.manifestPath ?? 'target/manifest.json';
|
|
427
439
|
const manifestPath = resolve(dbtProjectDir, manifestInput);
|
|
428
440
|
if (!(manifestPath === dbtProjectDir || manifestPath.startsWith(`${dbtProjectDir}/`))) {
|
|
429
441
|
throw Object.assign(new Error('dbt manifestPath must stay inside dbt projectDir.'), { code: 'DBT_ARTIFACT_INVALID' });
|
|
430
442
|
}
|
|
431
|
-
|
|
443
|
+
const profilesDirInput = typeof body.profilesDir === 'string'
|
|
444
|
+
? body.profilesDir
|
|
445
|
+
: projectConfig.dbt?.profilesDir;
|
|
446
|
+
const configuredProfilesDir = profilesDirInput ? resolve(projectRoot, profilesDirInput) : undefined;
|
|
447
|
+
const profilePath = findDbtProfilePaths(projectRoot, dbtProjectDir, configuredProfilesDir)[0];
|
|
448
|
+
const profilesDir = configuredProfilesDir ?? (profilePath ? dirname(profilePath) : undefined);
|
|
449
|
+
return { dbtProjectDir, manifestPath, projectDirInput, manifestInput, profilesDir, repoUrl, branch, subPath, repoWarnings: repo?.warnings ?? [] };
|
|
432
450
|
};
|
|
433
451
|
const previewDbtOnboarding = (body = {}) => {
|
|
434
452
|
const paths = onboardingDbtPaths(body);
|
|
@@ -445,8 +463,11 @@ export async function startLocalServer(opts) {
|
|
|
445
463
|
}
|
|
446
464
|
const fingerprint = createHash('sha256').update(readFileSync(paths.manifestPath)).digest('hex');
|
|
447
465
|
return {
|
|
448
|
-
projectDir:
|
|
466
|
+
projectDir: portableProjectPath(projectRoot, paths.dbtProjectDir),
|
|
449
467
|
manifestPath: relative(paths.dbtProjectDir, paths.manifestPath).replace(/\\/g, '/'),
|
|
468
|
+
...(paths.profilesDir ? { profilesDir: portableProjectPath(projectRoot, paths.profilesDir) } : {}),
|
|
469
|
+
...(paths.repoUrl ? { repoUrl: paths.repoUrl, branch: paths.branch, subPath: paths.subPath } : {}),
|
|
470
|
+
...(paths.repoWarnings.length ? { warnings: paths.repoWarnings } : {}),
|
|
450
471
|
fingerprint,
|
|
451
472
|
projectName: typeof raw.metadata?.project_name === 'string' ? raw.metadata.project_name : undefined,
|
|
452
473
|
counts: {
|
|
@@ -1900,17 +1921,19 @@ export async function startLocalServer(opts) {
|
|
|
1900
1921
|
// A real app needs a certified anchor: only offer confirmation when at least
|
|
1901
1922
|
// one certified tile is present. Otherwise fall through to the escalate path
|
|
1902
1923
|
// (research / draft the missing blocks) so the user isn't sent to a dead end.
|
|
1903
|
-
const proposed = Boolean(proposal && proposal.tiles.some((tile) => tile.certification === 'certified' && !tile.error));
|
|
1924
|
+
const proposed = Boolean(proposal && proposal.tiles.some((tile) => (tile.certification === 'certified' || tile.certification === 'reviewed_semantic') && !tile.error));
|
|
1904
1925
|
const sessionPlan = agentRunRecord(session.plan);
|
|
1905
1926
|
const appTitle = agentRunString(sessionPlan?.name) ?? 'App proposal';
|
|
1906
1927
|
const certifiedCount = proposal?.coverage.certifiedTiles ?? 0;
|
|
1907
1928
|
const generatedCount = proposal?.coverage.generatedTiles ?? 0;
|
|
1929
|
+
const semanticCount = proposal?.coverage.semanticTiles ?? 0;
|
|
1908
1930
|
const gapCount = proposal?.coverage.gaps ?? 0;
|
|
1909
1931
|
// A coverage gap is NOT terminal — leave status open so the gate can escalate to
|
|
1910
1932
|
// drafting the missing blocks. Only genuine infra errors (the catch above) block.
|
|
1911
1933
|
return {
|
|
1912
1934
|
summary: proposed
|
|
1913
1935
|
? `Proposed ${certifiedCount} certified tile${certifiedCount === 1 ? '' : 's'}`
|
|
1936
|
+
+ (semanticCount > 0 ? ` + ${semanticCount} reviewed semantic tile${semanticCount === 1 ? '' : 's'}` : '')
|
|
1914
1937
|
+ (generatedCount > 0 ? ` + ${generatedCount} AI-generated (review required)` : '')
|
|
1915
1938
|
+ (gapCount > 0 ? `; ${gapCount} question${gapCount === 1 ? '' : 's'} still uncovered` : '')
|
|
1916
1939
|
+ '. Review the list and confirm to create the app.'
|
|
@@ -1924,6 +1947,7 @@ export async function startLocalServer(opts) {
|
|
|
1924
1947
|
dashboardId: session.dashboardId,
|
|
1925
1948
|
plan: session.plan,
|
|
1926
1949
|
proposal,
|
|
1950
|
+
proposalHash: session.proposalHash,
|
|
1927
1951
|
warnings: session.warnings,
|
|
1928
1952
|
}, session.appId)] : [],
|
|
1929
1953
|
evaluations: [
|
|
@@ -3390,8 +3414,10 @@ export async function startLocalServer(opts) {
|
|
|
3390
3414
|
}
|
|
3391
3415
|
if (req.method === 'GET' && path === '/api/onboarding/status') {
|
|
3392
3416
|
const requestId = apiRequestId('onboarding-status');
|
|
3393
|
-
const dbtProjectDir =
|
|
3417
|
+
const dbtProjectDir = findDbtProjectPath(projectRoot, projectConfig);
|
|
3394
3418
|
const manifestPath = resolve(dbtProjectDir, projectConfig.dbt?.manifestPath ?? 'target/manifest.json');
|
|
3419
|
+
const configuredProfilesDir = projectConfig.dbt?.profilesDir ? resolve(projectRoot, projectConfig.dbt.profilesDir) : undefined;
|
|
3420
|
+
const profilePath = findDbtProfilePaths(projectRoot, dbtProjectDir, configuredProfilesDir)[0];
|
|
3395
3421
|
const registry = loadDomainPackageRegistry(projectRoot);
|
|
3396
3422
|
const manifestFound = existsSync(manifestPath);
|
|
3397
3423
|
let snapshotId;
|
|
@@ -3412,6 +3438,12 @@ export async function startLocalServer(opts) {
|
|
|
3412
3438
|
configured: Boolean(projectConfig.dbt),
|
|
3413
3439
|
projectDir: projectConfig.dbt?.projectDir,
|
|
3414
3440
|
manifestPath: projectConfig.dbt?.manifestPath,
|
|
3441
|
+
profilesDir: configuredProfilesDir
|
|
3442
|
+
? portableProjectPath(projectRoot, configuredProfilesDir)
|
|
3443
|
+
: profilePath ? portableProjectPath(projectRoot, dirname(profilePath)) : undefined,
|
|
3444
|
+
repoUrl: projectConfig.dbt?.repoUrl,
|
|
3445
|
+
branch: projectConfig.dbt?.branch,
|
|
3446
|
+
subPath: projectConfig.dbt?.subPath,
|
|
3415
3447
|
projectFound: existsSync(join(dbtProjectDir, 'dbt_project.yml')),
|
|
3416
3448
|
manifestFound,
|
|
3417
3449
|
},
|
|
@@ -3457,7 +3489,9 @@ export async function startLocalServer(opts) {
|
|
|
3457
3489
|
throw error;
|
|
3458
3490
|
const { dbtProjectDir } = onboardingDbtPaths(body);
|
|
3459
3491
|
try {
|
|
3460
|
-
|
|
3492
|
+
const { profilesDir } = onboardingDbtPaths(body);
|
|
3493
|
+
const parseArgs = buildDbtParseArgs(dbtProjectDir, profilesDir);
|
|
3494
|
+
execFileSync('dbt', parseArgs, {
|
|
3461
3495
|
cwd: dbtProjectDir,
|
|
3462
3496
|
timeout: 120_000,
|
|
3463
3497
|
maxBuffer: 1024 * 1024,
|
|
@@ -3483,16 +3517,24 @@ export async function startLocalServer(opts) {
|
|
|
3483
3517
|
...projectConfig,
|
|
3484
3518
|
manifestVersion: 3,
|
|
3485
3519
|
modeling: { mode: 'dbt-first' },
|
|
3486
|
-
dbt: {
|
|
3520
|
+
dbt: {
|
|
3521
|
+
projectDir: preview.projectDir,
|
|
3522
|
+
manifestPath: preview.manifestPath,
|
|
3523
|
+
...(preview.profilesDir ? { profilesDir: preview.profilesDir } : {}),
|
|
3524
|
+
...(preview.repoUrl ? { repoUrl: preview.repoUrl, branch: preview.branch, subPath: preview.subPath } : {}),
|
|
3525
|
+
},
|
|
3487
3526
|
semanticLayer: { provider: 'dbt', projectPath: preview.projectDir },
|
|
3488
3527
|
};
|
|
3489
3528
|
const configPath = join(projectRoot, 'dql.config.json');
|
|
3490
3529
|
const previousConfigSource = readFileSync(configPath, 'utf8');
|
|
3491
3530
|
const previousProjectConfig = projectConfig;
|
|
3531
|
+
const previousConnection = connection;
|
|
3492
3532
|
const tempPath = `${configPath}.tmp-${process.pid}`;
|
|
3493
3533
|
writeFileSync(tempPath, `${JSON.stringify(nextConfig, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
3494
3534
|
renameSync(tempPath, configPath);
|
|
3495
3535
|
projectConfig = nextConfig;
|
|
3536
|
+
if (!configuredConnection)
|
|
3537
|
+
connection = resolveDbtProfileRuntimeConnection(projectRoot, nextConfig);
|
|
3496
3538
|
projectSnapshots.invalidate();
|
|
3497
3539
|
invalidateAgentProjectState(projectRoot);
|
|
3498
3540
|
let snapshotId;
|
|
@@ -3504,6 +3546,7 @@ export async function startLocalServer(opts) {
|
|
|
3504
3546
|
writeFileSync(rollbackPath, previousConfigSource, { encoding: 'utf8', mode: 0o600 });
|
|
3505
3547
|
renameSync(rollbackPath, configPath);
|
|
3506
3548
|
projectConfig = previousProjectConfig;
|
|
3549
|
+
connection = previousConnection;
|
|
3507
3550
|
projectSnapshots.invalidate();
|
|
3508
3551
|
invalidateAgentProjectState(projectRoot);
|
|
3509
3552
|
throw Object.assign(new Error(`dbt-first configuration did not compile and was rolled back: ${error instanceof Error ? error.message : String(error)}`), { code: 'SNAPSHOT_BUILD_FAILED' });
|
|
@@ -4446,6 +4489,52 @@ export async function startLocalServer(opts) {
|
|
|
4446
4489
|
// ── Skills (spec 16) — user-authored business context. AI drafts, humans
|
|
4447
4490
|
// certify; skills never carry certification. PROJECT skills (user empty)
|
|
4448
4491
|
// are shared; PERSONAL skills (user set) are user-bound. ────────────────
|
|
4492
|
+
if (req.method === 'GET' && path === '/api/skills/settings') {
|
|
4493
|
+
try {
|
|
4494
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4495
|
+
res.end(serializeJSON(buildSkillPathSettings(projectRoot)));
|
|
4496
|
+
}
|
|
4497
|
+
catch (error) {
|
|
4498
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4499
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
4500
|
+
}
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
if (req.method === 'PUT' && path === '/api/skills/settings') {
|
|
4504
|
+
try {
|
|
4505
|
+
const body = (await readJSON(req).catch(() => ({})));
|
|
4506
|
+
const requestedPath = typeof body.path === 'string' ? body.path.trim() : '';
|
|
4507
|
+
if (!requestedPath || requestedPath.includes('\0')) {
|
|
4508
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4509
|
+
res.end(serializeJSON({ error: 'Provide an existing Skills folder path.' }));
|
|
4510
|
+
return;
|
|
4511
|
+
}
|
|
4512
|
+
const resolvedPath = resolve(projectRoot, requestedPath);
|
|
4513
|
+
if (!existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) {
|
|
4514
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4515
|
+
res.end(serializeJSON({ error: `Skills folder not found: ${resolvedPath}` }));
|
|
4516
|
+
return;
|
|
4517
|
+
}
|
|
4518
|
+
const configPath = join(projectRoot, 'dql.config.json');
|
|
4519
|
+
const raw = existsSync(configPath)
|
|
4520
|
+
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
4521
|
+
: {};
|
|
4522
|
+
const layout = raw.layout && typeof raw.layout === 'object' && !Array.isArray(raw.layout)
|
|
4523
|
+
? raw.layout
|
|
4524
|
+
: {};
|
|
4525
|
+
raw.layout = { ...layout, skillsPath: requestedPath };
|
|
4526
|
+
writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, 'utf-8');
|
|
4527
|
+
projectConfig = loadProjectConfig(projectRoot);
|
|
4528
|
+
invalidateAgentProjectState(projectRoot);
|
|
4529
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4530
|
+
res.end(serializeJSON(buildSkillPathSettings(projectRoot)));
|
|
4531
|
+
}
|
|
4532
|
+
catch (error) {
|
|
4533
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
4534
|
+
res.end(serializeJSON({ error: error instanceof Error ? error.message : String(error) }));
|
|
4535
|
+
}
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
4449
4538
|
if (req.method === 'GET' && path === '/api/skills') {
|
|
4450
4539
|
try {
|
|
4451
4540
|
const skills = loadSkills(projectRoot).skills
|
|
@@ -5148,6 +5237,33 @@ export async function startLocalServer(opts) {
|
|
|
5148
5237
|
}
|
|
5149
5238
|
return;
|
|
5150
5239
|
}
|
|
5240
|
+
const appStoryRun = path.match(/^\/api\/apps\/([^/]+)\/dashboards\/([^/]+)\/story$/);
|
|
5241
|
+
if (req.method === 'POST' && appStoryRun) {
|
|
5242
|
+
const body = await readJSON(req).catch(() => ({}));
|
|
5243
|
+
const runId = typeof body.runId === 'string' ? body.runId : '';
|
|
5244
|
+
const evidence = dashboardRunEvidence.get(runId);
|
|
5245
|
+
const appId = decodeURIComponent(appStoryRun[1]);
|
|
5246
|
+
const dashboardId = decodeURIComponent(appStoryRun[2]);
|
|
5247
|
+
if (!evidence || evidence.expiresAt < Date.now() || evidence.appId !== appId || evidence.dashboardId !== dashboardId) {
|
|
5248
|
+
dashboardRunEvidence.delete(runId);
|
|
5249
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
5250
|
+
res.end(serializeJSON({ error: 'Dashboard run evidence expired or does not match this App. Run the dashboard again.' }));
|
|
5251
|
+
return;
|
|
5252
|
+
}
|
|
5253
|
+
// The endpoint accepts runId only. Optional provider wording can be added
|
|
5254
|
+
// here later; the claim-checked deterministic brief is always available.
|
|
5255
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
5256
|
+
res.end(serializeJSON({
|
|
5257
|
+
runId,
|
|
5258
|
+
snapshotId: evidence.snapshotId,
|
|
5259
|
+
filterFingerprint: evidence.filterFingerprint,
|
|
5260
|
+
resultFingerprint: evidence.resultFingerprint,
|
|
5261
|
+
personaFingerprint: evidence.personaFingerprint,
|
|
5262
|
+
facts: evidence.facts,
|
|
5263
|
+
story: evidence.story,
|
|
5264
|
+
}));
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5151
5267
|
const appDashRun = path.match(/^\/api\/apps\/([^/]+)\/dashboards\/([^/]+)\/run$/);
|
|
5152
5268
|
if (req.method === 'POST' && appDashRun) {
|
|
5153
5269
|
try {
|
|
@@ -5160,6 +5276,12 @@ export async function startLocalServer(opts) {
|
|
|
5160
5276
|
res.end(serializeJSON({ error: `Dashboard "${dashboardId}" not found in app "${appId}"` }));
|
|
5161
5277
|
return;
|
|
5162
5278
|
}
|
|
5279
|
+
const activeApp = activePersonaAppId();
|
|
5280
|
+
if (activeApp && activeApp !== appId) {
|
|
5281
|
+
res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
5282
|
+
res.end(serializeJSON({ error: `The active persona belongs to App "${activeApp}", not "${appId}".` }));
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5163
5285
|
const manifest = buildManifest({ projectRoot });
|
|
5164
5286
|
const variables = body.variables && typeof body.variables === 'object'
|
|
5165
5287
|
? body.variables
|
|
@@ -5258,6 +5380,70 @@ export async function startLocalServer(opts) {
|
|
|
5258
5380
|
});
|
|
5259
5381
|
continue;
|
|
5260
5382
|
}
|
|
5383
|
+
if (item.semantic) {
|
|
5384
|
+
try {
|
|
5385
|
+
if (!semanticLayer)
|
|
5386
|
+
throw new Error('Semantic layer is not available for this governed query.');
|
|
5387
|
+
const knownMetrics = new Set(semanticLayer.listMetrics().map((metric) => metric.name));
|
|
5388
|
+
const missingMetrics = item.semantic.metrics.filter((metric) => !knownMetrics.has(metric));
|
|
5389
|
+
if (missingMetrics.length)
|
|
5390
|
+
throw new Error(`Semantic definition changed; missing metric(s): ${missingMetrics.join(', ')}`);
|
|
5391
|
+
const knownModels = new Set(semanticLayer.listSemanticModels().map((model) => model.name));
|
|
5392
|
+
const missingModels = item.semantic.semanticModelRefs.filter((model) => !knownModels.has(model));
|
|
5393
|
+
if (missingModels.length)
|
|
5394
|
+
throw new Error(`Semantic definition changed; missing model(s): ${missingModels.join(', ')}`);
|
|
5395
|
+
const targetConnection = requireActiveConnection(isConnectionConfig(body.connection) ? body.connection : connection);
|
|
5396
|
+
const tableMapping = await resolveSemanticTableMapping(executor, targetConnection, semanticLayer);
|
|
5397
|
+
const activeFilters = Object.entries(dashboardVariables)
|
|
5398
|
+
.filter(([field, value]) => item.semantic.dimensions?.includes(field) && value !== undefined && value !== null && value !== '')
|
|
5399
|
+
.map(([dimension, value]) => ({ dimension, operator: 'equals', values: Array.isArray(value) ? value.map(String) : [String(value)] }));
|
|
5400
|
+
const staticFilters = (item.semantic.filters ?? []).map((filter) => ({
|
|
5401
|
+
dimension: filter.field,
|
|
5402
|
+
operator: filter.operator,
|
|
5403
|
+
values: Array.isArray(filter.value) ? filter.value.map(String) : [String(filter.value)],
|
|
5404
|
+
}));
|
|
5405
|
+
const composed = composeRuntimeSemanticQuery({
|
|
5406
|
+
metrics: item.semantic.metrics,
|
|
5407
|
+
dimensions: item.semantic.dimensions ?? [],
|
|
5408
|
+
filters: [...staticFilters, ...activeFilters],
|
|
5409
|
+
...(item.semantic.timeDimension ? { timeDimension: { name: item.semantic.timeDimension, granularity: 'month' } } : {}),
|
|
5410
|
+
orderBy: item.semantic.orderBy?.map((order) => ({ name: order.field, direction: order.direction })),
|
|
5411
|
+
limit: item.semantic.limit,
|
|
5412
|
+
engine: item.semantic.provider,
|
|
5413
|
+
}, semanticLayer, {
|
|
5414
|
+
projectRoot,
|
|
5415
|
+
projectConfig,
|
|
5416
|
+
detectedProvider: semanticDetectedProvider,
|
|
5417
|
+
driver: targetConnection.driver,
|
|
5418
|
+
tableMapping,
|
|
5419
|
+
});
|
|
5420
|
+
if (!composed)
|
|
5421
|
+
throw new Error('The governed semantic query could not be composed.');
|
|
5422
|
+
const prepared = prepareLocalExecution(composed.sql, targetConnection, projectRoot, projectConfig);
|
|
5423
|
+
const result = await executor.executeQuery(prepared.sql, [], {}, prepared.connection);
|
|
5424
|
+
tiles.push({
|
|
5425
|
+
tileId: item.i,
|
|
5426
|
+
status: 'ok',
|
|
5427
|
+
tileType: 'semantic',
|
|
5428
|
+
title: item.title ?? item.semantic.id,
|
|
5429
|
+
viz: item.viz,
|
|
5430
|
+
result: normalizeQueryResult(result),
|
|
5431
|
+
trustState: 'review_required',
|
|
5432
|
+
citation: { kind: 'semantic_query', name: item.semantic.id },
|
|
5433
|
+
});
|
|
5434
|
+
}
|
|
5435
|
+
catch (err) {
|
|
5436
|
+
tiles.push({
|
|
5437
|
+
tileId: item.i,
|
|
5438
|
+
status: 'error',
|
|
5439
|
+
tileType: 'semantic',
|
|
5440
|
+
title: item.title ?? item.semantic.id,
|
|
5441
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5442
|
+
trustState: 'review_required',
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
continue;
|
|
5446
|
+
}
|
|
5261
5447
|
const block = resolveDashboardItemBlock(item, manifest);
|
|
5262
5448
|
if (!block) {
|
|
5263
5449
|
tiles.push({
|
|
@@ -5381,12 +5567,55 @@ export async function startLocalServer(opts) {
|
|
|
5381
5567
|
}
|
|
5382
5568
|
}
|
|
5383
5569
|
localApps?.close();
|
|
5570
|
+
const snapshot = projectSnapshot();
|
|
5571
|
+
const filterFingerprint = createHash('sha256').update(JSON.stringify(dashboardVariables)).digest('hex');
|
|
5572
|
+
const resultFingerprint = createHash('sha256').update(JSON.stringify(tiles.map((tile) => ({ tileId: tile.tileId, status: tile.status, result: tile.result })))).digest('hex');
|
|
5573
|
+
const personaFingerprint = createHash('sha256').update(activePersonaAppId() ?? 'global').digest('hex');
|
|
5574
|
+
const runId = `app_run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5575
|
+
const storyResult = buildDeterministicDashboardStory({
|
|
5576
|
+
goal: loaded.dashboard.story?.goal ?? loaded.dashboard.metadata.businessOutcome ?? loaded.dashboard.metadata.title,
|
|
5577
|
+
audience: loaded.dashboard.story?.audience ?? loaded.dashboard.metadata.audience,
|
|
5578
|
+
filters: dashboardVariables,
|
|
5579
|
+
tiles: tiles.map((tile) => ({
|
|
5580
|
+
tileId: tile.tileId,
|
|
5581
|
+
title: typeof tile.title === 'string' ? tile.title : tile.tileId,
|
|
5582
|
+
status: tile.status,
|
|
5583
|
+
trustState: (tile.trustState === 'certified' || tile.trustState === 'review_required' || tile.trustState === 'draft_ready'
|
|
5584
|
+
? tile.trustState
|
|
5585
|
+
: tile.certificationStatus === 'certified' ? 'certified' : 'review_required'),
|
|
5586
|
+
result: tile.result,
|
|
5587
|
+
citation: tile.citation,
|
|
5588
|
+
})),
|
|
5589
|
+
eligibleTileIds: loaded.dashboard.story?.eligibleTileIds,
|
|
5590
|
+
driverTileIds: loaded.dashboard.story?.driverTileIds,
|
|
5591
|
+
});
|
|
5592
|
+
dashboardRunEvidence.set(runId, {
|
|
5593
|
+
appId,
|
|
5594
|
+
dashboardId,
|
|
5595
|
+
snapshotId: snapshot.snapshotId,
|
|
5596
|
+
filterFingerprint,
|
|
5597
|
+
resultFingerprint,
|
|
5598
|
+
personaFingerprint,
|
|
5599
|
+
facts: storyResult.facts,
|
|
5600
|
+
story: storyResult.story,
|
|
5601
|
+
expiresAt: Date.now() + 15 * 60_000,
|
|
5602
|
+
});
|
|
5603
|
+
for (const [id, evidence] of dashboardRunEvidence)
|
|
5604
|
+
if (evidence.expiresAt < Date.now())
|
|
5605
|
+
dashboardRunEvidence.delete(id);
|
|
5384
5606
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
5385
5607
|
res.end(serializeJSON({
|
|
5386
5608
|
appId,
|
|
5387
5609
|
dashboardId,
|
|
5388
5610
|
persona: activePersonaAppId() ? { appId: activePersonaAppId() } : null,
|
|
5611
|
+
runId,
|
|
5612
|
+
snapshotId: snapshot.snapshotId,
|
|
5613
|
+
filterFingerprint,
|
|
5614
|
+
resultFingerprint,
|
|
5615
|
+
personaFingerprint,
|
|
5389
5616
|
tiles,
|
|
5617
|
+
facts: storyResult.facts,
|
|
5618
|
+
story: storyResult.story,
|
|
5390
5619
|
}));
|
|
5391
5620
|
}
|
|
5392
5621
|
catch (err) {
|
|
@@ -6691,16 +6920,26 @@ export async function startLocalServer(opts) {
|
|
|
6691
6920
|
res.end(serializeJSON({ error: 'goal is required' }));
|
|
6692
6921
|
return;
|
|
6693
6922
|
}
|
|
6694
|
-
const
|
|
6695
|
-
const
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6923
|
+
const snapshot = projectSnapshot();
|
|
6924
|
+
const kgPath = defaultKgPath(projectRoot);
|
|
6925
|
+
await reindexProject(projectRoot, { kgPath, manifest: snapshot.manifest });
|
|
6926
|
+
const kg = new KGStore(kgPath);
|
|
6927
|
+
let plan;
|
|
6928
|
+
try {
|
|
6929
|
+
plan = planAppFromPrompt({
|
|
6930
|
+
prompt: goal,
|
|
6931
|
+
kg,
|
|
6932
|
+
domain: typeof body.domain === 'string' ? body.domain : undefined,
|
|
6933
|
+
audience: typeof body.audience === 'string' ? body.audience : undefined,
|
|
6934
|
+
mode: body.mode === 'personal' ? 'personal' : 'stakeholder',
|
|
6935
|
+
snapshotId: snapshot.snapshotId,
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
finally {
|
|
6939
|
+
kg.close();
|
|
6940
|
+
}
|
|
6702
6941
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
6703
|
-
res.end(serializeJSON({ plan,
|
|
6942
|
+
res.end(serializeJSON({ plan, snapshotId: snapshot.snapshotId }));
|
|
6704
6943
|
}
|
|
6705
6944
|
catch (error) {
|
|
6706
6945
|
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
@@ -7595,9 +7834,35 @@ export async function startLocalServer(opts) {
|
|
|
7595
7834
|
?? Object.keys(connections)[0]
|
|
7596
7835
|
?? 'default';
|
|
7597
7836
|
const dbtProfiles = discoverDbtProfileConnections(projectRoot, cfg);
|
|
7837
|
+
const activeDbtProfile = dbtProfiles.find((candidate) => candidate.missingFields.length === 0
|
|
7838
|
+
&& !candidate.warnings.some((warning) => warning.startsWith('DuckDB file not found'))
|
|
7839
|
+
&& !candidate.warnings.some((warning) => warning.startsWith('Not the default dbt target')));
|
|
7840
|
+
const activeConnection = connection ? {
|
|
7841
|
+
source: Object.keys(connections).length > 0 ? 'dql_config' : activeDbtProfile ? 'dbt_profile' : 'runtime',
|
|
7842
|
+
driver: connection.driver,
|
|
7843
|
+
...(Object.keys(connections).length === 0 && activeDbtProfile ? { profileId: activeDbtProfile.id } : {}),
|
|
7844
|
+
} : null;
|
|
7598
7845
|
const connectorStatus = getConnectorInstallStatuses(projectRoot);
|
|
7599
7846
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7600
|
-
res.end(serializeJSON({ default: defaultKey, connections, dbtProfiles, connectorStatus }));
|
|
7847
|
+
res.end(serializeJSON({ default: defaultKey, connections, dbtProfiles, activeConnection, connectorStatus }));
|
|
7848
|
+
return;
|
|
7849
|
+
}
|
|
7850
|
+
if (req.method === 'POST' && path === '/api/connections/dbt-profiles/preview') {
|
|
7851
|
+
const body = await readJSON(req).catch(() => ({}));
|
|
7852
|
+
const profilePath = typeof body.path === 'string' ? body.path.trim() : '';
|
|
7853
|
+
if (!profilePath) {
|
|
7854
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7855
|
+
res.end(serializeJSON({ error: 'Enter a profiles.yml file or folder path.' }));
|
|
7856
|
+
return;
|
|
7857
|
+
}
|
|
7858
|
+
const dbtProfiles = discoverDbtProfileConnections(projectRoot, loadProjectConfig(projectRoot), profilePath);
|
|
7859
|
+
if (dbtProfiles.length === 0) {
|
|
7860
|
+
res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7861
|
+
res.end(serializeJSON({ error: 'No supported dbt profile targets were found at that path.' }));
|
|
7862
|
+
return;
|
|
7863
|
+
}
|
|
7864
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7865
|
+
res.end(serializeJSON({ dbtProfiles }));
|
|
7601
7866
|
return;
|
|
7602
7867
|
}
|
|
7603
7868
|
if (req.method === "GET" && path === "/api/datasets") {
|
|
@@ -10663,6 +10928,18 @@ export function loadProjectConfig(projectRoot) {
|
|
|
10663
10928
|
}
|
|
10664
10929
|
return config;
|
|
10665
10930
|
}
|
|
10931
|
+
/** Current Git-backed Skills folder and what the loader can read from it. */
|
|
10932
|
+
export function buildSkillPathSettings(projectRoot) {
|
|
10933
|
+
const resolvedPath = skillsDir(projectRoot);
|
|
10934
|
+
const loaded = loadSkills(projectRoot);
|
|
10935
|
+
return {
|
|
10936
|
+
path: configuredSkillsPath(projectRoot),
|
|
10937
|
+
resolvedPath,
|
|
10938
|
+
exists: existsSync(resolvedPath) && statSync(resolvedPath).isDirectory(),
|
|
10939
|
+
skillCount: loaded.skills.filter((skill) => skill.scope === 'project').length,
|
|
10940
|
+
errorCount: loaded.errors.length,
|
|
10941
|
+
};
|
|
10942
|
+
}
|
|
10666
10943
|
function dbtFirstOwnsSemanticAuthoring(config) {
|
|
10667
10944
|
return config.manifestVersion === 3 && config.modeling?.mode === 'dbt-first';
|
|
10668
10945
|
}
|
|
@@ -16202,15 +16479,23 @@ function resolveDbtManifestPath(projectRoot, projectConfig = {}) {
|
|
|
16202
16479
|
candidates.push(join(projectRoot, 'target', 'manifest.json'), join(resolve(projectRoot, '..'), 'target', 'manifest.json'), join(resolve(projectRoot, '../dbt'), 'target', 'manifest.json'), join(resolve(projectRoot, '../../dbt'), 'target', 'manifest.json'));
|
|
16203
16480
|
return candidates.find((candidate, index, list) => list.indexOf(candidate) === index && existsSync(candidate));
|
|
16204
16481
|
}
|
|
16205
|
-
export function discoverDbtProfileConnections(projectRoot, projectConfig) {
|
|
16482
|
+
export function discoverDbtProfileConnections(projectRoot, projectConfig, explicitPath) {
|
|
16206
16483
|
const dbtProjectPath = findDbtProjectPath(projectRoot, projectConfig);
|
|
16207
|
-
const projectProfileName = readDbtProjectProfileName(dbtProjectPath);
|
|
16208
|
-
const
|
|
16484
|
+
const projectProfileName = explicitPath ? null : readDbtProjectProfileName(dbtProjectPath);
|
|
16485
|
+
const configuredProfilesDir = projectConfig.dbt?.profilesDir
|
|
16486
|
+
? resolve(projectRoot, projectConfig.dbt.profilesDir)
|
|
16487
|
+
: undefined;
|
|
16488
|
+
const profilePaths = explicitPath
|
|
16489
|
+
? findExplicitDbtProfilePaths(projectRoot, explicitPath)
|
|
16490
|
+
: findDbtProfilePaths(projectRoot, dbtProjectPath, configuredProfilesDir);
|
|
16209
16491
|
const candidates = [];
|
|
16210
16492
|
for (const profilePath of profilePaths) {
|
|
16211
16493
|
const profiles = readYamlFile(profilePath);
|
|
16212
16494
|
if (!profiles)
|
|
16213
16495
|
continue;
|
|
16496
|
+
const profileRelativeBase = explicitPath && existsSync(join(dirname(profilePath), 'dbt_project.yml'))
|
|
16497
|
+
? dirname(profilePath)
|
|
16498
|
+
: dbtProjectPath;
|
|
16214
16499
|
for (const [profileName, rawProfile] of Object.entries(profiles)) {
|
|
16215
16500
|
if (!rawProfile || typeof rawProfile !== 'object')
|
|
16216
16501
|
continue;
|
|
@@ -16234,7 +16519,7 @@ export function discoverDbtProfileConnections(projectRoot, projectConfig) {
|
|
|
16234
16519
|
mapped.connection.filepath &&
|
|
16235
16520
|
mapped.connection.filepath !== ':memory:') {
|
|
16236
16521
|
if (!isAbsoluteLikePath(mapped.connection.filepath)) {
|
|
16237
|
-
mapped.connection.filepath = resolve(
|
|
16522
|
+
mapped.connection.filepath = resolve(profileRelativeBase, mapped.connection.filepath);
|
|
16238
16523
|
}
|
|
16239
16524
|
if (!existsSync(mapped.connection.filepath)) {
|
|
16240
16525
|
mapped.warnings.push(`DuckDB file not found at ${mapped.connection.filepath} — run \`dbt build\` (or \`dbt seed\`) to create it before querying.`);
|
|
@@ -16259,6 +16544,21 @@ export function discoverDbtProfileConnections(projectRoot, projectConfig) {
|
|
|
16259
16544
|
}
|
|
16260
16545
|
return candidates.slice(0, 20);
|
|
16261
16546
|
}
|
|
16547
|
+
export function buildDbtParseArgs(dbtProjectDir, profilesDir) {
|
|
16548
|
+
return [
|
|
16549
|
+
'parse',
|
|
16550
|
+
'--project-dir', dbtProjectDir,
|
|
16551
|
+
...(profilesDir ? ['--profiles-dir', profilesDir] : []),
|
|
16552
|
+
];
|
|
16553
|
+
}
|
|
16554
|
+
/** CFG-003: use a complete default dbt target when no saved DQL connection exists. */
|
|
16555
|
+
export function resolveDbtProfileRuntimeConnection(projectRoot, projectConfig) {
|
|
16556
|
+
const candidates = discoverDbtProfileConnections(projectRoot, projectConfig);
|
|
16557
|
+
const complete = candidates.filter((candidate) => candidate.missingFields.length === 0
|
|
16558
|
+
&& !candidate.warnings.some((warning) => warning.startsWith('DuckDB file not found')));
|
|
16559
|
+
const defaultTarget = complete.find((candidate) => !candidate.warnings.some((warning) => warning.startsWith('Not the default dbt target')));
|
|
16560
|
+
return defaultTarget?.connection ?? (complete.length === 1 ? complete[0].connection : null);
|
|
16561
|
+
}
|
|
16262
16562
|
function findDbtProjectPath(projectRoot, projectConfig) {
|
|
16263
16563
|
const configuredDbtDir = projectConfig.dbt?.projectDir
|
|
16264
16564
|
? resolve(projectRoot, projectConfig.dbt.projectDir)
|
|
@@ -16279,8 +16579,18 @@ function findDbtProjectPath(projectRoot, projectConfig) {
|
|
|
16279
16579
|
?? semanticDbtDir
|
|
16280
16580
|
?? projectRoot;
|
|
16281
16581
|
}
|
|
16282
|
-
function
|
|
16582
|
+
function portableProjectPath(projectRoot, absolutePath) {
|
|
16583
|
+
const normalizedAbsolute = resolve(absolutePath).replace(/\\/g, '/');
|
|
16584
|
+
const relativePath = relative(projectRoot, absolutePath).replace(/\\/g, '/');
|
|
16585
|
+
return !relativePath
|
|
16586
|
+
? '.'
|
|
16587
|
+
: relativePath === '..' || relativePath.startsWith('../')
|
|
16588
|
+
? normalizedAbsolute
|
|
16589
|
+
: relativePath;
|
|
16590
|
+
}
|
|
16591
|
+
function findDbtProfilePaths(projectRoot, dbtProjectPath, configuredProfilesDir) {
|
|
16283
16592
|
const dirs = [
|
|
16593
|
+
configuredProfilesDir,
|
|
16284
16594
|
process.env.DBT_PROFILES_DIR,
|
|
16285
16595
|
dbtProjectPath,
|
|
16286
16596
|
projectRoot,
|
|
@@ -16288,7 +16598,7 @@ function findDbtProfilePaths(projectRoot, dbtProjectPath) {
|
|
|
16288
16598
|
].filter((value) => Boolean(value));
|
|
16289
16599
|
const paths = [];
|
|
16290
16600
|
for (const dir of dirs) {
|
|
16291
|
-
for (const filename of ['profiles.yml', 'profiles.yaml']) {
|
|
16601
|
+
for (const filename of ['profiles.yml', 'profiles.yaml', 'profile.yml', 'profile.yaml']) {
|
|
16292
16602
|
const profilePath = resolve(dir, filename);
|
|
16293
16603
|
if (existsSync(profilePath) && !paths.includes(profilePath)) {
|
|
16294
16604
|
paths.push(profilePath);
|
|
@@ -16297,6 +16607,24 @@ function findDbtProfilePaths(projectRoot, dbtProjectPath) {
|
|
|
16297
16607
|
}
|
|
16298
16608
|
return paths;
|
|
16299
16609
|
}
|
|
16610
|
+
function findExplicitDbtProfilePaths(projectRoot, input) {
|
|
16611
|
+
const trimmed = input.trim();
|
|
16612
|
+
if (!trimmed)
|
|
16613
|
+
return [];
|
|
16614
|
+
const expanded = trimmed === '~'
|
|
16615
|
+
? homedir()
|
|
16616
|
+
: trimmed.startsWith('~/')
|
|
16617
|
+
? join(homedir(), trimmed.slice(2))
|
|
16618
|
+
: trimmed;
|
|
16619
|
+
const absolute = resolve(projectRoot, expanded);
|
|
16620
|
+
if (!existsSync(absolute))
|
|
16621
|
+
return [];
|
|
16622
|
+
if (!statSync(absolute).isDirectory())
|
|
16623
|
+
return [absolute];
|
|
16624
|
+
return ['profiles.yml', 'profiles.yaml', 'profile.yml', 'profile.yaml']
|
|
16625
|
+
.map((filename) => join(absolute, filename))
|
|
16626
|
+
.filter((profilePath) => existsSync(profilePath));
|
|
16627
|
+
}
|
|
16300
16628
|
function readDbtProjectProfileName(dbtProjectPath) {
|
|
16301
16629
|
const projectFile = join(dbtProjectPath, 'dbt_project.yml');
|
|
16302
16630
|
const projectYaml = readYamlFile(projectFile);
|