Skip to content

Commit 1e11454

Browse files
authored
Improve pnpm engine errors (#1077)
* Improve pnpm engine errors This PR adds detailed and consistent error messages for the pnpm engine buildpack + includes snapshot testing for the output. * Update CHANGELOG.md Signed-off-by: Colin Casey <[email protected]> --------- Signed-off-by: Colin Casey <[email protected]>
1 parent 9dacbe0 commit 1e11454

File tree

7 files changed

+123
-75
lines changed

7 files changed

+123
-75
lines changed

Diff for: Cargo.lock

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

Diff for: buildpacks/nodejs-pnpm-engine/CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Updated error messages and formatting. ([#1077](https://github.com/heroku/buildpacks-nodejs/pull/1077))
13+
1014
## [3.6.0] - 2025-04-09
1115

1216
- No changes.

Diff for: buildpacks/nodejs-pnpm-engine/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ workspace = true
88

99
[dependencies]
1010
bullet_stream = "0.7"
11+
heroku-nodejs-utils.workspace = true
1112
indoc = "2"
1213
libcnb = { version = "=0.28.1", features = ["trace"] }
1314

1415
[dev-dependencies]
16+
insta = "1"
1517
libcnb-test = "=0.28.1"
1618
test_support.workspace = true

Diff for: buildpacks/nodejs-pnpm-engine/src/errors.rs

+68-55
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,94 @@
1-
use crate::BUILDPACK_NAME;
2-
use bullet_stream::state::Bullet;
3-
use bullet_stream::{style, Print};
1+
use crate::PnpmEngineBuildpackError;
2+
use bullet_stream::style;
3+
use heroku_nodejs_utils::error_handling::error_message_builder::SetIssuesUrl;
4+
use heroku_nodejs_utils::error_handling::{
5+
on_framework_error, ErrorMessage, ErrorMessageBuilder, ErrorType, SuggestRetryBuild,
6+
SuggestSubmitIssue,
7+
};
48
use indoc::formatdoc;
5-
use std::fmt::Display;
6-
use std::io::{stderr, Stderr};
79

8-
#[derive(Debug, Copy, Clone)]
9-
pub(crate) enum PnpmEngineBuildpackError {
10-
CorepackRequired,
11-
}
10+
const BUILDPACK_NAME: &str = "Heroku Node.js pnpm Engine";
11+
12+
const ISSUES_URL: &str = "https://github.com/heroku/buildpacks-nodejs/issues";
1213

13-
pub(crate) fn on_error(error: libcnb::Error<PnpmEngineBuildpackError>) {
14-
let logger = Print::new(stderr()).without_header();
14+
pub(crate) fn on_error(error: libcnb::Error<PnpmEngineBuildpackError>) -> ErrorMessage {
1515
match error {
16-
libcnb::Error::BuildpackError(buildpack_error) => {
17-
on_buildpack_error(buildpack_error, logger);
18-
}
19-
framework_error => on_framework_error(&framework_error, logger),
16+
libcnb::Error::BuildpackError(e) => on_buildpack_error(e),
17+
e => on_framework_error(BUILDPACK_NAME, ISSUES_URL, &e),
2018
}
2119
}
2220

23-
fn on_buildpack_error(error: PnpmEngineBuildpackError, logger: Print<Bullet<Stderr>>) {
21+
// Wraps the error_message() builder to preset the issues_url field
22+
fn error_message() -> ErrorMessageBuilder<SetIssuesUrl> {
23+
heroku_nodejs_utils::error_handling::error_message().issues_url(ISSUES_URL.to_string())
24+
}
25+
26+
fn on_buildpack_error(error: PnpmEngineBuildpackError) -> ErrorMessage {
2427
match error {
2528
PnpmEngineBuildpackError::CorepackRequired => {
26-
print_error_details(logger, &"Corepack Requirement Error").error(formatdoc! {"
27-
A pnpm lockfile ({pnpm_lockfile}) was detected, but the
29+
let corepack_enable = style::command("corepack enable");
30+
let corepack_use_pnpm = style::command("corepack use pnpm@*");
31+
let heroku_nodejs_corepack = style::command("heroku/nodejs-corepack");
32+
let package_manager = style::value("packageManager");
33+
let pnpm = style::value("pnpm");
34+
let pnpm_lockfile = style::value("pnpm-lock.yaml");
35+
let package_json = style::value("package.json");
36+
37+
error_message()
38+
.error_type(ErrorType::UserFacing(
39+
SuggestRetryBuild::No,
40+
SuggestSubmitIssue::No,
41+
))
42+
.header("Corepack Requirement Error")
43+
.body(formatdoc! {"
44+
A pnpm lockfile ({pnpm_lockfile}) was detected, but the \
2845
version of {pnpm} to install could not be determined.
2946
30-
{pnpm} may be installed via the {heroku_nodejs_corepack}
31-
buildpack. It requires the desired {pnpm} version to be set
47+
{pnpm} may be installed via the {heroku_nodejs_corepack} \
48+
buildpack. It requires the desired {pnpm} version to be set \
3249
via the {package_manager} key in {package_json}.
3350
34-
To set {package_manager} in {package_json} to the latest
51+
To set {package_manager} in {package_json} to the latest \
3552
{pnpm}, run:
3653
3754
{corepack_enable}
3855
{corepack_use_pnpm}
3956
4057
Then commit the result, and try again.
41-
",
42-
corepack_enable = style::command("corepack enable"),
43-
corepack_use_pnpm = style::command("corepack use pnpm@*"),
44-
heroku_nodejs_corepack = style::command("heroku/nodejs-corepack"),
45-
package_manager = style::value("packageManager"),
46-
pnpm = style::value("pnpm"),
47-
pnpm_lockfile = style::value("pnpm-lock.yaml"),
48-
package_json = style::value("package.json")});
58+
" })
59+
.create()
4960
}
5061
}
5162
}
5263

53-
fn on_framework_error(
54-
error: &libcnb::Error<PnpmEngineBuildpackError>,
55-
logger: Print<Bullet<Stderr>>,
56-
) {
57-
print_error_details(logger, &error)
58-
.error(formatdoc! {"
59-
{buildpack_name} internal error.
64+
#[cfg(test)]
65+
mod tests {
66+
use super::*;
67+
use bullet_stream::strip_ansi;
68+
use insta::{assert_snapshot, with_settings};
69+
use libcnb::Error;
70+
use test_support::test_name;
6071

61-
The framework used by this buildpack encountered an unexpected error.
62-
63-
If you can't deploy to Heroku due to this issue, check the official Heroku Status page at \
64-
status.heroku.com for any ongoing incidents. After all incidents resolve, retry your build.
65-
66-
If the issue persists and you think you found a bug in the buildpack or framework, reproduce \
67-
the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository \
68-
and include the details.
69-
70-
", buildpack_name = style::value(BUILDPACK_NAME) });
71-
}
72+
#[test]
73+
fn test_pnpm_engine_corepack_required_error() {
74+
assert_error_snapshot(PnpmEngineBuildpackError::CorepackRequired);
75+
}
7276

73-
fn print_error_details(
74-
logger: Print<Bullet<Stderr>>,
75-
error: &impl Display,
76-
) -> Print<Bullet<Stderr>> {
77-
logger
78-
.bullet(style::important("DEBUG INFO:"))
79-
.sub_bullet(error.to_string())
80-
.done()
77+
fn assert_error_snapshot(error: impl Into<Error<PnpmEngineBuildpackError>>) {
78+
let error_message = strip_ansi(on_error(error.into()).to_string());
79+
let test_name = format!(
80+
"errors__{}",
81+
test_name()
82+
.split("::")
83+
.last()
84+
.unwrap()
85+
.trim_start_matches("test")
86+
);
87+
with_settings!({
88+
prepend_module_to_snapshot => false,
89+
omit_expression => true,
90+
}, {
91+
assert_snapshot!(test_name, error_message);
92+
});
93+
}
8194
}

Diff for: buildpacks/nodejs-pnpm-engine/src/main.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@
55

66
mod errors;
77

8-
use crate::errors::PnpmEngineBuildpackError;
8+
use bullet_stream::Print;
99
use libcnb::build::{BuildContext, BuildResult};
1010
use libcnb::data::build_plan::BuildPlanBuilder;
1111
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
1212
use libcnb::generic::{GenericMetadata, GenericPlatform};
1313
use libcnb::{buildpack_main, Buildpack};
1414
#[cfg(test)]
1515
use libcnb_test as _;
16+
use std::io::stderr;
1617
#[cfg(test)]
1718
use test_support as _;
1819

19-
const BUILDPACK_NAME: &str = "Heroku Node.js pnpm Engine Buildpack";
20-
2120
struct PnpmEngineBuildpack;
2221

2322
impl Buildpack for PnpmEngineBuildpack {
@@ -40,17 +39,30 @@ impl Buildpack for PnpmEngineBuildpack {
4039
DetectResultBuilder::fail().build()
4140
}
4241

43-
fn build(&self, _context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
42+
fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
43+
let _logger = Print::new(stderr()).h1(context
44+
.buildpack_descriptor
45+
.buildpack
46+
.name
47+
.as_ref()
48+
.expect("The buildpack.toml should have a 'name' field set"));
49+
4450
// This buildpack does not install pnpm yet, suggest using
4551
// `heroku/nodejs-corepack` instead.
4652
Err(PnpmEngineBuildpackError::CorepackRequired)?
4753
}
4854

4955
fn on_error(&self, error: libcnb::Error<Self::Error>) {
50-
errors::on_error(error);
56+
let error_message = errors::on_error(error);
57+
eprintln!("\n{error_message}");
5158
}
5259
}
5360

61+
#[derive(Debug, Copy, Clone)]
62+
pub(crate) enum PnpmEngineBuildpackError {
63+
CorepackRequired,
64+
}
65+
5466
impl From<PnpmEngineBuildpackError> for libcnb::Error<PnpmEngineBuildpackError> {
5567
fn from(value: PnpmEngineBuildpackError) -> Self {
5668
libcnb::Error::BuildpackError(value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
source: buildpacks/nodejs-pnpm-engine/src/errors.rs
3+
---
4+
! Corepack Requirement Error
5+
!
6+
! A pnpm lockfile (`pnpm-lock.yaml`) was detected, but the version of `pnpm` to install could not be determined.
7+
!
8+
! `pnpm` may be installed via the `heroku/nodejs-corepack` buildpack. It requires the desired `pnpm` version to be set via the `packageManager` key in `package.json`.
9+
!
10+
! To set `packageManager` in `package.json` to the latest `pnpm`, run:
11+
!
12+
! `corepack enable`
13+
! `corepack use pnpm@*`
14+
!
15+
! Then commit the result, and try again.

Diff for: buildpacks/nodejs-pnpm-engine/tests/integration_test.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@ fn pnpm_unknown_version() {
1717
assert_contains!(
1818
ctx.pack_stderr,
1919
&formatdoc! {"
20-
! A pnpm lockfile (`pnpm-lock.yaml`) was detected, but the
21-
! version of `pnpm` to install could not be determined.
22-
!
23-
! `pnpm` may be installed via the `heroku/nodejs-corepack`
24-
! buildpack. It requires the desired `pnpm` version to be set
25-
! via the `packageManager` key in `package.json`.
26-
!
27-
! To set `packageManager` in `package.json` to the latest
28-
! `pnpm`, run:
29-
!
30-
! `corepack enable`
31-
! `corepack use pnpm@*`
32-
!
33-
! Then commit the result, and try again.
34-
"}
20+
! A pnpm lockfile (`pnpm-lock.yaml`) was detected, but the \
21+
version of `pnpm` to install could not be determined.
22+
!
23+
! `pnpm` may be installed via the `heroku/nodejs-corepack` \
24+
buildpack. It requires the desired `pnpm` version to be set \
25+
via the `packageManager` key in `package.json`.
26+
!
27+
! To set `packageManager` in `package.json` to the latest \
28+
`pnpm`, run:
29+
!
30+
! `corepack enable`
31+
! `corepack use pnpm@*`
32+
!
33+
! Then commit the result, and try again.
34+
"}
3535
);
3636
},
3737
);

0 commit comments

Comments
 (0)