Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/npm-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ jobs:
node --check ../../scripts/retag-latest-dist-tags.js
node --test ../../scripts/retag-latest-dist-tags.test.js
node --test lib/termux-run-claude-native.test.js
node --test lib/bunfs-esm-loader.test.js
node --test lib/native-validators.test.js

- name: Verify audited metadata
run: |
Expand Down
14 changes: 13 additions & 1 deletion config/claude-native-audited-versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@
"entry_format": "esm-chunked",
"tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==",
"tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d",
"cycle_hoists": [],
"status": "termux_verified"
},
"2.1.248": {
Expand All @@ -787,7 +788,18 @@
"tarball_sha256": "bd2e2b6ebace34039b073b0f586d521ec09c895fa2a39de8d1c86423ae5884ca",
"status": "offset_discovered",
"num_modules": 1946,
"byte_count": 136124175
"byte_count": 136124175,
"cycle_hoists": [
{ "file": "chunk-vmw9kxhv.js", "targetModule": "chunk-y0jj307t.js", "expectedOccurrences": 1, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-ppd18hq1.js", "expectedOccurrences": 1, "assertProperties": ["MonitorTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-830s9480.js", "expectedOccurrences": 1, "assertProperties": ["EndConversationTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-m4hx5z9g.js", "expectedOccurrences": 1, "assertProperties": ["ArtifactTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-eg121wbt.js", "expectedOccurrences": 1, "assertProperties": ["WorkflowTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-prrm838s.js", "expectedOccurrences": 1, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-exvwsc2a.js", "expectedOccurrences": 2, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-s61yn21a.js", "expectedOccurrences": 1, "assertProperties": ["default"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-040m9a9s.js", "expectedOccurrences": 3, "assertProperties": ["getWorkflowCommands", "invalidateWorkflowCache", "warmWorkflows"] }
]
}
}
}
10 changes: 10 additions & 0 deletions packages/claude-code/bin/claude
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ NODE="${MAGI_NODE:-node}"
if [ -n "${MAGI_NODE:-}" ]; then
export PATH="$(dirname -- "$NODE"):$PATH"
fi

if ! "$NODE" -e '
const [major, minor] = process.versions.node.split(".").map(Number);
const ok = major > 23 || (major === 23 && minor >= 5) || (major === 22 && minor >= 15);
process.exit(ok ? 0 : 1);
'; then
echo "Error: claude-code requires Node.js >=22.15.0 <23.0.0 or >=23.5.0 (module.registerHooks() API). Detected: $("$NODE" --version)" >&2
exit 1
fi

CONFIG_FILE="${PACKAGE_DIR}/config/claude-native-audited-versions.json"
PACKAGE_VERSION=$("$NODE" -e 'const fs=require("fs"); const path=require("path"); const p=JSON.parse(fs.readFileSync(path.join(process.argv[1],"package.json"),"utf8")); process.stdout.write(p.version)' "${PACKAGE_DIR}")
CLAUDE_VERSION="${CLAUDE_TERMUX_CLAUDE_VERSION:-${PACKAGE_VERSION}}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@
"entry_format": "esm-chunked",
"tarball_integrity": "sha512-Qbn5HnZbYeW4GdifVkDGfcVKqj3/f3U9sfVd3LEaUZh08CcS8D/ptJ76zuBfQUGHJHfToAdNVfak86uJ84b1Dg==",
"tarball_sha256": "668662e7b5d91a93cff6c75736e60f3d5d3bed4cbe077ae4feefc63ad1253f4d",
"cycle_hoists": [],
"status": "termux_verified"
},
"2.1.248": {
Expand All @@ -787,7 +788,18 @@
"tarball_sha256": "bd2e2b6ebace34039b073b0f586d521ec09c895fa2a39de8d1c86423ae5884ca",
"status": "offset_discovered",
"num_modules": 1946,
"byte_count": 136124175
"byte_count": 136124175,
"cycle_hoists": [
{ "file": "chunk-vmw9kxhv.js", "targetModule": "chunk-y0jj307t.js", "expectedOccurrences": 1, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-ppd18hq1.js", "expectedOccurrences": 1, "assertProperties": ["MonitorTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-830s9480.js", "expectedOccurrences": 1, "assertProperties": ["EndConversationTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-m4hx5z9g.js", "expectedOccurrences": 1, "assertProperties": ["ArtifactTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-eg121wbt.js", "expectedOccurrences": 1, "assertProperties": ["WorkflowTool"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-prrm838s.js", "expectedOccurrences": 1, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-exvwsc2a.js", "expectedOccurrences": 2, "assertProperties": [] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-s61yn21a.js", "expectedOccurrences": 1, "assertProperties": ["default"] },
{ "file": "chunk-9n7t2tbt.js", "targetModule": "chunk-040m9a9s.js", "expectedOccurrences": 3, "assertProperties": ["getWorkflowCommands", "invalidateWorkflowCache", "warmWorkflows"] }
]
}
}
}
115 changes: 104 additions & 11 deletions packages/claude-code/lib/bunfs-esm-loader.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,119 @@ let CHILD_PROCESS_GUARD_PATH = null;
let VM_GUARD_PATH = null;
let WS_STUB_PATH = null;

let CYCLE_HOISTS = [];

export function initialize(data) {
PROCESS_OWNED_DIR = data.processOwnedDir;
SOURCE_BIN = data.sourceBin;
CHILD_PROCESS_GUARD_PATH = data.childProcessGuardPath;
VM_GUARD_PATH = data.vmGuardPath;
WS_STUB_PATH = data.wsStubPath;
CYCLE_HOISTS = Array.isArray(data.cycleHoists) ? data.cycleHoists : [];
}

function tryHoistCycleBreakingImports(filePath, source) {
const rel = path.relative(PROCESS_OWNED_DIR, filePath);
const records = CYCLE_HOISTS.filter((r) => r.file === rel);
if (records.length === 0) return null;

let hoistedImportLines = '';
let newSource = source;
let varIndex = 0;
const targetToVar = new Map();

for (const record of records) {
const literal = `import.meta.require("/$bunfs/root/${record.targetModule}")`;
const occurrences = newSource.split(literal).length - 1;
if (occurrences !== record.expectedOccurrences) continue;

const real = path.resolve(PROCESS_OWNED_DIR, record.targetModule);
if (path.relative(PROCESS_OWNED_DIR, real).startsWith('..')) continue;
if (!existsSync(real)) continue;

let varName = targetToVar.get(record.targetModule);
if (!varName) {
varName = `__bunfsHoisted_${varIndex++}`;
targetToVar.set(record.targetModule, varName);
hoistedImportLines += `import * as ${varName} from ${JSON.stringify(pathToFileURL(real).href)};\n`;
if (record.assertProperties && record.assertProperties.length > 0) {
hoistedImportLines += `__bunfsAssertHoistedProps(${varName}, ${JSON.stringify(record.targetModule)}, ${JSON.stringify(record.assertProperties)});\n`;
}
}
newSource = newSource.replaceAll(literal, varName);
}

if (!targetToVar.size) return null;
return { hoistedImportLine: hoistedImportLines, source: newSource };
}

function buildImportMetaRequirePolyfillPrelude(anchorUrl) {
return (
`import __bunfsGuardedChildProcess from ${JSON.stringify(pathToFileURL(CHILD_PROCESS_GUARD_PATH).href)};\n` +
`import __bunfsGuardedVm from ${JSON.stringify(pathToFileURL(VM_GUARD_PATH).href)};\n` +
`import { createRequire as __bunfsCreateRequire } from "node:module";\n` +
`import __bunfsMetaRequirePath from "node:path";\n` +
`import { existsSync as __bunfsMetaRequireExistsSync } from "node:fs";\n` +
`import { readFileSync as __bunfsMetaRequireReadFileSync } from "node:fs";\n` +
`function __bunfsAssertHoistedProps(ns, targetModule, propNames) {\n` +
` for (const p of propNames) {\n` +
` let v;\n` +
` try {\n` +
` v = ns[p];\n` +
` } catch (e) {\n` +
` if (e instanceof ReferenceError) {\n` +
` throw new Error("bunfs cycle-hoist: accessing \\"" + p + "\\" on " + targetModule + " threw ReferenceError (TDZ) - evaluation-order regression");\n` +
` }\n` +
` throw e;\n` +
` }\n` +
` if (v === undefined) {\n` +
` throw new Error("bunfs cycle-hoist: \\"" + p + "\\" is undefined after hoisting from " + targetModule + " (evaluation-order regression?)");\n` +
` }\n` +
` }\n` +
`}\n` +
`const __bunfsRealRequire = __bunfsCreateRequire(${JSON.stringify(anchorUrl)});\n` +
`const __bunfsOwnedDir = ${JSON.stringify(PROCESS_OWNED_DIR)};\n` +
`const __bunfsMetaRequire = (id) => {\n` +
` if (id === "child_process" || id === "node:child_process") return __bunfsGuardedChildProcess;\n` +
` if (id === "vm" || id === "node:vm") return __bunfsGuardedVm;\n` +
` if (typeof id === "string" && id.startsWith("/$bunfs/root/")) {\n` +
` const rel = id.slice("/$bunfs/root/".length);\n` +
` if (rel.includes("..") || __bunfsMetaRequirePath.isAbsolute(rel)) {\n` +
` throw new Error("bunfs meta-require: rejected specifier " + id);\n` +
` }\n` +
` const real = __bunfsMetaRequirePath.resolve(__bunfsOwnedDir, rel);\n` +
` if (__bunfsMetaRequirePath.relative(__bunfsOwnedDir, real).startsWith("..")) {\n` +
` throw new Error("bunfs meta-require: path escapes process-owned dir: " + id);\n` +
` }\n` +
` if (!__bunfsMetaRequireExistsSync(real)) {\n` +
` throw new Error("bunfs meta-require: missing extracted module " + id + " -> " + real);\n` +
` }\n` +
` const ext = __bunfsMetaRequirePath.extname(real);\n` +
` if (ext === ".md" || ext === ".txt") {\n` +
` return __bunfsMetaRequireReadFileSync(real, "utf8");\n` +
` }\n` +
` return __bunfsRealRequire(real);\n` +
` }\n` +
` return __bunfsRealRequire(id);\n` +
`};\n`
);
}

export async function resolve(specifier, context, nextResolve) {
if (specifier === 'child_process' || specifier === 'node:child_process') {
return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' };
}
if (specifier === 'vm' || specifier === 'node:vm') {
return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' };
}
if (specifier === 'ws') {
return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' };
export function resolve(specifier, context, nextResolve) {
const parentURL = context && context.parentURL;
const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href;
const fromChunk = typeof parentURL === 'string' && parentURL.startsWith(ownedPrefix);

if (fromChunk) {
if (specifier === 'child_process' || specifier === 'node:child_process') {
return { url: pathToFileURL(CHILD_PROCESS_GUARD_PATH).href, shortCircuit: true, format: 'module' };
}
if (specifier === 'vm' || specifier === 'node:vm') {
return { url: pathToFileURL(VM_GUARD_PATH).href, shortCircuit: true, format: 'module' };
}
if (specifier === 'ws') {
return { url: pathToFileURL(WS_STUB_PATH).href, shortCircuit: true, format: 'module' };
}
}
if (specifier.startsWith('/$bunfs/root/')) {
const rel = specifier.slice('/$bunfs/root/'.length);
Expand All @@ -58,17 +140,28 @@ export async function resolve(specifier, context, nextResolve) {
return nextResolve(specifier, context);
}

export async function load(url, context, nextLoad) {
export function load(url, context, nextLoad) {
const ownedPrefix = pathToFileURL(PROCESS_OWNED_DIR + path.sep).href;
if (!url.startsWith(ownedPrefix)) {
return nextLoad(url, context);
}
const filePath = fileURLToPath(url);
let source = readFileSync(filePath, 'utf8');

let hoistedImportLine = '';
const hoistResult = tryHoistCycleBreakingImports(filePath, source);
if (hoistResult) {
hoistedImportLine = hoistResult.hoistedImportLine;
source = hoistResult.source;
}

if (source.includes('import.meta.require')) {
const anchorUrl = pathToFileURL(SOURCE_BIN).href;
source = buildImportMetaRequirePolyfillPrelude(anchorUrl) +
source = hoistedImportLine +
buildImportMetaRequirePolyfillPrelude(anchorUrl) +
source.replaceAll('import.meta.require', '__bunfsMetaRequire');
} else if (hoistedImportLine) {
source = hoistedImportLine + source;
}
return { format: 'module', source, shortCircuit: true };
}
Loading