From 3736e629712273f220e20c8a286c24ec5796ae46 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 14 Apr 2025 14:53:12 -0300 Subject: [PATCH 1/3] Improve Yarn errors This PR adds detailed and consistent error messages for the Yarn buildpack + includes snapshot testing for the output. --- Cargo.lock | 4 +- buildpacks/nodejs-yarn/Cargo.toml | 6 +- buildpacks/nodejs-yarn/src/cmd.rs | 67 +-- .../nodejs-yarn/src/configure_yarn_cache.rs | 18 +- buildpacks/nodejs-yarn/src/errors.rs | 507 ++++++++++++++++++ buildpacks/nodejs-yarn/src/install_yarn.rs | 13 +- buildpacks/nodejs-yarn/src/main.rs | 88 +-- .../errors___yarn_build_script_error.snap | 25 + .../errors___yarn_cache_get_error.snap | 17 + ...rrors___yarn_cli_layer_download_error.snap | 14 + ...s___yarn_cli_layer_installation_error.snap | 14 + ...rs___yarn_cli_layer_permissions_error.snap | 14 + ...rors___yarn_cli_layer_temp_file_error.snap | 14 + .../errors___yarn_cli_layer_untar_error.snap | 14 + .../errors___yarn_default_parse_error.snap | 14 + ...arn_deps_layer_create_cache_dir_error.snap | 14 + ..._yarn_deps_layer_yarn_cache_set_error.snap | 17 + ...ors___yarn_disable_global_cache_error.snap | 17 + .../errors___yarn_install_error.snap | 21 + .../errors___yarn_inventory_parse_error.snap | 19 + ...arn_node_build_scripts_metadata_error.snap | 17 + ...rors___yarn_package_json_access_error.snap | 14 + ...rrors___yarn_package_json_parse_error.snap | 14 + ...ion_detect_yarn_version_command_error.snap | 17 + ...rsion_detect_yarn_version_parse_error.snap | 14 + .../errors___yarn_version_resolve_error.snap | 14 + ...rors___yarn_version_unsupported_error.snap | 12 + 27 files changed, 886 insertions(+), 133 deletions(-) create mode 100644 buildpacks/nodejs-yarn/src/errors.rs create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_build_script_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cache_get_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_download_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_installation_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_permissions_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_temp_file_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_untar_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_default_parse_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_create_cache_dir_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_yarn_cache_set_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_disable_global_cache_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_install_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_inventory_parse_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_node_build_scripts_metadata_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_access_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_parse_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_command_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_parse_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_resolve_error.snap create mode 100644 buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_unsupported_error.snap diff --git a/Cargo.lock b/Cargo.lock index 5dfe96cf..18c19568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -759,14 +759,16 @@ dependencies = [ "fun_run", "heroku-nodejs-utils", "indoc", + "insta", "libcnb", "libcnb-test", "libherokubuildpack", "serde", + "serde_json", "tempfile", "test_support", - "thiserror 2.0.12", "toml", + "ureq", ] [[package]] diff --git a/buildpacks/nodejs-yarn/Cargo.toml b/buildpacks/nodejs-yarn/Cargo.toml index bf782d94..5e701d5c 100644 --- a/buildpacks/nodejs-yarn/Cargo.toml +++ b/buildpacks/nodejs-yarn/Cargo.toml @@ -10,6 +10,7 @@ workspace = true bullet_stream = "0.7" fun_run = "0.5" heroku-nodejs-utils.workspace = true +indoc = "2" libcnb = { version = "=0.28.1", features = ["trace"] } libherokubuildpack = { version = "=0.28.1", default-features = false, features = [ "download", @@ -18,10 +19,11 @@ libherokubuildpack = { version = "=0.28.1", default-features = false, features = ] } serde = "1" tempfile = "3" -thiserror = "2" toml = "0.8" [dev-dependencies] -indoc = "2" +insta = "1" libcnb-test = "=0.28.1" test_support.workspace = true +serde_json = "1" +ureq = "3" diff --git a/buildpacks/nodejs-yarn/src/cmd.rs b/buildpacks/nodejs-yarn/src/cmd.rs index 8e075a17..6054e09b 100644 --- a/buildpacks/nodejs-yarn/src/cmd.rs +++ b/buildpacks/nodejs-yarn/src/cmd.rs @@ -2,7 +2,7 @@ use crate::yarn::Yarn; use bullet_stream::state::SubBullet; use bullet_stream::{style, Print}; use fun_run::{CmdError, CommandWithName}; -use heroku_nodejs_utils::vrs::Version; +use heroku_nodejs_utils::vrs::{Version, VersionError}; use libcnb::Env; use std::io::Stderr; use std::{ @@ -10,60 +10,43 @@ use std::{ process::{Command, Stdio}, }; -#[derive(thiserror::Error, Debug)] -pub(crate) enum Error { - #[error("Couldn't start yarn command: {0}")] - Spawn(std::io::Error), - #[error("Couldn't finish yarn command: {0}")] - Wait(std::io::Error), - #[error("Yarn command finished with a non-zero exit code: {0}")] - Exit(std::process::ExitStatus), - #[error("Yarn output couldn't be parsed: {0}")] - Parse(String), +#[derive(Debug)] +pub(crate) enum YarnVersionError { + Command(CmdError), + Parse(String, VersionError), } /// Execute `yarn --version` to determine what version of `yarn` is in effect /// for this codebase. -pub(crate) fn yarn_version(env: &Env) -> Result { - let output = Command::new("yarn") +pub(crate) fn yarn_version(env: &Env) -> Result { + Command::new("yarn") .arg("--version") .envs(env) - .stdout(Stdio::piped()) - .spawn() - .map_err(Error::Spawn)? - .wait_with_output() - .map_err(Error::Wait)?; - - output - .status - .success() - .then_some(()) - .ok_or(Error::Exit(output.status))?; - let stdout = String::from_utf8_lossy(&output.stdout); - stdout - .parse() - .map_err(|_| Error::Parse(stdout.into_owned())) + .named_output() + .map_err(YarnVersionError::Command) + .and_then(|output| { + let stdout = output.stdout_lossy(); + stdout + .parse::() + .map_err(|e| YarnVersionError::Parse(stdout, e)) + }) } /// Execute `yarn config get` to determine where the yarn cache is. -pub(crate) fn yarn_get_cache(yarn_line: &Yarn, env: &Env) -> Result { - let mut args = vec!["config", "get"]; +pub(crate) fn yarn_get_cache(yarn_line: &Yarn, env: &Env) -> Result { + let mut cmd = Command::new("yarn"); + cmd.envs(env); + + cmd.arg("config"); + cmd.arg("get"); if yarn_line == &Yarn::Yarn1 { - args.push("cache-folder"); + cmd.arg("cache-folder"); } else { - args.push("cacheFolder"); + cmd.arg("cacheFolder"); } - let output = Command::new("yarn") - .args(args) - .envs(env) - .stdout(Stdio::piped()) - .spawn() - .map_err(Error::Spawn)? - .wait_with_output() - .map_err(Error::Wait)?; - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - Ok(stdout.into()) + cmd.named_output() + .map(|output| PathBuf::from(output.stdout_lossy().trim())) } /// Execute `yarn config set` to set the yarn cache to a specfic location. diff --git a/buildpacks/nodejs-yarn/src/configure_yarn_cache.rs b/buildpacks/nodejs-yarn/src/configure_yarn_cache.rs index ec62ad79..166c35de 100644 --- a/buildpacks/nodejs-yarn/src/configure_yarn_cache.rs +++ b/buildpacks/nodejs-yarn/src/configure_yarn_cache.rs @@ -1,3 +1,5 @@ +use crate::yarn::Yarn; +use crate::{cmd, YarnBuildpack, YarnBuildpackError}; use bullet_stream::state::SubBullet; use bullet_stream::Print; use libcnb::build::BuildContext; @@ -9,10 +11,7 @@ use libcnb::Env; use serde::{Deserialize, Serialize}; use std::fs; use std::io::Stderr; -use thiserror::Error; - -use crate::yarn::Yarn; -use crate::{cmd, YarnBuildpack, YarnBuildpackError}; +use std::path::PathBuf; /// `DepsLayer` is a layer for caching yarn dependencies from build to build. /// This layer is irrelevant in zero-install mode, as cached dependencies are @@ -62,8 +61,9 @@ pub(crate) fn configure_yarn_cache( cache_usage_count: new_metadata.cache_usage_count + 1.0, ..new_metadata })?; - fs::create_dir(deps_layer.path().join(CACHE_DIR)) - .map_err(DepsLayerError::CreateCacheDir)?; + + let cache_dir = deps_layer.path().join(CACHE_DIR); + fs::create_dir(&cache_dir).map_err(|e| DepsLayerError::CreateCacheDir(cache_dir, e))?; } } @@ -86,11 +86,9 @@ pub(crate) struct DepsLayerMetadata { yarn: Yarn, } -#[derive(Error, Debug)] +#[derive(Debug)] pub(crate) enum DepsLayerError { - #[error("Couldn't create yarn dependency cache: {0}")] - CreateCacheDir(std::io::Error), - #[error("Couldn't set yarn cache folder: {0}")] + CreateCacheDir(PathBuf, std::io::Error), YarnCacheSet(fun_run::CmdError), } diff --git a/buildpacks/nodejs-yarn/src/errors.rs b/buildpacks/nodejs-yarn/src/errors.rs new file mode 100644 index 00000000..7b4d61d3 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/errors.rs @@ -0,0 +1,507 @@ +use crate::cmd::YarnVersionError; +use crate::configure_yarn_cache::DepsLayerError; +use crate::install_yarn::CliLayerError; +use crate::YarnBuildpackError; +use bullet_stream::style; +use fun_run::CmdError; +use heroku_nodejs_utils::buildplan::{ + NodeBuildScriptsMetadataError, NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME, +}; +use heroku_nodejs_utils::error_handling::error_message_builder::SetIssuesUrl; +use heroku_nodejs_utils::error_handling::{ + file_value, on_framework_error, on_package_json_error, ErrorMessage, ErrorMessageBuilder, + ErrorType, SuggestRetryBuild, SuggestSubmitIssue, +}; +use heroku_nodejs_utils::vrs::{Requirement, VersionError}; +use indoc::formatdoc; + +const BUILDPACK_NAME: &str = "Heroku Node.js Yarn"; + +const ISSUES_URL: &str = "https://github.com/heroku/buildpacks-nodejs/issues"; + +pub(crate) fn on_error(error: libcnb::Error) -> ErrorMessage { + match error { + libcnb::Error::BuildpackError(e) => on_buildpack_error(e), + e => on_framework_error(BUILDPACK_NAME, ISSUES_URL, &e), + } +} + +// Wraps the error_message() builder to preset the issues_url field +fn error_message() -> ErrorMessageBuilder { + heroku_nodejs_utils::error_handling::error_message().issues_url(ISSUES_URL.to_string()) +} + +fn on_buildpack_error(error: YarnBuildpackError) -> ErrorMessage { + match error { + YarnBuildpackError::BuildScript(e) => on_build_script_error(&e), + YarnBuildpackError::CliLayer(e) => on_cli_layer_error(&e), + YarnBuildpackError::DepsLayer(e) => on_deps_layer_error(&e), + YarnBuildpackError::InventoryParse(e) => on_inventory_parse_error(&e), + YarnBuildpackError::PackageJson(e) => on_package_json_error(BUILDPACK_NAME, ISSUES_URL, e), + YarnBuildpackError::YarnCacheGet(e) => on_yarn_cache_get_error(&e), + YarnBuildpackError::YarnDisableGlobalCache(e) => on_yarn_disable_global_cache_error(&e), + YarnBuildpackError::YarnInstall(e) => on_yarn_install_error(&e), + YarnBuildpackError::YarnVersionDetect(e) => on_yarn_version_detect_error(&e), + YarnBuildpackError::YarnVersionUnsupported(version) => { + on_yarn_version_unsupported_error(version) + } + YarnBuildpackError::YarnVersionResolve(requirement) => { + on_yarn_version_resolve_error(&requirement) + } + YarnBuildpackError::YarnDefaultParse(e) => on_yarn_default_parse_error(&e), + YarnBuildpackError::NodeBuildScriptsMetadata(e) => on_node_build_scripts_metadata_error(e), + } +} + +fn on_build_script_error(error: &CmdError) -> ErrorMessage { + let build_script = style::value(error.name()); + let package_json = style::value("package.json"); + let heroku_prebuild = style::value("heroku-prebuild"); + let heroku_build = style::value("heroku-build"); + let build = style::value("build"); + let heroku_postbuild = style::value("heroku-postbuild"); + error_message() + .error_type(ErrorType::UserFacing(SuggestRetryBuild::Yes, SuggestSubmitIssue::Yes)) + .header("Failed to execute build script") + .body(formatdoc! { " + The {BUILDPACK_NAME} buildpack allows customization of the build process by executing the following scripts \ + if they are defined in {package_json}: + - {heroku_prebuild} + - {heroku_build} or {build} + - {heroku_postbuild} + + An unexpected error occurred while executing {build_script}. See the log output above for more information. + + Suggestions: + - Ensure that this command runs locally without error. + "}) + .debug_info(error.to_string()) + .create() +} + +fn on_cli_layer_error(error: &CliLayerError) -> ErrorMessage { + match error { + CliLayerError::TempFile(e) => error_message() + .error_type(ErrorType::Internal) + .header("Failed to open temporary file") + .body(formatdoc! {" + An unexpected I/O error occurred while downloading the Yarn package manager into a \ + temporary directory. + " }) + .debug_info(e.to_string()) + .create(), + + CliLayerError::Download(e) => error_message() + .error_type(ErrorType::UserFacing(SuggestRetryBuild::Yes, SuggestSubmitIssue::No)) + .header("Failed to download Yarn") + .body(formatdoc! {" + An unexpected error occurred while downloading the Yarn package manager. This error can \ + occur due to an unstable network connection or an issue with the upstream repository. + + Suggestions: + - Check the npm status page for any ongoing incidents ({npm_status_url}) + ", npm_status_url = style::url("https://status.npmjs.org/") }) + .debug_info(e.to_string()) + .create(), + + CliLayerError::Untar(path, e) => error_message() + .error_type(ErrorType::UserFacing(SuggestRetryBuild::Yes, SuggestSubmitIssue::Yes)) + .header("Failed to extract the downloaded Yarn package file") + .body(formatdoc! {" + An unexpected I/O occurred while extracting the contents of the downloaded Yarn package file at {path}. + ", path = file_value(path) }) + .debug_info(e.to_string()) + .create(), + + CliLayerError::Installation(e) => error_message() + .error_type(ErrorType::UserFacing(SuggestRetryBuild::Yes, SuggestSubmitIssue::Yes)) + .header("Failed to install the downloaded Yarn package") + .body(formatdoc! {" + An unexpected error occurred while installing the downloaded Yarn package. + " }) + .debug_info(e.to_string()) + .create(), + + CliLayerError::Permissions(e) => error_message() + .error_type(ErrorType::Internal) + .header("Permissions error for Yarn installation") + .body(formatdoc! {" + An unexpected I/O error occurred while setting permissions on the Yarn package manager installation. + " }) + .debug_info(e.to_string()) + .create(), + } +} + +fn on_deps_layer_error(error: &DepsLayerError) -> ErrorMessage { + match error { + DepsLayerError::CreateCacheDir(path, e) => error_message() + .error_type(ErrorType::Internal) + .header("Failed to create Yarn cache directory") + .body(formatdoc! {" + An unexpected I/O error occurred while creating the cache directory at {path} that will be \ + used by the Yarn package manager. + ", path = file_value(path) }) + .debug_info(e.to_string()) + .create(), + + DepsLayerError::YarnCacheSet(e) => error_message() + .error_type(ErrorType::Internal) + .header("Failed to configure Yarn cache directory") + .body(formatdoc! {" + An unexpected error occurred while configuring the Yarn cache directory. + " }) + .debug_info(e.to_string()) + .create(), + } +} + +fn on_inventory_parse_error(error: &toml::de::Error) -> ErrorMessage { + error_message() + .error_type(ErrorType::Internal) + .header("Failed to parse Yarn inventory") + .body(formatdoc! {" + The {BUILDPACK_NAME} buildpack was unable to parse the Yarn inventory file. + "}) + .debug_info(error.to_string()) + .create() +} + +fn on_yarn_cache_get_error(error: &CmdError) -> ErrorMessage { + error_message() + .error_type(ErrorType::Internal) + .header("Failed to read configured Yarn cache directory") + .body(formatdoc! {" + The {BUILDPACK_NAME} buildpack was unable to read the configuration for the Yarn cache directory. + "}) + .debug_info(error.to_string()) + .create() +} + +fn on_yarn_disable_global_cache_error(error: &CmdError) -> ErrorMessage { + error_message() + .error_type(ErrorType::Internal) + .header("Failed to disable Yarn global cache") + .body(formatdoc! {" + The {BUILDPACK_NAME} buildpack was unable to disable the Yarn global cache. + "}) + .debug_info(error.to_string()) + .create() +} + +fn on_yarn_install_error(error: &CmdError) -> ErrorMessage { + let yarn_install = style::value(error.name()); + error_message() + .error_type(ErrorType::UserFacing( + SuggestRetryBuild::Yes, + SuggestSubmitIssue::Yes, + )) + .header("Failed to install Node modules") + .body(formatdoc! { " + The {BUILDPACK_NAME} buildpack uses the command {yarn_install} to install your Node modules. This command \ + failed and the buildpack cannot continue. This error can occur due to an unstable network connection. See the log output above for more information. + + Suggestions: + - Ensure that this command runs locally without error (exit status = 0). + - Check the status of the upstream Node module repository service at https://status.npmjs.org/ + " }) + .debug_info(error.to_string()) + .create() +} + +fn on_yarn_version_detect_error(error: &YarnVersionError) -> ErrorMessage { + match error { + YarnVersionError::Command(e) => error_message() + .error_type(ErrorType::Internal) + .header("Failed to determine Yarn version") + .body(formatdoc! { " + An unexpected error occurred while attempting to determine the current Yarn version \ + from the system. + " }) + .debug_info(e.to_string()) + .create(), + + YarnVersionError::Parse(stdout, e) => error_message() + .error_type(ErrorType::Internal) + .header("Failed to parse npm version") + .body(formatdoc! { " + An unexpected error occurred while parsing Yarn version information from '{stdout}'. + " }) + .debug_info(e.to_string()) + .create() + } +} + +fn on_yarn_version_unsupported_error(version: u64) -> ErrorMessage { + error_message() + .error_type(ErrorType::UserFacing( + SuggestRetryBuild::No, + SuggestSubmitIssue::Yes, + )) + .header("Unsupported Yarn version") + .body(formatdoc! {" + The {BUILDPACK_NAME} buildpack does not support Yarn version {version}. + + Suggestions: + - Update your package.json to specify a supported Yarn version. + "}) + .create() +} + +fn on_yarn_version_resolve_error(requirement: &Requirement) -> ErrorMessage { + let requested_version = style::value(requirement.to_string()); + let yarn_releases_url = style::url("https://github.com/yarnpkg/berry/releases"); + let inventory_url = style::url("https://github.com/heroku/buildpacks-nodejs/blob/main/buildpacks/nodejs-yarn/inventory.toml"); + let package_json = style::value("package.json"); + let engines_key = style::value("engines.yarn"); + error_message() + .error_type(ErrorType::UserFacing(SuggestRetryBuild::No, SuggestSubmitIssue::Yes)) + .header(format!("Error resolving requested Yarn version {requested_version}")) + .body(formatdoc! { " + The requested Yarn version could not be resolved to a known release in this buildpack's \ + inventory of Yarn releases. + + Suggestions: + - Confirm if this is a valid Yarn release at {yarn_releases_url} + - Check if this buildpack includes the requested Yarn version in its inventory file at {inventory_url} + - Update the {engines_key} field in {package_json} to a single version or version range that \ + includes a published Yarn version. + " }) + .create() +} + +fn on_yarn_default_parse_error(error: &VersionError) -> ErrorMessage { + error_message() + .error_type(ErrorType::Internal) + .header("Failed to parse default Yarn version") + .body(formatdoc! {" + The {BUILDPACK_NAME} buildpack was unable to parse the default Yarn version. + "}) + .debug_info(error.to_string()) + .create() +} + +fn on_node_build_scripts_metadata_error(error: NodeBuildScriptsMetadataError) -> ErrorMessage { + let NodeBuildScriptsMetadataError::InvalidEnabledValue(value) = error; + let value_type = value.type_str(); + let requires_metadata = style::value("[requires.metadata]"); + let buildplan_name = style::value(NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME); + error_message() + .error_type(ErrorType::UserFacing( + SuggestRetryBuild::No, + SuggestSubmitIssue::Yes, + )) + .header("Invalid build plan metadata") + .body(formatdoc! {" + A participating buildpack has set invalid {requires_metadata} for the \ + build plan named {buildplan_name}. + + Expected metadata format: + [requires.metadata] + enabled = + + But was: + [requires.metadata] + enabled = <{value_type}> + "}) + .create() +} + +#[cfg(test)] +mod tests { + use super::*; + use bullet_stream::strip_ansi; + use fun_run::{CmdError, CommandWithName}; + use heroku_nodejs_utils::package_json::PackageJsonError; + use heroku_nodejs_utils::vrs::Version; + use insta::{assert_snapshot, with_settings}; + use libcnb::Error; + use libherokubuildpack::download::DownloadError; + use std::process::Command; + use test_support::test_name; + + #[test] + fn test_yarn_build_script_error() { + assert_error_snapshot(YarnBuildpackError::BuildScript(create_cmd_error( + "yarn run build", + ))); + } + + #[test] + fn test_yarn_cli_layer_temp_file_error() { + assert_error_snapshot(YarnBuildpackError::CliLayer(CliLayerError::TempFile( + create_io_error("Disk full"), + ))); + } + + #[test] + fn test_yarn_cli_layer_download_error() { + assert_error_snapshot(YarnBuildpackError::CliLayer(CliLayerError::Download( + create_download_http_error(), + ))); + } + + #[test] + fn test_yarn_cli_layer_untar_error() { + assert_error_snapshot(YarnBuildpackError::CliLayer(CliLayerError::Untar( + "/layers/yarn/dist".into(), + create_io_error("Disk full"), + ))); + } + + #[test] + fn test_yarn_cli_layer_installation_error() { + assert_error_snapshot(YarnBuildpackError::CliLayer(CliLayerError::Installation( + create_io_error("Disk full"), + ))); + } + + #[test] + fn test_yarn_cli_layer_permissions_error() { + assert_error_snapshot(YarnBuildpackError::CliLayer(CliLayerError::Permissions( + create_io_error("Invalid permissions"), + ))); + } + + #[test] + fn test_yarn_deps_layer_create_cache_dir_error() { + assert_error_snapshot(YarnBuildpackError::DepsLayer( + DepsLayerError::CreateCacheDir( + "/layers/yarn/deps/cache".into(), + create_io_error("Disk full"), + ), + )); + } + + #[test] + fn test_yarn_deps_layer_yarn_cache_set_error() { + assert_error_snapshot(YarnBuildpackError::DepsLayer(DepsLayerError::YarnCacheSet( + create_cmd_error("yarn config set cache-dir /some/dir"), + ))); + } + + #[test] + fn test_yarn_inventory_parse_error() { + assert_error_snapshot(YarnBuildpackError::InventoryParse(create_toml_error())); + } + + #[test] + fn test_yarn_package_json_access_error() { + assert_error_snapshot(YarnBuildpackError::PackageJson( + PackageJsonError::AccessError(create_io_error("test I/O error blah")), + )); + } + + #[test] + fn test_yarn_package_json_parse_error() { + assert_error_snapshot(YarnBuildpackError::PackageJson( + PackageJsonError::ParseError(create_json_error()), + )); + } + + #[test] + fn test_yarn_cache_get_error() { + assert_error_snapshot(YarnBuildpackError::YarnCacheGet(create_cmd_error( + "yarn config get cache-dir", + ))); + } + + #[test] + fn test_yarn_disable_global_cache_error() { + assert_error_snapshot(YarnBuildpackError::YarnDisableGlobalCache( + create_cmd_error("yarn config set enableGlobalCache false"), + )); + } + + #[test] + fn test_yarn_install_error() { + assert_error_snapshot(YarnBuildpackError::YarnInstall(create_cmd_error( + "yarn install", + ))); + } + + #[test] + fn test_yarn_version_detect_yarn_version_command_error() { + assert_error_snapshot(YarnBuildpackError::YarnVersionDetect( + YarnVersionError::Command(create_cmd_error("yarn --version")), + )); + } + + #[test] + fn test_yarn_version_detect_yarn_version_parse_error() { + assert_error_snapshot(YarnBuildpackError::YarnVersionDetect( + YarnVersionError::Parse("not.a.version".to_string(), create_version_error()), + )); + } + + #[test] + fn test_yarn_version_unsupported_error() { + assert_error_snapshot(YarnBuildpackError::YarnVersionUnsupported(0)); + } + + #[test] + fn test_yarn_version_resolve_error() { + assert_error_snapshot(YarnBuildpackError::YarnVersionResolve( + Requirement::parse("1.2.3").unwrap(), + )); + } + + #[test] + fn test_yarn_default_parse_error() { + assert_error_snapshot(YarnBuildpackError::YarnDefaultParse(create_version_error())); + } + + #[test] + fn test_yarn_node_build_scripts_metadata_error() { + assert_error_snapshot(YarnBuildpackError::NodeBuildScriptsMetadata( + NodeBuildScriptsMetadataError::InvalidEnabledValue(toml::Value::String( + "not a boolean".into(), + )), + )); + } + + fn assert_error_snapshot(error: impl Into>) { + let error_message = strip_ansi(on_error(error.into()).to_string()); + let test_name = format!( + "errors__{}", + test_name() + .split("::") + .last() + .unwrap() + .trim_start_matches("test") + ); + with_settings!({ + prepend_module_to_snapshot => false, + omit_expression => true, + }, { + assert_snapshot!(test_name, error_message); + }); + } + + fn create_io_error(text: &str) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::Other, text) + } + + fn create_cmd_error(command: impl Into) -> CmdError { + Command::new("false") + .named(command.into()) + .named_output() + .unwrap_err() + } + + fn create_version_error() -> VersionError { + Version::parse("not.a.version").unwrap_err() + } + + fn create_json_error() -> serde_json::error::Error { + serde_json::from_str::(r#"{\n "name":\n}"#).unwrap_err() + } + + fn create_toml_error() -> toml::de::Error { + toml::from_str::("[[artifacts").unwrap_err() + } + + fn create_download_http_error() -> DownloadError { + Box::new(ureq::get("broken/ url").call().unwrap_err()).into() + } +} diff --git a/buildpacks/nodejs-yarn/src/install_yarn.rs b/buildpacks/nodejs-yarn/src/install_yarn.rs index 298fc5f7..a4ca9a1c 100644 --- a/buildpacks/nodejs-yarn/src/install_yarn.rs +++ b/buildpacks/nodejs-yarn/src/install_yarn.rs @@ -13,8 +13,8 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::io::Stderr; use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; use tempfile::NamedTempFile; -use thiserror::Error; use heroku_nodejs_utils::inv::Release; @@ -63,7 +63,7 @@ pub(crate) fn install_yarn( log = log.sub_bullet(format!("Extracting yarn {}", release.version)); decompress_tarball(&mut yarn_tgz.into_file(), dist_layer.path()) - .map_err(CliLayerError::Untar)?; + .map_err(|e| CliLayerError::Untar(dist_layer.path(), e))?; log = log.sub_bullet(format!("Installing yarn {}", release.version)); @@ -98,17 +98,12 @@ pub(crate) struct CliLayerMetadata { os: String, } -#[derive(Error, Debug)] +#[derive(Debug)] pub(crate) enum CliLayerError { - #[error("Couldn't create tempfile for yarn CLI: {0}")] TempFile(std::io::Error), - #[error("Couldn't download yarn CLI: {0}")] Download(DownloadError), - #[error("Couldn't decompress yarn CLI: {0}")] - Untar(std::io::Error), - #[error("Couldn't move yarn CLI to the target location: {0}")] + Untar(PathBuf, std::io::Error), Installation(std::io::Error), - #[error("Couldn't set CLI permissions: {0}")] Permissions(std::io::Error), } diff --git a/buildpacks/nodejs-yarn/src/main.rs b/buildpacks/nodejs-yarn/src/main.rs index 91a30fdf..b3663336 100644 --- a/buildpacks/nodejs-yarn/src/main.rs +++ b/buildpacks/nodejs-yarn/src/main.rs @@ -3,11 +3,20 @@ // to be able selectively opt out of coverage for functions/lines/modules. #![cfg_attr(coverage_nightly, feature(coverage_attribute))] +use crate::cmd::YarnVersionError; +use crate::configure_yarn_cache::{configure_yarn_cache, DepsLayerError}; +use crate::install_yarn::{install_yarn, CliLayerError}; use crate::yarn::Yarn; use bullet_stream::{style, Print}; +use heroku_nodejs_utils::buildplan::{ + read_node_build_scripts_metadata, NodeBuildScriptsMetadataError, + NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME, +}; use heroku_nodejs_utils::inv::Inventory; use heroku_nodejs_utils::package_json::{PackageJson, PackageJsonError}; use heroku_nodejs_utils::vrs::{Requirement, VersionError}; +#[cfg(test)] +use indoc as _; use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; use libcnb::data::build_plan::BuildPlanBuilder; use libcnb::data::launch::{LaunchBuilder, ProcessBuilder}; @@ -17,25 +26,16 @@ use libcnb::generic::GenericMetadata; use libcnb::generic::GenericPlatform; use libcnb::layer_env::Scope; use libcnb::{buildpack_main, Buildpack, Env}; -use std::io::{stderr, stdout}; -use thiserror::Error; - -use crate::configure_yarn_cache::{configure_yarn_cache, DepsLayerError}; -use crate::install_yarn::{install_yarn, CliLayerError}; -use heroku_nodejs_utils::buildplan::{ - read_node_build_scripts_metadata, NodeBuildScriptsMetadataError, - NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME, -}; -#[cfg(test)] -use indoc as _; #[cfg(test)] use libcnb_test as _; +use std::io::{stderr, stdout}; #[cfg(test)] use test_support as _; mod cfg; mod cmd; mod configure_yarn_cache; +mod errors; mod install_yarn; mod yarn; @@ -89,7 +89,7 @@ impl Buildpack for YarnBuildpack { let yarn_version = match cmd::yarn_version(&env) { // Install yarn if it's not present. - Err(cmd::Error::Spawn(_)) => { + Err(YarnVersionError::Command(_)) => { let mut bullet = log.bullet("Detecting yarn CLI version to install"); let inventory: Inventory = @@ -197,75 +197,25 @@ impl Buildpack for YarnBuildpack { } fn on_error(&self, error: libcnb::Error) { - let log = Print::new(stdout()).without_header(); - match error { - libcnb::Error::BuildpackError(bp_err) => match bp_err { - YarnBuildpackError::BuildScript(_) => { - log.error(format!("Yarn build script error\n\n{bp_err}")); - } - YarnBuildpackError::CliLayer(_) => { - log.error(format!("Yarn distribution layer error\n\n{bp_err}")); - } - YarnBuildpackError::DepsLayer(_) => { - log.error(format!("Yarn dependency layer error\n\n{bp_err}")); - } - YarnBuildpackError::InventoryParse(_) => { - log.error(format!("Yarn inventory parse error\n\n{bp_err}")); - } - YarnBuildpackError::PackageJson(_) => { - log.error(format!("Yarn package.json error\n\n{bp_err}")); - } - YarnBuildpackError::YarnCacheGet(_) - | YarnBuildpackError::YarnDisableGlobalCache(_) => { - log.error(format!("Yarn cache error\n\n{bp_err}")); - } - YarnBuildpackError::YarnInstall(_) => { - log.error(format!("Yarn install error\n\n{bp_err}")); - } - YarnBuildpackError::YarnVersionDetect(_) - | YarnBuildpackError::YarnVersionResolve(_) - | YarnBuildpackError::YarnVersionUnsupported(_) - | YarnBuildpackError::YarnDefaultParse(_) => { - log.error(format!("Yarn version error\n\n{bp_err}")); - } - YarnBuildpackError::NodeBuildScriptsMetadata(_) => { - log.error(format!("Yarn buildplan error\n\n{bp_err}")); - } - }, - err => { - log.error(format!("Yarn internal buildpack error\n\n{err}")); - } - } + let error_message = errors::on_error(error); + eprintln!("\n{error_message}"); } } -#[derive(Error, Debug)] +#[derive(Debug)] enum YarnBuildpackError { - #[error("Couldn't run build script: {0}")] BuildScript(fun_run::CmdError), - #[error("{0}")] - CliLayer(#[from] CliLayerError), - #[error("{0}")] - DepsLayer(#[from] DepsLayerError), - #[error("Couldn't parse yarn inventory: {0}")] + CliLayer(CliLayerError), + DepsLayer(DepsLayerError), InventoryParse(toml::de::Error), - #[error("Couldn't parse package.json: {0}")] PackageJson(PackageJsonError), - #[error("Couldn't read yarn cache folder: {0}")] - YarnCacheGet(cmd::Error), - #[error("Couldn't disable yarn global cache: {0}")] + YarnCacheGet(fun_run::CmdError), YarnDisableGlobalCache(fun_run::CmdError), - #[error("Yarn install error: {0}")] YarnInstall(fun_run::CmdError), - #[error("Couldn't determine yarn version: {0}")] - YarnVersionDetect(cmd::Error), - #[error("Unsupported yarn version: {0}")] + YarnVersionDetect(YarnVersionError), YarnVersionUnsupported(u64), - #[error("Couldn't resolve yarn version requirement ({0}) to a known yarn version")] YarnVersionResolve(Requirement), - #[error("Couldn't parse yarn default version range: {0}")] YarnDefaultParse(VersionError), - #[error("Couldn't parse metadata for the buildplan named {NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME}: {0:?}")] NodeBuildScriptsMetadata(NodeBuildScriptsMetadataError), } diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_build_script_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_build_script_error.snap new file mode 100644 index 00000000..209477fa --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_build_script_error.snap @@ -0,0 +1,25 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn run build` + exit status: 1 + stdout: + stderr: + +! Failed to execute build script +! +! The Heroku Node.js Yarn buildpack allows customization of the build process by executing the following scripts if they are defined in `package.json`: +! - `heroku-prebuild` +! - `heroku-build` or `build` +! - `heroku-postbuild` +! +! An unexpected error occurred while executing `yarn run build`. See the log output above for more information. +! +! Suggestions: +! - Ensure that this command runs locally without error. +! +! Use the debug information above to troubleshoot and retry your build. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cache_get_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cache_get_error.snap new file mode 100644 index 00000000..a38334d4 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cache_get_error.snap @@ -0,0 +1,17 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn config get cache-dir` + exit status: 1 + stdout: + stderr: + +! Failed to read configured Yarn cache directory +! +! The Heroku Node.js Yarn buildpack was unable to read the configuration for the Yarn cache directory. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_download_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_download_error.snap new file mode 100644 index 00000000..112f50cc --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_download_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - HTTP error while downloading file: http: invalid format + +! Failed to download Yarn +! +! An unexpected error occurred while downloading the Yarn package manager. This error can occur due to an unstable network connection or an issue with the upstream repository. +! +! Suggestions: +! - Check the npm status page for any ongoing incidents (https://status.npmjs.org/) +! +! Use the debug information above to troubleshoot and retry your build. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_installation_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_installation_error.snap new file mode 100644 index 00000000..8434f66a --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_installation_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Disk full + +! Failed to install the downloaded Yarn package +! +! An unexpected error occurred while installing the downloaded Yarn package. +! +! Use the debug information above to troubleshoot and retry your build. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_permissions_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_permissions_error.snap new file mode 100644 index 00000000..2df5d370 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_permissions_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Invalid permissions + +! Permissions error for Yarn installation +! +! An unexpected I/O error occurred while setting permissions on the Yarn package manager installation. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_temp_file_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_temp_file_error.snap new file mode 100644 index 00000000..ef5a1027 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_temp_file_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Disk full + +! Failed to open temporary file +! +! An unexpected I/O error occurred while downloading the Yarn package manager into a temporary directory. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_untar_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_untar_error.snap new file mode 100644 index 00000000..db8e7128 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_cli_layer_untar_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Disk full + +! Failed to extract the downloaded Yarn package file +! +! An unexpected I/O occurred while extracting the contents of the downloaded Yarn package file at `/layers/yarn/dist`. +! +! Use the debug information above to troubleshoot and retry your build. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_default_parse_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_default_parse_error.snap new file mode 100644 index 00000000..c4952e73 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_default_parse_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Failed to parse version. + +! Failed to parse default Yarn version +! +! The Heroku Node.js Yarn buildpack was unable to parse the default Yarn version. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_create_cache_dir_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_create_cache_dir_error.snap new file mode 100644 index 00000000..97abff5c --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_create_cache_dir_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Disk full + +! Failed to create Yarn cache directory +! +! An unexpected I/O error occurred while creating the cache directory at `/layers/yarn/deps/cache` that will be used by the Yarn package manager. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_yarn_cache_set_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_yarn_cache_set_error.snap new file mode 100644 index 00000000..061ac92a --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_deps_layer_yarn_cache_set_error.snap @@ -0,0 +1,17 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn config set cache-dir /some/dir` + exit status: 1 + stdout: + stderr: + +! Failed to configure Yarn cache directory +! +! An unexpected error occurred while configuring the Yarn cache directory. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_disable_global_cache_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_disable_global_cache_error.snap new file mode 100644 index 00000000..841728a5 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_disable_global_cache_error.snap @@ -0,0 +1,17 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn config set enableGlobalCache false` + exit status: 1 + stdout: + stderr: + +! Failed to disable Yarn global cache +! +! The Heroku Node.js Yarn buildpack was unable to disable the Yarn global cache. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_install_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_install_error.snap new file mode 100644 index 00000000..aa517227 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_install_error.snap @@ -0,0 +1,21 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn install` + exit status: 1 + stdout: + stderr: + +! Failed to install Node modules +! +! The Heroku Node.js Yarn buildpack uses the command `yarn install` to install your Node modules. This command failed and the buildpack cannot continue. This error can occur due to an unstable network connection. See the log output above for more information. +! +! Suggestions: +! - Ensure that this command runs locally without error (exit status = 0). +! - Check the status of the upstream Node module repository service at https://status.npmjs.org/ +! +! Use the debug information above to troubleshoot and retry your build. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_inventory_parse_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_inventory_parse_error.snap new file mode 100644 index 00000000..386735cf --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_inventory_parse_error.snap @@ -0,0 +1,19 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - TOML parse error at line 1, column 12 + | + 1 | [[artifacts + | ^ + invalid table header + expected `.`, `]]` + +! Failed to parse Yarn inventory +! +! The Heroku Node.js Yarn buildpack was unable to parse the Yarn inventory file. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_node_build_scripts_metadata_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_node_build_scripts_metadata_error.snap new file mode 100644 index 00000000..3e517361 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_node_build_scripts_metadata_error.snap @@ -0,0 +1,17 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +! Invalid build plan metadata +! +! A participating buildpack has set invalid `[requires.metadata]` for the build plan named `node_build_scripts`. +! +! Expected metadata format: +! [requires.metadata] +! enabled = +! +! But was: +! [requires.metadata] +! enabled = +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_access_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_access_error.snap new file mode 100644 index 00000000..7ba16396 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_access_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - test I/O error blah + +! Error reading `./package.json` +! +! The Heroku Node.js Yarn reads from `./package.json` to complete the build but the file can't be read. +! +! Suggestions: +! - Ensure the file has read permissions. +! +! Use the debug information above to troubleshoot and retry your build. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_parse_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_parse_error.snap new file mode 100644 index 00000000..a462accd --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_package_json_parse_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - key must be a string at line 1 column 2 + +! Error parsing `./package.json` +! +! The Heroku Node.js Yarn reads from `./package.json` to complete the build but the file isn't valid JSON. +! +! Suggestions: +! - Ensure the file follows the JSON format described at https://www.json.org/ +! +! Use the debug information above to troubleshoot and retry your build. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_command_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_command_error.snap new file mode 100644 index 00000000..9e47a9fe --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_command_error.snap @@ -0,0 +1,17 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Command failed `yarn --version` + exit status: 1 + stdout: + stderr: + +! Failed to determine Yarn version +! +! An unexpected error occurred while attempting to determine the current Yarn version from the system. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_parse_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_parse_error.snap new file mode 100644 index 00000000..17e05d15 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_detect_yarn_version_parse_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +- Debug Info: + - Failed to parse version. + +! Failed to parse npm version +! +! An unexpected error occurred while parsing Yarn version information from 'not.a.version'. +! +! The causes for this error are unknown. We do not have suggestions for diagnosis or a workaround at this time. You can help our understanding by sharing your buildpack log and a description of the issue at: +! https://github.com/heroku/buildpacks-nodejs/issues +! +! If you're able to reproduce the problem with an example application and the `pack` build tool (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/), adding that information to the discussion will also help. Once we have more information around the causes of this error we may update this message. diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_resolve_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_resolve_error.snap new file mode 100644 index 00000000..0c978fb7 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_resolve_error.snap @@ -0,0 +1,14 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +! Error resolving requested Yarn version `1.2.3` +! +! The requested Yarn version could not be resolved to a known release in this buildpack's inventory of Yarn releases. +! +! Suggestions: +! - Confirm if this is a valid Yarn release at https://github.com/yarnpkg/berry/releases +! - Check if this buildpack includes the requested Yarn version in its inventory file at https://github.com/heroku/buildpacks-nodejs/blob/main/buildpacks/nodejs-yarn/inventory.toml +! - Update the `engines.yarn` field in `package.json` to a single version or version range that includes a published Yarn version. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues diff --git a/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_unsupported_error.snap b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_unsupported_error.snap new file mode 100644 index 00000000..fc71f938 --- /dev/null +++ b/buildpacks/nodejs-yarn/src/snapshots/errors___yarn_version_unsupported_error.snap @@ -0,0 +1,12 @@ +--- +source: buildpacks/nodejs-yarn/src/errors.rs +--- +! Unsupported Yarn version +! +! The Heroku Node.js Yarn buildpack does not support Yarn version 0. +! +! Suggestions: +! - Update your package.json to specify a supported Yarn version. +! +! If the issue persists and you think you found a bug in the buildpack, reproduce the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository and include the details here: +! https://github.com/heroku/buildpacks-nodejs/issues From 6e259e00974ddaa20a133fe01933b0472507f2ca Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 14 Apr 2025 14:55:03 -0300 Subject: [PATCH 2/3] Update CHANGELOG.md Signed-off-by: Colin Casey --- buildpacks/nodejs-yarn/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/buildpacks/nodejs-yarn/CHANGELOG.md b/buildpacks/nodejs-yarn/CHANGELOG.md index 4ff5254c..f811c7b5 100644 --- a/buildpacks/nodejs-yarn/CHANGELOG.md +++ b/buildpacks/nodejs-yarn/CHANGELOG.md @@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + - Added Yarn version 4.9.0. + +### Changed + +- Updated error messages and formatting. ([#1074](https://github.com/heroku/buildpacks-nodejs/pull/1074)) + ## [3.6.0] - 2025-04-09 - No changes. From cb4be6e52e518b7930019a4621cfb2be2b67c8f4 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Mon, 14 Apr 2025 14:55:53 -0300 Subject: [PATCH 3/3] Fixed lint errors --- buildpacks/nodejs-yarn/src/cmd.rs | 2 +- buildpacks/nodejs-yarn/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildpacks/nodejs-yarn/src/cmd.rs b/buildpacks/nodejs-yarn/src/cmd.rs index 6054e09b..3dfeaae7 100644 --- a/buildpacks/nodejs-yarn/src/cmd.rs +++ b/buildpacks/nodejs-yarn/src/cmd.rs @@ -7,7 +7,7 @@ use libcnb::Env; use std::io::Stderr; use std::{ path::{Path, PathBuf}, - process::{Command, Stdio}, + process::Command, }; #[derive(Debug)] diff --git a/buildpacks/nodejs-yarn/src/main.rs b/buildpacks/nodejs-yarn/src/main.rs index b3663336..ba4e3c40 100644 --- a/buildpacks/nodejs-yarn/src/main.rs +++ b/buildpacks/nodejs-yarn/src/main.rs @@ -28,7 +28,7 @@ use libcnb::layer_env::Scope; use libcnb::{buildpack_main, Buildpack, Env}; #[cfg(test)] use libcnb_test as _; -use std::io::{stderr, stdout}; +use std::io::stderr; #[cfg(test)] use test_support as _;