diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fac42ad --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2026 Maho +# SPDX-License-Identifier: OSL-3.0 + +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + name: Typecheck and build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + cache: npm + + - run: npm ci + + - run: npm run typecheck + + - run: npm run compile + + # Catches packaging errors (bad manifest, missing files) before a tag is cut, + # since the release workflow only runs once the tag is already burned. + - run: npx @vscode/vsce package --out maho-ci.vsix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 707a9cf..1e10843 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,8 @@ jobs: - run: npm ci + - run: npm run typecheck + - run: npm run compile - run: npx @vscode/vsce package diff --git a/AGENTS.md b/AGENTS.md index 0c378d0..f498bd7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,12 +12,13 @@ This is a VS Code extension written in TypeScript. ```bash npm install # Install dependencies +npm run typecheck # Typecheck with tsc (esbuild does not typecheck) npm run compile # Compile TypeScript to JavaScript npm run watch # Watch mode for development npm run package # Package as .vsix for distribution ``` -There are no tests configured in this project. +There are no tests configured in this project. `.github/workflows/ci.yml` runs typecheck, compile and `vsce package` on every push to `main` and every PR; the release workflow repeats the typecheck before publishing. Note that `npm run compile` is a bare esbuild bundle: it strips types without checking them, so a passing build proves nothing about type correctness on its own. ## Testing locally @@ -73,6 +74,9 @@ Single-file extension (`src/extension.ts`) using the `vscode-languageclient` pac 1. Checks for `maho` CLI in the workspace root 2. Reads the `maho.phpCommand` setting (defaults to `php`), splits it, and prepends it to `./maho dev:lsp:start` 3. Starts a `LanguageClient` with the resulting command + 4. Registers Maho's MCP server (`./maho dev:mcp:start`) via `vscode.lm.registerMcpServerDefinitionProvider`, feature-detected so hosts without the API skip it - **`deactivate()`** — stops the language client +The script is always passed as the relative `./maho` with `cwd` set to the workspace root, never as a host absolute path, which would not exist when `maho.phpCommand` runs PHP inside a container. The absolute path is used only for the `fs.existsSync` activation check. + The extension itself does not contain the LSP server — it delegates to the `maho` CLI (part of the Maho ecommerce framework, v26.5+) which runs the actual LSP. diff --git a/README.md b/README.md index 293f6a5..f763255 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,16 @@ By default, the extension uses `php` from your PATH and the `maho` CLI in the wo ### Docker +The configured command is run with the workspace root as its working directory, and the script is passed as `./maho`. When PHP runs inside a container, pass `-w` with the project's path *inside the container* so `./maho` resolves there: + ```json { - "maho.phpCommand": "docker exec mycontainer php" + "maho.phpCommand": "docker exec -w /var/www/html mycontainer php" } ``` +Without `-w`, `./maho` resolves against the container's own working directory, which is usually not the project root. + ## Features All features work across both **PHP** and **XML** files. diff --git a/package.json b/package.json index b718352..e5ec6d2 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "maho.phpCommand": { "type": "string", "default": "php", - "description": "PHP command prepended to `./maho dev:lsp:start` and `./maho dev:mcp:start`. Supports simple paths (e.g. `/usr/local/bin/php`) or Docker-style invocations (e.g. `docker exec mycontainer php`)." + "description": "PHP command prepended to `./maho dev:lsp:start` and `./maho dev:mcp:start`, run from the workspace root. Supports simple paths (e.g. `/usr/local/bin/php`) or Docker-style invocations. For Docker, set the container working directory so `./maho` resolves (e.g. `docker exec -w /var/www/html mycontainer php`)." } } }, diff --git a/src/extension.ts b/src/extension.ts index 26bd163..45eb054 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { } from 'vscode-languageclient/node'; let client: LanguageClient | undefined; +const MAHO_SCRIPT = './maho'; export function activate(context: ExtensionContext): void { const workspaceFolder = workspace.workspaceFolders?.[0]; @@ -26,12 +27,14 @@ export function activate(context: ExtensionContext): void { const parts = phpCommand.split(/\s+/).filter(Boolean); const cwd = workspaceFolder.uri.fsPath; - startLspClient(parts, mahoPath, cwd); - registerMcpServer(context, parts, mahoPath, workspaceFolder.uri); + // Relative to cwd, not the host absolute path: the PHP command may run in another + // filesystem namespace (e.g. `docker exec -w /app php`) where that path is absent. + startLspClient(parts, MAHO_SCRIPT, cwd); + registerMcpServer(context, parts, MAHO_SCRIPT, workspaceFolder.uri); } -function startLspClient(phpParts: string[], mahoPath: string, cwd: string): void { - const [command, ...args] = [...phpParts, mahoPath, 'dev:lsp:start']; +function startLspClient(phpParts: string[], mahoScript: string, cwd: string): void { + const [command, ...args] = [...phpParts, mahoScript, 'dev:lsp:start']; const serverOptions: ServerOptions = { command, @@ -65,14 +68,14 @@ function startLspClient(phpParts: string[], mahoPath: string, cwd: string): void function registerMcpServer( context: ExtensionContext, phpParts: string[], - mahoPath: string, + mahoScript: string, workspaceUri: vscode.Uri, ): void { if (!vscode.lm?.registerMcpServerDefinitionProvider) { return; } - const [command, ...args] = [...phpParts, mahoPath, 'dev:mcp:start']; + const [command, ...args] = [...phpParts, mahoScript, 'dev:mcp:start']; const provider: vscode.McpServerDefinitionProvider = { provideMcpServerDefinitions() {