Skip to content

Commit

Permalink
cli: add NODE_RUN_SCRIPT_NAME env to node --run
Browse files Browse the repository at this point in the history
PR-URL: #53032
Refs: #52673
Reviewed-By: Daniel Lemire <daniel@lemire.me>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
  • Loading branch information
anonrig authored and targos committed May 21, 2024
1 parent bb844de commit c70b2f7
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 35 deletions.
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,10 @@ Modules preloaded with `--require` will run before modules preloaded with `--imp

<!-- YAML
added: v22.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/53032
description: NODE_RUN_SCRIPT_NAME environment variable is added.
-->

> Stability: 1.1 - Active development
Expand Down Expand Up @@ -1897,6 +1901,13 @@ are:
* Running `pre` or `post` scripts in addition to the specified script.
* Defining package manager-specific environment variables.

#### Environment variables

The following environment variables are set when running a script with `--run`:

* `NODE_RUN_SCRIPT_NAME`: The name of the script being run. For example, if
`--run` is used to run `test`, the value of this variable will be `test`.

### `--secure-heap=n`

<!-- YAML
Expand Down
76 changes: 42 additions & 34 deletions src/node_task_runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ static constexpr const char* bin_path = "/node_modules/.bin";
#endif // _WIN32

ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
const std::string& script_name,
std::string_view command,
const PositionalArgs& positional_args) {
memset(&options_, 0, sizeof(uv_process_options_t));
Expand Down Expand Up @@ -51,39 +52,9 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
// callback.
process_.data = this;

std::string command_str(command);
SetEnvironmentVariables(current_bin_path, script_name);

// Set environment variables
uv_env_item_t* env_items;
int env_count;
CHECK_EQ(0, uv_os_environ(&env_items, &env_count));
env = std::unique_ptr<char*[]>(new char*[env_count + 1]);
options_.env = env.get();

// Iterate over environment variables once to store them in the current
// ProcessRunner instance.
for (int i = 0; i < env_count; i++) {
std::string name = env_items[i].name;
auto value = env_items[i].value;

#ifdef _WIN32
// We use comspec environment variable to find cmd.exe path on Windows
// Example: 'C:\\Windows\\system32\\cmd.exe'
// If we don't find it, we fallback to 'cmd.exe' for Windows
if (StringEqualNoCase(name.c_str(), "comspec")) {
file_ = value;
}
#endif // _WIN32

// Check if environment variable key is matching case-insensitive "path"
if (StringEqualNoCase(name.c_str(), "path")) {
env_vars_.push_back(name + "=" + current_bin_path + value);
} else {
// Environment variables should be in "KEY=value" format
env_vars_.push_back(name + "=" + value);
}
}
uv_os_free_environ(env_items, env_count);
std::string command_str(command);

// Use the stored reference on the instance.
options_.file = file_.c_str();
Expand Down Expand Up @@ -128,11 +99,47 @@ ProcessRunner::ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
options_.args[i] = const_cast<char*>(command_args_[i].c_str());
}
options_.args[argc] = nullptr;
}

void ProcessRunner::SetEnvironmentVariables(const std::string& current_bin_path,
const std::string& script_name) {
uv_env_item_t* env_items;
int env_count;
CHECK_EQ(0, uv_os_environ(&env_items, &env_count));

// Iterate over environment variables once to store them in the current
// ProcessRunner instance.
for (int i = 0; i < env_count; i++) {
std::string name = env_items[i].name;
std::string value = env_items[i].value;

#ifdef _WIN32
// We use comspec environment variable to find cmd.exe path on Windows
// Example: 'C:\\Windows\\system32\\cmd.exe'
// If we don't find it, we fallback to 'cmd.exe' for Windows
if (StringEqualNoCase(name.c_str(), "comspec")) {
file_ = value;
}
#endif // _WIN32

if (StringEqualNoCase(name.c_str(), "path")) {
// Add bin_path to the beginning of the PATH
value = current_bin_path + value;
}
env_vars_.push_back(name + "=" + value);
}
uv_os_free_environ(env_items, env_count);

// Add NODE_RUN_SCRIPT_NAME environment variable to the environment
// to indicate which script is being run.
env_vars_.push_back("NODE_RUN_SCRIPT_NAME=" + script_name);

env = std::unique_ptr<char*[]>(new char*[env_vars_.size() + 1]);
options_.env = env.get();
for (size_t i = 0; i < env_vars_.size(); i++) {
options_.env[i] = const_cast<char*>(env_vars_[i].c_str());
}
options_.env[env_count] = nullptr;
options_.env[env_vars_.size()] = nullptr;
}

// EscapeShell escapes a string to be used as a command line argument.
Expand Down Expand Up @@ -276,7 +283,8 @@ void RunTask(std::shared_ptr<InitializationResultImpl> result,
return;
}

auto runner = ProcessRunner(result, command, positional_args);
auto runner =
ProcessRunner(result, std::string(command_id), command, positional_args);
runner.Run();
}

Expand Down
3 changes: 3 additions & 0 deletions src/node_task_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ using PositionalArgs = std::vector<std::string_view>;
class ProcessRunner {
public:
ProcessRunner(std::shared_ptr<InitializationResultImpl> result,
const std::string& script_name,
std::string_view command_id,
const PositionalArgs& positional_args);
void Run();
Expand All @@ -43,6 +44,8 @@ class ProcessRunner {

// OnExit is the callback function that is called when the process exits.
void OnExit(int64_t exit_status, int term_signal);
void SetEnvironmentVariables(const std::string& bin_path,
const std::string& script_name);

#ifdef _WIN32
std::string file_ = "cmd.exe";
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion test/fixtures/run-script/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"custom-env": "custom-env",
"custom-env-windows": "custom-env.bat",
"path-env": "path-env",
"path-env-windows": "path-env.bat"
"path-env-windows": "path-env.bat",
"special-env-variables": "special-env-variables",
"special-env-variables-windows": "special-env-variables.bat"
}
}
2 changes: 2 additions & 0 deletions test/message/node_run_non_existent.out
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Available scripts are:
custom-env-windows: custom-env.bat
path-env: path-env
path-env-windows: path-env.bat
special-env-variables: special-env-variables
special-env-variables-windows: special-env-variables.bat
12 changes: 12 additions & 0 deletions test/parallel/test-node-run.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,16 @@ describe('node run [command]', () => {
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});

it('should set special environment variables', async () => {
const scriptName = `special-env-variables${envSuffix}`;
const child = await common.spawnPromisified(
process.execPath,
[ '--no-warnings', '--run', scriptName],
{ cwd: fixtures.path('run-script') },
);
assert.ok(child.stdout.includes(scriptName));
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});
});

0 comments on commit c70b2f7

Please sign in to comment.