@@ -3,13 +3,16 @@ defmodule NPM.NodeRunner do
33
44 @ spec run ( String . t ( ) , [ String . t ( ) ] , keyword ( ) ) :: { String . t ( ) , non_neg_integer ( ) }
55 def run ( entrypoint , args , opts \\ [ ] ) do
6- node_modules_dir = Keyword . get ( opts , :node_modules_dir , "node_modules" )
6+ node_modules_dir = Path . expand ( Keyword . get ( opts , :node_modules_dir , "node_modules" ) )
77 env = Keyword . get ( opts , :env , [ ] ) ++ NPM.Exec . env ( node_modules_dir )
8+ entrypoint = resolve_entrypoint ( Path . expand ( entrypoint ) , node_modules_dir )
89
9- loader_path = write_loader ( Path . expand ( node_modules_dir ) , Path . expand ( entrypoint ) )
10+ loader_path = write_loader ( node_modules_dir , entrypoint )
1011
1112 try do
12- System . cmd ( "node" , [ loader_path | args ] ,
13+ node_args = [ "--preserve-symlinks" , "--preserve-symlinks-main" , loader_path | args ]
14+
15+ System . cmd ( "node" , node_args ,
1316 env: env ,
1417 stderr_to_stdout: true ,
1518 cd: Keyword . get ( opts , :cd , File . cwd! ( ) )
@@ -19,16 +22,63 @@ defmodule NPM.NodeRunner do
1922 end
2023 end
2124
25+ defp resolve_entrypoint ( entrypoint , node_modules_dir ) do
26+ bin_dir = Path . join ( node_modules_dir , ".bin" )
27+
28+ if String . starts_with? ( entrypoint , bin_dir ) do
29+ command = Path . basename ( entrypoint )
30+
31+ case find_package_entrypoint ( command , node_modules_dir ) do
32+ { :ok , resolved } -> resolved
33+ :error -> entrypoint
34+ end
35+ else
36+ entrypoint
37+ end
38+ end
39+
40+ defp find_package_entrypoint ( command , node_modules_dir ) do
41+ bin_path = Path . join ( [ node_modules_dir , ".bin" , command ] )
42+ real_bin_path = resolve_symlink ( bin_path )
43+
44+ with { :ok , content } <- File . read ( real_bin_path ) ,
45+ [ _ , rel_path ] <- Regex . run ( ~r{ import\s +["'](\. \. /[^"']+)["']} , content ) do
46+ { :ok , Path . expand ( rel_path , Path . dirname ( real_bin_path ) ) }
47+ else
48+ _ ->
49+ case NPM.Exec . which ( command , node_modules_dir ) do
50+ { :ok , pkg_path } -> { :ok , Path . expand ( pkg_path ) }
51+ _ -> :error
52+ end
53+ end
54+ end
55+
56+ defp resolve_symlink ( path , depth \\ 10 )
57+ defp resolve_symlink ( path , 0 ) , do: path
58+
59+ defp resolve_symlink ( path , depth ) do
60+ case File . read_link ( path ) do
61+ { :ok , target } ->
62+ resolve_symlink ( Path . expand ( target , Path . dirname ( path ) ) , depth - 1 )
63+
64+ { :error , _ } ->
65+ path
66+ end
67+ end
68+
2269 defp write_loader ( node_modules_dir , entrypoint ) do
23- path = Path . join ( System . tmp_dir! ( ) , "npm-node-runner-#{ System . unique_integer ( [ :positive ] ) } .mjs" )
70+ path = Path . join ( Path . dirname ( node_modules_dir ) , ". npm-node-runner-#{ System . unique_integer ( [ :positive ] ) } .mjs" )
2471
2572 File . write! ( path , """
2673 import { createRequire } from 'node:module'
27- const require = createRequire(import.meta.url)
28- globalThis.require = require
29- process.env.NODE_PATH = #{ inspect ( node_modules_dir ) }
74+ import { pathToFileURL } from 'node:url'
75+
76+ const nmDir = #{ inspect ( node_modules_dir ) }
77+ globalThis.require = createRequire(pathToFileURL(nmDir + '/').href)
78+ process.env.NODE_PATH = nmDir
3079 require('node:module').Module._initPaths()
31- await import(#{ inspect ( entrypoint ) } )
80+
81+ await import(pathToFileURL(#{ inspect ( entrypoint ) } ).href)
3282 """ )
3383
3484 path
0 commit comments