Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 8 additions & 0 deletions buildpacks/nodejs-function-invoker/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The minimum version of Node.js required in Functions is validated during the build.

### Changed

- Update function runtime to 0.14.3 ([#734](https://github.com/heroku/buildpacks-nodejs/pull/734))

## [2.3.0] - 2023-11-09

- No changes.
Expand Down
2 changes: 1 addition & 1 deletion buildpacks/nodejs-function-invoker/buildpack.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ id = "heroku-22"

[metadata.runtime]
package_name = "@heroku/sf-fx-runtime-nodejs"
package_version = "0.14.1"
package_version = "0.14.3"

[metadata.release]
image = { repository = "docker.io/heroku/buildpack-nodejs-function-invoker" }
43 changes: 42 additions & 1 deletion buildpacks/nodejs-function-invoker/src/function.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use heroku_nodejs_utils::package_json::{PackageJson, PackageJsonError};
use libcnb::read_toml_file;
use heroku_nodejs_utils::vrs::{Requirement, Version};
use libcnb::{read_toml_file, Env};
use libherokubuildpack::toml::toml_select_value;
use std::path::PathBuf;
use std::process::Command;
use thiserror::Error;

pub(crate) fn is_function<P>(d: P) -> bool
Expand Down Expand Up @@ -56,6 +58,35 @@ where
.and_then(|dependencies| dependencies.get(package_name).cloned()))
}

pub(crate) fn check_minumum_node_version<P>(app_dir: P) -> Result<(), MinimumNodeVersionError>
where
P: Into<PathBuf>,
{
let minimum_version = Requirement::parse(">=16").expect("The minimum version should be valid.");

let version = Command::new("node")
.arg("--version")
.envs(&Env::from_current())
.current_dir(app_dir.into())
.output()
.map_err(MinimumNodeVersionError::Command)
.and_then(|output| {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
stdout
.parse::<Version>()
.map_err(|e| MinimumNodeVersionError::ParseVersion(stdout, e))
})?;

if minimum_version.satisfies(&version) {
Ok(())
} else {
Err(MinimumNodeVersionError::DoesNotMeetMinimumRequirement(
minimum_version,
version,
))
}
}

#[derive(Error, Debug)]
pub(crate) enum MainError {
#[error("Could not determine function file location from package.json. {0}")]
Expand All @@ -76,6 +107,16 @@ pub(crate) enum ExplicitRuntimeDependencyError {
DeclaredAsDevDependency { package_name: String },
}

#[derive(Error, Debug)]
pub(crate) enum MinimumNodeVersionError {
#[error("Failure while attempting to parse `node --version` output\nOutput: {0}\nError: {1}")]
ParseVersion(String, heroku_nodejs_utils::vrs::VersionError),
#[error("Failure while attempting to execute `node --version`\nError: {0}")]
Command(std::io::Error),
#[error("The minimum required version of Node.js is {0} but version {1} is installed. Please update the `engines.node` field in your package.json to a newer version.")]
DoesNotMeetMinimumRequirement(Requirement, Version),
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
23 changes: 17 additions & 6 deletions buildpacks/nodejs-function-invoker/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::function::{
get_declared_runtime_package_version, get_main, is_function, ExplicitRuntimeDependencyError,
MainError,
check_minumum_node_version, get_declared_runtime_package_version, get_main, is_function,
ExplicitRuntimeDependencyError, MainError, MinimumNodeVersionError,
};
use crate::layers::{
RuntimeLayer, RuntimeLayerError, ScriptLayer, ScriptLayerError, NODEJS_RUNTIME_SCRIPT,
Expand Down Expand Up @@ -74,6 +74,9 @@ impl Buildpack for NodeJsInvokerBuildpack {
let package_name = &metadata_runtime.package_name;
let package_version = &metadata_runtime.package_version;

check_minumum_node_version(app_dir)
.map_err(NodeJsInvokerBuildpackError::MinimumNodeVersion)?;

log_info("Checking for function file");
get_main(app_dir).map_err(NodeJsInvokerBuildpackError::MainFunction)?;

Expand Down Expand Up @@ -151,6 +154,12 @@ impl Buildpack for NodeJsInvokerBuildpack {
err_string,
);
}
NodeJsInvokerBuildpackError::MinimumNodeVersion(_) => {
log_error(
"Node.js Function Invoker minimum Node.js version error",
err_string,
);
}
}
},
error,
Expand All @@ -160,14 +169,16 @@ impl Buildpack for NodeJsInvokerBuildpack {

#[derive(Error, Debug)]
enum NodeJsInvokerBuildpackError {
#[error("{0}")]
#[error(transparent)]
MainFunction(#[from] MainError),
#[error("{0}")]
#[error(transparent)]
RuntimeLayer(#[from] RuntimeLayerError),
#[error("{0}")]
#[error(transparent)]
ScriptLayer(#[from] ScriptLayerError),
#[error("{0}")]
#[error(transparent)]
ExplicitRuntimeDependencyFunction(#[from] ExplicitRuntimeDependencyError),
#[error(transparent)]
MinimumNodeVersion(#[from] MinimumNodeVersionError),
}

impl From<NodeJsInvokerBuildpackError> for libcnb::Error<NodeJsInvokerBuildpackError> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "git"
},
"engines": {
"node": "^14.0"
"node": "^16.0"
},
"scripts": {
"lint": "eslint . --ext .js --config .eslintrc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"parserOptions": {
"ecmaVersion": 2017
},
"env": {
"es6": true
},
"rules": {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"test": "./**/*.test.js",
"recursive": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = async function (event, context, logger) {
logger.info("logging info is a fun 1")
return "Hello World".toLowerCase();
}
Loading