diff --git a/src/node_task_runner.cc b/src/node_task_runner.cc index 22c02e83e12..2b3e005abf3 100644 --- a/src/node_task_runner.cc +++ b/src/node_task_runner.cc @@ -123,7 +123,7 @@ void ProcessRunner::SetEnvironmentVariables() { // Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to // indicate which package.json is being processed. env_vars_.push_back("NODE_RUN_PACKAGE_JSON_PATH=" + - package_json_path_.string()); + ConvertPathToUTF8(package_json_path_)); env_ = std::unique_ptr(new char*[env_vars_.size() + 1]); options_.env = env_.get(); @@ -206,7 +206,7 @@ void ProcessRunner::OnExit(int64_t exit_status, int term_signal) { void ProcessRunner::Run() { // keeps the string alive until destructor - cwd_ = package_json_path_.parent_path().string(); + cwd_ = ConvertPathToUTF8(package_json_path_.parent_path()); options_.cwd = cwd_.c_str(); if (int r = uv_spawn(loop_, &process_, &options_)) { fprintf(stderr, "Error: %s\n", uv_strerror(r)); @@ -228,14 +228,14 @@ FindPackageJson(const std::filesystem::path& cwd) { // Append "path/node_modules/.bin" to the env var, if it is a directory. auto node_modules_bin = directory_path / "node_modules" / ".bin"; if (std::filesystem::is_directory(node_modules_bin)) { - path_env_var += node_modules_bin.string() + env_var_separator; + path_env_var += ConvertPathToUTF8(node_modules_bin) + env_var_separator; } if (raw_content.empty()) { package_json_path = directory_path / "package.json"; // This is required for Windows because std::filesystem::path::c_str() // returns wchar_t* on Windows, and char* on other platforms. - std::string contents = package_json_path.string(); + std::string contents = ConvertPathToUTF8(package_json_path); USE(ReadFileSync(&raw_content, contents.c_str()) > 0); } } @@ -258,7 +258,7 @@ void RunTask(const std::shared_ptr& result, if (!package_json.has_value()) { fprintf(stderr, "Can't find package.json for directory %s\n", - cwd.string().c_str()); + ConvertPathToUTF8(cwd).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -274,7 +274,7 @@ void RunTask(const std::shared_ptr& result, simdjson::ondemand::object main_object; if (json_parser.iterate(raw_json).get(document)) { - fprintf(stderr, "Can't parse %s\n", path.string().c_str()); + fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -283,9 +283,9 @@ void RunTask(const std::shared_ptr& result, if (root_error == simdjson::error_code::INCORRECT_TYPE) { fprintf(stderr, "Root value unexpected not an object for %s\n\n", - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); } else { - fprintf(stderr, "Can't parse %s\n", path.string().c_str()); + fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str()); } result->exit_code_ = ExitCode::kGenericUserError; return; @@ -294,8 +294,9 @@ void RunTask(const std::shared_ptr& result, // If package_json object doesn't have "scripts" field, throw an error. simdjson::ondemand::object scripts_object; if (main_object["scripts"].get_object().get(scripts_object)) { - fprintf( - stderr, "Can't find \"scripts\" field in %s\n", path.string().c_str()); + fprintf(stderr, + "Can't find \"scripts\" field in %s\n", + ConvertPathToUTF8(path).c_str()); result->exit_code_ = ExitCode::kGenericUserError; return; } @@ -309,13 +310,13 @@ void RunTask(const std::shared_ptr& result, "Script \"%.*s\" is unexpectedly not a string for %s\n\n", static_cast(command_id.size()), command_id.data(), - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); } else { fprintf(stderr, "Missing script: \"%.*s\" for %s\n\n", static_cast(command_id.size()), command_id.data(), - path.string().c_str()); + ConvertPathToUTF8(path).c_str()); fprintf(stderr, "Available scripts are:\n"); // Reset the object to iterate over it again diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index e24117f6b16..7c1f6609f6f 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -5,8 +5,11 @@ common.requireNoPackageJSONAbove(); const { it, describe } = require('node:test'); const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); const envSuffix = common.isWindows ? '-windows' : ''; describe('node --run [command]', () => { @@ -201,6 +204,45 @@ describe('node --run [command]', () => { assert.strictEqual(child.code, 0); }); + it('handles package paths outside the active Windows code page', + { skip: !common.isWindows }, async () => { + tmpdir.refresh(); + + const projectDir = path.join(tmpdir.path, 'node-run-\u{20BB7}'); + const packageJsonPath = path.join(projectDir, 'package.json'); + const nodeModulesBin = path.join(projectDir, 'node_modules', '.bin'); + const checkScript = path.join(projectDir, 'check.js'); + + fs.mkdirSync(nodeModulesBin, { recursive: true }); + fs.writeFileSync(packageJsonPath, JSON.stringify({ + scripts: { + unicode: `"${process.execPath}" check.js`, + }, + })); + fs.writeFileSync(checkScript, ` + 'use strict'; + console.log(JSON.stringify({ + cwd: process.cwd(), + packageJsonPath: process.env.NODE_RUN_PACKAGE_JSON_PATH, + path: process.env.PATH, + })); + `); + + const child = await common.spawnPromisified( + process.execPath, + [ '--run', 'unicode'], + { cwd: projectDir }, + ); + + assert.strictEqual(child.stderr, ''); + assert.strictEqual(child.code, 0); + + const output = JSON.parse(child.stdout); + assert.strictEqual(output.cwd, projectDir); + assert.strictEqual(output.packageJsonPath, packageJsonPath); + assert.strictEqual(output.path.split(path.delimiter)[0], nodeModulesBin); + }); + it('returns error on unparsable file', async () => { const child = await common.spawnPromisified( process.execPath,