Skip to content

Commit 99c479f

Browse files
committed
vfs: keep --experimental-vfs negatable
Keep VFS enabled by default while allowing the negated flag to disable node:vfs exposure and --vfs-load. Assisted-by: pi Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 26a1dc8 commit 99c479f

9 files changed

Lines changed: 81 additions & 18 deletions

File tree

‎doc/api/cli.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2313,6 +2313,18 @@ changes:
23132313

23142314
Disable the experimental [`node:sqlite`][] module.
23152315

2316+
### `--no-experimental-vfs`
2317+
2318+
<!-- YAML
2319+
added: v26.4.0
2320+
changes:
2321+
- version: REPLACEME
2322+
pr-url: https://github.com/nodejs/node/pull/66318
2323+
description: VFS is unflagged but still experimental.
2324+
-->
2325+
2326+
Disable the experimental [`node:vfs`][] module and `--vfs-load`.
2327+
23162328
### `--no-experimental-webstorage`
23172329

23182330
<!-- YAML
@@ -3853,7 +3865,9 @@ May be given at most once.
38533865

38543866
Mounts `source` as a virtual file system ([`node:vfs`][]), and runs the entry
38553867
point and all subsequent `require()`/`import` resolution against that mount
3856-
rather than the real file system. The mount is placed at a reserved mount point
3868+
rather than the real file system. This option is unavailable when
3869+
`--no-experimental-vfs` disables [`node:vfs`][]. The mount is placed at a
3870+
reserved mount point
38573871
assigned by Node.js, so it never shadows real paths and no target can be
38583872
chosen. The entry point is taken from the mount the same way `node <directory>`
38593873
takes one: the mount's own `package.json` `"main"`, or `index.js`. Any
@@ -4255,6 +4269,7 @@ one is included in the list below.
42554269
* `--no-experimental-global-navigator`
42564270
* `--no-experimental-sqlite`
42574271
* `--no-experimental-strip-types`
4272+
* `--no-experimental-vfs`
42584273
* `--no-experimental-webstorage`
42594274
* `--no-extra-info-on-fatal-exception`
42604275
* `--no-force-async-hooks-checks`

‎doc/node.1‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,9 @@ Legacy alias for \fB--no-require-module\fR.
11941194
.It Fl -no-experimental-sqlite
11951195
Disable the experimental \fBnode:sqlite\fR module.
11961196
.
1197+
.It Fl -no-experimental-vfs
1198+
Disable the experimental \fBnode:vfs\fR module and \fB--vfs-load\fR.
1199+
.
11971200
.It Fl -no-experimental-webstorage
11981201
Disable \fBWeb Storage\fR support.
11991202
.
@@ -1932,7 +1935,9 @@ Print node's version.
19321935
May be given at most once.
19331936
Mounts \fBsource\fR as a virtual file system (\fBnode:vfs\fR), and runs the entry
19341937
point and all subsequent \fBrequire()\fR/\fBimport\fR resolution against that mount
1935-
rather than the real file system. The mount is placed at a reserved mount point
1938+
rather than the real file system. This option is unavailable when
1939+
\fB--no-experimental-vfs\fR disables \fBnode:vfs\fR. The mount is placed at a
1940+
reserved mount point
19361941
assigned by Node.js, so it never shadows real paths and no target can be
19371942
chosen. The entry point is taken from the mount the same way \fBnode <directory>\fR
19381943
takes one: the mount's own \fBpackage.json\fR \fB"main"\fR, or \fBindex.js\fR. Any
@@ -2294,6 +2299,8 @@ one is included in the list below.
22942299
.It
22952300
\fB--no-experimental-strip-types\fR
22962301
.It
2302+
\fB--no-experimental-vfs\fR
2303+
.It
22972304
\fB--no-experimental-webstorage\fR
22982305
.It
22992306
\fB--no-extra-info-on-fatal-exception\fR

‎lib/internal/process/pre_execution.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,10 @@ function setupQuic() {
524524
}
525525

526526
function setupVfs() {
527+
if (!getOptionValue('--experimental-vfs')) {
528+
return;
529+
}
530+
527531
const { BuiltinModule } = require('internal/bootstrap/realm');
528532
BuiltinModule.allowRequireByUsers('vfs');
529533
}

‎src/node.cc‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,9 @@ static ExitCode InitializeNodeWithArgsInternal(
10491049
// count below needs the arguments the command line itself gave.
10501050
{
10511051
auto* env_options = per_process::cli_options->per_isolate->per_env.get();
1052+
if (!env_options->experimental_vfs && env_options->vfs_load) {
1053+
errors->push_back("--vfs-load requires node:vfs to be enabled");
1054+
}
10521055
// A second --vfs-load would silently replace the first, and the option
10531056
// itself cannot say how often it was given; count it in the node options
10541057
// the command line yielded.

‎src/node_options.cc‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -789,11 +789,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
789789
NoOp{},
790790
#endif
791791
kAllowedInEnvvar);
792-
// Retained as a no-op for compatibility; node:vfs is no longer gated.
793792
AddOption("--experimental-vfs",
794-
"",
793+
"experimental node:vfs module",
795794
BOOL_FIELD(experimental_vfs),
796-
kAllowedInEnvvar);
795+
kAllowedInEnvvar,
796+
true);
797797
// Choosing the entry point is the command line's alone: an environment
798798
// variable must not be able to redirect what a `node <args>` invocation runs,
799799
// so this is rejected in NODE_OPTIONS. The source and whether to run from it

‎src/node_options.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class EnvironmentOptions : public Options {
214214
DEFINE_BOOL_FIELD(experimental_websocket) = true;
215215
DEFINE_BOOL_FIELD(experimental_sqlite) = HAVE_SQLITE;
216216
DEFINE_BOOL_FIELD(experimental_stream_iter) = EXPERIMENTALS_DEFAULT_VALUE;
217-
DEFINE_BOOL_FIELD(experimental_vfs) = EXPERIMENTALS_DEFAULT_VALUE;
217+
DEFINE_BOOL_FIELD(experimental_vfs) = true;
218218
DEFINE_BOOL_FIELD(vfs_load) = false;
219219
DEFINE_BOOL_FIELD(webstorage) = HAVE_SQLITE;
220220
DEFINE_BOOL_FIELD(experimental_dtls) = EXPERIMENTALS_DEFAULT_VALUE;

‎test/parallel/test-module-builtin-experimental.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ if (common.hasFFI) {
5151
], { status: 0 });
5252
}
5353

54+
// node:vfs is enabled by default and can be disabled with
55+
// --no-experimental-vfs.
56+
spawnSyncAndAssert(process.execPath, [
57+
'--no-experimental-vfs',
58+
'-e', `const m = require('node:module'); if (m.builtinModules.includes('node:vfs')) process.exit(1); try { require('node:vfs'); } catch (e) { if (e.code === 'ERR_UNKNOWN_BUILTIN_MODULE') process.exit(0); } process.exit(1);`,
59+
], { status: 0 });
60+
5461
const schemeOnlyBuiltins = ['node:test', 'node:sea', 'node:vfs'];
5562
if (common.hasFFI) {
5663
schemeOnlyBuiltins.push('node:ffi');

‎test/parallel/test-vfs-flag.js‎

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

3-
// node:vfs is available without an experimental flag. The old flag remains a
4-
// no-op for compatibility, and the module remains available with --no- form.
3+
// node:vfs is available without an experimental flag. The old positive flag
4+
// remains accepted for compatibility, and --no-experimental-vfs disables it.
55

66
require('../common');
77
const { spawnSyncAndAssert } = require('../common/child_process');
@@ -28,14 +28,25 @@ const { spawnSyncAndAssert } = require('../common/child_process');
2828
});
2929
}
3030

31-
// The old flag is accepted for compatibility but no longer gates the module.
32-
for (const flag of ['--experimental-vfs', '--no-experimental-vfs']) {
33-
spawnSyncAndAssert(process.execPath, [
34-
flag,
35-
'-p',
36-
'require("node:module").builtinModules.includes("node:vfs")',
37-
], { stdout: 'true\n', stderr: '' });
38-
}
31+
// The old positive flag is accepted for compatibility but no longer gates the
32+
// module.
33+
spawnSyncAndAssert(process.execPath, [
34+
'--experimental-vfs',
35+
'-p',
36+
'require("node:module").builtinModules.includes("node:vfs")',
37+
], { stdout: 'true\n', stderr: '' });
38+
39+
// --no-experimental-vfs disables node:vfs.
40+
spawnSyncAndAssert(process.execPath, [
41+
'--no-experimental-vfs',
42+
'-p',
43+
'require("node:module").builtinModules.includes("node:vfs")',
44+
], { stdout: 'false\n', stderr: '' });
45+
46+
spawnSyncAndAssert(process.execPath, [
47+
'--no-experimental-vfs',
48+
'-e', 'require("node:vfs")',
49+
], { status: 1, stderr: /ERR_UNKNOWN_BUILTIN_MODULE/ });
3950

4051
// Bare `vfs` (no node: scheme) remains unavailable.
4152
spawnSyncAndAssert(process.execPath, [

‎test/parallel/test-vfs-load.js‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ function envArg(flag, value) {
4949
assert.match(res.stdout, /hello from inside the mount/);
5050
}
5151

52+
// --no-experimental-vfs disables --vfs-load.
53+
{
54+
const dir = fixture('disabled');
55+
fs.mkdirSync(dir, { recursive: true });
56+
const res = run(['--no-experimental-vfs', `--vfs-load=${dir}`]);
57+
assert.notStrictEqual(res.status, 0);
58+
assert.match(res.stderr, /--vfs-load requires node:vfs to be enabled/);
59+
}
60+
5261
// A provider registered by a -r (CommonJS) preload backs a custom file format.
5362
{
5463
const providerModule = fixture('provider.js');
@@ -318,8 +327,8 @@ if (hasNodeOptions) {
318327
}
319328
}
320329

321-
// The old --experimental-vfs option remains accepted in NODE_OPTIONS, but no
322-
// longer gates --vfs-load.
330+
// The old --experimental-vfs option remains accepted in NODE_OPTIONS. Its
331+
// positive form no longer gates --vfs-load, and its negated form disables it.
323332
if (hasNodeOptions) {
324333
const dir = fixture('env-flag-cli-load');
325334
fs.mkdirSync(dir, { recursive: true });
@@ -331,6 +340,13 @@ if (hasNodeOptions) {
331340
});
332341
assert.strictEqual(res.status, 0, res.stderr);
333342
assert.match(res.stdout, /ran/);
343+
344+
const disabled = spawnSync(process.execPath, [`--vfs-load=${dir}`], {
345+
encoding: 'utf8',
346+
env: { ...process.env, NODE_OPTIONS: '--no-experimental-vfs' },
347+
});
348+
assert.notStrictEqual(disabled.status, 0);
349+
assert.match(disabled.stderr, /--vfs-load requires node:vfs to be enabled/);
334350
}
335351

336352
// Under --vfs-load the entry point comes from the mount, so no positional

0 commit comments

Comments
 (0)