mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 02:24:57 +00:00
* fix: isolate npm plugin installs per package * test: assert isolated npm plugin projects in upgrade survivor * test: assert plugin lifecycle npm project roots * test: resolve npm project deps in live assertions * fix: resolve codex bins from isolated npm projects * docs: document isolated npm plugin projects * ci: configure testbox workflow for crabbox * fix: stabilize npm project fingerprint * fix: keep fetch runtime import side-effect free * test: keep dynamic live model unit hermetic * ci: handle empty node toolcache roots * test: make nounset toolcache probe deterministic
224 lines
7.7 KiB
Bash
224 lines
7.7 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
openclaw_node_version_matches() {
|
|
local actual="$1"
|
|
local requested="$2"
|
|
if [[ -z "$requested" ]]; then
|
|
return 0
|
|
fi
|
|
case "$requested" in
|
|
*x)
|
|
[[ "${actual%%.*}" == "${requested%%.*}" ]] || return 1
|
|
if [[ "${requested%%.*}" == "22" ]]; then
|
|
openclaw_node_version_at_least "$actual" "22.19.0"
|
|
fi
|
|
;;
|
|
*.*.*)
|
|
[[ "$actual" == "$requested" ]]
|
|
;;
|
|
*.*)
|
|
[[ "$actual" == "$requested".* ]]
|
|
;;
|
|
*)
|
|
[[ "${actual%%.*}" == "$requested" ]]
|
|
;;
|
|
esac
|
|
}
|
|
|
|
openclaw_node_version_at_least() {
|
|
local actual="$1"
|
|
local minimum="$2"
|
|
local actual_major actual_minor actual_patch minimum_major minimum_minor minimum_patch
|
|
IFS=. read -r actual_major actual_minor actual_patch <<< "$actual"
|
|
IFS=. read -r minimum_major minimum_minor minimum_patch <<< "$minimum"
|
|
actual_minor="${actual_minor:-0}"
|
|
actual_patch="${actual_patch:-0}"
|
|
minimum_minor="${minimum_minor:-0}"
|
|
minimum_patch="${minimum_patch:-0}"
|
|
|
|
if (( actual_major != minimum_major )); then
|
|
(( actual_major > minimum_major ))
|
|
return
|
|
fi
|
|
if (( actual_minor != minimum_minor )); then
|
|
(( actual_minor > minimum_minor ))
|
|
return
|
|
fi
|
|
(( actual_patch >= minimum_patch ))
|
|
}
|
|
|
|
openclaw_active_node_version() {
|
|
node -p 'process.versions.node' 2>/dev/null || true
|
|
}
|
|
|
|
openclaw_prepend_node_bin() {
|
|
local node_bin_dir="$1"
|
|
local github_path_dir="${2:-$node_bin_dir}"
|
|
local shell_node_bin_dir="$node_bin_dir"
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
shell_node_bin_dir="$(cygpath -u "$node_bin_dir" 2>/dev/null || printf '%s' "$node_bin_dir")"
|
|
fi
|
|
export PATH="$shell_node_bin_dir:$PATH"
|
|
if [[ -n "${GITHUB_PATH:-}" ]]; then
|
|
local github_node_bin_dir="$github_path_dir"
|
|
if [[ $# -lt 2 ]] && command -v cygpath >/dev/null 2>&1; then
|
|
github_node_bin_dir="$shell_node_bin_dir"
|
|
github_node_bin_dir="$(cygpath -w "$shell_node_bin_dir" 2>/dev/null || printf '%s' "$shell_node_bin_dir")"
|
|
fi
|
|
echo "$github_node_bin_dir" >> "$GITHUB_PATH"
|
|
fi
|
|
hash -r
|
|
}
|
|
|
|
openclaw_find_toolcache_node() {
|
|
local requested_node="$1"
|
|
local roots=()
|
|
local root
|
|
for root in \
|
|
"${RUNNER_TOOL_CACHE:-}" \
|
|
"${AGENT_TOOLSDIRECTORY:-}" \
|
|
"${ACTIONS_RUNNER_TOOL_CACHE:-}" \
|
|
"${OPENCLAW_CONTAINER_TOOL_CACHE:-/__t}" \
|
|
"/opt/hostedtoolcache" \
|
|
"/home/runner/_work/_tool" \
|
|
"/Users/runner/hostedtoolcache" \
|
|
"/c/hostedtoolcache/windows"
|
|
do
|
|
if [[ ! -d "$root" && "$root" == *\\* ]] && command -v cygpath >/dev/null 2>&1; then
|
|
root="$(cygpath -u "$root" 2>/dev/null || printf '%s' "$root")"
|
|
fi
|
|
if [[ -d "$root/node" ]]; then
|
|
roots+=("$root/node")
|
|
elif [[ "$(basename "$root")" == "node" && -d "$root" ]]; then
|
|
roots+=("$root")
|
|
fi
|
|
done
|
|
|
|
local node_root candidate candidate_version
|
|
for node_root in ${roots[@]+"${roots[@]}"}; do
|
|
while IFS= read -r candidate; do
|
|
candidate_version="$("$candidate" -p 'process.versions.node' 2>/dev/null || true)"
|
|
if openclaw_node_version_matches "$candidate_version" "$requested_node"; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done < <(find "$node_root" \( -name node -o -name node.exe \) -type f 2>/dev/null | sort -r)
|
|
done
|
|
return 1
|
|
}
|
|
|
|
openclaw_resolve_node_download_version() {
|
|
local requested_node="$1"
|
|
if [[ "$requested_node" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
[[ "$requested_node" == v* ]] && printf '%s\n' "$requested_node" || printf 'v%s\n' "$requested_node"
|
|
return 0
|
|
fi
|
|
|
|
local prefix="${requested_node#v}"
|
|
prefix="${prefix%%[xX]*}"
|
|
prefix="v${prefix}"
|
|
[[ "$prefix" == *. ]] || prefix="${prefix}."
|
|
curl -fsSL https://nodejs.org/dist/index.json |
|
|
OPENCLAW_NODE_PREFIX="$prefix" python3 -c 'import json, os, sys
|
|
prefix = os.environ["OPENCLAW_NODE_PREFIX"]
|
|
for item in json.load(sys.stdin):
|
|
version = item.get("version", "")
|
|
if version.startswith(prefix):
|
|
print(version)
|
|
break
|
|
'
|
|
}
|
|
|
|
openclaw_node_download_platform() {
|
|
local os_name arch_name
|
|
os_name="$(uname -s)"
|
|
arch_name="$(uname -m)"
|
|
case "$os_name:$arch_name" in
|
|
Linux:x86_64) printf 'linux-x64\n' ;;
|
|
Linux:aarch64 | Linux:arm64) printf 'linux-arm64\n' ;;
|
|
Darwin:x86_64) printf 'darwin-x64\n' ;;
|
|
Darwin:arm64) printf 'darwin-arm64\n' ;;
|
|
MINGW*:x86_64 | MSYS*:x86_64 | CYGWIN*:x86_64 | MINGW*:AMD64 | MSYS*:AMD64 | CYGWIN*:AMD64)
|
|
printf 'win-x64\n'
|
|
;;
|
|
MINGW*:aarch64 | MINGW*:arm64 | MSYS*:aarch64 | MSYS*:arm64 | CYGWIN*:aarch64 | CYGWIN*:arm64) printf 'win-arm64\n' ;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
openclaw_download_node() {
|
|
local requested_node="$1"
|
|
local version platform archive_url install_root temp_root
|
|
version="$(openclaw_resolve_node_download_version "$requested_node")"
|
|
platform="$(openclaw_node_download_platform)" || return 1
|
|
temp_root="${RUNNER_TEMP:-/tmp}"
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
temp_root="$(cygpath -u "$temp_root" 2>/dev/null || printf '%s\n' "$temp_root")"
|
|
fi
|
|
install_root="${temp_root}/openclaw-node-${version}-${platform}"
|
|
if [[ "$platform" == win-* ]]; then
|
|
local archive_path ps_archive_path ps_install_root ps_bin_dir node_bin_dir
|
|
archive_path="${temp_root}/node-${version}-${platform}.zip"
|
|
archive_url="https://nodejs.org/dist/${version}/node-${version}-${platform}.zip"
|
|
rm -rf "$install_root"
|
|
mkdir -p "$install_root"
|
|
echo "Downloading Node ${version} from ${archive_url}"
|
|
curl -fsSL -o "$archive_path" "$archive_url"
|
|
ps_archive_path="$archive_path"
|
|
ps_install_root="$install_root"
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
ps_archive_path="$(cygpath -w "$archive_path")"
|
|
ps_install_root="$(cygpath -w "$install_root")"
|
|
fi
|
|
ps_bin_dir="$ps_install_root\\node-${version}-${platform}"
|
|
node_bin_dir="$install_root/node-${version}-${platform}"
|
|
if command -v pwsh >/dev/null 2>&1; then
|
|
pwsh -NoLogo -NoProfile -Command "Expand-Archive -LiteralPath '${ps_archive_path}' -DestinationPath '${ps_install_root}' -Force"
|
|
openclaw_prepend_node_bin "$node_bin_dir" "$ps_bin_dir"
|
|
elif command -v powershell.exe >/dev/null 2>&1; then
|
|
powershell.exe -NoLogo -NoProfile -Command "Expand-Archive -LiteralPath '${ps_archive_path}' -DestinationPath '${ps_install_root}' -Force"
|
|
openclaw_prepend_node_bin "$node_bin_dir" "$ps_bin_dir"
|
|
else
|
|
unzip -q "$archive_path" -d "$install_root"
|
|
openclaw_prepend_node_bin "$node_bin_dir"
|
|
fi
|
|
else
|
|
archive_url="https://nodejs.org/dist/${version}/node-${version}-${platform}.tar.xz"
|
|
mkdir -p "$install_root"
|
|
echo "Downloading Node ${version} from ${archive_url}"
|
|
curl -fsSL "$archive_url" | tar -xJ -C "$install_root" --strip-components=1
|
|
openclaw_prepend_node_bin "$install_root/bin"
|
|
fi
|
|
}
|
|
|
|
openclaw_ensure_node() {
|
|
local requested_node="${1:-}"
|
|
requested_node="${requested_node#v}"
|
|
if [[ -z "$requested_node" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local active_node_version node_bin
|
|
active_node_version="$(openclaw_active_node_version)"
|
|
if openclaw_node_version_matches "$active_node_version" "$requested_node"; then
|
|
echo "Using active Node ${active_node_version} at $(command -v node)"
|
|
return 0
|
|
fi
|
|
|
|
node_bin="$(openclaw_find_toolcache_node "$requested_node" || true)"
|
|
if [[ -n "$node_bin" ]]; then
|
|
echo "Using Node $("$node_bin" -p 'process.versions.node') from $node_bin"
|
|
openclaw_prepend_node_bin "$(dirname "$node_bin")"
|
|
else
|
|
openclaw_download_node "$requested_node" || true
|
|
fi
|
|
|
|
active_node_version="$(openclaw_active_node_version)"
|
|
if ! openclaw_node_version_matches "$active_node_version" "$requested_node"; then
|
|
echo "::error::Expected Node '${requested_node}', but active node is '${active_node_version:-missing}' at $(command -v node || true)"
|
|
return 1
|
|
fi
|
|
}
|