Skip to content

Commit 9b570af

Browse files
committed
add drizzle studio
1 parent 7cb791f commit 9b570af

File tree

14 files changed

+471
-126
lines changed

14 files changed

+471
-126
lines changed

vscode-extension/package.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
{
4141
"command": "drizzle.visualizer:stop",
4242
"title": "🌧️ Stop Drizzle Visualizer server"
43+
},
44+
{
45+
"command": "drizzle.studio:stop",
46+
"title": "🌧️ Stop Drizzle Studio server"
4347
}
4448
],
4549
"codeLens": [
@@ -48,7 +52,17 @@
4852
"typescript"
4953
]
5054
}
51-
]
55+
],
56+
"configuration": {
57+
"title": "Drizzle",
58+
"properties": {
59+
"drizzle.studio.url": {
60+
"type": "string",
61+
"default": "https://local.drizzle.studio",
62+
"description": "Drizzle Studio URL"
63+
}
64+
}
65+
}
5266
},
5367
"scripts": {
5468
"package": "vsce package",

vscode-extension/src/context.ts

+24-28
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,49 @@
1-
import * as vscode from "vscode";
2-
import { outputChannel } from "./utils";
31
import path from "node:path";
2+
import * as vscode from "vscode";
43

5-
/* Local state */
6-
let $context: vscode.ExtensionContext | undefined = undefined;
4+
import pkg from "../package.json";
5+
import { toastError } from "./utils";
76

87
/* Constants */
98
const OutputKey = "[Context]";
109

11-
export function setExtensionContext(context: vscode.ExtensionContext) {
12-
$context = context;
10+
type Configuration = typeof pkg.contributes.configuration.properties;
11+
type ConfigurationKey = keyof Configuration extends `${string}.${infer Rest}`
12+
? Rest
13+
: never;
14+
15+
export function getConfiguration<Type>(key: ConfigurationKey) {
16+
return vscode.workspace.getConfiguration("drizzle").get<Type>(key);
1317
}
1418

15-
export function getExtensionContext() {
16-
if (!$context) {
17-
const msg = `${OutputKey} Context not set`;
18-
outputChannel.appendLine(msg);
19+
export function getWorkspaceRootFolder(startPath: string) {
20+
const workspaceRootPath = vscode.workspace.getWorkspaceFolder(
21+
vscode.Uri.file(startPath),
22+
)?.uri.fsPath;
23+
24+
if (!workspaceRootPath) {
25+
const msg = `${OutputKey} No workspace root folder found`;
26+
toastError(msg);
1927
throw new Error(msg);
2028
}
2129

22-
return $context;
30+
return workspaceRootPath;
2331
}
2432

25-
export async function getProjectWorkingDir(configPath: string) {
33+
export async function findProjectWorkingDir(configPath: string) {
2634
const pwd = path.dirname(await findNearestPackageJson(configPath));
2735

2836
if (!pwd) {
2937
const msg = `${OutputKey} No workspace folder`;
30-
vscode.window.showErrorMessage(msg);
31-
outputChannel.appendLine(msg);
38+
toastError(msg);
3239
throw new Error(msg);
3340
}
3441

3542
return pwd;
3643
}
3744

3845
async function findNearestPackageJson(startPath: string) {
39-
const rootPath = vscode.workspace.getWorkspaceFolder(
40-
vscode.Uri.file(startPath),
41-
)?.uri.fsPath;
42-
43-
const msg = `${OutputKey} No root folder found. Unable to find package.json`;
44-
45-
if (!rootPath) {
46-
vscode.window.showErrorMessage(msg);
47-
outputChannel.appendLine(msg);
48-
throw new Error(msg);
49-
}
50-
46+
const rootPath = getWorkspaceRootFolder(startPath);
5147
let currentDir = path.dirname(startPath);
5248

5349
while (currentDir.startsWith(rootPath)) {
@@ -60,7 +56,7 @@ async function findNearestPackageJson(startPath: string) {
6056
}
6157
}
6258

63-
vscode.window.showErrorMessage(msg);
64-
outputChannel.appendLine(msg);
59+
const msg = `${OutputKey} No package.json found in workspace`;
60+
toastError(msg);
6561
throw new Error(msg);
6662
}

vscode-extension/src/extension.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
11
import * as vscode from "vscode";
22

33
import visualizerPkg from "../apps/visualizer/package.json";
4-
import { setExtensionContext } from "./context";
54
import { OpenVisualizerCodeLens } from "./modules/visualizer/open-visualizer/codelens";
6-
import { OpenVisualizerCommand } from "./modules/visualizer/open-visualizer/command";
7-
import { StopVisualizerCommand } from "./modules/visualizer/stop-visualizer/command";
5+
import {
6+
OpenVisualizer,
7+
OpenVisualizerCommand,
8+
} from "./modules/visualizer/open-visualizer/command";
9+
import {
10+
StopVisualizerCommand,
11+
StopVisualizer,
12+
} from "./modules/visualizer/stop-visualizer/command";
813
import { stopVisualizer } from "./modules/visualizer/server";
914
import { outputChannel } from "./utils";
15+
import {
16+
OpenStudioCommand,
17+
OpenStudio,
18+
SelectEnvAndOpenStudio,
19+
SelectEnvAndOpenStudioCommand,
20+
} from "./modules/studio/open-studio/command";
21+
import {
22+
StopStudioCommand,
23+
StopStudio,
24+
} from "./modules/studio/stop-studio/command";
25+
import { OpenStudioCodeLens } from "./modules/studio/open-studio/codelens";
26+
import { stopStudio } from "./modules/studio/server";
1027

1128
export function activate(context: vscode.ExtensionContext) {
12-
setExtensionContext(context);
1329
checkNodeVersion();
1430

15-
// Register CodeLens and commands
1631
context.subscriptions.push(
17-
OpenVisualizerCodeLens,
18-
OpenVisualizerCommand,
19-
StopVisualizerCommand,
32+
/* Studio */
33+
vscode.commands.registerCommand(OpenStudioCommand, OpenStudio),
34+
vscode.commands.registerCommand(
35+
SelectEnvAndOpenStudioCommand,
36+
SelectEnvAndOpenStudio,
37+
),
38+
vscode.commands.registerCommand(StopStudioCommand, StopStudio),
39+
vscode.languages.registerCodeLensProvider(
40+
{ pattern: "**/*{drizzle,config}.ts", language: "typescript" },
41+
new OpenStudioCodeLens(),
42+
),
43+
44+
/* Visualizer */
45+
vscode.commands.registerCommand(OpenVisualizerCommand, OpenVisualizer),
46+
vscode.commands.registerCommand(StopVisualizerCommand, StopVisualizer),
47+
vscode.languages.registerCodeLensProvider(
48+
{ pattern: "**/*{drizzle,config}.ts", language: "typescript" },
49+
new OpenVisualizerCodeLens(),
50+
),
2051
);
2152
}
2253

2354
// This method is called when your extension is deactivated
2455
export function deactivate() {
2556
stopVisualizer();
57+
stopStudio();
2658
}
2759

2860
function checkNodeVersion() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as vscode from "vscode";
2+
3+
import { findDrizzleConfigLines } from "../../../utils";
4+
import { findDrizzleKitPath } from "../server";
5+
import { SelectEnvAndOpenStudioCommand } from "./command";
6+
7+
export class OpenStudioCodeLens implements vscode.CodeLensProvider {
8+
async provideCodeLenses(
9+
document: vscode.TextDocument,
10+
): Promise<vscode.CodeLens[]> {
11+
const drizzleKitPath = findDrizzleKitPath(document.uri.fsPath);
12+
13+
if (!drizzleKitPath) {
14+
return [];
15+
}
16+
17+
return findDrizzleConfigLines(document.getText(), { requireDb: true }).map(
18+
({ index }) => {
19+
const range = new vscode.Range(
20+
new vscode.Position(index, 0),
21+
new vscode.Position(index, 0),
22+
);
23+
24+
return new vscode.CodeLens(range, {
25+
title: "🌧️ Open Drizzle Studio",
26+
command: SelectEnvAndOpenStudioCommand,
27+
tooltip: "Open Drizzle Studio",
28+
arguments: [document.uri.fsPath],
29+
});
30+
},
31+
);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as vscode from "vscode";
2+
3+
import { getConfiguration } from "../../../context";
4+
import { outputChannel, render, toastError } from "../../../utils";
5+
import { createDrizzleStudioPanel } from "../panel";
6+
import { startStudio } from "../server";
7+
8+
export const OpenStudioCommand = "drizzle.studio:open";
9+
10+
export async function OpenStudio(...args: any[]) {
11+
const OutputKey = `[${OpenStudioCommand}]`;
12+
const configPath = args[0];
13+
const envFile = args[1];
14+
15+
if (!configPath || typeof configPath !== "string") {
16+
toastError(`${OutputKey} Expected config path to be a string`);
17+
return;
18+
}
19+
20+
try {
21+
await startStudio(configPath, envFile);
22+
const panel = createDrizzleStudioPanel();
23+
24+
const studioUrl =
25+
getConfiguration<string>("studio.url") || "https://local.drizzle.studio";
26+
27+
outputChannel.appendLine(`${OutputKey} using Studio URL: ${studioUrl}`);
28+
29+
panel.webview.html = render(`
30+
<iframe
31+
src="${studioUrl}"
32+
width="100%"
33+
height="100%"
34+
frameborder="0"
35+
style="border: none;"
36+
sandbox="allow-scripts allow-same-origin allow-downloads"
37+
/>`);
38+
39+
panel.reveal();
40+
} catch (error) {
41+
toastError(
42+
`${OutputKey} Failed to start Drizzle Studio: ${
43+
error instanceof Error ? error.message : String(error)
44+
}`,
45+
);
46+
return;
47+
}
48+
}
49+
50+
export const SelectEnvAndOpenStudioCommand =
51+
"drizzle.studio:select_env_and_open";
52+
53+
/* Local state */
54+
let $lastSelectedEnv: string | undefined;
55+
56+
export async function SelectEnvAndOpenStudio(configPath: string) {
57+
// Find .env files in the workspace
58+
const envFiles = await vscode.workspace.findFiles(
59+
"**/.env*",
60+
"**/node_modules/**",
61+
);
62+
63+
// Create quick pick items
64+
const items = envFiles.map((file) => ({
65+
label: vscode.workspace.asRelativePath(file),
66+
path: file.fsPath,
67+
}));
68+
69+
// Add option to not use an env file
70+
items.unshift({ label: "None", path: "Don't use an env file" });
71+
72+
// Move last selected item to top if it exists
73+
if ($lastSelectedEnv) {
74+
const lastSelectedIndex = items.findIndex(
75+
(item) => item.path === $lastSelectedEnv,
76+
);
77+
if (lastSelectedIndex > -1) {
78+
const [lastItem] = items.splice(lastSelectedIndex, 1);
79+
items.unshift(lastItem);
80+
}
81+
}
82+
83+
// Show quick pick
84+
const selected = await vscode.window.showQuickPick(items, {
85+
placeHolder: "Select an environment file",
86+
});
87+
88+
if (selected) {
89+
// Save the selection to workspace configuration (except for "None")
90+
if (selected.label !== "None") {
91+
$lastSelectedEnv = selected.path;
92+
}
93+
94+
// Call open studio command with the selected env file
95+
await vscode.commands.executeCommand(
96+
OpenStudioCommand,
97+
configPath,
98+
selected.label === "None" ? undefined : selected.path,
99+
);
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { WebviewPanel } from "vscode";
2+
3+
import { createPanel, render } from "../../utils";
4+
5+
/* Local state */
6+
let $panel: WebviewPanel | undefined = undefined;
7+
8+
export function createDrizzleStudioPanel() {
9+
if ($panel) {
10+
return $panel;
11+
}
12+
13+
$panel = createPanel({
14+
id: "DrizzleStudio",
15+
title: "Drizzle Studio",
16+
onDispose: () => {
17+
$panel = undefined;
18+
},
19+
});
20+
21+
$panel.webview.html = render(`
22+
<p style="display: flex; justify-content: center; align-items: center; height: 100%; margin: 0; font-size: 1.5rem; font-weight: bold;">
23+
Starting Drizzle Studio...
24+
</p>
25+
`);
26+
27+
return $panel;
28+
}
29+
30+
export function closeDrizzleStudioPanel() {
31+
$panel?.dispose();
32+
}

0 commit comments

Comments
 (0)