diff --git a/6-next/alpine3.23/Dockerfile b/6-next/alpine3.23/Dockerfile index 40e0bcf7..59e3961b 100644 --- a/6-next/alpine3.23/Dockerfile +++ b/6-next/alpine3.23/Dockerfile @@ -50,8 +50,20 @@ ENV GHOST_TARBALL=https://github.com/TryGhost/Ghost/releases/download/v6.63.0/gh ENV GHOST_SHA256=20b2ac6e936299d5b88b37d2a7175712ad35e6ade78a594ab6d495256ab54292 RUN set -eux; \ -# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one - corepack enable; \ +# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one -- except on +# arm32v7, where pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback. +# pnpm 11 is the last JavaScript implementation and reads the same lockfile format + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ +# yarn still goes to corepack so "rm -rf /opt/yarn-*" below does not leave its symlinks dangling + corepack enable yarn; \ + npm install -g 'pnpm@11.26.0'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run. +# Inert under corepack, which owns version switching; exported so it survives the "gosu ghost" drop + export pnpm_config_pm_on_fail=ignore; \ \ # everything under the install dir is created by "ghost" rather than chowned afterwards: a recursive # chown would copy the whole tree into a new layer diff --git a/6-next/bookworm/Dockerfile b/6-next/bookworm/Dockerfile index 25fa0e0d..20c24901 100644 --- a/6-next/bookworm/Dockerfile +++ b/6-next/bookworm/Dockerfile @@ -67,8 +67,20 @@ RUN set -eux; \ python3 \ ; \ \ -# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one - corepack enable; \ +# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one -- except on +# arm32v7, where pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback. +# pnpm 11 is the last JavaScript implementation and reads the same lockfile format + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ +# yarn still goes to corepack so "rm -rf /opt/yarn-*" below does not leave its symlinks dangling + corepack enable yarn; \ + npm install -g 'pnpm@11.26.0'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run. +# Inert under corepack, which owns version switching; exported so it survives the "gosu ghost" drop + export pnpm_config_pm_on_fail=ignore; \ \ # everything under the install dir is created by "ghost" rather than chowned afterwards: a recursive # chown would copy the whole tree into a new layer diff --git a/6/alpine3.23/Dockerfile b/6/alpine3.23/Dockerfile index 6f8fda2f..4314282e 100644 --- a/6/alpine3.23/Dockerfile +++ b/6/alpine3.23/Dockerfile @@ -52,7 +52,18 @@ RUN set -eux; \ \ apk add --no-cache --virtual .ghost-cli-deps git; \ \ - corepack enable; \ +# pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback, so corepack cannot +# produce the version "packageManager" pins on arm32v7. pnpm 11 is the last JavaScript +# implementation and reads the same lockfile format, so that arch installs with it instead + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ + npm install -g 'pnpm@11.26.0'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run; +# exported because Ghost-CLI spawns pnpm itself. Inert under corepack, which owns version switching + export pnpm_config_pm_on_fail=ignore; \ \ # corepack's downloads, pnpm's metadata cache and pnpm's store all derive their location # from these, so pointing them at /tmp keeps every throwaway byte in one place @@ -92,6 +103,9 @@ RUN set -eux; \ mkdir -p "$GHOST_INSTALL"; \ chown node:node "$GHOST_INSTALL"; \ \ +# "ghost install" and "pnpm store prune" below both run pnpm; see the note in the Ghost-CLI stage + export pnpm_config_pm_on_fail=ignore; \ + \ apk add --no-cache --virtual .build-deps-ghost g++ linux-headers make python3 py3-setuptools; \ \ gosu node ghost install "$GHOST_VERSION" --db mysql --dbhost mysql --no-prompt --no-stack --no-setup --dir "$GHOST_INSTALL"; \ diff --git a/6/bookworm/Dockerfile b/6/bookworm/Dockerfile index 452e2ea5..57e33f64 100644 --- a/6/bookworm/Dockerfile +++ b/6/bookworm/Dockerfile @@ -51,7 +51,18 @@ RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends ca-certificates git; \ \ - corepack enable; \ +# pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback, so corepack cannot +# produce the version "packageManager" pins on arm32v7. pnpm 11 is the last JavaScript +# implementation and reads the same lockfile format, so that arch installs with it instead + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ + npm install -g 'pnpm@11.26.0'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run; +# exported because Ghost-CLI spawns pnpm itself. Inert under corepack, which owns version switching + export pnpm_config_pm_on_fail=ignore; \ \ # corepack's downloads, pnpm's metadata cache and pnpm's store all derive their location # from these, so pointing them at /tmp keeps every throwaway byte in one place @@ -94,6 +105,9 @@ RUN set -eux; \ mkdir -p "$GHOST_INSTALL"; \ chown node:node "$GHOST_INSTALL"; \ \ +# "ghost install" and "pnpm store prune" below both run pnpm; see the note in the Ghost-CLI stage + export pnpm_config_pm_on_fail=ignore; \ + \ savedAptMark="$(apt-mark showmanual)"; \ apt-get update; \ apt-get install -y --no-install-recommends g++ make python3; \ diff --git a/Dockerfile-next.template b/Dockerfile-next.template index 3bca6203..c734c596 100644 --- a/Dockerfile-next.template +++ b/Dockerfile-next.template @@ -86,8 +86,20 @@ RUN set -eux; \ ; \ \ {{ ) end -}} -# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one - corepack enable; \ +# Ghost pins its pnpm by hash in "packageManager", so corepack fetches exactly that one -- except on +# arm32v7, where pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback. +# pnpm 11 is the last JavaScript implementation and reads the same lockfile format + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ +# yarn still goes to corepack so "rm -rf /opt/yarn-*" below does not leave its symlinks dangling + corepack enable yarn; \ + npm install -g 'pnpm@{{ .pnpm.fallbackVersion }}'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run. +# Inert under corepack, which owns version switching; exported so it survives the "gosu ghost" drop + export pnpm_config_pm_on_fail=ignore; \ \ # everything under the install dir is created by "ghost" rather than chowned afterwards: a recursive # chown would copy the whole tree into a new layer diff --git a/Dockerfile.template b/Dockerfile.template index f717c17a..d0e03a83 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -78,7 +78,18 @@ RUN set -eux; \ apt-get install -y --no-install-recommends ca-certificates git; \ {{ ) end -}} \ - corepack enable; \ +# pnpm 12 is a Rust binary with no 32-bit ARM target and no JavaScript fallback, so corepack cannot +# produce the version "packageManager" pins on arm32v7. pnpm 11 is the last JavaScript +# implementation and reads the same lockfile format, so that arch installs with it instead + if [ "$(node -p 'process.arch')" = 'arm' ]; then \ + npm install -g 'pnpm@{{ .pnpm.fallbackVersion }}'; \ + else \ + corepack enable; \ + fi; \ + \ +# pnpm 11 would otherwise honour "packageManager" and switch back to the binary it cannot run; +# exported because Ghost-CLI spawns pnpm itself. Inert under corepack, which owns version switching + export pnpm_config_pm_on_fail=ignore; \ \ # corepack's downloads, pnpm's metadata cache and pnpm's store all derive their location # from these, so pointing them at /tmp keeps every throwaway byte in one place @@ -125,6 +136,9 @@ RUN set -eux; \ mkdir -p "$GHOST_INSTALL"; \ chown node:node "$GHOST_INSTALL"; \ \ +# "ghost install" and "pnpm store prune" below both run pnpm; see the note in the Ghost-CLI stage + export pnpm_config_pm_on_fail=ignore; \ + \ {{ if is_alpine then ( -}} apk add --no-cache --virtual .build-deps-ghost g++ linux-headers make python3 py3-setuptools; \ {{ ) else ( -}} diff --git a/versions.json b/versions.json index 616ae914..1e872e4a 100644 --- a/versions.json +++ b/versions.json @@ -8,6 +8,9 @@ "node": { "version": "22" }, + "pnpm": { + "fallbackVersion": "11.26.0" + }, "variants": { "bookworm": { "arches": [ @@ -36,6 +39,9 @@ "node": { "version": "22" }, + "pnpm": { + "fallbackVersion": "11.26.0" + }, "variants": { "bookworm": { "arches": [ diff --git a/versions.sh b/versions.sh index bb7921b0..ab2aec22 100755 --- a/versions.sh +++ b/versions.sh @@ -73,6 +73,17 @@ if [ -z "$cliSha" ]; then exit 1 fi +# pnpm 12 is a Rust binary and publishes no 32-bit ARM target, and unlike every pnpm before it +# there is no JavaScript implementation to fall back to -- the npm package is a downloader that +# fetches "@pnpm/exe.-". So corepack cannot produce the version "packageManager" +# pins when building for arm32v7. pnpm 11 is the last JavaScript implementation, is still released +# alongside 12 ("latest-11"), and reads the same lockfile format, so that architecture builds with +# it instead. Pinned here so it is refreshed by the update workflow like every other version. +pnpmFallbackVersion="$( + fetch 'https://registry.npmjs.org/pnpm' '."dist-tags"."latest-11" // empty' \ + | jq --raw-output '."dist-tags"."latest-11"' +)" + for version in "${versions[@]}"; do rcVersion="${version%-rc}" rcGrepV='-v' @@ -195,7 +206,7 @@ for version in "${versions[@]}"; do '{ cli: { version: $version, sha: $sha } }')" fi - export fullVersion nodeVersion + export fullVersion nodeVersion pnpmFallbackVersion json="$(jq <<<"$json" --compact-output --argjson doc "$doc" --argjson source "$sourceJson" ' env.nodeVersion as $nodeVersion | .[env.version] = ( @@ -203,6 +214,7 @@ for version in "${versions[@]}"; do + $source + { node: { version: $nodeVersion }, + pnpm: { fallbackVersion: env.pnpmFallbackVersion }, variants: ( $doc | with_entries(