Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions doc/api/vfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,43 @@ const memoryVfs = vfs.create();
const realVfs = vfs.create(new vfs.RealFSProvider('/tmp/vfs-root'));
```

## `vfs.mounted()`

<!-- YAML
added: REPLACEME
-->

* Returns: {VirtualFileSystem\[]}

Returns the {VirtualFileSystem} instances that are currently mounted, in the
order they were mounted. This includes file systems mounted with
[`--vfs-mount`][] or [`--vfs-load`][], and the assets of a
[Single Executable Application][], as well as those mounted by calling
[`vfs.mount()`][].

Mounts are tracked per thread. A [`Worker`][] lists its own mounts, including
its own mounts of the [`--vfs-mount`][] and [`--vfs-load`][] sources, and not
those of the thread that started it.

A new array is returned on every call. It does not update as file systems are
mounted and unmounted.

```cjs
const vfs = require('node:vfs');

const a = vfs.create();
const b = vfs.create();
a.mount();
b.mount();

vfs.mounted(); // [a, b]
vfs.mounted().map((fs) => fs.mountPoint);
// e.g. ['/dev/null/vfs/0', '/dev/null/vfs/1']

a.unmount();
vfs.mounted(); // [b]
```

## `vfs.registerProvider(entry)`

<!-- YAML
Expand Down Expand Up @@ -696,11 +733,13 @@ fields use synthetic but stable values:
[Single Executable Application]: single-executable-applications.md
[`--import`]: cli.md#--importmodule
[`--require`]: cli.md#-r---require-module
[`--vfs-load`]: cli.md#--vfs-loadsource
[`--vfs-mount`]: cli.md#--vfs-mountsource
[`MemoryProvider`]: #class-memoryprovider
[`RealFSProvider`]: #class-realfsprovider
[`VirtualFileSystem`]: #class-virtualfilesystem
[`VirtualProvider`]: #class-virtualprovider
[`Worker`]: worker_threads.md#class-worker
[`ZipProvider`]: #class-zipprovider
[`ffi.dlopen()`]: ffi.md#ffidlopenpath-definitions
[`fs.BigIntStats`]: fs.md#class-fsstats
Expand Down
6 changes: 6 additions & 0 deletions lib/internal/vfs/setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ArrayFrom,
ArrayPrototypeForEach,
MapPrototypeForEach,
ObjectKeys,
Expand Down Expand Up @@ -106,6 +107,10 @@ function registerVFS(vfs) {
}
}

function getMountedVFS() {
return ArrayFrom(activeVFSLayers.values());
}

function deregisterVFS(vfs) {
if (!activeVFSLayers.delete(vfs[kLayerId])) return;
debug('deregister layer=%d active=%d', vfs[kLayerId], activeVFSLayers.size);
Expand Down Expand Up @@ -1095,4 +1100,5 @@ function uninstallHooks() {
module.exports = {
registerVFS,
deregisterVFS,
getMountedVFS,
};
10 changes: 10 additions & 0 deletions lib/vfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,18 @@ function create(provider, options) {
return new VirtualFileSystem(provider, options);
}

/**
* Returns the VirtualFileSystem instances currently mounted in this thread,
* in the order they were mounted.
* @returns {VirtualFileSystem[]}
*/
function mounted() {
return require('internal/vfs/setup').getMountedVFS();
}

module.exports = {
create,
mounted,
registerProvider,
VirtualFileSystem,
VirtualProvider,
Expand Down
107 changes: 107 additions & 0 deletions test/parallel/test-vfs-mounted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Flags: --experimental-vfs
'use strict';

require('../common');
const assert = require('assert');
const vfs = require('node:vfs');

assert.deepStrictEqual(vfs.mounted(), []);

const a = vfs.create();
const b = vfs.create();
const c = vfs.create();

// Created but not yet mounted.
assert.deepStrictEqual(vfs.mounted(), []);

// Mount order, not creation order.
b.mount();
a.mount();
c.mount();
{
const list = vfs.mounted();
assert.strictEqual(list.length, 3);
assert.strictEqual(list[0], b);
assert.strictEqual(list[1], a);
assert.strictEqual(list[2], c);
assert.ok(list.every((fs) => fs instanceof vfs.VirtualFileSystem));
assert.ok(list.every((fs) => fs.mounted));
}

// The result is a snapshot; mutating it does not affect the registry.
{
const list = vfs.mounted();
list.length = 0;
assert.strictEqual(vfs.mounted().length, 3);
assert.notStrictEqual(vfs.mounted(), vfs.mounted());
}

a.unmount();
{
const list = vfs.mounted();
assert.strictEqual(list.length, 2);
assert.strictEqual(list[0], b);
assert.strictEqual(list[1], c);
}

// Remounting moves the instance to the end.
a.mount();
{
const list = vfs.mounted();
assert.strictEqual(list.length, 3);
assert.strictEqual(list[0], b);
assert.strictEqual(list[1], c);
assert.strictEqual(list[2], a);
}

// Unmounting an already-unmounted instance leaves the list unchanged.
b.unmount();
b.unmount();
{
const list = vfs.mounted();
assert.strictEqual(list.length, 2);
assert.strictEqual(list[0], c);
assert.strictEqual(list[1], a);
}

// Explicit resource management unmounts and removes the entry.
{
using d = vfs.create();
d.mount();
assert.strictEqual(vfs.mounted().at(-1), d);
}
assert.strictEqual(vfs.mounted().length, 2);

a.unmount();
c.unmount();
assert.deepStrictEqual(vfs.mounted(), []);

// Mounts made by --vfs-mount at startup are listed too, in command-line order.
{
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
const path = require('path');
const { spawnSyncAndAssert } = require('../common/child_process');

tmpdir.refresh();
const first = tmpdir.resolve('first');
const second = tmpdir.resolve('second');
fs.mkdirSync(first);
fs.mkdirSync(second);
fs.writeFileSync(path.join(first, 'name.txt'), 'first');
fs.writeFileSync(path.join(second, 'name.txt'), 'second');

spawnSyncAndAssert(process.execPath, [
'--experimental-vfs',
`--vfs-mount=${first}`,
`--vfs-mount=${second}`,
'-p',
`const fs = require('fs');
require('node:vfs').mounted()
.map((v) => fs.readFileSync(v.mountPoint + '/name.txt', 'utf8'))
.join(',')`,
], {
stdout: 'first,second',
trim: true,
});
}
Loading