Skip to content

Improve Yarn errors #1079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions buildpacks/nodejs-yarn/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions buildpacks/nodejs-yarn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
69 changes: 26 additions & 43 deletions buildpacks/nodejs-yarn/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,51 @@ 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::{
path::{Path, PathBuf},
process::{Command, Stdio},
process::Command,
};

#[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<Version, Error> {
let output = Command::new("yarn")
pub(crate) fn yarn_version(env: &Env) -> Result<Version, YarnVersionError> {
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::<Version>()
.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<PathBuf, Error> {
let mut args = vec!["config", "get"];
pub(crate) fn yarn_get_cache(yarn_line: &Yarn, env: &Env) -> Result<PathBuf, CmdError> {
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.
Expand Down
18 changes: 8 additions & 10 deletions buildpacks/nodejs-yarn/src/configure_yarn_cache.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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))?;
}
}

Expand All @@ -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),
}

Expand Down
Loading