Parcourir la source

Merge pull request #11157 from growilabs/support/change-pnpm-installation-method

support: switch pnpm install to corepack & devcontainer node:2
Yuki Takei il y a 1 mois
Parent
commit
c7a4df219d

+ 4 - 4
.devcontainer/app/devcontainer-lock.json

@@ -5,10 +5,10 @@
       "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
       "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
     },
-    "ghcr.io/devcontainers/features/node:1": {
-      "version": "1.7.1",
-      "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
-      "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
+    "ghcr.io/devcontainers/features/node:2": {
+      "version": "2.0.0",
+      "resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f",
+      "integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f"
     }
   }
 }

+ 11 - 2
.devcontainer/app/devcontainer.json

@@ -7,8 +7,9 @@
   "workspaceFolder": "/workspace/growi",
 
   "features": {
-    "ghcr.io/devcontainers/features/node:1": {
-      "version": "24.14.0"
+    "ghcr.io/devcontainers/features/node:2": {
+      "version": "24",
+      "pnpmVersion": "11.1.1"
     },
     "ghcr.io/devcontainers/features/github-cli:1": {}
   },
@@ -20,6 +21,14 @@
   // Use 'postCreateCommand' to run commands after the container is created.
   "postCreateCommand": "/bin/bash ./.devcontainer/app/postCreateCommand.sh",
 
+  // Expose tool install dirs (pnpm global, uv/claude) to every shell spawned by VS Code
+  // (integrated terminal, lifecycle commands, and extension shells such as Claude Code)
+  // without relying on ~/.bashrc, which non-interactive shells don't source.
+  "remoteEnv": {
+    "PNPM_HOME": "/home/vscode/.local/share/pnpm",
+    "PATH": "/home/vscode/.local/share/pnpm/bin:/home/vscode/.local/bin:${containerEnv:PATH}"
+  },
+
   // Configure tool-specific properties.
   "customizations": {
     "vscode": {

+ 7 - 9
.devcontainer/app/postCreateCommand.sh

@@ -18,17 +18,15 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
 curl -fsSL https://claude.ai/install.sh | bash
 
 # Setup pnpm
-SHELL=bash pnpm setup
-eval "$(cat /home/vscode/.bashrc)"
+export PNPM_HOME="${PNPM_HOME:-$HOME/.local/share/pnpm}"
+export PATH="$PNPM_HOME/bin:$HOME/.local/bin:$PATH"
+mkdir -p "$PNPM_HOME"
+# Use the Docker volume mounted at /workspace/.pnpm-store (see .devcontainer/compose.yml).
+# Without this, pnpm auto-falls-back to <workspace>/.pnpm-store because $HOME
+# (overlay FS) and the workspace (bind mount) are on different filesystems.
 pnpm config set store-dir /workspace/.pnpm-store
 
-# Install turbo
-pnpm install turbo --global
-
-# Install typescript-language-server for Claude Code LSP plugin
-# Use `npm -g` (not `pnpm --global`) so the binary lands in nvm's node bin, which is on the default PATH.
-# pnpm's global bin requires PNPM_HOME from ~/.bashrc, which the Claude Code extension's shell doesn't source.
-npm install -g typescript-language-server typescript
+pnpm install --global turbo typescript-language-server typescript
 
 # Install dependencies
 turbo run bootstrap

+ 3 - 3
.github/workflows/ci-app.yml

@@ -49,7 +49,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
-      - uses: pnpm/action-setup@v4
+      - uses: pnpm/action-setup@v6
 
       - uses: actions/setup-node@v4
         with:
@@ -104,7 +104,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
-      - uses: pnpm/action-setup@v4
+      - uses: pnpm/action-setup@v6
 
       - uses: actions/setup-node@v4
         with:
@@ -169,7 +169,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
 
-      - uses: pnpm/action-setup@v4
+      - uses: pnpm/action-setup@v6
 
       - uses: actions/setup-node@v4
         with:

+ 3 - 3
.github/workflows/ci-pdf-converter.yml

@@ -34,7 +34,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -70,7 +70,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -109,7 +109,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 3 - 3
.github/workflows/ci-slackbot-proxy.yml

@@ -35,7 +35,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -100,7 +100,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -178,7 +178,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 1 - 1
.github/workflows/release-pdf-converter.yml

@@ -81,7 +81,7 @@ jobs:
       with:
         ref: ${{ github.event.pull_request.base.ref }}
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 1 - 1
.github/workflows/release-slackbot-proxy.yml

@@ -88,7 +88,7 @@ jobs:
       with:
         ref: ${{ github.event.pull_request.base.ref }}
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 2 - 2
.github/workflows/release-subpackages.yml

@@ -33,7 +33,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -67,7 +67,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 2 - 2
.github/workflows/release.yml

@@ -22,7 +22,7 @@ jobs:
       with:
         ref: ${{ github.event.pull_request.base.ref }}
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -159,7 +159,7 @@ jobs:
       with:
         ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 5 - 5
.github/workflows/reusable-app-prod.yml

@@ -35,7 +35,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -144,8 +144,8 @@ jobs:
       run: |
         tar -xf ${{ needs.build-prod.outputs.PROD_FILES }}
 
-    # Run after extraction so pnpm/action-setup@v4 can read packageManager from package.json
-    - uses: pnpm/action-setup@v4
+    # Run after extraction so pnpm/action-setup@v6 can read packageManager from package.json
+    - uses: pnpm/action-setup@v6
 
     - name: pnpm run server:ci
       working-directory: ./apps/app
@@ -204,7 +204,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:
@@ -311,7 +311,7 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - uses: pnpm/action-setup@v4
+    - uses: pnpm/action-setup@v6
 
     - uses: actions/setup-node@v4
       with:

+ 9 - 13
apps/app/docker/Dockerfile

@@ -2,7 +2,6 @@
 
 ARG NODE_VERSION=24
 ARG OPT_DIR="/opt"
-ARG PNPM_HOME="/root/.local/share/pnpm"
 
 ##
 ## base — official Node.js image with pnpm + turbo
@@ -10,16 +9,18 @@ ARG PNPM_HOME="/root/.local/share/pnpm"
 FROM node:24-bookworm AS base
 
 ARG OPT_DIR
-ARG PNPM_HOME
 
 WORKDIR $OPT_DIR
 
-# Install pnpm (standalone script, no version hardcoding)
-RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL=/bin/sh sh -
-ENV PNPM_HOME=$PNPM_HOME
-ENV PATH="$PNPM_HOME:$PNPM_HOME/bin:$PATH"
+# Activate corepack so the pnpm version pinned in the workspace package.json
+# "packageManager" field is used (avoids drift between Dockerfile and local/CI).
+RUN corepack enable
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME/bin:$PATH"
 
 # Install turbo globally
+# Note: no cache mount here — pnpm global install links binaries into the store,
+# and the BuildKit cache mount would be unmounted after RUN, breaking those links.
 RUN pnpm add turbo --global
 
 
@@ -47,20 +48,15 @@ RUN turbo prune @growi/app @growi/pdf-converter --docker
 FROM base AS deps
 
 ARG OPT_DIR
-ARG PNPM_HOME
-
-ENV PNPM_HOME=$PNPM_HOME
-ENV PATH="$PNPM_HOME:$PNPM_HOME/bin:$PATH"
 
 WORKDIR $OPT_DIR
 
 # Copy only package manifests and lockfile for dependency caching
 COPY --from=pruner $OPT_DIR/out/json/ .
 
-# Separate store path keeps the BuildKit cache mount away from the pnpm bootstrap store.
 # --ignore-scripts: postinstall (prisma generate) needs full source, runs in builder stage.
-RUN --mount=type=cache,target=/root/.pnpm-store,sharing=locked \
-  pnpm install --frozen-lockfile --ignore-scripts --store-dir /root/.pnpm-store
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
+  pnpm install --frozen-lockfile --ignore-scripts
 
 
 ##

+ 3 - 1
apps/app/docker/Dockerfile.dockerignore

@@ -22,7 +22,9 @@ out
 # ============================================================
 # Unrelated apps
 # ============================================================
-apps/slackbot-proxy
+apps/**
+!apps/app
+!apps/pdf-converter
 
 # ============================================================
 # Test files

+ 10 - 18
apps/pdf-converter/docker/Dockerfile

@@ -2,7 +2,6 @@
 
 ARG NODE_VERSION=24
 ARG OPT_DIR="/opt"
-ARG PNPM_HOME="/root/.local/share/pnpm"
 
 ##
 ## base
@@ -10,23 +9,19 @@ ARG PNPM_HOME="/root/.local/share/pnpm"
 FROM node:${NODE_VERSION}-slim AS base
 
 ARG OPT_DIR
-ARG PNPM_HOME
 
 WORKDIR $OPT_DIR
 
-# install tools
-RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
-  --mount=type=cache,target=/var/cache/apt,sharing=locked \
-  apt-get update && apt-get install -y ca-certificates wget --no-install-recommends
-
-# install pnpm
-RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" PNPM_VERSION="10.32.1" sh -
-ENV PNPM_HOME=$PNPM_HOME
-ENV PATH="$PNPM_HOME:$PATH"
+# Activate corepack so the pnpm version pinned in the workspace package.json
+# "packageManager" field is used (avoids drift between Dockerfile and local/CI).
+RUN corepack enable
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME/bin:$PATH"
 
 # install turbo
-RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
-  pnpm add turbo --global
+# Note: no cache mount here — pnpm global install links binaries into the store,
+# and the BuildKit cache mount would be unmounted after RUN, breaking those links.
+RUN pnpm add turbo --global
 
 
 
@@ -35,15 +30,12 @@ RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
 ##
 FROM base AS builder
 
-ENV PNPM_HOME=$PNPM_HOME
-ENV PATH="$PNPM_HOME:$PATH"
-
 WORKDIR $OPT_DIR
 
 COPY . .
 
-RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
-  pnpm install ---frozen-lockfile
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
+  pnpm install --frozen-lockfile --ignore-scripts
 
 # build
 RUN turbo run clean

+ 10 - 0
apps/pdf-converter/docker/Dockerfile.dockerignore

@@ -0,0 +1,10 @@
+**/node_modules
+**/dist
+**/coverage
+**/Dockerfile
+**/*.dockerignore
+**/.pnpm-store
+**/.turbo
+out
+apps/**
+!apps/pdf-converter

+ 9 - 6
apps/slackbot-proxy/docker/Dockerfile

@@ -11,13 +11,15 @@ ENV optDir="/opt"
 
 WORKDIR ${optDir}
 
-# install pnpm
-RUN apt-get update && apt-get install -y ca-certificates wget --no-install-recommends \
-  && wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" PNPM_VERSION="10.32.1" sh -
-ENV PNPM_HOME="/root/.local/share/pnpm"
-ENV PATH="$PNPM_HOME:$PATH"
+# Activate corepack so the pnpm version pinned in the workspace package.json
+# "packageManager" field is used (avoids drift between Dockerfile and local/CI).
+RUN corepack enable
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME/bin:$PATH"
 
 # install turbo
+# Note: no cache mount here — pnpm global install links binaries into the store,
+# and the BuildKit cache mount would be unmounted after RUN, breaking those links.
 RUN pnpm add turbo --global
 
 
@@ -33,7 +35,8 @@ WORKDIR ${optDir}
 
 COPY . .
 
-RUN pnpm install --frozen-lockfile --ignore-scripts
+RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
+  pnpm install --frozen-lockfile --ignore-scripts
 
 # build
 RUN turbo run build --filter @growi/slackbot-proxy

+ 2 - 1
apps/slackbot-proxy/docker/Dockerfile.dockerignore

@@ -6,4 +6,5 @@
 **/.pnpm-store
 **/.turbo
 out
-apps/app
+apps/**
+!apps/slackbot-proxy

+ 1 - 43
package.json

@@ -20,7 +20,7 @@
   "bugs": {
     "url": "https://github.com/growilabs/growi/issues"
   },
-  "packageManager": "pnpm@10.32.1",
+  "packageManager": "pnpm@11.1.1",
   "scripts": {
     "bootstrap": "pnpm install",
     "start": "pnpm run app:server",
@@ -87,48 +87,6 @@
     "vitest": "^3.2.4",
     "vitest-mock-extended": "^3.1.0"
   },
-  "// comments for pnpm.overrides": {
-    "@lykmapipo/common>flat": "flat v6 is provided only by ESM, but @lykmapipo/common requires CommonJS version",
-    "@lykmapipo/common>mime": "mime v4 is provided only by ESM, but @lykmapipo/common requires CommonJS version",
-    "@lykmapipo/common>parse-json": "parse-json v6 is provided only by ESM, but @lykmapipo/common requires CommonJS version",
-    "axios": "CVE-2025-XXXXX: CRLF Injection + Prototype Pollution combo leads to HTTP Request Smuggling (CVSS 10.0). All versions < 1.15.0 are vulnerable."
-  },
-  "// comments for pnpm.packageExtensions": {
-    "@orval/core": "@orval/core bundles @stoplight/json-ref-resolver which requires lodash/get at runtime, but @orval/core does not declare lodash as a dependency"
-  },
-  "pnpm": {
-    "overrides": {
-      "@lykmapipo/common>flat": "5.0.2",
-      "@lykmapipo/common>mime": "3.0.0",
-      "@lykmapipo/common>parse-json": "5.2.0",
-      "axios": "^1.15.0"
-    },
-    "packageExtensions": {
-      "@orval/core": {
-        "dependencies": {
-          "lodash": "*"
-        }
-      }
-    },
-    "ignoredBuiltDependencies": [
-      "@swc/core",
-      "core-js",
-      "esbuild",
-      "leveldown",
-      "protobufjs",
-      "puppeteer",
-      "ttf2woff2"
-    ],
-    "onlyBuiltDependencies": [
-      "lefthook"
-    ],
-    "// comments for patchedDependencies": {
-      "@marp-team/marp-core": "The patch excludes mathjax-full from the dependency graph of Marp Core."
-    },
-    "patchedDependencies": {
-      "@marp-team/marp-core": "packages/presentation/patches/@marp-team__marp-core.patch"
-    }
-  },
   "engines": {
     "node": "^24"
   }

+ 46 - 0
pnpm-workspace.yaml

@@ -1,3 +1,49 @@
 packages:
   - 'apps/*'
   - 'packages/*'
+
+overrides:
+  # flat v6 is provided only by ESM, but @lykmapipo/common requires CommonJS version
+  '@lykmapipo/common>flat': 5.0.2
+  # mime v4 is provided only by ESM, but @lykmapipo/common requires CommonJS version
+  '@lykmapipo/common>mime': 3.0.0
+  # parse-json v6 is provided only by ESM, but @lykmapipo/common requires CommonJS version
+  '@lykmapipo/common>parse-json': 5.2.0
+  # CVE-2025-XXXXX: CRLF Injection + Prototype Pollution combo leads to HTTP Request Smuggling (CVSS 10.0).
+  # All versions < 1.15.0 are vulnerable.
+  axios: ^1.15.0
+
+packageExtensions:
+  # @orval/core bundles @stoplight/json-ref-resolver which requires lodash/get at runtime,
+  # but @orval/core does not declare lodash as a dependency.
+  '@orval/core':
+    dependencies:
+      lodash: '*'
+
+patchedDependencies:
+  # The patch excludes mathjax-full from the dependency graph of Marp Core.
+  '@marp-team/marp-core': packages/presentation/patches/@marp-team__marp-core.patch
+
+# pnpm v11+ unified allowlist: true=run install scripts, false=skip them.
+# Migrated from onlyBuiltDependencies (true) and ignoredBuiltDependencies (false).
+allowBuilds:
+  lefthook: true
+  '@swc/core': false
+  core-js: false
+  esbuild: false
+  leveldown: false
+  protobufjs: false
+  puppeteer: false
+  ttf2woff2: false
+  # Prisma: apps/app's `postinstall: prisma generate` covers the work that these
+  # packages' install scripts would do. In particular, `prisma generate` itself
+  # downloads the engine binary on demand (verified by removing
+  # libquery_engine-*.so.node and re-running `prisma generate` — the binary is
+  # restored byte-for-byte), so `@prisma/engines`' postinstall is redundant here.
+  '@prisma/client': false
+  '@prisma/engines': false
+  prisma: false
+  # sharp ships platform-specific prebuilt binaries via optional dependencies
+  # (e.g. @img/sharp-linux-x64, @img/sharp-libvips-linux-x64), so its install
+  # script (which would build libvips from source as a fallback) is not needed.
+  sharp: false