@@ -128,8 +128,19 @@ function spawnInvocation(command, commandArgs, env, platform) {
128128}
129129
130130const cmdMetaCharactersRe = / ( [ ( ) \] [ % ! ^ " ` < > & | ; , * ? ] ) / g;
131- const jsRuntimeEntrypoints = new Set ( [ "pnpm" , "npm" , "npx" , "corepack" , "node" , "yarn" , "bun" ] ) ;
131+ const jsRuntimeEntrypoints = new Set ( [
132+ "pnpm" ,
133+ "npm" ,
134+ "npx" ,
135+ "corepack" ,
136+ "node" ,
137+ "yarn" ,
138+ "bun" ,
139+ "bunx" ,
140+ ] ) ;
132141const awsMacosCorepackEntrypoints = new Set ( [ "pnpm" , "yarn" , "corepack" ] ) ;
142+ const awsMacosBunEntrypoints = new Set ( [ "bun" , "bunx" ] ) ;
143+ const awsMacosBunVersion = "1.3.14" ;
133144const minimumBlacksmithCrabboxVersion = [ 0 , 22 , 0 ] ;
134145const shellControlCommandPrefixes = new Set ( [
135146 "if" ,
@@ -785,25 +796,37 @@ function commandNeedsAwsMacosPackageManager(commandArgs) {
785796 if ( isChangedGateCommand ( commandArgs ) ) {
786797 return true ;
787798 }
799+ return commandNeedsEntrypoint ( commandArgs , awsMacosCorepackEntrypoints ) ;
800+ }
801+
802+ function commandNeedsAwsMacosBun ( commandArgs ) {
803+ return commandNeedsEntrypoint ( commandArgs , awsMacosBunEntrypoints ) ;
804+ }
805+
806+ function commandNeedsEntrypoint ( commandArgs , entrypoints ) {
788807 if ( commandArgs . length === 1 ) {
789- return shellCommandWordCandidates ( commandArgs [ 0 ] ) . some ( commandWordsNeedAwsMacosPackageManager ) ;
808+ return shellCommandWordCandidates ( commandArgs [ 0 ] ) . some ( ( words ) =>
809+ commandWordsNeedEntrypoint ( words , entrypoints ) ,
810+ ) ;
790811 }
791- return commandWordsNeedAwsMacosPackageManager ( normalizedCommandWords ( commandArgs ) ) ;
812+ return commandWordsNeedEntrypoint ( normalizedCommandWords ( commandArgs ) , entrypoints ) ;
792813}
793814
794- function commandWordsNeedAwsMacosPackageManager ( wordsInput ) {
815+ function commandWordsNeedEntrypoint ( wordsInput , entrypoints ) {
795816 let words = wordsInput ;
796817 words = normalizeExecutableWords ( words ) ;
797818 const first = ( words [ 0 ] ?? "" ) . split ( "/" ) . pop ( ) ;
798- if ( awsMacosCorepackEntrypoints . has ( first ) ) {
819+ if ( entrypoints . has ( first ) ) {
799820 return true ;
800821 }
801822
802823 const inlineCommand = shellInlineCommand ( words ) ;
803824 if ( ! inlineCommand ) {
804825 return false ;
805826 }
806- return shellCommandWordCandidates ( inlineCommand ) . some ( commandWordsNeedAwsMacosPackageManager ) ;
827+ return shellCommandWordCandidates ( inlineCommand ) . some ( ( candidateWords ) =>
828+ commandWordsNeedEntrypoint ( candidateWords , entrypoints ) ,
829+ ) ;
807830}
808831
809832function isChangedGateCommand ( commandArgs ) {
@@ -1668,7 +1691,7 @@ function injectRemoteChangedGateGitBootstrap(commandArgs, changedGateBase) {
16681691 return normalizedArgs ;
16691692}
16701693
1671- function remoteAwsMacosJsBootstrap ( { packageManager = false } = { } ) {
1694+ function remoteAwsMacosJsBootstrap ( { packageManager = false , bun = false } = { } ) {
16721695 const nodeVersion = process . env . OPENCLAW_CRABBOX_MACOS_NODE_VERSION ?. trim ( ) || "24.15.0" ;
16731696 const bootstrap = [
16741697 "openclaw_crabbox_bootstrap_macos_js() {" ,
@@ -1750,6 +1773,42 @@ function remoteAwsMacosJsBootstrap({ packageManager = false } = {}) {
17501773 "pnpm --version >&2;" ,
17511774 ) ;
17521775 }
1776+ // Raw AWS macOS boxes skip setup-node-env, so Bun needs its own user-local pin.
1777+ if ( bun ) {
1778+ bootstrap . push (
1779+ `bun_version=${ shellQuote ( awsMacosBunVersion ) } ;` ,
1780+ 'bun_root="$tool_root/bun-v${bun_version}";' ,
1781+ 'bun_ready_marker="$bun_root/.openclaw-crabbox-bun-ready";' ,
1782+ 'export PATH="$bun_root/bin:$PATH";' ,
1783+ 'if [ ! -x "$bun_root/bin/bun" ] || [ ! -f "$bun_ready_marker" ]; then' ,
1784+ 'mkdir -p "$tool_root" || { status=$?; return "$status"; };' ,
1785+ 'bun_install_lock="$tool_root/.bun-${bun_version}.lock";' ,
1786+ "bun_lock_acquired=0;" ,
1787+ "bun_lock_deadline=$((SECONDS + 300));" ,
1788+ "while true; do" ,
1789+ 'if mkdir "$bun_install_lock" 2>/dev/null; then bun_lock_acquired=1; printf "%s\\n" "$$" >"$bun_install_lock/pid" || { status=$?; rm -rf "$bun_install_lock"; return "$status"; }; break; fi;' ,
1790+ 'if [ -x "$bun_root/bin/bun" ] && [ -f "$bun_ready_marker" ]; then break; fi;' ,
1791+ 'if [ "$SECONDS" -ge "$bun_lock_deadline" ]; then' ,
1792+ 'bun_lock_pid="$(cat "$bun_install_lock/pid" 2>/dev/null || true)";' ,
1793+ 'if [ -n "$bun_lock_pid" ] && kill -0 "$bun_lock_pid" 2>/dev/null; then echo "timed out waiting for active macOS Bun install lock: $bun_install_lock pid=$bun_lock_pid" >&2; return 1; fi;' ,
1794+ 'echo "reclaiming stale macOS Bun install lock: $bun_install_lock" >&2;' ,
1795+ 'rm -rf "$bun_install_lock" || return 1;' ,
1796+ "bun_lock_deadline=$((SECONDS + 300));" ,
1797+ "fi;" ,
1798+ "sleep 1;" ,
1799+ "done;" ,
1800+ 'release_bun_install_lock() { if [ "$bun_lock_acquired" = "1" ]; then rm -rf "$bun_install_lock" 2>/dev/null || true; fi; };' ,
1801+ 'if [ ! -x "$bun_root/bin/bun" ] || [ ! -f "$bun_ready_marker" ]; then' ,
1802+ 'rm -rf "$bun_root" || { status=$?; release_bun_install_lock; return "$status"; };' ,
1803+ 'mkdir -p "$bun_root" || { status=$?; release_bun_install_lock; return "$status"; };' ,
1804+ 'npm install --global --prefix "$bun_root" "bun@${bun_version}" || { status=$?; release_bun_install_lock; return "$status"; };' ,
1805+ 'touch "$bun_ready_marker" || { status=$?; release_bun_install_lock; return "$status"; };' ,
1806+ "fi;" ,
1807+ "release_bun_install_lock;" ,
1808+ "fi;" ,
1809+ "bun --version >&2 || return 1;" ,
1810+ ) ;
1811+ }
17531812 bootstrap . push ( "};" , "openclaw_crabbox_bootstrap_macos_js" ) ;
17541813 return bootstrap . join ( " " ) ;
17551814}
@@ -1775,6 +1834,7 @@ function scopedAwsMacosEnvCommand(commandArgs) {
17751834 return {
17761835 runtimeEntrypoint : targetEntrypoint ,
17771836 packageManager : awsMacosCorepackEntrypoints . has ( targetEntrypoint ) ,
1837+ bun : awsMacosBunEntrypoints . has ( targetEntrypoint ) ,
17781838 shellCommand : `openclaw_crabbox_env ${ shellJoin ( commandArgs . slice ( 1 ) ) } ` ,
17791839 } ;
17801840}
@@ -1805,6 +1865,7 @@ function injectRemoteAwsMacosJsBootstrap(commandArgs, providerName) {
18051865 const shellCommand = `${ remoteAwsMacosJsBootstrap ( {
18061866 packageManager :
18071867 directScopedEnvCommand ?. packageManager || commandNeedsAwsMacosPackageManager ( runArgs ) ,
1868+ bun : directScopedEnvCommand ?. bun || commandNeedsAwsMacosBun ( runArgs ) ,
18081869 } ) } && { ${ originalShellCommand } \n}`;
18091870
18101871 if ( ! hasOption ( normalizedArgs , "--shell" ) ) {
@@ -1876,13 +1937,13 @@ function prepareAwsMacosScriptStdinBootstrap(commandArgs, providerName) {
18761937}
18771938
18781939function createAwsMacosScriptStdinWrapper ( script ) {
1879- const packageManager = scriptNeedsAwsMacosPackageManager ( script ) ;
1940+ const requirements = awsMacosScriptBootstrapRequirements ( script ) ;
18801941 if ( ! script . startsWith ( "#!" ) ) {
1881- return `${ remoteAwsMacosJsBootstrap ( { packageManager } ) } || exit $?\n${ script } ` ;
1942+ return `${ remoteAwsMacosJsBootstrap ( requirements ) } || exit $?\n${ script } ` ;
18821943 }
18831944 const delimiterValue = uniqueHereDocDelimiter ( script ) ;
18841945 return [
1885- `${ remoteAwsMacosJsBootstrap ( { packageManager } ) } || exit $?` ,
1946+ `${ remoteAwsMacosJsBootstrap ( requirements ) } || exit $?` ,
18861947 'tmp_script="$(mktemp "${TMPDIR:-/tmp}/openclaw-crabbox-script.XXXXXX")" || exit $?' ,
18871948 'cleanup_openclaw_crabbox_script() { rm -f "$tmp_script"; }' ,
18881949 "trap cleanup_openclaw_crabbox_script EXIT" ,
@@ -1895,7 +1956,8 @@ function createAwsMacosScriptStdinWrapper(script) {
18951956 ] . join ( "\n" ) ;
18961957}
18971958
1898- function scriptNeedsAwsMacosPackageManager ( script ) {
1959+ function awsMacosScriptBootstrapRequirements ( script ) {
1960+ const requirements = { packageManager : false , bun : false } ;
18991961 const firstLine = script . match ( / ^ [ ^ \r \n ] * / u) ?. [ 0 ] ?? "" ;
19001962 if ( firstLine . startsWith ( "#!" ) ) {
19011963 let words = firstLine . slice ( 2 ) . trim ( ) . split ( / \s + / u) . filter ( Boolean ) ;
@@ -1905,11 +1967,13 @@ function scriptNeedsAwsMacosPackageManager(script) {
19051967 words = words . slice ( 1 ) ;
19061968 }
19071969 }
1908- if ( commandWordsNeedAwsMacosPackageManager ( words ) ) {
1909- return true ;
1910- }
1970+ requirements . packageManager = commandWordsNeedEntrypoint ( words , awsMacosCorepackEntrypoints ) ;
1971+ requirements . bun = commandWordsNeedEntrypoint ( words , awsMacosBunEntrypoints ) ;
1972+ return requirements ;
19111973 }
1912- return commandNeedsAwsMacosPackageManager ( [ script ] ) ;
1974+ requirements . packageManager = commandNeedsAwsMacosPackageManager ( [ script ] ) ;
1975+ requirements . bun = commandNeedsAwsMacosBun ( [ script ] ) ;
1976+ return requirements ;
19131977}
19141978
19151979function uniqueHereDocDelimiter ( script ) {
@@ -2369,15 +2433,15 @@ if (
23692433) {
23702434 if ( isAwsMacosRemoteTarget ( normalizedArgs , provider ) ) {
23712435 console . error (
2372- `[crabbox] provider=aws macOS raw boxes may lack Node/Corepack/pnpm for ${ runtimeEntrypoint || "--script-stdin" } ; bootstrapping a pinned user-local Node toolchain before the command` ,
2436+ `[crabbox] provider=aws macOS raw boxes may lack Node/Corepack/pnpm/Bun for ${ runtimeEntrypoint || "--script-stdin" } ; bootstrapping pinned user-local JavaScript tooling before the command` ,
23732437 ) ;
23742438 } else {
23752439 const id = optionValue ( normalizedArgs , "--id" ) ;
23762440 const hydrate = id
23772441 ? `pnpm crabbox:hydrate -- --id ${ id } `
23782442 : "pnpm crabbox:warmup, then pnpm crabbox:hydrate -- --id <id>" ;
23792443 console . error (
2380- `[crabbox] warning: provider=aws raw boxes may lack Node/Corepack/pnpm for ${ runtimeEntrypoint } ; hydrate first (${ hydrate } ) or pass --provider blacksmith-testbox for OpenClaw CI-like proof; not switching providers automatically` ,
2444+ `[crabbox] warning: provider=aws raw boxes may lack Node/Corepack/pnpm/Bun for ${ runtimeEntrypoint } ; hydrate first (${ hydrate } ) or pass --provider blacksmith-testbox for OpenClaw CI-like proof; not switching providers automatically` ,
23812445 ) ;
23822446 }
23832447}
0 commit comments