diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index d2cc094..8b7ad76 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -72,6 +72,8 @@ jobs: - name: Build universal VS Code extension run: | npm install + npm run build + test -f dist/extension.js || (echo "Build failed: dist/extension.js not found" && exit 1) mkdir -p "$GITHUB_WORKSPACE/tmp/release" npx @vscode/vsce package -o "$GITHUB_WORKSPACE/tmp/release/gastro-vscode.vsix" working-directory: editors/vscode diff --git a/DECISIONS.md b/DECISIONS.md index 106a33c..e1f3937 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -24,3 +24,5 @@ - **2026-04-03** (m+git@andri.dk) Compile-time `{{ raw }}...{{ endraw }}` blocks. Content inside is escaped for literal display: template delimiters (`{{`/`}}`) and HTML characters (`<`, `>`, `&`). Whitespace around markers is always trimmed (no opt-out). Runs in `TransformTemplate` after comment extraction, before wrap transformation. No runtime function registered. LSP stubs prevent parse errors; `stripRawBlocks` prevents false diagnostics. Code examples in the example site inlined directly into `.gastro` files, replacing the `content/docs.go` + `CodeBlock` component pattern. - **2026-04-04** (m+git@andri.dk) Consolidated `gastro-lsp` into `gastro lsp` subcommand (single binary). Release archives are now `.tar.gz`/`.zip` per platform with SHA256SUMS. VS Code extension is universal (no bundled binary, finds `gastro` on PATH). Zed extension downloads archives. Scaffolded `go.mod` uses the version of the `gastro` binary that created it. - **2026-04-05** (m+git@andri.dk) LSP: fix deadlock in `invalidateComponentPropsCache` (re-entrant `dataMu` lock), graceful degradation when gopls is missing (instance kept alive with `gopls=nil`), and VS Code gopls install prompt. Instance initialization moved outside `dataMu.Lock()` to avoid blocking during I/O. Custom `gastro/goplsNotAvailable` notification sent once to the editor; VS Code extension intercepts it and offers one-click `go install golang.org/x/tools/gopls@latest` via terminal. +- **2026-04-05** (m+git@andri.dk) VS Code extension: added esbuild bundler to inline `vscode-languageclient` into `dist/extension.js`. Fixes published `.vsix` missing `node_modules/` (`.vscodeignore` excluded them but no bundler existed). CI updated to run `npm run build` before `vsce package`. +- **2026-04-05** (m+git@andri.dk) VS Code extension: migrated from plain JS to strict TypeScript (`src/extension.ts`). Modernized from deprecated `client.onReady()` to v9 async pattern (register notifications before `await client.start()`). Added `npm run typecheck` script. esbuild compiles TS; `tsc --noEmit` is type-check only. diff --git a/docs/contributing.md b/docs/contributing.md index c7ede0a..ab91c02 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -129,13 +129,21 @@ Quick orientation: ```sh cd editors/vscode -npm install # must run before the extension works +npm install # install dependencies (including esbuild) +npm run build # bundle extension.js into dist/extension.js ``` -The extension depends on `vscode-languageclient`. Without `npm install`, VS -Code will fail to activate the extension with a module-not-found error. +The extension is bundled with esbuild. `dist/extension.js` is the entry point +loaded by VS Code — the raw `extension.js` source is not used directly. -To test changes, symlink the extension directory into VS Code's extensions: +For iterative development, use watch mode to rebuild on changes: + +```sh +npm run watch +``` + +To test changes, symlink the extension directory into VS Code's extensions +(or use `mise run link:vscode` which also runs the build): ```sh ln -s "$(pwd)" ~/.vscode/extensions/gastro-vscode diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore index ed9a6a8..daac0b0 100644 --- a/editors/vscode/.gitignore +++ b/editors/vscode/.gitignore @@ -1,2 +1,3 @@ bin/ +dist/ package-lock.json diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index 4fa8b51..8ddd78e 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -2,3 +2,6 @@ node_modules/** package-lock.json bin/ +src/ +tsconfig.json +esbuild.mjs diff --git a/editors/vscode/esbuild.mjs b/editors/vscode/esbuild.mjs new file mode 100644 index 0000000..7cdbdee --- /dev/null +++ b/editors/vscode/esbuild.mjs @@ -0,0 +1,22 @@ +import * as esbuild from "esbuild"; + +const watch = process.argv.includes("--watch"); + +const ctx = await esbuild.context({ + entryPoints: ["src/extension.ts"], + bundle: true, + outfile: "dist/extension.js", + external: ["vscode"], + format: "cjs", + platform: "node", + minify: !watch, + sourcemap: watch, +}); + +if (watch) { + await ctx.watch(); + console.log("Watching for changes..."); +} else { + await ctx.rebuild(); + await ctx.dispose(); +} diff --git a/editors/vscode/extension.js b/editors/vscode/extension.js deleted file mode 100644 index a56f398..0000000 --- a/editors/vscode/extension.js +++ /dev/null @@ -1,75 +0,0 @@ -const { workspace, window, commands } = require("vscode"); -const { - LanguageClient, - TransportKind, -} = require("vscode-languageclient/node"); - -let client; - -function activate(context) { - const config = workspace.getConfiguration("gastro"); - const customPath = config.get("lspPath"); - - const command = customPath || "gastro"; - const args = customPath ? [] : ["lsp"]; - - const serverOptions = { - command, - args, - transport: TransportKind.stdio, - }; - - const clientOptions = { - documentSelector: [{ scheme: "file", language: "gastro" }], - }; - - client = new LanguageClient( - "gastro-lsp", - "Gastro Language Server", - serverOptions, - clientOptions, - ); - - client.start().catch((err) => { - const msg = - `Gastro LSP failed to start. Is "gastro" installed and on your PATH?\n\n` + - `Install with: go install github.com/andrioid/gastro/cmd/gastro@latest\n` + - `Or: mise use github:andrioid/gastro@latest`; - window.showErrorMessage(msg); - }); - - // Handle custom notification when gopls is not available - client.onReady().then(() => { - client.onNotification("gastro/goplsNotAvailable", async (params) => { - const action = await window.showWarningMessage( - "Gopls is not installed. Go language features (completions, hover, " + - "diagnostics) in the frontmatter will be limited.\n\n" + - "Install gopls to enable full Go intelligence.", - "Install gopls", - "Dismiss", - ); - - if (action === "Install gopls") { - const terminal = window.createTerminal("Install gopls"); - terminal.show(); - terminal.sendText("go install golang.org/x/tools/gopls@latest"); - - const reload = await window.showInformationMessage( - "After gopls finishes installing, reload the window to activate Go features.", - "Reload Window", - ); - if (reload === "Reload Window") { - commands.executeCommand("workbench.action.reloadWindow"); - } - } - }); - }); -} - -function deactivate() { - if (client && client.isRunning()) { - return client.stop(); - } -} - -module.exports = { activate, deactivate }; diff --git a/editors/vscode/package.json b/editors/vscode/package.json index a1d3b07..2a07161 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -30,9 +30,20 @@ "component", "language-server" ], + "scripts": { + "build": "node esbuild.mjs", + "watch": "node esbuild.mjs --watch", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist" + }, "dependencies": { "vscode-languageclient": "^9.0.1" }, + "devDependencies": { + "@types/vscode": "^1.80.0", + "esbuild": "^0.25.0", + "typescript": "^5.8.0" + }, "contributes": { "configuration": { "title": "Gastro", @@ -68,5 +79,5 @@ } ] }, - "main": "./extension.js" + "main": "./dist/extension.js" } diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts new file mode 100644 index 0000000..5f06179 --- /dev/null +++ b/editors/vscode/src/extension.ts @@ -0,0 +1,81 @@ +import * as vscode from "vscode"; +import { + LanguageClient, + type LanguageClientOptions, + type ServerOptions, + TransportKind, +} from "vscode-languageclient/node"; + +let client: LanguageClient | undefined; + +export async function activate( + context: vscode.ExtensionContext, +): Promise { + const config = vscode.workspace.getConfiguration("gastro"); + const customPath = config.get("lspPath"); + + const command = customPath || "gastro"; + const args = customPath ? [] : ["lsp"]; + + const serverOptions: ServerOptions = { + command, + args, + transport: TransportKind.stdio, + }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: "file", language: "gastro" }], + }; + + client = new LanguageClient( + "gastro-lsp", + "Gastro Language Server", + serverOptions, + clientOptions, + ); + + // Register notifications before start so none are missed during init + client.onNotification( + "gastro/goplsNotAvailable", + async (_params: unknown) => { + const action = await vscode.window.showWarningMessage( + "Gopls is not installed. Go language features (completions, hover, " + + "diagnostics) in the frontmatter will be limited.\n\n" + + "Install gopls to enable full Go intelligence.", + "Install gopls", + "Dismiss", + ); + + if (action === "Install gopls") { + const terminal = vscode.window.createTerminal("Install gopls"); + terminal.show(); + terminal.sendText("go install golang.org/x/tools/gopls@latest"); + + const reload = await vscode.window.showInformationMessage( + "After gopls finishes installing, reload the window to activate Go features.", + "Reload Window", + ); + if (reload === "Reload Window") { + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + } + }, + ); + + try { + await client.start(); + } catch { + vscode.window.showErrorMessage( + `Gastro LSP failed to start. Is "gastro" installed and on your PATH?\n\n` + + `Install with: go install github.com/andrioid/gastro/cmd/gastro@latest\n` + + `Or: mise use github:andrioid/gastro@latest`, + ); + } +} + +export function deactivate(): Thenable | undefined { + if (client?.isRunning()) { + return client.stop(); + } + return undefined; +} diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json new file mode 100644 index 0000000..0256b4e --- /dev/null +++ b/editors/vscode/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "dist", + "rootDir": "src", + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src"] +} diff --git a/scripts/link/vscode b/scripts/link/vscode index ea07bdc..3bcfea5 100755 --- a/scripts/link/vscode +++ b/scripts/link/vscode @@ -4,6 +4,9 @@ set -euo pipefail +# Bundle the extension so dist/extension.js exists +npm run --prefix editors/vscode build + EXTENSION_DIR="$HOME/.vscode/extensions" VERSION=$(node -p "require('./editors/vscode/package.json').version") LINK_NAME="andrioid.gastro-vscode-${VERSION}"