Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion build/esbuild/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,8 @@ async function buildAll() {
copyZeroMQ(),
copyZeroMQOld(),
copyNodeGypBuild(),
buildVSCodeJsonRPC()
buildVSCodeJsonRPC(),
buildSqlLanguageServer()
);
}

Expand Down Expand Up @@ -540,6 +541,84 @@ async function copyNodeGypBuild() {
await fs.copy(source, target, { recursive: true });
}

async function buildSqlLanguageServer() {
// Bundle the sql-language-server with all its dependencies into a single file
const entryPoint = path.join(
extensionFolder,
'node_modules',
'@deepnote',
'sql-language-server',
'dist',
'bin',
'vscodeExtensionServer.js'
);
const outfile = path.join(extensionFolder, 'dist', 'sqlLanguageServer.cjs');

await esbuild.build({
entryPoints: [entryPoint],
bundle: true,
platform: 'node',
target: 'node18',
outfile,
format: 'cjs',
external: [
// These are optional database drivers - exclude to reduce bundle size
// They will be loaded dynamically if available
'sqlite3',
'mysql2',
'pg',
'pg-native',
'@google-cloud/bigquery',
// SSH tunneling dependencies with native modules - must be copied separately
'ssh2',
'cpu-features',
'node-ssh-forward'
],
minify: false,
sourcemap: false
});

// Copy ALL node_modules that the sql-language-server needs
// This includes the full transitive dependency tree for:
// - node-ssh-forward (SSH tunneling)
// - mysql2, pg, sqlite3 (database drivers)
// Instead of manually tracking dependencies, we copy all required packages
const sqlLspNodeModules = path.join(extensionFolder, 'dist', 'sql-lsp-modules');
await fs.ensureDir(sqlLspNodeModules);

// Create a minimal package.json and install dependencies
const packageJson = {
name: 'sql-lsp-deps',
version: '1.0.0',
dependencies: {
'node-ssh-forward': '^0.6.3',
mysql2: '^3.9.8',
pg: '^8.9.0',
sqlite3: '^5.0.3',
'@google-cloud/bigquery': '^8.1.1'
}
};

const packageJsonPath = path.join(sqlLspNodeModules, 'package.json');
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

// Run npm install in the sql-lsp-modules directory
const { execSync } = require('child_process');

try {
execSync('npm install --omit=dev --ignore-scripts', {
cwd: sqlLspNodeModules,
stdio: 'inherit'
});
} catch (error) {
console.error('Failed to install sql-lsp dependencies:', error);
throw error;
}

// Keep package.json for debugging/audit purposes, remove only lock file
await fs.remove(path.join(sqlLspNodeModules, 'package-lock.json'));
}

async function buildVSCodeJsonRPC() {
const source = path.join(extensionFolder, 'node_modules', 'vscode-jsonrpc');
const target = path.join(extensionFolder, 'dist', 'node_modules', 'vscode-jsonrpc', 'index.js');
Expand Down
49 changes: 36 additions & 13 deletions src/kernels/deepnote/deepnoteLspClientManager.node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import * as vscode from 'vscode';
import { CancellationError } from 'vscode';
import { inject, injectable } from 'inversify';
Expand Down Expand Up @@ -381,12 +382,20 @@ export class DeepnoteLspClientManager
logger.info(`Starting SQL LSP with ${connections.length} database connection(s)`);

// Use IPC transport - must match the server's hardcoded 'node-ipc' method
// Set NODE_PATH to include the sql-lsp-modules directory for runtime dependencies
const sqlLspModulesPath = this.getSqlLspModulesPath();
const nodePathEnv = sqlLspModulesPath ? { NODE_PATH: sqlLspModulesPath } : {};

const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
run: {
module: serverModule,
transport: TransportKind.ipc,
options: { env: { ...process.env, ...nodePathEnv } }
},
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: { execArgv: ['--nolazy', '--inspect=6009'] }
options: { execArgv: ['--nolazy', '--inspect=6009'], env: { ...process.env, ...nodePathEnv } }
}
};

Expand Down Expand Up @@ -532,7 +541,7 @@ export class DeepnoteLspClientManager
* @returns Path to the vscodeExtensionServer.js module for IPC transport
*/
private getSqlLanguageServerModule(): string {
// Try require.resolve first - this handles different package layouts
// Try require.resolve first - this handles different package layouts (works in dev mode)
try {
const serverModule = require.resolve('@deepnote/sql-language-server/dist/bin/vscodeExtensionServer.js');

Expand All @@ -543,7 +552,8 @@ export class DeepnoteLspClientManager
logger.trace('require.resolve failed, falling back to path construction:', error);
}

// Fallback: use extension path construction
// Fallback: use extension path construction (works in packaged extension)
// The sql-language-server is bundled into dist/sqlLanguageServer.cjs during build
let extensionPath = vscode.extensions.getExtension('Deepnote.vscode-deepnote')?.extensionPath;

if (!extensionPath) {
Expand All @@ -552,20 +562,33 @@ export class DeepnoteLspClientManager
logger.trace('Using __dirname to find extension path:', extensionPath);
}

const serverModule = path.join(
extensionPath,
'node_modules',
'@deepnote',
'sql-language-server',
'dist',
'bin',
'vscodeExtensionServer.js'
);
const serverModule = path.join(extensionPath, 'dist', 'sqlLanguageServer.cjs');
logger.trace('SQL LSP server module (fallback):', serverModule);

return serverModule;
}

/**
* Get the path to the sql-lsp-modules directory containing runtime dependencies
* @returns Path to the node_modules directory for SQL LSP, or undefined if not found
*/
private getSqlLspModulesPath(): string | undefined {
let extensionPath = vscode.extensions.getExtension('Deepnote.vscode-deepnote')?.extensionPath;

if (!extensionPath) {
extensionPath = path.join(__dirname, '..', '..', '..');
}

const modulesPath = path.join(extensionPath, 'dist', 'sql-lsp-modules', 'node_modules');

// Return undefined if the directory doesn't exist
if (!fs.existsSync(modulesPath)) {
return undefined;
}

return modulesPath;
}

/**
* Get SQL connections configuration from integration storage for the current project.
* Only returns integrations that are configured for the specific project.
Expand Down
Loading