Skip to content

Commit 376c0e9

Browse files
davidsmfreireclaude
andcommitted
feat(vscode): editor settings + bundled-binary VSIX pipeline
LSP server now reads `sqlshield.schema` and `sqlshield.dialect` from the client's `initializationOptions` and `workspace/didChangeConfiguration`, merging them per-field with `.sqlshield.toml` (editor wins). Existing documents are revalidated when settings change so diagnostics reflect the new schema/dialect immediately. Extension declares the new settings in `package.json`, forwards them to the server, and resolves its binary in three steps: `sqlshield.serverPath` override → bundled `server/sqlshield-lsp[.exe]` (shipped in platform-specific VSIXs) → `sqlshield-lsp` on PATH. Adds a VSCode Marketplace workflow that cross-compiles `sqlshield-lsp` for linux x64/arm64, darwin x64/arm64, and win32 x64/arm64, stages each binary into the extension, and packages a `--target`-tagged VSIX per platform. Tag-pushed releases (vscode-v*) publish to the VS Code Marketplace and Open VSX (when OVSX_PAT secret is set). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1035e15 commit 376c0e9

8 files changed

Lines changed: 397 additions & 35 deletions

File tree

.github/workflows/vscode.yml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
name: VSCode Extension
2+
3+
on:
4+
push:
5+
tags: ['vscode-v*']
6+
pull_request:
7+
paths:
8+
- 'editors/vscode/**'
9+
- 'sqlshield-lsp/**'
10+
- 'sqlshield/**'
11+
- '.github/workflows/vscode.yml'
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
package:
19+
name: package (${{ matrix.target.vsce }})
20+
runs-on: ${{ matrix.target.runner }}
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
target:
25+
- { runner: ubuntu-latest, rust: x86_64-unknown-linux-gnu, vsce: linux-x64, use_cross: false }
26+
- { runner: ubuntu-latest, rust: aarch64-unknown-linux-gnu, vsce: linux-arm64, use_cross: true }
27+
- { runner: macos-14, rust: aarch64-apple-darwin, vsce: darwin-arm64, use_cross: false }
28+
- { runner: macos-14, rust: x86_64-apple-darwin, vsce: darwin-x64, use_cross: false }
29+
- { runner: windows-latest, rust: x86_64-pc-windows-msvc, vsce: win32-x64, use_cross: false }
30+
- { runner: windows-latest, rust: aarch64-pc-windows-msvc, vsce: win32-arm64, use_cross: false }
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- uses: dtolnay/rust-toolchain@stable
35+
with:
36+
targets: ${{ matrix.target.rust }}
37+
38+
- uses: Swatinem/rust-cache@v2
39+
with:
40+
key: vsce-${{ matrix.target.vsce }}
41+
42+
- name: Install cross
43+
if: matrix.target.use_cross
44+
run: cargo install cross --locked
45+
46+
- name: Build sqlshield-lsp (cross)
47+
if: matrix.target.use_cross
48+
run: cross build -p sqlshield-lsp --release --target ${{ matrix.target.rust }}
49+
50+
- name: Build sqlshield-lsp (native / rustup target)
51+
if: ${{ !matrix.target.use_cross }}
52+
run: cargo build -p sqlshield-lsp --release --target ${{ matrix.target.rust }}
53+
54+
- name: Stage binary into extension
55+
shell: bash
56+
run: |
57+
mkdir -p editors/vscode/server
58+
if [[ "${{ runner.os }}" == "Windows" ]]; then
59+
cp target/${{ matrix.target.rust }}/release/sqlshield-lsp.exe editors/vscode/server/sqlshield-lsp.exe
60+
else
61+
cp target/${{ matrix.target.rust }}/release/sqlshield-lsp editors/vscode/server/sqlshield-lsp
62+
chmod +x editors/vscode/server/sqlshield-lsp
63+
fi
64+
65+
- uses: actions/setup-node@v4
66+
with:
67+
node-version: '20'
68+
cache: 'npm'
69+
cache-dependency-path: editors/vscode/package-lock.json
70+
71+
- name: Install extension dependencies
72+
working-directory: editors/vscode
73+
run: npm ci
74+
75+
- name: Compile extension
76+
working-directory: editors/vscode
77+
run: npm run compile
78+
79+
- name: Package VSIX
80+
working-directory: editors/vscode
81+
shell: bash
82+
run: |
83+
mkdir -p ../../dist
84+
npx --yes @vscode/vsce package \
85+
--target ${{ matrix.target.vsce }} \
86+
--out ../../dist/sqlshield-${{ matrix.target.vsce }}.vsix
87+
88+
- uses: actions/upload-artifact@v4
89+
with:
90+
name: vsix-${{ matrix.target.vsce }}
91+
path: dist/*.vsix
92+
if-no-files-found: error
93+
94+
publish:
95+
name: publish to marketplaces
96+
needs: package
97+
runs-on: ubuntu-latest
98+
if: startsWith(github.ref, 'refs/tags/vscode-v')
99+
permissions:
100+
contents: write
101+
env:
102+
VSCE_PAT: ${{ secrets.VSCE_PAT }}
103+
OVSX_PAT: ${{ secrets.OVSX_PAT }}
104+
steps:
105+
- uses: actions/checkout@v4
106+
107+
- uses: actions/setup-node@v4
108+
with:
109+
node-version: '20'
110+
cache: 'npm'
111+
cache-dependency-path: editors/vscode/package-lock.json
112+
113+
- uses: actions/download-artifact@v4
114+
with:
115+
path: dist
116+
pattern: vsix-*
117+
merge-multiple: true
118+
119+
- name: List VSIX files
120+
run: ls -lh dist/
121+
122+
- name: Publish to VS Code Marketplace
123+
working-directory: editors/vscode
124+
run: |
125+
for vsix in ../../dist/*.vsix; do
126+
echo "Publishing $vsix"
127+
npx --yes @vscode/vsce publish --packagePath "$vsix"
128+
done
129+
130+
- name: Publish to Open VSX
131+
if: ${{ env.OVSX_PAT != '' }}
132+
working-directory: editors/vscode
133+
run: |
134+
for vsix in ../../dist/*.vsix; do
135+
echo "Publishing $vsix to Open VSX"
136+
npx --yes ovsx publish "$vsix" -p "$OVSX_PAT"
137+
done

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editors/vscode/README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,39 @@ files.
66

77
## Requirements
88

9-
The extension wraps the `sqlshield-lsp` binary; install it first:
9+
Platform-specific builds of this extension bundle the `sqlshield-lsp`
10+
binary, so installing the extension is enough on Linux x64/arm64,
11+
macOS, and Windows x64/arm64. On unsupported platforms (or for users
12+
who'd rather use their own build), install the binary manually:
1013

1114
```bash
1215
cargo install sqlshield-lsp
1316
```
1417

15-
If `sqlshield-lsp` is not on `PATH`, set `sqlshield.serverPath` to the
16-
absolute path.
18+
If `sqlshield-lsp` is not on `PATH` and the bundled binary isn't being
19+
picked up, set `sqlshield.serverPath` to the absolute path.
1720

1821
## How it works
1922

2023
The extension spawns `sqlshield-lsp` over stdio and forwards diagnostics
21-
to the editor as you type. A `.sqlshield.toml` at the workspace root
22-
configures schema location and dialect; see the project README for the
23-
full set of options.
24+
to the editor as you type. Configuration comes from two sources, with
25+
editor settings winning per-field:
26+
27+
1. The `sqlshield.*` VS Code settings below.
28+
2. A `.sqlshield.toml` discovered by walking up from the workspace root.
2429

2530
## Commands
2631

2732
* **sqlshield: Restart Language Server** — restart the server without
28-
reloading the window. Useful after editing the schema or
29-
`.sqlshield.toml` from outside VS Code.
33+
reloading the window. Useful after switching the binary path.
3034

3135
## Settings
3236

33-
* `sqlshield.serverPath` — binary path (default: `sqlshield-lsp`).
37+
* `sqlshield.schema` — path to the schema SQL file (relative to
38+
workspace root or absolute). Overrides `schema` in `.sqlshield.toml`.
39+
* `sqlshield.dialect` — SQL dialect (`postgres`, `mysql`, `sqlite`,
40+
`mssql`, `snowflake`, `bigquery`, `redshift`, `clickhouse`, `duckdb`,
41+
`hive`, `ansi`, `generic`). Overrides `dialect` in `.sqlshield.toml`.
42+
* `sqlshield.serverPath` — binary path. Leave blank for the bundled
43+
binary; falls back to `sqlshield-lsp` on `PATH`.
3444
* `sqlshield.trace.server` — log LSP traffic for debugging.

editors/vscode/package.json

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,33 @@
4343
"properties": {
4444
"sqlshield.serverPath": {
4545
"type": "string",
46-
"default": "sqlshield-lsp",
47-
"description": "Path to the sqlshield-lsp binary. Defaults to 'sqlshield-lsp' on PATH."
46+
"default": "",
47+
"description": "Path to the sqlshield-lsp binary. Leave blank to use the binary bundled with this extension; falls back to 'sqlshield-lsp' on PATH."
48+
},
49+
"sqlshield.schema": {
50+
"type": "string",
51+
"default": "",
52+
"description": "Path to the schema SQL file (relative to the workspace root or absolute). Overrides 'schema' in .sqlshield.toml. Leave blank to use .sqlshield.toml or the default 'schema.sql'."
53+
},
54+
"sqlshield.dialect": {
55+
"type": "string",
56+
"default": "",
57+
"enum": [
58+
"",
59+
"generic",
60+
"postgres",
61+
"mysql",
62+
"sqlite",
63+
"mssql",
64+
"snowflake",
65+
"bigquery",
66+
"redshift",
67+
"clickhouse",
68+
"duckdb",
69+
"hive",
70+
"ansi"
71+
],
72+
"description": "SQL dialect for parsing. Overrides 'dialect' in .sqlshield.toml. Leave blank to use .sqlshield.toml or the default ('generic')."
4873
},
4974
"sqlshield.trace.server": {
5075
"type": "string",

editors/vscode/src/extension.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Thin VS Code wrapper around the `sqlshield-lsp` server binary. The
22
// extension's only job is to spawn the server, wire up the LSP transport,
3-
// and expose a "Restart Language Server" command. All diagnostic logic lives
4-
// in the Rust crate.
3+
// forward configuration to the server, and expose a "Restart Language
4+
// Server" command. All diagnostic logic lives in the Rust crate.
55

6+
import * as fs from "fs";
67
import * as path from "path";
78
import { ExtensionContext, window, workspace, commands } from "vscode";
89
import {
@@ -20,6 +21,16 @@ export async function activate(context: ExtensionContext): Promise<void> {
2021
await stop();
2122
await start(context);
2223
}),
24+
workspace.onDidChangeConfiguration(async (e) => {
25+
// Server-relevant settings that require a restart (the binary path
26+
// can't be hot-swapped). Per-doc settings (schema/dialect) are
27+
// instead pushed to the running server via didChangeConfiguration,
28+
// which the LSP client wires up automatically.
29+
if (e.affectsConfiguration("sqlshield.serverPath")) {
30+
await stop();
31+
await start(context);
32+
}
33+
}),
2334
);
2435

2536
await start(context);
@@ -29,9 +40,8 @@ export async function deactivate(): Promise<void> {
2940
await stop();
3041
}
3142

32-
async function start(_context: ExtensionContext): Promise<void> {
33-
const config = workspace.getConfiguration("sqlshield");
34-
const serverPath = config.get<string>("serverPath") ?? "sqlshield-lsp";
43+
async function start(context: ExtensionContext): Promise<void> {
44+
const serverPath = resolveServerPath(context);
3545

3646
const serverOptions: ServerOptions = {
3747
run: { command: serverPath, transport: TransportKind.stdio },
@@ -51,7 +61,9 @@ async function start(_context: ExtensionContext): Promise<void> {
5161
{ scheme: "file", language: "typescript" },
5262
{ scheme: "file", language: "typescriptreact" },
5363
],
64+
initializationOptions: () => readSqlshieldSettings(),
5465
synchronize: {
66+
configurationSection: "sqlshield",
5567
fileEvents: workspace.createFileSystemWatcher("**/.sqlshield.toml"),
5668
},
5769
outputChannelName: "sqlshield",
@@ -86,3 +98,37 @@ async function stop(): Promise<void> {
8698
}
8799
client = undefined;
88100
}
101+
102+
/// Resolve the LSP binary in this order:
103+
/// 1. `sqlshield.serverPath` setting (explicit override).
104+
/// 2. Bundled binary inside the VSIX (shipped by platform-specific
105+
/// builds in CI).
106+
/// 3. `sqlshield-lsp` on PATH (lets users who `cargo install` keep
107+
/// working without bundled binaries).
108+
function resolveServerPath(context: ExtensionContext): string {
109+
const override = workspace
110+
.getConfiguration("sqlshield")
111+
.get<string>("serverPath", "")
112+
.trim();
113+
if (override) {
114+
return override;
115+
}
116+
117+
const exe = process.platform === "win32" ? "sqlshield-lsp.exe" : "sqlshield-lsp";
118+
const bundled = path.join(context.extensionPath, "server", exe);
119+
if (fs.existsSync(bundled)) {
120+
return bundled;
121+
}
122+
123+
return "sqlshield-lsp";
124+
}
125+
126+
/// The settings we forward to the LSP server. Keep in sync with the
127+
/// `EditorSettings` struct in `sqlshield-lsp/src/config.rs`.
128+
function readSqlshieldSettings(): { schema?: string; dialect?: string } {
129+
const cfg = workspace.getConfiguration("sqlshield");
130+
return {
131+
schema: cfg.get<string>("schema", "") || undefined,
132+
dialect: cfg.get<string>("dialect", "") || undefined,
133+
};
134+
}

sqlshield-lsp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ path = "src/main.rs"
1515
[dependencies]
1616
dashmap = "5"
1717
serde = { version = "1", features = ["derive"] }
18+
serde_json = "1"
1819
sqlparser = "0.43.1"
1920
sqlshield = { workspace = true }
2021
tokio = { version = "1", features = ["io-std", "macros", "rt-multi-thread"] }

0 commit comments

Comments
 (0)