Skip to content

Commit a078c1e

Browse files
authored
Improve Yarn errors (#1079)
* Improve Yarn errors This PR adds detailed and consistent error messages for the Yarn buildpack + includes snapshot testing for the output. * Update CHANGELOG.md Signed-off-by: Colin Casey <[email protected]> * Fixed lint errors --------- Signed-off-by: Colin Casey <[email protected]>
1 parent a693d4e commit a078c1e

28 files changed

+894
-134
lines changed

Diff for: Cargo.lock

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: buildpacks/nodejs-yarn/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
1012
- Added Yarn version 4.9.0.
13+
14+
### Changed
15+
16+
- Updated error messages and formatting. ([#1074](https://github.com/heroku/buildpacks-nodejs/pull/1074))
17+
1118
## [3.6.0] - 2025-04-09
1219

1320
- No changes.

Diff for: buildpacks/nodejs-yarn/Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ workspace = true
1010
bullet_stream = "0.7"
1111
fun_run = "0.5"
1212
heroku-nodejs-utils.workspace = true
13+
indoc = "2"
1314
libcnb = { version = "=0.28.1", features = ["trace"] }
1415
libherokubuildpack = { version = "=0.28.1", default-features = false, features = [
1516
"download",
@@ -18,10 +19,11 @@ libherokubuildpack = { version = "=0.28.1", default-features = false, features =
1819
] }
1920
serde = "1"
2021
tempfile = "3"
21-
thiserror = "2"
2222
toml = "0.8"
2323

2424
[dev-dependencies]
25-
indoc = "2"
25+
insta = "1"
2626
libcnb-test = "=0.28.1"
2727
test_support.workspace = true
28+
serde_json = "1"
29+
ureq = "3"

Diff for: buildpacks/nodejs-yarn/src/cmd.rs

+26-43
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,51 @@ use crate::yarn::Yarn;
22
use bullet_stream::state::SubBullet;
33
use bullet_stream::{style, Print};
44
use fun_run::{CmdError, CommandWithName};
5-
use heroku_nodejs_utils::vrs::Version;
5+
use heroku_nodejs_utils::vrs::{Version, VersionError};
66
use libcnb::Env;
77
use std::io::Stderr;
88
use std::{
99
path::{Path, PathBuf},
10-
process::{Command, Stdio},
10+
process::Command,
1111
};
1212

13-
#[derive(thiserror::Error, Debug)]
14-
pub(crate) enum Error {
15-
#[error("Couldn't start yarn command: {0}")]
16-
Spawn(std::io::Error),
17-
#[error("Couldn't finish yarn command: {0}")]
18-
Wait(std::io::Error),
19-
#[error("Yarn command finished with a non-zero exit code: {0}")]
20-
Exit(std::process::ExitStatus),
21-
#[error("Yarn output couldn't be parsed: {0}")]
22-
Parse(String),
13+
#[derive(Debug)]
14+
pub(crate) enum YarnVersionError {
15+
Command(CmdError),
16+
Parse(String, VersionError),
2317
}
2418

2519
/// Execute `yarn --version` to determine what version of `yarn` is in effect
2620
/// for this codebase.
27-
pub(crate) fn yarn_version(env: &Env) -> Result<Version, Error> {
28-
let output = Command::new("yarn")
21+
pub(crate) fn yarn_version(env: &Env) -> Result<Version, YarnVersionError> {
22+
Command::new("yarn")
2923
.arg("--version")
3024
.envs(env)
31-
.stdout(Stdio::piped())
32-
.spawn()
33-
.map_err(Error::Spawn)?
34-
.wait_with_output()
35-
.map_err(Error::Wait)?;
36-
37-
output
38-
.status
39-
.success()
40-
.then_some(())
41-
.ok_or(Error::Exit(output.status))?;
42-
let stdout = String::from_utf8_lossy(&output.stdout);
43-
stdout
44-
.parse()
45-
.map_err(|_| Error::Parse(stdout.into_owned()))
25+
.named_output()
26+
.map_err(YarnVersionError::Command)
27+
.and_then(|output| {
28+
let stdout = output.stdout_lossy();
29+
stdout
30+
.parse::<Version>()
31+
.map_err(|e| YarnVersionError::Parse(stdout, e))
32+
})
4633
}
4734

4835
/// Execute `yarn config get` to determine where the yarn cache is.
49-
pub(crate) fn yarn_get_cache(yarn_line: &Yarn, env: &Env) -> Result<PathBuf, Error> {
50-
let mut args = vec!["config", "get"];
36+
pub(crate) fn yarn_get_cache(yarn_line: &Yarn, env: &Env) -> Result<PathBuf, CmdError> {
37+
let mut cmd = Command::new("yarn");
38+
cmd.envs(env);
39+
40+
cmd.arg("config");
41+
cmd.arg("get");
5142
if yarn_line == &Yarn::Yarn1 {
52-
args.push("cache-folder");
43+
cmd.arg("cache-folder");
5344
} else {
54-
args.push("cacheFolder");
45+
cmd.arg("cacheFolder");
5546
}
56-
let output = Command::new("yarn")
57-
.args(args)
58-
.envs(env)
59-
.stdout(Stdio::piped())
60-
.spawn()
61-
.map_err(Error::Spawn)?
62-
.wait_with_output()
63-
.map_err(Error::Wait)?;
6447

65-
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
66-
Ok(stdout.into())
48+
cmd.named_output()
49+
.map(|output| PathBuf::from(output.stdout_lossy().trim()))
6750
}
6851

6952
/// Execute `yarn config set` to set the yarn cache to a specfic location.

Diff for: buildpacks/nodejs-yarn/src/configure_yarn_cache.rs

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::yarn::Yarn;
2+
use crate::{cmd, YarnBuildpack, YarnBuildpackError};
13
use bullet_stream::state::SubBullet;
24
use bullet_stream::Print;
35
use libcnb::build::BuildContext;
@@ -9,10 +11,7 @@ use libcnb::Env;
911
use serde::{Deserialize, Serialize};
1012
use std::fs;
1113
use std::io::Stderr;
12-
use thiserror::Error;
13-
14-
use crate::yarn::Yarn;
15-
use crate::{cmd, YarnBuildpack, YarnBuildpackError};
14+
use std::path::PathBuf;
1615

1716
/// `DepsLayer` is a layer for caching yarn dependencies from build to build.
1817
/// This layer is irrelevant in zero-install mode, as cached dependencies are
@@ -62,8 +61,9 @@ pub(crate) fn configure_yarn_cache(
6261
cache_usage_count: new_metadata.cache_usage_count + 1.0,
6362
..new_metadata
6463
})?;
65-
fs::create_dir(deps_layer.path().join(CACHE_DIR))
66-
.map_err(DepsLayerError::CreateCacheDir)?;
64+
65+
let cache_dir = deps_layer.path().join(CACHE_DIR);
66+
fs::create_dir(&cache_dir).map_err(|e| DepsLayerError::CreateCacheDir(cache_dir, e))?;
6767
}
6868
}
6969

@@ -86,11 +86,9 @@ pub(crate) struct DepsLayerMetadata {
8686
yarn: Yarn,
8787
}
8888

89-
#[derive(Error, Debug)]
89+
#[derive(Debug)]
9090
pub(crate) enum DepsLayerError {
91-
#[error("Couldn't create yarn dependency cache: {0}")]
92-
CreateCacheDir(std::io::Error),
93-
#[error("Couldn't set yarn cache folder: {0}")]
91+
CreateCacheDir(PathBuf, std::io::Error),
9492
YarnCacheSet(fun_run::CmdError),
9593
}
9694

0 commit comments

Comments
 (0)