mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
sea: add option to disable the experimental SEA warning
Refs: https://github.com/nodejs/single-executable/discussions/60 Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: https://github.com/nodejs/node/pull/47588 Fixes: https://github.com/nodejs/node/issues/47741 Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Tierney Cyren <hello@bnb.im> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
1 parent
c868aad15a
commit
2de10f5149
14 files changed
+443
-149
No files matched your search
@@ -150,6 +150,7 @@
|
||||
/src/node_sea* @nodejs/single-executable
|
||||
/test/fixtures/postject-copy @nodejs/single-executable
|
||||
/test/parallel/test-single-executable-* @nodejs/single-executable
|
||||
/test/sequential/test-single-executable-* @nodejs/single-executable
|
||||
/tools/dep_updaters/update-postject.sh @nodejs/single-executable
|
||||
|
||||
# Permission Model
|
||||
|
||||
@@ -164,7 +164,8 @@ The configuration currently reads the following top-level fields:
|
||||
```json
|
||||
{
|
||||
"main": "/path/to/bundled/script.js",
|
||||
"output": "/path/to/write/the/generated/blob.blob"
|
||||
"output": "/path/to/write/the/generated/blob.blob",
|
||||
"disableExperimentalSEAWarning": true // Default: false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const {
|
||||
prepareMainThreadExecution,
|
||||
markBootstrapComplete,
|
||||
} = require('internal/process/pre_execution');
|
||||
const { isSea } = internalBinding('sea');
|
||||
const { isExperimentalSeaWarningNeeded } = internalBinding('sea');
|
||||
const { emitExperimentalWarning } = require('internal/util');
|
||||
const { embedderRequire, embedderRunCjs } = require('internal/util/embedding');
|
||||
const { getEmbedderEntryFunction } = internalBinding('mksnapshot');
|
||||
@@ -11,7 +11,7 @@ const { getEmbedderEntryFunction } = internalBinding('mksnapshot');
|
||||
prepareMainThreadExecution(false, true);
|
||||
markBootstrapComplete();
|
||||
|
||||
if (isSea()) {
|
||||
if (isExperimentalSeaWarningNeeded()) {
|
||||
emitExperimentalWarning('Single executable application');
|
||||
}
|
||||
|
||||
|
||||
+33
-5
@@ -58,8 +58,8 @@ bool JSONParser::Parse(const std::string& content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> JSONParser::GetTopLevelField(
|
||||
const std::string& field) {
|
||||
std::optional<std::string> JSONParser::GetTopLevelStringField(
|
||||
std::string_view field) {
|
||||
Isolate* isolate = isolate_.get();
|
||||
Local<Context> context = context_.Get(isolate);
|
||||
Local<Object> content_object = content_.Get(isolate);
|
||||
@@ -67,9 +67,11 @@ std::optional<std::string> JSONParser::GetTopLevelField(
|
||||
// It's not a real script, so don't print the source line.
|
||||
errors::PrinterTryCatch bootstrapCatch(
|
||||
isolate, errors::PrinterTryCatch::kDontPrintSourceLine);
|
||||
if (!content_object
|
||||
->Get(context, OneByteString(isolate, field.c_str(), field.length()))
|
||||
.ToLocal(&value) ||
|
||||
Local<Value> field_local;
|
||||
if (!ToV8Value(context, field, isolate).ToLocal(&field_local)) {
|
||||
return {};
|
||||
}
|
||||
if (!content_object->Get(context, field_local).ToLocal(&value) ||
|
||||
!value->IsString()) {
|
||||
return {};
|
||||
}
|
||||
@@ -77,4 +79,30 @@ std::optional<std::string> JSONParser::GetTopLevelField(
|
||||
return utf8_value.ToString();
|
||||
}
|
||||
|
||||
std::optional<bool> JSONParser::GetTopLevelBoolField(std::string_view field) {
|
||||
Isolate* isolate = isolate_.get();
|
||||
Local<Context> context = context_.Get(isolate);
|
||||
Local<Object> content_object = content_.Get(isolate);
|
||||
Local<Value> value;
|
||||
bool has_field;
|
||||
// It's not a real script, so don't print the source line.
|
||||
errors::PrinterTryCatch bootstrapCatch(
|
||||
isolate, errors::PrinterTryCatch::kDontPrintSourceLine);
|
||||
Local<Value> field_local;
|
||||
if (!ToV8Value(context, field, isolate).ToLocal(&field_local)) {
|
||||
return {};
|
||||
}
|
||||
if (!content_object->Has(context, field_local).To(&has_field)) {
|
||||
return {};
|
||||
}
|
||||
if (!has_field) {
|
||||
return false;
|
||||
}
|
||||
if (!content_object->Get(context, field_local).ToLocal(&value) ||
|
||||
!value->IsBoolean()) {
|
||||
return {};
|
||||
}
|
||||
return value->BooleanValue(isolate);
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
+2
-1
@@ -18,7 +18,8 @@ class JSONParser {
|
||||
JSONParser();
|
||||
~JSONParser() {}
|
||||
bool Parse(const std::string& content);
|
||||
std::optional<std::string> GetTopLevelField(const std::string& field);
|
||||
std::optional<std::string> GetTopLevelStringField(std::string_view field);
|
||||
std::optional<bool> GetTopLevelBoolField(std::string_view field);
|
||||
|
||||
private:
|
||||
// We might want a lighter-weight JSON parser for this use case. But for now
|
||||
|
||||
+79
-12
@@ -33,15 +33,41 @@ using v8::Value;
|
||||
namespace node {
|
||||
namespace sea {
|
||||
|
||||
namespace {
|
||||
// A special number that will appear at the beginning of the single executable
|
||||
// preparation blobs ready to be injected into the binary. We use this to check
|
||||
// that the data given to us are intended for building single executable
|
||||
// applications.
|
||||
static const uint32_t kMagic = 0x143da20;
|
||||
const uint32_t kMagic = 0x143da20;
|
||||
|
||||
std::string_view FindSingleExecutableCode() {
|
||||
enum class SeaFlags : uint32_t {
|
||||
kDefault = 0,
|
||||
kDisableExperimentalSeaWarning = 1 << 0,
|
||||
};
|
||||
|
||||
SeaFlags operator|(SeaFlags x, SeaFlags y) {
|
||||
return static_cast<SeaFlags>(static_cast<uint32_t>(x) |
|
||||
static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
SeaFlags operator&(SeaFlags x, SeaFlags y) {
|
||||
return static_cast<SeaFlags>(static_cast<uint32_t>(x) &
|
||||
static_cast<uint32_t>(y));
|
||||
}
|
||||
|
||||
SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) {
|
||||
return x = x | y;
|
||||
}
|
||||
|
||||
struct SeaResource {
|
||||
SeaFlags flags = SeaFlags::kDefault;
|
||||
std::string_view code;
|
||||
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
|
||||
};
|
||||
|
||||
SeaResource FindSingleExecutableResource() {
|
||||
CHECK(IsSingleExecutable());
|
||||
static const std::string_view sea_code = []() -> std::string_view {
|
||||
static const SeaResource sea_resource = []() -> SeaResource {
|
||||
size_t size;
|
||||
#ifdef __APPLE__
|
||||
postject_options options;
|
||||
@@ -55,18 +81,40 @@ std::string_view FindSingleExecutableCode() {
|
||||
#endif
|
||||
uint32_t first_word = reinterpret_cast<const uint32_t*>(code)[0];
|
||||
CHECK_EQ(first_word, kMagic);
|
||||
SeaFlags flags{
|
||||
reinterpret_cast<const SeaFlags*>(code + sizeof(first_word))[0]};
|
||||
// TODO(joyeecheung): do more checks here e.g. matching the versions.
|
||||
return {code + sizeof(first_word), size - sizeof(first_word)};
|
||||
return {
|
||||
flags,
|
||||
{
|
||||
code + SeaResource::kHeaderSize,
|
||||
size - SeaResource::kHeaderSize,
|
||||
},
|
||||
};
|
||||
}();
|
||||
return sea_code;
|
||||
return sea_resource;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view FindSingleExecutableCode() {
|
||||
SeaResource sea_resource = FindSingleExecutableResource();
|
||||
return sea_resource.code;
|
||||
}
|
||||
|
||||
bool IsSingleExecutable() {
|
||||
return postject_has_resource();
|
||||
}
|
||||
|
||||
void IsSingleExecutable(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(IsSingleExecutable());
|
||||
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
|
||||
if (!IsSingleExecutable()) {
|
||||
args.GetReturnValue().Set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SeaResource sea_resource = FindSingleExecutableResource();
|
||||
args.GetReturnValue().Set(!static_cast<bool>(
|
||||
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
|
||||
}
|
||||
|
||||
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
|
||||
@@ -90,6 +138,7 @@ namespace {
|
||||
struct SeaConfig {
|
||||
std::string main_path;
|
||||
std::string output_path;
|
||||
SeaFlags flags = SeaFlags::kDefault;
|
||||
};
|
||||
|
||||
std::optional<SeaConfig> ParseSingleExecutableConfig(
|
||||
@@ -112,7 +161,8 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.main_path = parser.GetTopLevelField("main").value_or(std::string());
|
||||
result.main_path =
|
||||
parser.GetTopLevelStringField("main").value_or(std::string());
|
||||
if (result.main_path.empty()) {
|
||||
FPrintF(stderr,
|
||||
"\"main\" field of %s is not a non-empty string\n",
|
||||
@@ -121,7 +171,7 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
||||
}
|
||||
|
||||
result.output_path =
|
||||
parser.GetTopLevelField("output").value_or(std::string());
|
||||
parser.GetTopLevelStringField("output").value_or(std::string());
|
||||
if (result.output_path.empty()) {
|
||||
FPrintF(stderr,
|
||||
"\"output\" field of %s is not a non-empty string\n",
|
||||
@@ -129,6 +179,18 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<bool> disable_experimental_sea_warning =
|
||||
parser.GetTopLevelBoolField("disableExperimentalSEAWarning");
|
||||
if (!disable_experimental_sea_warning.has_value()) {
|
||||
FPrintF(stderr,
|
||||
"\"disableExperimentalSEAWarning\" field of %s is not a Boolean\n",
|
||||
config_path);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (disable_experimental_sea_warning.value()) {
|
||||
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -144,9 +206,11 @@ bool GenerateSingleExecutableBlob(const SeaConfig& config) {
|
||||
|
||||
std::vector<char> sink;
|
||||
// TODO(joyeecheung): reuse the SnapshotSerializerDeserializer for this.
|
||||
sink.reserve(sizeof(kMagic) + main_script.size());
|
||||
sink.reserve(SeaResource::kHeaderSize + main_script.size());
|
||||
const char* pos = reinterpret_cast<const char*>(&kMagic);
|
||||
sink.insert(sink.end(), pos, pos + sizeof(kMagic));
|
||||
pos = reinterpret_cast<const char*>(&(config.flags));
|
||||
sink.insert(sink.end(), pos, pos + sizeof(SeaFlags));
|
||||
sink.insert(
|
||||
sink.end(), main_script.data(), main_script.data() + main_script.size());
|
||||
|
||||
@@ -181,11 +245,14 @@ void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
SetMethod(context, target, "isSea", IsSingleExecutable);
|
||||
SetMethod(context,
|
||||
target,
|
||||
"isExperimentalSeaWarningNeeded",
|
||||
IsExperimentalSeaWarningNeeded);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(IsSingleExecutable);
|
||||
registry->Register(IsExperimentalSeaWarningNeeded);
|
||||
}
|
||||
|
||||
} // namespace sea
|
||||
|
||||
@@ -991,6 +991,22 @@ Validates the schema of a diagnostic report file whose path is specified in
|
||||
Validates the schema of a diagnostic report whose content is specified in
|
||||
`report`. If the report fails validation, an exception is thrown.
|
||||
|
||||
## SEA Module
|
||||
|
||||
The `sea` module provides helper functions for testing Single Executable
|
||||
Application functionality.
|
||||
|
||||
### `skipIfSingleExecutableIsNotSupported()`
|
||||
|
||||
Skip the rest of the tests if single executable applications are not supported
|
||||
in the current configuration.
|
||||
|
||||
### `injectAndCodeSign(targetExecutable, resource)`
|
||||
|
||||
Uses Postect to inject the contents of the file at the path `resource` into
|
||||
the target executable file at the path `targetExecutable` and ultimately code
|
||||
sign the final binary.
|
||||
|
||||
## tick Module
|
||||
|
||||
The `tick` module provides a helper function that can be used to call a callback
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const { readFileSync } = require('fs');
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
function skipIfSingleExecutableIsNotSupported() {
|
||||
if (!process.config.variables.single_executable_application)
|
||||
common.skip('Single Executable Application support has been disabled.');
|
||||
|
||||
if (!['darwin', 'win32', 'linux'].includes(process.platform))
|
||||
common.skip(`Unsupported platform ${process.platform}.`);
|
||||
|
||||
if (process.platform === 'linux' && process.config.variables.is_debug === 1)
|
||||
common.skip('Running the resultant binary fails with `Couldn\'t read target executable"`.');
|
||||
|
||||
if (process.config.variables.node_shared)
|
||||
common.skip('Running the resultant binary fails with ' +
|
||||
'`/home/iojs/node-tmp/.tmp.2366/sea: error while loading shared libraries: ' +
|
||||
'libnode.so.112: cannot open shared object file: No such file or directory`.');
|
||||
|
||||
if (process.config.variables.icu_gyp_path === 'tools/icu/icu-system.gyp')
|
||||
common.skip('Running the resultant binary fails with ' +
|
||||
'`/home/iojs/node-tmp/.tmp.2379/sea: error while loading shared libraries: ' +
|
||||
'libicui18n.so.71: cannot open shared object file: No such file or directory`.');
|
||||
|
||||
if (!process.config.variables.node_use_openssl || process.config.variables.node_shared_openssl)
|
||||
common.skip('Running the resultant binary fails with `Node.js is not compiled with OpenSSL crypto support`.');
|
||||
|
||||
if (process.config.variables.want_separate_host_toolset !== 0)
|
||||
common.skip('Running the resultant binary fails with `Segmentation fault (core dumped)`.');
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
const osReleaseText = readFileSync('/etc/os-release', { encoding: 'utf-8' });
|
||||
const isAlpine = /^NAME="Alpine Linux"/m.test(osReleaseText);
|
||||
if (isAlpine) common.skip('Alpine Linux is not supported.');
|
||||
|
||||
if (process.arch === 's390x') {
|
||||
common.skip('On s390x, postject fails with `memory access out of bounds`.');
|
||||
}
|
||||
|
||||
if (process.arch === 'ppc64') {
|
||||
common.skip('On ppc64, this test times out.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function injectAndCodeSign(targetExecutable, resource) {
|
||||
const postjectFile = fixtures.path('postject-copy', 'node_modules', 'postject', 'dist', 'cli.js');
|
||||
execFileSync(process.execPath, [
|
||||
postjectFile,
|
||||
targetExecutable,
|
||||
'NODE_SEA_BLOB',
|
||||
resource,
|
||||
'--sentinel-fuse', 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2',
|
||||
...process.platform === 'darwin' ? [ '--macho-segment-name', 'NODE_SEA' ] : [],
|
||||
]);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
execFileSync('codesign', [ '--sign', '-', targetExecutable ]);
|
||||
execFileSync('codesign', [ '--verify', targetExecutable ]);
|
||||
} else if (process.platform === 'win32') {
|
||||
let signtoolFound = false;
|
||||
try {
|
||||
execFileSync('where', [ 'signtool' ]);
|
||||
signtoolFound = true;
|
||||
} catch (err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
if (signtoolFound) {
|
||||
let certificatesFound = false;
|
||||
try {
|
||||
execFileSync('signtool', [ 'sign', '/fd', 'SHA256', targetExecutable ]);
|
||||
certificatesFound = true;
|
||||
} catch (err) {
|
||||
if (!/SignTool Error: No certificates were found that met all the given criteria/.test(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if (certificatesFound) {
|
||||
execFileSync('signtool', 'verify', '/pa', 'SHA256', targetExecutable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
skipIfSingleExecutableIsNotSupported,
|
||||
injectAndCodeSign,
|
||||
};
|
||||
Vendored
+8
-4
@@ -3,11 +3,15 @@ const createdRequire = createRequire(__filename);
|
||||
|
||||
// Although, require('../common') works locally, that couldn't be used here
|
||||
// because we set NODE_TEST_DIR=/Users/iojs/node-tmp on Jenkins CI.
|
||||
const { expectWarning } = createdRequire(process.env.COMMON_DIRECTORY);
|
||||
const { expectWarning, mustNotCall } = createdRequire(process.env.COMMON_DIRECTORY);
|
||||
|
||||
expectWarning('ExperimentalWarning',
|
||||
'Single executable application is an experimental feature and ' +
|
||||
'might change at any time');
|
||||
if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) {
|
||||
process.on('warning', mustNotCall());
|
||||
} else {
|
||||
expectWarning('ExperimentalWarning',
|
||||
'Single executable application is an experimental feature and ' +
|
||||
'might change at any time');
|
||||
}
|
||||
|
||||
// Should be possible to require core modules that optionally require the
|
||||
// "node:" scheme.
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// This tests the creation of a single executable application.
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const { copyFileSync, readFileSync, writeFileSync, existsSync } = require('fs');
|
||||
const { execFileSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { strictEqual } = require('assert');
|
||||
const assert = require('assert');
|
||||
|
||||
if (!process.config.variables.single_executable_application)
|
||||
common.skip('Single Executable Application support has been disabled.');
|
||||
|
||||
if (!['darwin', 'win32', 'linux'].includes(process.platform))
|
||||
common.skip(`Unsupported platform ${process.platform}.`);
|
||||
|
||||
if (process.platform === 'linux' && process.config.variables.is_debug === 1)
|
||||
common.skip('Running the resultant binary fails with `Couldn\'t read target executable"`.');
|
||||
|
||||
if (process.config.variables.node_shared)
|
||||
common.skip('Running the resultant binary fails with ' +
|
||||
'`/home/iojs/node-tmp/.tmp.2366/sea: error while loading shared libraries: ' +
|
||||
'libnode.so.112: cannot open shared object file: No such file or directory`.');
|
||||
|
||||
if (process.config.variables.icu_gyp_path === 'tools/icu/icu-system.gyp')
|
||||
common.skip('Running the resultant binary fails with ' +
|
||||
'`/home/iojs/node-tmp/.tmp.2379/sea: error while loading shared libraries: ' +
|
||||
'libicui18n.so.71: cannot open shared object file: No such file or directory`.');
|
||||
|
||||
if (!process.config.variables.node_use_openssl || process.config.variables.node_shared_openssl)
|
||||
common.skip('Running the resultant binary fails with `Node.js is not compiled with OpenSSL crypto support`.');
|
||||
|
||||
if (process.config.variables.want_separate_host_toolset !== 0)
|
||||
common.skip('Running the resultant binary fails with `Segmentation fault (core dumped)`.');
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
const osReleaseText = readFileSync('/etc/os-release', { encoding: 'utf-8' });
|
||||
const isAlpine = /^NAME="Alpine Linux"/m.test(osReleaseText);
|
||||
if (isAlpine) common.skip('Alpine Linux is not supported.');
|
||||
|
||||
if (process.arch === 's390x') {
|
||||
common.skip('On s390x, postject fails with `memory access out of bounds`.');
|
||||
}
|
||||
|
||||
if (process.arch === 'ppc64') {
|
||||
common.skip('On ppc64, this test times out.');
|
||||
}
|
||||
}
|
||||
|
||||
const inputFile = fixtures.path('sea.js');
|
||||
const requirableFile = join(tmpdir.path, 'requirable.js');
|
||||
const configFile = join(tmpdir.path, 'sea-config.json');
|
||||
const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob');
|
||||
const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
writeFileSync(requirableFile, `
|
||||
module.exports = {
|
||||
hello: 'world',
|
||||
};
|
||||
`);
|
||||
|
||||
writeFileSync(configFile, `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob"
|
||||
}
|
||||
`);
|
||||
|
||||
// Copy input to working directory
|
||||
copyFileSync(inputFile, join(tmpdir.path, 'sea.js'));
|
||||
execFileSync(process.execPath, ['--experimental-sea-config', 'sea-config.json'], {
|
||||
cwd: tmpdir.path
|
||||
});
|
||||
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
copyFileSync(process.execPath, outputFile);
|
||||
const postjectFile = fixtures.path('postject-copy', 'node_modules', 'postject', 'dist', 'cli.js');
|
||||
execFileSync(process.execPath, [
|
||||
postjectFile,
|
||||
outputFile,
|
||||
'NODE_SEA_BLOB',
|
||||
seaPrepBlob,
|
||||
'--sentinel-fuse', 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2',
|
||||
...process.platform === 'darwin' ? [ '--macho-segment-name', 'NODE_SEA' ] : [],
|
||||
]);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
execFileSync('codesign', [ '--sign', '-', outputFile ]);
|
||||
execFileSync('codesign', [ '--verify', outputFile ]);
|
||||
} else if (process.platform === 'win32') {
|
||||
let signtoolFound = false;
|
||||
try {
|
||||
execFileSync('where', [ 'signtool' ]);
|
||||
signtoolFound = true;
|
||||
} catch (err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
if (signtoolFound) {
|
||||
let certificatesFound = false;
|
||||
try {
|
||||
execFileSync('signtool', [ 'sign', '/fd', 'SHA256', outputFile ]);
|
||||
certificatesFound = true;
|
||||
} catch (err) {
|
||||
if (!/SignTool Error: No certificates were found that met all the given criteria/.test(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if (certificatesFound) {
|
||||
execFileSync('signtool', 'verify', '/pa', 'SHA256', outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const singleExecutableApplicationOutput = execFileSync(
|
||||
outputFile,
|
||||
[ '-a', '--b=c', 'd' ],
|
||||
{ env: { COMMON_DIRECTORY: join(__dirname, '..', 'common') } });
|
||||
strictEqual(singleExecutableApplicationOutput.toString(), 'Hello, world! 😊\n');
|
||||
@@ -115,6 +115,30 @@ const { join } = require('path');
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = join(tmpdir.path, 'invalid-disableExperimentalSEAWarning.json');
|
||||
writeFileSync(config, `
|
||||
{
|
||||
"main": "bundle.js",
|
||||
"output": "sea-prep.blob",
|
||||
"disableExperimentalSEAWarning": "💥"
|
||||
}
|
||||
`, 'utf8');
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
});
|
||||
const stderr = child.stderr.toString();
|
||||
assert.strictEqual(child.status, 1);
|
||||
assert(
|
||||
stderr.includes(
|
||||
`"disableExperimentalSEAWarning" field of ${config} is not a Boolean`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = join(tmpdir.path, 'nonexistent-main-relative.json');
|
||||
|
||||
@@ -48,3 +48,68 @@ const { join } = require('path');
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert(existsSync(output));
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = join(tmpdir.path, 'no-disableExperimentalSEAWarning.json');
|
||||
const main = join(tmpdir.path, 'bundle.js');
|
||||
const output = join(tmpdir.path, 'output.blob');
|
||||
writeFileSync(main, 'console.log("hello")', 'utf-8');
|
||||
const configJson = JSON.stringify({
|
||||
main: 'bundle.js',
|
||||
output: 'output.blob',
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
});
|
||||
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert(existsSync(output));
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = join(tmpdir.path, 'true-disableExperimentalSEAWarning.json');
|
||||
const main = join(tmpdir.path, 'bundle.js');
|
||||
const output = join(tmpdir.path, 'output.blob');
|
||||
writeFileSync(main, 'console.log("hello")', 'utf-8');
|
||||
const configJson = JSON.stringify({
|
||||
main: 'bundle.js',
|
||||
output: 'output.blob',
|
||||
disableExperimentalSEAWarning: true,
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
});
|
||||
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert(existsSync(output));
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
const config = join(tmpdir.path, 'false-disableExperimentalSEAWarning.json');
|
||||
const main = join(tmpdir.path, 'bundle.js');
|
||||
const output = join(tmpdir.path, 'output.blob');
|
||||
writeFileSync(main, 'console.log("hello")', 'utf-8');
|
||||
const configJson = JSON.stringify({
|
||||
main: 'bundle.js',
|
||||
output: 'output.blob',
|
||||
disableExperimentalSEAWarning: false,
|
||||
});
|
||||
writeFileSync(config, configJson, 'utf8');
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', config], {
|
||||
cwd: tmpdir.path,
|
||||
});
|
||||
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert(existsSync(output));
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
injectAndCodeSign,
|
||||
skipIfSingleExecutableIsNotSupported,
|
||||
} = require('../common/sea');
|
||||
|
||||
skipIfSingleExecutableIsNotSupported();
|
||||
|
||||
// This tests the creation of a single executable application which has the
|
||||
// experimental SEA warning disabled.
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const { copyFileSync, writeFileSync, existsSync } = require('fs');
|
||||
const { execFileSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { strictEqual } = require('assert');
|
||||
const assert = require('assert');
|
||||
|
||||
const inputFile = fixtures.path('sea.js');
|
||||
const requirableFile = join(tmpdir.path, 'requirable.js');
|
||||
const configFile = join(tmpdir.path, 'sea-config.json');
|
||||
const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob');
|
||||
const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
writeFileSync(requirableFile, `
|
||||
module.exports = {
|
||||
hello: 'world',
|
||||
};
|
||||
`);
|
||||
|
||||
writeFileSync(configFile, `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob",
|
||||
"disableExperimentalSEAWarning": true
|
||||
}
|
||||
`);
|
||||
|
||||
// Copy input to working directory
|
||||
copyFileSync(inputFile, join(tmpdir.path, 'sea.js'));
|
||||
execFileSync(process.execPath, ['--experimental-sea-config', 'sea-config.json'], {
|
||||
cwd: tmpdir.path
|
||||
});
|
||||
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
copyFileSync(process.execPath, outputFile);
|
||||
injectAndCodeSign(outputFile, seaPrepBlob);
|
||||
|
||||
const singleExecutableApplicationOutput = execFileSync(
|
||||
outputFile,
|
||||
[ '-a', '--b=c', 'd' ],
|
||||
{ env: { COMMON_DIRECTORY: join(__dirname, '..', 'common') } });
|
||||
strictEqual(singleExecutableApplicationOutput.toString(), 'Hello, world! 😊\n');
|
||||
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
injectAndCodeSign,
|
||||
skipIfSingleExecutableIsNotSupported,
|
||||
} = require('../common/sea');
|
||||
|
||||
skipIfSingleExecutableIsNotSupported();
|
||||
|
||||
// This tests the creation of a single executable application.
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const { copyFileSync, writeFileSync, existsSync } = require('fs');
|
||||
const { execFileSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { strictEqual } = require('assert');
|
||||
const assert = require('assert');
|
||||
|
||||
const inputFile = fixtures.path('sea.js');
|
||||
const requirableFile = join(tmpdir.path, 'requirable.js');
|
||||
const configFile = join(tmpdir.path, 'sea-config.json');
|
||||
const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob');
|
||||
const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
writeFileSync(requirableFile, `
|
||||
module.exports = {
|
||||
hello: 'world',
|
||||
};
|
||||
`);
|
||||
|
||||
writeFileSync(configFile, `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob",
|
||||
"disableExperimentalSEAWarning": false
|
||||
}
|
||||
`);
|
||||
|
||||
// Copy input to working directory
|
||||
copyFileSync(inputFile, join(tmpdir.path, 'sea.js'));
|
||||
execFileSync(process.execPath, ['--experimental-sea-config', 'sea-config.json'], {
|
||||
cwd: tmpdir.path
|
||||
});
|
||||
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
copyFileSync(process.execPath, outputFile);
|
||||
injectAndCodeSign(outputFile, seaPrepBlob);
|
||||
|
||||
const singleExecutableApplicationOutput = execFileSync(
|
||||
outputFile,
|
||||
[ '-a', '--b=c', 'd' ],
|
||||
{ env: { COMMON_DIRECTORY: join(__dirname, '..', 'common') } });
|
||||
strictEqual(singleExecutableApplicationOutput.toString(), 'Hello, world! 😊\n');
|
||||
Reference in new issue
Block a user