Skip to content

Commit

Permalink
pnpm support (#1224)
Browse files Browse the repository at this point in the history
* pnpm support

These changes build on top of [existing corepack support](#1222) to allow `pnpm` to be installed via [Corepack](https://nodejs.org/docs/latest/api/corepack.html).

The build process has also been modified use the `pnpm` binary at the following stages:
- installing dependencies
- running build scripts:
  - `heroku-prebuild`
  - `build` (or `heroku-postbuild`)
  - `heroku-cleanup`
- pruning dev dependencies

* Prune the pnpm store periodically (#1231)

* Multiple lockfiles error (#1228)

These changes build on top of [existing pnpm support](#1224) and modify the failure message shown when multiple lockfiles are detected to now include pnpm.

* Add pnpm caching (#1225)

These changes build on top of [existing pnpm support](#1224) to allow `pnpm` dependencies to be saved and restored between builds.

* pnpm support (default version) (#1226)

These changes build on top of [existing pnpm support](#1224) to default to the `latest` version when it appears that pnpm should be used but there is no `engines.pnpm` or `packageManager` specified in `package.json`.

* pnpm support engines (#1227)

These changes build on top of [existing pnpm support](#1224) to use the version specified by the `engines.pnpm` field in `package.json`.

* Only prune when it's safe to do so (#1238)

* Update CHANGELOG.md
  • Loading branch information
colincasey authored Apr 29, 2024
1 parent 179914d commit f045fd7
Show file tree
Hide file tree
Showing 60 changed files with 9,877 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Support [pnpm](https://pnpm.io/) ([#1224](https://github.com/heroku/heroku-buildpack-nodejs/pull/1224))

## [v244] - 2024-04-25

Expand Down
48 changes: 44 additions & 4 deletions bin/compile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ meta_set "build-step" "init"
[ -e "$BUILD_DIR/node_modules" ] && PREBUILD=true || PREBUILD=false
[ -f "$BUILD_DIR/yarn.lock" ] && YARN=true || YARN=false
[ -f "$BUILD_DIR/package-lock.json" ] && NPM_LOCK=true || NPM_LOCK=false
[ -f "$BUILD_DIR/pnpm-lock.yaml" ] && PNPM=true || PNPM=false
YARN_2=$(detect_yarn_2 "$YARN" "$BUILD_DIR")

### Save build info
Expand Down Expand Up @@ -196,19 +197,22 @@ fi
### Configure package manager cache directories
[ ! "$YARN_CACHE_FOLDER" ] && YARN_CACHE_FOLDER=$(mktemp -d -t yarncache.XXXXX)
[ ! "$NPM_CONFIG_CACHE" ] && NPM_CONFIG_CACHE=$(mktemp -d -t npmcache.XXXXX)
export YARN_CACHE_FOLDER NPM_CONFIG_CACHE
[ ! "$PNPM_CONFIG_CACHE" ] && PNPM_CONFIG_CACHE=$(mktemp -d -t pnpmcache.XXXXX)
export YARN_CACHE_FOLDER NPM_CONFIG_CACHE PNPM_CONFIG_CACHE

install_bins() {
local node_engine npm_engine yarn_engine node_version package_manager
local node_engine npm_engine yarn_engine node_version package_manager pnpm_engine

node_engine=$(read_json "$BUILD_DIR/package.json" ".engines.node")
npm_engine=$(read_json "$BUILD_DIR/package.json" ".engines.npm")
yarn_engine=$(read_json "$BUILD_DIR/package.json" ".engines.yarn")
pnpm_engine=$(read_json "$BUILD_DIR/package.json" ".engines.pnpm")
package_manager=$(read_json "$BUILD_DIR/package.json" ".packageManager")

meta_set "node-version-request" "$node_engine"
meta_set "npm-version-request" "$npm_engine"
meta_set "yarn-version-request" "$yarn_engine"
meta_set "pnpm-version-request" "$pnpm_engine"
meta_set "package-manager-request" "$package_manager"

echo "engines.node (package.json): ${node_engine:-unspecified}"
Expand All @@ -218,6 +222,10 @@ install_bins() {
echo "engines.yarn (package.json): ${yarn_engine:-unspecified (use default)}"
fi

if $PNPM || [ -n "$pnpm_engine" ]; then
echo "engines.pnpm (package.json): ${pnpm_engine:-unspecified (use default)}"
fi

if [ -n "$package_manager" ]; then
echo "packageManager (package.json): $(echo "$package_manager" | cut -d "+" -f 1)"
fi
Expand All @@ -226,6 +234,10 @@ install_bins() {

warn_node_engine "$node_engine"

if [ -n "$pnpm_engine" ] && [[ "$package_manager" == pnpm* ]]; then
warn_multiple_pnpm_version "$package_manager" "$pnpm_engine"
fi

if [ -n "$yarn_engine" ] && [[ "$package_manager" == yarn* ]]; then
warn_multiple_yarn_version "$package_manager" "$yarn_engine"
fi
Expand Down Expand Up @@ -257,9 +269,33 @@ install_bins() {
fi
fi

if $PNPM; then
meta_set "build-step" "install-pnpm-using-corepack"

default_pnpm_version="pnpm@latest"
using_default_pnpm=false

if [[ "$package_manager" == pnpm* ]]; then
package_manager="$package_manager"
elif [ -n "$pnpm_engine" ]; then
package_manager="pnpm@${pnpm_engine}"
else
using_default_pnpm=true
package_manager="$default_pnpm_version"
fi

monitor "install-pnpm-using-corepack" install_pnpm_using_corepack_package_manager "$package_manager" "$node_version" "$PNPM_CONFIG_CACHE"
if [ "$using_default_pnpm" == true ]; then
warn_default_pnpm_version_used "$default_pnpm_version"
fi
fi

if $YARN; then
mcount "version.yarn.$(yarn --version)"
meta_set "yarn-version" "$(yarn --version)"
elif $PNPM; then
mcount "version.pnpm.$(pnpm --version)"
meta_set "pnpm-version" "$(pnpm --version)"
else
mcount "version.npm.$(npm --version)"
meta_set "npm-version" "$(npm --version)"
Expand Down Expand Up @@ -305,7 +341,7 @@ restore_cache() {
elif [[ "$cache_status" == "valid" ]]; then
header "Restoring cache"
if [[ "$cache_directories" == "" ]]; then
restore_default_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$YARN_CACHE_FOLDER" "$NPM_CONFIG_CACHE"
restore_default_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$YARN_CACHE_FOLDER" "$NPM_CONFIG_CACHE" "$PNPM_CONFIG_CACHE"
else
restore_custom_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$cache_directories"
fi
Expand Down Expand Up @@ -344,6 +380,8 @@ build_dependencies() {
yarn_2_install "$BUILD_DIR"
elif $YARN; then
yarn_node_modules "$BUILD_DIR"
elif $PNPM; then
pnpm_install "$BUILD_DIR" "$CACHE_DIR"
elif $PREBUILD; then
echo "Prebuild detected (node_modules already exists)"
npm_rebuild "$BUILD_DIR"
Expand Down Expand Up @@ -376,7 +414,7 @@ cache_build() {
:
elif [[ "$cache_directories" == "" ]]; then
header "Caching build"
save_default_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$YARN_CACHE_FOLDER" "$NPM_CONFIG_CACHE"
save_default_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$YARN_CACHE_FOLDER" "$NPM_CONFIG_CACHE" "$PNPM_CONFIG_CACHE"
else
header "Caching build"
save_custom_cache_directories "$BUILD_DIR" "$CACHE_DIR" "$cache_directories"
Expand All @@ -388,6 +426,8 @@ cache_build() {
prune_devdependencies() {
if $YARN || $YARN_2; then
yarn_prune_devdependencies "$BUILD_DIR" "$YARN_CACHE_FOLDER" "$BP_DIR"
elif $PNPM; then
pnpm_prune_devdependencies "$BUILD_DIR"
else
npm_prune_devdependencies "$BUILD_DIR"
fi
Expand Down
10 changes: 10 additions & 0 deletions lib/binaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ install_yarn_using_corepack_package_manager() {
echo "Using yarn $(yarn --version)"
}

install_pnpm_using_corepack_package_manager() {
local package_manager="$1"
local node_version="$2"
local pnpm_cache="$3"
install_corepack_package_manager "$package_manager" "$node_version"
suppress_output pnpm --version
echo "Using pnpm $(pnpm --version)"
pnpm config set store-dir "$pnpm_cache" 2>&1
}

install_corepack_package_manager() {
local node_major_version
local node_minor_version
Expand Down
50 changes: 50 additions & 0 deletions lib/cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ restore_default_cache_directories() {
local cache_dir=${2:-}
local yarn_cache_dir=${3:-}
local npm_cache=${4:-}
local pnpm_cache_dir=${5:-}

if [[ "$YARN" == "true" ]]; then
if has_yarn_cache "$build_dir"; then
Expand All @@ -67,6 +68,15 @@ restore_default_cache_directories() {
else
echo "- yarn cache (not cached - skipping)"
fi
elif [[ "$PNPM" == "true" ]]; then
if [[ -d "$cache_dir/node/cache/pnpm" ]]; then
rm -rf "$pnpm_cache_dir"
mv "$cache_dir/node/cache/pnpm" "$pnpm_cache_dir"
echo "- pnpm cache"
meta_set "pnpm_cache" "true"
else
echo "- pnpm cache (not cached - skipping)"
fi
elif [[ "$USE_NPM_INSTALL" == "false" ]]; then
if [[ -d "$cache_dir/node/cache/npm" ]]; then
rm -rf "$npm_cache"
Expand Down Expand Up @@ -131,6 +141,7 @@ save_default_cache_directories() {
local cache_dir=${2:-}
local yarn_cache_dir=${3:-}
local npm_cache=${4:-}
local pnpm_cache_dir=${5:-}

if [[ "$YARN" == "true" ]]; then
if [[ -d "$yarn_cache_dir" ]]; then
Expand All @@ -141,6 +152,11 @@ save_default_cache_directories() {
fi
echo "- yarn cache"
fi
elif [[ "$PNPM" == "true" ]]; then
if [[ -d "$pnpm_cache_dir" ]]; then
mv "$pnpm_cache_dir" "$cache_dir/node/cache/pnpm"
echo "- pnpm cache"
fi
elif [[ "$USE_NPM_INSTALL" == "false" ]]; then
if [[ -d "$npm_cache" ]]; then
mv "$npm_cache" "$cache_dir/node/cache/npm"
Expand Down Expand Up @@ -194,3 +210,37 @@ save_custom_cache_directories() {

meta_set "node-custom-cache-dirs" "true"
}

DEFAULT_PNPM_PRUNE_COUNTER_VALUE="40"

load_pnpm_prune_store_counter() {
local cache_dir=${1:-}

if [ -f "$cache_dir/pnpm_prune_store_counter" ]; then
counter=$(<"$cache_dir/pnpm_prune_store_counter")
if ! is_int "$counter" || (( counter < 0 )); then
counter="$DEFAULT_PNPM_PRUNE_COUNTER_VALUE"
fi
else
counter="$DEFAULT_PNPM_PRUNE_COUNTER_VALUE"
fi

echo "$counter"
}

save_pnpm_prune_store_counter() {
local cache_dir=${1:-}
local new_value=${2:-}

if ! is_int "$new_value" || (( new_value < 0 )); then
new_value="$DEFAULT_PNPM_PRUNE_COUNTER_VALUE"
fi

echo "$new_value" > "$cache_dir/pnpm_prune_store_counter"
}

is_int() {
case ${1#[-+]} in
'' | *[!0-9]*) return 1;;
esac
}
67 changes: 67 additions & 0 deletions lib/dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ run_if_present() {
if [[ -n "$script" ]]; then
monitor "${script_name}-script" yarn run "$script_name"
fi
elif $PNPM; then
echo "Running $script_name"
monitor "${script_name}-script" pnpm run --if-present "$script_name"
else
echo "Running $script_name"
monitor "${script_name}-script" npm run "$script_name" --if-present
Expand Down Expand Up @@ -65,6 +68,14 @@ run_build_if_present() {
monitor "${script_name}-script" yarn run "$script_name"
fi
fi
elif $PNPM; then
echo "Running $script_name"
if [[ -n $NODE_BUILD_FLAGS ]]; then
echo "Running with $NODE_BUILD_FLAGS flags"
monitor "${script_name}-script" pnpm run --if-present "$script_name" -- "$NODE_BUILD_FLAGS"
else
monitor "${script_name}-script" pnpm run --if-present "$script_name"
fi
else
echo "Running $script_name"
if [[ -n $NODE_BUILD_FLAGS ]]; then
Expand Down Expand Up @@ -305,3 +316,59 @@ npm_prune_devdependencies() {
meta_set "skipped-prune" "false"
fi
}

pnpm_install() {
local build_dir=${1:-}
local cache_dir=${2:-}

echo "Running 'pnpm install' with pnpm-lock.yaml"
cd "$build_dir" || return

monitor "pnpm-install" pnpm install --prod=false --frozen-lockfile 2>&1

# prune the store when the counter reaches zero to clean up errant package versions which may have been upgraded/removed
counter=$(load_pnpm_prune_store_counter "$cache_dir")
if (( counter == 0 )); then
echo "Cleaning up pnpm store"
suppress_output pnpm store prune
fi
save_pnpm_prune_store_counter "$cache_dir" "$(( counter - 1 ))"
}

pnpm_prune_devdependencies() {
local build_dir=${1:-}

cd "$build_dir" || return

pnpm_version=$(pnpm --version)
pnpm_major_version=$(echo "$pnpm_version" | cut -d "." -f 1)
pnpm_minor_version=$(echo "$pnpm_version" | cut -d "." -f 2)
pnpm_patch_version=$(echo "$pnpm_version" | cut -d "." -f 3)

pnpm_prune_args=("prune" "--prod")

# prior to 8.15.6, pnpm prune would execute lifecycle scripts such as `preinstall` and `postinstall`
# so we should check if we're on that version + there are lifecycle scripts registered and, if so,
# we'll let the user know that pruning can't be done safely so we're skipping it
if (( "$pnpm_major_version" < 8 )) || \
(( "$pnpm_major_version" == 8 && "$pnpm_minor_version" < 15 )) || \
(( "$pnpm_major_version" == 8 && "$pnpm_minor_version" == 15 && "$pnpm_patch_version" < 6)); then
# the following are lifecycle scripts that will execute on install/prune by pnpm
if [ -n "$(read_json "$build_dir/package.json" ".scripts.\"pnpm:devPreinstall\"")" ] ||
[ -n "$(read_json "$build_dir/package.json" ".scripts.preinstall")" ] ||
[ -n "$(read_json "$build_dir/package.json" ".scripts.install")" ] ||
[ -n "$(read_json "$build_dir/package.json" ".scripts.postinstall")" ] ||
[ -n "$(read_json "$build_dir/package.json" ".scripts.prepare")" ]; then
warn_skipping_unsafe_pnpm_prune "$pnpm_version"
meta_set "skipped-prune" "true"
return
fi
else
# we're on a version that supports this flag (8.15.6 and higher)
pnpm_prune_args+=("--ignore-scripts")
fi

pnpm "${pnpm_prune_args[@]}" 2>&1

meta_set "skipped-prune" "false"
}
Loading

0 comments on commit f045fd7

Please sign in to comment.