Skip to content

Commit efacca6

Browse files
committed
feat(vscode): add ability to specify project path
1 parent 419336a commit efacca6

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

vscode/extension/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
"ms-python.python"
2828
],
2929
"contributes": {
30+
"configuration": {
31+
"type": "object",
32+
"title": "SQLMesh",
33+
"properties": {
34+
"sqlmesh.projectPath": {
35+
"type": "string",
36+
"default": "",
37+
"markdownDescription": "The path to the SQLMesh project. If not set, the extension will try to find the project root automatically. If set, the extension will use the project root as the workspace path, e.g. it will run `sqlmesh` and `sqlmesh_lsp` in the project root. The path can be absolute or relative to the workspace root."
38+
}
39+
}
40+
},
3041
"viewsContainers": {
3142
"panel": [
3243
{
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { workspace, WorkspaceFolder } from 'vscode'
2+
import path from 'path'
3+
import fs from 'fs'
4+
import { Result, err, ok } from '@bus/result'
5+
import { traceVerbose, traceInfo } from './common/log'
6+
7+
export interface SqlmeshConfiguration {
8+
projectPath: string
9+
}
10+
11+
/**
12+
* Get the SQLMesh configuration from VS Code settings.
13+
*
14+
* @returns The SQLMesh configuration
15+
*/
16+
export function getSqlmeshConfiguration(): SqlmeshConfiguration {
17+
const config = workspace.getConfiguration('sqlmesh')
18+
const projectPath = config.get<string>('projectPath', '')
19+
20+
return {
21+
projectPath,
22+
}
23+
}
24+
25+
/**
26+
* Validate and resolve the project path from configuration.
27+
* If no project path is configured, use the workspace folder.
28+
* If the project path is configured, it must be a directory that contains a SQLMesh project.
29+
*
30+
* @param workspaceFolder The current workspace folder
31+
* @returns A Result containing the resolved project path or an error
32+
*/
33+
export function resolveProjectPath(
34+
workspaceFolder: WorkspaceFolder,
35+
): Result<string, string> {
36+
const config = getSqlmeshConfiguration()
37+
38+
if (!config.projectPath) {
39+
// If no project path is configured, use the workspace folder
40+
traceVerbose('No project path configured, using workspace folder')
41+
return ok(workspaceFolder.uri.fsPath)
42+
}
43+
let resolvedPath: string
44+
45+
// Check if the path is absolute
46+
if (path.isAbsolute(config.projectPath)) {
47+
resolvedPath = config.projectPath
48+
} else {
49+
// Resolve relative path from workspace root
50+
resolvedPath = path.join(workspaceFolder.uri.fsPath, config.projectPath)
51+
}
52+
53+
// Normalize the path
54+
resolvedPath = path.normalize(resolvedPath)
55+
56+
// Validate that the path exists
57+
if (!fs.existsSync(resolvedPath)) {
58+
return err(`Configured project path does not exist: ${resolvedPath}`)
59+
}
60+
61+
// Validate that it's a directory
62+
const stats = fs.statSync(resolvedPath)
63+
if (!stats.isDirectory()) {
64+
return err(`Configured project path is not a directory: ${resolvedPath}`)
65+
}
66+
67+
// Check if it contains SQLMesh project files (config.yaml, config.yml, or config.py)
68+
const configFiles = ['config.yaml', 'config.yml', 'config.py']
69+
const hasConfigFile = configFiles.some(file =>
70+
fs.existsSync(path.join(resolvedPath, file)),
71+
)
72+
if (!hasConfigFile) {
73+
traceInfo(`Warning: No SQLMesh configuration file found in ${resolvedPath}`)
74+
}
75+
76+
traceVerbose(`Using project path: ${resolvedPath}`)
77+
return ok(resolvedPath)
78+
}

vscode/extension/src/utilities/sqlmesh/sqlmesh.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { execAsync } from '../exec'
1111
import z from 'zod'
1212
import { ProgressLocation, window } from 'vscode'
1313
import { IS_WINDOWS } from '../isWindows'
14+
import { resolveProjectPath } from '../config'
1415

1516
export interface SqlmeshExecInfo {
1617
workspacePath: string
@@ -28,8 +29,12 @@ export interface SqlmeshExecInfo {
2829
*/
2930
export const isTcloudProject = async (): Promise<Result<boolean, string>> => {
3031
const projectRoot = await getProjectRoot()
31-
const tcloudYamlPath = path.join(projectRoot.uri.fsPath, 'tcloud.yaml')
32-
const tcloudYmlPath = path.join(projectRoot.uri.fsPath, 'tcloud.yml')
32+
const resolvedPath = resolveProjectPath(projectRoot)
33+
if (isErr(resolvedPath)) {
34+
return err(resolvedPath.error)
35+
}
36+
const tcloudYamlPath = path.join(resolvedPath.value, 'tcloud.yaml')
37+
const tcloudYmlPath = path.join(resolvedPath.value, 'tcloud.yml')
3338
const isTcloudYamlFilePresent = fs.existsSync(tcloudYamlPath)
3439
const isTcloudYmlFilePresent = fs.existsSync(tcloudYmlPath)
3540
if (isTcloudYamlFilePresent || isTcloudYmlFilePresent) {
@@ -83,8 +88,15 @@ export const isSqlmeshEnterpriseInstalled = async (): Promise<
8388
return tcloudBin
8489
}
8590
const projectRoot = await getProjectRoot()
91+
const resolvedPath = resolveProjectPath(projectRoot)
92+
if (isErr(resolvedPath)) {
93+
return err({
94+
type: 'generic',
95+
message: resolvedPath.error,
96+
})
97+
}
8698
const called = await execAsync(tcloudBin.value, ['is_sqlmesh_installed'], {
87-
cwd: projectRoot.uri.fsPath,
99+
cwd: resolvedPath.value,
88100
})
89101
if (called.exitCode !== 0) {
90102
return err({
@@ -183,7 +195,14 @@ export const sqlmeshExec = async (): Promise<
183195
> => {
184196
const sqlmesh = IS_WINDOWS ? 'sqlmesh.exe' : 'sqlmesh'
185197
const projectRoot = await getProjectRoot()
186-
const workspacePath = projectRoot.uri.fsPath
198+
const resolvedPath = resolveProjectPath(projectRoot)
199+
if (isErr(resolvedPath)) {
200+
return err({
201+
type: 'generic',
202+
message: resolvedPath.error,
203+
})
204+
}
205+
const workspacePath = resolvedPath.value
187206
const interpreterDetails = await getInterpreterDetails()
188207
traceLog(`Interpreter details: ${JSON.stringify(interpreterDetails)}`)
189208
if (interpreterDetails.path) {
@@ -300,7 +319,14 @@ export const sqlmeshLspExec = async (): Promise<
300319
> => {
301320
const sqlmeshLSP = IS_WINDOWS ? 'sqlmesh_lsp.exe' : 'sqlmesh_lsp'
302321
const projectRoot = await getProjectRoot()
303-
const workspacePath = projectRoot.uri.fsPath
322+
const resolvedPath = resolveProjectPath(projectRoot)
323+
if (isErr(resolvedPath)) {
324+
return err({
325+
type: 'generic',
326+
message: resolvedPath.error,
327+
})
328+
}
329+
const workspacePath = resolvedPath.value
304330
const interpreterDetails = await getInterpreterDetails()
305331
traceLog(`Interpreter details: ${JSON.stringify(interpreterDetails)}`)
306332
if (interpreterDetails.path) {

0 commit comments

Comments
 (0)