Repository: railwayapp/nixpacks (rust)

Issue:
Nixpacks does not make the installed node_modules executables available
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

When building a node project with custom commands (i.e.: `--build-cmd`), the installed npm packages are not available to be used in the image.

For example, trying to build a Blitz project and passing `blitz build` to `--build-cmd` fails because `blitz` is not found. 
Note that in a simple project, the build works without a custom command, but the point is that I'd expect whatever was installed to be available in the image. 

### To reproduce

1. Clone this repo: https://github.com/crondinini/blitzjs
2. Run nixpacks with: `nixpacks . build --build-cmd 'blitz dev'`
3. See the error:
```
[+] Building 36.6s (11/12)
 => [internal] load build definition from Dockerfile                                0.2s
 => => transferring dockerfile: 626B                                                0.1s
 => [internal] load .dockerignore                                                   0.2s
 => => transferring context: 2B                                                     0.0s
 => [internal] load metadata for ghcr.io/railwayapp/nixpacks:debian-1657820962      0.6s
 => [internal] load build context                                                   0.1s
 => => transferring context: 681.65kB                                               0.1s
 => [stage-0 1/8] FROM ghcr.io/railwayapp/nixpacks:debian-1657820962@sha256:f116f7  0.0s
 => CACHED [stage-0 2/8] WORKDIR /app/                                              0.0s
 => CACHED [stage-0 3/8] COPY environment.nix /app/                                 0.0s
 => CACHED [stage-0 4/8] RUN nix-env -if environment.nix                            0.0s
 => [stage-0 5/8] COPY . /app/                                                      0.1s
 => [stage-0 6/8] RUN --mount=type=cache,id=mgAt8pKUNJE-/usr/local/share/cache/ya  35.0s
 => ERROR [stage-0 7/8] RUN --mount=type=cache,id=mgAt8pKUNJE-node_modules/cache,t  0.4s
------
 > [stage-0 7/8] RUN --mount=type=cache,id=mgAt8pKUNJE-node_modules/cache,target=node_modules/.cache blitz dev:
#11 0.412 /bin/bash: line 1: blitz: command not found
```

### Expected behavior

I'd expect the installed packages to be available by adding `node_modules/.bin` to the PATH.

Looking at the code for other providers, both [`python`](https://github.com/railwayapp/nixpacks/blob/main/src/providers/python.rs#L73) and [`ruby`](https://github.com/railwayapp/nixpacks/blob/main/src/providers/ruby.rs#L57-L59) have a step during the install process that adds what looks like the `bin` folder related to the packages installed (I'm assuming this based on the folder name, but I could be wrong as I'm not familiar with those languages).

### Environment

MacOS Monterey 12.4
nixpacks 0.2.11

Pull request:
Add node_modules/.bin to path
Fixes #399

Add the `./node_modules/.bin` directory to the path in Node apps.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9467413..f7fee6b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,13 +19,33 @@ You should see `Hello from Node` printed to the console.
 
 ## Debugging
 
-When debugging it can be useful to see the `environment.nix` and `Dockerfile` generated. You can do this my saving the build artifact to a specific directory instead of to a temp dir.
+When debugging it can be useful to see the intermediate files that Nixpacks generates (e.g. `Dockerfile`) You can do this my saving the build artifact to a specific directory instead of to a temp dir.
 
 ```
 cargo run -- build examples/node --out test
 ```
 
-_The `test` directory will contain everything that would be built with Docker._
+_The `test` directory will contain everything that would be built with Docker. All the files that Nixpacks generates are in `.nixpacks`. You can manually build the image with `docker build test -f test/.nixpacks/Dockerfile`_.
+
+## Snapshot Tests
+
+Nixpacks uses [insta](https://github.com/mitsuhiko/insta) for snapshot tests. We use snapshot tests to generate and compare all build plans for the test apps in `examples/`. If a snapshot test fails due to a change to a provider, that is okay. It just means the snapshot needs to be reviewed and accepted. To test and review all snapshots, you can
+
+First install insta
+
+```
+cargo install cargo-insta
+```
+
+Test and review the generate plan tests.
+
+```
+cargo insta test --review -- --test generate_plan_tests
+```
+
+The snapshots are checked into CI and are reviewed as part of the PR. They ensure that a change to one part of Nixpacks does not unexpectedly change an unrelated part.
+
+[Read the docs](https://insta.rs/docs/) for more information on cargo insta.
 
 ## Contribution Ideas
 
diff --git a/src/providers/node/mod.rs b/src/providers/node/mod.rs
index bc6ebc3..be729aa 100644
--- a/src/providers/node/mod.rs
+++ b/src/providers/node/mod.rs
@@ -71,6 +71,7 @@ impl Provider for NodeProvider {
         // Install
         let mut install = Phase::install(Some(NodeProvider::get_install_command(app)));
         install.add_cache_directory(NodeProvider::get_package_manager_cache_dir(app));
+        install.add_path("/app/node_modules/.bin".to_string());
 
         // Cypress cache directory
         let all_deps = NodeProvider::get_all_deps(app)?;
@@ -79,7 +80,6 @@ impl Provider for NodeProvider {
         }
 
         // Build
-
         let mut build = if NodeProvider::is_nx_monorepo(app) {
             let app_name = NodeProvider::get_nx_app_name(app, env)?.unwrap();
             Phase::build(Some(format!("npx nx run {}:build:production", app_name)))


Test patch:
diff --git a/tests/snapshots/generate_plan_tests__node.snap b/tests/snapshots/generate_plan_tests__node.snap
index 619b553..4b1ab13 100644
--- a/tests/snapshots/generate_plan_tests__node.snap
+++ b/tests/snapshots/generate_plan_tests__node.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_bun.snap b/tests/snapshots/generate_plan_tests__node_bun.snap
index 5e28774..0696d50 100644
--- a/tests/snapshots/generate_plan_tests__node_bun.snap
+++ b/tests/snapshots/generate_plan_tests__node_bun.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.bun"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_bun_no_start.snap b/tests/snapshots/generate_plan_tests__node_bun_no_start.snap
index 9c50fc7..a26afbb 100644
--- a/tests/snapshots/generate_plan_tests__node_bun_no_start.snap
+++ b/tests/snapshots/generate_plan_tests__node_bun_no_start.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.bun"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_bun_web_server.snap b/tests/snapshots/generate_plan_tests__node_bun_web_server.snap
index 9c50fc7..a26afbb 100644
--- a/tests/snapshots/generate_plan_tests__node_bun_web_server.snap
+++ b/tests/snapshots/generate_plan_tests__node_bun_web_server.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.bun"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_custom_version.snap b/tests/snapshots/generate_plan_tests__node_custom_version.snap
index 06a82d5..c9e6fe7 100644
--- a/tests/snapshots/generate_plan_tests__node_custom_version.snap
+++ b/tests/snapshots/generate_plan_tests__node_custom_version.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_main_file.snap b/tests/snapshots/generate_plan_tests__node_main_file.snap
index 8abfc19..ef81ecb 100644
--- a/tests/snapshots/generate_plan_tests__node_main_file.snap
+++ b/tests/snapshots/generate_plan_tests__node_main_file.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_main_file_not_exist.snap b/tests/snapshots/generate_plan_tests__node_main_file_not_exist.snap
index 8c514ac..7cf4ecc 100644
--- a/tests/snapshots/generate_plan_tests__node_main_file_not_exist.snap
+++ b/tests/snapshots/generate_plan_tests__node_main_file_not_exist.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_monorepo.snap b/tests/snapshots/generate_plan_tests__node_monorepo.snap
index e8d8a17..f71454e 100644
--- a/tests/snapshots/generate_plan_tests__node_monorepo.snap
+++ b/tests/snapshots/generate_plan_tests__node_monorepo.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/usr/local/share/.cache/yarn/v6"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_no_lockfile_canvas.snap b/tests/snapshots/generate_plan_tests__node_no_lockfile_canvas.snap
index 19e222b..09fba10 100644
--- a/tests/snapshots/generate_plan_tests__node_no_lockfile_canvas.snap
+++ b/tests/snapshots/generate_plan_tests__node_no_lockfile_canvas.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_no_scripts.snap b/tests/snapshots/generate_plan_tests__node_no_scripts.snap
index 8c514ac..7cf4ecc 100644
--- a/tests/snapshots/generate_plan_tests__node_no_scripts.snap
+++ b/tests/snapshots/generate_plan_tests__node_no_scripts.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_npm.snap b/tests/snapshots/generate_plan_tests__node_npm.snap
index c6c2918..754dcd3 100644
--- a/tests/snapshots/generate_plan_tests__node_npm.snap
+++ b/tests/snapshots/generate_plan_tests__node_npm.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_npm_old_lockfile.snap b/tests/snapshots/generate_plan_tests__node_npm_old_lockfile.snap
index c4ab1bf..c052ffc 100644
--- a/tests/snapshots/generate_plan_tests__node_npm_old_lockfile.snap
+++ b/tests/snapshots/generate_plan_tests__node_npm_old_lockfile.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_nx.snap b/tests/snapshots/generate_plan_tests__node_nx.snap
index 67aec94..d010ff4 100644
--- a/tests/snapshots/generate_plan_tests__node_nx.snap
+++ b/tests/snapshots/generate_plan_tests__node_nx.snap
@@ -22,6 +22,9 @@ expression: plan
       "cacheDirectories": [
         "/root/.npm",
         "/root/.cache/Cypress"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_pnpm.snap b/tests/snapshots/generate_plan_tests__node_pnpm.snap
index d7b3a2c..4b835ca 100644
--- a/tests/snapshots/generate_plan_tests__node_pnpm.snap
+++ b/tests/snapshots/generate_plan_tests__node_pnpm.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.cache/pnpm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_pnpm_custom_node_version.snap b/tests/snapshots/generate_plan_tests__node_pnpm_custom_node_version.snap
index 0caefdd..1dc3796 100644
--- a/tests/snapshots/generate_plan_tests__node_pnpm_custom_node_version.snap
+++ b/tests/snapshots/generate_plan_tests__node_pnpm_custom_node_version.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.cache/pnpm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_pnpm_v7.snap b/tests/snapshots/generate_plan_tests__node_pnpm_v7.snap
index 49c1d84..7edd2ad 100644
--- a/tests/snapshots/generate_plan_tests__node_pnpm_v7.snap
+++ b/tests/snapshots/generate_plan_tests__node_pnpm_v7.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.cache/pnpm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_variables.snap b/tests/snapshots/generate_plan_tests__node_variables.snap
index 8c514ac..7cf4ecc 100644
--- a/tests/snapshots/generate_plan_tests__node_variables.snap
+++ b/tests/snapshots/generate_plan_tests__node_variables.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_yarn.snap b/tests/snapshots/generate_plan_tests__node_yarn.snap
index 73773df..5883d2b 100644
--- a/tests/snapshots/generate_plan_tests__node_yarn.snap
+++ b/tests/snapshots/generate_plan_tests__node_yarn.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/usr/local/share/.cache/yarn/v6"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_yarn_berry.snap b/tests/snapshots/generate_plan_tests__node_yarn_berry.snap
index 3a10a7c..e2cc2d3 100644
--- a/tests/snapshots/generate_plan_tests__node_yarn_berry.snap
+++ b/tests/snapshots/generate_plan_tests__node_yarn_berry.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/usr/local/share/.cache/yarn/v6"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_yarn_custom_node_version.snap b/tests/snapshots/generate_plan_tests__node_yarn_custom_node_version.snap
index e9ab8f3..66d4b7a 100644
--- a/tests/snapshots/generate_plan_tests__node_yarn_custom_node_version.snap
+++ b/tests/snapshots/generate_plan_tests__node_yarn_custom_node_version.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/usr/local/share/.cache/yarn/v6"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__node_yarn_prisma.snap b/tests/snapshots/generate_plan_tests__node_yarn_prisma.snap
index 3d9c056..52dbbfe 100644
--- a/tests/snapshots/generate_plan_tests__node_yarn_prisma.snap
+++ b/tests/snapshots/generate_plan_tests__node_yarn_prisma.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/usr/local/share/.cache/yarn/v6"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__overriding_environment_variables.snap b/tests/snapshots/generate_plan_tests__overriding_environment_variables.snap
index 4e280ba..4a8b86a 100644
--- a/tests/snapshots/generate_plan_tests__overriding_environment_variables.snap
+++ b/tests/snapshots/generate_plan_tests__overriding_environment_variables.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {
diff --git a/tests/snapshots/generate_plan_tests__procfile.snap b/tests/snapshots/generate_plan_tests__procfile.snap
index 8c514ac..7cf4ecc 100644
--- a/tests/snapshots/generate_plan_tests__procfile.snap
+++ b/tests/snapshots/generate_plan_tests__procfile.snap
@@ -21,6 +21,9 @@ expression: plan
       ],
       "cacheDirectories": [
         "/root/.npm"
+      ],
+      "paths": [
+        "/app/node_modules/.bin"
       ]
     },
     {