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
110 changes: 86 additions & 24 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ NODEJS_MANUAL_DOWNLOAD_URL="${FLOCKS_NODEJS_MANUAL_DOWNLOAD_URL:-https://nodejs.
NVM_INSTALL_SCRIPT_URL="${FLOCKS_NVM_INSTALL_SCRIPT_URL:-https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh}"
NVM_GITEE_REPO_URL="${FLOCKS_NVM_GITEE_REPO_URL:-https://gitee.com/mirrors/nvm.git}"
NVM_GITEE_RAW_URL_PREFIX="${FLOCKS_NVM_GITEE_RAW_URL_PREFIX:-https://gitee.com/mirrors/nvm/raw}"
NODE_CMD="node"
NPM_CMD="npm"
NPX_CMD="npx"

info() {
printf '[flocks] %s\n' "$1"
Expand Down Expand Up @@ -54,6 +57,30 @@ clear_command_cache() {
hash -r 2>/dev/null || true
}

command_exists() {
local cmd="$1"
[[ -n "$cmd" ]] || return 1

if [[ "$cmd" == */* ]]; then
[[ -x "$cmd" ]]
return
fi

command -v "$cmd" >/dev/null 2>&1
}

node_command_available() {
command_exists "$NODE_CMD"
}

npm_command_available() {
command_exists "$NPM_CMD"
}

npx_command_available() {
command_exists "$NPX_CMD"
}

is_zh_install() {
[[ "$INSTALL_LANGUAGE" == zh* || "$INSTALL_LANGUAGE" == cn* ]]
}
Expand Down Expand Up @@ -290,9 +317,9 @@ refresh_path() {
append_path "$HOME/.bun/bin"
append_path "$HOME/.npm-global/bin"

if has_cmd npm; then
if npm_command_available; then
local npm_prefix
npm_prefix="$(npm config get prefix 2>/dev/null | tr -d '\r' || true)"
npm_prefix="$(get_npm_prefix || true)"
if [[ -n "$npm_prefix" && "$npm_prefix" != "undefined" && "$npm_prefix" != "null" ]]; then
append_path "$npm_prefix"
append_path "$npm_prefix/bin"
Expand All @@ -301,12 +328,12 @@ refresh_path() {
}

get_npm_prefix() {
if ! has_cmd npm; then
if ! npm_command_available; then
return 1
fi

local npm_prefix
npm_prefix="$(npm config get prefix 2>/dev/null | tr -d '\r' || true)"
npm_prefix="$("$NPM_CMD" config get prefix 2>/dev/null | tr -d '\r' || true)"
if [[ -z "$npm_prefix" || "$npm_prefix" == "undefined" || "$npm_prefix" == "null" ]]; then
return 1
fi
Expand Down Expand Up @@ -378,25 +405,50 @@ parse_args() {
done
}

get_node_major_version() {
if ! has_cmd node; then
return 1
fi

get_node_major_version_from_cmd() {
local node_cmd="$1"
local version
version="$(node -v 2>/dev/null | tr -d '\r' || true)"
command_exists "$node_cmd" || return 1

version="$("$node_cmd" -v 2>/dev/null | tr -d '\r' || true)"
version="${version#v}"
version="${version%%.*}"
[[ "$version" =~ ^[0-9]+$ ]] || return 1
printf '%s' "$version"
}

node_version_satisfies_requirement() {
get_node_major_version() {
get_node_major_version_from_cmd "$NODE_CMD"
}

node_command_satisfies_requirement() {
local node_cmd="$1"
local major
major="$(get_node_major_version)" || return 1
major="$(get_node_major_version_from_cmd "$node_cmd")" || return 1
[[ "$major" -ge "$MIN_NODE_MAJOR" ]]
}

node_version_satisfies_requirement() {
node_command_satisfies_requirement "$NODE_CMD"
}

set_node_toolchain_from_bin_dir() {
local bin_dir="$1"
[[ -n "$bin_dir" && -d "$bin_dir" ]] || return 1
[[ -x "$bin_dir/node" && -x "$bin_dir/npm" ]] || return 1

NODE_CMD="$bin_dir/node"
NPM_CMD="$bin_dir/npm"
if [[ -x "$bin_dir/npx" ]]; then
NPX_CMD="$bin_dir/npx"
else
NPX_CMD="npx"
fi

append_path "$bin_dir"
clear_command_cache
}

load_nvm() {
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
[[ -s "$NVM_DIR/nvm.sh" ]] || return 1
Expand Down Expand Up @@ -489,10 +541,20 @@ install_nodejs_with_nvm() {
fi
clear_command_cache

if ! node_version_satisfies_requirement; then
warn "nvm activated Node.js ${MIN_NODE_MAJOR}, but the current shell still resolves a different node binary. Open a new shell and retry.$(nodejs_manual_download_hint)"
return 1
if node_command_satisfies_requirement "node" && npm_command_available; then
return 0
fi

if [[ -n "${NVM_BIN:-}" ]] \
&& command_exists "$NVM_BIN/npm" \
&& node_command_satisfies_requirement "$NVM_BIN/node"; then
set_node_toolchain_from_bin_dir "$NVM_BIN"
warn "nvm activated Node.js ${MIN_NODE_MAJOR}, but the current shell still resolves a different node binary. Continuing with the nvm-managed toolchain for this installer run. Open a new shell afterwards.$(nodejs_manual_download_hint)"
return 0
fi

warn "nvm activated Node.js ${MIN_NODE_MAJOR}, but the nvm-managed runtime is still not usable in this shell.$(nodejs_manual_download_hint)"
return 1
}

install_nodejs_macos() {
Expand Down Expand Up @@ -578,11 +640,11 @@ install_nodejs_linux() {
}

ensure_npm_installed() {
if has_cmd npm && node_version_satisfies_requirement; then
if npm_command_available && node_version_satisfies_requirement; then
return
fi

if has_cmd node; then
if node_command_available; then
local current_major
current_major="$(get_node_major_version || true)"
if [[ -n "$current_major" ]]; then
Expand All @@ -605,12 +667,12 @@ ensure_npm_installed() {
esac

refresh_path
has_cmd npm || fail "Node.js (including npm) was installed, but npm is still not available. Check PATH and retry.$(nodejs_manual_download_hint)"
npm_command_available || fail "Node.js (including npm) was installed, but npm is still not available. Check PATH and retry.$(nodejs_manual_download_hint)"
node_version_satisfies_requirement || fail "Detected Node.js version is too old. This project requires Node.js ${MIN_NODE_MAJOR}+.$(nodejs_manual_download_hint)"
}

ensure_npm_global_prefix_writable() {
has_cmd npm || fail "npm was not found. Install Node.js 22+ (including npm) and retry.$(nodejs_manual_download_hint)"
npm_command_available || fail "npm was not found. Install Node.js 22+ (including npm) and retry.$(nodejs_manual_download_hint)"

local npm_prefix target_dir user_prefix
npm_prefix="$(get_npm_prefix || true)"
Expand All @@ -627,7 +689,7 @@ ensure_npm_global_prefix_writable() {
user_prefix="$HOME/.npm-global"
info "Global npm directory is not writable. Switching to user prefix: $user_prefix"
mkdir -p "$user_prefix"
npm config set prefix "$user_prefix"
"$NPM_CMD" config set prefix "$user_prefix"
refresh_path
}

Expand Down Expand Up @@ -944,7 +1006,7 @@ resolve_chrome_for_testing_path_from_dir() {

install_chrome_for_testing() {
local browser_dir browser_path="" install_status
if ! has_cmd npx; then
if ! npx_command_available; then
if is_zh_install; then
warn "未找到 npx,跳过浏览器安装;这不影响 Flocks 启动,可稍后重新安装。"
else
Expand All @@ -965,7 +1027,7 @@ install_chrome_for_testing() {
fi

set +e
npm_config_registry="$NPM_REGISTRY" npx --yes @puppeteer/browsers install chrome@stable --path "$browser_dir" 1>&2
npm_config_registry="$NPM_REGISTRY" "$NPX_CMD" --yes @puppeteer/browsers install chrome@stable --path "$browser_dir" 1>&2
install_status=$?
set -e

Expand Down Expand Up @@ -1021,7 +1083,7 @@ install_agent_browser() {
if ! has_cmd agent-browser; then
ensure_npm_global_prefix_writable
info "Installing the agent-browser CLI..."
npm_config_registry="$NPM_REGISTRY" npm install --global agent-browser
npm_config_registry="$NPM_REGISTRY" "$NPM_CMD" install --global agent-browser
refresh_path
ensure_agent_browser_user_path_if_needed
has_cmd agent-browser || fail "agent-browser finished installing, but it is still not available. Check PATH and retry."
Expand Down Expand Up @@ -1066,7 +1128,7 @@ main() {
fi
(
cd "$ROOT_DIR/webui"
npm_config_registry="$NPM_REGISTRY" npm install
npm_config_registry="$NPM_REGISTRY" "$NPM_CMD" install
)

if [[ "$INSTALL_TUI" -eq 1 ]]; then
Expand Down
4 changes: 2 additions & 2 deletions tests/scripts/test_browser_runtime_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ def test_bash_installer_prefers_explicit_browser_configuration() -> None:
assert "AGENT_BROWSER_EXECUTABLE_PATH" in script
assert "get_chrome_for_testing_dir()" in script
assert "resolve_chrome_for_testing_path_from_dir()" in script
assert 'npx --yes @puppeteer/browsers install chrome@stable --path "$browser_dir"' in script
assert '"$NPX_CMD" --yes @puppeteer/browsers install chrome@stable --path "$browser_dir"' in script
assert 'Downloading Chrome for Testing.' in script
assert 'If browser installation fails, Flocks can still start and you can reinstall it later.' in script
assert 'npm_config_registry="$NPM_REGISTRY" npx --yes @puppeteer/browsers install chrome@stable --path "$browser_dir" 1>&2' in script
assert 'npm_config_registry="$NPM_REGISTRY" "$NPX_CMD" --yes @puppeteer/browsers install chrome@stable --path "$browser_dir" 1>&2' in script
assert '"$browser_dir"/**/"Google Chrome for Testing"' in script
assert '"$browser_dir"/**/chrome.exe' in script
assert '"$browser_dir"/**/chrome' in script
Expand Down
Loading