From 39db6a0e64eadf135319ec27588fd7a505fe18f9 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Wed, 29 Apr 2026 11:59:55 +0200 Subject: [PATCH 1/2] src: support multiple versions in node.config.json Signed-off-by: Marco Ippolito --- doc/api/cli.md | 38 + doc/node-config-schema.json | 2074 +++++++++++++++-------------- lib/internal/options.js | 60 +- src/node_config_file.cc | 243 +++- src/node_config_file.h | 8 + test/parallel/test-config-file.js | 227 ++++ 6 files changed, 1610 insertions(+), 1040 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index e979ec95c4259d..d6ac9712d5662f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1091,6 +1091,44 @@ The configuration file supports namespace-specific options: * Namespace fields like `test`, `watch`, and `permission` contain configuration specific to that subsystem. +The configuration file can target a specific Node.js major version with +`nodeVersion`: + +```json +{ + "nodeVersion": 25, + "nodeOptions": { + "watch-path": "src" + } +} +``` + +To keep multiple version-specific configurations in the same file, use the +`configs` array. Node.js will use the first entry whose `nodeVersion` matches +the current Node.js major version: + +```json +{ + "$schema": "https://nodejs.org/dist/latest-v26.x/docs/node-config-schema.json", + "configs": [ + { + "nodeVersion": 25, + "config": { + "$schema": "https://nodejs.org/dist/latest-v25.x/docs/node-config-schema.json", + "nodeOptions": { + "watch-path": "src" + } + } + } + ] +} +``` + +When `configs` is used, the top level may only contain `$schema` and +`configs`. Each `configs` item must define an integer `nodeVersion` and an +object `config`. A single top-level config does not require `nodeVersion`, but +if present it must match the current Node.js major version. + When a namespace is present in the configuration file, Node.js automatically enables the corresponding flag (e.g., `--test`, `--watch`, `--permission`). This allows you to configure diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index d33c73e9b4c556..a78031061dca87 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -1,1036 +1,1088 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "additionalProperties": false, - "required": [], - "properties": { - "$schema": { - "type": "string" + "oneOf": [ + { + "$ref": "#/$defs/config" }, - "nodeOptions": { - "additionalProperties": false, - "required": [], - "properties": { - "addons": { - "type": "boolean", - "description": "disable loading native addons" - }, - "allow-addons": { - "type": "boolean", - "description": "allow use of addons when any permissions are set" - }, - "allow-child-process": { - "type": "boolean", - "description": "allow use of child process when any permissions are set" - }, - "allow-ffi": { - "type": "boolean", - "description": "allow use of FFI when any permissions are set (only in builds with FFI support)" - }, - "allow-fs-read": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "allow permissions to read the filesystem" - }, - "allow-fs-write": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "allow permissions to write in the filesystem" - }, - "allow-inspector": { - "type": "boolean", - "description": "allow use of inspector when any permissions are set" - }, - "allow-net": { - "type": "boolean", - "description": "allow use of network when any permissions are set" - }, - "allow-wasi": { - "type": "boolean", - "description": "allow wasi when any permissions are set" - }, - "allow-worker": { - "type": "boolean", - "description": "allow worker threads when any permissions are set" - }, - "async-context-frame": { - "type": "boolean", - "description": "Improve AsyncLocalStorage performance with AsyncContextFrame" - }, - "conditions": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "additional user conditions for conditional exports and imports" - }, - "cpu-prof": { - "type": "boolean", - "description": "Start the V8 CPU profiler on start up, and write the CPU profile to disk before exit. If --cpu-prof-dir is not specified, write the profile to the current working directory." - }, - "cpu-prof-dir": { - "type": "string", - "description": "Directory where the V8 profiles generated by --cpu-prof will be placed. Does not affect --prof." - }, - "cpu-prof-interval": { - "type": "number", - "description": "specified sampling interval in microseconds for the V8 CPU profile generated with --cpu-prof. (default: 1000)" - }, - "cpu-prof-name": { - "type": "string", - "description": "specified file name of the V8 CPU profile generated with --cpu-prof" - }, - "debug-arraybuffer-allocations": { - "type": "boolean", - "description": "" - }, - "deprecation": { - "type": "boolean", - "description": "silence deprecation warnings" - }, - "diagnostic-dir": { - "type": "string", - "description": "set dir for all output files (default: current working directory)" - }, - "disable-proto": { - "type": "string", - "description": "disable Object.prototype.__proto__" - }, - "disable-sigusr1": { - "type": "boolean", - "description": "Disable inspector thread to be listening for SIGUSR1 signal" - }, - "disable-warning": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "silence specific process warnings" - }, - "disable-wasm-trap-handler": { - "type": "boolean", - "description": "Disable trap-handler-based WebAssembly bound checks. V8 will insert inline bound checks when compiling WebAssembly which may slow down performance." - }, - "dns-result-order": { - "type": "string", - "description": "set default value of verbatim in dns.lookup. Options are 'ipv4first' (IPv4 addresses are placed before IPv6 addresses) 'ipv6first' (IPv6 addresses are placed before IPv4 addresses) 'verbatim' (addresses are in the order the DNS resolver returned)" - }, - "enable-fips": { - "type": "boolean", - "description": "enable FIPS crypto at startup" - }, - "enable-source-maps": { - "type": "boolean", - "description": "Source Map V3 support for stack traces" - }, - "entry-url": { - "type": "boolean", - "description": "Treat the entrypoint as a URL" - }, - "experimental-addon-modules": { - "type": "boolean", - "description": "experimental import support for addons" - }, - "experimental-detect-module": { - "type": "boolean", - "description": "when ambiguous modules fail to evaluate because they contain ES module syntax, try again to evaluate them as ES modules" - }, - "experimental-eventsource": { - "type": "boolean", - "description": "experimental EventSource API" - }, - "experimental-ffi": { - "type": "boolean", - "description": "experimental node:ffi module (only in builds with FFI support)" - }, - "experimental-global-navigator": { - "type": "boolean", - "description": "expose experimental Navigator API on the global scope" - }, - "experimental-import-meta-resolve": { - "type": "boolean", - "description": "experimental ES Module import.meta.resolve() parentURL support" - }, - "experimental-loader": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "use the specified module as a custom loader" - }, - "experimental-print-required-tla": { - "type": "boolean", - "description": "Print pending top-level await. If --require-module is true, evaluate asynchronous graphs loaded by `require()` but do not run the microtasks, in order to to find and print top-level await in the graph" - }, - "experimental-repl-await": { - "type": "boolean", - "description": "experimental await keyword support in REPL" - }, - "experimental-require-module": { - "type": "boolean", - "description": "Legacy alias for --require-module" - }, - "experimental-shadow-realm": { - "type": "boolean", - "description": "" - }, - "experimental-sqlite": { - "type": "boolean", - "description": "experimental node:sqlite module" - }, - "experimental-vm-modules": { - "type": "boolean", - "description": "experimental ES Module support in vm module" - }, - "experimental-websocket": { - "type": "boolean", - "description": "experimental WebSocket API" - }, - "experimental-webstorage": { - "type": "boolean", - "description": "experimental Web Storage API" - }, - "extra-info-on-fatal-exception": { - "type": "boolean", - "description": "hide extra information on fatal exception that causes exit" - }, - "force-async-hooks-checks": { - "type": "boolean", - "description": "disable checks for async_hooks" - }, - "force-context-aware": { - "type": "boolean", - "description": "disable loading non-context-aware addons" - }, - "force-fips": { - "type": "boolean", - "description": "force FIPS crypto (cannot be disabled)" - }, - "force-node-api-uncaught-exceptions-policy": { - "type": "boolean", - "description": "enforces 'uncaughtException' event on Node API asynchronous callbacks" - }, - "frozen-intrinsics": { - "type": "boolean", - "description": "experimental frozen intrinsics support" - }, - "global-search-paths": { - "type": "boolean", - "description": "disable global module search paths" - }, - "heap-prof": { - "type": "boolean", - "description": "Start the V8 heap profiler on start up, and write the heap profile to disk before exit. If --heap-prof-dir is not specified, write the profile to the current working directory." - }, - "heap-prof-dir": { - "type": "string", - "description": "Directory where the V8 heap profiles generated by --heap-prof will be placed." - }, - "heap-prof-interval": { - "type": "number", - "description": "specified sampling interval in bytes for the V8 heap profile generated with --heap-prof. (default: 512 * 1024)" - }, - "heap-prof-name": { - "type": "string", - "description": "specified file name of the V8 heap profile generated with --heap-prof" - }, - "heapsnapshot-near-heap-limit": { - "type": "number", - "description": "Generate heap snapshots whenever V8 is approaching the heap limit. No more than the specified number of heap snapshots will be generated." - }, - "heapsnapshot-signal": { - "type": "string", - "description": "Generate heap snapshot on specified signal" - }, - "icu-data-dir": { - "type": "string", - "description": "set ICU data load path to dir (overrides NODE_ICU_DATA) (note: linked-in ICU data is present)" - }, - "import": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "ES module to preload (option can be repeated)" - }, - "input-type": { - "type": "string", - "description": "set module type for string input" - }, - "insecure-http-parser": { - "type": "boolean", - "description": "use an insecure HTTP parser that accepts invalid HTTP headers" - }, - "inspect": { - "type": "boolean", - "description": "activate inspector on host:port (default: 127.0.0.1:9229)" - }, - "inspect-brk": { - "type": "boolean", - "description": "activate inspector on host:port and break at start of user script" - }, - "inspect-port": { - "type": "number", - "description": "set host:port for inspector" - }, - "inspect-publish-uid": { - "type": "string", - "description": "comma separated list of destinations for inspector uid(default: stderr,http)" - }, - "inspect-wait": { - "type": "boolean", - "description": "activate inspector on host:port and wait for debugger to be attached" - }, - "localstorage-file": { - "type": "string", - "description": "file used to persist localStorage data" - }, - "max-http-header-size": { - "type": "number", - "description": "set the maximum size of HTTP headers (default: 16384 (16KB))" - }, - "max-old-space-size-percentage": { - "type": "string", - "description": "set V8's max old space size as a percentage of available memory (e.g., '50%'). Takes precedence over --max-old-space-size." - }, - "network-family-autoselection": { - "type": "boolean", - "description": "Disable network address family autodetection algorithm" - }, - "network-family-autoselection-attempt-timeout": { - "type": "number", - "description": "Sets the default value for the network family autoselection attempt timeout." - }, - "node-snapshot": { - "type": "boolean", - "description": "" - }, - "openssl-config": { - "type": "string", - "description": "load OpenSSL configuration from the specified file (overrides OPENSSL_CONF)" - }, - "openssl-legacy-provider": { - "type": "boolean", - "description": "enable OpenSSL 3.0 legacy provider" - }, - "openssl-shared-config": { - "type": "boolean", - "description": "enable OpenSSL shared configuration" - }, - "pending-deprecation": { - "type": "boolean", - "description": "emit pending deprecation warnings" - }, - "permission": { - "type": "boolean", - "description": "enable the permission system" - }, - "preserve-symlinks": { - "type": "boolean", - "description": "preserve symbolic links when resolving" - }, - "preserve-symlinks-main": { - "type": "boolean", - "description": "preserve symbolic links when resolving the main module" - }, - "redirect-warnings": { - "type": "string", - "description": "write warnings to file instead of stderr" - }, - "report-compact": { - "type": "boolean", - "description": "output compact single-line JSON" - }, - "report-dir": { - "type": "string", - "description": "define custom report pathname. (default: current working directory)" - }, - "report-exclude-env": { - "type": "boolean", - "description": "Exclude environment variables when generating report (default: false)" - }, - "report-exclude-network": { - "type": "boolean", - "description": "exclude network interface diagnostics. (default: false)" - }, - "report-filename": { - "type": "string", - "description": "define custom report file name. (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)" - }, - "report-on-fatalerror": { - "type": "boolean", - "description": "generate diagnostic report on fatal (internal) errors" - }, - "report-on-signal": { - "type": "boolean", - "description": "generate diagnostic report upon receiving signals" - }, - "report-signal": { - "type": "string", - "description": "causes diagnostic report to be produced on provided signal, unsupported in Windows. (default: SIGUSR2)" - }, - "report-uncaught-exception": { - "type": "boolean", - "description": "generate diagnostic report on uncaught exceptions" - }, - "require": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "CommonJS module to preload (option can be repeated)" - }, - "require-module": { - "type": "boolean", - "description": "Allow loading synchronous ES Modules in require()." - }, - "secure-heap": { - "type": "number", - "description": "total size of the OpenSSL secure heap" - }, - "secure-heap-min": { - "type": "number", - "description": "minimum allocation size from the OpenSSL secure heap" - }, - "snapshot-blob": { - "type": "string", - "description": "Path to the snapshot blob that's either the result of snapshotbuilding, or the blob that is used to restore the application state" - }, - "stack-trace-limit": { - "type": "number", - "description": "" - }, - "strip-types": { - "type": "boolean", - "description": "Type-stripping for TypeScript files." - }, - "test-coverage-branches": { - "type": "number", - "description": "the branch coverage minimum threshold" - }, - "test-coverage-exclude": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "exclude files from coverage report that match this glob pattern" - }, - "test-coverage-functions": { - "type": "number", - "description": "the function coverage minimum threshold" - }, - "test-coverage-include": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "include files in coverage report that match this glob pattern" - }, - "test-coverage-lines": { - "type": "number", - "description": "the line coverage minimum threshold" - }, - "test-global-setup": { - "type": "string", - "description": "specifies the path to the global setup file" - }, - "test-isolation": { - "type": "string", - "description": "configures the type of test isolation used in the test runner" - }, - "test-name-pattern": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "run tests whose name matches this regular expression" - }, - "test-only": { - "type": "boolean", - "description": "run tests with 'only' option set" - }, - "test-random-seed": { - "type": "number", - "description": "seed used to randomize test execution order" - }, - "test-randomize": { - "type": "boolean", - "description": "run tests in a random order" - }, - "test-reporter": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "report test output using the given reporter" - }, - "test-reporter-destination": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "report given reporter to the given destination" - }, - "test-rerun-failures": { - "type": "string", - "description": "specifies the path to the rerun state file" - }, - "test-shard": { - "type": "string", - "description": "run test at specific shard" - }, - "test-skip-pattern": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "run tests whose name do not match this regular expression" - }, - "throw-deprecation": { - "type": "boolean", - "description": "throw an exception on deprecations" - }, - "title": { - "type": "string", - "description": "the process title to use on startup" - }, - "tls-cipher-list": { - "type": "string", - "description": "use an alternative default TLS cipher list" - }, - "tls-keylog": { - "type": "string", - "description": "log TLS decryption keys to named file for traffic analysis" - }, - "tls-max-v1.2": { - "type": "boolean", - "description": "set default TLS maximum to TLSv1.2 (default: TLSv1.3)" - }, - "tls-max-v1.3": { - "type": "boolean", - "description": "set default TLS maximum to TLSv1.3 (default: TLSv1.3)" - }, - "tls-min-v1.0": { - "type": "boolean", - "description": "set default TLS minimum to TLSv1.0 (default: TLSv1.2)" - }, - "tls-min-v1.1": { - "type": "boolean", - "description": "set default TLS minimum to TLSv1.1 (default: TLSv1.2)" - }, - "tls-min-v1.2": { - "type": "boolean", - "description": "set default TLS minimum to TLSv1.2 (default: TLSv1.2)" - }, - "tls-min-v1.3": { - "type": "boolean", - "description": "set default TLS minimum to TLSv1.3 (default: TLSv1.2)" - }, - "trace-deprecation": { - "type": "boolean", - "description": "show stack traces on deprecations" - }, - "trace-env": { - "type": "boolean", - "description": "Print accesses to the environment variables" - }, - "trace-env-js-stack": { - "type": "boolean", - "description": "Print accesses to the environment variables and the JavaScript stack trace" - }, - "trace-env-native-stack": { - "type": "boolean", - "description": "Print accesses to the environment variables and the native stack trace" - }, - "trace-event-categories": { - "type": "string", - "description": "comma separated list of trace event categories to record" - }, - "trace-event-file-pattern": { - "type": "string", - "description": "Template string specifying the filepath for the trace-events data, it supports ${rotation} and ${pid}." - }, - "trace-exit": { - "type": "boolean", - "description": "show stack trace when an environment exits" - }, - "trace-promises": { - "type": "boolean", - "description": "show stack traces on promise initialization and resolution" - }, - "trace-require-module": { - "type": "string", - "description": "Print access to require(esm). Options are 'all' (print all usage) and 'no-node-modules' (excluding usage from the node_modules folder)" - }, - "trace-sigint": { - "type": "boolean", - "description": "enable printing JavaScript stacktrace on SIGINT" - }, - "trace-sync-io": { - "type": "boolean", - "description": "show stack trace when use of sync IO is detected after the first tick" - }, - "trace-tls": { - "type": "boolean", - "description": "prints TLS packet trace information to stderr" - }, - "trace-uncaught": { - "type": "boolean", - "description": "show stack traces for the `throw` behind uncaught exceptions" - }, - "trace-warnings": { - "type": "boolean", - "description": "show stack traces on process warnings" - }, - "track-heap-objects": { - "type": "boolean", - "description": "track heap object allocations for heap snapshots" - }, - "unhandled-rejections": { - "type": "string", - "description": "define unhandled rejections behavior. Options are 'strict' (always raise an error), 'throw' (raise an error unless 'unhandledRejection' hook is set), 'warn' (log a warning), 'none' (silence warnings), 'warn-with-error-code' (log a warning and set exit code 1 unless 'unhandledRejection' hook is set). (default: throw)" - }, - "use-bundled-ca": { - "type": "boolean", - "description": "use bundled CA store (default)" - }, - "use-env-proxy": { - "type": "boolean", - "description": "parse proxy settings from HTTP_PROXY/HTTPS_PROXY/NO_PROXYenvironment variables and apply the setting in global HTTP/HTTPS clients" - }, - "use-largepages": { - "type": "string", - "description": "Map the Node.js static code to large pages. Options are 'off' (the default value, meaning do not map), 'on' (map and ignore failure, reporting it to stderr), or 'silent' (map and silently ignore failure)" - }, - "use-openssl-ca": { - "type": "boolean", - "description": "use OpenSSL's default CA store" - }, - "use-system-ca": { - "type": "boolean", - "description": "use system's CA store" - }, - "v8-pool-size": { - "type": "number", - "description": "set V8's thread pool size" - }, - "verify-base-objects": { - "type": "boolean", - "description": "" - }, - "warnings": { - "type": "boolean", - "description": "silence all process warnings" - }, - "watch": { - "type": "boolean", - "description": "run in watch mode" - }, - "watch-kill-signal": { - "type": "string", - "description": "kill signal to send to the process on watch mode restarts(default: SIGTERM)" - }, - "watch-path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "path to watch" - }, - "watch-preserve-output": { - "type": "boolean", - "description": "preserve outputs on watch mode restart" - }, - "zero-fill-buffers": { - "type": "boolean", - "description": "automatically zero-fill all newly allocated Buffer instances" - } - }, - "type": "object" - }, - "permission": { + { "type": "object", "additionalProperties": false, - "required": [], + "required": [ + "configs" + ], "properties": { - "allow-addons": { - "type": "boolean", - "description": "allow use of addons when any permissions are set" - }, - "allow-ffi": { - "type": "boolean", - "description": "allow use of FFI when any permissions are set (only in builds with FFI support)" - }, - "allow-child-process": { - "type": "boolean", - "description": "allow use of child process when any permissions are set" - }, - "allow-fs-read": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" + "$schema": { + "type": "string" + }, + "configs": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "nodeVersion", + "config" + ], + "properties": { + "nodeVersion": { + "type": "integer" + }, + "config": { + "$ref": "#/$defs/config" } } - ], - "description": "allow permissions to read the filesystem" - }, - "allow-fs-write": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "allow permissions to write in the filesystem" - }, - "allow-inspector": { - "type": "boolean", - "description": "allow use of inspector when any permissions are set" - }, - "allow-net": { - "type": "boolean", - "description": "allow use of network when any permissions are set" - }, - "allow-wasi": { - "type": "boolean", - "description": "allow wasi when any permissions are set" - }, - "allow-worker": { - "type": "boolean", - "description": "allow worker threads when any permissions are set" - }, - "permission": { - "type": "boolean", - "description": "enable the permission system" + } } } - }, - "test": { - "type": "object", + } + ], + "$defs": { + "config": { "additionalProperties": false, "required": [], "properties": { - "experimental-test-coverage": { - "type": "boolean", - "description": "enable code coverage in the test runner" - }, - "experimental-test-module-mocks": { - "type": "boolean", - "description": "enable module mocking in the test runner" - }, - "test": { - "type": "boolean", - "description": "launch test runner on startup" - }, - "test-concurrency": { - "type": "number", - "description": "specify test runner concurrency" - }, - "test-coverage-branches": { - "type": "number", - "description": "the branch coverage minimum threshold" - }, - "test-coverage-exclude": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "exclude files from coverage report that match this glob pattern" - }, - "test-coverage-functions": { - "type": "number", - "description": "the function coverage minimum threshold" - }, - "test-coverage-include": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "include files in coverage report that match this glob pattern" - }, - "test-coverage-lines": { - "type": "number", - "description": "the line coverage minimum threshold" - }, - "test-force-exit": { - "type": "boolean", - "description": "force test runner to exit upon completion" - }, - "test-global-setup": { - "type": "string", - "description": "specifies the path to the global setup file" - }, - "test-isolation": { - "type": "string", - "description": "configures the type of test isolation used in the test runner" - }, - "test-name-pattern": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } + "$schema": { + "type": "string" + }, + "nodeOptions": { + "additionalProperties": false, + "required": [], + "properties": { + "addons": { + "type": "boolean", + "description": "disable loading native addons" + }, + "allow-addons": { + "type": "boolean", + "description": "allow use of addons when any permissions are set" + }, + "allow-child-process": { + "type": "boolean", + "description": "allow use of child process when any permissions are set" + }, + "allow-ffi": { + "type": "boolean", + "description": "allow use of FFI when any permissions are set" + }, + "allow-fs-read": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "allow permissions to read the filesystem" + }, + "allow-fs-write": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "allow permissions to write in the filesystem" + }, + "allow-inspector": { + "type": "boolean", + "description": "allow use of inspector when any permissions are set" + }, + "allow-net": { + "type": "boolean", + "description": "allow use of network when any permissions are set" + }, + "allow-wasi": { + "type": "boolean", + "description": "allow wasi when any permissions are set" + }, + "allow-worker": { + "type": "boolean", + "description": "allow worker threads when any permissions are set" + }, + "async-context-frame": { + "type": "boolean", + "description": "Improve AsyncLocalStorage performance with AsyncContextFrame" + }, + "conditions": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "additional user conditions for conditional exports and imports" + }, + "cpu-prof": { + "type": "boolean", + "description": "Start the V8 CPU profiler on start up, and write the CPU profile to disk before exit. If --cpu-prof-dir is not specified, write the profile to the current working directory." + }, + "cpu-prof-dir": { + "type": "string", + "description": "Directory where the V8 profiles generated by --cpu-prof will be placed. Does not affect --prof." + }, + "cpu-prof-interval": { + "type": "number", + "description": "specified sampling interval in microseconds for the V8 CPU profile generated with --cpu-prof. (default: 1000)" + }, + "cpu-prof-name": { + "type": "string", + "description": "specified file name of the V8 CPU profile generated with --cpu-prof" + }, + "debug-arraybuffer-allocations": { + "type": "boolean", + "description": "" + }, + "deprecation": { + "type": "boolean", + "description": "silence deprecation warnings" + }, + "diagnostic-dir": { + "type": "string", + "description": "set dir for all output files (default: current working directory)" + }, + "disable-proto": { + "type": "string", + "description": "disable Object.prototype.__proto__" + }, + "disable-sigusr1": { + "type": "boolean", + "description": "Disable inspector thread to be listening for SIGUSR1 signal" + }, + "disable-warning": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "silence specific process warnings" + }, + "disable-wasm-trap-handler": { + "type": "boolean", + "description": "Disable trap-handler-based WebAssembly bound checks. V8 will insert inline bound checks when compiling WebAssembly which may slow down performance." + }, + "dns-result-order": { + "type": "string", + "description": "set default value of verbatim in dns.lookup. Options are 'ipv4first' (IPv4 addresses are placed before IPv6 addresses) 'ipv6first' (IPv6 addresses are placed before IPv4 addresses) 'verbatim' (addresses are in the order the DNS resolver returned)" + }, + "enable-fips": { + "type": "boolean", + "description": "enable FIPS crypto at startup" + }, + "enable-source-maps": { + "type": "boolean", + "description": "Source Map V3 support for stack traces" + }, + "entry-url": { + "type": "boolean", + "description": "Treat the entrypoint as a URL" + }, + "experimental-addon-modules": { + "type": "boolean", + "description": "experimental import support for addons" + }, + "experimental-detect-module": { + "type": "boolean", + "description": "when ambiguous modules fail to evaluate because they contain ES module syntax, try again to evaluate them as ES modules" + }, + "experimental-eventsource": { + "type": "boolean", + "description": "experimental EventSource API" + }, + "experimental-ffi": { + "type": "boolean", + "description": "experimental node:ffi module" + }, + "experimental-global-navigator": { + "type": "boolean", + "description": "expose experimental Navigator API on the global scope" + }, + "experimental-import-meta-resolve": { + "type": "boolean", + "description": "experimental ES Module import.meta.resolve() parentURL support" + }, + "experimental-loader": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "use the specified module as a custom loader" + }, + "experimental-print-required-tla": { + "type": "boolean", + "description": "Print pending top-level await. If --require-module is true, evaluate asynchronous graphs loaded by `require()` but do not run the microtasks, in order to to find and print top-level await in the graph" + }, + "experimental-repl-await": { + "type": "boolean", + "description": "experimental await keyword support in REPL" + }, + "experimental-require-module": { + "type": "boolean", + "description": "Legacy alias for --require-module" + }, + "experimental-shadow-realm": { + "type": "boolean", + "description": "" + }, + "experimental-sqlite": { + "type": "boolean", + "description": "experimental node:sqlite module" + }, + "experimental-stream-iter": { + "type": "boolean", + "description": "experimental iterable streams API (node:stream/iter)" + }, + "experimental-vm-modules": { + "type": "boolean", + "description": "experimental ES Module support in vm module" + }, + "experimental-websocket": { + "type": "boolean", + "description": "experimental WebSocket API" + }, + "experimental-webstorage": { + "type": "boolean", + "description": "experimental Web Storage API" + }, + "extra-info-on-fatal-exception": { + "type": "boolean", + "description": "hide extra information on fatal exception that causes exit" + }, + "force-async-hooks-checks": { + "type": "boolean", + "description": "disable checks for async_hooks" + }, + "force-context-aware": { + "type": "boolean", + "description": "disable loading non-context-aware addons" + }, + "force-fips": { + "type": "boolean", + "description": "force FIPS crypto (cannot be disabled)" + }, + "force-node-api-uncaught-exceptions-policy": { + "type": "boolean", + "description": "enforces 'uncaughtException' event on Node API asynchronous callbacks" + }, + "frozen-intrinsics": { + "type": "boolean", + "description": "experimental frozen intrinsics support" + }, + "global-search-paths": { + "type": "boolean", + "description": "disable global module search paths" + }, + "heap-prof": { + "type": "boolean", + "description": "Start the V8 heap profiler on start up, and write the heap profile to disk before exit. If --heap-prof-dir is not specified, write the profile to the current working directory." + }, + "heap-prof-dir": { + "type": "string", + "description": "Directory where the V8 heap profiles generated by --heap-prof will be placed." + }, + "heap-prof-interval": { + "type": "number", + "description": "specified sampling interval in bytes for the V8 heap profile generated with --heap-prof. (default: 512 * 1024)" + }, + "heap-prof-name": { + "type": "string", + "description": "specified file name of the V8 heap profile generated with --heap-prof" + }, + "heapsnapshot-near-heap-limit": { + "type": "number", + "description": "Generate heap snapshots whenever V8 is approaching the heap limit. No more than the specified number of heap snapshots will be generated." + }, + "heapsnapshot-signal": { + "type": "string", + "description": "Generate heap snapshot on specified signal" + }, + "icu-data-dir": { + "type": "string", + "description": "set ICU data load path to dir (overrides NODE_ICU_DATA) (note: linked-in ICU data is present)" + }, + "import": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "ES module to preload (option can be repeated)" + }, + "input-type": { + "type": "string", + "description": "set module type for string input" + }, + "insecure-http-parser": { + "type": "boolean", + "description": "use an insecure HTTP parser that accepts invalid HTTP headers" + }, + "inspect": { + "type": "boolean", + "description": "activate inspector on host:port (default: 127.0.0.1:9229)" + }, + "inspect-brk": { + "type": "boolean", + "description": "activate inspector on host:port and break at start of user script" + }, + "inspect-port": { + "type": "number", + "description": "set host:port for inspector" + }, + "inspect-publish-uid": { + "type": "string", + "description": "comma separated list of destinations for inspector uid(default: stderr,http)" + }, + "inspect-wait": { + "type": "boolean", + "description": "activate inspector on host:port and wait for debugger to be attached" + }, + "localstorage-file": { + "type": "string", + "description": "file used to persist localStorage data" + }, + "max-http-header-size": { + "type": "number", + "description": "set the maximum size of HTTP headers (default: 16384 (16KB))" + }, + "max-old-space-size-percentage": { + "type": "string", + "description": "set V8's max old space size as a percentage of available memory (e.g., '50%'). Takes precedence over --max-old-space-size." + }, + "network-family-autoselection": { + "type": "boolean", + "description": "Disable network address family autodetection algorithm" + }, + "network-family-autoselection-attempt-timeout": { + "type": "number", + "description": "Sets the default value for the network family autoselection attempt timeout." + }, + "node-snapshot": { + "type": "boolean", + "description": "" + }, + "openssl-config": { + "type": "string", + "description": "load OpenSSL configuration from the specified file (overrides OPENSSL_CONF)" + }, + "openssl-legacy-provider": { + "type": "boolean", + "description": "enable OpenSSL 3.0 legacy provider" + }, + "openssl-shared-config": { + "type": "boolean", + "description": "enable OpenSSL shared configuration" + }, + "pending-deprecation": { + "type": "boolean", + "description": "emit pending deprecation warnings" + }, + "permission": { + "type": "boolean", + "description": "enable the permission system" + }, + "permission-audit": { + "type": "boolean", + "description": "enable audit only for the permission system" + }, + "preserve-symlinks": { + "type": "boolean", + "description": "preserve symbolic links when resolving" + }, + "preserve-symlinks-main": { + "type": "boolean", + "description": "preserve symbolic links when resolving the main module" + }, + "redirect-warnings": { + "type": "string", + "description": "write warnings to file instead of stderr" + }, + "report-compact": { + "type": "boolean", + "description": "output compact single-line JSON" + }, + "report-dir": { + "type": "string", + "description": "define custom report pathname. (default: current working directory)" + }, + "report-exclude-env": { + "type": "boolean", + "description": "Exclude environment variables when generating report (default: false)" + }, + "report-exclude-network": { + "type": "boolean", + "description": "exclude network interface diagnostics. (default: false)" + }, + "report-filename": { + "type": "string", + "description": "define custom report file name. (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)" + }, + "report-on-fatalerror": { + "type": "boolean", + "description": "generate diagnostic report on fatal (internal) errors" + }, + "report-on-signal": { + "type": "boolean", + "description": "generate diagnostic report upon receiving signals" + }, + "report-signal": { + "type": "string", + "description": "causes diagnostic report to be produced on provided signal, unsupported in Windows. (default: SIGUSR2)" + }, + "report-uncaught-exception": { + "type": "boolean", + "description": "generate diagnostic report on uncaught exceptions" + }, + "require": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "CommonJS module to preload (option can be repeated)" + }, + "require-module": { + "type": "boolean", + "description": "Allow loading synchronous ES Modules in require()." + }, + "secure-heap": { + "type": "number", + "description": "total size of the OpenSSL secure heap" + }, + "secure-heap-min": { + "type": "number", + "description": "minimum allocation size from the OpenSSL secure heap" + }, + "snapshot-blob": { + "type": "string", + "description": "Path to the snapshot blob that's either the result of snapshotbuilding, or the blob that is used to restore the application state" + }, + "stack-trace-limit": { + "type": "number", + "description": "" + }, + "strip-types": { + "type": "boolean", + "description": "Type-stripping for TypeScript files." + }, + "test-coverage-branches": { + "type": "number", + "description": "the branch coverage minimum threshold" + }, + "test-coverage-exclude": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "exclude files from coverage report that match this glob pattern" + }, + "test-coverage-functions": { + "type": "number", + "description": "the function coverage minimum threshold" + }, + "test-coverage-include": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "include files in coverage report that match this glob pattern" + }, + "test-coverage-lines": { + "type": "number", + "description": "the line coverage minimum threshold" + }, + "test-global-setup": { + "type": "string", + "description": "specifies the path to the global setup file" + }, + "test-isolation": { + "type": "string", + "description": "configures the type of test isolation used in the test runner" + }, + "test-name-pattern": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "run tests whose name matches this regular expression" + }, + "test-only": { + "type": "boolean", + "description": "run tests with 'only' option set" + }, + "test-random-seed": { + "type": "number", + "description": "seed used to randomize test execution order" + }, + "test-randomize": { + "type": "boolean", + "description": "run tests in a random order" + }, + "test-reporter": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "report test output using the given reporter" + }, + "test-reporter-destination": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "report given reporter to the given destination" + }, + "test-rerun-failures": { + "type": "string", + "description": "specifies the path to the rerun state file" + }, + "test-shard": { + "type": "string", + "description": "run test at specific shard" + }, + "test-skip-pattern": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "run tests whose name do not match this regular expression" + }, + "throw-deprecation": { + "type": "boolean", + "description": "throw an exception on deprecations" + }, + "title": { + "type": "string", + "description": "the process title to use on startup" + }, + "tls-cipher-list": { + "type": "string", + "description": "use an alternative default TLS cipher list" + }, + "tls-keylog": { + "type": "string", + "description": "log TLS decryption keys to named file for traffic analysis" + }, + "tls-max-v1.2": { + "type": "boolean", + "description": "set default TLS maximum to TLSv1.2 (default: TLSv1.3)" + }, + "tls-max-v1.3": { + "type": "boolean", + "description": "set default TLS maximum to TLSv1.3 (default: TLSv1.3)" + }, + "tls-min-v1.0": { + "type": "boolean", + "description": "set default TLS minimum to TLSv1.0 (default: TLSv1.2)" + }, + "tls-min-v1.1": { + "type": "boolean", + "description": "set default TLS minimum to TLSv1.1 (default: TLSv1.2)" + }, + "tls-min-v1.2": { + "type": "boolean", + "description": "set default TLS minimum to TLSv1.2 (default: TLSv1.2)" + }, + "tls-min-v1.3": { + "type": "boolean", + "description": "set default TLS minimum to TLSv1.3 (default: TLSv1.2)" + }, + "trace-deprecation": { + "type": "boolean", + "description": "show stack traces on deprecations" + }, + "trace-env": { + "type": "boolean", + "description": "Print accesses to the environment variables" + }, + "trace-env-js-stack": { + "type": "boolean", + "description": "Print accesses to the environment variables and the JavaScript stack trace" + }, + "trace-env-native-stack": { + "type": "boolean", + "description": "Print accesses to the environment variables and the native stack trace" + }, + "trace-event-categories": { + "type": "string", + "description": "comma separated list of trace event categories to record" + }, + "trace-event-file-pattern": { + "type": "string", + "description": "Template string specifying the filepath for the trace-events data, it supports ${rotation} and ${pid}." + }, + "trace-exit": { + "type": "boolean", + "description": "show stack trace when an environment exits" + }, + "trace-promises": { + "type": "boolean", + "description": "show stack traces on promise initialization and resolution" + }, + "trace-require-module": { + "type": "string", + "description": "Print access to require(esm). Options are 'all' (print all usage) and 'no-node-modules' (excluding usage from the node_modules folder)" + }, + "trace-sigint": { + "type": "boolean", + "description": "enable printing JavaScript stacktrace on SIGINT" + }, + "trace-sync-io": { + "type": "boolean", + "description": "show stack trace when use of sync IO is detected after the first tick" + }, + "trace-tls": { + "type": "boolean", + "description": "prints TLS packet trace information to stderr" + }, + "trace-uncaught": { + "type": "boolean", + "description": "show stack traces for the `throw` behind uncaught exceptions" + }, + "trace-warnings": { + "type": "boolean", + "description": "show stack traces on process warnings" + }, + "track-heap-objects": { + "type": "boolean", + "description": "track heap object allocations for heap snapshots" + }, + "unhandled-rejections": { + "type": "string", + "description": "define unhandled rejections behavior. Options are 'strict' (always raise an error), 'throw' (raise an error unless 'unhandledRejection' hook is set), 'warn' (log a warning), 'none' (silence warnings), 'warn-with-error-code' (log a warning and set exit code 1 unless 'unhandledRejection' hook is set). (default: throw)" + }, + "use-bundled-ca": { + "type": "boolean", + "description": "use bundled CA store (default)" + }, + "use-env-proxy": { + "type": "boolean", + "description": "parse proxy settings from HTTP_PROXY/HTTPS_PROXY/NO_PROXYenvironment variables and apply the setting in global HTTP/HTTPS clients" + }, + "use-largepages": { + "type": "string", + "description": "Map the Node.js static code to large pages. Options are 'off' (the default value, meaning do not map), 'on' (map and ignore failure, reporting it to stderr), or 'silent' (map and silently ignore failure)" + }, + "use-openssl-ca": { + "type": "boolean", + "description": "use OpenSSL's default CA store" + }, + "use-system-ca": { + "type": "boolean", + "description": "use system's CA store" + }, + "v8-pool-size": { + "type": "number", + "description": "set V8's thread pool size" + }, + "verify-base-objects": { + "type": "boolean", + "description": "" + }, + "warnings": { + "type": "boolean", + "description": "silence all process warnings" + }, + "watch": { + "type": "boolean", + "description": "run in watch mode" + }, + "watch-kill-signal": { + "type": "string", + "description": "kill signal to send to the process on watch mode restarts(default: SIGTERM)" + }, + "watch-path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "path to watch" + }, + "watch-preserve-output": { + "type": "boolean", + "description": "preserve outputs on watch mode restart" + }, + "zero-fill-buffers": { + "type": "boolean", + "description": "automatically zero-fill all newly allocated Buffer instances" } - ], - "description": "run tests whose name matches this regular expression" - }, - "test-only": { - "type": "boolean", - "description": "run tests with 'only' option set" + }, + "type": "object" }, - "test-random-seed": { - "type": "number", - "description": "seed used to randomize test execution order" + "nodeVersion": { + "type": "integer" }, - "test-randomize": { - "type": "boolean", - "description": "run tests in a random order" - }, - "test-reporter": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "report test output using the given reporter" - }, - "test-reporter-destination": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } + "permission": { + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "allow-addons": { + "type": "boolean", + "description": "allow use of addons when any permissions are set" + }, + "allow-child-process": { + "type": "boolean", + "description": "allow use of child process when any permissions are set" + }, + "allow-ffi": { + "type": "boolean", + "description": "allow use of FFI when any permissions are set" + }, + "allow-fs-read": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "allow permissions to read the filesystem" + }, + "allow-fs-write": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "allow permissions to write in the filesystem" + }, + "allow-inspector": { + "type": "boolean", + "description": "allow use of inspector when any permissions are set" + }, + "allow-net": { + "type": "boolean", + "description": "allow use of network when any permissions are set" + }, + "allow-wasi": { + "type": "boolean", + "description": "allow wasi when any permissions are set" + }, + "allow-worker": { + "type": "boolean", + "description": "allow worker threads when any permissions are set" + }, + "permission": { + "type": "boolean", + "description": "enable the permission system" } - ], - "description": "report given reporter to the given destination" - }, - "test-rerun-failures": { - "type": "string", - "description": "specifies the path to the rerun state file" - }, - "test-shard": { - "type": "string", - "description": "run test at specific shard" + } }, - "test-skip-pattern": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } + "test": { + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "experimental-test-coverage": { + "type": "boolean", + "description": "enable code coverage in the test runner" + }, + "experimental-test-module-mocks": { + "type": "boolean", + "description": "enable module mocking in the test runner" + }, + "test": { + "type": "boolean", + "description": "launch test runner on startup" + }, + "test-concurrency": { + "type": "number", + "description": "specify test runner concurrency" + }, + "test-coverage-branches": { + "type": "number", + "description": "the branch coverage minimum threshold" + }, + "test-coverage-exclude": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "exclude files from coverage report that match this glob pattern" + }, + "test-coverage-functions": { + "type": "number", + "description": "the function coverage minimum threshold" + }, + "test-coverage-include": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "include files in coverage report that match this glob pattern" + }, + "test-coverage-lines": { + "type": "number", + "description": "the line coverage minimum threshold" + }, + "test-force-exit": { + "type": "boolean", + "description": "force test runner to exit upon completion" + }, + "test-global-setup": { + "type": "string", + "description": "specifies the path to the global setup file" + }, + "test-isolation": { + "type": "string", + "description": "configures the type of test isolation used in the test runner" + }, + "test-name-pattern": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "run tests whose name matches this regular expression" + }, + "test-only": { + "type": "boolean", + "description": "run tests with 'only' option set" + }, + "test-random-seed": { + "type": "number", + "description": "seed used to randomize test execution order" + }, + "test-randomize": { + "type": "boolean", + "description": "run tests in a random order" + }, + "test-reporter": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "report test output using the given reporter" + }, + "test-reporter-destination": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "report given reporter to the given destination" + }, + "test-rerun-failures": { + "type": "string", + "description": "specifies the path to the rerun state file" + }, + "test-shard": { + "type": "string", + "description": "run test at specific shard" + }, + "test-skip-pattern": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "run tests whose name do not match this regular expression" + }, + "test-timeout": { + "type": "number", + "description": "specify test runner timeout" + }, + "test-update-snapshots": { + "type": "boolean", + "description": "regenerate test snapshots" } - ], - "description": "run tests whose name do not match this regular expression" - }, - "test-timeout": { - "type": "number", - "description": "specify test runner timeout" + } }, - "test-update-snapshots": { - "type": "boolean", - "description": "regenerate test snapshots" - } - } - }, - "watch": { - "type": "object", - "additionalProperties": false, - "required": [], - "properties": { "watch": { - "type": "boolean", - "description": "run in watch mode" - }, - "watch-kill-signal": { - "type": "string", - "description": "kill signal to send to the process on watch mode restarts(default: SIGTERM)" - }, - "watch-path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "watch": { + "type": "boolean", + "description": "run in watch mode" + }, + "watch-kill-signal": { + "type": "string", + "description": "kill signal to send to the process on watch mode restarts(default: SIGTERM)" + }, + "watch-path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ], + "description": "path to watch" + }, + "watch-preserve-output": { + "type": "boolean", + "description": "preserve outputs on watch mode restart" } - ], - "description": "path to watch" - }, - "watch-preserve-output": { - "type": "boolean", - "description": "preserve outputs on watch mode restart" + } } - } + }, + "type": "object" } - }, - "type": "object" + } } diff --git a/lib/internal/options.js b/lib/internal/options.js index 92993d037fb653..3480f5794b76cc 100644 --- a/lib/internal/options.js +++ b/lib/internal/options.js @@ -65,9 +65,8 @@ function generateConfigJsonSchema() { return { __proto__: null, type, description }; } - const schema = { + const configSchema = { __proto__: null, - $schema: 'https://json-schema.org/draft/2020-12/schema', additionalProperties: false, required: [], properties: { @@ -82,13 +81,17 @@ function generateConfigJsonSchema() { properties: { __proto__: null }, type: 'object', }, + nodeVersion: { + __proto__: null, + type: 'integer', + }, __proto__: null, }, type: 'object', }; // Get the root properties object for adding namespaces - const rootProperties = schema.properties; + const rootProperties = configSchema.properties; const nodeOptions = rootProperties.nodeOptions.properties; // Add env options to nodeOptions (backward compatibility) @@ -130,7 +133,7 @@ function generateConfigJsonSchema() { ArrayPrototypeMap(sortedKeys, (key) => [key, nodeOptions[key]]), ); - schema.properties.nodeOptions.properties = sortedProperties; + configSchema.properties.nodeOptions.properties = sortedProperties; // Also sort the root level properties const sortedRootKeys = ArrayPrototypeSort(ObjectKeys(rootProperties)); @@ -138,7 +141,54 @@ function generateConfigJsonSchema() { ArrayPrototypeMap(sortedRootKeys, (key) => [key, rootProperties[key]]), ); - schema.properties = sortedRootProperties; + configSchema.properties = sortedRootProperties; + + const schema = { + __proto__: null, + $schema: 'https://json-schema.org/draft/2020-12/schema', + oneOf: [ + { __proto__: null, $ref: '#/$defs/config' }, + { + __proto__: null, + type: 'object', + additionalProperties: false, + required: ['configs'], + properties: { + __proto__: null, + $schema: { + __proto__: null, + type: 'string', + }, + configs: { + __proto__: null, + type: 'array', + minItems: 1, + items: { + __proto__: null, + type: 'object', + additionalProperties: false, + required: ['nodeVersion', 'config'], + properties: { + __proto__: null, + nodeVersion: { + __proto__: null, + type: 'integer', + }, + config: { + __proto__: null, + $ref: '#/$defs/config', + }, + }, + }, + }, + }, + }, + ], + $defs: { + __proto__: null, + config: configSchema, + }, + }; return schema; } diff --git a/src/node_config_file.cc b/src/node_config_file.cc index 925d71fecc52a3..8651a143f406cd 100644 --- a/src/node_config_file.cc +++ b/src/node_config_file.cc @@ -1,7 +1,10 @@ #include "node_config_file.h" #include "debug_utils-inl.h" +#include "node_version.h" #include "simdjson.h" +#include + namespace node { constexpr std::string_view kConfigFileFlag = "--experimental-config-file"; @@ -223,39 +226,115 @@ ParseResult ConfigReader::ParseOptions( return ParseResult::Valid; } -ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) { - std::string file_content; - // Read the configuration file - int r = ReadFileSync(&file_content, config_path.data()); - if (r != 0) { - const char* err = uv_strerror(r); - FPrintF( - stderr, "Cannot read configuration from %s: %s\n", config_path, err); - return ParseResult::FileError; +ParseResult ConfigReader::ParseNodeVersion( + simdjson::ondemand::value* version_value, + const std::string_view& config_path) { + int64_t version; + if (version_value->get_int64().get(version)) { + FPrintF(stderr, + "\"nodeVersion\" value unexpected for %s " + "(should be an integer)\n", + config_path.data()); + return ParseResult::InvalidContent; } - // Parse the configuration file - simdjson::ondemand::parser json_parser; - simdjson::ondemand::document document; - if (json_parser.iterate(file_content).get(document)) { - FPrintF(stderr, "Can't parse %s\n", config_path.data()); + if (version != NODE_MAJOR_VERSION) { + FPrintF(stderr, + "\"nodeVersion\" %" PRId64 + " does not match current Node.js version %d " + "for %s\n", + version, + NODE_MAJOR_VERSION, + config_path.data()); return ParseResult::InvalidContent; } - // Validate config is an object - simdjson::ondemand::object main_object; - auto root_error = document.get_object().get(main_object); - if (root_error) { - if (root_error == simdjson::error_code::INCORRECT_TYPE) { + return ParseResult::Valid; +} + +ParseResult ConfigReader::ParseConfigs(simdjson::ondemand::array* configs, + const std::string_view& config_path) { + size_t index = 0; + + for (auto raw_config : *configs) { + simdjson::ondemand::object config_wrapper; + if (raw_config.get_object().get(config_wrapper)) { FPrintF(stderr, - "Root value unexpected not an object for %s\n\n", + "\"configs[%zu]\" value unexpected for %s " + "(should be an object)\n", + index, config_path.data()); - } else { - FPrintF(stderr, "Can't parse %s\n", config_path.data()); + return ParseResult::InvalidContent; } - return ParseResult::InvalidContent; + + simdjson::ondemand::value version_value; + auto version_error = + config_wrapper.find_field_unordered("nodeVersion").get(version_value); + if (version_error == simdjson::NO_SUCH_FIELD) { + FPrintF(stderr, + "\"configs[%zu].nodeVersion\" is required for %s\n", + index, + config_path.data()); + return ParseResult::InvalidContent; + } + if (version_error) { + return ParseResult::InvalidContent; + } + + int64_t version; + if (version_value.get_int64().get(version)) { + FPrintF(stderr, + "\"configs[%zu].nodeVersion\" value unexpected for %s " + "(should be an integer)\n", + index, + config_path.data()); + return ParseResult::InvalidContent; + } + + if (version != NODE_MAJOR_VERSION) { + index++; + continue; + } + + simdjson::ondemand::value config_value; + auto config_error = + config_wrapper.find_field_unordered("config").get(config_value); + if (config_error == simdjson::NO_SUCH_FIELD) { + FPrintF(stderr, + "\"configs[%zu].config\" is required for %s\n", + index, + config_path.data()); + return ParseResult::InvalidContent; + } + if (config_error) { + return ParseResult::InvalidContent; + } + + simdjson::ondemand::object selected_config; + if (config_value.get_object().get(selected_config)) { + FPrintF(stderr, + "\"configs[%zu].config\" value unexpected for %s " + "(should be an object)\n", + index, + config_path.data()); + return ParseResult::InvalidContent; + } + + return ParseConfigObject(&selected_config, config_path, false); } + FPrintF(stderr, + "No config found for current Node.js version %d in " + "\"configs\" for %s\n", + NODE_MAJOR_VERSION, + config_path.data()); + return ParseResult::InvalidContent; +} + +ParseResult ConfigReader::ParseConfigObject( + simdjson::ondemand::object* config_object, + const std::string_view& config_path, + bool allow_version_selection) { // Get all available namespaces for validation std::vector available_namespaces = options_parser::MapAvailableNamespaces(); @@ -271,7 +350,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) { std::unordered_set namespaces_with_implicit_flags; // Iterate through the main object to find all namespaces - for (auto field : main_object) { + for (auto field : *config_object) { std::string_view field_name; if (field.unescaped_key().get(field_name)) { return ParseResult::InvalidContent; @@ -279,6 +358,47 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) { std::string namespace_name(field_name); + if (namespace_name == "$schema") { + continue; + } + + if (namespace_name == "nodeVersion") { + simdjson::ondemand::value version_value; + if (field.value().get(version_value)) { + return ParseResult::InvalidContent; + } + ParseResult result = ParseNodeVersion(&version_value, config_path); + if (result != ParseResult::Valid) { + return result; + } + continue; + } + + if (namespace_name == "configs") { + if (!allow_version_selection) { + FPrintF(stderr, + "\"configs\" is not allowed inside a versioned config " + "for %s\n", + config_path.data()); + return ParseResult::InvalidContent; + } + + simdjson::ondemand::array configs; + auto field_error = field.value().get_array().get(configs); + if (field_error) { + FPrintF(stderr, + "\"configs\" value unexpected for %s " + "(should be an array)\n", + config_path.data()); + return ParseResult::InvalidContent; + } + ParseResult result = ParseConfigs(&configs, config_path); + if (result != ParseResult::Valid) { + return result; + } + continue; + } + // TODO(@marco-ippolito): Remove warning for testRunner namespace if (namespace_name == "testRunner") { FPrintF(stderr, @@ -340,6 +460,81 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) { return ParseResult::Valid; } +ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) { + std::string file_content; + // Read the configuration file + int r = ReadFileSync(&file_content, config_path.data()); + if (r != 0) { + const char* err = uv_strerror(r); + FPrintF( + stderr, "Cannot read configuration from %s: %s\n", config_path, err); + return ParseResult::FileError; + } + + // Parse the configuration file + simdjson::ondemand::parser json_parser; + simdjson::ondemand::document document; + if (json_parser.iterate(file_content).get(document)) { + FPrintF(stderr, "Can't parse %s\n", config_path.data()); + return ParseResult::InvalidContent; + } + + // Validate config is an object + simdjson::ondemand::object main_object; + auto root_error = document.get_object().get(main_object); + if (root_error) { + if (root_error == simdjson::error_code::INCORRECT_TYPE) { + FPrintF(stderr, + "Root value unexpected not an object for %s\n\n", + config_path.data()); + } else { + FPrintF(stderr, "Can't parse %s\n", config_path.data()); + } + return ParseResult::InvalidContent; + } + + bool has_configs = false; + bool has_other_fields = false; + for (auto field : main_object) { + std::string_view field_name; + if (field.unescaped_key().get(field_name)) { + return ParseResult::InvalidContent; + } + + if (field_name == "$schema") { + continue; + } + + if (field_name == "configs") { + has_configs = true; + } else { + has_other_fields = true; + } + } + + if (has_configs && has_other_fields) { + FPrintF(stderr, + "\"configs\" cannot be mixed with other configuration fields " + "for %s\n", + config_path.data()); + return ParseResult::InvalidContent; + } + + simdjson::ondemand::parser config_parser; + simdjson::ondemand::document config_document; + if (config_parser.iterate(file_content).get(config_document)) { + FPrintF(stderr, "Can't parse %s\n", config_path.data()); + return ParseResult::InvalidContent; + } + + simdjson::ondemand::object config_object; + if (config_document.get_object().get(config_object)) { + return ParseResult::InvalidContent; + } + + return ParseConfigObject(&config_object, config_path, true); +} + std::string ConfigReader::GetNodeOptions() { std::string acc = ""; const size_t total_options = node_options_.size(); diff --git a/src/node_config_file.h b/src/node_config_file.h index afe0e84765b8cb..cc8d8e4c4c235e 100644 --- a/src/node_config_file.h +++ b/src/node_config_file.h @@ -39,6 +39,14 @@ class ConfigReader { size_t GetFlagsSize(); private: + ParseResult ParseConfigObject(simdjson::ondemand::object* config_object, + const std::string_view& config_path, + bool allow_version_selection); + ParseResult ParseNodeVersion(simdjson::ondemand::value* version_value, + const std::string_view& config_path); + ParseResult ParseConfigs(simdjson::ondemand::array* configs, + const std::string_view& config_path); + // Parse options for a specific namespace (including nodeOptions for backward // compatibility) ParseResult ParseOptions(simdjson::ondemand::object* options_object, diff --git a/test/parallel/test-config-file.js b/test/parallel/test-config-file.js index 334884d9ed893a..e371980c5f25d5 100644 --- a/test/parallel/test-config-file.js +++ b/test/parallel/test-config-file.js @@ -55,6 +55,233 @@ test('should handle empty object json', async () => { assert.strictEqual(result.code, 0); }); +describe('runtime version checks', () => { + const currentMajor = Number(process.versions.node.split('.')[0]); + + async function runConfig(config, filename = 'version-config.json') { + tmpdir.refresh(); + const configPath = join(tmpdir.path, filename); + writeFileSync(configPath, JSON.stringify(config)); + + return spawnPromisified(process.execPath, [ + '--no-warnings', + `--experimental-config-file=${configPath}`, + '-p', 'http.maxHeaderSize', + ]); + } + + it('should accept a top-level config without nodeVersion', async () => { + const result = await runConfig({ + nodeOptions: { 'max-http-header-size': 10 }, + }, 'top-level-without-version.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + it('should accept a config file matching the current Node.js version', async () => { + const result = await runConfig({ + nodeVersion: currentMajor, + nodeOptions: { 'max-http-header-size': 10 }, + }, 'matching-version.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + it('should reject a config file targeting another Node.js version', async () => { + const result = await runConfig({ + nodeVersion: currentMajor + 1, + nodeOptions: { 'max-http-header-size': 10 }, + }, 'mismatching-version.json'); + assert.match(result.stderr, /"nodeVersion" \d+ does not match current Node\.js version \d+/); + assert.strictEqual(result.stdout, ''); + assert.strictEqual(result.code, 9); + }); + + it('should select a matching config from configs', async () => { + const result = await runConfig({ + configs: [ + { + nodeVersion: currentMajor + 1, + config: { + nodeOptions: { 'max-http-header-size': 20 }, + }, + }, + { + nodeVersion: currentMajor, + config: { + nodeOptions: { 'max-http-header-size': 10 }, + }, + }, + ], + }, 'versioned-configs.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + it('should reject configs without an entry for the current version', async () => { + const result = await runConfig({ + configs: [ + { + nodeVersion: currentMajor + 1, + config: { + nodeOptions: { 'max-http-header-size': 10 }, + }, + }, + ], + }, 'missing-versioned-config.json'); + assert.match(result.stderr, /No config found for current Node\.js version \d+ in "configs"/); + assert.strictEqual(result.stdout, ''); + assert.strictEqual(result.code, 9); + }); + + it('should ignore invalid config payloads for non-matching versions', async () => { + const result = await runConfig({ + configs: [ + { + nodeVersion: currentMajor + 1, + config: false, + }, + { + nodeVersion: currentMajor, + config: { + nodeOptions: { 'max-http-header-size': 10 }, + }, + }, + ], + }, 'ignored-non-matching-config.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + it('should use the first matching config from configs', async () => { + const result = await runConfig({ + configs: [ + { + nodeVersion: currentMajor, + config: { + nodeOptions: { 'max-http-header-size': 10 }, + }, + }, + { + nodeVersion: currentMajor, + config: { + nodeOptions: { 'max-http-header-size': 20 }, + }, + }, + ], + }, 'first-matching-versioned-config.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + it('should allow $schema with configs', async () => { + const result = await runConfig({ + $schema: 'https://nodejs.org/dist/vX.Y.Z/docs/node-config-schema.json', + configs: [ + { + nodeVersion: currentMajor, + config: { + $schema: 'https://nodejs.org/dist/vX.Y.Z/docs/node-config-schema.json', + nodeOptions: { 'max-http-header-size': 10 }, + }, + }, + ], + }, 'schema-with-versioned-config.json'); + assert.strictEqual(result.stderr, ''); + assert.strictEqual(result.stdout, '10\n'); + assert.strictEqual(result.code, 0); + }); + + for (const { name, config, error } of [ + { + name: 'configs is empty', + config: { configs: [] }, + error: /No config found for current Node\.js version \d+ in "configs"/, + }, + { + name: 'configs is not an array', + config: { configs: {} }, + error: /"configs" value unexpected .* \(should be an array\)/, + }, + { + name: 'configs contains a non-object entry', + config: { configs: [false] }, + error: /"configs\[0\]" value unexpected .* \(should be an object\)/, + }, + { + name: 'configs entry is missing nodeVersion', + config: { configs: [{ config: {} }] }, + error: /"configs\[0\]\.nodeVersion" is required/, + }, + { + name: 'configs entry has a non-integer nodeVersion', + config: { configs: [{ nodeVersion: `${currentMajor}`, config: {} }] }, + error: /"configs\[0\]\.nodeVersion" value unexpected .* \(should be an integer\)/, + }, + { + name: 'matching configs entry is missing config', + config: { configs: [{ nodeVersion: currentMajor }] }, + error: /"configs\[0\]\.config" is required/, + }, + { + name: 'matching configs entry has a non-object config', + config: { configs: [{ nodeVersion: currentMajor, config: false }] }, + error: /"configs\[0\]\.config" value unexpected .* \(should be an object\)/, + }, + { + name: 'configs is mixed with preceding config fields', + config: { + nodeOptions: { 'max-http-header-size': 10 }, + configs: [{ nodeVersion: currentMajor, config: {} }], + }, + error: /"configs" cannot be mixed with other configuration fields/, + }, + { + name: 'configs is mixed with following config fields', + config: { + configs: [{ nodeVersion: currentMajor, config: {} }], + nodeOptions: { 'max-http-header-size': 10 }, + }, + error: /"configs" cannot be mixed with other configuration fields/, + }, + { + name: 'configs is nested inside a selected config', + config: { + configs: [{ + nodeVersion: currentMajor, + config: { configs: [] }, + }], + }, + error: /"configs" is not allowed inside a versioned config/, + }, + { + name: 'selected config targets another version', + config: { + configs: [{ + nodeVersion: currentMajor, + config: { + nodeVersion: currentMajor + 1, + nodeOptions: { 'max-http-header-size': 10 }, + }, + }], + }, + error: /"nodeVersion" \d+ does not match current Node\.js version \d+/, + }, + ]) { + it(`should reject when ${name}`, async () => { + const result = await runConfig(config); + assert.match(result.stderr, error); + assert.strictEqual(result.stdout, ''); + assert.strictEqual(result.code, 9); + }); + } +}); + test('should parse boolean flag', onlyWithAmaroAndNodeOptions, async () => { const result = await spawnPromisified(process.execPath, [ `--experimental-config-file=${fixtures.path('rc/strip-types.json')}`, From f2d182ce354d4c2f658aa5f45e5ffddf0f7fd819 Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Wed, 29 Apr 2026 12:18:20 +0200 Subject: [PATCH 2/2] doc: document the latest-vX.x schema Signed-off-by: Marco Ippolito --- doc/api/cli.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index d6ac9712d5662f..09df4370d3dadd 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1064,7 +1064,8 @@ The alias `--experimental-default-config-file` is equivalent to `--experimental-config-file` without an argument. Node.js will read the configuration file and apply the settings. The configuration file should be a JSON file with the following structure. `vX.Y.Z` -in the `$schema` must be replaced with the version of Node.js you are using. +in the `$schema` must be replaced with the version of Node.js you are using or +`latest-vX.x` for the latest version of that major release line. ```json {