| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 89fe635 commit 352d635
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -180,6 +180,7 @@ The configuration currently reads the following top-level fields: | |||
| 180 | 180 | "useSnapshot": false, // Default: false | |
| 181 | 181 | "useCodeCache": true, // Default: false | |
| 182 | 182 | "execArgv": ["--no-warnings", "--max-old-space-size=4096"], // Optional | |
| 183 | + "execArgvExtension": "env", // Default: "env", options: "none", "env", "cli" | ||
| 183 | 184 | "assets": { // Optional | |
| 184 | 185 | "a.dat": "/path/to/a.dat", | |
| 185 | 186 | "b.txt": "/path/to/b.txt" | |
@@ -314,6 +315,42 @@ similar to what would happen if the application is started with: | |||
| 314 | 315 | node --no-warnings --max-old-space-size=2048 /path/to/bundled/script.js user-arg1 user-arg2 | |
| 315 | 316 | ``` | |
| 316 | 317 | ||
| 318 | + ### Execution argument extension | ||
| 319 | + | ||
| 320 | + The `execArgvExtension` field controls how additional execution arguments can be | ||
| 321 | + provided beyond those specified in the `execArgv` field. It accepts one of three string values: | ||
| 322 | + | ||
| 323 | + * `"none"`: No extension is allowed. Only the arguments specified in `execArgv` will be used, | ||
| 324 | + and the `NODE_OPTIONS` environment variable will be ignored. | ||
| 325 | + * `"env"`: _(Default)_ The `NODE_OPTIONS` environment variable can extend the execution arguments. | ||
| 326 | + This is the default behavior to maintain backward compatibility. | ||
| 327 | + * `"cli"`: The executable can be launched with `--node-options="--flag1 --flag2"`, and those flags | ||
| 328 | + will be parsed as execution arguments for Node.js instead of being passed to the user script. | ||
| 329 | + This allows using arguments that are not supported by the `NODE_OPTIONS` environment variable. | ||
| 330 | + | ||
| 331 | + For example, with `"execArgvExtension": "cli"`: | ||
| 332 | + | ||
| 333 | + ```json | ||
| 334 | + { | ||
| 335 | + "main": "/path/to/bundled/script.js", | ||
| 336 | + "output": "/path/to/write/the/generated/blob.blob", | ||
| 337 | + "execArgv": ["--no-warnings"], | ||
| 338 | + "execArgvExtension": "cli" | ||
| 339 | + } | ||
| 340 | + ``` | ||
| 341 | + | ||
| 342 | + The executable can be launched as: | ||
| 343 | + | ||
| 344 | + ```console | ||
| 345 | + ./my-sea --node-options="--trace-exit" user-arg1 user-arg2 | ||
| 346 | + ``` | ||
| 347 | + | ||
| 348 | + This would be equivalent to running: | ||
| 349 | + | ||
| 350 | + ```console | ||
| 351 | + node --no-warnings --trace-exit /path/to/bundled/script.js user-arg1 user-arg2 | ||
| 352 | + ``` | ||
| 353 | + | ||
| 317 | 354 | ## In the injected main script | |
| 318 | 355 | ||
| 319 | 356 | ### Single-executable application API | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -940,7 +940,17 @@ static ExitCode InitializeNodeWithArgsInternal( | |||
| 940 | 940 | } | |
| 941 | 941 | ||
| 942 | 942 | #if !defined(NODE_WITHOUT_NODE_OPTIONS) | |
| 943 | - if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) { | ||
| 943 | + bool should_parse_node_options = | ||
| 944 | + !(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv); | ||
| 945 | + #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION | ||
| 946 | + if (sea::IsSingleExecutable()) { | ||
| 947 | + sea::SeaResource sea_resource = sea::FindSingleExecutableResource(); | ||
| 948 | + if (sea_resource.exec_argv_extension != sea::SeaExecArgvExtension::kEnv) { | ||
| 949 | + should_parse_node_options = false; | ||
| 950 | + } | ||
| 951 | + } | ||
| 952 | + #endif | ||
| 953 | + if (should_parse_node_options) { | ||
| 944 | 954 | // NODE_OPTIONS environment variable is preferred over the file one. | |
| 945 | 955 | if (credentials::SafeGetenv("NODE_OPTIONS", &node_options) || | |
| 946 | 956 | !node_options.empty()) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ | |||
| 7 | 7 | #include "node_errors.h" | |
| 8 | 8 | #include "node_external_reference.h" | |
| 9 | 9 | #include "node_internals.h" | |
| 10 | + #include "node_options.h" | ||
| 10 | 11 | #include "node_snapshot_builder.h" | |
| 11 | 12 | #include "node_union_bytes.h" | |
| 12 | 13 | #include "node_v8_platform-inl.h" | |
@@ -86,6 +87,11 @@ size_t SeaSerializer::Write(const SeaResource& sea) { | |||
| 86 | 87 | uint32_t flags = static_cast<uint32_t>(sea.flags); | |
| 87 | 88 | Debug("Write SEA flags %x\n", flags); | |
| 88 | 89 | written_total += WriteArithmetic<uint32_t>(flags); | |
| 90 | + | ||
| 91 | + Debug("Write SEA resource exec argv extension %u\n", | ||
| 92 | + static_cast<uint8_t>(sea.exec_argv_extension)); | ||
| 93 | + written_total += | ||
| 94 | + WriteArithmetic<uint8_t>(static_cast<uint8_t>(sea.exec_argv_extension)); | ||
| 89 | 95 | DCHECK_EQ(written_total, SeaResource::kHeaderSize); | |
| 90 | 96 | ||
| 91 | 97 | Debug("Write SEA code path %p, size=%zu\n", | |
@@ -158,6 +164,11 @@ SeaResource SeaDeserializer::Read() { | |||
| 158 | 164 | CHECK_EQ(magic, kMagic); | |
| 159 | 165 | SeaFlags flags(static_cast<SeaFlags>(ReadArithmetic<uint32_t>())); | |
| 160 | 166 | Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags)); | |
| 167 | + | ||
| 168 | + uint8_t extension_value = ReadArithmetic<uint8_t>(); | ||
| 169 | + SeaExecArgvExtension exec_argv_extension = | ||
| 170 | + static_cast<SeaExecArgvExtension>(extension_value); | ||
| 171 | + Debug("Read SEA resource exec argv extension %u\n", extension_value); | ||
| 161 | 172 | CHECK_EQ(read_total, SeaResource::kHeaderSize); | |
| 162 | 173 | ||
| 163 | 174 | std::string_view code_path = | |
@@ -212,7 +223,13 @@ SeaResource SeaDeserializer::Read() { | |||
| 212 | 223 | exec_argv.emplace_back(arg); | |
| 213 | 224 | } | |
| 214 | 225 | } | |
| 215 | - return {flags, code_path, code, code_cache, assets, exec_argv}; | ||
| 226 | + return {flags, | ||
| 227 | + exec_argv_extension, | ||
| 228 | + code_path, | ||
| 229 | + code, | ||
| 230 | + code_cache, | ||
| 231 | + assets, | ||
| 232 | + exec_argv}; | ||
| 216 | 233 | } | |
| 217 | 234 | ||
| 218 | 235 | std::string_view FindSingleExecutableBlob() { | |
@@ -297,26 +314,55 @@ std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) { | |||
| 297 | 314 | if (IsSingleExecutable()) { | |
| 298 | 315 | static std::vector<char*> new_argv; | |
| 299 | 316 | static std::vector<std::string> exec_argv_storage; | |
| 317 | + static std::vector<std::string> cli_extension_args; | ||
| 300 | 318 | ||
| 301 | 319 | SeaResource sea_resource = FindSingleExecutableResource(); | |
| 302 | 320 | ||
| 303 | 321 | new_argv.clear(); | |
| 304 | 322 | exec_argv_storage.clear(); | |
| 323 | + cli_extension_args.clear(); | ||
| 324 | + | ||
| 325 | + // Handle CLI extension mode for --node-options | ||
| 326 | + if (sea_resource.exec_argv_extension == SeaExecArgvExtension::kCli) { | ||
| 327 | + // Extract --node-options and filter argv | ||
| 328 | + for (int i = 1; i < argc; ++i) { | ||
| 329 | + if (strncmp(argv[i], "--node-options=", 15) == 0) { | ||
| 330 | + std::string node_options = argv[i] + 15; | ||
| 331 | + std::vector<std::string> errors; | ||
| 332 | + cli_extension_args = ParseNodeOptionsEnvVar(node_options, &errors); | ||
| 333 | + // Remove this argument by shifting the rest | ||
| 334 | + for (int j = i; j < argc - 1; ++j) { | ||
| 335 | + argv[j] = argv[j + 1]; | ||
| 336 | + } | ||
| 337 | + argc--; | ||
| 338 | + i--; // Adjust index since we removed an element | ||
| 339 | + } | ||
| 340 | + } | ||
| 341 | + } | ||
| 305 | 342 | ||
| 306 | - // Reserve space for argv[0], exec argv, original argv, and nullptr | ||
| 307 | - new_argv.reserve(argc + sea_resource.exec_argv.size() + 2); | ||
| 343 | + // Reserve space for argv[0], exec argv, cli extension args, original argv, | ||
| 344 | + // and nullptr | ||
| 345 | + new_argv.reserve(argc + sea_resource.exec_argv.size() + | ||
| 346 | + cli_extension_args.size() + 2); | ||
| 308 | 347 | new_argv.emplace_back(argv[0]); | |
| 309 | 348 | ||
| 310 | 349 | // Insert exec argv from SEA config | |
| 311 | 350 | if (!sea_resource.exec_argv.empty()) { | |
| 312 | - exec_argv_storage.reserve(sea_resource.exec_argv.size()); | ||
| 351 | + exec_argv_storage.reserve(sea_resource.exec_argv.size() + | ||
| 352 | + cli_extension_args.size()); | ||
| 313 | 353 | for (const auto& arg : sea_resource.exec_argv) { | |
| 314 | 354 | exec_argv_storage.emplace_back(arg); | |
| 315 | 355 | new_argv.emplace_back(exec_argv_storage.back().data()); | |
| 316 | 356 | } | |
| 317 | 357 | } | |
| 318 | 358 | ||
| 319 | - // Add actual run time arguments. | ||
| 359 | + // Insert CLI extension args | ||
| 360 | + for (const auto& arg : cli_extension_args) { | ||
| 361 | + exec_argv_storage.emplace_back(arg); | ||
| 362 | + new_argv.emplace_back(exec_argv_storage.back().data()); | ||
| 363 | + } | ||
| 364 | + | ||
| 365 | + // Add actual run time arguments | ||
| 320 | 366 | new_argv.insert(new_argv.end(), argv, argv + argc); | |
| 321 | 367 | new_argv.emplace_back(nullptr); | |
| 322 | 368 | argc = new_argv.size() - 1; | |
@@ -332,6 +378,7 @@ struct SeaConfig { | |||
| 332 | 378 | std::string main_path; | |
| 333 | 379 | std::string output_path; | |
| 334 | 380 | SeaFlags flags = SeaFlags::kDefault; | |
| 381 | + SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; | ||
| 335 | 382 | std::unordered_map<std::string, std::string> assets; | |
| 336 | 383 | std::vector<std::string> exec_argv; | |
| 337 | 384 | }; | |
@@ -475,6 +522,27 @@ std::optional<SeaConfig> ParseSingleExecutableConfig( | |||
| 475 | 522 | result.flags |= SeaFlags::kIncludeExecArgv; | |
| 476 | 523 | result.exec_argv = std::move(exec_argv); | |
| 477 | 524 | } | |
| 525 | + } else if (key == "execArgvExtension") { | ||
| 526 | + std::string_view extension_str; | ||
| 527 | + if (field.value().get_string().get(extension_str)) { | ||
| 528 | + FPrintF(stderr, | ||
| 529 | + "\"execArgvExtension\" field of %s is not a string\n", | ||
| 530 | + config_path); | ||
| 531 | + return std::nullopt; | ||
| 532 | + } | ||
| 533 | + if (extension_str == "none") { | ||
| 534 | + result.exec_argv_extension = SeaExecArgvExtension::kNone; | ||
| 535 | + } else if (extension_str == "env") { | ||
| 536 | + result.exec_argv_extension = SeaExecArgvExtension::kEnv; | ||
| 537 | + } else if (extension_str == "cli") { | ||
| 538 | + result.exec_argv_extension = SeaExecArgvExtension::kCli; | ||
| 539 | + } else { | ||
| 540 | + FPrintF(stderr, | ||
| 541 | + "\"execArgvExtension\" field of %s must be one of " | ||
| 542 | + "\"none\", \"env\", or \"cli\"\n", | ||
| 543 | + config_path); | ||
| 544 | + return std::nullopt; | ||
| 545 | + } | ||
| 478 | 546 | } | |
| 479 | 547 | } | |
| 480 | 548 | ||
@@ -674,6 +742,7 @@ ExitCode GenerateSingleExecutableBlob( | |||
| 674 | 742 | } | |
| 675 | 743 | SeaResource sea{ | |
| 676 | 744 | config.flags, | |
| 745 | + config.exec_argv_extension, | ||
| 677 | 746 | config.main_path, | |
| 678 | 747 | builds_snapshot_from_main | |
| 679 | 748 | ? std::string_view{snapshot_blob.data(), snapshot_blob.size()} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,8 +31,15 @@ enum class SeaFlags : uint32_t { | |||
| 31 | 31 | kIncludeExecArgv = 1 << 4, | |
| 32 | 32 | }; | |
| 33 | 33 | ||
| 34 | + enum class SeaExecArgvExtension : uint8_t { | ||
| 35 | + kNone = 0, | ||
| 36 | + kEnv = 1, | ||
| 37 | + kCli = 2, | ||
| 38 | + }; | ||
| 39 | + | ||
| 34 | 40 | struct SeaResource { | |
| 35 | 41 | SeaFlags flags = SeaFlags::kDefault; | |
| 42 | + SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv; | ||
| 36 | 43 | std::string_view code_path; | |
| 37 | 44 | std::string_view main_code_or_snapshot; | |
| 38 | 45 | std::optional<std::string_view> code_cache; | |
@@ -42,7 +49,8 @@ struct SeaResource { | |||
| 42 | 49 | bool use_snapshot() const; | |
| 43 | 50 | bool use_code_cache() const; | |
| 44 | 51 | ||
| 45 | - static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); | ||
| 52 | + static constexpr size_t kHeaderSize = | ||
| 53 | + sizeof(kMagic) + sizeof(SeaFlags) + sizeof(SeaExecArgvExtension); | ||
| 46 | 54 | }; | |
| 47 | 55 | ||
| 48 | 56 | bool IsSingleExecutable(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,14 @@ | |||
| 1 | + const assert = require('assert'); | ||
| 2 | + | ||
| 3 | + console.log('process.argv:', JSON.stringify(process.argv)); | ||
| 4 | + console.log('process.execArgv:', JSON.stringify(process.execArgv)); | ||
| 5 | + | ||
| 6 | + // Should have execArgv from SEA config + CLI --node-options | ||
| 7 | + assert.deepStrictEqual(process.execArgv, ['--no-warnings', '--max-old-space-size=1024']); | ||
| 8 | + | ||
| 9 | + assert.deepStrictEqual(process.argv.slice(2), [ | ||
| 10 | + 'user-arg1', | ||
| 11 | + 'user-arg2' | ||
| 12 | + ]); | ||
| 13 | + | ||
| 14 | + console.log('execArgvExtension cli test passed'); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,19 @@ | |||
| 1 | + const assert = require('assert'); | ||
| 2 | + | ||
| 3 | + process.emitWarning('This warning should not be shown in the output', 'TestWarning'); | ||
| 4 | + | ||
| 5 | + console.log('process.argv:', JSON.stringify(process.argv)); | ||
| 6 | + console.log('process.execArgv:', JSON.stringify(process.execArgv)); | ||
| 7 | + | ||
| 8 | + // Should have execArgv from SEA config. | ||
| 9 | + // Note that flags from NODE_OPTIONS are not included in process.execArgv no matter it's | ||
| 10 | + // an SEA or not, but we can test whether it works by checking that the warning emitted | ||
| 11 | + // above was silenced. | ||
| 12 | + assert.deepStrictEqual(process.execArgv, ['--no-warnings']); | ||
| 13 | + | ||
| 14 | + assert.deepStrictEqual(process.argv.slice(2), [ | ||
| 15 | + 'user-arg1', | ||
| 16 | + 'user-arg2' | ||
| 17 | + ]); | ||
| 18 | + | ||
| 19 | + console.log('execArgvExtension env test passed'); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,14 @@ | |||
| 1 | + const assert = require('assert'); | ||
| 2 | + | ||
| 3 | + console.log('process.argv:', JSON.stringify(process.argv)); | ||
| 4 | + console.log('process.execArgv:', JSON.stringify(process.execArgv)); | ||
| 5 | + | ||
| 6 | + // Should only have execArgv from SEA config, no NODE_OPTIONS | ||
| 7 | + assert.deepStrictEqual(process.execArgv, ['--no-warnings']); | ||
| 8 | + | ||
| 9 | + assert.deepStrictEqual(process.argv.slice(2), [ | ||
| 10 | + 'user-arg1', | ||
| 11 | + 'user-arg2' | ||
| 12 | + ]); | ||
| 13 | + | ||
| 14 | + console.log('execArgvExtension none test passed'); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,63 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + require('../common'); | ||
| 4 | + | ||
| 5 | + const { | ||
| 6 | + generateSEA, | ||
| 7 | + skipIfSingleExecutableIsNotSupported, | ||
| 8 | + } = require('../common/sea'); | ||
| 9 | + | ||
| 10 | + skipIfSingleExecutableIsNotSupported(); | ||
| 11 | + | ||
| 12 | + // This tests the execArgvExtension "cli" mode in single executable applications. | ||
| 13 | + | ||
| 14 | + const fixtures = require('../common/fixtures'); | ||
| 15 | + const tmpdir = require('../common/tmpdir'); | ||
| 16 | + const { copyFileSync, writeFileSync, existsSync } = require('fs'); | ||
| 17 | + const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); | ||
| 18 | + const { join } = require('path'); | ||
| 19 | + const assert = require('assert'); | ||
| 20 | + | ||
| 21 | + const configFile = tmpdir.resolve('sea-config.json'); | ||
| 22 | + const seaPrepBlob = tmpdir.resolve('sea-prep.blob'); | ||
| 23 | + const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea'); | ||
| 24 | + | ||
| 25 | + tmpdir.refresh(); | ||
| 26 | + | ||
| 27 | + // Copy test fixture to working directory | ||
| 28 | + copyFileSync(fixtures.path('sea-exec-argv-extension-cli.js'), tmpdir.resolve('sea.js')); | ||
| 29 | + | ||
| 30 | + writeFileSync(configFile, ` | ||
| 31 | + { | ||
| 32 | + "main": "sea.js", | ||
| 33 | + "output": "sea-prep.blob", | ||
| 34 | + "disableExperimentalSEAWarning": true, | ||
| 35 | + "execArgv": ["--no-warnings"], | ||
| 36 | + "execArgvExtension": "cli" | ||
| 37 | + } | ||
| 38 | + `); | ||
| 39 | + | ||
| 40 | + spawnSyncAndExitWithoutError( | ||
| 41 | + process.execPath, | ||
| 42 | + ['--experimental-sea-config', 'sea-config.json'], | ||
| 43 | + { cwd: tmpdir.path }); | ||
| 44 | + | ||
| 45 | + assert(existsSync(seaPrepBlob)); | ||
| 46 | + | ||
| 47 | + generateSEA(outputFile, process.execPath, seaPrepBlob); | ||
| 48 | + | ||
| 49 | + // Test that --node-options works with execArgvExtension: "cli" | ||
| 50 | + spawnSyncAndAssert( | ||
| 51 | + outputFile, | ||
| 52 | + ['--node-options=--max-old-space-size=1024', 'user-arg1', 'user-arg2'], | ||
| 53 | + { | ||
| 54 | + env: { | ||
| 55 | + ...process.env, | ||
| 56 | + NODE_OPTIONS: '--max-old-space-size=2048', // Should be ignored | ||
| 57 | + COMMON_DIRECTORY: join(__dirname, '..', 'common'), | ||
| 58 | + NODE_DEBUG_NATIVE: 'SEA', | ||
| 59 | + } | ||
| 60 | + }, | ||
| 61 | + { | ||
| 62 | + stdout: /execArgvExtension cli test passed/ | ||
| 63 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments