Skip to content

Commit 3833d98

Browse files
committed
vfs: add SEA (Single Executable Application) integration
Add support for accessing SEA embedded assets through the VFS: - Add lib/internal/vfs/sea.js with createSeaVfs() and getSeaVfs() functions - Add fs.getSeaVfs() to get a VFS populated with SEA assets - Add fs.hasSeaAssets() to check if SEA assets are available - SEA assets are mounted at /sea by default (configurable via prefix option) - Document SEA integration in fs.md This allows SEA applications to access embedded assets using standard fs APIs instead of the lower-level sea.getAsset() API.
1 parent 40ab909 commit 3833d98

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

doc/api/fs.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8695,6 +8695,85 @@ for await (const file of glob('/virtual/src/**/*.js')) {
86958695
}
86968696
```
86978697

8698+
### SEA (Single Executable Application) integration
8699+
8700+
When running as a Single Executable Application, the VFS can automatically
8701+
provide access to embedded assets through standard file system APIs.
8702+
8703+
#### `fs.hasSeaAssets()`
8704+
8705+
<!-- YAML
8706+
added: REPLACEME
8707+
-->
8708+
8709+
* Returns: {boolean}
8710+
8711+
Returns `true` if running as a SEA with embedded assets, `false` otherwise.
8712+
8713+
```cjs
8714+
const fs = require('node:fs');
8715+
8716+
if (fs.hasSeaAssets()) {
8717+
console.log('Running as SEA with assets');
8718+
}
8719+
```
8720+
8721+
#### `fs.getSeaVfs([options])`
8722+
8723+
<!-- YAML
8724+
added: REPLACEME
8725+
-->
8726+
8727+
* `options` {Object}
8728+
* `prefix` {string} Mount point prefix for SEA assets. **Default:** `'/sea'`.
8729+
* `moduleHooks` {boolean} Whether to enable require/import hooks.
8730+
**Default:** `true`.
8731+
* Returns: {VirtualFileSystem|null}
8732+
8733+
Returns a VFS populated with all SEA embedded assets, mounted at the specified
8734+
prefix. Returns `null` if not running as a SEA or if there are no assets.
8735+
8736+
The VFS is a singleton - subsequent calls return the same instance (options
8737+
are only used on the first call).
8738+
8739+
```cjs
8740+
const fs = require('node:fs');
8741+
8742+
// In a SEA with an embedded 'config.json' asset:
8743+
const seaVfs = fs.getSeaVfs();
8744+
if (seaVfs) {
8745+
// Access embedded assets via standard fs APIs
8746+
const config = fs.readFileSync('/sea/config.json', 'utf8');
8747+
console.log(JSON.parse(config));
8748+
8749+
// Or require modules from embedded assets
8750+
const utils = require('/sea/utils.js');
8751+
}
8752+
```
8753+
8754+
To use SEA assets, first configure them in your SEA configuration file:
8755+
8756+
```json
8757+
{
8758+
"main": "app.js",
8759+
"output": "app.blob",
8760+
"assets": {
8761+
"config.json": "path/to/config.json",
8762+
"utils.js": "path/to/utils.js"
8763+
}
8764+
}
8765+
```
8766+
8767+
Then access them via the VFS:
8768+
8769+
```cjs
8770+
const fs = require('node:fs');
8771+
const seaVfs = fs.getSeaVfs();
8772+
8773+
// Assets are accessible at /sea/<asset-key>
8774+
const config = fs.readFileSync('/sea/config.json', 'utf8');
8775+
```
8776+
86988777
### Limitations
86998778

87008779
The current VFS implementation has the following limitations:

lib/fs.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3207,18 +3207,42 @@ function globSync(pattern, options) {
32073207
}
32083208

32093209
const lazyVfs = getLazy(() => require('internal/vfs/virtual_fs').VirtualFileSystem);
3210+
const lazySeaVfs = getLazy(() => require('internal/vfs/sea'));
32103211

32113212
/**
32123213
* Creates a new virtual file system instance.
32133214
* @param {object} [options] Configuration options
32143215
* @param {boolean} [options.fallthrough=true] Whether to fall through to real fs on miss
3216+
* @param {boolean} [options.moduleHooks=true] Whether to enable require/import hooks
32153217
* @returns {VirtualFileSystem}
32163218
*/
32173219
function createVirtual(options) {
32183220
const VirtualFileSystem = lazyVfs();
32193221
return new VirtualFileSystem(options);
32203222
}
32213223

3224+
/**
3225+
* Gets the VFS containing SEA (Single Executable Application) assets.
3226+
* Returns null if not running as a SEA or if there are no assets.
3227+
* @param {object} [options] Configuration options
3228+
* @param {string} [options.prefix='/sea'] Mount point prefix for SEA assets
3229+
* @param {boolean} [options.moduleHooks=true] Whether to enable require/import hooks
3230+
* @returns {VirtualFileSystem|null}
3231+
*/
3232+
function getSeaVfs(options) {
3233+
const { getSeaVfs: getSeaVfsInternal } = lazySeaVfs();
3234+
return getSeaVfsInternal(options);
3235+
}
3236+
3237+
/**
3238+
* Checks if SEA assets are available.
3239+
* @returns {boolean}
3240+
*/
3241+
function hasSeaAssets() {
3242+
const { hasSeaAssets: hasSeaAssetsInternal } = lazySeaVfs();
3243+
return hasSeaAssetsInternal();
3244+
}
3245+
32223246
module.exports = fs = {
32233247
appendFile,
32243248
appendFileSync,
@@ -3237,6 +3261,8 @@ module.exports = fs = {
32373261
createReadStream,
32383262
createVirtual,
32393263
createWriteStream,
3264+
getSeaVfs,
3265+
hasSeaAssets,
32403266
exists,
32413267
existsSync,
32423268
fchown,

lib/internal/vfs/sea.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use strict';
2+
3+
const {
4+
StringPrototypeStartsWith,
5+
} = primordials;
6+
7+
const { isSea, getAsset, getAssetKeys } = require('sea');
8+
9+
// Lazy-loaded VFS
10+
let cachedSeaVfs = null;
11+
12+
/**
13+
* Creates a VirtualFileSystem populated with SEA assets.
14+
* Assets are mounted at the specified prefix (default: '/sea').
15+
* @param {object} [options] Configuration options
16+
* @param {string} [options.prefix='/sea'] Mount point prefix for SEA assets
17+
* @param {boolean} [options.moduleHooks=true] Whether to enable require/import hooks
18+
* @returns {VirtualFileSystem|null} The VFS instance, or null if not running as SEA
19+
*/
20+
function createSeaVfs(options = {}) {
21+
if (!isSea()) {
22+
return null;
23+
}
24+
25+
const { VirtualFileSystem } = require('internal/vfs/virtual_fs');
26+
const prefix = options.prefix ?? '/sea';
27+
const moduleHooks = options.moduleHooks !== false;
28+
29+
const vfs = new VirtualFileSystem({ moduleHooks });
30+
31+
// Get all asset keys and populate VFS
32+
const keys = getAssetKeys();
33+
for (let i = 0; i < keys.length; i++) {
34+
const key = keys[i];
35+
// Get asset content as ArrayBuffer and convert to Buffer
36+
const content = getAsset(key);
37+
const buffer = Buffer.from(content);
38+
39+
// Determine the path - if key starts with /, use as-is, otherwise prepend /
40+
const path = StringPrototypeStartsWith(key, '/') ? key : `/${key}`;
41+
vfs.addFile(path, buffer);
42+
}
43+
44+
// Mount at the specified prefix
45+
vfs.mount(prefix);
46+
47+
return vfs;
48+
}
49+
50+
/**
51+
* Gets or creates the default SEA VFS instance.
52+
* This is a singleton that is lazily created on first access.
53+
* @param {object} [options] Configuration options (only used on first call)
54+
* @returns {VirtualFileSystem|null} The VFS instance, or null if not running as SEA
55+
*/
56+
function getSeaVfs(options) {
57+
if (cachedSeaVfs === null) {
58+
cachedSeaVfs = createSeaVfs(options);
59+
}
60+
return cachedSeaVfs;
61+
}
62+
63+
/**
64+
* Checks if SEA VFS is available (i.e., running as SEA with assets).
65+
* @returns {boolean}
66+
*/
67+
function hasSeaAssets() {
68+
if (!isSea()) {
69+
return false;
70+
}
71+
const keys = getAssetKeys();
72+
return keys.length > 0;
73+
}
74+
75+
module.exports = {
76+
createSeaVfs,
77+
getSeaVfs,
78+
hasSeaAssets,
79+
};

test/parallel/test-vfs-sea.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
7+
// Test that SEA functions exist
8+
assert.strictEqual(typeof fs.getSeaVfs, 'function');
9+
assert.strictEqual(typeof fs.hasSeaAssets, 'function');
10+
11+
// Test that SEA functions return appropriate values when not in SEA
12+
{
13+
// hasSeaAssets should return false when not running as SEA
14+
const hasAssets = fs.hasSeaAssets();
15+
assert.strictEqual(hasAssets, false);
16+
17+
// getSeaVfs should return null when not running as SEA
18+
const seaVfs = fs.getSeaVfs();
19+
assert.strictEqual(seaVfs, null);
20+
}
21+
22+
// Test with custom options (should still return null when not in SEA)
23+
{
24+
const seaVfs = fs.getSeaVfs({ prefix: '/custom-sea', moduleHooks: false });
25+
assert.strictEqual(seaVfs, null);
26+
}

0 commit comments

Comments
 (0)