From c8571b8ba336e6272b1558aabdd88585d450fd07 Mon Sep 17 00:00:00 2001 From: Colin Casey Date: Tue, 7 Jan 2025 09:04:33 -0400 Subject: [PATCH] Replace `commons` logger with `bullet_stream` (#993) * Replace `commons` logger with `bullet_stream` Some of the buildpacks here were early adopters of the output module originally developed [in the Ruby buildpack](https://github.com/heroku/buildpacks-ruby/blob/221fe5bd5bfc5e2715e3810ce353a1252564f012/commons/src/output/mod.rs). This output module has been replaced by [bullet_stream](https://github.com/heroku-buildpacks/bullet_stream). Eventually, all the buildpacks here will use [bullet_stream](https://github.com/heroku-buildpacks/bullet_stream). For now, I'm just migrating over the existing usage so that I no longer have to deal with errors like this: https://github.com/heroku/heroku-buildpack-nodejs/actions/runs/12626267505/job/35179157027#step:4:24 --- Cargo.lock | 252 +++--------------- buildpacks/nodejs-npm-engine/CHANGELOG.md | 4 + buildpacks/nodejs-npm-engine/Cargo.toml | 2 +- buildpacks/nodejs-npm-engine/src/errors.rs | 124 ++++----- .../nodejs-npm-engine/src/install_npm.rs | 70 ++--- buildpacks/nodejs-npm-engine/src/main.rs | 53 ++-- buildpacks/nodejs-npm-install/CHANGELOG.md | 4 + buildpacks/nodejs-npm-install/Cargo.toml | 2 +- .../src/configure_npm_cache_directory.rs | 19 +- buildpacks/nodejs-npm-install/src/errors.rs | 104 ++++---- buildpacks/nodejs-npm-install/src/main.rs | 167 ++++++------ buildpacks/nodejs-pnpm-engine/CHANGELOG.md | 4 + buildpacks/nodejs-pnpm-engine/Cargo.toml | 2 +- buildpacks/nodejs-pnpm-engine/src/errors.rs | 46 ++-- common/nodejs-utils/Cargo.toml | 2 +- common/nodejs-utils/src/application.rs | 38 ++- 16 files changed, 349 insertions(+), 544 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63aee2e3..2f3671f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,16 +38,6 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" -[[package]] -name = "ascii_table" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2bee9b9ee0e5768772e38c07ef0ba23a490d7e1336ec7207c25712a2661c55" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "async-trait" version = "0.1.81" @@ -71,30 +61,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -152,20 +127,16 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.16.0" +name = "bullet_stream" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1e038a9e6b7e36319ab42141434b0c7a93f7714aa90abf63cc5086e106027bb7" [[package]] -name = "byte-unit" -version = "4.0.19" +name = "bumpalo" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" -dependencies = [ - "serde", - "utf8-width", -] +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecount" @@ -241,51 +212,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "commons" -version = "0.0.0" -source = "git+https://github.com/heroku/buildpacks-ruby?branch=main#d8a1d0a166d37df3b78a9d20cdaa316f5a00056e" -dependencies = [ - "ascii_table", - "byte-unit", - "const_format", - "fancy-regex 0.13.0", - "fs-err", - "fs_extra", - "fun_run", - "glob", - "indoc", - "lazy_static", - "libcnb 0.23.0", - "libherokubuildpack 0.21.0", - "regex", - "serde", - "sha2", - "tempfile", - "thiserror 1.0.69", - "walkdir", -] - -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -432,24 +358,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fancy-regex" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set 0.5.3", - "regex-automata", - "regex-syntax", -] - [[package]] name = "fancy-regex" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "bit-set 0.8.0", + "bit-set", "regex-automata", "regex-syntax", ] @@ -504,15 +419,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -614,12 +520,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "globset" version = "0.4.15" @@ -645,9 +545,9 @@ version = "0.0.0" dependencies = [ "heroku-nodejs-utils", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "opentelemetry 0.27.1", "serde", "test_support", @@ -660,9 +560,9 @@ name = "heroku-nodejs-engine-buildpack" version = "0.0.0" dependencies = [ "heroku-nodejs-utils", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "serde", "serde_json", "sha2", @@ -680,9 +580,9 @@ dependencies = [ "base64", "heroku-nodejs-utils", "hex", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "rand", "serde", "serde_json", @@ -699,9 +599,9 @@ version = "0.0.0" dependencies = [ "heroku-nodejs-utils", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "serde", "test_support", "toml", @@ -713,12 +613,12 @@ name = "heroku-nodejs-utils" version = "0.0.0" dependencies = [ "anyhow", + "bullet_stream", "chrono", - "commons", "indoc", "keep_a_changelog_file", - "libcnb-data 0.26.0", - "libherokubuildpack 0.26.0", + "libcnb-data", + "libherokubuildpack", "node-semver", "regex", "serde", @@ -738,9 +638,9 @@ version = "0.0.0" dependencies = [ "heroku-nodejs-utils", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "serde", "tempfile", "test_support", @@ -753,13 +653,13 @@ dependencies = [ name = "heroku-npm-engine-buildpack" version = "0.0.0" dependencies = [ - "commons", + "bullet_stream", "fun_run", "heroku-nodejs-utils", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", - "libherokubuildpack 0.26.0", + "libherokubuildpack", "serde", "serde_json", "test_support", @@ -770,11 +670,11 @@ dependencies = [ name = "heroku-npm-install-buildpack" version = "0.0.0" dependencies = [ - "commons", + "bullet_stream", "fun_run", "heroku-nodejs-utils", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", "serde", "serde_json", @@ -785,9 +685,9 @@ dependencies = [ name = "heroku-pnpm-engine-buildpack" version = "0.0.0" dependencies = [ - "commons", + "bullet_stream", "indoc", - "libcnb 0.26.0", + "libcnb", "libcnb-test", "test_support", ] @@ -1050,29 +950,15 @@ version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" -[[package]] -name = "libcnb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b3e7c4d57d10b3e2a76b15fb3ae98a56be73655325ae26723fcb6a4709fd64" -dependencies = [ - "libcnb-common 0.23.0", - "libcnb-data 0.23.0", - "libcnb-proc-macros 0.23.0", - "serde", - "thiserror 1.0.69", - "toml", -] - [[package]] name = "libcnb" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98756d5a203c14dc67adc4e0419e83ddacaef6ef6bd9ac198cf55e310509ac1f" dependencies = [ - "libcnb-common 0.26.0", - "libcnb-data 0.26.0", - "libcnb-proc-macros 0.26.0", + "libcnb-common", + "libcnb-data", + "libcnb-proc-macros", "opentelemetry 0.21.0", "opentelemetry-stdout", "opentelemetry_sdk", @@ -1081,17 +967,6 @@ dependencies = [ "toml", ] -[[package]] -name = "libcnb-common" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719c4b07c0d221587a49919308c88fa41e01256e533da643c6cde0a88840cccb" -dependencies = [ - "serde", - "thiserror 1.0.69", - "toml", -] - [[package]] name = "libcnb-common" version = "0.26.0" @@ -1103,28 +978,14 @@ dependencies = [ "toml", ] -[[package]] -name = "libcnb-data" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab235141d51d47ecffd1fc7a8efc2851063048ba9d4498963f1ad963c275eee" -dependencies = [ - "fancy-regex 0.13.0", - "libcnb-proc-macros 0.23.0", - "serde", - "thiserror 1.0.69", - "toml", - "uriparse", -] - [[package]] name = "libcnb-data" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3938870d11721a0d3466b7daefd09362b6851a3277b3930fc41e449185d7c553" dependencies = [ - "fancy-regex 0.14.0", - "libcnb-proc-macros 0.26.0", + "fancy-regex", + "libcnb-proc-macros", "serde", "thiserror 2.0.9", "toml", @@ -1140,26 +1001,14 @@ dependencies = [ "cargo_metadata", "ignore", "indoc", - "libcnb-common 0.26.0", - "libcnb-data 0.26.0", + "libcnb-common", + "libcnb-data", "petgraph", "thiserror 2.0.9", "uriparse", "which", ] -[[package]] -name = "libcnb-proc-macros" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8d7feb9d84bdd3b9f6ff892508f78e1a75c39a6ab8f247f6106bd2d9bae489" -dependencies = [ - "cargo_metadata", - "fancy-regex 0.13.0", - "quote", - "syn", -] - [[package]] name = "libcnb-proc-macros" version = "0.26.0" @@ -1167,7 +1016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be015e5279a2848ef937cc797cecf6432354b6a74c72530b0b07e6e36800f65" dependencies = [ "cargo_metadata", - "fancy-regex 0.14.0", + "fancy-regex", "quote", "syn", ] @@ -1180,23 +1029,14 @@ checksum = "b46991106f2bbe68e141ea3fbc02452493dc52677c39ede22a5918fc130e5c23" dependencies = [ "fastrand", "fs_extra", - "libcnb-common 0.26.0", - "libcnb-data 0.26.0", + "libcnb-common", + "libcnb-data", "libcnb-package", "regex", "tempfile", "thiserror 2.0.9", ] -[[package]] -name = "libherokubuildpack" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146f61983fd384cb5ab5373acdd8f53fcb4b27ecb200435a6bfb6a70b421bc9d" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "libherokubuildpack" version = "0.26.0" @@ -1205,7 +1045,7 @@ checksum = "45070d23cda8614758579eddae211a6267e86bcef8ab75a1cd239eac45a6778d" dependencies = [ "flate2", "hex", - "libcnb 0.26.0", + "libcnb", "pathdiff", "serde", "sha2", @@ -1823,7 +1663,7 @@ name = "test_support" version = "0.0.0" dependencies = [ "bon", - "libcnb 0.26.0", + "libcnb", "libcnb-test", "serde_json", "tempfile", @@ -1954,12 +1794,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" -[[package]] -name = "unicode-xid" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" - [[package]] name = "untrusted" version = "0.9.0" @@ -2017,12 +1851,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/buildpacks/nodejs-npm-engine/CHANGELOG.md b/buildpacks/nodejs-npm-engine/CHANGELOG.md index 7c1450cb..b718cf65 100644 --- a/buildpacks/nodejs-npm-engine/CHANGELOG.md +++ b/buildpacks/nodejs-npm-engine/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + - Added npm version 11.0.0. +- Replaced `commons` output module with `bullet_stream`. ([#993](https://github.com/heroku/buildpacks-nodejs/pull/993)) + ## [3.4.0] - 2024-12-13 - No changes. diff --git a/buildpacks/nodejs-npm-engine/Cargo.toml b/buildpacks/nodejs-npm-engine/Cargo.toml index 79908618..3c73372b 100644 --- a/buildpacks/nodejs-npm-engine/Cargo.toml +++ b/buildpacks/nodejs-npm-engine/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true workspace = true [dependencies] -commons = { git = "https://github.com/heroku/buildpacks-ruby", branch = "main" } +bullet_stream = "0.3" fun_run = "0.2" heroku-nodejs-utils.workspace = true indoc = "2" diff --git a/buildpacks/nodejs-npm-engine/src/errors.rs b/buildpacks/nodejs-npm-engine/src/errors.rs index 7ac7243c..592f9064 100644 --- a/buildpacks/nodejs-npm-engine/src/errors.rs +++ b/buildpacks/nodejs-npm-engine/src/errors.rs @@ -1,14 +1,13 @@ use crate::install_npm::NpmEngineLayerError; use crate::BUILDPACK_NAME; use crate::{node, npm}; -use commons::output::build_log::{BuildLog, Logger, StartedLogger}; -use commons::output::fmt; -use commons::output::fmt::DEBUG_INFO; +use bullet_stream::state::Bullet; +use bullet_stream::{style, Print}; use heroku_nodejs_utils::package_json::PackageJsonError; use heroku_nodejs_utils::vrs::Requirement; use indoc::formatdoc; use std::fmt::Display; -use std::io::stdout; +use std::io::{stdout, Stdout}; const USE_DEBUG_INFORMATION_AND_RETRY_BUILD: &str = "\ Use the debug information above to troubleshoot and retry your build."; @@ -29,7 +28,7 @@ pub(crate) enum NpmEngineBuildpackError { } pub(crate) fn on_error(error: libcnb::Error) { - let logger = BuildLog::new(stdout()).without_buildpack_name(); + let logger = Print::new(stdout()).without_header(); match error { libcnb::Error::BuildpackError(buildpack_error) => { on_buildpack_error(buildpack_error, logger); @@ -38,7 +37,7 @@ pub(crate) fn on_error(error: libcnb::Error) { } } -fn on_buildpack_error(error: NpmEngineBuildpackError, logger: Box) { +fn on_buildpack_error(error: NpmEngineBuildpackError, logger: Print>) { match error { NpmEngineBuildpackError::PackageJson(e) => on_package_json_error(e, logger), NpmEngineBuildpackError::MissingNpmEngineRequirement => { @@ -54,12 +53,11 @@ fn on_buildpack_error(error: NpmEngineBuildpackError, logger: Box) { +fn on_package_json_error(error: PackageJsonError, logger: Print>) { match error { PackageJsonError::AccessError(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Error reading {package_json}. This buildpack requires {package_json} to complete the build but the file can’t be read. @@ -67,12 +65,11 @@ fn on_package_json_error(error: PackageJsonError, logger: Box {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", package_json = fmt::value("package.json")}); + ", package_json = style::value("package.json")}); } PackageJsonError::ParseError(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Error reading {package_json}. This buildpack requires {package_json} to complete the build but the file \ @@ -81,13 +78,13 @@ fn on_package_json_error(error: PackageJsonError, logger: Box {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", package_json = fmt::value("package.json"), npm_install = fmt::value("npm install") }); + ", package_json = style::value("package.json"), npm_install = style::value("npm install") }); } } } -fn on_missing_npm_engine_requirement_error(logger: Box) { - logger.announce().error(&formatdoc! {" +fn on_missing_npm_engine_requirement_error(logger: Print>) { + logger.error(formatdoc! {" Missing {engines_key} key in {package_json}. This buildpack requires the `engines.npm` key to determine which engine versions to install. @@ -95,13 +92,11 @@ fn on_missing_npm_engine_requirement_error(logger: Box) { Retry your build. {SUBMIT_AN_ISSUE} - ", engines_key = fmt::value("engines.npm"), package_json = fmt::value("package.json") }); + ", engines_key = style::value("engines.npm"), package_json = style::value("package.json") }); } -fn on_inventory_parse_error(error: &toml::de::Error, logger: Box) { - print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" +fn on_inventory_parse_error(error: &toml::de::Error, logger: Print>) { + print_error_details(logger, &error).error(formatdoc! {" Failed to load available {npm} versions. An unexpected error occurred while loading the available {npm} versions. @@ -109,11 +104,11 @@ fn on_inventory_parse_error(error: &toml::de::Error, logger: Box) { - logger.announce().error(&formatdoc! {" +fn on_npm_version_resolve_error(requirement: &Requirement, logger: Print>) { + logger.error(formatdoc! {" Error resolving requested {npm} version {requested_version}. Can’t find the `npm` version that matches the requested version declared in {package_json} ({requested_version}). @@ -128,19 +123,18 @@ fn on_npm_version_resolve_error(requirement: &Requirement, logger: Box) { +fn on_npm_engine_layer_error(error: NpmEngineLayerError, logger: Print>) { match error { NpmEngineLayerError::Download(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Failed to download {npm}. An unexpected error occurred while downloading the {npm} package. This error can occur due to an unstable network connection. @@ -148,61 +142,52 @@ fn on_npm_engine_layer_error(error: NpmEngineLayerError, logger: Box { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" An unexpected error occurred while opening the downloaded {npm} package file. {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm") }); + ", npm = style::value("npm") }); } NpmEngineLayerError::DecompressTarball(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" An unexpected error occurred while extracting the contents of the downloaded {npm} package file. {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm") }); + ", npm = style::value("npm") }); } NpmEngineLayerError::RemoveExistingNpmInstall(e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" An unexpected error occurred while removing the existing {npm} installation. {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm") }); + ", npm = style::value("npm") }); } NpmEngineLayerError::InstallNpm(e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" An unexpected error occurred while installing the downloaded {npm} package. {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm") }); + ", npm = style::value("npm") }); } } } -fn on_node_version_error(error: node::VersionError, logger: Box) { +fn on_node_version_error(error: node::VersionError, logger: Print>) { match error { node::VersionError::Command(e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to determine {node} version information. An unexpected error occurred while executing {node_version}. @@ -210,28 +195,24 @@ fn on_node_version_error(error: node::VersionError, logger: Box { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to parse {node} version information. An unexpected error occurred while parsing version information from {output}. {SUBMIT_AN_ISSUE} - ", node = fmt::value("Node"), output = fmt::value(stdout) }); + ", node = style::value("Node"), output = style::value(stdout) }); } } } -fn on_npm_version_error(error: npm::VersionError, logger: Box) { +fn on_npm_version_error(error: npm::VersionError, logger: Print>) { match error { npm::VersionError::Command(e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to determine {npm} version information. An unexpected error occurred while executing {npm_version}. @@ -239,29 +220,26 @@ fn on_npm_version_error(error: npm::VersionError, logger: Box {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm"), npm_version = fmt::value(e.name())}); + ", npm = style::value("npm"), npm_version = style::value(e.name())}); } npm::VersionError::Parse(stdout, e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to parse {npm} version information. An unexpected error occurred while parsing version information from {output}. {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm"), output = fmt::value(stdout) }); + ", npm = style::value("npm"), output = style::value(stdout) }); } } } fn on_framework_error( error: &libcnb::Error, - logger: Box, + logger: Print>, ) { print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" {buildpack_name} internal error. The framework used by this buildpack encountered an unexpected error. @@ -273,15 +251,15 @@ fn on_framework_error( the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository \ and include the details. - ", buildpack_name = fmt::value(BUILDPACK_NAME) }); + ", buildpack_name = style::value(BUILDPACK_NAME) }); } fn print_error_details( - logger: Box, + logger: Print>, error: &impl Display, -) -> Box { +) -> Print> { logger - .section(DEBUG_INFO) - .step(&error.to_string()) - .end_section() + .bullet(style::important("DEBUG INFO:")) + .sub_bullet(error.to_string()) + .done() } diff --git a/buildpacks/nodejs-npm-engine/src/install_npm.rs b/buildpacks/nodejs-npm-engine/src/install_npm.rs index 9939f379..2ae79d9e 100644 --- a/buildpacks/nodejs-npm-engine/src/install_npm.rs +++ b/buildpacks/nodejs-npm-engine/src/install_npm.rs @@ -1,10 +1,5 @@ -use std::fs::File; -use std::path::Path; -use std::process::Command; - -use commons::output::fmt; -use commons::output::interface::SectionLogger; -use commons::output::section_log::{log_step, log_step_timed}; +use bullet_stream::state::SubBullet; +use bullet_stream::{style, Print}; use fun_run::{CommandWithName, NamedOutput}; use libcnb::build::BuildContext; use libcnb::data::layer_name; @@ -14,6 +9,10 @@ use libcnb::layer::{ use libherokubuildpack::download::{download_file, DownloadError}; use libherokubuildpack::tar::decompress_tarball; use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::Stdout; +use std::path::Path; +use std::process::Command; use heroku_nodejs_utils::inv::Release; use heroku_nodejs_utils::vrs::Version; @@ -25,8 +24,8 @@ pub(crate) fn install_npm( context: &BuildContext, npm_release: &Release, node_version: &Version, - _logger: &dyn SectionLogger, -) -> Result<(), libcnb::Error> { + mut logger: Print>, +) -> Result>, libcnb::Error> { let new_metadata = NpmEngineLayerMetadata { node_version: node_version.to_string(), npm_version: npm_release.version.to_string(), @@ -56,13 +55,13 @@ pub(crate) fn install_npm( match npm_engine_layer.state { LayerState::Restored { .. } => { - log_step("Using cached npm"); + logger = logger.sub_bullet("Using cached npm"); } LayerState::Empty { ref cause } => { npm_engine_layer.write_metadata(new_metadata)?; if let EmptyLayerCause::RestoredLayerAction { cause } = cause { - log_step(format!( + logger = logger.sub_bullet(format!( "Invalidating cached npm ({} changed)", cause.join(", ") )); @@ -73,37 +72,42 @@ pub(crate) fn install_npm( // this install process is generalized from the npm install script at: // https://www.npmjs.com/install.sh - download_and_unpack_release( + logger = download_and_unpack_release( &npm_release.url, &downloaded_package_path, &npm_engine_layer.path(), + logger, )?; - remove_existing_npm_installation(&npm_cli_script)?; - install_npm_package(&npm_cli_script, &downloaded_package_path)?; + logger = remove_existing_npm_installation(&npm_cli_script, logger)?; + logger = install_npm_package(&npm_cli_script, &downloaded_package_path, logger)?; } } - Ok(()) + Ok(logger) } fn download_and_unpack_release( download_from: &String, download_to: &Path, unpack_into: &Path, -) -> Result<(), NpmEngineLayerError> { - log_step_timed(format!("Downloading {}", fmt::value(download_from)), || { - download_file(download_from, download_to) - .map_err(NpmEngineLayerError::Download) - .and_then(|()| File::open(download_to).map_err(NpmEngineLayerError::OpenTarball)) - .and_then(|mut npm_tgz_file| { - decompress_tarball(&mut npm_tgz_file, unpack_into) - .map_err(NpmEngineLayerError::DecompressTarball) - }) - }) + logger: Print>, +) -> Result>, NpmEngineLayerError> { + let timer = logger.start_timer(format!("Downloading {}", style::value(download_from))); + download_file(download_from, download_to) + .map_err(NpmEngineLayerError::Download) + .and_then(|()| File::open(download_to).map_err(NpmEngineLayerError::OpenTarball)) + .and_then(|mut npm_tgz_file| { + decompress_tarball(&mut npm_tgz_file, unpack_into) + .map_err(NpmEngineLayerError::DecompressTarball) + })?; + Ok(timer.done()) } -fn remove_existing_npm_installation(npm_cli_script: &Path) -> Result<(), NpmEngineLayerError> { - log_step("Removing npm bundled with Node.js"); +fn remove_existing_npm_installation( + npm_cli_script: &Path, + mut logger: Print>, +) -> Result>, NpmEngineLayerError> { + logger = logger.sub_bullet("Removing npm bundled with Node.js"); Command::new("node") .args([ &npm_cli_script.to_string_lossy(), @@ -114,11 +118,15 @@ fn remove_existing_npm_installation(npm_cli_script: &Path) -> Result<(), NpmEngi ]) .named_output() .map_err(NpmEngineLayerError::RemoveExistingNpmInstall) - .map(|_| ()) + .map(|_| logger) } -fn install_npm_package(npm_cli_script: &Path, package: &Path) -> Result<(), NpmEngineLayerError> { - log_step("Installing requested npm"); +fn install_npm_package( + npm_cli_script: &Path, + package: &Path, + mut logger: Print>, +) -> Result>, NpmEngineLayerError> { + logger = logger.sub_bullet("Installing requested npm"); Command::new("node") .args([ &npm_cli_script.to_string_lossy(), @@ -129,7 +137,7 @@ fn install_npm_package(npm_cli_script: &Path, package: &Path) -> Result<(), NpmE .named_output() .and_then(NamedOutput::nonzero_captured) .map_err(NpmEngineLayerError::InstallNpm) - .map(|_| ()) + .map(|_| logger) } fn changed_metadata_fields( diff --git a/buildpacks/nodejs-npm-engine/src/main.rs b/buildpacks/nodejs-npm-engine/src/main.rs index 3bbc8449..81a43a25 100644 --- a/buildpacks/nodejs-npm-engine/src/main.rs +++ b/buildpacks/nodejs-npm-engine/src/main.rs @@ -5,9 +5,8 @@ mod npm; use crate::errors::NpmEngineBuildpackError; use crate::install_npm::install_npm; -use commons::output::build_log::{BuildLog, Logger, SectionLogger}; -use commons::output::fmt; -use commons::output::section_log::log_step; +use bullet_stream::state::SubBullet; +use bullet_stream::{style, Print}; use fun_run::CommandWithName; use heroku_nodejs_utils::inv::{Inventory, Release}; use heroku_nodejs_utils::package_json::PackageJson; @@ -21,7 +20,7 @@ use libcnb::{buildpack_main, Buildpack, Env}; use libcnb_test as _; #[cfg(test)] use serde_json as _; -use std::io::stdout; +use std::io::{stdout, Stdout}; use std::path::Path; use std::process::Command; #[cfg(test)] @@ -63,7 +62,7 @@ impl Buildpack for NpmEngineBuildpack { } fn build(&self, context: BuildContext) -> libcnb::Result { - let mut logger = BuildLog::new(stdout()).buildpack_name(BUILDPACK_NAME); + let mut logger = Print::new(stdout()).h1(BUILDPACK_NAME); let env = Env::from_current(); let inventory: Inventory = toml::from_str(INVENTORY).map_err(NpmEngineBuildpackError::InventoryParse)?; @@ -71,14 +70,14 @@ impl Buildpack for NpmEngineBuildpack { read_requested_npm_version(&context.app_dir.join("package.json"))?; let node_version = get_node_version(&env)?; - let section = logger.section("Installing npm"); - let npm_release = - resolve_requested_npm_version(&requested_npm_version, &inventory, section.as_ref())?; - install_npm(&context, &npm_release, &node_version, section.as_ref())?; - log_installed_npm_version(&env, section.as_ref())?; - logger = section.end_section(); + let section = logger.bullet("Installing npm"); + let (npm_release, section) = + resolve_requested_npm_version(&requested_npm_version, &inventory, section)?; + let section = install_npm(&context, &npm_release, &node_version, section)?; + let section = log_installed_npm_version(&env, section)?; + logger = section.done(); - logger.finish_logging(); + logger.done(); BuildResultBuilder::new().build() } @@ -103,13 +102,13 @@ fn read_requested_npm_version( fn resolve_requested_npm_version( requested_version: &Requirement, inventory: &Inventory, - _section_logger: &dyn SectionLogger, -) -> Result { - log_step(format!( + mut section_logger: Print>, +) -> Result<(Release, Print>), NpmEngineBuildpackError> { + section_logger = section_logger.sub_bullet(format!( "Found {} version {} declared in {}", - fmt::value("engines.npm"), - fmt::value(requested_version.to_string()), - fmt::value("package.json") + style::value("engines.npm"), + style::value(requested_version.to_string()), + style::value("package.json") )); let npm_release = inventory @@ -119,13 +118,13 @@ fn resolve_requested_npm_version( ))? .to_owned(); - log_step(format!( + section_logger = section_logger.sub_bullet(format!( "Resolved version {} to {}", - fmt::value(requested_version.to_string()), - fmt::value(npm_release.version.to_string()) + style::value(requested_version.to_string()), + style::value(npm_release.version.to_string()) )); - Ok(npm_release) + Ok((npm_release, section_logger)) } fn get_node_version(env: &Env) -> Result { @@ -143,8 +142,8 @@ fn get_node_version(env: &Env) -> Result { fn log_installed_npm_version( env: &Env, - _section_logger: &dyn SectionLogger, -) -> Result<(), NpmEngineBuildpackError> { + section_logger: Print>, +) -> Result>, NpmEngineBuildpackError> { Command::from(npm::Version { env }) .named_output() .map_err(npm::VersionError::Command) @@ -156,10 +155,10 @@ fn log_installed_npm_version( }) .map_err(NpmEngineBuildpackError::NpmVersion) .map(|npm_version| { - log_step(format!( + section_logger.sub_bullet(format!( "Successfully installed {}", - fmt::value(format!("npm@{npm_version}")), - )); + style::value(format!("npm@{npm_version}")), + )) }) } diff --git a/buildpacks/nodejs-npm-install/CHANGELOG.md b/buildpacks/nodejs-npm-install/CHANGELOG.md index 80dfe20d..47eaabba 100644 --- a/buildpacks/nodejs-npm-install/CHANGELOG.md +++ b/buildpacks/nodejs-npm-install/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Replaced `commons` output module with `bullet_stream` ([#993](https://github.com/heroku/buildpacks-nodejs/pull/993)) + ## [3.4.0] - 2024-12-13 ### Changed diff --git a/buildpacks/nodejs-npm-install/Cargo.toml b/buildpacks/nodejs-npm-install/Cargo.toml index 0ea733b3..16a47d3c 100644 --- a/buildpacks/nodejs-npm-install/Cargo.toml +++ b/buildpacks/nodejs-npm-install/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true workspace = true [dependencies] -commons = { git = "https://github.com/heroku/buildpacks-ruby", branch = "main" } +bullet_stream = "0.3" fun_run = "0.2" heroku-nodejs-utils.workspace = true indoc = "2" diff --git a/buildpacks/nodejs-npm-install/src/configure_npm_cache_directory.rs b/buildpacks/nodejs-npm-install/src/configure_npm_cache_directory.rs index 433a0ee0..4bc4d118 100644 --- a/buildpacks/nodejs-npm-install/src/configure_npm_cache_directory.rs +++ b/buildpacks/nodejs-npm-install/src/configure_npm_cache_directory.rs @@ -1,5 +1,5 @@ -use commons::output::build_log::SectionLogger; -use commons::output::section_log::log_step; +use bullet_stream::state::SubBullet; +use bullet_stream::Print; use fun_run::CommandWithName; use libcnb::build::BuildContext; use libcnb::data::layer_name; @@ -8,6 +8,7 @@ use libcnb::layer::{ }; use libcnb::Env; use serde::{Deserialize, Serialize}; +use std::io::Stdout; use crate::errors::NpmInstallBuildpackError; use crate::{npm, NpmInstallBuildpack}; @@ -15,8 +16,8 @@ use crate::{npm, NpmInstallBuildpack}; pub(crate) fn configure_npm_cache_directory( context: &BuildContext, env: &Env, - _section_logger: &dyn SectionLogger, -) -> Result<(), libcnb::Error> { + mut section_logger: Print>, +) -> Result>, libcnb::Error> { let new_metadata = NpmCacheLayerMetadata { layer_version: LAYER_VERSION.to_string(), }; @@ -39,18 +40,18 @@ pub(crate) fn configure_npm_cache_directory( match npm_cache_layer.state { LayerState::Restored { .. } => { - log_step("Restoring npm cache"); + section_logger = section_logger.sub_bullet("Restoring npm cache"); } LayerState::Empty { cause } => { if let EmptyLayerCause::RestoredLayerAction { .. } = cause { - log_step("Restoring npm cache"); + section_logger = section_logger.sub_bullet("Restoring npm cache"); } - log_step("Creating npm cache"); + section_logger = section_logger.sub_bullet("Creating npm cache"); npm_cache_layer.write_metadata(new_metadata)?; } } - log_step("Configuring npm cache directory"); + section_logger = section_logger.sub_bullet("Configuring npm cache directory"); npm::SetCacheConfig { env, cache_dir: &npm_cache_layer.path(), @@ -59,7 +60,7 @@ pub(crate) fn configure_npm_cache_directory( .named_output() .map_err(NpmInstallBuildpackError::NpmSetCacheDir)?; - Ok(()) + Ok(section_logger) } const LAYER_VERSION: &str = "1"; diff --git a/buildpacks/nodejs-npm-install/src/errors.rs b/buildpacks/nodejs-npm-install/src/errors.rs index a717d62f..b6e60047 100644 --- a/buildpacks/nodejs-npm-install/src/errors.rs +++ b/buildpacks/nodejs-npm-install/src/errors.rs @@ -1,8 +1,7 @@ use crate::npm; use crate::BUILDPACK_NAME; -use commons::output::build_log::{BuildLog, Logger, StartedLogger}; -use commons::output::fmt; -use commons::output::fmt::DEBUG_INFO; +use bullet_stream::state::Bullet; +use bullet_stream::{style, Print}; use fun_run::CmdError; use heroku_nodejs_utils::application; use heroku_nodejs_utils::buildplan::{ @@ -12,7 +11,7 @@ use heroku_nodejs_utils::package_json::PackageJsonError; use indoc::formatdoc; use std::fmt::Display; use std::io; -use std::io::stdout; +use std::io::{stdout, Stdout}; const USE_DEBUG_INFORMATION_AND_RETRY_BUILD: &str = "\ Use the debug information above to troubleshoot and retry your build."; @@ -34,7 +33,7 @@ pub(crate) enum NpmInstallBuildpackError { } pub(crate) fn on_error(error: libcnb::Error) { - let logger = BuildLog::new(stdout()).without_buildpack_name(); + let logger = Print::new(stdout()).without_header(); match error { libcnb::Error::BuildpackError(buildpack_error) => { on_buildpack_error(buildpack_error, logger); @@ -43,7 +42,7 @@ pub(crate) fn on_error(error: libcnb::Error) { } } -fn on_buildpack_error(error: NpmInstallBuildpackError, logger: Box) { +fn on_buildpack_error(error: NpmInstallBuildpackError, logger: Print>) { match error { NpmInstallBuildpackError::Application(e) => on_application_error(&e, logger), NpmInstallBuildpackError::BuildScript(e) => on_build_script_error(&e, logger), @@ -60,11 +59,11 @@ fn on_buildpack_error(error: NpmInstallBuildpackError, logger: Box, + logger: Print>, ) { let NodeBuildScriptsMetadataError::InvalidEnabledValue(value) = error; let value_type = value.type_str(); - logger.announce().error(&formatdoc! { " + logger.error(formatdoc! { " A participating buildpack has set invalid `[requires.metadata]` for the build plan \ named `{NODE_BUILD_SCRIPTS_BUILD_PLAN_NAME}`. @@ -78,12 +77,11 @@ fn on_node_build_scripts_metadata_error( "}); } -fn on_package_json_error(error: PackageJsonError, logger: Box) { +fn on_package_json_error(error: PackageJsonError, logger: Print>) { match error { PackageJsonError::AccessError(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Error reading {package_json}. This buildpack requires {package_json} to complete the build but the file can’t be read. @@ -91,12 +89,11 @@ fn on_package_json_error(error: PackageJsonError, logger: Box {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", package_json = fmt::value("package.json") }); + ", package_json = style::value("package.json") }); } PackageJsonError::ParseError(e) => { print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Error reading {package_json}. This buildpack requires {package_json} to complete the build but the file \ @@ -105,15 +102,13 @@ fn on_package_json_error(error: PackageJsonError, logger: Box {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", package_json = fmt::value("package.json"), npm_install = fmt::value("npm install") }); + ", package_json = style::value("package.json"), npm_install = style::value("npm install") }); } } } -fn on_set_cache_dir_error(error: &CmdError, logger: Box) { - print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" +fn on_set_cache_dir_error(error: &CmdError, logger: Print>) { + print_error_details(logger, &error).error(formatdoc! {" Failed to set the {npm} cache directory. An unexpected error occurred while setting the {npm} cache directory. @@ -121,40 +116,35 @@ fn on_set_cache_dir_error(error: &CmdError, logger: Box) { {USE_DEBUG_INFORMATION_AND_RETRY_BUILD} {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm") }); + ", npm = style::value("npm") }); } -fn on_npm_version_error(error: npm::VersionError, logger: Box) { +fn on_npm_version_error(error: npm::VersionError, logger: Print>) { match error { npm::VersionError::Command(e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to determine {npm} version information. An unexpected error occurred while executing {npm_version}. {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm"), npm_version = fmt::value(e.name()) }); + ", npm = style::value("npm"), npm_version = style::value(e.name()) }); } npm::VersionError::Parse(stdout, e) => { - print_error_details(logger, &e) - .announce() - .error(&formatdoc! {" + print_error_details(logger, &e).error(formatdoc! {" Failed to parse {npm} version information. An unexpected error occurred while parsing version information from {output}. {SUBMIT_AN_ISSUE} - ", npm = fmt::value("npm"), output = fmt::value(stdout) }); + ", npm = style::value("npm"), output = style::value(stdout) }); } } } -fn on_npm_install_error(error: &CmdError, logger: Box) { +fn on_npm_install_error(error: &CmdError, logger: Print>) { print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Failed to install Node modules. The {buildpack_name} uses the command {npm_install} to install your Node modules. This command \ @@ -164,13 +154,12 @@ fn on_npm_install_error(error: &CmdError, logger: Box) { without error (exit status = 0) and retry your build. If that doesn’t help, check the status of the upstream Node module repository service at https://status.npmjs.org/. - ", npm_install = fmt::value(error.name()), buildpack_name = fmt::value(BUILDPACK_NAME) }); + ", npm_install = style::value(error.name()), buildpack_name = style::value(BUILDPACK_NAME) }); } -fn on_build_script_error(error: &CmdError, logger: Box) { +fn on_build_script_error(error: &CmdError, logger: Print>) { print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" Failed to execute build script. The {buildpack_name} allows customization of the build process by executing the following scripts \ @@ -183,38 +172,35 @@ fn on_build_script_error(error: &CmdError, logger: Box) { Ensure that this command runs locally without error and retry your build. ", - build_script = fmt::value(error.name()), - buildpack_name = fmt::value(BUILDPACK_NAME), - package_json = fmt::value("package.json"), - heroku_prebuild = fmt::value("heroku-prebuild"), - heroku_build = fmt::value("heroku-build"), - build = fmt::value("build"), - heroku_postbuild = fmt::value("heroku-postbuild"), + build_script = style::value(error.name()), + buildpack_name = style::value(BUILDPACK_NAME), + package_json = style::value("package.json"), + heroku_prebuild = style::value("heroku-prebuild"), + heroku_build = style::value("heroku-build"), + build = style::value("build"), + heroku_postbuild = style::value("heroku-postbuild"), }); } -fn on_application_error(error: &application::Error, logger: Box) { - logger.announce().error(&error.to_string()); +fn on_application_error(error: &application::Error, logger: Print>) { + logger.error(error.to_string()); } -fn on_detect_error(error: &io::Error, logger: Box) { - print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" +fn on_detect_error(error: &io::Error, logger: Print>) { + print_error_details(logger, &error).error(formatdoc! {" Unable to complete buildpack detection. An unexpected error occurred while determining if the {buildpack_name} should be \ run for this application. See the log output above for more information. - ", buildpack_name = fmt::value(BUILDPACK_NAME) }); + ", buildpack_name = style::value(BUILDPACK_NAME) }); } fn on_framework_error( error: &libcnb::Error, - logger: Box, + logger: Print>, ) { print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" {buildpack_name} internal error. The framework used by this buildpack encountered an unexpected error. @@ -223,15 +209,15 @@ fn on_framework_error( status.heroku.com for any ongoing incidents. After all incidents resolve, retry your build. {SUBMIT_AN_ISSUE} - ", buildpack_name = fmt::value(BUILDPACK_NAME) }); + ", buildpack_name = style::value(BUILDPACK_NAME) }); } fn print_error_details( - logger: Box, + logger: Print>, error: &impl Display, -) -> Box { +) -> Print> { logger - .section(DEBUG_INFO) - .step(&error.to_string()) - .end_section() + .bullet(style::important("DEBUG INFO:")) + .sub_bullet(error.to_string()) + .done() } diff --git a/buildpacks/nodejs-npm-install/src/main.rs b/buildpacks/nodejs-npm-install/src/main.rs index 127fb22e..1ff9476d 100644 --- a/buildpacks/nodejs-npm-install/src/main.rs +++ b/buildpacks/nodejs-npm-install/src/main.rs @@ -6,10 +6,8 @@ mod npm; use crate::configure_npm_cache_directory::configure_npm_cache_directory; use crate::configure_npm_runtime_env::configure_npm_runtime_env; use crate::errors::NpmInstallBuildpackError; -use commons::output::build_log::{BuildLog, Logger, SectionLogger}; -use commons::output::fmt; -use commons::output::section_log::{log_step, log_step_stream}; -use commons::output::warn_later::WarnGuard; +use bullet_stream::state::SubBullet; +use bullet_stream::{style, Print}; use fun_run::{CommandWithName, NamedOutput}; use heroku_nodejs_utils::application; use heroku_nodejs_utils::buildplan::{ @@ -30,7 +28,6 @@ use libcnb_test as _; #[cfg(test)] use serde_json as _; use std::io::{stdout, Stdout}; -use std::path::Path; #[cfg(test)] use test_support as _; @@ -73,8 +70,7 @@ impl Buildpack for NpmInstallBuildpack { } fn build(&self, context: BuildContext) -> libcnb::Result { - let logger = BuildLog::new(stdout()).buildpack_name(BUILDPACK_NAME); - let warn_later = WarnGuard::new(stdout()); + let logger = Print::new(stdout()).h1(BUILDPACK_NAME); let env = Env::from_current(); let app_dir = &context.app_dir; let package_json = PackageJson::read(app_dir.join("package.json")) @@ -82,31 +78,34 @@ impl Buildpack for NpmInstallBuildpack { let node_build_scripts_metadata = read_node_build_scripts_metadata(&context.buildpack_plan) .map_err(NpmInstallBuildpackError::NodeBuildScriptsMetadata)?; - run_application_checks(app_dir, &warn_later)?; + application::check_for_singular_lockfile(app_dir) + .map_err(NpmInstallBuildpackError::Application)?; - let section = logger.section("Installing node modules"); - log_npm_version(&env, section.as_ref())?; - configure_npm_cache_directory(&context, &env, section.as_ref())?; - run_npm_install(&env, section.as_ref())?; - let logger = section.end_section(); + let section = logger.bullet("Installing node modules"); + let section = log_npm_version(&env, section)?; + let section = configure_npm_cache_directory(&context, &env, section)?; + let section = run_npm_install(&env, section)?; + let logger = section.done(); - let section = logger.section("Running scripts"); - run_build_scripts( - &package_json, - &node_build_scripts_metadata, - &env, - section.as_ref(), - )?; - let logger = section.end_section(); + let section = logger.bullet("Running scripts"); + let section = + run_build_scripts(&package_json, &node_build_scripts_metadata, &env, section)?; + let logger = section.done(); - let section = logger.section("Configuring default processes"); - let build_result = configure_default_processes(&context, &package_json, section.as_ref()); - let logger = section.end_section(); + let section = logger.bullet("Configuring default processes"); + let (build_result, section) = configure_default_processes(&context, &package_json, section); + let logger = section.done(); configure_npm_runtime_env(&context)?; - logger.finish_logging(); - warn_later.warn_now(); + let logger = + if let Some(prebuilt_modules_warning) = application::warn_prebuilt_modules(app_dir) { + logger.warning(prebuilt_modules_warning) + } else { + logger + }; + + logger.done(); build_result } @@ -115,18 +114,10 @@ impl Buildpack for NpmInstallBuildpack { } } -fn run_application_checks( - app_dir: &Path, - warn_later: &WarnGuard, -) -> Result<(), NpmInstallBuildpackError> { - application::warn_prebuilt_modules(app_dir, warn_later); - application::check_for_singular_lockfile(app_dir).map_err(NpmInstallBuildpackError::Application) -} - fn log_npm_version( env: &Env, - _section_logger: &dyn SectionLogger, -) -> Result<(), NpmInstallBuildpackError> { + section_logger: Print>, +) -> Result>, NpmInstallBuildpackError> { npm::Version { env } .into_command() .named_output() @@ -140,90 +131,100 @@ fn log_npm_version( }) .map_err(NpmInstallBuildpackError::NpmVersion) .map(|version| { - log_step(format!( + section_logger.sub_bullet(format!( "Using npm version {}", - fmt::value(version.to_string()) - )); + style::value(version.to_string()) + )) }) } fn run_npm_install( env: &Env, - _section_logger: &dyn SectionLogger, -) -> Result<(), NpmInstallBuildpackError> { + mut section_logger: Print>, +) -> Result>, NpmInstallBuildpackError> { let mut npm_install = npm::Install { env }.into_command(); - log_step_stream( - format!("Running {}", fmt::command(npm_install.name())), - |stream| { - npm_install - .stream_output(stream.io(), stream.io()) - .and_then(NamedOutput::nonzero_captured) - .map_err(NpmInstallBuildpackError::NpmInstall) - }, - )?; - Ok(()) + section_logger + .stream_with( + format!("Running {}", style::command(npm_install.name())), + |stdout, stderr| { + npm_install + .stream_output(stdout, stderr) + .and_then(NamedOutput::nonzero_captured) + .map_err(NpmInstallBuildpackError::NpmInstall) + }, + ) + .map(|_| section_logger) } fn run_build_scripts( package_json: &PackageJson, node_build_scripts_metadata: &NodeBuildScriptsMetadata, env: &Env, - _section_logger: &dyn SectionLogger, -) -> Result<(), NpmInstallBuildpackError> { + mut section_logger: Print>, +) -> Result>, NpmInstallBuildpackError> { let build_scripts = package_json.build_scripts(); if build_scripts.is_empty() { - log_step("No build scripts found"); + section_logger = section_logger.sub_bullet("No build scripts found"); } else { for script in build_scripts { if let Some(false) = node_build_scripts_metadata.enabled { - log_step(format!( + section_logger = section_logger.sub_bullet(format!( "Not running {} as it was disabled by a participating buildpack", - fmt::value(script) + style::value(script) )); } else { let mut npm_run = npm::RunScript { env, script }.into_command(); - log_step_stream( - format!("Running {}", fmt::command(npm_run.name())), - |stream| { + section_logger.stream_with( + format!("Running {}", style::command(npm_run.name())), + |stdout, stderr| { npm_run - .stream_output(stream.io(), stream.io()) + .stream_output(stdout, stderr) .and_then(NamedOutput::nonzero_captured) .map_err(NpmInstallBuildpackError::BuildScript) }, )?; } } - }; - Ok(()) + } + Ok(section_logger) } fn configure_default_processes( context: &BuildContext, package_json: &PackageJson, - _section_logger: &dyn SectionLogger, -) -> Result> { + section_logger: Print>, +) -> ( + Result>, + Print>, +) { if context.app_dir.join("Procfile").exists() { - log_step("Skipping default web process (Procfile detected)"); - BuildResultBuilder::new().build() + ( + BuildResultBuilder::new().build(), + section_logger.sub_bullet("Skipping default web process (Procfile detected)"), + ) } else if package_json.has_start_script() { - log_step(format!( - "Adding default web process for {}", - fmt::value("npm start") - )); - BuildResultBuilder::new() - .launch( - LaunchBuilder::new() - .process( - ProcessBuilder::new(process_type!("web"), ["npm", "start"]) - .default(true) - .build(), - ) - .build(), - ) - .build() + ( + BuildResultBuilder::new() + .launch( + LaunchBuilder::new() + .process( + ProcessBuilder::new(process_type!("web"), ["npm", "start"]) + .default(true) + .build(), + ) + .build(), + ) + .build(), + section_logger.sub_bullet(format!( + "Adding default web process for {}", + style::value("npm start") + )), + ) } else { - log_step("Skipping default web process (no start script defined)"); - BuildResultBuilder::new().build() + ( + BuildResultBuilder::new().build(), + section_logger.sub_bullet("Skipping default web process (no start script defined)"), + ) } } diff --git a/buildpacks/nodejs-pnpm-engine/CHANGELOG.md b/buildpacks/nodejs-pnpm-engine/CHANGELOG.md index 1807b0b7..e0fca299 100644 --- a/buildpacks/nodejs-pnpm-engine/CHANGELOG.md +++ b/buildpacks/nodejs-pnpm-engine/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Replaced `commons` output module with `bullet_stream` ([#993](https://github.com/heroku/buildpacks-nodejs/pull/993)) + ## [3.4.0] - 2024-12-13 - No changes. diff --git a/buildpacks/nodejs-pnpm-engine/Cargo.toml b/buildpacks/nodejs-pnpm-engine/Cargo.toml index 5fcce9cf..402d601a 100644 --- a/buildpacks/nodejs-pnpm-engine/Cargo.toml +++ b/buildpacks/nodejs-pnpm-engine/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true workspace = true [dependencies] -commons = { git = "https://github.com/heroku/buildpacks-ruby", branch = "main" } +bullet_stream = "0.3" indoc = "2" libcnb = { version = "=0.26.0", features = ["trace"] } diff --git a/buildpacks/nodejs-pnpm-engine/src/errors.rs b/buildpacks/nodejs-pnpm-engine/src/errors.rs index 8323eae4..44489896 100644 --- a/buildpacks/nodejs-pnpm-engine/src/errors.rs +++ b/buildpacks/nodejs-pnpm-engine/src/errors.rs @@ -1,10 +1,9 @@ use crate::BUILDPACK_NAME; -use commons::output::build_log::{BuildLog, Logger, StartedLogger}; -use commons::output::fmt; -use commons::output::fmt::DEBUG_INFO; +use bullet_stream::state::Bullet; +use bullet_stream::{style, Print}; use indoc::formatdoc; use std::fmt::Display; -use std::io::stdout; +use std::io::{stdout, Stdout}; #[derive(Debug, Copy, Clone)] pub(crate) enum PnpmEngineBuildpackError { @@ -12,7 +11,7 @@ pub(crate) enum PnpmEngineBuildpackError { } pub(crate) fn on_error(error: libcnb::Error) { - let logger = BuildLog::new(stdout()).without_buildpack_name(); + let logger = Print::new(stdout()).without_header(); match error { libcnb::Error::BuildpackError(buildpack_error) => { on_buildpack_error(buildpack_error, logger); @@ -21,12 +20,10 @@ pub(crate) fn on_error(error: libcnb::Error) { } } -fn on_buildpack_error(error: PnpmEngineBuildpackError, logger: Box) { +fn on_buildpack_error(error: PnpmEngineBuildpackError, logger: Print>) { match error { PnpmEngineBuildpackError::CorepackRequired => { - print_error_details(logger, &"Corepack Requirement Error") - .announce() - .error(&formatdoc! {" + print_error_details(logger, &"Corepack Requirement Error").error(formatdoc! {" A pnpm lockfile ({pnpm_lockfile}) was detected, but the version of {pnpm} to install could not be determined. @@ -42,24 +39,23 @@ fn on_buildpack_error(error: PnpmEngineBuildpackError, logger: Box, - logger: Box, + logger: Print>, ) { print_error_details(logger, &error) - .announce() - .error(&formatdoc! {" + .error(formatdoc! {" {buildpack_name} internal error. The framework used by this buildpack encountered an unexpected error. @@ -71,15 +67,15 @@ fn on_framework_error( the issue locally with a minimal example. Open an issue in the buildpack's GitHub repository \ and include the details. - ", buildpack_name = fmt::value(BUILDPACK_NAME) }); + ", buildpack_name = style::value(BUILDPACK_NAME) }); } fn print_error_details( - logger: Box, + logger: Print>, error: &impl Display, -) -> Box { +) -> Print> { logger - .section(DEBUG_INFO) - .step(&error.to_string()) - .end_section() + .bullet(style::important("DEBUG INFO:")) + .sub_bullet(error.to_string()) + .done() } diff --git a/common/nodejs-utils/Cargo.toml b/common/nodejs-utils/Cargo.toml index b1f92e20..983bcfe3 100644 --- a/common/nodejs-utils/Cargo.toml +++ b/common/nodejs-utils/Cargo.toml @@ -9,7 +9,7 @@ workspace = true [dependencies] anyhow = "1" chrono = { version = "0.4", default-features = false, features = ["serde"] } -commons = { git = "https://github.com/heroku/buildpacks-ruby", branch = "main" } +bullet_stream = "0.3" indoc = "2" keep_a_changelog_file = "0.1.0" libcnb-data = "=0.26.0" diff --git a/common/nodejs-utils/src/application.rs b/common/nodejs-utils/src/application.rs index 4aa6a097..c5816240 100644 --- a/common/nodejs-utils/src/application.rs +++ b/common/nodejs-utils/src/application.rs @@ -1,10 +1,7 @@ use crate::package_manager::PackageManager; -use commons::output::fmt; -use commons::output::section_log::log_warning_later; -use commons::output::warn_later::WarnGuard; +use bullet_stream::style; use indoc::{formatdoc, writedoc}; use std::fmt::{Display, Formatter}; -use std::io::Stdout; use std::path::Path; /// Checks for npm, Yarn, pnpm, and shrink-wrap lockfiles and raises an error if multiple are detected. @@ -33,16 +30,18 @@ pub fn check_for_singular_lockfile(app_dir: &Path) -> Result<(), Error> { /// Checks if the `node_modules` folder is present in the given directory which indicates that /// the application contains files that it shouldn't in its git repository. If this is the case, -/// a delayed warning will be published to the logger. To ensure the delayed warning is properly -/// displayed it should be used in conjunction with a [`WarnGuard`]. -pub fn warn_prebuilt_modules(app_dir: &Path, _warn_later: &WarnGuard) { +/// a warning message will be returned. +#[must_use] +pub fn warn_prebuilt_modules(app_dir: &Path) -> Option { if app_dir.join("node_modules").exists() { - log_warning_later(formatdoc! {" + Some(formatdoc! {" Warning: {node_modules} checked into source control Add these files and directories to {gitignore}. See the Dev Center for more info: https://devcenter.heroku.com/articles/node-best-practices#only-git-the-important-bits - ", node_modules = fmt::value("node_modules"), gitignore = fmt::value(".gitignore") }); + ", node_modules = style::value("node_modules"), gitignore = style::value(".gitignore") }) + } else { + None } } @@ -78,12 +77,12 @@ impl Display for Error { Ensure the resulting lockfile is committed to the repository, then try again. ", - npm_install = fmt::value("npm install"), - npm_lockfile = fmt::value(PackageManager::Npm.lockfile().to_string_lossy()), - yarn_install = fmt::value("yarn install"), - yarn_lockfile = fmt::value(PackageManager::Yarn.lockfile().to_string_lossy()), - pnpm_install = fmt::value("pnpm install"), - pnpm_lockfile = fmt::value(PackageManager::Pnpm.lockfile().to_string_lossy()), + npm_install = style::value("npm install"), + npm_lockfile = style::value(PackageManager::Npm.lockfile().to_string_lossy()), + yarn_install = style::value("yarn install"), + yarn_lockfile = style::value(PackageManager::Yarn.lockfile().to_string_lossy()), + pnpm_install = style::value("pnpm install"), + pnpm_lockfile = style::value(PackageManager::Pnpm.lockfile().to_string_lossy()), )?; Ok(()) } @@ -104,7 +103,7 @@ impl Display for Error { ")?; for package_manager in PackageManager::iterator() { - writedoc!(f, "- To use {} to install your application's dependencies please delete the following lockfiles:\n\n", fmt::value(package_manager.to_string()))?; + writedoc!(f, "- To use {} to install your application's dependencies please delete the following lockfiles:\n\n", style::value(package_manager.to_string()))?; for other_package_manager in PackageManager::iterator() { if package_manager != other_package_manager { let other_lockfile = other_package_manager @@ -132,10 +131,7 @@ impl Display for Error { #[cfg(test)] mod tests { - use crate::application::Error; - use crate::package_manager::PackageManager; - use commons::output::fmt; - use indoc::formatdoc; + use super::*; #[test] fn test_error_output_for_multiple_lockfiles() { @@ -169,7 +165,7 @@ mod tests { See the Knowledge Base for more info: https://help.heroku.com/0KU2EM53 Once your application has only one lockfile, commit the results to git and retry your build. - ", npm = fmt::value("npm"), pnpm = fmt::value("pnpm"), yarn = fmt::value("Yarn") } + ", npm = style::value("npm"), pnpm = style::value("pnpm"), yarn = style::value("Yarn") } ); } }