11#include " node_task_runner.h"
22#include " util.h"
33
4- #include < filesystem>
54#include < regex> // NOLINT(build/c++11)
65
76namespace node ::task_runner {
87
98#ifdef _WIN32
10- static constexpr const char * bin_path = " \\ node_modules \\ .bin " ;
9+ static constexpr const char * env_var_separator = " ; " ;
1110#else
12- static constexpr const char * bin_path = " /node_modules/.bin " ;
11+ static constexpr const char * env_var_separator = " : " ;
1312#endif // _WIN32
1413
1514ProcessRunner::ProcessRunner (std::shared_ptr<InitializationResultImpl> result,
16- std::string_view package_json_path,
15+ const std::filesystem::path& package_json_path,
1716 std::string_view script_name,
1817 std::string_view command,
18+ std::string_view path_env_var,
1919 const PositionalArgs& positional_args) {
2020 memset (&options_, 0 , sizeof (uv_process_options_t ));
2121
22- // Get the current working directory.
23- char cwd[PATH_MAX_BYTES];
24- size_t cwd_size = PATH_MAX_BYTES;
25- CHECK_EQ (uv_cwd (cwd, &cwd_size), 0 );
26- CHECK_GT (cwd_size, 0 );
27-
28- #ifdef _WIN32
29- std::string current_bin_path = cwd + std::string (bin_path) + " ;" ;
30- #else
31- std::string current_bin_path = cwd + std::string (bin_path) + " :" ;
32- #endif // _WIN32
22+ package_json_path_ = std::filesystem::path (package_json_path);
23+ script_name_ = std::string (script_name);
24+ path_env_var_ = path_env_var;
3325
3426 // Inherit stdin, stdout, and stderr from the parent process.
3527 options_.stdio_count = 3 ;
@@ -54,10 +46,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
5446 // callback.
5547 process_.data = this ;
5648
57- SetEnvironmentVariables (current_bin_path,
58- std::string_view (cwd, cwd_size),
59- package_json_path,
60- script_name);
49+ SetEnvironmentVariables ();
6150
6251 std::string command_str (command);
6352
@@ -106,10 +95,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
10695 options_.args [argc] = nullptr ;
10796}
10897
109- void ProcessRunner::SetEnvironmentVariables (const std::string& current_bin_path,
110- std::string_view cwd,
111- std::string_view package_json_path,
112- std::string_view script_name) {
98+ void ProcessRunner::SetEnvironmentVariables () {
11399 uv_env_item_t * env_items;
114100 int env_count;
115101 CHECK_EQ (0 , uv_os_environ (&env_items, &env_count));
@@ -130,28 +116,21 @@ void ProcessRunner::SetEnvironmentVariables(const std::string& current_bin_path,
130116#endif // _WIN32
131117
132118 if (StringEqualNoCase (name.c_str (), " path" )) {
133- // Add bin_path to the beginning of the PATH
134- value = current_bin_path + value;
119+ // Add path env variable to the beginning of the PATH
120+ value = path_env_var_ + value;
135121 }
136122 env_vars_.push_back (name + " =" + value);
137123 }
138124 uv_os_free_environ (env_items, env_count);
139125
140126 // Add NODE_RUN_SCRIPT_NAME environment variable to the environment
141127 // to indicate which script is being run.
142- env_vars_.push_back (" NODE_RUN_SCRIPT_NAME=" + std::string (script_name) );
128+ env_vars_.push_back (" NODE_RUN_SCRIPT_NAME=" + script_name_ );
143129
144130 // Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to
145131 // indicate which package.json is being processed.
146- if (std::filesystem::path (package_json_path).is_absolute ()) {
147- // TODO(anonrig): Traverse up the directory tree until we find a
148- // package.json
149- env_vars_.push_back (" NODE_RUN_PACKAGE_JSON_PATH=" +
150- std::string (package_json_path));
151- } else {
152- auto path = std::filesystem::path (cwd) / std::string (package_json_path);
153- env_vars_.push_back (" NODE_RUN_PACKAGE_JSON_PATH=" + path.string ());
154- }
132+ env_vars_.push_back (" NODE_RUN_PACKAGE_JSON_PATH=" +
133+ package_json_path_.string ());
155134
156135 env = std::unique_ptr<char *[]>(new char *[env_vars_.size () + 1 ]);
157136 options_.env = env.get ();
@@ -240,19 +219,56 @@ void ProcessRunner::Run() {
240219 uv_run (loop_, UV_RUN_DEFAULT);
241220}
242221
222+ std::optional<std::tuple<std::filesystem::path, std::string, std::string>>
223+ FindPackageJson (const std::filesystem::path& cwd) {
224+ auto package_json_path = cwd / " package.json" ;
225+ std::string raw_content;
226+ std::string path_env_var;
227+
228+ for (auto directory_path = cwd; directory_path != cwd.root_path ();
229+ directory_path = directory_path.parent_path ()) {
230+ // Append "path/node_modules/.bin" to the env var, if it is a directory.
231+ auto node_modules_bin = directory_path / " node_modules/.bin" ;
232+ if (std::filesystem::is_directory (node_modules_bin)) {
233+ path_env_var += node_modules_bin.string () + env_var_separator;
234+ }
235+
236+ if (raw_content.empty ()) {
237+ package_json_path = directory_path / " package.json" ;
238+ // No need to exclude BOM since simdjson will skip it.
239+ USE (ReadFileSync (&raw_content, package_json_path.c_str ()));
240+ }
241+ }
242+
243+ // This means that there is no package.json until the root directory.
244+ // In this case, we just return nullopt, which will terminate the process..
245+ if (raw_content.empty ()) {
246+ return std::nullopt ;
247+ }
248+
249+ return std::tuple (package_json_path, raw_content, path_env_var);
250+ }
251+
243252void RunTask (std::shared_ptr<InitializationResultImpl> result,
244253 std::string_view command_id,
245254 const std::vector<std::string_view>& positional_args) {
246- std::string_view path = " package.json " ;
247- std::string raw_json ;
255+ auto cwd = std::filesystem::current_path () ;
256+ auto package_json = FindPackageJson (cwd) ;
248257
249- // No need to exclude BOM since simdjson will skip it.
250- if (ReadFileSync (&raw_json, path.data ()) < 0 ) {
258+ if (!package_json.has_value ()) {
251259 fprintf (stderr, " Can't read package.json\n " );
252260 result->exit_code_ = ExitCode::kGenericUserError ;
253261 return ;
254262 }
255263
264+ // Path to the package.json file.
265+ auto path = std::get<0 >(*package_json);
266+ // Raw content of the package.json file.
267+ auto raw_json = std::get<1 >(*package_json);
268+ // This represents the `PATH` environment variable.
269+ // It always ends with ";" or ":" depending on the platform.
270+ auto path_env_var = std::get<2 >(*package_json);
271+
256272 simdjson::ondemand::parser json_parser;
257273 simdjson::ondemand::document document;
258274 simdjson::ondemand::object main_object;
@@ -302,8 +318,8 @@ void RunTask(std::shared_ptr<InitializationResultImpl> result,
302318 return ;
303319 }
304320
305- auto runner =
306- ProcessRunner ( result, path, command_id, command, positional_args);
321+ auto runner = ProcessRunner (
322+ result, path, command_id, command, path_env_var , positional_args);
307323 runner.Run ();
308324}
309325
@@ -317,10 +333,11 @@ PositionalArgs GetPositionalArgs(const std::vector<std::string>& args) {
317333 if (auto dash_dash = std::find (args.begin (), args.end (), " --" );
318334 dash_dash != args.end ()) {
319335 PositionalArgs positional_args{};
336+ positional_args.reserve (args.size () - (dash_dash - args.begin ()));
320337 for (auto it = dash_dash + 1 ; it != args.end (); ++it) {
321338 // SAFETY: The following code is safe because the lifetime of the
322339 // arguments is guaranteed to be valid until the end of the task runner.
323- positional_args.push_back ( std::string_view ( it->c_str (), it->size () ));
340+ positional_args.emplace_back ( it->c_str (), it->size ());
324341 }
325342 return positional_args;
326343 }
0 commit comments