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,
19- const PositionalArgs& positional_args) {
18+ std::string_view path_env_var,
19+ const PositionalArgs& positional_args)
20+ : init_result(std::move(result)),
21+ package_json_path_ (package_json_path),
22+ script_name_(script_name),
23+ path_env_var_(path_env_var) {
2024 memset (&options_, 0 , sizeof (uv_process_options_t ));
2125
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
33-
3426 // Inherit stdin, stdout, and stderr from the parent process.
3527 options_.stdio_count = 3 ;
3628 child_stdio[0 ].flags = UV_INHERIT_FD;
@@ -46,18 +38,13 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
4638 options_.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS;
4739#endif
4840
49- init_result = std::move (result);
50-
5141 // Set the process handle data to this class instance.
5242 // This is used to access the class instance from the OnExit callback.
5343 // It is required because libuv doesn't allow passing lambda functions as a
5444 // callback.
5545 process_.data = this ;
5646
57- SetEnvironmentVariables (current_bin_path,
58- std::string_view (cwd, cwd_size),
59- package_json_path,
60- script_name);
47+ SetEnvironmentVariables ();
6148
6249 std::string command_str (command);
6350
@@ -73,12 +60,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
7360 }
7461
7562#ifdef _WIN32
76- // We check whether file_ ends with cmd.exe in a case-insensitive manner.
77- // C++20 provides ends_with, but we roll our own for compatibility.
78- const char * cmdexe = " cmd.exe" ;
79- if (file_.size () >= strlen (cmdexe) &&
80- StringEqualNoCase (cmdexe,
81- file_.c_str () + file_.size () - strlen (cmdexe))) {
63+ if (file_.ends_with (" cmd.exe" )) {
8264 // If the file is cmd.exe, use the following command line arguments:
8365 // "/c" Carries out the command and exit.
8466 // "/d" Disables execution of AutoRun commands.
@@ -106,10 +88,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
10688 options_.args [argc] = nullptr ;
10789}
10890
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) {
91+ void ProcessRunner::SetEnvironmentVariables () {
11392 uv_env_item_t * env_items;
11493 int env_count;
11594 CHECK_EQ (0 , uv_os_environ (&env_items, &env_count));
@@ -130,28 +109,21 @@ void ProcessRunner::SetEnvironmentVariables(const std::string& current_bin_path,
130109#endif // _WIN32
131110
132111 if (StringEqualNoCase (name.c_str (), " path" )) {
133- // Add bin_path to the beginning of the PATH
134- value = current_bin_path + value;
112+ // Add path env variable to the beginning of the PATH
113+ value = path_env_var_ + value;
135114 }
136115 env_vars_.push_back (name + " =" + value);
137116 }
138117 uv_os_free_environ (env_items, env_count);
139118
140119 // Add NODE_RUN_SCRIPT_NAME environment variable to the environment
141120 // to indicate which script is being run.
142- env_vars_.push_back (" NODE_RUN_SCRIPT_NAME=" + std::string (script_name) );
121+ env_vars_.push_back (" NODE_RUN_SCRIPT_NAME=" + script_name_ );
143122
144123 // Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to
145124 // 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- }
125+ env_vars_.push_back (" NODE_RUN_PACKAGE_JSON_PATH=" +
126+ package_json_path_.string ());
155127
156128 env = std::unique_ptr<char *[]>(new char *[env_vars_.size () + 1 ]);
157129 options_.env = env.get ();
@@ -240,19 +212,58 @@ void ProcessRunner::Run() {
240212 uv_run (loop_, UV_RUN_DEFAULT);
241213}
242214
215+ std::optional<std::tuple<std::filesystem::path, std::string, std::string>>
216+ FindPackageJson (const std::filesystem::path& cwd) {
217+ auto package_json_path = cwd / " package.json" ;
218+ std::string raw_content;
219+ std::string path_env_var;
220+ auto root_path = cwd.root_path ();
221+
222+ for (auto directory_path = cwd;
223+ !std::filesystem::equivalent (root_path, directory_path);
224+ directory_path = directory_path.parent_path ()) {
225+ // Append "path/node_modules/.bin" to the env var, if it is a directory.
226+ auto node_modules_bin = directory_path / " node_modules" / " .bin" ;
227+ if (std::filesystem::is_directory (node_modules_bin)) {
228+ path_env_var += node_modules_bin.string () + env_var_separator;
229+ }
230+
231+ if (raw_content.empty ()) {
232+ package_json_path = directory_path / " package.json" ;
233+ // This is required for Windows because std::filesystem::path::c_str()
234+ // returns wchar_t* on Windows, and char* on other platforms.
235+ std::string contents = package_json_path.string ();
236+ USE (ReadFileSync (&raw_content, contents.c_str ()) > 0 );
237+ }
238+ }
239+
240+ // This means that there is no package.json until the root directory.
241+ // In this case, we just return nullopt, which will terminate the process..
242+ if (raw_content.empty ()) {
243+ return std::nullopt ;
244+ }
245+
246+ return {{package_json_path, raw_content, path_env_var}};
247+ }
248+
243249void RunTask (std::shared_ptr<InitializationResultImpl> result,
244250 std::string_view command_id,
245251 const std::vector<std::string_view>& positional_args) {
246- std::string_view path = " package.json " ;
247- std::string raw_json ;
252+ auto cwd = std::filesystem::current_path () ;
253+ auto package_json = FindPackageJson (cwd) ;
248254
249- // No need to exclude BOM since simdjson will skip it.
250- if (ReadFileSync (&raw_json, path.data ()) < 0 ) {
255+ if (!package_json.has_value ()) {
251256 fprintf (stderr, " Can't read package.json\n " );
252257 result->exit_code_ = ExitCode::kGenericUserError ;
253258 return ;
254259 }
255260
261+ // - path: Path to the package.json file.
262+ // - raw_json: Raw content of the package.json file.
263+ // - path_env_var: This represents the `PATH` environment variable.
264+ // It always ends with ";" or ":" depending on the platform.
265+ auto [path, raw_json, path_env_var] = *package_json;
266+
256267 simdjson::ondemand::parser json_parser;
257268 simdjson::ondemand::document document;
258269 simdjson::ondemand::object main_object;
@@ -302,8 +313,8 @@ void RunTask(std::shared_ptr<InitializationResultImpl> result,
302313 return ;
303314 }
304315
305- auto runner =
306- ProcessRunner ( result, path, command_id, command, positional_args);
316+ auto runner = ProcessRunner (
317+ result, path, command_id, command, path_env_var , positional_args);
307318 runner.Run ();
308319}
309320
@@ -317,10 +328,11 @@ PositionalArgs GetPositionalArgs(const std::vector<std::string>& args) {
317328 if (auto dash_dash = std::find (args.begin (), args.end (), " --" );
318329 dash_dash != args.end ()) {
319330 PositionalArgs positional_args{};
331+ positional_args.reserve (args.size () - (dash_dash - args.begin ()));
320332 for (auto it = dash_dash + 1 ; it != args.end (); ++it) {
321333 // SAFETY: The following code is safe because the lifetime of the
322334 // 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 () ));
335+ positional_args.emplace_back ( it->c_str (), it->size ());
324336 }
325337 return positional_args;
326338 }
0 commit comments