-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create dbt project from VS Code (#446)
* activate extension after vscode startup * enable mocha tests on client * delete dependency from server * add command for creating project * handle ctrl+c * add log * add test * support different versions * fix setting project path * skip open for test * rename test * improve test * add logs * increase retry timeout * create settings.json for new project * kill process after closing terminal * skip test on linux * delete test * replace only first tilde * enable autosave * delete test * Revert "add logs" This reverts commit af92a7b. * extract const * add readme for create project command
- Loading branch information
1 parent
0b3db2b
commit 2be5eb3
Showing
14 changed files
with
293 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
client/src/commands/CreateDbtProject/CreateDbtProject.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { exec } from 'node:child_process'; | ||
import { EOL } from 'node:os'; | ||
import { promisify, TextEncoder } from 'node:util'; | ||
import { commands, OpenDialogOptions, Uri, window, workspace } from 'vscode'; | ||
import { log } from '../../Logger'; | ||
import { PythonExtension } from '../../python/PythonExtension'; | ||
import { Command } from '../CommandManager'; | ||
import { DbtInitTerminal } from './DbtInitTerminal'; | ||
|
||
enum DbtInitState { | ||
Default, | ||
ExpectProjectName, | ||
ProjectAlreadyExists, | ||
} | ||
|
||
export class CreateDbtProject implements Command { | ||
readonly id = 'dbtWizard.createDbtProject'; | ||
|
||
static readonly TERMINAL_NAME = 'Create dbt project'; | ||
static readonly SETTINGS_JSON_CONTENT = `{ | ||
"files.autoSave": "afterDelay" | ||
} | ||
`; | ||
|
||
async execute(projectFolder?: string, skipOpen?: boolean): Promise<void> { | ||
const dbtInitCommandPromise = this.getDbtInitCommand(); | ||
|
||
const projectFolderUri = projectFolder ? Uri.file(projectFolder) : await CreateDbtProject.openDialogForFolder(); | ||
if (projectFolderUri) { | ||
const pty = new DbtInitTerminal(`Creating dbt project in "${projectFolderUri.fsPath}" folder.\n\rPlease answer all questions.\n\r`); | ||
const terminal = window.createTerminal({ | ||
name: CreateDbtProject.TERMINAL_NAME, | ||
pty, | ||
}); | ||
terminal.show(); | ||
|
||
const dbtInitCommand = await dbtInitCommandPromise; | ||
if (!dbtInitCommand) { | ||
window | ||
.showWarningMessage('dbt not found, please choose Python that is used to run dbt.') | ||
.then(undefined, e => log(`Error while showing warning: ${e instanceof Error ? e.message : String(e)}`)); | ||
await commands.executeCommand('python.setInterpreter'); | ||
return; | ||
} | ||
|
||
log(`Running init command: ${dbtInitCommand}`); | ||
|
||
const initProcess = exec(dbtInitCommand, { cwd: projectFolderUri.fsPath }); | ||
|
||
let dbtInitState = DbtInitState.Default; | ||
let projectName: string | undefined; | ||
initProcess.on('exit', async (code: number | null) => { | ||
if (code === 0 && dbtInitState !== DbtInitState.ProjectAlreadyExists) { | ||
const projectUri = projectName ? Uri.joinPath(projectFolderUri, projectName) : projectFolderUri; | ||
const vscodeUri = Uri.joinPath(projectUri, '.vscode'); | ||
await workspace.fs.createDirectory(vscodeUri); | ||
await workspace.fs.writeFile(Uri.joinPath(vscodeUri, 'settings.json'), new TextEncoder().encode(CreateDbtProject.SETTINGS_JSON_CONTENT)); | ||
if (!skipOpen) { | ||
await commands.executeCommand('vscode.openFolder', projectUri, { forceNewWindow: true }); | ||
} | ||
} else { | ||
pty.writeRed(`${EOL}Command failed, please try again.${EOL}`); | ||
dbtInitState = DbtInitState.Default; | ||
} | ||
}); | ||
|
||
pty.onDataSubmitted((data: string) => { | ||
if (dbtInitState === DbtInitState.ExpectProjectName) { | ||
projectName = data; | ||
dbtInitState = DbtInitState.Default; | ||
} | ||
if (data.includes(DbtInitTerminal.CONTROL_CODES.ctrlC)) { | ||
initProcess.kill('SIGTERM'); | ||
} | ||
initProcess.stdin?.write(`${data}${EOL}`); | ||
}); | ||
|
||
initProcess.stdout?.on('data', (data: string) => { | ||
if (data.includes('name for your project')) { | ||
dbtInitState = DbtInitState.ExpectProjectName; | ||
} else if (data.includes('already exists here')) { | ||
dbtInitState = DbtInitState.ProjectAlreadyExists; | ||
} | ||
|
||
pty.write(data); | ||
}); | ||
} | ||
} | ||
|
||
async getDbtInitCommand(): Promise<string | undefined> { | ||
const pythonInfo = await new PythonExtension().getPythonInfo(); | ||
if (!pythonInfo) { | ||
return undefined; | ||
} | ||
|
||
const promisifiedExec = promisify(exec); | ||
const pythonCommand = `${pythonInfo.path} -c "import dbt.main; dbt.main.main(['--version'])"`; | ||
const cliCommand = 'dbt --version'; | ||
|
||
const settledResults = await Promise.allSettled([promisifiedExec(pythonCommand), promisifiedExec(cliCommand)]); | ||
const [pythonVersion, cliVersion] = settledResults.map(v => (v.status === 'fulfilled' ? v.value : undefined)); | ||
if (pythonVersion && pythonVersion.stderr.length > 0) { | ||
return `${pythonInfo.path} -c "import dbt.main; dbt.main.main(['init'])"`; | ||
} | ||
if (cliVersion && cliVersion.stderr.length > 0) { | ||
return 'dbt init'; | ||
} | ||
return undefined; | ||
} | ||
|
||
static async openDialogForFolder(): Promise<Uri | undefined> { | ||
const options: OpenDialogOptions = { | ||
title: 'Choose Folder For dbt Project', | ||
openLabel: 'Open', | ||
canSelectFiles: false, | ||
canSelectFolders: true, | ||
canSelectMany: false, | ||
}; | ||
const result = await window.showOpenDialog(options); | ||
return result && result.length > 0 ? result[0] : undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { EOL } from 'node:os'; | ||
import { Event, EventEmitter, Pseudoterminal, TerminalDimensions } from 'vscode'; | ||
|
||
export class DbtInitTerminal implements Pseudoterminal { | ||
static readonly CONTROL_CODES = { | ||
ctrlC: '\u0003', | ||
enter: '\r', | ||
backspace: '\u007F', | ||
moveCursorBack: '\u001B[D', | ||
deleteCharacter: '\u001B[P', | ||
}; | ||
|
||
private writeEmitter = new EventEmitter<string>(); | ||
private dataSubmittedEventEmitter = new EventEmitter<string>(); | ||
|
||
constructor(private startMessage: string) {} | ||
|
||
onDidWrite = this.writeEmitter.event; | ||
line = ''; | ||
|
||
open(_initialDimensions: TerminalDimensions | undefined): void { | ||
this.writeRed(this.startMessage); | ||
} | ||
|
||
close(): void { | ||
this.dataSubmittedEventEmitter.fire(DbtInitTerminal.CONTROL_CODES.ctrlC); | ||
} | ||
|
||
handleInput(data: string): void { | ||
if (data === DbtInitTerminal.CONTROL_CODES.enter) { | ||
this.writeEmitter.fire('\r\n'); | ||
this.dataSubmittedEventEmitter.fire(this.line); | ||
this.line = ''; | ||
return; | ||
} | ||
if (data === DbtInitTerminal.CONTROL_CODES.backspace) { | ||
if (this.line.length === 0) { | ||
return; | ||
} | ||
this.line = this.line.slice(0, -1); | ||
this.writeEmitter.fire(DbtInitTerminal.CONTROL_CODES.moveCursorBack); | ||
this.writeEmitter.fire(DbtInitTerminal.CONTROL_CODES.deleteCharacter); | ||
return; | ||
} | ||
if (data.includes(DbtInitTerminal.CONTROL_CODES.ctrlC)) { | ||
this.dataSubmittedEventEmitter.fire(DbtInitTerminal.CONTROL_CODES.ctrlC); | ||
} | ||
if (data.includes(DbtInitTerminal.CONTROL_CODES.enter)) { | ||
const lines = data.split(DbtInitTerminal.CONTROL_CODES.enter); | ||
this.line = lines[lines.length - 1]; | ||
this.writeEmitter.fire(data.replaceAll(DbtInitTerminal.CONTROL_CODES.enter, '\n\r')); | ||
} else { | ||
this.line += data; | ||
this.writeEmitter.fire(data); | ||
} | ||
} | ||
|
||
write(data: string): void { | ||
this.writeEmitter.fire(data.replaceAll(EOL, '\n\r')); | ||
} | ||
|
||
writeRed(data: string): void { | ||
this.write(`\u001B[31m${data}\u001B[0m`); | ||
} | ||
|
||
get onDataSubmitted(): Event<string> { | ||
return this.dataSubmittedEventEmitter.event; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.