Repository: railwayapp/nixpacks
Pull request #1251 (2025-01-02 21:15:23)

node env clarification
quick doc improvement, this one confused me.



Patch:
diff --git a/docs/pages/docs/providers/node.md b/docs/pages/docs/providers/node.md
index d6ddb87..a3cecde 100644
--- a/docs/pages/docs/providers/node.md
+++ b/docs/pages/docs/providers/node.md
@@ -8,7 +8,7 @@ The Node provider supports NPM, Yarn, Yarn 2, PNPM and Bun.
 
 ## Environment Variables
 
-The Node provider sets the following environment variables:
+The Node provider sets the following environment variables when the container is running (not during build):
 
 - `CI=true`
 - `NODE_ENV=production`
diff --git a/examples/deno-jsonc/deno.jsonc b/examples/deno-jsonc/deno.jsonc
new file mode 100644
index 0000000..9064591
--- /dev/null
+++ b/examples/deno-jsonc/deno.jsonc
@@ -0,0 +1,15 @@
+// THIS FILE HAS ALL KINDS OF COMMENTS POSSIBLE TO TEST JSONC
+{
+  //some random comment
+  "tasks": {
+    // random afterline comment
+    /* :) */ "dev": "deno run --watch main.ts",
+    "start": "deno start main.ts"
+  } /*
+    This 
+    is some multiline comment
+    */,
+  "imports": {
+    "@std/assert": "jsr:@std/assert@1"
+  }
+}
diff --git a/examples/deno-jsonc/deno.lock b/examples/deno-jsonc/deno.lock
new file mode 100644
index 0000000..3513c0f
--- /dev/null
+++ b/examples/deno-jsonc/deno.lock
@@ -0,0 +1,18 @@
+{
+  "version": "4",
+  "specifiers": {
+    "jsr:@std/assert@1": "1.0.8",
+    "jsr:@std/internal@^1.0.5": "1.0.5"
+  },
+  "jsr": {
+    "@std/assert@1.0.8": {
+      "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b",
+      "dependencies": [
+        "jsr:@std/internal"
+      ]
+    },
+    "@std/internal@1.0.5": {
+      "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
+    }
+  }
+}
diff --git a/examples/deno-jsonc/main.ts b/examples/deno-jsonc/main.ts
new file mode 100644
index 0000000..bdf52b9
--- /dev/null
+++ b/examples/deno-jsonc/main.ts
@@ -0,0 +1,7 @@
+export function add(a: number, b: number): number {
+  return a + b;
+}
+// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
+if (import.meta.main) {
+  console.log("Add 2 + 3 =", add(2, 3));
+}
\ No newline at end of file
diff --git a/examples/node-node-version/.node-version b/examples/node-node-version/.node-version
new file mode 100644
index 0000000..dc0bb0f
--- /dev/null
+++ b/examples/node-node-version/.node-version
@@ -0,0 +1,1 @@
+v22.12.0
diff --git a/examples/node-node-version/index.js b/examples/node-node-version/index.js
new file mode 100644
index 0000000..140fc15
--- /dev/null
+++ b/examples/node-node-version/index.js
@@ -0,0 +1,2 @@
+console.log("Hello from Node");
+console.log(`Node version: ${process.version}`);
diff --git a/examples/node-node-version/package-lock.json b/examples/node-node-version/package-lock.json
new file mode 100644
index 0000000..d18e0e0
--- /dev/null
+++ b/examples/node-node-version/package-lock.json
@@ -0,0 +1,12 @@
+{
+  "name": "node",
+  "version": "1.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "node",
+      "version": "1.0.0"
+    }
+  }
+}
diff --git a/examples/node-node-version/package.json b/examples/node-node-version/package.json
new file mode 100644
index 0000000..2f12d8f
--- /dev/null
+++ b/examples/node-node-version/package.json
@@ -0,0 +1,8 @@
+{
+  "name": "node",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "start": "node index.js"
+  }
+}
diff --git a/src/nixpacks/app.rs b/src/nixpacks/app.rs
index d94e841..7aea9a1 100644
--- a/src/nixpacks/app.rs
+++ b/src/nixpacks/app.rs
@@ -190,6 +190,47 @@ impl App {
         Ok(toml_file)
     }
 
+    /// Parse jsonc files as json by ignoring all kinds of comments
+    pub fn read_jsonc<T>(&self, name: &str) -> Result<T>
+    where
+        T: DeserializeOwned,
+    {
+        let mut cleaned_jsonc = String::new();
+        let contents = self.read_file(name)?;
+        let mut chars = contents.chars().peekable();
+        while let Some(current_char) = chars.next() {
+            match current_char {
+                '/' if chars.peek() == Some(&'/') => {
+                    while let Some(&next_char) = chars.peek() {
+                        chars.next();
+                        if next_char == '\n' {
+                            break;
+                        }
+                    }
+                }
+                '/' if chars.peek() == Some(&'*') => {
+                    chars.next();
+                    loop {
+                        match chars.next() {
+                            Some('*') if chars.peek() == Some(&'/') => {
+                                chars.next();
+                                break;
+                            }
+                            None => break,
+                            _ => continue,
+                        }
+                    }
+                }
+                _ => cleaned_jsonc.push(current_char),
+            }
+        }
+        let value: T = serde_json::from_str(cleaned_jsonc.as_str()).with_context(|| {
+            let relative_path = self.strip_source_path(Path::new(name)).unwrap();
+            format!("Error reading {} as JSONC", relative_path.to_str().unwrap())
+        })?;
+        Ok(value)
+    }
+
     /// Try to yaml-parse a file.
     pub fn read_yaml<T>(&self, name: &str) -> Result<T>
     where
@@ -281,6 +322,14 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn test_read_jsonc_file() -> Result<()> {
+        let app = App::new("./examples/deno-jsonc")?;
+        let value: Map<String, Value> = app.read_jsonc("deno.jsonc")?;
+        assert!(value.get("tasks").is_some());
+        Ok(())
+    }
+
     #[test]
     fn test_read_toml_file() -> Result<()> {
         let app = App::new("./examples/rust-rocket")?;
diff --git a/src/nixpacks/images.rs b/src/nixpacks/images.rs
index a76a945..4bfbc77 100644
--- a/src/nixpacks/images.rs
+++ b/src/nixpacks/images.rs
@@ -1,5 +1,5 @@
-pub const DEBIAN_BASE_IMAGE: &str = "ghcr.io/railwayapp/nixpacks:debian-1733184274";
-pub const UBUNTU_BASE_IMAGE: &str = "ghcr.io/railwayapp/nixpacks:ubuntu-1733184274";
+pub const DEBIAN_BASE_IMAGE: &str = "ghcr.io/railwayapp/nixpacks:debian-1735603445";
+pub const UBUNTU_BASE_IMAGE: &str = "ghcr.io/railwayapp/nixpacks:ubuntu-1735603445";
 pub const DEFAULT_BASE_IMAGE: &str = UBUNTU_BASE_IMAGE;
 
 pub const STANDALONE_IMAGE: &str = "ubuntu:jammy";
diff --git a/src/providers/deno.rs b/src/providers/deno.rs
index a5289ec..3deef7b 100644
--- a/src/providers/deno.rs
+++ b/src/providers/deno.rs
@@ -82,7 +82,7 @@ impl DenoProvider {
         if app.includes_file("deno.json") || app.includes_file("deno.jsonc") {
             let deno_json: DenoJson = app
                 .read_json("deno.json")
-                .or_else(|_| app.read_json("deno.jsonc"))?;
+                .or_else(|_| app.read_jsonc("deno.jsonc"))?;
 
             if let Some(tasks) = deno_json.tasks {
                 if let Some(start) = tasks.start {
diff --git a/src/providers/node/mod.rs b/src/providers/node/mod.rs
index e4e72e3..49e727f 100644
--- a/src/providers/node/mod.rs
+++ b/src/providers/node/mod.rs
@@ -340,7 +340,18 @@ impl NodeProvider {
             None
         };
 
-        let node_version = env_node_version.or(pkg_node_version).or(nvmrc_node_version);
+        let dot_node_version = if app.includes_file(".node-version") {
+            let node_version_file = app.read_file(".node-version")?;
+            // Using simple string transform since .node-version don't currently have a convention around the use of lts/* implemented in parse_nvmrc method
+            Some(node_version_file.trim().replace('v', ""))
+        } else {
+            None
+        };
+
+        let node_version = env_node_version
+            .or(pkg_node_version)
+            .or(nvmrc_node_version)
+            .or(dot_node_version);
 
         let node_version = match node_version {
             Some(node_version) => node_version,
@@ -362,7 +373,7 @@ impl NodeProvider {
             pkg_manager = "pnpm";
         } else if app.includes_file("yarn.lock") {
             pkg_manager = "yarn";
-        } else if app.includes_file("bun.lockb") {
+        } else if app.includes_file("bun.lockb") || app.includes_file("bun.lock") {
             pkg_manager = "bun";
         }
         pkg_manager.to_string()
@@ -397,7 +408,7 @@ impl NodeProvider {
             }
         } else if app.includes_file("package-lock.json") {
             install_cmd = "npm ci".to_string();
-        } else if app.includes_file("bun.lockb") {
+        } else if app.includes_file("bun.lockb") || app.includes_file("bun.lock") {
             install_cmd = "bun i --no-save".to_string();
         }
 
@@ -961,6 +972,23 @@ mod test {
         Ok(())
     }
 
+    #[test]
+    fn test_version_from_node_version_file() -> Result<()> {
+        assert_eq!(
+            NodeProvider::get_nix_node_pkg(
+                &PackageJson {
+                    name: Some(String::default()),
+                    ..Default::default()
+                },
+                &App::new("examples/node-node-version")?,
+                &Environment::default()
+            )?,
+            Pkg::new("nodejs_22")
+        );
+
+        Ok(())
+    }
+
     #[test]
     fn test_version_from_nvmrc_lts() -> Result<()> {
         assert_eq!(
diff --git a/src/providers/python.rs b/src/providers/python.rs
index 30d1165..b6766f6 100644
--- a/src/providers/python.rs
+++ b/src/providers/python.rs
@@ -86,7 +86,6 @@ impl Provider for PythonProvider {
                 if let Some(poetry_version) =
                     PythonProvider::parse_tool_versions_poetry_version(file_content)?
                 {
-                    println!("Using poetry version from .tool-versions: {poetry_version}");
                     version = poetry_version;
                 }
             }
@@ -114,7 +113,6 @@ impl Provider for PythonProvider {
                 if let Some(uv_version) =
                     PythonProvider::parse_tool_versions_uv_version(file_content)?
                 {
-                    println!("Using uv version from .tool-versions: {uv_version}");
                     version = uv_version;
                 }
             }


Test patch:
diff --git a/examples/deno-jsonc/main_test.ts b/examples/deno-jsonc/main_test.ts
new file mode 100644
index 0000000..3d981e9
--- /dev/null
+++ b/examples/deno-jsonc/main_test.ts
@@ -0,0 +1,6 @@
+import { assertEquals } from "@std/assert";
+import { add } from "./main.ts";
+
+Deno.test(function addTest() {
+  assertEquals(add(2, 3), 5);
+});
diff --git a/tests/snapshots/generate_plan_tests__deno_jsonc.snap b/tests/snapshots/generate_plan_tests__deno_jsonc.snap
new file mode 100644
index 0000000..dd51556
--- /dev/null
+++ b/tests/snapshots/generate_plan_tests__deno_jsonc.snap
@@ -0,0 +1,24 @@
+---
+source: tests/generate_plan_tests.rs
+expression: plan
+---
+{
+  "providers": [],
+  "buildImage": "[build_image]",
+  "variables": {
+    "NIXPACKS_METADATA": "deno"
+  },
+  "phases": {
+    "setup": {
+      "name": "setup",
+      "nixPkgs": [
+        "deno"
+      ],
+      "nixOverlays": [],
+      "nixpkgsArchive": "[archive]"
+    }
+  },
+  "start": {
+    "cmd": "deno start main.ts"
+  }
+}
diff --git a/tests/snapshots/generate_plan_tests__node_node_version.snap b/tests/snapshots/generate_plan_tests__node_node_version.snap
new file mode 100644
index 0000000..a7f82aa
--- /dev/null
+++ b/tests/snapshots/generate_plan_tests__node_node_version.snap
@@ -0,0 +1,54 @@
+---
+source: tests/generate_plan_tests.rs
+expression: plan
+---
+{
+  "providers": [],
+  "buildImage": "[build_image]",
+  "variables": {
+    "CI": "true",
+    "NIXPACKS_METADATA": "node",
+    "NODE_ENV": "production",
+    "NPM_CONFIG_PRODUCTION": "false"
+  },
+  "phases": {
+    "build": {
+      "name": "build",
+      "dependsOn": [
+        "install"
+      ],
+      "cacheDirectories": [
+        "node_modules/.cache"
+      ]
+    },
+    "install": {
+      "name": "install",
+      "dependsOn": [
+        "setup"
+      ],
+      "cmds": [
+        "npm ci"
+      ],
+      "cacheDirectories": [
+        "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
+      ]
+    },
+    "setup": {
+      "name": "setup",
+      "nixPkgs": [
+        "nodejs_22",
+        "npm-8_x"
+      ],
+      "nixOverlays": [
+        "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz"
+      ],
+      "nixpkgsArchive": "[archive]"
+    }
+  },
+  "start": {
+    "cmd": "npm run start"
+  }
+}