Skip to content

Commit 85538d2

Browse files
authored
fix: Package the SQL language server. (#240)
* fix: Package the SQL language server. * feedback
1 parent 2590146 commit 85538d2

File tree

2 files changed

+116
-14
lines changed

2 files changed

+116
-14
lines changed

build/esbuild/build.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ async function buildAll() {
483483
copyZeroMQ(),
484484
copyZeroMQOld(),
485485
copyNodeGypBuild(),
486-
buildVSCodeJsonRPC()
486+
buildVSCodeJsonRPC(),
487+
buildSqlLanguageServer()
487488
);
488489
}
489490

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

544+
async function buildSqlLanguageServer() {
545+
// Bundle the sql-language-server with all its dependencies into a single file
546+
const entryPoint = path.join(
547+
extensionFolder,
548+
'node_modules',
549+
'@deepnote',
550+
'sql-language-server',
551+
'dist',
552+
'bin',
553+
'vscodeExtensionServer.js'
554+
);
555+
const outfile = path.join(extensionFolder, 'dist', 'sqlLanguageServer.cjs');
556+
557+
await esbuild.build({
558+
entryPoints: [entryPoint],
559+
bundle: true,
560+
platform: 'node',
561+
target: 'node18',
562+
outfile,
563+
format: 'cjs',
564+
external: [
565+
// These are optional database drivers - exclude to reduce bundle size
566+
// They will be loaded dynamically if available
567+
'sqlite3',
568+
'mysql2',
569+
'pg',
570+
'pg-native',
571+
'@google-cloud/bigquery',
572+
// SSH tunneling dependencies with native modules - must be copied separately
573+
'ssh2',
574+
'cpu-features',
575+
'node-ssh-forward'
576+
],
577+
minify: false,
578+
sourcemap: false
579+
});
580+
581+
// Copy ALL node_modules that the sql-language-server needs
582+
// This includes the full transitive dependency tree for:
583+
// - node-ssh-forward (SSH tunneling)
584+
// - mysql2, pg, sqlite3 (database drivers)
585+
// Instead of manually tracking dependencies, we copy all required packages
586+
const sqlLspNodeModules = path.join(extensionFolder, 'dist', 'sql-lsp-modules');
587+
await fs.ensureDir(sqlLspNodeModules);
588+
589+
// Create a minimal package.json and install dependencies
590+
const packageJson = {
591+
name: 'sql-lsp-deps',
592+
version: '1.0.0',
593+
dependencies: {
594+
'node-ssh-forward': '^0.6.3',
595+
mysql2: '^3.9.8',
596+
pg: '^8.9.0',
597+
sqlite3: '^5.0.3',
598+
'@google-cloud/bigquery': '^8.1.1'
599+
}
600+
};
601+
602+
const packageJsonPath = path.join(sqlLspNodeModules, 'package.json');
603+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
604+
605+
// Run npm install in the sql-lsp-modules directory
606+
const { execSync } = require('child_process');
607+
608+
try {
609+
execSync('npm install --omit=dev --ignore-scripts', {
610+
cwd: sqlLspNodeModules,
611+
stdio: 'inherit'
612+
});
613+
} catch (error) {
614+
console.error('Failed to install sql-lsp dependencies:', error);
615+
throw error;
616+
}
617+
618+
// Keep package.json for debugging/audit purposes, remove only lock file
619+
await fs.remove(path.join(sqlLspNodeModules, 'package-lock.json'));
620+
}
621+
543622
async function buildVSCodeJsonRPC() {
544623
const source = path.join(extensionFolder, 'node_modules', 'vscode-jsonrpc');
545624
const target = path.join(extensionFolder, 'dist', 'node_modules', 'vscode-jsonrpc', 'index.js');

src/kernels/deepnote/deepnoteLspClientManager.node.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as fs from 'fs';
12
import * as vscode from 'vscode';
23
import { CancellationError } from 'vscode';
34
import { inject, injectable } from 'inversify';
@@ -381,12 +382,20 @@ export class DeepnoteLspClientManager
381382
logger.info(`Starting SQL LSP with ${connections.length} database connection(s)`);
382383

383384
// Use IPC transport - must match the server's hardcoded 'node-ipc' method
385+
// Set NODE_PATH to include the sql-lsp-modules directory for runtime dependencies
386+
const sqlLspModulesPath = this.getSqlLspModulesPath();
387+
const nodePathEnv = sqlLspModulesPath ? { NODE_PATH: sqlLspModulesPath } : {};
388+
384389
const serverOptions: ServerOptions = {
385-
run: { module: serverModule, transport: TransportKind.ipc },
390+
run: {
391+
module: serverModule,
392+
transport: TransportKind.ipc,
393+
options: { env: { ...process.env, ...nodePathEnv } }
394+
},
386395
debug: {
387396
module: serverModule,
388397
transport: TransportKind.ipc,
389-
options: { execArgv: ['--nolazy', '--inspect=6009'] }
398+
options: { execArgv: ['--nolazy', '--inspect=6009'], env: { ...process.env, ...nodePathEnv } }
390399
}
391400
};
392401

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

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

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

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

555-
const serverModule = path.join(
556-
extensionPath,
557-
'node_modules',
558-
'@deepnote',
559-
'sql-language-server',
560-
'dist',
561-
'bin',
562-
'vscodeExtensionServer.js'
563-
);
565+
const serverModule = path.join(extensionPath, 'dist', 'sqlLanguageServer.cjs');
564566
logger.trace('SQL LSP server module (fallback):', serverModule);
565567

566568
return serverModule;
567569
}
568570

571+
/**
572+
* Get the path to the sql-lsp-modules directory containing runtime dependencies
573+
* @returns Path to the node_modules directory for SQL LSP, or undefined if not found
574+
*/
575+
private getSqlLspModulesPath(): string | undefined {
576+
let extensionPath = vscode.extensions.getExtension('Deepnote.vscode-deepnote')?.extensionPath;
577+
578+
if (!extensionPath) {
579+
extensionPath = path.join(__dirname, '..', '..', '..');
580+
}
581+
582+
const modulesPath = path.join(extensionPath, 'dist', 'sql-lsp-modules', 'node_modules');
583+
584+
// Return undefined if the directory doesn't exist
585+
if (!fs.existsSync(modulesPath)) {
586+
return undefined;
587+
}
588+
589+
return modulesPath;
590+
}
591+
569592
/**
570593
* Get SQL connections configuration from integration storage for the current project.
571594
* Only returns integrations that are configured for the specific project.

0 commit comments

Comments
 (0)