Skip to content

Commit fdb8519

Browse files
anandgupta42claude
andcommitted
fix(dbt-tools): address code review findings for config auto-discovery
- Add `ALTIMATE_CODE_PYTHON_PATH` as highest priority in `discoverPython()` so VS Code's explicit Python selection is not silently ignored - Align `ALTIMATE_CODE_VIRTUAL_ENV` priority: move to tier 4 in `resolveDbt()` (before project-local `.venv`) to match `discoverPython()` priority and prevent Python/dbt resolving from different environments - Add `JSON.parse` error handling in `read()` — malformed config file now falls back to auto-discovery instead of crashing - Fix stale inline comment numbering in `dbt-resolve.ts` (1-11) - Fix environment-dependent `read returns null` test by setting CWD to temp dir - Add tests: `ALTIMATE_CODE_PYTHON_PATH` priority, malformed config fallback - Run prettier on `dbt-resolve.ts` and `dbt-resolve.test.ts` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b4b402b commit fdb8519

4 files changed

Lines changed: 113 additions & 35 deletions

File tree

packages/dbt-tools/src/config.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ export function findProjectRoot(start = process.cwd()): string | null {
3535

3636
/**
3737
* Discover the Python binary for a given project root.
38-
* Priority: ALTIMATE_CODE_VIRTUAL_ENV → project-local .venv → VIRTUAL_ENV → CONDA_PREFIX → which python3
38+
* Priority: ALTIMATE_CODE_PYTHON_PATH → ALTIMATE_CODE_VIRTUAL_ENV → project-local .venv → VIRTUAL_ENV → CONDA_PREFIX → which python3
3939
*/
4040
export function discoverPython(projectRoot: string): string {
41+
// ALTIMATE_CODE_PYTHON_PATH (explicit selection from vscode-altimate-mcp-server — highest priority)
42+
const altPython = process.env.ALTIMATE_CODE_PYTHON_PATH
43+
if (altPython && existsSync(altPython)) return altPython
44+
4145
// ALTIMATE_CODE_VIRTUAL_ENV (injected by vscode-altimate-mcp-server — explicit user selection wins)
4246
const altVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
4347
if (altVenv) {
@@ -85,8 +89,12 @@ export function discoverPython(projectRoot: string): string {
8589
async function read(): Promise<Config | null> {
8690
const p = configPath()
8791
if (existsSync(p)) {
88-
const raw = await readFile(p, "utf-8")
89-
return JSON.parse(raw) as Config
92+
try {
93+
const raw = await readFile(p, "utf-8")
94+
return JSON.parse(raw) as Config
95+
} catch {
96+
// Malformed config — fall through to auto-discovery
97+
}
9098
}
9199
// No config file — auto-discover from cwd so `altimate-dbt init` isn't required
92100
const projectRoot = findProjectRoot()

packages/dbt-tools/src/dbt-resolve.ts

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@ export interface ResolvedDbt {
4545
* 1. ALTIMATE_DBT_PATH env var (explicit user override)
4646
* 2. Sibling of ALTIMATE_CODE_PYTHON_PATH (set by vscode-altimate-mcp-server)
4747
* 3. Sibling of configured pythonPath (same venv/bin)
48-
* 4. Project-local .venv/bin/dbt (uv, pdm, venv, rye, poetry in-project)
49-
* 5. CONDA_PREFIX/bin/dbt (conda environments)
50-
* 6. ALTIMATE_CODE_VIRTUAL_ENV/bin/dbt (set by vscode-altimate-mcp-server)
48+
* 4. ALTIMATE_CODE_VIRTUAL_ENV/bin/dbt (set by vscode-altimate-mcp-server)
49+
* 5. Project-local .venv/bin/dbt (uv, pdm, venv, rye, poetry in-project)
50+
* 6. CONDA_PREFIX/bin/dbt (conda environments)
5151
* 7. VIRTUAL_ENV/bin/dbt (activated venv)
5252
* 8. Pyenv real path resolution (follow shims)
53-
* 9. `which dbt` on current PATH
54-
* 10. Common known locations (~/.local/bin/dbt for pipx, etc.)
53+
* 9. asdf/mise shim resolution
54+
* 10. `which dbt` on current PATH
55+
* 11. Common known locations (~/.local/bin/dbt for pipx, etc.)
5556
*
5657
* Each candidate is validated by checking it exists and is executable.
5758
*/
@@ -88,15 +89,29 @@ export function resolveDbt(pythonPath?: string, projectRoot?: string): ResolvedD
8889
} catch {}
8990
}
9091

91-
// 3. Project-local .venv/bin/dbt (uv, pdm, venv, poetry in-project, rye)
92+
// 4. ALTIMATE_CODE_VIRTUAL_ENV (injected by vscode-altimate-mcp-server, avoids conflicts with user's VIRTUAL_ENV)
93+
const altVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
94+
if (altVenv) {
95+
candidates.push({
96+
path: join(altVenv, "bin", "dbt"),
97+
source: `ALTIMATE_CODE_VIRTUAL_ENV (${altVenv})`,
98+
binDir: join(altVenv, "bin"),
99+
})
100+
}
101+
102+
// 5. Project-local .venv/bin/dbt (uv, pdm, venv, poetry in-project, rye)
92103
if (projectRoot) {
93104
for (const venvDir of [".venv", "venv", "env"]) {
94105
const localDbt = join(projectRoot, venvDir, "bin", "dbt")
95-
candidates.push({ path: localDbt, source: `${venvDir}/ in project root`, binDir: join(projectRoot, venvDir, "bin") })
106+
candidates.push({
107+
path: localDbt,
108+
source: `${venvDir}/ in project root`,
109+
binDir: join(projectRoot, venvDir, "bin"),
110+
})
96111
}
97112
}
98113

99-
// 5. CONDA_PREFIX (conda/mamba/micromamba — set after `conda activate`)
114+
// 6. CONDA_PREFIX (conda/mamba/micromamba — set after `conda activate`)
100115
const condaPrefix = process.env.CONDA_PREFIX
101116
if (condaPrefix) {
102117
candidates.push({
@@ -106,16 +121,6 @@ export function resolveDbt(pythonPath?: string, projectRoot?: string): ResolvedD
106121
})
107122
}
108123

109-
// 6. ALTIMATE_CODE_VIRTUAL_ENV (injected by vscode-altimate-mcp-server, avoids conflicts with user's VIRTUAL_ENV)
110-
const altVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
111-
if (altVenv) {
112-
candidates.push({
113-
path: join(altVenv, "bin", "dbt"),
114-
source: `ALTIMATE_CODE_VIRTUAL_ENV (${altVenv})`,
115-
binDir: join(altVenv, "bin"),
116-
})
117-
}
118-
119124
// 7. VIRTUAL_ENV (set by venv/virtualenv activate scripts)
120125
const virtualEnv = process.env.VIRTUAL_ENV
121126
if (virtualEnv) {
@@ -162,7 +167,7 @@ export function resolveDbt(pythonPath?: string, projectRoot?: string): ResolvedD
162167
} catch {}
163168
}
164169

165-
// 8. `which dbt` on current PATH (catches pipx ~/.local/bin, system pip, homebrew, etc.)
170+
// 10. `which dbt` on current PATH (catches pipx ~/.local/bin, system pip, homebrew, etc.)
166171
try {
167172
const whichDbt = execFileSync("which", ["dbt"], {
168173
encoding: "utf-8",
@@ -174,7 +179,7 @@ export function resolveDbt(pythonPath?: string, projectRoot?: string): ResolvedD
174179
}
175180
} catch {}
176181

177-
// 9. Common known locations (last resort)
182+
// 11. Common known locations (last resort)
178183
const home = process.env.HOME ?? ""
179184
const knownPaths = [
180185
{ path: join(home, ".local", "bin", "dbt"), source: "~/.local/bin/dbt (pipx/user pip)" },
@@ -202,9 +207,7 @@ export function resolveDbt(pythonPath?: string, projectRoot?: string): ResolvedD
202207
*/
203208
export function validateDbt(resolved: ResolvedDbt): { version: string; isFusion: boolean } | null {
204209
try {
205-
const env = resolved.binDir
206-
? { ...process.env, PATH: `${resolved.binDir}:${process.env.PATH}` }
207-
: process.env
210+
const env = resolved.binDir ? { ...process.env, PATH: `${resolved.binDir}:${process.env.PATH}` } : process.env
208211

209212
const out = execFileSync(resolved.path, ["--version"], {
210213
encoding: "utf-8",

packages/dbt-tools/test/config.test.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ describe("config", () => {
2020
})
2121

2222
test("read returns null for missing file", async () => {
23-
const { read } = await import("../src/config")
24-
const result = await read()
25-
expect(result).toBeNull()
23+
const origCwd = process.cwd()
24+
process.chdir(dir)
25+
try {
26+
const { read } = await import("../src/config")
27+
const result = await read()
28+
expect(result).toBeNull()
29+
} finally {
30+
process.chdir(origCwd)
31+
}
2632
})
2733

2834
test("write and read round-trip", async () => {
@@ -76,6 +82,31 @@ describe("config", () => {
7682
}
7783
})
7884

85+
test("read falls back to auto-discovery on malformed config file", async () => {
86+
const { read } = await import("../src/config")
87+
// Write a malformed JSON config file
88+
const configDir = join(dir, ".altimate-code")
89+
await mkdir(configDir, { recursive: true })
90+
await writeFile(join(configDir, "dbt.json"), "{ invalid json !!!")
91+
92+
// Create a dbt project so auto-discovery has something to find
93+
await writeFile(join(dir, "dbt_project.yml"), "name: test")
94+
const binDir = join(dir, ".venv", "bin")
95+
await mkdir(binDir, { recursive: true })
96+
await writeFile(join(binDir, "python3"), "#!/bin/sh")
97+
98+
const origCwd = process.cwd()
99+
process.chdir(dir)
100+
try {
101+
const result = await read()
102+
// Should fall through to auto-discovery instead of crashing
103+
expect(result).not.toBeNull()
104+
expect(result!.dbtIntegration).toBe("corecommand")
105+
} finally {
106+
process.chdir(origCwd)
107+
}
108+
})
109+
79110
test("read returns null when no config file and no dbt_project.yml in cwd", async () => {
80111
// dir has no dbt_project.yml and HOME has no config file
81112
const origCwd = process.cwd()
@@ -138,6 +169,32 @@ describe("discoverPython", () => {
138169
await rm(dir, { recursive: true, force: true })
139170
})
140171

172+
test("ALTIMATE_CODE_PYTHON_PATH takes highest priority", async () => {
173+
const { discoverPython } = await import("../src/config")
174+
175+
const altPythonBin = join(dir, "alt-python", "bin")
176+
await mkdir(altPythonBin, { recursive: true })
177+
await writeFile(join(altPythonBin, "python3"), "#!/bin/sh")
178+
179+
const localBin = join(dir, "project", ".venv", "bin")
180+
await mkdir(localBin, { recursive: true })
181+
await writeFile(join(localBin, "python3"), "#!/bin/sh")
182+
183+
const origPython = process.env.ALTIMATE_CODE_PYTHON_PATH
184+
const origVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
185+
process.env.ALTIMATE_CODE_PYTHON_PATH = join(altPythonBin, "python3")
186+
process.env.ALTIMATE_CODE_VIRTUAL_ENV = join(dir, "project", ".venv")
187+
try {
188+
const result = discoverPython(join(dir, "project"))
189+
expect(result).toBe(join(altPythonBin, "python3"))
190+
} finally {
191+
if (origPython !== undefined) process.env.ALTIMATE_CODE_PYTHON_PATH = origPython
192+
else delete process.env.ALTIMATE_CODE_PYTHON_PATH
193+
if (origVenv !== undefined) process.env.ALTIMATE_CODE_VIRTUAL_ENV = origVenv
194+
else delete process.env.ALTIMATE_CODE_VIRTUAL_ENV
195+
}
196+
})
197+
141198
test("ALTIMATE_CODE_VIRTUAL_ENV takes priority over project-local .venv", async () => {
142199
const { discoverPython } = await import("../src/config")
143200

@@ -163,8 +220,10 @@ describe("discoverPython", () => {
163220
test("falls back to project-local .venv/bin/python3", async () => {
164221
const { discoverPython } = await import("../src/config")
165222

166-
const orig = process.env.ALTIMATE_CODE_VIRTUAL_ENV
223+
const origVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
224+
const origPython = process.env.ALTIMATE_CODE_PYTHON_PATH
167225
delete process.env.ALTIMATE_CODE_VIRTUAL_ENV
226+
delete process.env.ALTIMATE_CODE_PYTHON_PATH
168227

169228
const binDir = join(dir, ".venv", "bin")
170229
await mkdir(binDir, { recursive: true })
@@ -174,15 +233,18 @@ describe("discoverPython", () => {
174233
const result = discoverPython(dir)
175234
expect(result).toBe(join(binDir, "python3"))
176235
} finally {
177-
if (orig !== undefined) process.env.ALTIMATE_CODE_VIRTUAL_ENV = orig
236+
if (origVenv !== undefined) process.env.ALTIMATE_CODE_VIRTUAL_ENV = origVenv
237+
if (origPython !== undefined) process.env.ALTIMATE_CODE_PYTHON_PATH = origPython
178238
}
179239
})
180240

181241
test("tries python3 before python in each location", async () => {
182242
const { discoverPython } = await import("../src/config")
183243

184-
const orig = process.env.ALTIMATE_CODE_VIRTUAL_ENV
244+
const origVenv = process.env.ALTIMATE_CODE_VIRTUAL_ENV
245+
const origPython = process.env.ALTIMATE_CODE_PYTHON_PATH
185246
delete process.env.ALTIMATE_CODE_VIRTUAL_ENV
247+
delete process.env.ALTIMATE_CODE_PYTHON_PATH
186248

187249
const binDir = join(dir, ".venv", "bin")
188250
await mkdir(binDir, { recursive: true })
@@ -193,7 +255,8 @@ describe("discoverPython", () => {
193255
const result = discoverPython(dir)
194256
expect(result).toBe(join(binDir, "python3"))
195257
} finally {
196-
if (orig !== undefined) process.env.ALTIMATE_CODE_VIRTUAL_ENV = orig
258+
if (origVenv !== undefined) process.env.ALTIMATE_CODE_VIRTUAL_ENV = origVenv
259+
if (origPython !== undefined) process.env.ALTIMATE_CODE_PYTHON_PATH = origPython
197260
}
198261
})
199262
})

packages/dbt-tools/test/dbt-resolve.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ function fakePython(dir: string): string {
2727
chmodSync(p, 0o755)
2828
// Also create python3 symlink
2929
const p3 = join(dir, "python3")
30-
try { symlinkSync(p, p3) } catch {}
30+
try {
31+
symlinkSync(p, p3)
32+
} catch {}
3133
return p
3234
}
3335

@@ -38,7 +40,9 @@ beforeEach(() => {
3840
})
3941

4042
afterEach(() => {
41-
try { rmSync(tempDir, { recursive: true, force: true }) } catch {}
43+
try {
44+
rmSync(tempDir, { recursive: true, force: true })
45+
} catch {}
4246
})
4347

4448
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)