@scelar/nodepod 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__sw__.js +642 -642
- package/dist/__tests__/bench/integration.bench.d.ts +1 -0
- package/dist/__tests__/bench/memory-volume.bench.d.ts +1 -0
- package/dist/__tests__/bench/polyfills.bench.d.ts +1 -0
- package/dist/__tests__/bench/script-engine.bench.d.ts +1 -0
- package/dist/__tests__/bench/shell.bench.d.ts +1 -0
- package/dist/__tests__/bench/syntax-transforms.bench.d.ts +1 -0
- package/dist/__tests__/bench/version-resolver.bench.d.ts +1 -0
- package/dist/__tests__/buffer.test.d.ts +1 -0
- package/dist/__tests__/byte-encoding.test.d.ts +1 -0
- package/dist/__tests__/digest.test.d.ts +1 -0
- package/dist/__tests__/events.test.d.ts +1 -0
- package/dist/__tests__/memory-volume.test.d.ts +1 -0
- package/dist/__tests__/path.test.d.ts +1 -0
- package/dist/__tests__/process.test.d.ts +1 -0
- package/dist/__tests__/script-engine.test.d.ts +1 -0
- package/dist/__tests__/shell-builtins.test.d.ts +1 -0
- package/dist/__tests__/shell-interpreter.test.d.ts +1 -0
- package/dist/__tests__/shell-parser.test.d.ts +1 -0
- package/dist/__tests__/stream.test.d.ts +1 -0
- package/dist/__tests__/syntax-transforms.test.d.ts +1 -0
- package/dist/__tests__/version-resolver.test.d.ts +1 -0
- package/dist/{child_process-Dopvyd-E.js → child_process-53fMkug_.js} +4 -4
- package/dist/child_process-53fMkug_.js.map +1 -0
- package/dist/{child_process-B38qoN6R.cjs → child_process-lxSKECHq.cjs} +5 -5
- package/dist/child_process-lxSKECHq.cjs.map +1 -0
- package/dist/{index--Qr8LVpQ.js → index-B8lyh_ti.js} +1316 -559
- package/dist/index-B8lyh_ti.js.map +1 -0
- package/dist/{index-cnitc68U.cjs → index-C-TQIrdG.cjs} +1422 -612
- package/dist/index-C-TQIrdG.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/memory-volume.d.ts +1 -1
- package/dist/polyfills/wasi.d.ts +45 -4
- package/dist/script-engine.d.ts +2 -0
- package/dist/sdk/nodepod.d.ts +4 -3
- package/dist/sdk/types.d.ts +6 -0
- package/dist/syntax-transforms.d.ts +1 -0
- package/dist/threading/process-manager.d.ts +1 -1
- package/dist/threading/worker-protocol.d.ts +1 -1
- package/package.json +5 -3
- package/src/__tests__/bench/integration.bench.ts +117 -0
- package/src/__tests__/bench/memory-volume.bench.ts +115 -0
- package/src/__tests__/bench/polyfills.bench.ts +147 -0
- package/src/__tests__/bench/script-engine.bench.ts +104 -0
- package/src/__tests__/bench/shell.bench.ts +101 -0
- package/src/__tests__/bench/syntax-transforms.bench.ts +82 -0
- package/src/__tests__/bench/version-resolver.bench.ts +95 -0
- package/src/__tests__/buffer.test.ts +273 -0
- package/src/__tests__/byte-encoding.test.ts +98 -0
- package/src/__tests__/digest.test.ts +44 -0
- package/src/__tests__/events.test.ts +245 -0
- package/src/__tests__/memory-volume.test.ts +443 -0
- package/src/__tests__/path.test.ts +181 -0
- package/src/__tests__/process.test.ts +129 -0
- package/src/__tests__/script-engine.test.ts +229 -0
- package/src/__tests__/shell-builtins.test.ts +357 -0
- package/src/__tests__/shell-interpreter.test.ts +157 -0
- package/src/__tests__/shell-parser.test.ts +204 -0
- package/src/__tests__/stream.test.ts +142 -0
- package/src/__tests__/syntax-transforms.test.ts +158 -0
- package/src/__tests__/version-resolver.test.ts +184 -0
- package/src/constants/cdn-urls.ts +18 -18
- package/src/helpers/byte-encoding.ts +51 -39
- package/src/index.ts +192 -192
- package/src/memory-volume.ts +968 -941
- package/src/module-transformer.ts +368 -368
- package/src/packages/installer.ts +396 -396
- package/src/packages/version-resolver.ts +12 -2
- package/src/polyfills/buffer.ts +633 -628
- package/src/polyfills/child_process.ts +2288 -2288
- package/src/polyfills/esbuild.ts +854 -854
- package/src/polyfills/events.ts +282 -276
- package/src/polyfills/fs.ts +2888 -2888
- package/src/polyfills/http.ts +1450 -1449
- package/src/polyfills/process.ts +721 -690
- package/src/polyfills/readline.ts +692 -692
- package/src/polyfills/stream.ts +1620 -1620
- package/src/polyfills/tty.ts +71 -71
- package/src/polyfills/wasi.ts +1284 -22
- package/src/request-proxy.ts +716 -716
- package/src/script-engine.ts +465 -146
- package/src/sdk/nodepod.ts +525 -509
- package/src/sdk/types.ts +7 -0
- package/src/syntax-transforms.ts +543 -561
- package/src/threading/offload-worker.ts +383 -383
- package/src/threading/offload.ts +271 -271
- package/src/threading/process-manager.ts +956 -956
- package/src/threading/process-worker-entry.ts +858 -854
- package/src/threading/worker-protocol.ts +1 -1
- package/dist/child_process-B38qoN6R.cjs.map +0 -1
- package/dist/child_process-Dopvyd-E.js.map +0 -1
- package/dist/index--Qr8LVpQ.js.map +0 -1
- package/dist/index-cnitc68U.cjs.map +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import * as path from "../polyfills/path";
|
|
3
|
+
|
|
4
|
+
describe("path.normalize", () => {
|
|
5
|
+
it("resolves . and ..", () => {
|
|
6
|
+
expect(path.normalize("/foo/bar/../baz")).toBe("/foo/baz");
|
|
7
|
+
expect(path.normalize("/foo/./bar")).toBe("/foo/bar");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("removes duplicate slashes", () => {
|
|
11
|
+
expect(path.normalize("/foo//bar///baz")).toBe("/foo/bar/baz");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns "." for empty input', () => {
|
|
15
|
+
expect(path.normalize("")).toBe(".");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("handles relative paths with ..", () => {
|
|
19
|
+
expect(path.normalize("a/b/../../c")).toBe("c");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("does not go above root", () => {
|
|
23
|
+
expect(path.normalize("/../../foo")).toBe("/foo");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("path.join", () => {
|
|
28
|
+
it("joins segments with /", () => {
|
|
29
|
+
expect(path.join("/foo", "bar", "baz")).toBe("/foo/bar/baz");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("normalizes result", () => {
|
|
33
|
+
expect(path.join("/foo", "../bar")).toBe("/bar");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns "." for no args', () => {
|
|
37
|
+
expect(path.join()).toBe(".");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles empty strings", () => {
|
|
41
|
+
expect(path.join("", "foo", "")).toBe("foo");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("path.resolve", () => {
|
|
46
|
+
it("resolves relative to absolute base", () => {
|
|
47
|
+
expect(path.resolve("/foo", "bar")).toBe("/foo/bar");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("right-to-left resolution stops at absolute path", () => {
|
|
51
|
+
expect(path.resolve("a", "/b", "c")).toBe("/b/c");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("normalizes the result", () => {
|
|
55
|
+
expect(path.resolve("/foo", "bar", "..", "baz")).toBe("/foo/baz");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("path.dirname", () => {
|
|
60
|
+
it("returns parent directory", () => {
|
|
61
|
+
expect(path.dirname("/foo/bar.txt")).toBe("/foo");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns "/" for root-level file', () => {
|
|
65
|
+
expect(path.dirname("/file.txt")).toBe("/");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns "." for bare filename', () => {
|
|
69
|
+
expect(path.dirname("file.txt")).toBe(".");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("path.basename", () => {
|
|
74
|
+
it("returns filename from path", () => {
|
|
75
|
+
expect(path.basename("/foo/bar.txt")).toBe("bar.txt");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("strips suffix when provided", () => {
|
|
79
|
+
expect(path.basename("/foo/bar.txt", ".txt")).toBe("bar");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns empty for empty string", () => {
|
|
83
|
+
expect(path.basename("")).toBe("");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns last segment for directory path", () => {
|
|
87
|
+
expect(path.basename("/foo/bar/")).toBe("bar");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("path.extname", () => {
|
|
92
|
+
it("returns extension with dot", () => {
|
|
93
|
+
expect(path.extname("file.txt")).toBe(".txt");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns empty for no extension", () => {
|
|
97
|
+
expect(path.extname("Makefile")).toBe("");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns last extension for multiple dots", () => {
|
|
101
|
+
expect(path.extname("file.test.js")).toBe(".js");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns empty for leading dot only", () => {
|
|
105
|
+
expect(path.extname(".gitignore")).toBe("");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("path.relative", () => {
|
|
110
|
+
it("computes relative path between two absolute paths", () => {
|
|
111
|
+
expect(path.relative("/a/b", "/a/c")).toBe("../c");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns empty string for same path", () => {
|
|
115
|
+
expect(path.relative("/a/b", "/a/b")).toBe("");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("handles deeply nested paths", () => {
|
|
119
|
+
expect(path.relative("/a/b/c", "/a/b/c/d/e")).toBe("d/e");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("path.isAbsolute", () => {
|
|
124
|
+
it("returns true for paths starting with /", () => {
|
|
125
|
+
expect(path.isAbsolute("/foo")).toBe(true);
|
|
126
|
+
expect(path.isAbsolute("/")).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns false for relative paths", () => {
|
|
130
|
+
expect(path.isAbsolute("foo")).toBe(false);
|
|
131
|
+
expect(path.isAbsolute("./foo")).toBe(false);
|
|
132
|
+
expect(path.isAbsolute("../foo")).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("path.parse", () => {
|
|
137
|
+
it("decomposes path into parts", () => {
|
|
138
|
+
const result = path.parse("/home/user/file.txt");
|
|
139
|
+
expect(result.root).toBe("/");
|
|
140
|
+
expect(result.dir).toBe("/home/user");
|
|
141
|
+
expect(result.base).toBe("file.txt");
|
|
142
|
+
expect(result.ext).toBe(".txt");
|
|
143
|
+
expect(result.name).toBe("file");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("handles root path", () => {
|
|
147
|
+
const result = path.parse("/");
|
|
148
|
+
expect(result.root).toBe("/");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("handles relative path", () => {
|
|
152
|
+
const result = path.parse("foo/bar.js");
|
|
153
|
+
expect(result.root).toBe("");
|
|
154
|
+
expect(result.dir).toBe("foo");
|
|
155
|
+
expect(result.base).toBe("bar.js");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("path.format", () => {
|
|
160
|
+
it("reconstructs path from components", () => {
|
|
161
|
+
expect(path.format({ dir: "/home/user", base: "file.txt" })).toBe(
|
|
162
|
+
"/home/user/file.txt",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("uses name + ext when base is missing", () => {
|
|
167
|
+
expect(path.format({ dir: "/home", name: "file", ext: ".txt" })).toBe(
|
|
168
|
+
"/home/file.txt",
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("path constants", () => {
|
|
174
|
+
it('sep is "/"', () => {
|
|
175
|
+
expect(path.sep).toBe("/");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('delimiter is ":"', () => {
|
|
179
|
+
expect(path.delimiter).toBe(":");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { buildProcessEnv } from "../polyfills/process";
|
|
3
|
+
|
|
4
|
+
describe("process polyfill", () => {
|
|
5
|
+
describe("basic properties", () => {
|
|
6
|
+
it('has platform "linux"', () => {
|
|
7
|
+
const proc = buildProcessEnv();
|
|
8
|
+
expect(proc.platform).toBe("linux");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('has arch "x64"', () => {
|
|
12
|
+
const proc = buildProcessEnv();
|
|
13
|
+
expect(proc.arch).toBe("x64");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has version string starting with "v"', () => {
|
|
17
|
+
const proc = buildProcessEnv();
|
|
18
|
+
expect(proc.version).toMatch(/^v\d+/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("has pid as a number", () => {
|
|
22
|
+
const proc = buildProcessEnv();
|
|
23
|
+
expect(typeof proc.pid).toBe("number");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("has argv as an array", () => {
|
|
27
|
+
const proc = buildProcessEnv();
|
|
28
|
+
expect(Array.isArray(proc.argv)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("cwd / chdir", () => {
|
|
33
|
+
it("cwd() returns initial cwd", () => {
|
|
34
|
+
const proc = buildProcessEnv({ cwd: "/mydir" });
|
|
35
|
+
expect(proc.cwd()).toBe("/mydir");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("chdir changes cwd", () => {
|
|
39
|
+
const proc = buildProcessEnv({ cwd: "/" });
|
|
40
|
+
proc.chdir("/other");
|
|
41
|
+
expect(proc.cwd()).toBe("/other");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("env", () => {
|
|
46
|
+
it("includes default env vars", () => {
|
|
47
|
+
const proc = buildProcessEnv();
|
|
48
|
+
expect(proc.env.PATH).toBeDefined();
|
|
49
|
+
expect(proc.env.HOME).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("includes custom env vars passed in config", () => {
|
|
53
|
+
const proc = buildProcessEnv({ env: { MY_VAR: "hello" } });
|
|
54
|
+
expect(proc.env.MY_VAR).toBe("hello");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("includes NAPI_RS_FORCE_WASM=1", () => {
|
|
58
|
+
const proc = buildProcessEnv();
|
|
59
|
+
expect(proc.env.NAPI_RS_FORCE_WASM).toBe("1");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("is mutable", () => {
|
|
63
|
+
const proc = buildProcessEnv();
|
|
64
|
+
proc.env.CUSTOM = "value";
|
|
65
|
+
expect(proc.env.CUSTOM).toBe("value");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("hrtime", () => {
|
|
70
|
+
it("returns [seconds, nanoseconds] tuple", () => {
|
|
71
|
+
const proc = buildProcessEnv();
|
|
72
|
+
const hr = proc.hrtime();
|
|
73
|
+
expect(Array.isArray(hr)).toBe(true);
|
|
74
|
+
expect(hr.length).toBe(2);
|
|
75
|
+
expect(typeof hr[0]).toBe("number");
|
|
76
|
+
expect(typeof hr[1]).toBe("number");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("hrtime.bigint() returns bigint", () => {
|
|
80
|
+
const proc = buildProcessEnv();
|
|
81
|
+
const result = proc.hrtime.bigint();
|
|
82
|
+
expect(typeof result).toBe("bigint");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("nextTick", () => {
|
|
87
|
+
it("schedules callback asynchronously", async () => {
|
|
88
|
+
const proc = buildProcessEnv();
|
|
89
|
+
let called = false;
|
|
90
|
+
proc.nextTick(() => {
|
|
91
|
+
called = true;
|
|
92
|
+
});
|
|
93
|
+
expect(called).toBe(false);
|
|
94
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
95
|
+
expect(called).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("memoryUsage", () => {
|
|
100
|
+
it("returns object with expected shape", () => {
|
|
101
|
+
const proc = buildProcessEnv();
|
|
102
|
+
const mem = proc.memoryUsage();
|
|
103
|
+
expect(typeof mem.rss).toBe("number");
|
|
104
|
+
expect(typeof mem.heapTotal).toBe("number");
|
|
105
|
+
expect(typeof mem.heapUsed).toBe("number");
|
|
106
|
+
expect(typeof mem.external).toBe("number");
|
|
107
|
+
expect(typeof mem.arrayBuffers).toBe("number");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("stdout.write", () => {
|
|
112
|
+
it("calls onStdout callback when provided", () => {
|
|
113
|
+
const output: string[] = [];
|
|
114
|
+
const proc = buildProcessEnv({ onStdout: (text) => output.push(text) });
|
|
115
|
+
proc.stdout.write("test");
|
|
116
|
+
expect(output).toContain("test");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("kill / signals", () => {
|
|
121
|
+
it("kill emits signal on process", () => {
|
|
122
|
+
const proc = buildProcessEnv();
|
|
123
|
+
const fn = vi.fn();
|
|
124
|
+
proc.on("SIGTERM", fn);
|
|
125
|
+
proc.kill(proc.pid, "SIGTERM");
|
|
126
|
+
expect(fn).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ScriptEngine } from "../script-engine";
|
|
3
|
+
import { MemoryVolume } from "../memory-volume";
|
|
4
|
+
|
|
5
|
+
function createEngine(files?: Record<string, string>) {
|
|
6
|
+
const vol = new MemoryVolume();
|
|
7
|
+
vol.mkdirSync("/project", { recursive: true });
|
|
8
|
+
if (files) {
|
|
9
|
+
for (const [path, content] of Object.entries(files)) {
|
|
10
|
+
const dir = path.substring(0, path.lastIndexOf("/")) || "/";
|
|
11
|
+
if (dir !== "/") vol.mkdirSync(dir, { recursive: true });
|
|
12
|
+
vol.writeFileSync(path, content);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return { vol, engine: new ScriptEngine(vol, { cwd: "/project" }) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("ScriptEngine", () => {
|
|
19
|
+
describe("execute()", () => {
|
|
20
|
+
it("runs basic JS and returns exports", () => {
|
|
21
|
+
const { engine } = createEngine();
|
|
22
|
+
const result = engine.execute("module.exports = 42;", "/index.js");
|
|
23
|
+
expect(result.exports).toBe(42);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("module.exports = object", () => {
|
|
27
|
+
const { engine } = createEngine();
|
|
28
|
+
const result = engine.execute(
|
|
29
|
+
"module.exports = { x: 1 };",
|
|
30
|
+
"/index.js",
|
|
31
|
+
);
|
|
32
|
+
expect(result.exports).toEqual({ x: 1 });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("exports.x = value shorthand", () => {
|
|
36
|
+
const { engine } = createEngine();
|
|
37
|
+
const result = engine.execute("exports.x = 1;", "/index.js");
|
|
38
|
+
expect((result.exports as any).x).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("has access to __dirname and __filename", () => {
|
|
42
|
+
const { engine } = createEngine();
|
|
43
|
+
const result = engine.execute(
|
|
44
|
+
"module.exports = { dir: __dirname, file: __filename };",
|
|
45
|
+
"/project/test.js",
|
|
46
|
+
);
|
|
47
|
+
expect((result.exports as any).dir).toBe("/project");
|
|
48
|
+
expect((result.exports as any).file).toBe("/project/test.js");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("has access to process object", () => {
|
|
52
|
+
const { engine } = createEngine();
|
|
53
|
+
const result = engine.execute(
|
|
54
|
+
"module.exports = process.platform;",
|
|
55
|
+
"/index.js",
|
|
56
|
+
);
|
|
57
|
+
expect(result.exports).toBe("linux");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("handles syntax errors by throwing", () => {
|
|
61
|
+
const { engine } = createEngine();
|
|
62
|
+
expect(() => engine.execute("const {", "/bad.js")).toThrow();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("runFile()", () => {
|
|
67
|
+
it("reads file from volume and executes", () => {
|
|
68
|
+
const { engine } = createEngine({
|
|
69
|
+
"/project/app.js": 'module.exports = "hello";',
|
|
70
|
+
});
|
|
71
|
+
const result = engine.runFile("/project/app.js");
|
|
72
|
+
expect(result.exports).toBe("hello");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("throws for nonexistent file", () => {
|
|
76
|
+
const { engine } = createEngine();
|
|
77
|
+
expect(() => engine.runFile("/nonexistent.js")).toThrow();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("require()", () => {
|
|
82
|
+
it("requires a local file with relative path", () => {
|
|
83
|
+
const { engine } = createEngine({
|
|
84
|
+
"/project/lib.js": "module.exports = 10;",
|
|
85
|
+
});
|
|
86
|
+
const result = engine.execute(
|
|
87
|
+
'const lib = require("./lib"); module.exports = lib;',
|
|
88
|
+
"/project/index.js",
|
|
89
|
+
);
|
|
90
|
+
expect(result.exports).toBe(10);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("requires chained files (A requires B requires C)", () => {
|
|
94
|
+
const { engine } = createEngine({
|
|
95
|
+
"/project/c.js": "module.exports = 3;",
|
|
96
|
+
"/project/b.js":
|
|
97
|
+
'module.exports = require("./c") * 2;',
|
|
98
|
+
"/project/a.js":
|
|
99
|
+
'module.exports = require("./b") + 1;',
|
|
100
|
+
});
|
|
101
|
+
const result = engine.runFile("/project/a.js");
|
|
102
|
+
expect(result.exports).toBe(7);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("caches modules (same object returned on second require)", () => {
|
|
106
|
+
const { engine } = createEngine({
|
|
107
|
+
"/project/mod.js": "module.exports = { count: 0 };",
|
|
108
|
+
});
|
|
109
|
+
const result = engine.execute(
|
|
110
|
+
'const a = require("./mod"); const b = require("./mod"); a.count++; module.exports = b.count;',
|
|
111
|
+
"/project/test.js",
|
|
112
|
+
);
|
|
113
|
+
expect(result.exports).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("requires built-in modules: path", () => {
|
|
117
|
+
const { engine } = createEngine();
|
|
118
|
+
const result = engine.execute(
|
|
119
|
+
'const p = require("path"); module.exports = p.join("/a", "b");',
|
|
120
|
+
"/index.js",
|
|
121
|
+
);
|
|
122
|
+
expect(result.exports).toBe("/a/b");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("requires built-in modules: events", () => {
|
|
126
|
+
const { engine } = createEngine();
|
|
127
|
+
const result = engine.execute(
|
|
128
|
+
'const EE = require("events"); module.exports = typeof EE;',
|
|
129
|
+
"/index.js",
|
|
130
|
+
);
|
|
131
|
+
expect(result.exports).toBe("function");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("requires JSON files", () => {
|
|
135
|
+
const { engine } = createEngine({
|
|
136
|
+
"/project/data.json": '{"key": "value"}',
|
|
137
|
+
});
|
|
138
|
+
const result = engine.execute(
|
|
139
|
+
'module.exports = require("./data.json");',
|
|
140
|
+
"/project/index.js",
|
|
141
|
+
);
|
|
142
|
+
expect(result.exports).toEqual({ key: "value" });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("throws for missing module", () => {
|
|
146
|
+
const { engine } = createEngine();
|
|
147
|
+
expect(() =>
|
|
148
|
+
engine.execute('require("./nonexistent");', "/index.js"),
|
|
149
|
+
).toThrow();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("ESM auto-conversion", () => {
|
|
154
|
+
it("auto-converts import/export to CJS when required", () => {
|
|
155
|
+
const { engine } = createEngine({
|
|
156
|
+
"/project/mod.js": "export const x = 42;",
|
|
157
|
+
});
|
|
158
|
+
const result = engine.execute(
|
|
159
|
+
'const m = require("./mod"); module.exports = m.x;',
|
|
160
|
+
"/project/index.js",
|
|
161
|
+
);
|
|
162
|
+
expect(result.exports).toBe(42);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("handles export function containing dynamic import() without corruption", () => {
|
|
166
|
+
const { engine } = createEngine({
|
|
167
|
+
"/project/plugin.js": [
|
|
168
|
+
'import path from "path";',
|
|
169
|
+
"export function helper() { return 1; }",
|
|
170
|
+
"export function main() {",
|
|
171
|
+
" const loader = () => import('./other.js');",
|
|
172
|
+
" return { loader, val: path.join('a', 'b') };",
|
|
173
|
+
"}",
|
|
174
|
+
].join("\n"),
|
|
175
|
+
});
|
|
176
|
+
const result = engine.execute(
|
|
177
|
+
'const m = require("./plugin"); module.exports = m.main().val;',
|
|
178
|
+
"/project/index.js",
|
|
179
|
+
);
|
|
180
|
+
expect(result.exports).toBe("a/b");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("clearCache()", () => {
|
|
185
|
+
it("clears module cache so modules are re-evaluated", () => {
|
|
186
|
+
const { engine } = createEngine({
|
|
187
|
+
"/project/counter.js":
|
|
188
|
+
"let c = 0; module.exports = { inc() { return ++c; } };",
|
|
189
|
+
});
|
|
190
|
+
engine.execute(
|
|
191
|
+
'module.exports = require("./counter").inc();',
|
|
192
|
+
"/project/a.js",
|
|
193
|
+
);
|
|
194
|
+
engine.clearCache();
|
|
195
|
+
const result = engine.execute(
|
|
196
|
+
'module.exports = require("./counter").inc();',
|
|
197
|
+
"/project/b.js",
|
|
198
|
+
);
|
|
199
|
+
// c resets to 0 after cache clear
|
|
200
|
+
expect(result.exports).toBe(1);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("createREPL()", () => {
|
|
205
|
+
it("evaluates expressions", () => {
|
|
206
|
+
const { engine } = createEngine();
|
|
207
|
+
const repl = engine.createREPL();
|
|
208
|
+
expect(repl.eval("1 + 1")).toBe(2);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("evaluates variable declarations across calls", () => {
|
|
212
|
+
const { engine } = createEngine();
|
|
213
|
+
const repl = engine.createREPL();
|
|
214
|
+
repl.eval("var x = 10");
|
|
215
|
+
expect(repl.eval("x")).toBe(10);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("shebang handling", () => {
|
|
220
|
+
it("strips shebang line", () => {
|
|
221
|
+
const { engine } = createEngine({
|
|
222
|
+
"/project/script.js":
|
|
223
|
+
"#!/usr/bin/env node\nmodule.exports = 42;",
|
|
224
|
+
});
|
|
225
|
+
const result = engine.runFile("/project/script.js");
|
|
226
|
+
expect(result.exports).toBe(42);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|