@bash0816/claude-code 2.1.241 → 2.1.245
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/README.md +1 -1
- package/bin/claude +10 -2
- package/config/claude-native-audited-versions.json +8 -0
- package/config/claude-termux-release-manifest.json +1 -1
- package/lib/bunfs-child-process-guard.mjs +18 -0
- package/lib/bunfs-child-process-guard.test.js +113 -0
- package/lib/bunfs-esm-loader.mjs +74 -0
- package/lib/bunfs-esm-loader.test.js +329 -0
- package/lib/bunfs-extract.js +205 -0
- package/lib/bunfs-extract.test.js +265 -0
- package/lib/bunfs-vm-guard.mjs +116 -0
- package/lib/bunfs-vm-guard.test.js +142 -0
- package/lib/bunfs-ws-stub.mjs +11 -0
- package/lib/bunfs-yaml-shim.mjs +91 -0
- package/lib/prepare-native.js +30 -0
- package/lib/termux-run-claude-native.sh +132 -4
- package/lib/termux-run-claude-native.test.js +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
package/bin/claude
CHANGED
|
@@ -42,11 +42,18 @@ process.stdout.write(String(item[field]));
|
|
|
42
42
|
' "${CONFIG_FILE}" "${CLAUDE_VERSION}" "$1"
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
ENTRY_END_OFFSET=$(json_field entry_end_offset)
|
|
45
|
+
ENTRY_FORMAT=$(json_field entry_format || echo "legacy-cjs")
|
|
47
46
|
CACHE_DIR="${CLAUDE_TERMUX_PACKAGE_CACHE:-${HOME}/.claude-termux-native-package}/versions/${CLAUDE_VERSION}"
|
|
48
47
|
SOURCE_BIN="${CACHE_DIR}/app/node_modules/@anthropic-ai/claude-code-linux-arm64/claude"
|
|
49
48
|
|
|
49
|
+
if [ "${ENTRY_FORMAT}" = "esm-chunked" ]; then
|
|
50
|
+
ENTRY_JS_OFFSET=""
|
|
51
|
+
ENTRY_END_OFFSET=""
|
|
52
|
+
else
|
|
53
|
+
ENTRY_JS_OFFSET=$(json_field entry_js_offset)
|
|
54
|
+
ENTRY_END_OFFSET=$(json_field entry_end_offset)
|
|
55
|
+
fi
|
|
56
|
+
|
|
50
57
|
if [ ! -f "${SOURCE_BIN}" ]; then
|
|
51
58
|
"$NODE" "${PACKAGE_DIR}/lib/prepare-native.js" "${CLAUDE_VERSION}"
|
|
52
59
|
fi
|
|
@@ -58,6 +65,7 @@ fi
|
|
|
58
65
|
|
|
59
66
|
SOURCE_BIN="${SOURCE_BIN}" \
|
|
60
67
|
WORKDIR="${CACHE_DIR}/launcher-workdir" \
|
|
68
|
+
ENTRY_FORMAT="${ENTRY_FORMAT}" \
|
|
61
69
|
ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET}" \
|
|
62
70
|
ENTRY_END_OFFSET="${ENTRY_END_OFFSET}" \
|
|
63
71
|
CURRENT_CLAUDE_VERSION="${CLAUDE_VERSION}" \
|
|
@@ -770,6 +770,14 @@
|
|
|
770
770
|
"tarball_integrity": "sha512-tHsJhjwcoFyKTCQfYCclKjLtbnbbGKvcrzxUhMb6NDlPw9RrawmjSWUAdla7PtEVPaJsGkjGo4O2MQTlYIT+PA==",
|
|
771
771
|
"tarball_sha256": "41de896e672667bc5b5060419d5a1ff5cfe15b77659d1418b10df5095e38419a",
|
|
772
772
|
"status": "termux_verified"
|
|
773
|
+
},
|
|
774
|
+
"2.1.245": {
|
|
775
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.245",
|
|
776
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.245",
|
|
777
|
+
"entry_format": "esm-chunked",
|
|
778
|
+
"tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==",
|
|
779
|
+
"tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d",
|
|
780
|
+
"status": "offset_discovered"
|
|
773
781
|
}
|
|
774
782
|
}
|
|
775
783
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"package_name": "@bash0816/claude-code",
|
|
4
4
|
"latest_audited_version": "2.1.241",
|
|
5
|
-
"latest_candidate_version": "2.1.
|
|
5
|
+
"latest_candidate_version": "2.1.245",
|
|
6
6
|
"previous_stable_version": "2.1.240",
|
|
7
7
|
"stable_pinned_version": "2.1.220-2",
|
|
8
8
|
"manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const realChildProcess = require('node:child_process');
|
|
5
|
+
const { createGuardedChildProcess } = require('./native-update-guard.js');
|
|
6
|
+
const guarded = createGuardedChildProcess(realChildProcess, (v) => process.stderr.write(v));
|
|
7
|
+
|
|
8
|
+
export const spawn = guarded.spawn;
|
|
9
|
+
export const execFile = guarded.execFile;
|
|
10
|
+
export const exec = guarded.exec;
|
|
11
|
+
export const spawnSync = guarded.spawnSync;
|
|
12
|
+
export const execFileSync = guarded.execFileSync;
|
|
13
|
+
export const execSync = guarded.execSync;
|
|
14
|
+
export const ChildProcess = realChildProcess.ChildProcess;
|
|
15
|
+
export const fork = realChildProcess.fork;
|
|
16
|
+
export const _forkChild = realChildProcess._forkChild;
|
|
17
|
+
|
|
18
|
+
export default Object.assign({}, realChildProcess, guarded);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
test('bunfs-child-process-guard exports named exports for guarded methods', async () => {
|
|
7
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
8
|
+
|
|
9
|
+
assert.equal(typeof guard.spawn, 'function');
|
|
10
|
+
assert.equal(typeof guard.execFile, 'function');
|
|
11
|
+
assert.equal(typeof guard.exec, 'function');
|
|
12
|
+
assert.equal(typeof guard.spawnSync, 'function');
|
|
13
|
+
assert.equal(typeof guard.execFileSync, 'function');
|
|
14
|
+
assert.equal(typeof guard.execSync, 'function');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('bunfs-child-process-guard exports ChildProcess, fork, _forkChild', async () => {
|
|
18
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
19
|
+
|
|
20
|
+
// ChildProcess is a class
|
|
21
|
+
assert.equal(typeof guard.ChildProcess, 'function');
|
|
22
|
+
// fork and _forkChild are functions
|
|
23
|
+
assert.equal(typeof guard.fork, 'function');
|
|
24
|
+
assert.equal(typeof guard._forkChild, 'function');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('bunfs-child-process-guard provides default export with all methods', async () => {
|
|
28
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
29
|
+
const defaultExport = guard.default;
|
|
30
|
+
|
|
31
|
+
assert.ok(defaultExport);
|
|
32
|
+
assert.equal(typeof defaultExport, 'object');
|
|
33
|
+
assert.equal(typeof defaultExport.spawn, 'function');
|
|
34
|
+
assert.equal(typeof defaultExport.execFile, 'function');
|
|
35
|
+
assert.equal(typeof defaultExport.exec, 'function');
|
|
36
|
+
assert.equal(typeof defaultExport.spawnSync, 'function');
|
|
37
|
+
assert.equal(typeof defaultExport.execFileSync, 'function');
|
|
38
|
+
assert.equal(typeof defaultExport.execSync, 'function');
|
|
39
|
+
assert.equal(typeof defaultExport.ChildProcess, 'function');
|
|
40
|
+
assert.equal(typeof defaultExport.fork, 'function');
|
|
41
|
+
assert.equal(typeof defaultExport._forkChild, 'function');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('bunfs-child-process-guard blocks official package update via execFileSync', async () => {
|
|
45
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
46
|
+
|
|
47
|
+
// Try to execute npm install of official package
|
|
48
|
+
try {
|
|
49
|
+
guard.execFileSync('npm', ['install', '-g', '@anthropic-ai/claude-code@latest']);
|
|
50
|
+
// If it doesn't throw, that's an error (should be blocked)
|
|
51
|
+
assert.fail('Expected execFileSync to block official package update');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// Should throw with CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED code
|
|
54
|
+
assert.ok(
|
|
55
|
+
err.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED' ||
|
|
56
|
+
err.message.includes('disabled on Termux'),
|
|
57
|
+
`Expected block error, got: ${err.message}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('bunfs-child-process-guard blocks official package update via execSync', async () => {
|
|
63
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
64
|
+
|
|
65
|
+
// Try to execute npm install via exec
|
|
66
|
+
try {
|
|
67
|
+
guard.execSync('npm install -g @anthropic-ai/claude-code@latest');
|
|
68
|
+
// If it doesn't throw, that's an error (should be blocked)
|
|
69
|
+
assert.fail('Expected execSync to block official package update');
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// Should throw with CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED code
|
|
72
|
+
assert.ok(
|
|
73
|
+
err.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED' ||
|
|
74
|
+
err.message.includes('disabled on Termux'),
|
|
75
|
+
`Expected block error, got: ${err.message}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('bunfs-child-process-guard allows harmless commands', async () => {
|
|
81
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
82
|
+
|
|
83
|
+
// echo is a harmless command and should not be blocked
|
|
84
|
+
const result = guard.execFileSync('echo', ['hello']);
|
|
85
|
+
// Verify the command actually executed and produced output
|
|
86
|
+
assert.equal(result.toString().trim(), 'hello');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('bunfs-child-process-guard spawn blocks official package install', async () => {
|
|
90
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
91
|
+
|
|
92
|
+
// spawn should return a blocked child process (EventEmitter-like)
|
|
93
|
+
const child = guard.spawn('npm', ['install', '-g', '@anthropic-ai/claude-code']);
|
|
94
|
+
|
|
95
|
+
// Blocked spawn should have specific properties
|
|
96
|
+
assert.equal(child.stdout, null);
|
|
97
|
+
assert.equal(child.stderr, null);
|
|
98
|
+
assert.equal(child.stdin, null);
|
|
99
|
+
assert.equal(child.pid, 0);
|
|
100
|
+
assert.equal(child.killed, false);
|
|
101
|
+
|
|
102
|
+
// Verify it's event-like (has on method or can be used as event emitter)
|
|
103
|
+
assert.equal(typeof child.kill, 'function');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('bunfs-child-process-guard does not block other package installs', async () => {
|
|
107
|
+
const guard = await import('./bunfs-child-process-guard.mjs');
|
|
108
|
+
|
|
109
|
+
// Installing a different package should not be blocked
|
|
110
|
+
const result = guard.execFileSync('echo', ['@bash0816/claude-code']);
|
|
111
|
+
// Verify the command actually executed and produced correct output
|
|
112
|
+
assert.equal(result.toString().trim(), '@bash0816/claude-code');
|
|
113
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
let PROCESS_OWNED_DIR = null;
|
|
7
|
+
let SOURCE_BIN = null;
|
|
8
|
+
let CHILD_PROCESS_GUARD_PATH = null;
|
|
9
|
+
let VM_GUARD_PATH = null;
|
|
10
|
+
let WS_STUB_PATH = null;
|
|
11
|
+
|
|
12
|
+
export function initialize(data) {
|
|
13
|
+
PROCESS_OWNED_DIR = data.processOwnedDir;
|
|
14
|
+
SOURCE_BIN = data.sourceBin;
|
|
15
|
+
CHILD_PROCESS_GUARD_PATH = data.childProcessGuardPath;
|
|
16
|
+
VM_GUARD_PATH = data.vmGuardPath;
|
|
17
|
+
WS_STUB_PATH = data.wsStubPath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildImportMetaRequirePolyfillPrelude(anchorUrl) {
|
|
21
|
+
return (
|
|
22
|
+
`import __bunfsGuardedChildProcess from ${JSON.stringify(pathToFileURL(CHILD_PROCESS_GUARD_PATH).href)};\n` +
|
|
23
|
+
`import __bunfsGuardedVm from ${JSON.stringify(pathToFileURL(VM_GUARD_PATH).href)};\n` +
|
|
24
|
+
`import { createRequire as __bunfsCreateRequire } from "node:module";\n` +
|
|
25
|
+
`const __bunfsRealRequire = __bunfsCreateRequire(${JSON.stringify(anchorUrl)});\n` +
|
|
26
|
+
`const __bunfsMetaRequire = (id) => {\n` +
|
|
27
|
+
` if (id === "child_process" || id === "node:child_process") return __bunfsGuardedChildProcess;\n` +
|
|
28
|
+
` if (id === "vm" || id === "node:vm") return __bunfsGuardedVm;\n` +
|
|
29
|
+
` return __bunfsRealRequire(id);\n` +
|
|
30
|
+
`};\n`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
35
|
+
if (specifier === 'child_process' || specifier === 'node:child_process') {
|
|
36
|
+
return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' };
|
|
37
|
+
}
|
|
38
|
+
if (specifier === 'vm' || specifier === 'node:vm') {
|
|
39
|
+
return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' };
|
|
40
|
+
}
|
|
41
|
+
if (specifier === 'ws') {
|
|
42
|
+
return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' };
|
|
43
|
+
}
|
|
44
|
+
if (specifier.startsWith('/$bunfs/root/')) {
|
|
45
|
+
const rel = specifier.slice('/$bunfs/root/'.length);
|
|
46
|
+
if (rel.includes('..') || path.isAbsolute(rel)) {
|
|
47
|
+
throw new Error(`bunfs resolve: rejected specifier ${specifier}`);
|
|
48
|
+
}
|
|
49
|
+
const real = path.resolve(PROCESS_OWNED_DIR, rel);
|
|
50
|
+
if (path.relative(PROCESS_OWNED_DIR, real).startsWith('..')) {
|
|
51
|
+
throw new Error(`bunfs resolve: path escapes process-owned dir: ${specifier}`);
|
|
52
|
+
}
|
|
53
|
+
if (!existsSync(real)) {
|
|
54
|
+
throw new Error(`bunfs resolve: missing extracted module ${specifier} -> ${real}`);
|
|
55
|
+
}
|
|
56
|
+
return { url: pathToFileURL(real).href, shortCircuit: true, format: 'module' };
|
|
57
|
+
}
|
|
58
|
+
return nextResolve(specifier, context);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function load(url, context, nextLoad) {
|
|
62
|
+
const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href;
|
|
63
|
+
if (!url.startsWith(ownedPrefix)) {
|
|
64
|
+
return nextLoad(url, context);
|
|
65
|
+
}
|
|
66
|
+
const filePath = fileURLToPath(url);
|
|
67
|
+
let source = readFileSync(filePath, 'utf8');
|
|
68
|
+
if (source.includes('import.meta.require')) {
|
|
69
|
+
const anchorUrl = pathToFileURL(SOURCE_BIN).href;
|
|
70
|
+
source = buildImportMetaRequirePolyfillPrelude(anchorUrl) +
|
|
71
|
+
source.replaceAll('import.meta.require', '__bunfsMetaRequire');
|
|
72
|
+
}
|
|
73
|
+
return { format: 'module', source, shortCircuit: true };
|
|
74
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { pathToFileURL } = require('node:url');
|
|
9
|
+
|
|
10
|
+
// ESM test ファイルから CommonJS で import できないため、
|
|
11
|
+
// ここでは基本的な構造をテストする
|
|
12
|
+
test('bunfs-esm-loader module exports initialize, resolve, load functions', async () => {
|
|
13
|
+
// ESM モジュールを動的 import でテストする
|
|
14
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
15
|
+
assert.equal(typeof loader.initialize, 'function');
|
|
16
|
+
assert.equal(typeof loader.resolve, 'function');
|
|
17
|
+
assert.equal(typeof loader.load, 'function');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('resolve() handles child_process and node:child_process specifiers', async () => {
|
|
21
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
22
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
23
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
24
|
+
const guardPath = path.join(tempDir, 'guard.mjs');
|
|
25
|
+
fs.writeFileSync(guardPath, 'export default {};');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
loader.initialize({
|
|
29
|
+
processOwnedDir: tempDir,
|
|
30
|
+
sourceBin: '/dummy/bin',
|
|
31
|
+
childProcessGuardPath: guardPath,
|
|
32
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
33
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
37
|
+
|
|
38
|
+
// child_process should resolve to childProcessGuardPath
|
|
39
|
+
const result1 = await loader.resolve('child_process', {}, nextResolve);
|
|
40
|
+
assert.ok(result1.url.includes(guardPath));
|
|
41
|
+
assert.equal(result1.shortCircuit, true);
|
|
42
|
+
|
|
43
|
+
// node:child_process should also resolve to childProcessGuardPath
|
|
44
|
+
const result2 = await loader.resolve('node:child_process', {}, nextResolve);
|
|
45
|
+
assert.ok(result2.url.includes(guardPath));
|
|
46
|
+
assert.equal(result2.shortCircuit, true);
|
|
47
|
+
} finally {
|
|
48
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('resolve() handles vm and node:vm specifiers', async () => {
|
|
53
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
54
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
55
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
56
|
+
const vmGuardPath = path.join(tempDir, 'vm-guard.mjs');
|
|
57
|
+
fs.writeFileSync(vmGuardPath, 'export default {};');
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
loader.initialize({
|
|
61
|
+
processOwnedDir: tempDir,
|
|
62
|
+
sourceBin: '/dummy/bin',
|
|
63
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
64
|
+
vmGuardPath: vmGuardPath,
|
|
65
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
69
|
+
|
|
70
|
+
// vm should resolve to vmGuardPath
|
|
71
|
+
const result1 = await loader.resolve('vm', {}, nextResolve);
|
|
72
|
+
assert.ok(result1.url.includes(vmGuardPath));
|
|
73
|
+
assert.equal(result1.shortCircuit, true);
|
|
74
|
+
|
|
75
|
+
// node:vm should also resolve to vmGuardPath
|
|
76
|
+
const result2 = await loader.resolve('node:vm', {}, nextResolve);
|
|
77
|
+
assert.ok(result2.url.includes(vmGuardPath));
|
|
78
|
+
assert.equal(result2.shortCircuit, true);
|
|
79
|
+
} finally {
|
|
80
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('resolve() handles ws specifier', async () => {
|
|
85
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
86
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
87
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
88
|
+
const wsStubPath = path.join(tempDir, 'ws-stub.mjs');
|
|
89
|
+
fs.writeFileSync(wsStubPath, 'export default {};');
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
loader.initialize({
|
|
93
|
+
processOwnedDir: tempDir,
|
|
94
|
+
sourceBin: '/dummy/bin',
|
|
95
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
96
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
97
|
+
wsStubPath: wsStubPath,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
101
|
+
|
|
102
|
+
const result = await loader.resolve('ws', {}, nextResolve);
|
|
103
|
+
assert.ok(result.url.includes(wsStubPath));
|
|
104
|
+
assert.equal(result.shortCircuit, true);
|
|
105
|
+
} finally {
|
|
106
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('resolve() resolves /$bunfs/root/ specifiers to real files in processOwnedDir', async () => {
|
|
111
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
112
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
113
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
// Create a dummy file in processOwnedDir
|
|
116
|
+
const dummyFile = path.join(tempDir, 'foo.js');
|
|
117
|
+
fs.writeFileSync(dummyFile, 'export const foo = 1;');
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
loader.initialize({
|
|
121
|
+
processOwnedDir: tempDir,
|
|
122
|
+
sourceBin: '/dummy/bin',
|
|
123
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
124
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
125
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
129
|
+
|
|
130
|
+
const result = await loader.resolve('/$bunfs/root/foo.js', {}, nextResolve);
|
|
131
|
+
assert.ok(result.url.includes('foo.js'));
|
|
132
|
+
assert.equal(result.shortCircuit, true);
|
|
133
|
+
assert.equal(result.format, 'module');
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('resolve() rejects path traversal with ..', async () => {
|
|
140
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
141
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
142
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
loader.initialize({
|
|
146
|
+
processOwnedDir: tempDir,
|
|
147
|
+
sourceBin: '/dummy/bin',
|
|
148
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
149
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
150
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
154
|
+
|
|
155
|
+
await assert.rejects(
|
|
156
|
+
() => loader.resolve('/$bunfs/root/../../etc/passwd', {}, nextResolve),
|
|
157
|
+
/rejected specifier|escapes/,
|
|
158
|
+
);
|
|
159
|
+
} finally {
|
|
160
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('resolve() rejects absolute paths', async () => {
|
|
165
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
166
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
167
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
loader.initialize({
|
|
171
|
+
processOwnedDir: tempDir,
|
|
172
|
+
sourceBin: '/dummy/bin',
|
|
173
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
174
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
175
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
179
|
+
|
|
180
|
+
await assert.rejects(
|
|
181
|
+
() => loader.resolve('/$bunfs/root//etc/passwd', {}, nextResolve),
|
|
182
|
+
/rejected specifier|escapes/,
|
|
183
|
+
);
|
|
184
|
+
} finally {
|
|
185
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('resolve() throws error for missing extracted module', async () => {
|
|
190
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
191
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
192
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
loader.initialize({
|
|
196
|
+
processOwnedDir: tempDir,
|
|
197
|
+
sourceBin: '/dummy/bin',
|
|
198
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
199
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
200
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const nextResolve = async (spec, ctx) => ({ url: `unresolved:${spec}` });
|
|
204
|
+
|
|
205
|
+
await assert.rejects(
|
|
206
|
+
() => loader.resolve('/$bunfs/root/nonexistent.js', {}, nextResolve),
|
|
207
|
+
/missing extracted module/,
|
|
208
|
+
);
|
|
209
|
+
} finally {
|
|
210
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('resolve() calls nextResolve for unknown specifiers', async () => {
|
|
215
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
216
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
217
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
loader.initialize({
|
|
221
|
+
processOwnedDir: tempDir,
|
|
222
|
+
sourceBin: '/dummy/bin',
|
|
223
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
224
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
225
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
let nextResolveCalled = false;
|
|
229
|
+
const nextResolve = async (spec, ctx) => {
|
|
230
|
+
nextResolveCalled = true;
|
|
231
|
+
return { url: `unresolved:${spec}` };
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
await loader.resolve('some-unknown-package', {}, nextResolve);
|
|
235
|
+
assert.equal(nextResolveCalled, true);
|
|
236
|
+
} finally {
|
|
237
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('load() returns source as-is when import.meta.require is not present', async () => {
|
|
242
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
243
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
244
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
245
|
+
|
|
246
|
+
const testFile = path.join(tempDir, 'test.js');
|
|
247
|
+
const sourceCode = 'export const x = 1;';
|
|
248
|
+
fs.writeFileSync(testFile, sourceCode);
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
loader.initialize({
|
|
252
|
+
processOwnedDir: tempDir,
|
|
253
|
+
sourceBin: '/dummy/bin',
|
|
254
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
255
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
256
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const fileUrl = pathToFileURL(testFile).href;
|
|
260
|
+
const result = await loader.load(fileUrl, {}, async () => ({ source: 'fallback' }));
|
|
261
|
+
|
|
262
|
+
assert.equal(result.format, 'module');
|
|
263
|
+
assert.equal(result.source, sourceCode);
|
|
264
|
+
assert.equal(result.shortCircuit, true);
|
|
265
|
+
} finally {
|
|
266
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('load() injects polyfill prelude when import.meta.require is present', async () => {
|
|
271
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
272
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
273
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
const testFile = path.join(tempDir, 'test.js');
|
|
276
|
+
const sourceCode = 'const cp = import.meta.require("child_process");';
|
|
277
|
+
fs.writeFileSync(testFile, sourceCode);
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
loader.initialize({
|
|
281
|
+
processOwnedDir: tempDir,
|
|
282
|
+
sourceBin: '/dummy/bin',
|
|
283
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
284
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
285
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const fileUrl = pathToFileURL(testFile).href;
|
|
289
|
+
const result = await loader.load(fileUrl, {}, async () => ({ source: 'fallback' }));
|
|
290
|
+
|
|
291
|
+
assert.equal(result.format, 'module');
|
|
292
|
+
assert.ok(result.source.includes('__bunfsMetaRequire'));
|
|
293
|
+
assert.ok(result.source.includes('import __bunfsGuardedChildProcess'));
|
|
294
|
+
assert.ok(result.source.includes('import __bunfsGuardedVm'));
|
|
295
|
+
// Check that import.meta.require was replaced with __bunfsMetaRequire
|
|
296
|
+
assert.ok(result.source.includes('__bunfsMetaRequire("child_process")'));
|
|
297
|
+
assert.ok(!result.source.includes('import.meta.require("child_process")'));
|
|
298
|
+
assert.equal(result.shortCircuit, true);
|
|
299
|
+
} finally {
|
|
300
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('load() calls nextLoad for URLs outside processOwnedDir', async () => {
|
|
305
|
+
const loader = await import('./bunfs-esm-loader.mjs');
|
|
306
|
+
const tempDir = path.join(os.tmpdir(), `bunfs-esm-loader-test-${process.pid}-${Date.now()}`);
|
|
307
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
loader.initialize({
|
|
311
|
+
processOwnedDir: tempDir,
|
|
312
|
+
sourceBin: '/dummy/bin',
|
|
313
|
+
childProcessGuardPath: path.join(tempDir, 'guard.mjs'),
|
|
314
|
+
vmGuardPath: path.join(tempDir, 'vm-guard.mjs'),
|
|
315
|
+
wsStubPath: path.join(tempDir, 'ws-stub.mjs'),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
let nextLoadCalled = false;
|
|
319
|
+
const nextLoad = async (url, ctx) => {
|
|
320
|
+
nextLoadCalled = true;
|
|
321
|
+
return { source: 'fallback', format: 'module' };
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
await loader.load('file:///some/other/path/module.js', {}, nextLoad);
|
|
325
|
+
assert.equal(nextLoadCalled, true);
|
|
326
|
+
} finally {
|
|
327
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|