diff --git a/.serena/memories/critical-info.md b/.serena/memories/critical-info.md index 3a822cd873..053bf961d8 100644 --- a/.serena/memories/critical-info.md +++ b/.serena/memories/critical-info.md @@ -60,6 +60,7 @@ The memory is structured in a way that you can get the critical information abou - `scripts/changelog.py` — Changelog checks for the `update-changelog` skill: `check-merged` (PR merge status), `cross-ref` (milestone PRs vs changelog section), `report` (anomaly report to CHANGES-ISSUES.md). Calls `scripts/gh.py` via subprocess. See `mem:scripts/changelog`. - `scripts/error-reports.mjs` — Query error reports via RPC API with token authentication. Supports list/get operations with filtering and pagination. See `mem:scripts/error-reports`. - `scripts/clean-node-modules` — Remove stale `node_modules` from all pnpm workspaces (root, modules, member packages). Keeps the shared pnpm store at `/.pnpm-store` unless `--store`; ignores `external/` and `.opencode/`. Usage and reinstall steps: `mem:workflow/updating-pnpm`. +- `scripts/setup-opencode` — Install the opencode CLI in-container as a global pnpm package in `$PNPM_HOME`. Not part of the devenv image; see `mem:devenv/core`. - `scripts/ci` — CI orchestration script: runs lint, tests, and format checks per module (`frontend backend common render-wasm exporter mcp plugins library`). Logs go to `.ci-logs/`; read the log file on failure. See `mem:scripts/ci`. # Dependency graph diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index 7bd436221a..7e9161fff0 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -24,6 +24,8 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par - Network aliases (`- main`, `- redis`) are not declared in main.yml. Compose's auto-service-alias still registers `redis` on the shared network, so DNS for `redis` is non-deterministic with multiple instances. Backend uses `PENPOT_REDIS_URI=redis://penpot-devenv-wsN-valkey/0` (container_name) instead. - No cross-project `depends_on`. `manage.sh ensure-infra-up` uses Compose `--wait`; PostgreSQL, RustFS, and Mailpit expose healthchecks. PostgreSQL checks TCP so its temporary init server cannot report ready. - `JAVA_OPTS` in `manage.sh` is shadowed inside the container by `_env`. The `-e JAVA_OPTS=...` flag only matters for processes that don't source `_env`. +- opencode is not part of the devenv image. `scripts/setup-opencode` installs the `@opencode/cli` global pnpm package into the penpot user's `$PNPM_HOME` (`/home/penpot/.local/share/pnpm`, inside the user-data volume, so it survives a container re-creation); nothing pins the version in the repo, it tracks the `latest` dist-tag at install time. pnpm 12 puts the global packages in `$PNPM_HOME/global` and the bin dir in `$PNPM_HOME/bin`, and aborts with `ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH` unless that bin dir is on `PATH`; `files/bashrc` and the script both add it. The script exports `PNPM_HOME` itself because non-interactive shells never read `~/.bashrc`. +- `files/entrypoint.sh` chowns penpot's whole `~/.local` and `~/.cache` on every start, because `EXTERNAL_UID` remaps penpot's UID to the host user and build-time ownership then stops matching. That covers pnpm's global dir, store, `package-manager-store` and metadata cache, plus opencode's own state; without it, global installs and pnpm's `packageManager` auto-download fail on permissions. The dotfiles copied from `/root` (`.bashrc`, `.vimrc`, `.tmux.conf`) are chowned in the same pass. `~/.config` stays out on purpose (the opencode overlay bind-mounts a host directory inside it) and so does `/home/penpot` itself, which holds the repo bind mount. ## Worker policy diff --git a/.serena/memories/workflow/updating-pnpm.md b/.serena/memories/workflow/updating-pnpm.md index 1c92add414..cbaafdeffc 100644 --- a/.serena/memories/workflow/updating-pnpm.md +++ b/.serena/memories/workflow/updating-pnpm.md @@ -33,7 +33,9 @@ file (never pipe tool output through filters). images). Nothing may call corepack: it is gone from Node 25+. pnpm auto-downloads the `packageManager` version on mismatch (`pmOnFail: download`, the default), so drift self-heals; aligned pins - just skip the download. + just skip the download. Inside the devenv that download lands in the + penpot user's `package-manager-store`, which `files/entrypoint.sh` chowns + to penpot on every start (see `mem:devenv/core`). - The pnpm version pin lives in three places that move together: `PNPM_VERSION` (+ arch SHAs) in `docker/devenv/Dockerfile` and in `docker/images/Dockerfile.{media-processor,exporter,mcp}`, and the diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index ebc75a1d2d..4cc0e0817e 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -103,37 +103,6 @@ RUN set -eux; \ find /opt/node/include/node/openssl/archs -mindepth 1 -maxdepth 1 ! -name "$OPENSSL_ARCH" -exec rm -rf {} \; ; \ rm -f /tmp/nodejs.tar.gz /tmp/pnpm.tar.gz; -################################################################################ -## OPENCODE SETUP -################################################################################ - -# FROM base AS setup-opencode - -# ENV OPENCODE_VERSION=2.0.29 - -# RUN set -eux; \ -# ARCH="$(dpkg --print-architecture)"; \ -# case "${ARCH}" in \ -# aarch64|arm64) \ -# BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-arm64/-/cli-linux-arm64-${OPENCODE_VERSION}.tgz"; \ -# ESUM='0af39e75dbdfacc20f8f531ef35fb4dcc0b44c08084203518614272f232283ae'; \ -# ;; \ -# amd64|x86_64) \ -# BINARY_URL="https://registry.npmjs.org/@opencode/cli-linux-x64/-/cli-linux-x64-${OPENCODE_VERSION}.tgz"; \ -# ESUM='14b5a6638a1a093ac9d484599c844b10c29b445d04844b682ccc668ce26530a2'; \ -# ;; \ -# *) \ -# echo "Unsupported arch: ${ARCH}"; exit 1; \ -# ;; \ -# esac; \ -# curl -LfsSo /tmp/opencode.tgz "${BINARY_URL}"; \ -# echo "${ESUM} */tmp/opencode.tgz" | sha256sum -c -; \ -# mkdir -p /tmp/opencode; \ -# tar -xzf /tmp/opencode.tgz -C /tmp/opencode --strip-components=2 package/bin; \ -# chmod +x /tmp/opencode/opencode; \ -# rm -f /tmp/opencode.tgz; - - ################################################################################ ## CADDYSERVER SETUP ################################################################################ @@ -558,7 +527,6 @@ COPY --from=setup-rust /opt/cargo /opt/cargo COPY --from=setup-rust /opt/rustup /opt/rustup COPY --from=setup-rust /opt/emsdk /opt/emsdk COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy -# COPY --from=setup-opencode /tmp/opencode/opencode /opt/utils/bin/opencode COPY files/nginx.conf /etc/nginx/nginx.conf COPY files/nginx-mime.types /etc/nginx/mime.types diff --git a/docker/devenv/files/bashrc b/docker/devenv/files/bashrc index 5097ab010e..5fb66c9613 100644 --- a/docker/devenv/files/bashrc +++ b/docker/devenv/files/bashrc @@ -27,7 +27,7 @@ fi # pnpm export PNPM_HOME="/home/penpot/.local/share/pnpm" case ":$PATH:" in - *":$PNPM_HOME:"*) ;; - *) export PATH="$PNPM_HOME:$PATH" ;; + *":$PNPM_HOME/bin:"*) ;; + *) export PATH="$PNPM_HOME/bin:$PATH" ;; esac # pnpm end diff --git a/docker/devenv/files/entrypoint.sh b/docker/devenv/files/entrypoint.sh index d7a00a670a..193005d3fe 100755 --- a/docker/devenv/files/entrypoint.sh +++ b/docker/devenv/files/entrypoint.sh @@ -10,6 +10,10 @@ cp /root/.bashrc /home/penpot/.bashrc cp /root/.vimrc /home/penpot/.vimrc cp /root/.tmux.conf /home/penpot/.tmux.conf +chown -R penpot:penpot /home/penpot/.bashrc +chown -R penpot:penpot /home/penpot/.vimrc +chown -R penpot:penpot /home/penpot/.tmux.conf + # Seed SERENA_HOME with default config on first run mkdir -p ${SERENA_HOME} if [ ! -f "${SERENA_HOME}/serena_config.yml" ]; then @@ -21,6 +25,16 @@ chown penpot:users /home/penpot # we need to be able to install rust-analyzer and possibly other dependencies with rustup chown -R penpot:penpot /opt/rustup +# pnpm keeps its global dir (bin, global, store, package-manager-store) +# under ~/.local and its metadata cache under ~/.cache; opencode and other +# user-space tools write there too. Hand both trees back to penpot so +# packages, and pnpm itself, can be updated without root. Needed because the +# UID remap above can leave everything the image created owned by the old +# UID. ~/.config is deliberately excluded: the opencode overlay bind-mounts +# a host directory inside it. +mkdir -p /home/penpot/.local /home/penpot/.cache +chown -R penpot:penpot /home/penpot/.local /home/penpot/.cache + rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/ export JAVA_OPTS="-Djava.net.preferIPv4Stack=true" diff --git a/scripts/setup-opencode b/scripts/setup-opencode new file mode 100755 index 0000000000..044311b45f --- /dev/null +++ b/scripts/setup-opencode @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Install the opencode CLI as a global pnpm package. +# +# opencode is not part of the devenv image: run this when you want the +# `opencode` and `opencode2` commands. The install lives in $PNPM_HOME, which +# inside the devenv is the penpot home (the user-data volume), so it survives +# a container re-creation. +# +# Usage: scripts/setup-opencode + +set -euo pipefail + +if ! command -v pnpm >/dev/null 2>&1; then + echo "setup-opencode: pnpm is not on PATH." >&2 + exit 1 +fi + +# pnpm installs global packages under $PNPM_HOME and refuses to run when its +# bin dir is not on PATH: without this the install aborts with +# ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH. Non-interactive shells do not read +# ~/.bashrc, so both are set here. The default is the directory pnpm itself +# uses on Linux. +export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}" +path_hint=0 +case ":$PATH:" in + *":$PNPM_HOME/bin:"*) ;; + *) path_hint=1; export PATH="$PNPM_HOME/bin:$PATH" ;; +esac + +pnpm add -g --allow-build=@opencode/cli @opencode/cli +opencode --version + +if [[ $path_hint -eq 1 ]]; then + echo "note: $PNPM_HOME/bin is not on your PATH; add it to run 'opencode' directly." >&2 +fi