From ba3b82ccedc9e7948c9c9ac9e4176ab137a5bcdd Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Wed, 25 Mar 2026 03:40:26 +1000 Subject: [PATCH 1/2] feat: Automatic LSP configuration for Copilot CLI (#74) Automatically configure Language Server Protocol (LSP) servers in container images so Copilot CLI gets code intelligence out of the box. - Add lsp-typescript snippet: installs typescript-language-server for all images - Add lsp-csharp snippet: installs csharp-ls for dotnet images with .NET 10 SDK (csharp-ls v0.22.0 only ships net10.0 TFM, so dotnet-8/9 excluded) - Add lsp-rust snippet: adds rust-analyzer via rustup for rust images - Add lsp-golang snippet: installs gopls for golang images - Each snippet writes a config fragment to /etc/copilot/lsp-config.d/ - Entrypoint merges fragments into ~/.copilot/lsp-config.json at startup (skipped if user provides their own) - Update images.json and regenerate all Dockerfiles Closes #74 Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: GitButler --- .github/copilot-instructions.md | 9 +++--- docker/generated/Dockerfile.default | 9 ++++++ docker/generated/Dockerfile.dotnet | 20 +++++++++++++ docker/generated/Dockerfile.dotnet-10 | 20 +++++++++++++ docker/generated/Dockerfile.dotnet-8 | 9 ++++++ docker/generated/Dockerfile.dotnet-9 | 9 ++++++ docker/generated/Dockerfile.dotnet-playwright | 20 +++++++++++++ docker/generated/Dockerfile.dotnet-rust | 29 +++++++++++++++++++ docker/generated/Dockerfile.golang | 18 ++++++++++++ docker/generated/Dockerfile.playwright | 9 ++++++ docker/generated/Dockerfile.rust | 18 ++++++++++++ docker/images.json | 20 ++++++------- docker/shared/entrypoint.sh | 15 ++++++++++ docker/snippets/lsp-csharp.Dockerfile | 9 ++++++ docker/snippets/lsp-golang.Dockerfile | 7 +++++ docker/snippets/lsp-rust.Dockerfile | 7 +++++ docker/snippets/lsp-typescript.Dockerfile | 7 +++++ 17 files changed, 220 insertions(+), 15 deletions(-) create mode 100644 docker/snippets/lsp-csharp.Dockerfile create mode 100644 docker/snippets/lsp-golang.Dockerfile create mode 100644 docker/snippets/lsp-rust.Dockerfile create mode 100644 docker/snippets/lsp-typescript.Dockerfile diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 054c9f2..9dda7f2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -39,29 +39,28 @@ You can also use the command `session-info` for more info on mounts for this pro 3. **Build properties**: `Directory.Build.props` - - Line 4: `YYYY.MM.DD` + - Reads `CopilotHereVersion` from the `VERSION` file automatically (no manual edit needed) 4. **Build info**: `app/Infrastructure/BuildInfo.cs` - Line 13: `public const string BuildDate = "YYYY.MM.DD";` -5. **This file**: `.github/copilot-instructions.md` - - Update "Current version" below - ### Verification Checklist Before committing, verify all 5 locations have the EXACT SAME version: ```bash # Quick check - all should show the same version +cat VERSION grep "Version: " copilot_here.sh grep "Version: " copilot_here.ps1 grep "COPILOT_HERE_VERSION=" copilot_here.sh grep "CopilotHereVersion =" copilot_here.ps1 -grep "CopilotHereVersion>" Directory.Build.props grep "BuildDate = " app/Infrastructure/BuildInfo.cs ``` +Use `scripts/bump-version.sh YYYY.MM.DD` to update all locations at once. + ### When to Update Version - Any modification to shell function code diff --git a/docker/generated/Dockerfile.default b/docker/generated/Dockerfile.default index ab28b1c..08646a1 100644 --- a/docker/generated/Dockerfile.default +++ b/docker/generated/Dockerfile.default @@ -44,6 +44,15 @@ RUN ARCH=$(dpkg --print-architecture) \ && rm /tmp/powershell.tar.gz; \ fi +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet b/docker/generated/Dockerfile.dotnet index 0467b91..a96d117 100644 --- a/docker/generated/Dockerfile.dotnet +++ b/docker/generated/Dockerfile.dotnet @@ -95,6 +95,26 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ fi \ && rm dotnet-install.sh +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-csharp --- +# Install C# Language Server for code intelligence +# Install globally as root, then symlink to a shared location so appuser can access it +RUN dotnet tool install -g csharp-ls \ + && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls + +# Write LSP config fragment for C# +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "csharp": { "command": "csharp-ls", "args": [], "fileExtensions": { ".cs": "csharp", ".csx": "csharp" } } } }' \ + > /etc/copilot/lsp-config.d/csharp.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet-10 b/docker/generated/Dockerfile.dotnet-10 index 1f74541..673ab0b 100644 --- a/docker/generated/Dockerfile.dotnet-10 +++ b/docker/generated/Dockerfile.dotnet-10 @@ -69,6 +69,26 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ fi \ && rm dotnet-install.sh +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-csharp --- +# Install C# Language Server for code intelligence +# Install globally as root, then symlink to a shared location so appuser can access it +RUN dotnet tool install -g csharp-ls \ + && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls + +# Write LSP config fragment for C# +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "csharp": { "command": "csharp-ls", "args": [], "fileExtensions": { ".cs": "csharp", ".csx": "csharp" } } } }' \ + > /etc/copilot/lsp-config.d/csharp.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet-8 b/docker/generated/Dockerfile.dotnet-8 index 7703493..f338bb2 100644 --- a/docker/generated/Dockerfile.dotnet-8 +++ b/docker/generated/Dockerfile.dotnet-8 @@ -69,6 +69,15 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ fi \ && rm dotnet-install.sh +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet-9 b/docker/generated/Dockerfile.dotnet-9 index df9f62a..c3c9fff 100644 --- a/docker/generated/Dockerfile.dotnet-9 +++ b/docker/generated/Dockerfile.dotnet-9 @@ -69,6 +69,15 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ fi \ && rm dotnet-install.sh +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet-playwright b/docker/generated/Dockerfile.dotnet-playwright index 6cb3c46..1956ebb 100644 --- a/docker/generated/Dockerfile.dotnet-playwright +++ b/docker/generated/Dockerfile.dotnet-playwright @@ -136,6 +136,26 @@ RUN npx playwright install --with-deps # Make playwright browsers directory world-writable so any UID can access/update them RUN chmod -R 777 /ms-playwright +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-csharp --- +# Install C# Language Server for code intelligence +# Install globally as root, then symlink to a shared location so appuser can access it +RUN dotnet tool install -g csharp-ls \ + && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls + +# Write LSP config fragment for C# +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "csharp": { "command": "csharp-ls", "args": [], "fileExtensions": { ".cs": "csharp", ".csx": "csharp" } } } }' \ + > /etc/copilot/lsp-config.d/csharp.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.dotnet-rust b/docker/generated/Dockerfile.dotnet-rust index 794e525..9886a33 100644 --- a/docker/generated/Dockerfile.dotnet-rust +++ b/docker/generated/Dockerfile.dotnet-rust @@ -116,6 +116,35 @@ RUN chmod -R a+rwX /usr/local/cargo # Verify installation RUN rustc --version && cargo --version +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-csharp --- +# Install C# Language Server for code intelligence +# Install globally as root, then symlink to a shared location so appuser can access it +RUN dotnet tool install -g csharp-ls \ + && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls + +# Write LSP config fragment for C# +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "csharp": { "command": "csharp-ls", "args": [], "fileExtensions": { ".cs": "csharp", ".csx": "csharp" } } } }' \ + > /etc/copilot/lsp-config.d/csharp.json + +# --- snippet: lsp-rust --- +# Install Rust Analyzer for code intelligence +RUN rustup component add rust-analyzer + +# Write LSP config fragment for Rust +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "rust": { "command": "rust-analyzer", "args": [], "fileExtensions": { ".rs": "rust" } } } }' \ + > /etc/copilot/lsp-config.d/rust.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.golang b/docker/generated/Dockerfile.golang index 7283aa8..71ad5d7 100644 --- a/docker/generated/Dockerfile.golang +++ b/docker/generated/Dockerfile.golang @@ -63,6 +63,24 @@ RUN mkdir -p ${GOPATH} \ # Verify installation RUN go version +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-golang --- +# Install gopls (Go Language Server) for code intelligence +RUN go install golang.org/x/tools/gopls@latest + +# Write LSP config fragment for Go +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "go": { "command": "gopls", "args": ["serve"], "fileExtensions": { ".go": "go" } } } }' \ + > /etc/copilot/lsp-config.d/golang.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.playwright b/docker/generated/Dockerfile.playwright index 0ac03fe..9e91ea9 100644 --- a/docker/generated/Dockerfile.playwright +++ b/docker/generated/Dockerfile.playwright @@ -85,6 +85,15 @@ RUN npx playwright install --with-deps # Make playwright browsers directory world-writable so any UID can access/update them RUN chmod -R 777 /ms-playwright +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/generated/Dockerfile.rust b/docker/generated/Dockerfile.rust index fb90372..9a72ebe 100644 --- a/docker/generated/Dockerfile.rust +++ b/docker/generated/Dockerfile.rust @@ -65,6 +65,24 @@ RUN chmod -R a+rwX /usr/local/cargo # Verify installation RUN rustc --version && cargo --version +# --- snippet: lsp-typescript --- +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json + +# --- snippet: lsp-rust --- +# Install Rust Analyzer for code intelligence +RUN rustup component add rust-analyzer + +# Write LSP config fragment for Rust +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "rust": { "command": "rust-analyzer", "args": [], "fileExtensions": { ".rs": "rust" } } } }' \ + > /etc/copilot/lsp-config.d/rust.json + # --- snippet: copilot-cli --- # ARG for the Copilot CLI version - passed from build process # This ensures cache invalidation when a new version is available diff --git a/docker/images.json b/docker/images.json index 37b4d51..01fc921 100644 --- a/docker/images.json +++ b/docker/images.json @@ -2,43 +2,43 @@ "images": { "default": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "lsp-typescript", "copilot-cli", "entrypoint"] }, "dotnet": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "lsp-typescript", "lsp-csharp", "copilot-cli", "entrypoint"] }, "dotnet-8": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "lsp-typescript", "copilot-cli", "entrypoint"] }, "dotnet-9": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet9", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet9", "lsp-typescript", "copilot-cli", "entrypoint"] }, "dotnet-10": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet10", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet10", "lsp-typescript", "lsp-csharp", "copilot-cli", "entrypoint"] }, "playwright": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "playwright", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "playwright", "lsp-typescript", "copilot-cli", "entrypoint"] }, "rust": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "rust", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "rust", "lsp-typescript", "lsp-rust", "copilot-cli", "entrypoint"] }, "golang": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "golang", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "golang", "lsp-typescript", "lsp-golang", "copilot-cli", "entrypoint"] }, "dotnet-playwright": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "playwright", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "playwright", "lsp-typescript", "lsp-csharp", "copilot-cli", "entrypoint"] }, "dotnet-rust": { "base": "node:20-slim", - "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "rust", "copilot-cli", "entrypoint"] + "snippets": ["system-packages", "powershell", "dotnet-prereqs", "dotnet8", "dotnet9", "dotnet10", "rust", "lsp-typescript", "lsp-csharp", "lsp-rust", "copilot-cli", "entrypoint"] } } } diff --git a/docker/shared/entrypoint.sh b/docker/shared/entrypoint.sh index 8386d64..f92b0d8 100644 --- a/docker/shared/entrypoint.sh +++ b/docker/shared/entrypoint.sh @@ -69,5 +69,20 @@ chown -R "$USER_ID:$GROUP_ID" /home/appuser/.npm export HOME=/home/appuser +# Merge LSP config fragments into ~/.copilot/lsp-config.json (if not already provided by user) +if [ -d /etc/copilot/lsp-config.d ] && [ ! -f /home/appuser/.copilot/lsp-config.json ]; then + node -e " + const fs = require('fs'), path = require('path'); + const dir = '/etc/copilot/lsp-config.d'; + const servers = {}; + fs.readdirSync(dir).filter(f => f.endsWith('.json')).sort().forEach(f => { + const cfg = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); + Object.assign(servers, cfg.lspServers || {}); + }); + fs.writeFileSync('/home/appuser/.copilot/lsp-config.json', JSON.stringify({ lspServers: servers }, null, 2)); + " + chown "$USER_ID:$GROUP_ID" /home/appuser/.copilot/lsp-config.json +fi + # Switch to the user matching the host UID and execute the command passed to the script. exec gosu appuser "$@" diff --git a/docker/snippets/lsp-csharp.Dockerfile b/docker/snippets/lsp-csharp.Dockerfile new file mode 100644 index 0000000..9532073 --- /dev/null +++ b/docker/snippets/lsp-csharp.Dockerfile @@ -0,0 +1,9 @@ +# Install C# Language Server for code intelligence +# Install globally as root, then symlink to a shared location so appuser can access it +RUN dotnet tool install -g csharp-ls \ + && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls + +# Write LSP config fragment for C# +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "csharp": { "command": "csharp-ls", "args": [], "fileExtensions": { ".cs": "csharp", ".csx": "csharp" } } } }' \ + > /etc/copilot/lsp-config.d/csharp.json diff --git a/docker/snippets/lsp-golang.Dockerfile b/docker/snippets/lsp-golang.Dockerfile new file mode 100644 index 0000000..4e85f16 --- /dev/null +++ b/docker/snippets/lsp-golang.Dockerfile @@ -0,0 +1,7 @@ +# Install gopls (Go Language Server) for code intelligence +RUN go install golang.org/x/tools/gopls@latest + +# Write LSP config fragment for Go +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "go": { "command": "gopls", "args": ["serve"], "fileExtensions": { ".go": "go" } } } }' \ + > /etc/copilot/lsp-config.d/golang.json diff --git a/docker/snippets/lsp-rust.Dockerfile b/docker/snippets/lsp-rust.Dockerfile new file mode 100644 index 0000000..718c165 --- /dev/null +++ b/docker/snippets/lsp-rust.Dockerfile @@ -0,0 +1,7 @@ +# Install Rust Analyzer for code intelligence +RUN rustup component add rust-analyzer + +# Write LSP config fragment for Rust +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "rust": { "command": "rust-analyzer", "args": [], "fileExtensions": { ".rs": "rust" } } } }' \ + > /etc/copilot/lsp-config.d/rust.json diff --git a/docker/snippets/lsp-typescript.Dockerfile b/docker/snippets/lsp-typescript.Dockerfile new file mode 100644 index 0000000..9a9c262 --- /dev/null +++ b/docker/snippets/lsp-typescript.Dockerfile @@ -0,0 +1,7 @@ +# Install TypeScript Language Server for code intelligence +RUN npm install -g typescript typescript-language-server + +# Write LSP config fragment for TypeScript +RUN mkdir -p /etc/copilot/lsp-config.d && \ + echo '{ "lspServers": { "typescript": { "command": "typescript-language-server", "args": ["--stdio"], "fileExtensions": { ".ts": "typescript", ".tsx": "typescriptreact", ".js": "javascript", ".jsx": "javascriptreact" } } } }' \ + > /etc/copilot/lsp-config.d/typescript.json From 101502c302b6418e29021441a99970df9557b490 Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Wed, 25 Mar 2026 05:36:15 +1000 Subject: [PATCH 2/2] fix: Pin LSP server versions and add error handling Address PR review feedback: - Add ARG for typescript, typescript-language-server versions (default: latest) - Add ARG for csharp-ls version (default: latest) - Add ARG for gopls version (default: latest) - Use --tool-path for csharp-ls instead of -g + symlink - Add try/catch per LSP config fragment in entrypoint to prevent malformed JSON from bricking container startup - Regenerate all Dockerfiles Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: GitButler --- docker/generated/Dockerfile.default | 4 +++- docker/generated/Dockerfile.dotnet | 10 ++++++---- docker/generated/Dockerfile.dotnet-10 | 10 ++++++---- docker/generated/Dockerfile.dotnet-8 | 4 +++- docker/generated/Dockerfile.dotnet-9 | 4 +++- docker/generated/Dockerfile.dotnet-playwright | 10 ++++++---- docker/generated/Dockerfile.dotnet-rust | 10 ++++++---- docker/generated/Dockerfile.golang | 7 +++++-- docker/generated/Dockerfile.playwright | 4 +++- docker/generated/Dockerfile.rust | 4 +++- docker/shared/entrypoint.sh | 10 ++++++++-- docker/snippets/lsp-csharp.Dockerfile | 6 +++--- docker/snippets/lsp-golang.Dockerfile | 3 ++- docker/snippets/lsp-typescript.Dockerfile | 4 +++- 14 files changed, 60 insertions(+), 30 deletions(-) diff --git a/docker/generated/Dockerfile.default b/docker/generated/Dockerfile.default index 08646a1..4675104 100644 --- a/docker/generated/Dockerfile.default +++ b/docker/generated/Dockerfile.default @@ -46,7 +46,9 @@ RUN ARCH=$(dpkg --print-architecture) \ # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet b/docker/generated/Dockerfile.dotnet index a96d117..f3b803e 100644 --- a/docker/generated/Dockerfile.dotnet +++ b/docker/generated/Dockerfile.dotnet @@ -97,7 +97,9 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ @@ -106,9 +108,9 @@ RUN mkdir -p /etc/copilot/lsp-config.d && \ # --- snippet: lsp-csharp --- # Install C# Language Server for code intelligence -# Install globally as root, then symlink to a shared location so appuser can access it -RUN dotnet tool install -g csharp-ls \ - && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls +# Install directly to a shared location so appuser can access it +ARG CSHARP_LS_VERSION=latest +RUN dotnet tool install csharp-ls --tool-path /usr/local/bin --version ${CSHARP_LS_VERSION} # Write LSP config fragment for C# RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet-10 b/docker/generated/Dockerfile.dotnet-10 index 673ab0b..470dc39 100644 --- a/docker/generated/Dockerfile.dotnet-10 +++ b/docker/generated/Dockerfile.dotnet-10 @@ -71,7 +71,9 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ @@ -80,9 +82,9 @@ RUN mkdir -p /etc/copilot/lsp-config.d && \ # --- snippet: lsp-csharp --- # Install C# Language Server for code intelligence -# Install globally as root, then symlink to a shared location so appuser can access it -RUN dotnet tool install -g csharp-ls \ - && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls +# Install directly to a shared location so appuser can access it +ARG CSHARP_LS_VERSION=latest +RUN dotnet tool install csharp-ls --tool-path /usr/local/bin --version ${CSHARP_LS_VERSION} # Write LSP config fragment for C# RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet-8 b/docker/generated/Dockerfile.dotnet-8 index f338bb2..124d62a 100644 --- a/docker/generated/Dockerfile.dotnet-8 +++ b/docker/generated/Dockerfile.dotnet-8 @@ -71,7 +71,9 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet-9 b/docker/generated/Dockerfile.dotnet-9 index c3c9fff..7f0d563 100644 --- a/docker/generated/Dockerfile.dotnet-9 +++ b/docker/generated/Dockerfile.dotnet-9 @@ -71,7 +71,9 @@ RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet-playwright b/docker/generated/Dockerfile.dotnet-playwright index 1956ebb..087c97a 100644 --- a/docker/generated/Dockerfile.dotnet-playwright +++ b/docker/generated/Dockerfile.dotnet-playwright @@ -138,7 +138,9 @@ RUN chmod -R 777 /ms-playwright # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ @@ -147,9 +149,9 @@ RUN mkdir -p /etc/copilot/lsp-config.d && \ # --- snippet: lsp-csharp --- # Install C# Language Server for code intelligence -# Install globally as root, then symlink to a shared location so appuser can access it -RUN dotnet tool install -g csharp-ls \ - && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls +# Install directly to a shared location so appuser can access it +ARG CSHARP_LS_VERSION=latest +RUN dotnet tool install csharp-ls --tool-path /usr/local/bin --version ${CSHARP_LS_VERSION} # Write LSP config fragment for C# RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.dotnet-rust b/docker/generated/Dockerfile.dotnet-rust index 9886a33..c831d3c 100644 --- a/docker/generated/Dockerfile.dotnet-rust +++ b/docker/generated/Dockerfile.dotnet-rust @@ -118,7 +118,9 @@ RUN rustc --version && cargo --version # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ @@ -127,9 +129,9 @@ RUN mkdir -p /etc/copilot/lsp-config.d && \ # --- snippet: lsp-csharp --- # Install C# Language Server for code intelligence -# Install globally as root, then symlink to a shared location so appuser can access it -RUN dotnet tool install -g csharp-ls \ - && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls +# Install directly to a shared location so appuser can access it +ARG CSHARP_LS_VERSION=latest +RUN dotnet tool install csharp-ls --tool-path /usr/local/bin --version ${CSHARP_LS_VERSION} # Write LSP config fragment for C# RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.golang b/docker/generated/Dockerfile.golang index 71ad5d7..3c922b2 100644 --- a/docker/generated/Dockerfile.golang +++ b/docker/generated/Dockerfile.golang @@ -65,7 +65,9 @@ RUN go version # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ @@ -74,7 +76,8 @@ RUN mkdir -p /etc/copilot/lsp-config.d && \ # --- snippet: lsp-golang --- # Install gopls (Go Language Server) for code intelligence -RUN go install golang.org/x/tools/gopls@latest +ARG GOPLS_VERSION=latest +RUN go install golang.org/x/tools/gopls@${GOPLS_VERSION} # Write LSP config fragment for Go RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.playwright b/docker/generated/Dockerfile.playwright index 9e91ea9..d6c7cae 100644 --- a/docker/generated/Dockerfile.playwright +++ b/docker/generated/Dockerfile.playwright @@ -87,7 +87,9 @@ RUN chmod -R 777 /ms-playwright # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/generated/Dockerfile.rust b/docker/generated/Dockerfile.rust index 9a72ebe..6132253 100644 --- a/docker/generated/Dockerfile.rust +++ b/docker/generated/Dockerfile.rust @@ -67,7 +67,9 @@ RUN rustc --version && cargo --version # --- snippet: lsp-typescript --- # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/shared/entrypoint.sh b/docker/shared/entrypoint.sh index f92b0d8..24dc400 100644 --- a/docker/shared/entrypoint.sh +++ b/docker/shared/entrypoint.sh @@ -76,8 +76,14 @@ if [ -d /etc/copilot/lsp-config.d ] && [ ! -f /home/appuser/.copilot/lsp-config. const dir = '/etc/copilot/lsp-config.d'; const servers = {}; fs.readdirSync(dir).filter(f => f.endsWith('.json')).sort().forEach(f => { - const cfg = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); - Object.assign(servers, cfg.lspServers || {}); + const fullPath = path.join(dir, f); + try { + const contents = fs.readFileSync(fullPath, 'utf8'); + const cfg = JSON.parse(contents); + Object.assign(servers, cfg.lspServers || {}); + } catch (err) { + console.error('Warning: failed to load LSP config fragment ' + fullPath + ': ' + err.message); + } }); fs.writeFileSync('/home/appuser/.copilot/lsp-config.json', JSON.stringify({ lspServers: servers }, null, 2)); " diff --git a/docker/snippets/lsp-csharp.Dockerfile b/docker/snippets/lsp-csharp.Dockerfile index 9532073..98c8d84 100644 --- a/docker/snippets/lsp-csharp.Dockerfile +++ b/docker/snippets/lsp-csharp.Dockerfile @@ -1,7 +1,7 @@ # Install C# Language Server for code intelligence -# Install globally as root, then symlink to a shared location so appuser can access it -RUN dotnet tool install -g csharp-ls \ - && ln -s /root/.dotnet/tools/csharp-ls /usr/local/bin/csharp-ls +# Install directly to a shared location so appuser can access it +ARG CSHARP_LS_VERSION=latest +RUN dotnet tool install csharp-ls --tool-path /usr/local/bin --version ${CSHARP_LS_VERSION} # Write LSP config fragment for C# RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/snippets/lsp-golang.Dockerfile b/docker/snippets/lsp-golang.Dockerfile index 4e85f16..73b1f6d 100644 --- a/docker/snippets/lsp-golang.Dockerfile +++ b/docker/snippets/lsp-golang.Dockerfile @@ -1,5 +1,6 @@ # Install gopls (Go Language Server) for code intelligence -RUN go install golang.org/x/tools/gopls@latest +ARG GOPLS_VERSION=latest +RUN go install golang.org/x/tools/gopls@${GOPLS_VERSION} # Write LSP config fragment for Go RUN mkdir -p /etc/copilot/lsp-config.d && \ diff --git a/docker/snippets/lsp-typescript.Dockerfile b/docker/snippets/lsp-typescript.Dockerfile index 9a9c262..814badc 100644 --- a/docker/snippets/lsp-typescript.Dockerfile +++ b/docker/snippets/lsp-typescript.Dockerfile @@ -1,5 +1,7 @@ # Install TypeScript Language Server for code intelligence -RUN npm install -g typescript typescript-language-server +ARG TYPESCRIPT_VERSION=latest +ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=latest +RUN npm install -g typescript@${TYPESCRIPT_VERSION} typescript-language-server@${TYPESCRIPT_LANGUAGE_SERVER_VERSION} # Write LSP config fragment for TypeScript RUN mkdir -p /etc/copilot/lsp-config.d && \