mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
sea: implement sea.getAssetKeys()
This adds a new API to allow the bundled script in SEA to query the list of assets. PR-URL: https://github.com/nodejs/node/pull/59661 Refs: https://github.com/nodejs/single-executable/discussions/112 Reviewed-By: Darshan Sen <raisinten@gmail.com>
This commit is contained in:
parent
66fcca4328
commit
6428e2e4ca
|
|
@ -221,7 +221,10 @@ executable, users can retrieve the assets using the [`sea.getAsset()`][] and
|
|||
The single-executable application can access the assets as follows:
|
||||
|
||||
```cjs
|
||||
const { getAsset, getAssetAsBlob, getRawAsset } = require('node:sea');
|
||||
const { getAsset, getAssetAsBlob, getRawAsset, getAssetKeys } = require('node:sea');
|
||||
// Get all asset keys.
|
||||
const keys = getAssetKeys();
|
||||
console.log(keys); // ['a.jpg', 'b.txt']
|
||||
// Returns a copy of the data in an ArrayBuffer.
|
||||
const image = getAsset('a.jpg');
|
||||
// Returns a string decoded from the asset as UTF8.
|
||||
|
|
@ -232,8 +235,8 @@ const blob = getAssetAsBlob('a.jpg');
|
|||
const raw = getRawAsset('a.jpg');
|
||||
```
|
||||
|
||||
See documentation of the [`sea.getAsset()`][], [`sea.getAssetAsBlob()`][] and [`sea.getRawAsset()`][]
|
||||
APIs for more information.
|
||||
See documentation of the [`sea.getAsset()`][], [`sea.getAssetAsBlob()`][],
|
||||
[`sea.getRawAsset()`][] and [`sea.getAssetKeys()`][] APIs for more information.
|
||||
|
||||
### Startup snapshot support
|
||||
|
||||
|
|
@ -429,6 +432,19 @@ writes to the returned array buffer is likely to result in a crash.
|
|||
`assets` field in the single-executable application configuration.
|
||||
* Returns: {ArrayBuffer}
|
||||
|
||||
### `sea.getAssetKeys()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns {string\[]} An array containing all the keys of the assets
|
||||
embedded in the executable. If no assets are embedded, returns an empty array.
|
||||
|
||||
This method can be used to retrieve an array of all the keys of assets
|
||||
embedded into the single-executable application.
|
||||
An error is thrown when not running inside a single-executable application.
|
||||
|
||||
### `require(id)` in the injected main script is not file based
|
||||
|
||||
`require()` in the injected main script is not the same as the [`require()`][]
|
||||
|
|
@ -503,6 +519,7 @@ to help us document them.
|
|||
[`require.main`]: modules.md#accessing-the-main-module
|
||||
[`sea.getAsset()`]: #seagetassetkey-encoding
|
||||
[`sea.getAssetAsBlob()`]: #seagetassetasblobkey-options
|
||||
[`sea.getAssetKeys()`]: #seagetassetkeys
|
||||
[`sea.getRawAsset()`]: #seagetrawassetkey
|
||||
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
|
||||
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
|
||||
|
|
|
|||
16
lib/sea.js
16
lib/sea.js
|
|
@ -3,7 +3,7 @@ const {
|
|||
ArrayBufferPrototypeSlice,
|
||||
} = primordials;
|
||||
|
||||
const { isSea, getAsset: getAssetInternal } = internalBinding('sea');
|
||||
const { isSea, getAsset: getAssetInternal, getAssetKeys: getAssetKeysInternal } = internalBinding('sea');
|
||||
const { TextDecoder } = require('internal/encoding');
|
||||
const { validateString } = require('internal/validators');
|
||||
const {
|
||||
|
|
@ -68,9 +68,23 @@ function getAssetAsBlob(key, options) {
|
|||
return new Blob([asset], options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the keys of assets embedded into the
|
||||
* single-executable application.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getAssetKeys() {
|
||||
if (!isSea()) {
|
||||
throw new ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION();
|
||||
}
|
||||
|
||||
return getAssetKeysInternal() || [];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isSea,
|
||||
getAsset,
|
||||
getRawAsset,
|
||||
getAssetAsBlob,
|
||||
getAssetKeys,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <vector>
|
||||
|
||||
using node::ExitCode;
|
||||
using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
using v8::Context;
|
||||
|
|
@ -807,6 +808,25 @@ void GetAsset(const FunctionCallbackInfo<Value>& args) {
|
|||
args.GetReturnValue().Set(ab);
|
||||
}
|
||||
|
||||
void GetAssetKeys(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 0);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
SeaResource sea_resource = FindSingleExecutableResource();
|
||||
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
LocalVector<Value> keys(isolate);
|
||||
keys.reserve(sea_resource.assets.size());
|
||||
for (const auto& [key, _] : sea_resource.assets) {
|
||||
Local<Value> key_str;
|
||||
if (!ToV8Value(context, key).ToLocal(&key_str)) {
|
||||
return;
|
||||
}
|
||||
keys.push_back(key_str);
|
||||
}
|
||||
Local<Array> result = Array::New(isolate, keys.data(), keys.size());
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
MaybeLocal<Value> LoadSingleExecutableApplication(
|
||||
const StartExecutionCallbackInfo& info) {
|
||||
// Here we are currently relying on the fact that in NodeMainInstance::Run(),
|
||||
|
|
@ -858,12 +878,14 @@ void Initialize(Local<Object> target,
|
|||
"isExperimentalSeaWarningNeeded",
|
||||
IsExperimentalSeaWarningNeeded);
|
||||
SetMethod(context, target, "getAsset", GetAsset);
|
||||
SetMethod(context, target, "getAssetKeys", GetAssetKeys);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(IsSea);
|
||||
registry->Register(IsExperimentalSeaWarningNeeded);
|
||||
registry->Register(GetAsset);
|
||||
registry->Register(GetAssetKeys);
|
||||
}
|
||||
|
||||
} // namespace sea
|
||||
|
|
|
|||
9
test/fixtures/sea/get-asset-keys.js
vendored
Normal file
9
test/fixtures/sea/get-asset-keys.js
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
const { isSea, getAssetKeys } = require('node:sea');
|
||||
const assert = require('node:assert');
|
||||
|
||||
assert(isSea());
|
||||
|
||||
const keys = getAssetKeys();
|
||||
console.log('Asset keys:', JSON.stringify(keys.sort()));
|
||||
11
test/parallel/test-sea-get-asset-keys.js
Normal file
11
test/parallel/test-sea-get-asset-keys.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const { getAssetKeys } = require('node:sea');
|
||||
const assert = require('node:assert');
|
||||
|
||||
// Test that getAssetKeys throws when not in SEA
|
||||
assert.throws(() => getAssetKeys(), {
|
||||
code: 'ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION'
|
||||
});
|
||||
|
|
@ -57,6 +57,8 @@ test-watch-mode-inspect: SKIP
|
|||
test-single-executable-application: SKIP
|
||||
test-single-executable-application-assets: SKIP
|
||||
test-single-executable-application-assets-raw: SKIP
|
||||
test-single-executable-application-asset-keys-empty: SKIP
|
||||
test-single-executable-application-asset-keys: SKIP
|
||||
test-single-executable-application-disable-experimental-sea-warning: SKIP
|
||||
test-single-executable-application-empty: SKIP
|
||||
test-single-executable-application-exec-argv: SKIP
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
'use strict';
|
||||
|
||||
// This test verifies that the `getAssetKeys()` function works correctly
|
||||
// in a single executable application without any assets.
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
generateSEA,
|
||||
skipIfSingleExecutableIsNotSupported,
|
||||
} = require('../common/sea');
|
||||
|
||||
skipIfSingleExecutableIsNotSupported();
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const { copyFileSync, writeFileSync, existsSync } = require('fs');
|
||||
const {
|
||||
spawnSyncAndExitWithoutError,
|
||||
spawnSyncAndAssert,
|
||||
} = require('../common/child_process');
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
|
||||
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
|
||||
tmpdir.refresh();
|
||||
copyFileSync(fixtures.path('sea', 'get-asset-keys.js'), tmpdir.resolve('sea.js'));
|
||||
|
||||
writeFileSync(tmpdir.resolve('sea-config.json'), `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob"
|
||||
}
|
||||
`, 'utf8');
|
||||
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', 'sea-config.json'],
|
||||
{
|
||||
env: {
|
||||
NODE_DEBUG_NATIVE: 'SEA',
|
||||
...process.env,
|
||||
},
|
||||
cwd: tmpdir.path
|
||||
},
|
||||
{});
|
||||
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
generateSEA(outputFile, process.execPath, seaPrepBlob);
|
||||
|
||||
spawnSyncAndAssert(
|
||||
outputFile,
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_DEBUG_NATIVE: 'SEA',
|
||||
}
|
||||
},
|
||||
{
|
||||
stdout: /Asset keys: \[\]/,
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
// This test verifies that the `getAssetKeys()` function works correctly
|
||||
// in a single executable application with assets.
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
generateSEA,
|
||||
skipIfSingleExecutableIsNotSupported,
|
||||
} = require('../common/sea');
|
||||
|
||||
skipIfSingleExecutableIsNotSupported();
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const { copyFileSync, writeFileSync, existsSync } = require('fs');
|
||||
const {
|
||||
spawnSyncAndExitWithoutError,
|
||||
spawnSyncAndAssert,
|
||||
} = require('../common/child_process');
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const configFile = tmpdir.resolve('sea-config.json');
|
||||
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
|
||||
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
|
||||
tmpdir.refresh();
|
||||
copyFileSync(fixtures.path('sea', 'get-asset-keys.js'), tmpdir.resolve('sea.js'));
|
||||
writeFileSync(tmpdir.resolve('asset-1.txt'), 'This is asset 1');
|
||||
writeFileSync(tmpdir.resolve('asset-2.txt'), 'This is asset 2');
|
||||
writeFileSync(tmpdir.resolve('asset-3.txt'), 'This is asset 3');
|
||||
|
||||
writeFileSync(configFile, `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob",
|
||||
"assets": {
|
||||
"asset-1.txt": "asset-1.txt",
|
||||
"asset-2.txt": "asset-2.txt",
|
||||
"asset-3.txt": "asset-3.txt"
|
||||
}
|
||||
}
|
||||
`, 'utf8');
|
||||
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', 'sea-config.json'],
|
||||
{
|
||||
env: {
|
||||
NODE_DEBUG_NATIVE: 'SEA',
|
||||
...process.env,
|
||||
},
|
||||
cwd: tmpdir.path
|
||||
},
|
||||
{});
|
||||
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
generateSEA(outputFile, process.execPath, seaPrepBlob);
|
||||
|
||||
spawnSyncAndAssert(
|
||||
outputFile,
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_DEBUG_NATIVE: 'SEA',
|
||||
}
|
||||
},
|
||||
{
|
||||
stdout: /Asset keys: \["asset-1\.txt","asset-2\.txt","asset-3\.txt"\]/,
|
||||
}
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user