Skip to content

Commit 8ea8354

Browse files
RaisinTendanielleadams
authored andcommittedApr 11, 2023
src: add initial support for single executable applications
Compile a JavaScript file into a single executable application: ```console $ echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js $ cp $(command -v node) hello $ npx postject hello NODE_JS_CODE hello.js \ --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 $ npx postject hello NODE_JS_CODE hello.js \ --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \ --macho-segment-name NODE_JS $ ./hello world Hello, world! ``` Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #45038 Backport-PR-URL: #47495 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 956f786 commit 8ea8354

13 files changed

+506
-1
lines changed
 

‎configure.py

+10
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@
146146
default=None,
147147
help='use on deprecated SunOS systems that do not support ifaddrs.h')
148148

149+
parser.add_argument('--disable-single-executable-application',
150+
action='store_true',
151+
dest='disable_single_executable_application',
152+
default=None,
153+
help='Disable Single Executable Application support.')
154+
149155
parser.add_argument("--fully-static",
150156
action="store_true",
151157
dest="fully_static",
@@ -1402,6 +1408,10 @@ def configure_node(o):
14021408
if options.no_ifaddrs:
14031409
o['defines'] += ['SUNOS_NO_IFADDRS']
14041410

1411+
o['variables']['single_executable_application'] = b(not options.disable_single_executable_application)
1412+
if options.disable_single_executable_application:
1413+
o['defines'] += ['DISABLE_SINGLE_EXECUTABLE_APPLICATION']
1414+
14051415
# By default, enable ETW on Windows.
14061416
if flavor == 'win':
14071417
o['variables']['node_use_etw'] = b(not options.without_etw)

‎doc/api/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* [Readline](readline.md)
5353
* [REPL](repl.md)
5454
* [Report](report.md)
55+
* [Single executable applications](single-executable-applications.md)
5556
* [Stream](stream.md)
5657
* [String decoder](string_decoder.md)
5758
* [Test runner](test.md)
+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Single executable applications
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
> Stability: 1 - Experimental: This feature is being designed and will change.
6+
7+
<!-- source_link=lib/internal/main/single_executable_application.js -->
8+
9+
This feature allows the distribution of a Node.js application conveniently to a
10+
system that does not have Node.js installed.
11+
12+
Node.js supports the creation of [single executable applications][] by allowing
13+
the injection of a JavaScript file into the `node` binary. During start up, the
14+
program checks if anything has been injected. If the script is found, it
15+
executes its contents. Otherwise Node.js operates as it normally does.
16+
17+
The single executable application feature only supports running a single
18+
embedded [CommonJS][] file.
19+
20+
A bundled JavaScript file can be turned into a single executable application
21+
with any tool which can inject resources into the `node` binary.
22+
23+
Here are the steps for creating a single executable application using one such
24+
tool, [postject][]:
25+
26+
1. Create a JavaScript file:
27+
```console
28+
$ echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js
29+
```
30+
31+
2. Create a copy of the `node` executable and name it according to your needs:
32+
```console
33+
$ cp $(command -v node) hello
34+
```
35+
36+
3. Inject the JavaScript file into the copied binary by running `postject` with
37+
the following options:
38+
39+
* `hello` - The name of the copy of the `node` executable created in step 2.
40+
* `NODE_JS_CODE` - The name of the resource / note / section in the binary
41+
where the contents of the JavaScript file will be stored.
42+
* `hello.js` - The name of the JavaScript file created in step 1.
43+
* `--sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2` - The
44+
[fuse][] used by the Node.js project to detect if a file has been injected.
45+
* `--macho-segment-name NODE_JS` (only needed on macOS) - The name of the
46+
segment in the binary where the contents of the JavaScript file will be
47+
stored.
48+
49+
To summarize, here is the required command for each platform:
50+
51+
* On systems other than macOS:
52+
```console
53+
$ npx postject hello NODE_JS_CODE hello.js \
54+
--sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2
55+
```
56+
57+
* On macOS:
58+
```console
59+
$ npx postject hello NODE_JS_CODE hello.js \
60+
--sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
61+
--macho-segment-name NODE_JS
62+
```
63+
64+
4. Run the binary:
65+
```console
66+
$ ./hello world
67+
Hello, world!
68+
```
69+
70+
## Notes
71+
72+
### `require(id)` in the injected module is not file based
73+
74+
`require()` in the injected module is not the same as the [`require()`][]
75+
available to modules that are not injected. It also does not have any of the
76+
properties that non-injected [`require()`][] has except [`require.main`][]. It
77+
can only be used to load built-in modules. Attempting to load a module that can
78+
only be found in the file system will throw an error.
79+
80+
Instead of relying on a file based `require()`, users can bundle their
81+
application into a standalone JavaScript file to inject into the executable.
82+
This also ensures a more deterministic dependency graph.
83+
84+
However, if a file based `require()` is still needed, that can also be achieved:
85+
86+
```js
87+
const { createRequire } = require('node:module');
88+
require = createRequire(__filename);
89+
```
90+
91+
### `__filename` and `module.filename` in the injected module
92+
93+
The values of `__filename` and `module.filename` in the injected module are
94+
equal to [`process.execPath`][].
95+
96+
### `__dirname` in the injected module
97+
98+
The value of `__dirname` in the injected module is equal to the directory name
99+
of [`process.execPath`][].
100+
101+
### Single executable application creation process
102+
103+
A tool aiming to create a single executable Node.js application must
104+
inject the contents of a JavaScript file into:
105+
106+
* a resource named `NODE_JS_CODE` if the `node` binary is a [PE][] file
107+
* a section named `NODE_JS_CODE` in the `NODE_JS` segment if the `node` binary
108+
is a [Mach-O][] file
109+
* a note named `NODE_JS_CODE` if the `node` binary is an [ELF][] file
110+
111+
Search the binary for the
112+
`NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0` [fuse][] string and flip the
113+
last character to `1` to indicate that a resource has been injected.
114+
115+
### Platform support
116+
117+
Single-executable support is tested regularly on CI only on the following
118+
platforms:
119+
120+
* Windows
121+
* macOS
122+
* Linux (AMD64 only)
123+
124+
This is due to a lack of better tools to generate single-executables that can be
125+
used to test this feature on other platforms.
126+
127+
Suggestions for other resource injection tools/workflows are welcomed. Please
128+
start a discussion at <https://github.com/nodejs/single-executable/discussions>
129+
to help us document them.
130+
131+
[CommonJS]: modules.md#modules-commonjs-modules
132+
[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
133+
[Mach-O]: https://en.wikipedia.org/wiki/Mach-O
134+
[PE]: https://en.wikipedia.org/wiki/Portable_Executable
135+
[`process.execPath`]: process.md#processexecpath
136+
[`require()`]: modules.md#requireid
137+
[`require.main`]: modules.md#accessing-the-main-module
138+
[fuse]: https://www.electronjs.org/docs/latest/tutorial/fuses
139+
[postject]: https://github.com/nodejs/postject
140+
[single executable applications]: https://github.com/nodejs/single-executable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Maintaining Single Executable Applications support
2+
3+
Support for [single executable applications][] is one of the key technical
4+
priorities identified for the success of Node.js.
5+
6+
## High level strategy
7+
8+
From the [Next-10 discussions][] there are 2 approaches the project believes are
9+
important to support:
10+
11+
### Compile with Node.js into executable
12+
13+
This is the approach followed by [boxednode][].
14+
15+
No additional code within the Node.js project is needed to support the
16+
option of compiling a bundled application along with Node.js into a single
17+
executable application.
18+
19+
### Bundle into existing Node.js executable
20+
21+
This is the approach followed by [pkg][].
22+
23+
The project does not plan to provide the complete solution but instead the key
24+
elements which are required in the Node.js executable in order to enable
25+
bundling with the pre-built Node.js binaries. This includes:
26+
27+
* Looking for a segment within the executable that holds bundled code.
28+
* Running the bundled code when such a segment is found.
29+
30+
It is left up to external tools/solutions to:
31+
32+
* Bundle code into a single script.
33+
* Generate a command line with appropriate options.
34+
* Add a segment to an existing Node.js executable which contains
35+
the command line and appropriate headers.
36+
* Re-generate or removing signatures on the resulting executable
37+
* Provide a virtual file system, and hooking it in if needed to
38+
support native modules or reading file contents.
39+
40+
However, the project also maintains a separate tool, [postject][], for injecting
41+
arbitrary read-only resources into the binary such as those needed for bundling
42+
the application into the runtime.
43+
44+
## Planning
45+
46+
Planning for this feature takes place in the [single-executable repository][].
47+
48+
## Upcoming features
49+
50+
Currently, only running a single embedded CommonJS file is supported but support
51+
for the following features are in the list of work we'd like to get to:
52+
53+
* Running an embedded ESM file.
54+
* Running an archive of multiple files.
55+
* Embedding [Node.js CLI options][] into the binary.
56+
* [XCOFF][] executable format.
57+
* Run tests on Linux architectures/distributions other than AMD64 Ubuntu.
58+
59+
## Disabling single executable application support
60+
61+
To disable single executable application support, build Node.js with the
62+
`--disable-single-executable-application` configuration option.
63+
64+
## Implementation
65+
66+
When built with single executable application support, the Node.js process uses
67+
[`postject-api.h`][] to check if the `NODE_JS_CODE` section exists in the
68+
binary. If it is found, it passes the buffer to
69+
[`single_executable_application.js`][], which executes the contents of the
70+
embedded script.
71+
72+
[Next-10 discussions]: https://github.com/nodejs/next-10/blob/main/meetings/summit-nov-2021.md#single-executable-applications
73+
[Node.js CLI options]: https://nodejs.org/api/cli.html
74+
[XCOFF]: https://www.ibm.com/docs/en/aix/7.2?topic=formats-xcoff-object-file-format
75+
[`postject-api.h`]: https://github.com/nodejs/node/blob/71951a0e86da9253d7c422fa2520ee9143e557fa/test/fixtures/postject-copy/node_modules/postject/dist/postject-api.h
76+
[`single_executable_application.js`]: https://github.com/nodejs/node/blob/main/lib/internal/main/single_executable_application.js
77+
[boxednode]: https://github.com/mongodb-js/boxednode
78+
[pkg]: https://github.com/vercel/pkg
79+
[postject]: https://github.com/nodejs/postject
80+
[single executable applications]: https://github.com/nodejs/node/blob/main/doc/contributing/technical-priorities.md#single-executable-applications
81+
[single-executable repository]: https://github.com/nodejs/single-executable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
const {
3+
prepareMainThreadExecution,
4+
markBootstrapComplete,
5+
} = require('internal/process/pre_execution');
6+
const { getSingleExecutableCode } = internalBinding('sea');
7+
const { emitExperimentalWarning } = require('internal/util');
8+
const { Module, wrapSafe } = require('internal/modules/cjs/loader');
9+
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
10+
11+
prepareMainThreadExecution(false, true);
12+
markBootstrapComplete();
13+
14+
emitExperimentalWarning('Single executable application');
15+
16+
// This is roughly the same as:
17+
//
18+
// const mod = new Module(filename);
19+
// mod._compile(contents, filename);
20+
//
21+
// but the code has been duplicated because currently there is no way to set the
22+
// value of require.main to module.
23+
//
24+
// TODO(RaisinTen): Find a way to deduplicate this.
25+
26+
const filename = process.execPath;
27+
const contents = getSingleExecutableCode();
28+
const compiledWrapper = wrapSafe(filename, contents);
29+
30+
const customModule = new Module(filename, null);
31+
customModule.filename = filename;
32+
customModule.paths = Module._nodeModulePaths(customModule.path);
33+
34+
const customExports = customModule.exports;
35+
36+
function customRequire(path) {
37+
if (!Module.isBuiltin(path)) {
38+
throw new ERR_UNKNOWN_BUILTIN_MODULE(path);
39+
}
40+
41+
return require(path);
42+
}
43+
44+
customRequire.main = customModule;
45+
46+
const customFilename = customModule.filename;
47+
48+
const customDirname = customModule.path;
49+
50+
compiledWrapper(
51+
customExports,
52+
customRequire,
53+
customModule,
54+
customFilename,
55+
customDirname);

‎node.gyp

+6-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@
153153

154154
'include_dirs': [
155155
'src',
156-
'deps/v8/include'
156+
'deps/v8/include',
157+
'deps/postject'
157158
],
158159

159160
'sources': [
@@ -458,6 +459,7 @@
458459

459460
'include_dirs': [
460461
'src',
462+
'deps/postject',
461463
'<(SHARED_INTERMEDIATE_DIR)' # for node_natives.h
462464
],
463465
'dependencies': [
@@ -531,6 +533,7 @@
531533
'src/node_report.cc',
532534
'src/node_report_module.cc',
533535
'src/node_report_utils.cc',
536+
'src/node_sea.cc',
534537
'src/node_serdes.cc',
535538
'src/node_shadow_realm.cc',
536539
'src/node_snapshotable.cc',
@@ -641,6 +644,7 @@
641644
'src/node_report.h',
642645
'src/node_revert.h',
643646
'src/node_root_certs.h',
647+
'src/node_sea.h',
644648
'src/node_shadow_realm.h',
645649
'src/node_snapshotable.h',
646650
'src/node_snapshot_builder.h',
@@ -683,6 +687,7 @@
683687
'src/util-inl.h',
684688
# Dependency headers
685689
'deps/v8/include/v8.h',
690+
'deps/postject/postject-api.h'
686691
# javascript files to make for an even more pleasant IDE experience
687692
'<@(library_files)',
688693
'<@(deps_files)',

‎src/node.cc

+18
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "node_realm-inl.h"
4040
#include "node_report.h"
4141
#include "node_revert.h"
42+
#include "node_sea.h"
4243
#include "node_snapshot_builder.h"
4344
#include "node_v8_platform-inl.h"
4445
#include "node_version.h"
@@ -126,6 +127,7 @@
126127
#include <cstring>
127128

128129
#include <string>
130+
#include <tuple>
129131
#include <vector>
130132

131133
namespace node {
@@ -321,6 +323,18 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
321323
first_argv = env->argv()[1];
322324
}
323325

326+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
327+
if (sea::IsSingleExecutable()) {
328+
// TODO(addaleax): Find a way to reuse:
329+
//
330+
// LoadEnvironment(Environment*, const char*)
331+
//
332+
// instead and not add yet another main entry point here because this
333+
// already duplicates existing code.
334+
return StartExecution(env, "internal/main/single_executable_application");
335+
}
336+
#endif
337+
324338
if (first_argv == "inspect") {
325339
return StartExecution(env, "internal/main/inspect");
326340
}
@@ -1187,6 +1201,10 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
11871201
}
11881202

11891203
int Start(int argc, char** argv) {
1204+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
1205+
std::tie(argc, argv) = sea::FixupArgsForSEA(argc, argv);
1206+
#endif
1207+
11901208
CHECK_GT(argc, 0);
11911209

11921210
// Hack around with the argv pointer. Used for process.title = "blah".

‎src/node_binding.cc

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
V(process_wrap) \
6969
V(process_methods) \
7070
V(report) \
71+
V(sea) \
7172
V(serdes) \
7273
V(signal_wrap) \
7374
V(spawn_sync) \

‎src/node_external_reference.h

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class ExternalReferenceRegistry {
8686
V(url) \
8787
V(util) \
8888
V(pipe_wrap) \
89+
V(sea) \
8990
V(serdes) \
9091
V(string_decoder) \
9192
V(stream_wrap) \

‎src/node_options.cc

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "node_binding.h"
66
#include "node_external_reference.h"
77
#include "node_internals.h"
8+
#include "node_sea.h"
89
#if HAVE_OPENSSL
910
#include "openssl/opensslv.h"
1011
#endif
@@ -308,6 +309,10 @@ void Parse(
308309
// TODO(addaleax): Make that unnecessary.
309310

310311
DebugOptionsParser::DebugOptionsParser() {
312+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
313+
if (sea::IsSingleExecutable()) return;
314+
#endif
315+
311316
AddOption("--inspect-port",
312317
"set host:port for inspector",
313318
&DebugOptions::host_port,

‎src/node_sea.cc

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#include "node_sea.h"
2+
3+
#include "env-inl.h"
4+
#include "node_external_reference.h"
5+
#include "node_internals.h"
6+
#include "node_union_bytes.h"
7+
#include "simdutf.h"
8+
#include "v8.h"
9+
10+
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
11+
// the Node.js project that is present only once in the entire binary. It is
12+
// used by the postject_has_resource() function to efficiently detect if a
13+
// resource has been injected. See
14+
// https://github.com/nodejs/postject/blob/35343439cac8c488f2596d7c4c1dddfec1fddcae/postject-api.h#L42-L45.
15+
#define POSTJECT_SENTINEL_FUSE "NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2"
16+
#include "postject-api.h"
17+
#undef POSTJECT_SENTINEL_FUSE
18+
19+
#include <memory>
20+
#include <string_view>
21+
#include <tuple>
22+
#include <vector>
23+
24+
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
25+
26+
using v8::Context;
27+
using v8::FunctionCallbackInfo;
28+
using v8::Local;
29+
using v8::Object;
30+
using v8::Value;
31+
32+
namespace {
33+
34+
const std::string_view FindSingleExecutableCode() {
35+
static const std::string_view sea_code = []() -> std::string_view {
36+
size_t size;
37+
#ifdef __APPLE__
38+
postject_options options;
39+
postject_options_init(&options);
40+
options.macho_segment_name = "NODE_JS";
41+
const char* code = static_cast<const char*>(
42+
postject_find_resource("NODE_JS_CODE", &size, &options));
43+
#else
44+
const char* code = static_cast<const char*>(
45+
postject_find_resource("NODE_JS_CODE", &size, nullptr));
46+
#endif
47+
return {code, size};
48+
}();
49+
return sea_code;
50+
}
51+
52+
void GetSingleExecutableCode(const FunctionCallbackInfo<Value>& args) {
53+
node::Environment* env = node::Environment::GetCurrent(args);
54+
55+
static const std::string_view sea_code = FindSingleExecutableCode();
56+
57+
if (sea_code.empty()) {
58+
return;
59+
}
60+
61+
// TODO(joyeecheung): Use one-byte strings for ASCII-only source to save
62+
// memory/binary size - using UTF16 by default results in twice of the size
63+
// than necessary.
64+
static const node::UnionBytes sea_code_union_bytes =
65+
[]() -> node::UnionBytes {
66+
size_t expected_u16_length =
67+
simdutf::utf16_length_from_utf8(sea_code.data(), sea_code.size());
68+
auto out = std::make_shared<std::vector<uint16_t>>(expected_u16_length);
69+
size_t u16_length = simdutf::convert_utf8_to_utf16(
70+
sea_code.data(),
71+
sea_code.size(),
72+
reinterpret_cast<char16_t*>(out->data()));
73+
out->resize(u16_length);
74+
return node::UnionBytes{out};
75+
}();
76+
77+
args.GetReturnValue().Set(
78+
sea_code_union_bytes.ToStringChecked(env->isolate()));
79+
}
80+
81+
} // namespace
82+
83+
namespace node {
84+
namespace sea {
85+
86+
bool IsSingleExecutable() {
87+
return postject_has_resource();
88+
}
89+
90+
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
91+
// Repeats argv[0] at position 1 on argv as a replacement for the missing
92+
// entry point file path.
93+
if (IsSingleExecutable()) {
94+
char** new_argv = new char*[argc + 2];
95+
int new_argc = 0;
96+
new_argv[new_argc++] = argv[0];
97+
new_argv[new_argc++] = argv[0];
98+
99+
for (int i = 1; i < argc; ++i) {
100+
new_argv[new_argc++] = argv[i];
101+
}
102+
103+
new_argv[new_argc] = nullptr;
104+
105+
argc = new_argc;
106+
argv = new_argv;
107+
}
108+
109+
return {argc, argv};
110+
}
111+
112+
void Initialize(Local<Object> target,
113+
Local<Value> unused,
114+
Local<Context> context,
115+
void* priv) {
116+
SetMethod(
117+
context, target, "getSingleExecutableCode", GetSingleExecutableCode);
118+
}
119+
120+
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
121+
registry->Register(GetSingleExecutableCode);
122+
}
123+
124+
} // namespace sea
125+
} // namespace node
126+
127+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(sea, node::sea::Initialize)
128+
NODE_BINDING_EXTERNAL_REFERENCE(sea, node::sea::RegisterExternalReferences)
129+
130+
#endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)

‎src/node_sea.h

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef SRC_NODE_SEA_H_
2+
#define SRC_NODE_SEA_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
7+
8+
#include <tuple>
9+
10+
namespace node {
11+
namespace sea {
12+
13+
bool IsSingleExecutable();
14+
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv);
15+
16+
} // namespace sea
17+
} // namespace node
18+
19+
#endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
20+
21+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
22+
23+
#endif // SRC_NODE_SEA_H_

‎test/fixtures/sea.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { Module: { createRequire } } = require('module');
2+
const createdRequire = createRequire(__filename);
3+
4+
// Although, require('../common') works locally, that couldn't be used here
5+
// because we set NODE_TEST_DIR=/Users/iojs/node-tmp on Jenkins CI.
6+
const { expectWarning } = createdRequire(process.env.COMMON_DIRECTORY);
7+
8+
expectWarning('ExperimentalWarning',
9+
'Single executable application is an experimental feature and ' +
10+
'might change at any time');
11+
12+
const { deepStrictEqual, strictEqual, throws } = require('assert');
13+
const { dirname } = require('path');
14+
15+
deepStrictEqual(process.argv, [process.execPath, process.execPath, '-a', '--b=c', 'd']);
16+
17+
strictEqual(require.cache, undefined);
18+
strictEqual(require.extensions, undefined);
19+
strictEqual(require.main, module);
20+
strictEqual(require.resolve, undefined);
21+
22+
strictEqual(__filename, process.execPath);
23+
strictEqual(__dirname, dirname(process.execPath));
24+
strictEqual(module.exports, exports);
25+
26+
throws(() => require('./requirable.js'), {
27+
code: 'ERR_UNKNOWN_BUILTIN_MODULE',
28+
});
29+
30+
const requirable = createdRequire('./requirable.js');
31+
deepStrictEqual(requirable, {
32+
hello: 'world',
33+
});
34+
35+
console.log('Hello, world! 😊');

0 commit comments

Comments
 (0)
Please sign in to comment.