diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4fbe6de..52ef8d6b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## [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)) ## [v241] - 2024-04-04 - + - Added Node.js version 21.7.2. - Added Node.js version 20.12.1. - Added Node.js version 18.20.1. diff --git a/bin/compile b/bin/compile index aa5ab3109..bf0768172 100755 --- a/bin/compile +++ b/bin/compile @@ -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 @@ -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): $(echo "$package_manager" | cut -d "+" -f 1)" + 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" @@ -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 diff --git a/lib/binaries.sh b/lib/binaries.sh index 074da3e07..e8624b8be 100644 --- a/lib/binaries.sh +++ b/lib/binaries.sh @@ -134,6 +134,58 @@ 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) + corepack enable 2>&1 + + # The Corepack CLI interface was refactored in 0.20, before that the `install` command was called `prepare` and it + # doesn't support the --global argument - https://github.com/nodejs/corepack/blob/main/CHANGELOG.md#0200-2023-08-29 + corepack_major_version=$(echo "$corepack_version" | cut -d "." -f 1) + corepack_minor_version=$(echo "$corepack_version" | cut -d "." -f 2) + if (( corepack_major_version == 0 )) && (( corepack_minor_version < 20 )); then + corepack_install_command="prepare" + corepack_install_args=() + else + corepack_install_command="install" + corepack_install_args=("--global") + fi + + echo "Installing $(echo "$package_manager" | cut -d "+" -f 1) via corepack ${corepack_version}" + install_output=$(mktemp) + if ! corepack "${corepack_install_args[@]}" "$corepack_install_command" "$package_manager" > "$install_output" 2>&1; then + # always show the output on error + cat "$install_output" + if grep --ignore-case "mismatch hashes" "$install_output"; then + fail_corepack_install_invalid_hash "$package_manager" + else + fail_corepack_install_invalid_version "$package_manager" + fi + fi + else + fail_corepack_not_available "$package_manager" "$node_version" + fi +} + suppress_output() { local TMP_COMMAND_OUTPUT TMP_COMMAND_OUTPUT=$(mktemp) diff --git a/lib/failure.sh b/lib/failure.sh index b64cfa081..4027da084 100644 --- a/lib/failure.sh +++ b/lib/failure.sh @@ -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" @@ -840,3 +859,82 @@ 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' +} + +fail_corepack_install_invalid_hash() { + local package_manager="$1" + package_manager_name=$(echo "$package_manager" | cut -d "@" -f 1) + package_manager_version=$(echo "$package_manager" | cut -d "@" -f 2 | cut -d "+" -f 1) + package_manager_hash=$(echo "$package_manager" | cut -d "@" -f 2 | cut -d "+" -f 2) + + mcount "failures.corepack-install.hash" + meta_set "failure" "failures.corepack-install.hash" + header "Build failed" + warn "Error installing $package_manager_name version $package_manager_version + + The hash provided for the $package_manager_name version declared in package.json ($package_manager_hash) is incorrect. + + To correct this, run the following command: + + > corepack use $package_manager_name@$package_manager_version + + Then commit and push the changes to package.json." \ + "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-$package_manager_name-version" + fail +} + +fail_corepack_install_invalid_version() { + local package_manager="$1" + package_manager_name=$(echo "$package_manager" | cut -d "@" -f 1) + package_manager_version=$(echo "$package_manager" | cut -d "@" -f 2 | cut -d "+" -f 1) + + mcount "failures.corepack-install.version" + meta_set "failure" "failures.corepack-install.version" + header "Build failed" + warn "Error installing $package_manager_name version $package_manager_version + + Can’t find the $package_manager_name version that matches the requested version declared in package.json ($package_manager_version). + + Verify that the requested version range matches a published version of $package_manager_name by checking + https://www.npmjs.com/package/$package_manager_name?activeTab=versions or trying the following command: + + > npm show '$package_manager' versions + + Update the version specified field in package.json to a published $package_manager_name version" \ + "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-$package_manager_name-version" + fail +} diff --git a/lib/yarn-2.sh b/lib/yarn-2.sh index a8a4b3753..a095e7f79 100644 --- a/lib/yarn-2.sh +++ b/lib/yarn-2.sh @@ -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" diff --git a/test/fixtures/corepack-invalid-package-manager-sha/README.md b/test/fixtures/corepack-invalid-package-manager-sha/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-invalid-package-manager-sha/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-invalid-package-manager-sha/package.json b/test/fixtures/corepack-invalid-package-manager-sha/package.json new file mode 100644 index 000000000..3be63bafc --- /dev/null +++ b/test/fixtures/corepack-invalid-package-manager-sha/package.json @@ -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.2.3+sha224.BADSHA" +} diff --git a/test/fixtures/corepack-invalid-package-manager/README.md b/test/fixtures/corepack-invalid-package-manager/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-invalid-package-manager/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-invalid-package-manager/package.json b/test/fixtures/corepack-invalid-package-manager/package.json new file mode 100644 index 000000000..73eba7b57 --- /dev/null +++ b/test/fixtures/corepack-invalid-package-manager/package.json @@ -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@999.999.999" +} diff --git a/test/fixtures/corepack-node-14.18/README.md b/test/fixtures/corepack-node-14.18/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-14.18/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-14.18/package.json b/test/fixtures/corepack-node-14.18/package.json new file mode 100644 index 000000000..122114ed7 --- /dev/null +++ b/test/fixtures/corepack-node-14.18/package.json @@ -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" +} diff --git a/test/fixtures/corepack-node-14.19/README.md b/test/fixtures/corepack-node-14.19/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-14.19/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-14.19/package.json b/test/fixtures/corepack-node-14.19/package.json new file mode 100644 index 000000000..be3566e06 --- /dev/null +++ b/test/fixtures/corepack-node-14.19/package.json @@ -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" +} diff --git a/test/fixtures/corepack-node-15/README.md b/test/fixtures/corepack-node-15/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-15/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-15/package.json b/test/fixtures/corepack-node-15/package.json new file mode 100644 index 000000000..2d6de4c9a --- /dev/null +++ b/test/fixtures/corepack-node-15/package.json @@ -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" +} diff --git a/test/fixtures/corepack-node-16.8/README.md b/test/fixtures/corepack-node-16.8/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-16.8/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-16.8/package.json b/test/fixtures/corepack-node-16.8/package.json new file mode 100644 index 000000000..c8aca48c8 --- /dev/null +++ b/test/fixtures/corepack-node-16.8/package.json @@ -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" +} diff --git a/test/fixtures/corepack-node-16.9/README.md b/test/fixtures/corepack-node-16.9/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-16.9/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-16.9/package.json b/test/fixtures/corepack-node-16.9/package.json new file mode 100644 index 000000000..82c8ede14 --- /dev/null +++ b/test/fixtures/corepack-node-16.9/package.json @@ -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" +} diff --git a/test/fixtures/corepack-node-20.12/README.md b/test/fixtures/corepack-node-20.12/README.md new file mode 100644 index 000000000..cda334ae1 --- /dev/null +++ b/test/fixtures/corepack-node-20.12/README.md @@ -0,0 +1 @@ +A fake README, to keep npm from polluting stderr. \ No newline at end of file diff --git a/test/fixtures/corepack-node-20.12/package.json b/test/fixtures/corepack-node-20.12/package.json new file mode 100644 index 000000000..56e7196bc --- /dev/null +++ b/test/fixtures/corepack-node-20.12/package.json @@ -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.12.0" + }, + "scripts": { + "start": "node foo.js" + }, + "packageManager": "yarn@3.6.3" +} diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.gitignore b/test/fixtures/yarn-4-with-corepack-and-engine/.gitignore new file mode 100644 index 000000000..5d776d9e4 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack-and-engine/.gitignore @@ -0,0 +1,7 @@ +.yarn/* +!.yarn/cache +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip new file mode 100644 index 000000000..4f8c9fdfa Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip differ diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip new file mode 100644 index 000000000..efbc02830 Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip differ diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip new file mode 100644 index 000000000..ea2785886 Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip differ diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip new file mode 100644 index 000000000..11c13ac6a Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip differ diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip new file mode 100644 index 000000000..2c142e31d Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack-and-engine/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip differ diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/.yarnrc.yml b/test/fixtures/yarn-4-with-corepack-and-engine/.yarnrc.yml new file mode 100644 index 000000000..66cdf6885 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack-and-engine/.yarnrc.yml @@ -0,0 +1 @@ +enableColors: false diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/package.json b/test/fixtures/yarn-4-with-corepack-and-engine/package.json new file mode 100644 index 000000000..7246dc22e --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack-and-engine/package.json @@ -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" + } +} diff --git a/test/fixtures/yarn-4-with-corepack-and-engine/yarn.lock b/test/fixtures/yarn-4-with-corepack-and-engine/yarn.lock new file mode 100644 index 000000000..6516fc2a4 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack-and-engine/yarn.lock @@ -0,0 +1,59 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@types/node@npm:16.11.26": + version: 16.11.26 + resolution: "@types/node@npm:16.11.26" + checksum: 10c0/9e26dc087b32c2f47dcc7cc67db2ce35b44504515953ebc0c7f6c6cb030a273823dcd61e71ab0b0571e358e57315fe5aa5eb46c7aace1969c2e307a0c5b47572 + languageName: node + linkType: hard + +"echo-cli@npm:2.0.0": + version: 2.0.0 + resolution: "echo-cli@npm:2.0.0" + dependencies: + unescape-js: "npm:^1.0.3" + bin: + echo-cli: dist/index.js + checksum: 10c0/5e5d6394b2e50960e09476af048cb8d4bb58c18b7d7fdff3b74f01009b035d8e09562f2ca268d861fee467e6eaf0f5cfb0a322a9fa36ac7951d6238a1340a00f + languageName: node + linkType: hard + +"string.fromcodepoint@npm:^0.2.1": + version: 0.2.1 + resolution: "string.fromcodepoint@npm:0.2.1" + checksum: 10c0/2e26c7370daea0725f2cc3b0a2e4b84613c44b68130ad2afa1364b51fd48ebdfe6390086807d7b5e95d58e8a872aca46a53bbc182c549cd74c0ee9b46de32b02 + languageName: node + linkType: hard + +"unescape-js@npm:^1.0.3": + version: 1.1.4 + resolution: "unescape-js@npm:1.1.4" + dependencies: + string.fromcodepoint: "npm:^0.2.1" + checksum: 10c0/4f7cda5c524cb4392d482eba11762dbc43ff8cd0d0d88c4deecdacb7ec04d9162595406f66c5fbe9a6a565aabf7f2f1cc1889d44d805b1e8326deb7b3b279484 + languageName: node + linkType: hard + +"uuid@npm:9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10c0/8867e438990d1d33ac61093e2e4e3477a2148b844e4fa9e3c2360fa4399292429c4b6ec64537eb1659c97b2d10db349c673ad58b50e2824a11e0d3630de3c056 + languageName: node + linkType: hard + +"yarn-4@workspace:.": + version: 0.0.0-use.local + resolution: "yarn-4@workspace:." + dependencies: + "@types/node": "npm:16.11.26" + echo-cli: "npm:2.0.0" + uuid: "npm:9.0.0" + languageName: unknown + linkType: soft diff --git a/test/fixtures/yarn-4-with-corepack/.gitignore b/test/fixtures/yarn-4-with-corepack/.gitignore new file mode 100644 index 000000000..5d776d9e4 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack/.gitignore @@ -0,0 +1,7 @@ +.yarn/* +!.yarn/cache +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/test/fixtures/yarn-4-with-corepack/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip b/test/fixtures/yarn-4-with-corepack/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip new file mode 100644 index 000000000..4f8c9fdfa Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack/.yarn/cache/@types-node-npm-16.11.26-6163d95b7d-9e26dc087b.zip differ diff --git a/test/fixtures/yarn-4-with-corepack/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip b/test/fixtures/yarn-4-with-corepack/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip new file mode 100644 index 000000000..efbc02830 Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack/.yarn/cache/echo-cli-npm-2.0.0-b40e6b6835-5e5d6394b2.zip differ diff --git a/test/fixtures/yarn-4-with-corepack/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip b/test/fixtures/yarn-4-with-corepack/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip new file mode 100644 index 000000000..ea2785886 Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack/.yarn/cache/string.fromcodepoint-npm-0.2.1-84d94c4fb5-2e26c7370d.zip differ diff --git a/test/fixtures/yarn-4-with-corepack/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip b/test/fixtures/yarn-4-with-corepack/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip new file mode 100644 index 000000000..11c13ac6a Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack/.yarn/cache/unescape-js-npm-1.1.4-f41cc6935a-4f7cda5c52.zip differ diff --git a/test/fixtures/yarn-4-with-corepack/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip b/test/fixtures/yarn-4-with-corepack/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip new file mode 100644 index 000000000..2c142e31d Binary files /dev/null and b/test/fixtures/yarn-4-with-corepack/.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8867e43899.zip differ diff --git a/test/fixtures/yarn-4-with-corepack/.yarnrc.yml b/test/fixtures/yarn-4-with-corepack/.yarnrc.yml new file mode 100644 index 000000000..66cdf6885 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack/.yarnrc.yml @@ -0,0 +1 @@ +enableColors: false diff --git a/test/fixtures/yarn-4-with-corepack/package.json b/test/fixtures/yarn-4-with-corepack/package.json new file mode 100644 index 000000000..72c95b13d --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack/package.json @@ -0,0 +1,17 @@ +{ + "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" +} diff --git a/test/fixtures/yarn-4-with-corepack/yarn.lock b/test/fixtures/yarn-4-with-corepack/yarn.lock new file mode 100644 index 000000000..6516fc2a4 --- /dev/null +++ b/test/fixtures/yarn-4-with-corepack/yarn.lock @@ -0,0 +1,59 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@types/node@npm:16.11.26": + version: 16.11.26 + resolution: "@types/node@npm:16.11.26" + checksum: 10c0/9e26dc087b32c2f47dcc7cc67db2ce35b44504515953ebc0c7f6c6cb030a273823dcd61e71ab0b0571e358e57315fe5aa5eb46c7aace1969c2e307a0c5b47572 + languageName: node + linkType: hard + +"echo-cli@npm:2.0.0": + version: 2.0.0 + resolution: "echo-cli@npm:2.0.0" + dependencies: + unescape-js: "npm:^1.0.3" + bin: + echo-cli: dist/index.js + checksum: 10c0/5e5d6394b2e50960e09476af048cb8d4bb58c18b7d7fdff3b74f01009b035d8e09562f2ca268d861fee467e6eaf0f5cfb0a322a9fa36ac7951d6238a1340a00f + languageName: node + linkType: hard + +"string.fromcodepoint@npm:^0.2.1": + version: 0.2.1 + resolution: "string.fromcodepoint@npm:0.2.1" + checksum: 10c0/2e26c7370daea0725f2cc3b0a2e4b84613c44b68130ad2afa1364b51fd48ebdfe6390086807d7b5e95d58e8a872aca46a53bbc182c549cd74c0ee9b46de32b02 + languageName: node + linkType: hard + +"unescape-js@npm:^1.0.3": + version: 1.1.4 + resolution: "unescape-js@npm:1.1.4" + dependencies: + string.fromcodepoint: "npm:^0.2.1" + checksum: 10c0/4f7cda5c524cb4392d482eba11762dbc43ff8cd0d0d88c4deecdacb7ec04d9162595406f66c5fbe9a6a565aabf7f2f1cc1889d44d805b1e8326deb7b3b279484 + languageName: node + linkType: hard + +"uuid@npm:9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10c0/8867e438990d1d33ac61093e2e4e3477a2148b844e4fa9e3c2360fa4399292429c4b6ec64537eb1659c97b2d10db349c673ad58b50e2824a11e0d3630de3c056 + languageName: node + linkType: hard + +"yarn-4@workspace:.": + version: 0.0.0-use.local + resolution: "yarn-4@workspace:." + dependencies: + "@types/node": "npm:16.11.26" + echo-cli: "npm:2.0.0" + uuid: "npm:9.0.0" + languageName: unknown + linkType: soft diff --git a/test/fixtures/yarn-4/package.json b/test/fixtures/yarn-4/package.json index 560d3aa08..72c95b13d 100644 --- a/test/fixtures/yarn-4/package.json +++ b/test/fixtures/yarn-4/package.json @@ -13,5 +13,5 @@ "scripts": { "build": "echo-cli 'Build script check'" }, - "packageManager": "yarn@4.0.0" + "packageManager": "yarn@4.1.1" } diff --git a/test/run b/test/run index 7d71c2c3e..45d6eb135 100755 --- a/test/run +++ b/test/run @@ -181,7 +181,7 @@ testYarnRun() { testNoVersion() { local default_version="20" compile "no-version" - assertCaptured "engines.node (package.json): unspecified" + assertCaptured "engines.node (package.json): unspecified" assertCaptured "Resolving node version ${default_version}.x" assertCaptured "Downloading and installing node ${default_version}." assertCapturedSuccess @@ -777,6 +777,51 @@ testYarn3() { testYarn4() { compile "yarn-4" + assertCaptured "engines.yarn (package.json): unspecified (use default)" + assertCaptured "packageManager (package.json): yarn@4.1.1" + assertCaptured 'Yarn release script may conflict with "packageManager"' + assertCaptured "The package.json file indicates the target version of Yarn to install with:" + assertCaptured '- "packageManager": "yarn@4.1.1"' + assertCaptured "But the .yarnrc.yml configuration indicates a vendored release of Yarn should be used with:" + assertCaptured '- yarnPath: ".yarn/releases/yarn-4.0.0.cjs"' + assertCaptured "This will cause the buildpack to install yarn@4.1.1 but, when running Yarn commands, the vendored release" + assertCaptured 'at ".yarn/releases/yarn-4.0.0.cjs" will be executed instead.' + assertCaptured "To ensure we install the version of Yarn you want, choose only one of the following actions:" + assertCaptured '- Remove the "packageManager" field from package.json' + assertCaptured '- Remove the "yarnPath" configuration from .yarnrc.yml and delete the vendored release at ".yarn/releases/yarn-4.0.0.cjs"' + assertCaptured "Using yarn 4.0.0" + assertCaptured "Running 'yarn install' with yarn.lock" + assertCaptured "Build script check" + assertCaptured "Running 'yarn heroku prune'" + assertCaptured "- @types/node@npm:16.11.26, uuid@npm:9.0.0" + assertCapturedSuccess +} + +testYarn4WithCorepackEnabled() { + compile "yarn-4-with-corepack" + assertCaptured "engines.yarn (package.json): unspecified (use default)" + assertCaptured "packageManager (package.json): yarn@4.1.1" + assertCaptured "Installing yarn@4.1.1 via corepack" + assertCaptured "Using yarn 4.1.1" + assertCaptured "Running 'yarn install' with yarn.lock" + assertCaptured "Build script check" + assertCaptured "Running 'yarn heroku prune'" + assertCaptured "- @types/node@npm:16.11.26, uuid@npm:9.0.0" + assertCapturedSuccess +} + +testYarn4WithCorepackAndEngine() { + compile "yarn-4-with-corepack-and-engine" + assertCaptured "engines.yarn (package.json): 4.1.1" + assertCaptured "packageManager (package.json): yarn@4.1.1" + assertCaptured "Multiple Yarn versions declared" + assertCaptured "The package.json file indicates the target version of Yarn to install in two fields:" + assertCaptured '- "packageManager": "yarn@4.1.1"' + assertCaptured '- "engines.yarn": "4.1.1"' + assertCaptured 'If both fields are present, then "packageManager" will take precedence and "yarn@4.1.1" will be installed.' + assertCaptured "To ensure we install the version of Yarn you want, remove one of these fields." + assertCaptured "Installing yarn@4.1.1 via corepack" + assertCaptured "Using yarn 4.1.1" assertCaptured "Running 'yarn install' with yarn.lock" assertCaptured "Build script check" assertCaptured "Running 'yarn heroku prune'" @@ -856,8 +901,8 @@ testFailingBuild() { testInfoEmpty() { compile "info-empty" - assertCaptured "engines.node (package.json): unspecified" - assertCaptured "engines.npm (package.json): unspecified" + assertCaptured "engines.node (package.json): unspecified" + assertCaptured "engines.npm (package.json): unspecified" assertCaptured "Installing node modules (package.json)" assertCapturedSuccess } @@ -1664,6 +1709,50 @@ testUseNpm10() { assertCapturedSuccess } +testCorepackAvailable() { + compile "corepack-node-14.19" + assertCaptured "Installing yarn@2.2.2 via corepack 0.10.0" + assertCapturedSuccess + + compile "corepack-node-16.9" + assertCaptured "Installing yarn@2.2.2 via corepack 0.9.0" + assertCapturedSuccess + + compile "corepack-node-20.12" + assertCaptured "Installing yarn@3.6.3 via corepack 0.25.2" + assertCapturedSuccess +} + +testCorepackNotAvailable() { + compile "corepack-node-14.18" + assertCaptured "Corepack is not supported in Node.js v14.18.0" + assertCapturedError + + compile "corepack-node-15" + assertCaptured "Corepack is not supported in Node.js v15" + assertCapturedError + + compile "corepack-node-16.8" + assertCaptured "Corepack is not supported in Node.js v16.8.0" + assertCapturedError +} + +testCorepackInvalidVersion() { + compile "corepack-invalid-package-manager" + assertCaptured "packageManager (package.json): yarn@999.999.999" + assertCaptured "Installing yarn@999.999.999 via corepack 0.20.0" + assertCaptured "Error installing yarn version 999.999.999" + assertCaptured "Can’t find the yarn version that matches the requested version declared in package.json (999.999.999)" + assertCapturedError + + compile "corepack-invalid-package-manager-sha" + assertCaptured "packageManager (package.json): yarn@3.2.3" + assertCaptured "Installing yarn@3.2.3 via corepack 0.20.0" + assertCaptured "Error installing yarn version 3.2.3" + assertCaptured "The hash provided for the yarn version declared in package.json (sha224.BADSHA) is incorrect" + assertCapturedError +} + # Utils pushd "$(dirname 0)" >/dev/null