diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d4c65c243..a25c199f8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,14 +7,11 @@ ], "service": "php", "workspaceFolder": "/app", - "features": { - "ghcr.io/devcontainers/features/node:1": {} - }, "remoteUser": "nonroot", - "postCreateCommand": "npm install -g intelephense", "customizations": { "vscode": { "extensions": [ + "symfony.language-tools", "bmewburn.vscode-intelephense-client", "xdebug.php-debug" ], diff --git a/Dockerfile b/Dockerfile index 239beafe9..c8dce7c6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,6 +51,9 @@ CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile" ] # Dev FrankenPHP image FROM frankenphp_base AS frankenphp_dev +# Repeated until hadolint inherits the parent stage SHELL: https://github.com/hadolint/hadolint/pull/1258 +SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] + ENV APP_ENV=dev ENV XDEBUG_MODE=off ENV FRANKENPHP_WORKER_CONFIG=watch @@ -63,6 +66,21 @@ RUN <<-EOF git config --system --add safe.directory /app EOF +# Symfony Language Server, for editors and AI agents that don't bundle it themselves +# https://github.com/symfony/language-tools +ARG TARGETARCH +RUN <<-EOF + case "$TARGETARCH" in + amd64) lsp_arch=x64 ;; + arm64) lsp_arch=arm64 ;; + *) echo "unsupported architecture: $TARGETARCH"; exit 1 ;; + esac + # The release URL redirects to the latest tag, no API token nor rate limit involved + lsp_version=$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/symfony/language-tools/releases/latest)") + curl -fsSL "https://github.com/symfony/language-tools/releases/download/$lsp_version/symfony-lsp-$lsp_version-linux-$lsp_arch.tar.gz" \ + | tar -xz -C /usr/local/bin --strip-components=1 --wildcards '*/symfony-lsp' +EOF + COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/ CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ] @@ -70,6 +88,9 @@ CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ] # Builder for the prod FrankenPHP image FROM frankenphp_base AS frankenphp_prod_builder +# Repeated until hadolint inherits the parent stage SHELL: https://github.com/hadolint/hadolint/pull/1258 +SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] + ENV APP_ENV=prod RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" @@ -97,7 +118,7 @@ RUN <<-EOF EOF # Collect shared libraries needed by FrankenPHP and PHP extensions -# hadolint ignore=DL3008,SC3054,DL4006 +# hadolint ignore=DL3008,SC3054 RUN <<-'EOF' apt-get update apt-get install -y --no-install-recommends libtree diff --git a/docs/agents.md b/docs/agents.md index 5dc292af2..01672c0de 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -24,12 +24,13 @@ There is no official Dev Container feature for OpenCode yet, so install the CLI ```jsonc { - // Install the CLI on container creation (alongside the existing intelephense install) - "postCreateCommand": "npm install -g intelephense && curl -fsSL https://opencode.ai/install | bash", + // Install the CLI on container creation + "postCreateCommand": "curl -fsSL https://opencode.ai/install | bash", "customizations": { "vscode": { "extensions": [ "sst-dev.opencode", + "symfony.language-tools", "bmewburn.vscode-intelephense-client", "xdebug.php-debug", ], @@ -78,6 +79,7 @@ Visual Studio Code extension. Edit `.devcontainer/devcontainer.json`: "vscode": { "extensions": [ "anthropic.claude-code", + "symfony.language-tools", "bmewburn.vscode-intelephense-client", "xdebug.php-debug", ], @@ -92,6 +94,48 @@ Without the firewall, this is all you need. To let Claude Code run autonomously, [network sandbox](#optional-network-sandbox) first and add `anthropic.com`, `sentry.io`, and `statsig.com` to the allowlist. +## Symfony Language Server + +The dev image ships the [Symfony Language Server](https://github.com/symfony/language-tools) as +`/usr/local/bin/symfony-lsp`. It knows about routes, services, Twig, translations, environment +variables, Messenger, Security, forms, Doctrine and the rest of the framework, so an agent gets +real definitions and diagnostics instead of guesses. It complements a general PHP language server +such as Intelephense, it does not replace it. + +The `symfony.language-tools` Visual Studio Code extension bundles its own copy of the server, so +there is nothing to configure there. The binary in the image is for agents and editors that expect +to launch a server themselves. + +### With OpenCode + +Declare the server in `opencode.json` at the root of the project: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "lsp": { + "symfony": { + "command": ["symfony-lsp"], + "extensions": [".php", ".twig", ".yaml", ".yml", ".xml"] + } + } +} +``` + +### From the command line + +`symfony-lsp check` reports the same diagnostics without an editor, which is useful to let an agent +verify its own changes: + +```console +symfony-lsp check --format=json src/ templates/ +``` + +`--format=github` is also available for CI annotations. The check runs the application to index the +container, so pass `--source-only` when the code cannot be trusted. See +[Introducing symfony-lsp check](https://symfony.com/blog/introducing-symfony-lsp-check-symfony-aware-diagnostics-in-your-ci) +for the full picture. + ## Optional: network sandbox Letting an agent edit files and run commands without confirmation (autonomous, or "YOLO", mode)