1
1
#include " node_task_runner.h"
2
2
#include " util.h"
3
3
4
- #include < filesystem>
5
4
#include < regex> // NOLINT(build/c++11)
6
5
7
6
namespace node ::task_runner {
8
7
9
8
#ifdef _WIN32
10
- static constexpr const char * bin_path = " \\ node_modules \\ .bin " ;
9
+ static constexpr const char * env_var_separator = " ; " ;
11
10
#else
12
- static constexpr const char * bin_path = " /node_modules/.bin " ;
11
+ static constexpr const char * env_var_separator = " : " ;
13
12
#endif // _WIN32
14
13
15
14
ProcessRunner::ProcessRunner (std::shared_ptr<InitializationResultImpl> result,
16
- std::string_view package_json_path,
15
+ const std::filesystem::path& package_json_path,
17
16
std::string_view script_name,
18
17
std::string_view command,
18
+ std::string_view path_env_var,
19
19
const PositionalArgs& positional_args) {
20
20
memset (&options_, 0 , sizeof (uv_process_options_t ));
21
21
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;
33
25
34
26
// Inherit stdin, stdout, and stderr from the parent process.
35
27
options_.stdio_count = 3 ;
@@ -54,10 +46,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
54
46
// callback.
55
47
process_.data = this ;
56
48
57
- SetEnvironmentVariables (current_bin_path,
58
- std::string_view (cwd, cwd_size),
59
- package_json_path,
60
- script_name);
49
+ SetEnvironmentVariables ();
61
50
62
51
std::string command_str (command);
63
52
@@ -106,10 +95,7 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
106
95
options_.args [argc] = nullptr ;
107
96
}
108
97
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 () {
113
99
uv_env_item_t * env_items;
114
100
int env_count;
115
101
CHECK_EQ (0 , uv_os_environ (&env_items, &env_count));
@@ -130,28 +116,21 @@ void ProcessRunner::SetEnvironmentVariables(const std::string& current_bin_path,
130
116
#endif // _WIN32
131
117
132
118
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;
135
121
}
136
122
env_vars_.push_back (name + " =" + value);
137
123
}
138
124
uv_os_free_environ (env_items, env_count);
139
125
140
126
// Add NODE_RUN_SCRIPT_NAME environment variable to the environment
141
127
// 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_ );
143
129
144
130
// Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to
145
131
// 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 ());
155
134
156
135
env = std::unique_ptr<char *[]>(new char *[env_vars_.size () + 1 ]);
157
136
options_.env = env.get ();
@@ -240,19 +219,66 @@ void ProcessRunner::Run() {
240
219
uv_run (loop_, UV_RUN_DEFAULT);
241
220
}
242
221
222
+ std::optional<std::tuple<std::filesystem::path, std::string, std::string>>
223
+ FindPackageJson (const std::filesystem::path& cwd) {
224
+ // Two different paths are created only for improving readability.
225
+ // Otherwise, it is not needed.
226
+ auto directory_path = cwd;
227
+ auto package_json_path = directory_path / " package.json" ;
228
+ std::string raw_content;
229
+ std::string path_env_var =
230
+ (directory_path / " node_modules" / " .bin" ).string () + env_var_separator;
231
+
232
+ USE (ReadFileSync (&raw_content, package_json_path.c_str ()));
233
+
234
+ do {
235
+ directory_path = directory_path.parent_path ();
236
+
237
+ // Always append "node_modules/.bin" to the env var.
238
+ // This is in par with existing package managers.
239
+ path_env_var +=
240
+ (directory_path / " node_modules" / " .bin" ).string () + env_var_separator;
241
+
242
+ // No need to exclude BOM since simdjson will skip it.
243
+ if (raw_content.empty ()) {
244
+ package_json_path = directory_path / " package.json" ;
245
+ USE (ReadFileSync (&raw_content, package_json_path.c_str ()));
246
+ }
247
+
248
+ // Traverse up to the root directory.
249
+ // Note: root directory parent path is the root directory itself.
250
+ // Hence, "/".parent_path() == "/".
251
+ } while (directory_path != directory_path.parent_path ());
252
+
253
+ // This means that there is no package.json until the root directory.
254
+ // In this case, we just return nullopt, which will terminate the process..
255
+ if (raw_content.empty ()) {
256
+ return std::nullopt;
257
+ }
258
+
259
+ return std::tuple (package_json_path, raw_content, path_env_var);
260
+ }
261
+
243
262
void RunTask (std::shared_ptr<InitializationResultImpl> result,
244
263
std::string_view command_id,
245
264
const std::vector<std::string_view>& positional_args) {
246
- std::string_view path = " package.json " ;
247
- std::string raw_json ;
265
+ auto cwd = std::filesystem::current_path () ;
266
+ auto package_json = FindPackageJson (cwd) ;
248
267
249
- // No need to exclude BOM since simdjson will skip it.
250
- if (ReadFileSync (&raw_json, path.data ()) < 0 ) {
268
+ if (!package_json.has_value ()) {
251
269
fprintf (stderr, " Can't read package.json\n " );
252
270
result->exit_code_ = ExitCode::kGenericUserError ;
253
271
return ;
254
272
}
255
273
274
+ // Path to the package.json file.
275
+ auto path = std::get<0 >(*package_json);
276
+ // Raw content of the package.json file.
277
+ auto raw_json = std::get<1 >(*package_json);
278
+ // This represents the `PATH` environment variable.
279
+ // It always ends with ";" or ":" depending on the platform.
280
+ auto path_env_var = std::get<2 >(*package_json);
281
+
256
282
simdjson::ondemand::parser json_parser;
257
283
simdjson::ondemand::document document;
258
284
simdjson::ondemand::object main_object;
@@ -302,8 +328,8 @@ void RunTask(std::shared_ptr<InitializationResultImpl> result,
302
328
return ;
303
329
}
304
330
305
- auto runner =
306
- ProcessRunner ( result, path, command_id, command, positional_args);
331
+ auto runner = ProcessRunner (
332
+ result, path, command_id, command, path_env_var , positional_args);
307
333
runner.Run ();
308
334
}
309
335
0 commit comments