src: add config file support
PR-URL: https://github.com/nodejs/node/pull/57016 Refs: https://github.com/nodejs/node/issues/53787 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tierney Cyren <hello@bnb.im> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
This commit is contained in:
parent
5ab7c4c5b0
commit
f0e653d2af
1
Makefile
1
Makefile
@ -809,6 +809,7 @@ doc: $(NODE_EXE) doc-only ## Build Node.js, and then build the documentation wit
|
|||||||
|
|
||||||
out/doc:
|
out/doc:
|
||||||
mkdir -p $@
|
mkdir -p $@
|
||||||
|
cp doc/node_config_json_schema.json $@
|
||||||
|
|
||||||
# If it's a source tarball, doc/api already contains the generated docs.
|
# If it's a source tarball, doc/api already contains the generated docs.
|
||||||
# Just copy everything under doc/api over.
|
# Just copy everything under doc/api over.
|
||||||
|
@ -911,6 +911,69 @@ added: v23.6.0
|
|||||||
|
|
||||||
Enable experimental import support for `.node` addons.
|
Enable experimental import support for `.node` addons.
|
||||||
|
|
||||||
|
### `--experimental-config-file`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1.0 - Early development
|
||||||
|
|
||||||
|
Use this flag to specify a configuration file that will be loaded and parsed
|
||||||
|
before the application starts.
|
||||||
|
Node.js will read the configuration file and apply the settings.
|
||||||
|
The configuration file should be a JSON file
|
||||||
|
with the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
|
||||||
|
"experimental-transform-types": true,
|
||||||
|
"import": [
|
||||||
|
"amaro/transform"
|
||||||
|
],
|
||||||
|
"disable-warning": "ExperimentalWarning",
|
||||||
|
"watch-path": "src",
|
||||||
|
"watch-preserve-output": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only flags that are allowed in [`NODE_OPTIONS`][] are supported.
|
||||||
|
No-op flags are not supported.
|
||||||
|
Not all V8 flags are currently supported.
|
||||||
|
|
||||||
|
It is possible to use the [official JSON schema](../node_config_json_schema.json)
|
||||||
|
to validate the configuration file, which may vary depending on the Node.js version.
|
||||||
|
Each key in the configuration file corresponds to a flag that can be passed
|
||||||
|
as a command-line argument. The value of the key is the value that would be
|
||||||
|
passed to the flag.
|
||||||
|
|
||||||
|
For example, the configuration file above is equivalent to
|
||||||
|
the following command-line arguments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node --experimental-transform-types --import amaro/transform --disable-warning=ExperimentalWarning --watch-path=src --watch-preserve-output
|
||||||
|
```
|
||||||
|
|
||||||
|
The priority in configuration is as follows:
|
||||||
|
|
||||||
|
1. NODE\_OPTIONS and command-line options
|
||||||
|
2. Configuration file
|
||||||
|
3. Dotenv NODE\_OPTIONS
|
||||||
|
|
||||||
|
Values in the configuration file will not override the values in the environment
|
||||||
|
variables and command-line options, but will override the values in the `NODE_OPTIONS`
|
||||||
|
env file parsed by the `--env-file` flag.
|
||||||
|
|
||||||
|
If duplicate keys are present in the configuration file, only
|
||||||
|
the first key will be used.
|
||||||
|
|
||||||
|
The configuration parser will throw an error if the configuration file contains
|
||||||
|
unknown keys or keys that cannot used in `NODE_OPTIONS`.
|
||||||
|
|
||||||
|
Node.js will not sanitize or perform validation on the user-provided configuration,
|
||||||
|
so **NEVER** use untrusted configuration files.
|
||||||
|
|
||||||
### `--experimental-eventsource`
|
### `--experimental-eventsource`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
@ -166,6 +166,9 @@ Interpret the entry point as a URL.
|
|||||||
.It Fl -experimental-addon-modules
|
.It Fl -experimental-addon-modules
|
||||||
Enable experimental addon module support.
|
Enable experimental addon module support.
|
||||||
.
|
.
|
||||||
|
.It Fl -experimental-config-file
|
||||||
|
Enable support for experimental config file
|
||||||
|
.
|
||||||
.It Fl -experimental-import-meta-resolve
|
.It Fl -experimental-import-meta-resolve
|
||||||
Enable experimental ES modules support for import.meta.resolve().
|
Enable experimental ES modules support for import.meta.resolve().
|
||||||
.
|
.
|
||||||
|
578
doc/node_config_json_schema.json
Normal file
578
doc/node_config_json_schema.json
Normal file
@ -0,0 +1,578 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"addons": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allow-addons": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allow-child-process": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allow-fs-read": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allow-fs-write": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allow-wasi": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allow-worker": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"async-context-frame": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"conditions": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cpu-prof": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"cpu-prof-dir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cpu-prof-interval": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"cpu-prof-name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"debug-arraybuffer-allocations": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"deprecation": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"diagnostic-dir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"disable-proto": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"disable-sigusr1": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"disable-warning": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"disable-wasm-trap-handler": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"dns-result-order": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enable-fips": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"enable-source-maps": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"entry-url": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-addon-modules": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-detect-module": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-eventsource": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-global-navigator": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-import-meta-resolve": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-loader": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"experimental-print-required-tla": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-repl-await": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-require-module": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-shadow-realm": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-sqlite": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-strip-types": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-transform-types": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-vm-modules": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-wasm-modules": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-websocket": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"experimental-webstorage": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"extra-info-on-fatal-exception": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"force-async-hooks-checks": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"force-context-aware": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"force-fips": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"force-node-api-uncaught-exceptions-policy": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"frozen-intrinsics": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"global-search-paths": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"heap-prof": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"heap-prof-dir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"heap-prof-interval": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"heap-prof-name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"heapsnapshot-near-heap-limit": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"heapsnapshot-signal": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"icu-data-dir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"input-type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"insecure-http-parser": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"inspect": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"inspect-brk": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"inspect-port": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"inspect-publish-uid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"inspect-wait": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"localstorage-file": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"max-http-header-size": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"network-family-autoselection": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"network-family-autoselection-attempt-timeout": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"node-snapshot": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"openssl-config": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openssl-legacy-provider": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"openssl-shared-config": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"pending-deprecation": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"preserve-symlinks": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"preserve-symlinks-main": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"redirect-warnings": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"report-compact": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"report-dir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"report-exclude-env": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"report-exclude-network": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"report-filename": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"report-on-fatalerror": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"report-on-signal": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"report-signal": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"report-uncaught-exception": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"secure-heap": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"secure-heap-min": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"snapshot-blob": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"stack-trace-limit": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"test-coverage-branches": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"test-coverage-exclude": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test-coverage-functions": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"test-coverage-include": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test-coverage-lines": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"test-isolation": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"test-name-pattern": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test-only": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"test-reporter": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test-reporter-destination": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test-shard": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"test-skip-pattern": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"throw-deprecation": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tls-cipher-list": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tls-keylog": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tls-max-v1.2": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tls-max-v1.3": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tls-min-v1.0": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tls-min-v1.1": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tls-min-v1.2": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tls-min-v1.3": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-deprecation": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-env": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-env-js-stack": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-env-native-stack": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-event-categories": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trace-event-file-pattern": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trace-exit": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-promises": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-require-module": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"trace-sigint": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-sync-io": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-tls": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-uncaught": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"trace-warnings": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"track-heap-objects": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"unhandled-rejections": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"use-bundled-ca": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"use-largepages": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"use-openssl-ca": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"use-system-ca": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"v8-pool-size": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"verify-base-objects": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"warnings": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"watch-path": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"watch-preserve-output": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"zero-fill-buffers": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
@ -1,9 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
ArrayPrototypeMap,
|
||||||
|
ArrayPrototypeSort,
|
||||||
|
ObjectFromEntries,
|
||||||
|
ObjectKeys,
|
||||||
|
StringPrototypeReplace,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getCLIOptionsValues,
|
getCLIOptionsValues,
|
||||||
getCLIOptionsInfo,
|
getCLIOptionsInfo,
|
||||||
getEmbedderOptions: getEmbedderOptionsFromBinding,
|
getEmbedderOptions: getEmbedderOptionsFromBinding,
|
||||||
|
getEnvOptionsInputType,
|
||||||
} = internalBinding('options');
|
} = internalBinding('options');
|
||||||
|
|
||||||
let warnOnAllowUnauthorized = true;
|
let warnOnAllowUnauthorized = true;
|
||||||
@ -28,6 +37,45 @@ function getEmbedderOptions() {
|
|||||||
return embedderOptions ??= getEmbedderOptionsFromBinding();
|
return embedderOptions ??= getEmbedderOptionsFromBinding();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateConfigJsonSchema() {
|
||||||
|
const map = getEnvOptionsInputType();
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
__proto__: null,
|
||||||
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
__proto__: null,
|
||||||
|
},
|
||||||
|
type: 'object',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const { 0: key, 1: type } of map) {
|
||||||
|
const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
|
||||||
|
if (type === 'array') {
|
||||||
|
schema.properties[keyWithoutPrefix] = {
|
||||||
|
__proto__: null,
|
||||||
|
oneOf: [
|
||||||
|
{ __proto__: null, type: 'string' },
|
||||||
|
{ __proto__: null, type: 'array', items: { __proto__: null, type: 'string', minItems: 1 } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
schema.properties[keyWithoutPrefix] = { __proto__: null, type };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the proerties by key alphabetically.
|
||||||
|
const sortedKeys = ArrayPrototypeSort(ObjectKeys(schema.properties));
|
||||||
|
const sortedProperties = ObjectFromEntries(
|
||||||
|
ArrayPrototypeMap(sortedKeys, (key) => [key, schema.properties[key]]),
|
||||||
|
);
|
||||||
|
|
||||||
|
schema.properties = sortedProperties;
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
function refreshOptions() {
|
function refreshOptions() {
|
||||||
optionsDict = undefined;
|
optionsDict = undefined;
|
||||||
}
|
}
|
||||||
@ -55,5 +103,6 @@ module.exports = {
|
|||||||
getOptionValue,
|
getOptionValue,
|
||||||
getAllowUnauthorized,
|
getAllowUnauthorized,
|
||||||
getEmbedderOptions,
|
getEmbedderOptions,
|
||||||
|
generateConfigJsonSchema,
|
||||||
refreshOptions,
|
refreshOptions,
|
||||||
};
|
};
|
||||||
|
@ -116,6 +116,8 @@ function prepareExecution(options) {
|
|||||||
initializeSourceMapsHandlers();
|
initializeSourceMapsHandlers();
|
||||||
initializeDeprecations();
|
initializeDeprecations();
|
||||||
|
|
||||||
|
initializeConfigFileSupport();
|
||||||
|
|
||||||
require('internal/dns/utils').initializeDns();
|
require('internal/dns/utils').initializeDns();
|
||||||
|
|
||||||
if (isMainThread) {
|
if (isMainThread) {
|
||||||
@ -312,6 +314,12 @@ function setupSQLite() {
|
|||||||
BuiltinModule.allowRequireByUsers('sqlite');
|
BuiltinModule.allowRequireByUsers('sqlite');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initializeConfigFileSupport() {
|
||||||
|
if (getOptionValue('--experimental-config-file')) {
|
||||||
|
emitExperimentalWarning('--experimental-config-file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setupQuic() {
|
function setupQuic() {
|
||||||
if (!getOptionValue('--experimental-quic')) {
|
if (!getOptionValue('--experimental-quic')) {
|
||||||
return;
|
return;
|
||||||
|
2
node.gyp
2
node.gyp
@ -105,6 +105,7 @@
|
|||||||
'src/node_buffer.cc',
|
'src/node_buffer.cc',
|
||||||
'src/node_builtins.cc',
|
'src/node_builtins.cc',
|
||||||
'src/node_config.cc',
|
'src/node_config.cc',
|
||||||
|
'src/node_config_file.cc',
|
||||||
'src/node_constants.cc',
|
'src/node_constants.cc',
|
||||||
'src/node_contextify.cc',
|
'src/node_contextify.cc',
|
||||||
'src/node_credentials.cc',
|
'src/node_credentials.cc',
|
||||||
@ -230,6 +231,7 @@
|
|||||||
'src/node_blob.h',
|
'src/node_blob.h',
|
||||||
'src/node_buffer.h',
|
'src/node_buffer.h',
|
||||||
'src/node_builtins.h',
|
'src/node_builtins.h',
|
||||||
|
'src/node_config_file.h',
|
||||||
'src/node_constants.h',
|
'src/node_constants.h',
|
||||||
'src/node_context_data.h',
|
'src/node_context_data.h',
|
||||||
'src/node_contextify.h',
|
'src/node_contextify.h',
|
||||||
|
34
src/node.cc
34
src/node.cc
@ -20,6 +20,7 @@
|
|||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
#include "node_config_file.h"
|
||||||
#include "node_dotenv.h"
|
#include "node_dotenv.h"
|
||||||
#include "node_task_runner.h"
|
#include "node_task_runner.h"
|
||||||
|
|
||||||
@ -150,6 +151,9 @@ namespace per_process {
|
|||||||
// Instance is used to store environment variables including NODE_OPTIONS.
|
// Instance is used to store environment variables including NODE_OPTIONS.
|
||||||
node::Dotenv dotenv_file = Dotenv();
|
node::Dotenv dotenv_file = Dotenv();
|
||||||
|
|
||||||
|
// node_config_file.h
|
||||||
|
node::ConfigReader config_reader = ConfigReader();
|
||||||
|
|
||||||
// node_revert.h
|
// node_revert.h
|
||||||
// Bit flag used to track security reverts.
|
// Bit flag used to track security reverts.
|
||||||
unsigned int reverted_cve = 0;
|
unsigned int reverted_cve = 0;
|
||||||
@ -884,6 +888,36 @@ static ExitCode InitializeNodeWithArgsInternal(
|
|||||||
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
|
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string node_options_from_config;
|
||||||
|
if (auto path = per_process::config_reader.GetDataFromArgs(*argv)) {
|
||||||
|
switch (per_process::config_reader.ParseConfig(*path)) {
|
||||||
|
case ParseResult::Valid:
|
||||||
|
break;
|
||||||
|
case ParseResult::InvalidContent:
|
||||||
|
errors->push_back(std::string(*path) + ": invalid content");
|
||||||
|
break;
|
||||||
|
case ParseResult::FileError:
|
||||||
|
errors->push_back(std::string(*path) + ": not found");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
node_options_from_config = per_process::config_reader.AssignNodeOptions();
|
||||||
|
// (@marco-ippolito) Avoid reparsing the env options again
|
||||||
|
std::vector<std::string> env_argv_from_config =
|
||||||
|
ParseNodeOptionsEnvVar(node_options_from_config, errors);
|
||||||
|
|
||||||
|
// Check the number of flags in NODE_OPTIONS from the config file
|
||||||
|
// matches the parsed ones. This avoid users from sneaking in
|
||||||
|
// additional flags.
|
||||||
|
if (env_argv_from_config.size() !=
|
||||||
|
per_process::config_reader.GetFlagsSize()) {
|
||||||
|
errors->emplace_back("The number of NODE_OPTIONS doesn't match "
|
||||||
|
"the number of flags in the config file");
|
||||||
|
}
|
||||||
|
node_options += node_options_from_config;
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
|
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
|
||||||
if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) {
|
if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) {
|
||||||
// NODE_OPTIONS environment variable is preferred over the file one.
|
// NODE_OPTIONS environment variable is preferred over the file one.
|
||||||
|
195
src/node_config_file.cc
Normal file
195
src/node_config_file.cc
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
#include "node_config_file.h"
|
||||||
|
#include "debug_utils-inl.h"
|
||||||
|
#include "simdjson.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
std::optional<std::string_view> ConfigReader::GetDataFromArgs(
|
||||||
|
const std::vector<std::string>& args) {
|
||||||
|
constexpr std::string_view flag = "--experimental-config-file";
|
||||||
|
|
||||||
|
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||||
|
if (*it == flag) {
|
||||||
|
// Case: "--experimental-config-file foo"
|
||||||
|
if (auto next = std::next(it); next != args.end()) {
|
||||||
|
return *next;
|
||||||
|
}
|
||||||
|
} else if (it->starts_with(flag)) {
|
||||||
|
// Case: "--experimental-config-file=foo"
|
||||||
|
if (it->size() > flag.size() && (*it)[flag.size()] == '=') {
|
||||||
|
return it->substr(flag.size() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson::ondemand::object main_object;
|
||||||
|
// If document is not an object, throw an error.
|
||||||
|
if (auto root_error = document.get_object().get(main_object)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto env_options_map = options_parser::MapEnvOptionsFlagInputType();
|
||||||
|
simdjson::ondemand::value ondemand_value;
|
||||||
|
std::string_view key;
|
||||||
|
|
||||||
|
for (auto field : main_object) {
|
||||||
|
if (field.unescaped_key().get(key) || field.value().get(ondemand_value)) {
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The key needs to match the CLI option
|
||||||
|
std::string prefix = "--";
|
||||||
|
auto it = env_options_map.find(prefix.append(key));
|
||||||
|
if (it != env_options_map.end()) {
|
||||||
|
switch (it->second) {
|
||||||
|
case options_parser::OptionType::kBoolean: {
|
||||||
|
bool result;
|
||||||
|
if (ondemand_value.get_bool().get(result)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + (result ? "true" : "false"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// String array can allow both string and array types
|
||||||
|
case options_parser::OptionType::kStringList: {
|
||||||
|
simdjson::ondemand::json_type field_type;
|
||||||
|
if (ondemand_value.type().get(field_type)) {
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
switch (field_type) {
|
||||||
|
case simdjson::ondemand::json_type::array: {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
simdjson::ondemand::array raw_imports;
|
||||||
|
if (ondemand_value.get_array().get(raw_imports)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
for (auto raw_import : raw_imports) {
|
||||||
|
std::string_view import;
|
||||||
|
if (raw_import.get_string(import)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + std::string(import));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case simdjson::ondemand::json_type::string: {
|
||||||
|
std::string result;
|
||||||
|
if (ondemand_value.get_string(result)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case options_parser::OptionType::kString: {
|
||||||
|
std::string result;
|
||||||
|
if (ondemand_value.get_string(result)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case options_parser::OptionType::kInteger: {
|
||||||
|
int64_t result;
|
||||||
|
if (ondemand_value.get_int64().get(result)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + std::to_string(result));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case options_parser::OptionType::kHostPort:
|
||||||
|
case options_parser::OptionType::kUInteger: {
|
||||||
|
uint64_t result;
|
||||||
|
if (ondemand_value.get_uint64().get(result)) {
|
||||||
|
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
flags_.push_back(it->first + "=" + std::to_string(result));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case options_parser::OptionType::kNoOp: {
|
||||||
|
FPrintF(stderr,
|
||||||
|
"No-op flag %s is currently not supported\n",
|
||||||
|
it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case options_parser::OptionType::kV8Option: {
|
||||||
|
FPrintF(stderr,
|
||||||
|
"V8 flag %s is currently not supported\n",
|
||||||
|
it->first.c_str());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FPrintF(stderr, "Unknown or not allowed option %s\n", key.data());
|
||||||
|
return ParseResult::InvalidContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult::Valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ConfigReader::AssignNodeOptions() {
|
||||||
|
if (flags_.empty()) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
DCHECK_GT(flags_.size(), 0);
|
||||||
|
std::string acc;
|
||||||
|
acc.reserve(flags_.size() * 2);
|
||||||
|
for (size_t i = 0; i < flags_.size(); ++i) {
|
||||||
|
// The space is necessary at the beginning of the string
|
||||||
|
acc += " " + flags_[i];
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ConfigReader::GetFlagsSize() {
|
||||||
|
return flags_.size();
|
||||||
|
}
|
||||||
|
} // namespace node
|
43
src/node_config_file.h
Normal file
43
src/node_config_file.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef SRC_NODE_CONFIG_FILE_H_
|
||||||
|
#define SRC_NODE_CONFIG_FILE_H_
|
||||||
|
|
||||||
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include "simdjson.h"
|
||||||
|
#include "util-inl.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
// When trying to parse the configuration file, we can have three possible
|
||||||
|
// results:
|
||||||
|
// - Valid: The file was successfully parsed and the content is valid.
|
||||||
|
// - FileError: There was an error reading the file.
|
||||||
|
// - InvalidContent: The file was read, but the content is invalid.
|
||||||
|
enum ParseResult { Valid, FileError, InvalidContent };
|
||||||
|
|
||||||
|
// ConfigReader is the class that parses the configuration JSON file.
|
||||||
|
// It reads the file provided by --experimental-config-file and
|
||||||
|
// extracts the flags.
|
||||||
|
class ConfigReader {
|
||||||
|
public:
|
||||||
|
ParseResult ParseConfig(const std::string_view& config_path);
|
||||||
|
|
||||||
|
std::optional<std::string_view> GetDataFromArgs(
|
||||||
|
const std::vector<std::string>& args);
|
||||||
|
|
||||||
|
std::string AssignNodeOptions();
|
||||||
|
|
||||||
|
size_t GetFlagsSize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#endif // SRC_NODE_CONFIG_FILE_H_
|
@ -29,6 +29,7 @@ using v8::Name;
|
|||||||
using v8::Null;
|
using v8::Null;
|
||||||
using v8::Number;
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
using v8::String;
|
||||||
using v8::Undefined;
|
using v8::Undefined;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
namespace node {
|
namespace node {
|
||||||
@ -681,6 +682,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||||||
"set environment variables from supplied file",
|
"set environment variables from supplied file",
|
||||||
&EnvironmentOptions::optional_env_file);
|
&EnvironmentOptions::optional_env_file);
|
||||||
Implies("--env-file-if-exists", "[has_env_file_string]");
|
Implies("--env-file-if-exists", "[has_env_file_string]");
|
||||||
|
AddOption("--experimental-config-file",
|
||||||
|
"set config file from supplied file",
|
||||||
|
&EnvironmentOptions::experimental_config_file);
|
||||||
AddOption("--test",
|
AddOption("--test",
|
||||||
"launch test runner on startup",
|
"launch test runner on startup",
|
||||||
&EnvironmentOptions::test_runner);
|
&EnvironmentOptions::test_runner);
|
||||||
@ -1299,6 +1303,19 @@ std::string GetBashCompletion() {
|
|||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, options_parser::OptionType>
|
||||||
|
MapEnvOptionsFlagInputType() {
|
||||||
|
std::unordered_map<std::string, options_parser::OptionType> type_map;
|
||||||
|
const auto& parser = _ppop_instance;
|
||||||
|
for (const auto& item : parser.options_) {
|
||||||
|
if (!item.first.empty() && !item.first.starts_with('[') &&
|
||||||
|
item.second.env_setting == kAllowedInEnvvar) {
|
||||||
|
type_map[item.first] = item.second.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type_map;
|
||||||
|
}
|
||||||
|
|
||||||
struct IterateCLIOptionsScope {
|
struct IterateCLIOptionsScope {
|
||||||
explicit IterateCLIOptionsScope(Environment* env) {
|
explicit IterateCLIOptionsScope(Environment* env) {
|
||||||
// Temporarily act as if the current Environment's/IsolateData's options
|
// Temporarily act as if the current Environment's/IsolateData's options
|
||||||
@ -1542,6 +1559,81 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
|
|||||||
args.GetReturnValue().Set(ret);
|
args.GetReturnValue().Set(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function returns a map containing all the options available
|
||||||
|
// as NODE_OPTIONS and their input type
|
||||||
|
// Example --experimental-transform types: kBoolean
|
||||||
|
// This is used to determine the type of the input for each option
|
||||||
|
// to generate the config file json schema
|
||||||
|
void GetEnvOptionsInputType(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
Local<Context> context = isolate->GetCurrentContext();
|
||||||
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
|
||||||
|
if (!env->has_run_bootstrapping_code()) {
|
||||||
|
// No code because this is an assertion.
|
||||||
|
return env->ThrowError(
|
||||||
|
"Should not query options before bootstrapping is done");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutex::ScopedLock lock(per_process::cli_options_mutex);
|
||||||
|
|
||||||
|
Local<Map> flags_map = Map::New(isolate);
|
||||||
|
|
||||||
|
for (const auto& item : _ppop_instance.options_) {
|
||||||
|
if (!item.first.empty() && !item.first.starts_with('[') &&
|
||||||
|
item.second.env_setting == kAllowedInEnvvar) {
|
||||||
|
std::string type;
|
||||||
|
switch (static_cast<int>(item.second.type)) {
|
||||||
|
case 0: // No-op
|
||||||
|
case 1: // V8 flags
|
||||||
|
break; // V8 and NoOp flags are not supported
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
type = "boolean";
|
||||||
|
break;
|
||||||
|
case 3: // integer
|
||||||
|
case 4: // unsigned integer
|
||||||
|
case 6: // host port
|
||||||
|
type = "number";
|
||||||
|
break;
|
||||||
|
case 5: // string
|
||||||
|
type = "string";
|
||||||
|
break;
|
||||||
|
case 7: // string array
|
||||||
|
type = "array";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<String> value;
|
||||||
|
if (!String::NewFromUtf8(
|
||||||
|
isolate, type.data(), v8::NewStringType::kNormal, type.size())
|
||||||
|
.ToLocal(&value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<String> field;
|
||||||
|
if (!String::NewFromUtf8(isolate,
|
||||||
|
item.first.data(),
|
||||||
|
v8::NewStringType::kNormal,
|
||||||
|
item.first.size())
|
||||||
|
.ToLocal(&field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags_map->Set(context, field, value).IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.GetReturnValue().Set(flags_map);
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize(Local<Object> target,
|
void Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
@ -1554,7 +1646,8 @@ void Initialize(Local<Object> target,
|
|||||||
context, target, "getCLIOptionsInfo", GetCLIOptionsInfo);
|
context, target, "getCLIOptionsInfo", GetCLIOptionsInfo);
|
||||||
SetMethodNoSideEffect(
|
SetMethodNoSideEffect(
|
||||||
context, target, "getEmbedderOptions", GetEmbedderOptions);
|
context, target, "getEmbedderOptions", GetEmbedderOptions);
|
||||||
|
SetMethodNoSideEffect(
|
||||||
|
context, target, "getEnvOptionsInputType", GetEnvOptionsInputType);
|
||||||
Local<Object> env_settings = Object::New(isolate);
|
Local<Object> env_settings = Object::New(isolate);
|
||||||
NODE_DEFINE_CONSTANT(env_settings, kAllowedInEnvvar);
|
NODE_DEFINE_CONSTANT(env_settings, kAllowedInEnvvar);
|
||||||
NODE_DEFINE_CONSTANT(env_settings, kDisallowedInEnvvar);
|
NODE_DEFINE_CONSTANT(env_settings, kDisallowedInEnvvar);
|
||||||
@ -1580,6 +1673,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|||||||
registry->Register(GetCLIOptionsValues);
|
registry->Register(GetCLIOptionsValues);
|
||||||
registry->Register(GetCLIOptionsInfo);
|
registry->Register(GetCLIOptionsInfo);
|
||||||
registry->Register(GetEmbedderOptions);
|
registry->Register(GetEmbedderOptions);
|
||||||
|
registry->Register(GetEnvOptionsInputType);
|
||||||
}
|
}
|
||||||
} // namespace options_parser
|
} // namespace options_parser
|
||||||
|
|
||||||
|
@ -258,6 +258,7 @@ class EnvironmentOptions : public Options {
|
|||||||
|
|
||||||
bool report_exclude_env = false;
|
bool report_exclude_env = false;
|
||||||
bool report_exclude_network = false;
|
bool report_exclude_network = false;
|
||||||
|
std::string experimental_config_file;
|
||||||
|
|
||||||
inline DebugOptions* get_debug_options() { return &debug_options_; }
|
inline DebugOptions* get_debug_options() { return &debug_options_; }
|
||||||
inline const DebugOptions& debug_options() const { return debug_options_; }
|
inline const DebugOptions& debug_options() const { return debug_options_; }
|
||||||
@ -390,6 +391,7 @@ enum OptionType {
|
|||||||
kHostPort,
|
kHostPort,
|
||||||
kStringList,
|
kStringList,
|
||||||
};
|
};
|
||||||
|
std::unordered_map<std::string, OptionType> MapEnvOptionsFlagInputType();
|
||||||
|
|
||||||
template <typename Options>
|
template <typename Options>
|
||||||
class OptionsParser {
|
class OptionsParser {
|
||||||
@ -570,6 +572,10 @@ class OptionsParser {
|
|||||||
friend void GetCLIOptionsInfo(
|
friend void GetCLIOptionsInfo(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
friend std::string GetBashCompletion();
|
friend std::string GetBashCompletion();
|
||||||
|
friend std::unordered_map<std::string, OptionType>
|
||||||
|
MapEnvOptionsFlagInputType();
|
||||||
|
friend void GetEnvOptionsInputType(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
};
|
};
|
||||||
|
|
||||||
using StringVector = std::vector<std::string>;
|
using StringVector = std::vector<std::string>;
|
||||||
|
1
test/fixtures/dotenv/node-options-no-tranform.env
vendored
Normal file
1
test/fixtures/dotenv/node-options-no-tranform.env
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
NODE_OPTIONS="--no-experimental-strip-types"
|
3
test/fixtures/rc/empty-object.json
vendored
Normal file
3
test/fixtures/rc/empty-object.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
1
test/fixtures/rc/empty.json
vendored
Normal file
1
test/fixtures/rc/empty.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
3
test/fixtures/rc/host-port.json
vendored
Normal file
3
test/fixtures/rc/host-port.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"inspect-port": 65535
|
||||||
|
}
|
3
test/fixtures/rc/import-as-string.json
vendored
Normal file
3
test/fixtures/rc/import-as-string.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"import": "./test/fixtures/printA.js"
|
||||||
|
}
|
7
test/fixtures/rc/import.json
vendored
Normal file
7
test/fixtures/rc/import.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"import": [
|
||||||
|
"./test/fixtures/printA.js",
|
||||||
|
"./test/fixtures/printB.js",
|
||||||
|
"./test/fixtures/printC.js"
|
||||||
|
]
|
||||||
|
}
|
3
test/fixtures/rc/invalid-import.json
vendored
Normal file
3
test/fixtures/rc/invalid-import.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"import": [1]
|
||||||
|
}
|
3
test/fixtures/rc/negative-numeric.json
vendored
Normal file
3
test/fixtures/rc/negative-numeric.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"max-http-header-size": -1
|
||||||
|
}
|
3
test/fixtures/rc/no-op.json
vendored
Normal file
3
test/fixtures/rc/no-op.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"http-parser": true
|
||||||
|
}
|
3
test/fixtures/rc/not-node-options-flag.json
vendored
Normal file
3
test/fixtures/rc/not-node-options-flag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"--test": true
|
||||||
|
}
|
3
test/fixtures/rc/numeric.json
vendored
Normal file
3
test/fixtures/rc/numeric.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"max-http-header-size": 4294967295
|
||||||
|
}
|
4
test/fixtures/rc/override-property.json
vendored
Normal file
4
test/fixtures/rc/override-property.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"experimental-transform-types": true,
|
||||||
|
"experimental-transform-types": false
|
||||||
|
}
|
3
test/fixtures/rc/sneaky-flag.json
vendored
Normal file
3
test/fixtures/rc/sneaky-flag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"import": "./test/fixtures/printA.js --experimental-transform-types"
|
||||||
|
}
|
3
test/fixtures/rc/string.json
vendored
Normal file
3
test/fixtures/rc/string.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"test-reporter": "dot"
|
||||||
|
}
|
6
test/fixtures/rc/test.js
vendored
Normal file
6
test/fixtures/rc/test.js
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { test } = require('node:test');
|
||||||
|
const { ok } = require('node:assert');
|
||||||
|
|
||||||
|
test('should pass', () => {
|
||||||
|
ok(true);
|
||||||
|
});
|
3
test/fixtures/rc/transform-types.json
vendored
Normal file
3
test/fixtures/rc/transform-types.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"experimental-transform-types": true
|
||||||
|
}
|
3
test/fixtures/rc/unknown-flag.json
vendored
Normal file
3
test/fixtures/rc/unknown-flag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"some-unknown-flag": true
|
||||||
|
}
|
3
test/fixtures/rc/v8-flag.json
vendored
Normal file
3
test/fixtures/rc/v8-flag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"abort-on-uncaught-exception": true
|
||||||
|
}
|
256
test/parallel/test-config-file.js
Normal file
256
test/parallel/test-config-file.js
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { spawnPromisified } = require('../common');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
const { match, strictEqual } = require('node:assert');
|
||||||
|
const { test } = require('node:test');
|
||||||
|
|
||||||
|
test('should handle non existing json', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--experimental-config-file',
|
||||||
|
'i-do-not-exist.json',
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /Cannot read configuration from i-do-not-exist\.json: no such file or directory/);
|
||||||
|
match(result.stderr, /i-do-not-exist\.json: not found/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle empty json', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/empty.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /Can't parse/);
|
||||||
|
match(result.stderr, /empty\.json: invalid content/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle empty object json', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/empty-object.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
match(result.stdout, /Hello, World!/);
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse boolean flag', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/transform-types.json'),
|
||||||
|
fixtures.path('typescript/ts/transformation/test-enum.ts'),
|
||||||
|
]);
|
||||||
|
match(result.stderr, /--experimental-config-file is an experimental feature and might change at any time/);
|
||||||
|
match(result.stdout, /Hello, TypeScript!/);
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override a flag declared twice', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/override-property.json'),
|
||||||
|
fixtures.path('typescript/ts/transformation/test-enum.ts'),
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, 'Hello, TypeScript!\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should override env-file', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/transform-types.json'),
|
||||||
|
'--env-file', fixtures.path('dotenv/node-options-no-tranform.env'),
|
||||||
|
fixtures.path('typescript/ts/transformation/test-enum.ts'),
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
match(result.stdout, /Hello, TypeScript!/);
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override NODE_OPTIONS', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/transform-types.json'),
|
||||||
|
fixtures.path('typescript/ts/transformation/test-enum.ts'),
|
||||||
|
], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NODE_OPTIONS: '--no-experimental-transform-types',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not ovverride CLI flags', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--no-experimental-transform-types',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/transform-types.json'),
|
||||||
|
fixtures.path('typescript/ts/transformation/test-enum.ts'),
|
||||||
|
]);
|
||||||
|
match(result.stderr, /ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should parse array flag correctly', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/import.json'),
|
||||||
|
'--eval', 'setTimeout(() => console.log("D"),99)',
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, 'A\nB\nC\nD\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should validate invalid array flag', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/invalid-import.json'),
|
||||||
|
'--eval', 'setTimeout(() => console.log("D"),99)',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /invalid-import\.json: invalid content/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should validate array flag as string', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/import-as-string.json'),
|
||||||
|
'--eval', 'setTimeout(() => console.log("B"),99)',
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, 'A\nB\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw at unknown flag', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/unknown-flag.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /Unknown or not allowed option some-unknown-flag/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw at flag not available in NODE_OPTIONS', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/not-node-options-flag.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /Unknown or not allowed option --test/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsigned flag should be parsed correctly', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/numeric.json'),
|
||||||
|
'-p', 'http.maxHeaderSize',
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, '4294967295\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('numeric flag should not allow negative values', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/negative-numeric.json'),
|
||||||
|
'-p', 'http.maxHeaderSize',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /Invalid value for --max-http-header-size/);
|
||||||
|
match(result.stderr, /negative-numeric\.json: invalid content/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('v8 flag should not be allowed in config file', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/v8-flag.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /V8 flag --abort-on-uncaught-exception is currently not supported/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string flag should be parsed correctly', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--test',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/string.json'),
|
||||||
|
fixtures.path('rc/test.js'),
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, '.\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('host port flag should be parsed correctly', { skip: !process.features.inspector }, async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--expose-internals',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/host-port.json'),
|
||||||
|
'-p', 'require("internal/options").getOptionValue("--inspect-port").port',
|
||||||
|
]);
|
||||||
|
strictEqual(result.stderr, '');
|
||||||
|
strictEqual(result.stdout, '65535\n');
|
||||||
|
strictEqual(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no op flag should throw', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/no-op.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /No-op flag --http-parser is currently not supported/);
|
||||||
|
match(result.stderr, /no-op\.json: invalid content/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not allow users to sneak in a flag', async () => {
|
||||||
|
const result = await spawnPromisified(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-config-file',
|
||||||
|
fixtures.path('rc/sneaky-flag.json'),
|
||||||
|
'-p', '"Hello, World!"',
|
||||||
|
]);
|
||||||
|
match(result.stderr, /The number of NODE_OPTIONS doesn't match the number of flags in the config file/);
|
||||||
|
strictEqual(result.stdout, '');
|
||||||
|
strictEqual(result.code, 9);
|
||||||
|
});
|
40
test/parallel/test-config-json-schema.js
Normal file
40
test/parallel/test-config-json-schema.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Flags: --no-warnings --expose-internals
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
common.skipIfInspectorDisabled();
|
||||||
|
|
||||||
|
if (!common.hasCrypto) {
|
||||||
|
common.skip('missing crypto');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hasOpenSSL3 } = require('../common/crypto');
|
||||||
|
|
||||||
|
if (!hasOpenSSL3) {
|
||||||
|
common.skip('this test requires OpenSSL 3.x');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!common.hasIntl) {
|
||||||
|
// A handful of the tests fail when ICU is not included.
|
||||||
|
common.skip('missing Intl');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
generateConfigJsonSchema,
|
||||||
|
} = require('internal/options');
|
||||||
|
const schemaInDoc = require('../../doc/node_config_json_schema.json');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const schema = generateConfigJsonSchema();
|
||||||
|
|
||||||
|
// This assertion ensures that whenever we add a new env option, we also add it
|
||||||
|
// to the JSON schema. The function getEnvOptionsInputType() returns all the available
|
||||||
|
// env options, so we can generate the JSON schema from it and compare it to the
|
||||||
|
// current JSON schema.
|
||||||
|
// To regenerate the JSON schema, run:
|
||||||
|
// out/Release/node --expose-internals tools/doc/generate-json-schema.mjs
|
||||||
|
// And then run make doc to update the out/doc/node_config_json_schema.json file.
|
||||||
|
assert.strictEqual(JSON.stringify(schema), JSON.stringify(schemaInDoc), 'JSON schema is outdated.' +
|
||||||
|
'Run `out/Release/node --expose-internals tools/doc/generate-json-schema.mjs` to update it.');
|
7
tools/doc/generate-json-schema.mjs
Normal file
7
tools/doc/generate-json-schema.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Flags: --expose-internals
|
||||||
|
|
||||||
|
import internal from 'internal/options';
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
|
|
||||||
|
const schema = internal.generateConfigJsonSchema();
|
||||||
|
writeFileSync('doc/node_config_json_schema.json', `${JSON.stringify(schema, null, 2)}\n`);
|
Loading…
x
Reference in New Issue
Block a user