Skip to content

Commit

Permalink
Corepack support (Yarn only)
Browse files Browse the repository at this point in the history
These changes lay the groundwork for [Corepack](https://nodejs.org/docs/latest/api/corepack.html) support in Node.js projects. [Yarn 4 already prefers corepack for installation](https://yarnpkg.com/getting-started/install) so this feature will be available for use with Yarn and, in the future, it will be expanded to support pnpm.

[W-15237072](https://gus.lightning.force.com/a07EE00001m2GDFYA2)
  • Loading branch information
colincasey committed Apr 2, 2024
1 parent 9b6f31c commit 375fd1a
Show file tree
Hide file tree
Showing 37 changed files with 481 additions and 19 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 [Corepack](https://nodejs.org/api/corepack.html) installation of [Yarn](https://yarnpkg.com/) ([#1222](https://github.com/heroku/heroku-buildpack-nodejs/pull/1222))

## [v240] - 2024-03-27

Expand Down
54 changes: 39 additions & 15 deletions bin/compile
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,14 @@ if [[ "$YARN_2" == "true" ]]; then
# get yarn_path
VENDOR_PATH=$(get_yarn_path "$BUILD_DIR")

# fail for no yarnPath in rc
fail_missing_yarn_path "$BUILD_DIR" "$VENDOR_PATH"

# fail for missing yarn in .yarn/releases
fail_missing_yarn_vendor "$BUILD_DIR" "$VENDOR_PATH"
# if we're installing yarn via corepack we don't need to fail
# on the yarn path configuration or vendored script
if [[ "$(read_json "$BUILD_DIR/package.json" ".packageManager")" != yarn* ]]; then
# fail for no yarnPath in rc
fail_missing_yarn_path "$BUILD_DIR" "$VENDOR_PATH"
# fail for missing yarn in .yarn/releases
fail_missing_yarn_vendor "$BUILD_DIR" "$VENDOR_PATH"
fi
fi

### Configure package manager cache directories
Expand All @@ -196,25 +199,41 @@ fi
export YARN_CACHE_FOLDER NPM_CONFIG_CACHE

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

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")
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 "package-manager-request" "$package_manager"

echo "engines.node (package.json): ${node_engine:-unspecified}"
echo "engines.npm (package.json): ${npm_engine:-unspecified (use default)}"

echo "engines.node (package.json): ${node_engine:-unspecified}"
echo "engines.npm (package.json): ${npm_engine:-unspecified (use default)}"
if $YARN || [ -n "$yarn_engine" ]; then
echo "engines.yarn (package.json): ${yarn_engine:-unspecified (use default)}"
echo "engines.yarn (package.json): ${yarn_engine:-unspecified (use default)}"
fi

if [ -n "$package_manager" ]; then
echo "packageManager (package.json): $package_manager"
fi

echo ""

warn_node_engine "$node_engine"

if [ -n "$yarn_engine" ] && [[ "$package_manager" == yarn* ]]; then
warn_multiple_yarn_version "$package_manager" "$yarn_engine"
fi

if has_release_script "$BUILD_DIR" && [[ "$package_manager" == yarn* ]]; then
warn_yarn_release_script_with_package_manager "$package_manager" "$(get_yarn_path "$BUILD_DIR")"
fi

meta_set "build-step" "install-nodejs"
monitor "install-node-binary" install_nodejs "$node_engine" "$BUILD_DIR/.heroku/node"

Expand All @@ -225,12 +244,17 @@ install_bins() {
mcount "version.node.$node_version"
meta_set "node-version" "$node_version"

# Download yarn if there is a yarn.lock file or if the user
# has specified a version of yarn under "engines". We'll still
# only install using yarn if there is a yarn.lock file
if $YARN || [ -n "$yarn_engine" ]; then
meta_set "build-step" "install-yarn"
monitor "install-yarn-binary" install_yarn "$BUILD_DIR/.heroku/yarn" "$yarn_engine"
if ! has_release_script "$BUILD_DIR" && [[ "$package_manager" == yarn* ]]; then
meta_set "build-step" "install-yarn-using-corepack"
monitor "install-yarn-using-corepack" install_yarn_using_corepack_package_manager "$package_manager" "$node_version"
else
# Download yarn if there is a yarn.lock file or if the user
# has specified a version of yarn under "engines". We'll still
# only install using yarn if there is a yarn.lock file
if $YARN || [ -n "$yarn_engine" ]; then
meta_set "build-step" "install-yarn"
monitor "install-yarn-binary" install_yarn "$BUILD_DIR/.heroku/yarn" "$yarn_engine"
fi
fi

if $YARN; then
Expand Down
30 changes: 30 additions & 0 deletions lib/binaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,36 @@ install_npm() {
fi
}

install_yarn_using_corepack_package_manager() {
local package_manager="$1"
local node_version="$2"
install_corepack_package_manager "$package_manager" "$node_version"
suppress_output yarn --version
echo "Using yarn $(yarn --version)"
}

install_corepack_package_manager() {
local node_major_version
local node_minor_version

local package_manager="$1"
local node_version="$2"

node_major_version=$(echo "$node_version" | cut -d "." -f 1 | sed 's/^v//')
node_minor_version=$(echo "$node_version" | cut -d "." -f 2)

# Corepack is available in: v16.9.0, v14.19.0
if (( node_major_version >= 17)) || (( node_major_version == 14 && node_minor_version >= 19 )) || (( node_major_version >= 16 && node_minor_version >= 9 )); then
suppress_output corepack --version
corepack_version=$(corepack --version)

echo "Installing ${package_manager} via corepack ${corepack_version}"
corepack enable
else
fail_corepack_not_available "$package_manager" "$node_version"
fi
}

suppress_output() {
local TMP_COMMAND_OUTPUT
TMP_COMMAND_OUTPUT=$(mktemp)
Expand Down
54 changes: 54 additions & 0 deletions lib/failure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,25 @@ fail_missing_yarn_vendor() {
fi
}

fail_corepack_not_available() {
local package_manager="$1"
local node_version="$2"

mcount "failures.corepack-unsupported"
meta_set "failure" "failures.corepack-unsupported"
header "Build failed"
warn "Corepack is not supported in Node.js $node_version
Your application indicated that $package_manager should be installed using Corepack. This feature
is included with all Node.js releases starting from Node.js 14.19.0 / 16.9.0. The version
of Node.js used in this build is $node_version which does not support Corepack.
To use Corepack, update your Node.js version:
https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version
"
fail
}

log_other_failures() {
local log_file="$1"

Expand Down Expand Up @@ -840,3 +859,38 @@ warn_unmet_dep() {
mcount 'warnings.modules.unmet'
fi
}

warn_multiple_yarn_version() {
local package_manager="$1"
local yarn_engine="$2"
warn "Multiple Yarn versions declared
The package.json file indicates the target version of Yarn to install in two fields:
- \"packageManager\": \"$package_manager\"
- \"engines.yarn\": \"$yarn_engine\"
If both fields are present, then \"packageManager\" will take precedence and \"$package_manager\" will be installed.
To ensure we install the version of Yarn you want, remove one of these fields."
mcount 'warnings.yarn.multiple-version'
}

warn_yarn_release_script_with_package_manager() {
local package_manager="$1"
local release_script="$2"
warn "Yarn release script may conflict with \"packageManager\"
The package.json file indicates the target version of Yarn to install with:
- \"packageManager\": \"$package_manager\"
But the .yarnrc.yml configuration indicates a vendored release of Yarn should be used with:
- yarnPath: \"$release_script\"
This will cause the buildpack to install $package_manager but, when running Yarn commands, the vendored release
at \"$release_script\" will be executed instead.
To ensure we install the version of Yarn you want, choose only one of the following actions:
- Remove the \"packageManager\" field from package.json
- Remove the \"yarnPath\" configuration from .yarnrc.yml and delete the vendored release at \"$release_script\""
mcount 'warnings.yarn.release-script-with-package-manager'
}
7 changes: 7 additions & 0 deletions lib/yarn-2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ detect_yarn_2() {
fi
}

has_release_script() {
local build_dir="$1"
local yarn_path
yarn_path=$($YQ r "$build_dir/.yarnrc.yml" yarnPath 2>&1)
[[ -n "$yarn_path" ]] && [ -f "$build_dir/$yarn_path" ]
}

has_yarn_cache() {
local build_dir="$1"
local yarn_cache="$build_dir/.yarn/cache"
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-14.18/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-14.18/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "14.18.0"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@2.2.2"
}
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-14.19/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-14.19/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "14.19.0"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@2.2.2"
}
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-15/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-15/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "15.x"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@2.2.2"
}
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-16.8/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-16.8/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "16.8.0"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@2.2.2"
}
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-16.9/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-16.9/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "16.9.0"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@2.2.2"
}
1 change: 1 addition & 0 deletions test/fixtures/corepack-node-20.9/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A fake README, to keep npm from polluting stderr.
16 changes: 16 additions & 0 deletions test/fixtures/corepack-node-20.9/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "corepack-test-app",
"version": "0.0.1",
"description": "corepack availability test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "20.9.0"
},
"scripts": {
"start": "node foo.js"
},
"packageManager": "yarn@3.6.3"
}
7 changes: 7 additions & 0 deletions test/fixtures/yarn-4-with-corepack-and-engine/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions test/fixtures/yarn-4-with-corepack-and-engine/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enableColors: false
20 changes: 20 additions & 0 deletions test/fixtures/yarn-4-with-corepack-and-engine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "yarn-4",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@types/node": "16.11.26",
"uuid": "9.0.0"
},
"dependencies": {
"echo-cli": "2.0.0"
},
"scripts": {
"build": "echo-cli 'Build script check'"
},
"packageManager": "yarn@4.1.1",
"engines": {
"yarn": "4.1.1"
}
}
Loading

0 comments on commit 375fd1a

Please sign in to comment.