browser4-cli 0.1.15 → 0.1.17
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/bin/browser4-cli-darwin-arm64 +0 -0
- package/bin/browser4-cli-darwin-x64 +0 -0
- package/bin/browser4-cli-linux-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-x64 +0 -0
- package/bin/browser4-cli-linux-x64 +0 -0
- package/bin/browser4-cli-win32-x64.exe +0 -0
- package/package.json +1 -1
- package/scripts/install-browser4-cli.ps1 +63 -2
- package/scripts/install-browser4-cli.sh +126 -14
- package/scripts/postinstall.js +79 -11
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -154,12 +154,60 @@ function Get-DefaultInstallDir {
|
|
|
154
154
|
throw "Unsupported OS"
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
# ──────────────────────────────────────────────
|
|
158
|
+
# China mainland locale detection (zero-network)
|
|
159
|
+
# ──────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
<#
|
|
162
|
+
.SYNOPSIS
|
|
163
|
+
Detect whether the current system is likely in China mainland.
|
|
164
|
+
Uses only local env vars and .NET APIs — no network calls.
|
|
165
|
+
#>
|
|
166
|
+
function Test-ChinaLocale {
|
|
167
|
+
# 1 — Locale env vars
|
|
168
|
+
$lang = $env:LC_ALL, $env:LANG, $env:LC_CTYPE, $env:LC_MESSAGES | Where-Object { $_ } | Select-Object -First 1
|
|
169
|
+
if ($lang -and ($lang -match '^zh_CN' -or $lang -match '^zh-CN' -or $lang -match '^Chinese \(Simplified\)_China')) {
|
|
170
|
+
return $true
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# 2 — TZ env var
|
|
174
|
+
$tzEnv = $env:TZ
|
|
175
|
+
if ($tzEnv -and ($tzEnv -match '^Asia/(Shanghai|Chongqing|Urumqi|Harbin)$')) {
|
|
176
|
+
return $true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# 3 — .NET TimeZoneInfo (works on Windows and Unix PowerShell 7+)
|
|
180
|
+
try {
|
|
181
|
+
$tzId = [System.TimeZoneInfo]::Local.Id
|
|
182
|
+
if ($tzId -match '^Asia/(Shanghai|Chongqing|Urumqi|Harbin)$') {
|
|
183
|
+
return $true
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
# TimeZoneInfo not available (unlikely on PS 5.1+ but guard anyway)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# 4 — /etc/timezone (PowerShell on Linux/macOS)
|
|
190
|
+
if (-not $script:OSWin -and (Test-Path '/etc/timezone')) {
|
|
191
|
+
try {
|
|
192
|
+
$tz = Get-Content '/etc/timezone' -Raw -ErrorAction Stop
|
|
193
|
+
if ($tz -match '^Asia/(Shanghai|Chongqing|Urumqi|Harbin)$') {
|
|
194
|
+
return $true
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
# Permission or read error — skip
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return $false
|
|
202
|
+
}
|
|
203
|
+
|
|
157
204
|
# ──────────────────────────────────────────────
|
|
158
205
|
# Download URLs
|
|
159
206
|
# ──────────────────────────────────────────────
|
|
160
207
|
|
|
161
208
|
$GITHUB_REPO = "platonai/Browser4"
|
|
162
209
|
$OSS_BASE = "https://browser4.oss-cn-beijing.aliyuncs.com"
|
|
210
|
+
$script:ChinaDetected = $false
|
|
163
211
|
|
|
164
212
|
function Get-DownloadUrls {
|
|
165
213
|
param([string]$BinaryName, [string]$VersionTag)
|
|
@@ -181,8 +229,13 @@ function Get-DownloadUrls {
|
|
|
181
229
|
} elseif ($Source -eq "oss") {
|
|
182
230
|
$urls += @{ Url = $ossUrl; Label = "Aliyun OSS" }
|
|
183
231
|
} else {
|
|
184
|
-
$
|
|
185
|
-
|
|
232
|
+
if ($script:ChinaDetected) {
|
|
233
|
+
$urls += @{ Url = $ossUrl; Label = "Aliyun OSS" }
|
|
234
|
+
$urls += @{ Url = $ghUrl; Label = "GitHub Releases" }
|
|
235
|
+
} else {
|
|
236
|
+
$urls += @{ Url = $ghUrl; Label = "GitHub Releases" }
|
|
237
|
+
$urls += @{ Url = $ossUrl; Label = "Aliyun OSS" }
|
|
238
|
+
}
|
|
186
239
|
}
|
|
187
240
|
|
|
188
241
|
return $urls
|
|
@@ -359,6 +412,14 @@ function Main {
|
|
|
359
412
|
Write-Summary "╚════════════════════════════════════════╝" -Color Cyan
|
|
360
413
|
Write-Summary ""
|
|
361
414
|
|
|
415
|
+
# Auto-detect China mainland locale when no explicit source is given
|
|
416
|
+
if (-not $Source) {
|
|
417
|
+
$script:ChinaDetected = Test-ChinaLocale
|
|
418
|
+
if ($script:ChinaDetected) {
|
|
419
|
+
Write-Step "China mainland locale detected: preferring Aliyun OSS mirror."
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
362
423
|
# Detect platform
|
|
363
424
|
$platformKey = Get-PlatformKey
|
|
364
425
|
$binaryName = Get-BinaryName -PlatformKey $platformKey
|
|
@@ -36,6 +36,7 @@ SOURCE=""
|
|
|
36
36
|
ADD_TO_PATH=true
|
|
37
37
|
SILENT=false
|
|
38
38
|
DRY_RUN=false
|
|
39
|
+
CHINA_DETECTED=false
|
|
39
40
|
|
|
40
41
|
# ──────────────────────────────────────────────
|
|
41
42
|
# Helpers
|
|
@@ -110,6 +111,35 @@ while [[ $# -gt 0 ]]; do
|
|
|
110
111
|
esac
|
|
111
112
|
done
|
|
112
113
|
|
|
114
|
+
# ──────────────────────────────────────────────
|
|
115
|
+
# China mainland locale detection (zero-network)
|
|
116
|
+
# ──────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
detect_china_locale() {
|
|
119
|
+
# 1 — Locale env vars
|
|
120
|
+
local lang
|
|
121
|
+
lang="${LC_ALL:-${LANG:-${LC_CTYPE:-${LC_MESSAGES:-}}}}"
|
|
122
|
+
case "$lang" in
|
|
123
|
+
zh_CN*|zh-CN*|"Chinese (Simplified)_China"*) return 0 ;;
|
|
124
|
+
esac
|
|
125
|
+
|
|
126
|
+
# 2 — TZ env var
|
|
127
|
+
case "${TZ:-}" in
|
|
128
|
+
Asia/Shanghai|Asia/Chongqing|Asia/Urumqi|Asia/Harbin) return 0 ;;
|
|
129
|
+
esac
|
|
130
|
+
|
|
131
|
+
# 3 — /etc/timezone
|
|
132
|
+
if [[ -f /etc/timezone ]]; then
|
|
133
|
+
local tz
|
|
134
|
+
tz=$(cat /etc/timezone 2>/dev/null || true)
|
|
135
|
+
case "$tz" in
|
|
136
|
+
Asia/Shanghai|Asia/Chongqing|Asia/Urumqi|Asia/Harbin) return 0 ;;
|
|
137
|
+
esac
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
return 1
|
|
141
|
+
}
|
|
142
|
+
|
|
113
143
|
# ──────────────────────────────────────────────
|
|
114
144
|
# Platform detection
|
|
115
145
|
# ──────────────────────────────────────────────
|
|
@@ -140,14 +170,34 @@ detect_libc() {
|
|
|
140
170
|
return
|
|
141
171
|
fi
|
|
142
172
|
|
|
143
|
-
# Check for musl
|
|
144
|
-
if ldd
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
echo ""
|
|
173
|
+
# Check for musl via ldd --version (guarded against missing ldd)
|
|
174
|
+
if command -v ldd >/dev/null 2>&1; then
|
|
175
|
+
if ldd --version 2>&1 | grep -qi musl; then
|
|
176
|
+
echo "musl"
|
|
177
|
+
return
|
|
178
|
+
fi
|
|
150
179
|
fi
|
|
180
|
+
|
|
181
|
+
# Check for musl loader — covers common architectures
|
|
182
|
+
# x86_64, aarch64, armhf (32-bit ARM), i386, riscv64, s390x, ppc64le, mips64
|
|
183
|
+
local musl_loader
|
|
184
|
+
for musl_loader in \
|
|
185
|
+
/lib/ld-musl-x86_64.so.1 \
|
|
186
|
+
/lib/ld-musl-aarch64.so.1 \
|
|
187
|
+
/lib/ld-musl-armhf.so.1 \
|
|
188
|
+
/lib/ld-musl-i386.so.1 \
|
|
189
|
+
/lib/ld-musl-riscv64.so.1 \
|
|
190
|
+
/lib/ld-musl-s390x.so.1 \
|
|
191
|
+
/lib/ld-musl-ppc64le.so.1 \
|
|
192
|
+
/lib/ld-musl-mips64.so.1 \
|
|
193
|
+
/lib/ld-musl-mipsel.so.1; do
|
|
194
|
+
if [[ -f "$musl_loader" ]]; then
|
|
195
|
+
echo "musl"
|
|
196
|
+
return
|
|
197
|
+
fi
|
|
198
|
+
done
|
|
199
|
+
|
|
200
|
+
echo ""
|
|
151
201
|
}
|
|
152
202
|
|
|
153
203
|
get_platform_key() {
|
|
@@ -196,8 +246,14 @@ get_default_install_dir() {
|
|
|
196
246
|
# ──────────────────────────────────────────────
|
|
197
247
|
|
|
198
248
|
check_commands() {
|
|
199
|
-
|
|
200
|
-
|
|
249
|
+
local missing=()
|
|
250
|
+
for cmd in curl mktemp stat awk grep ln mkdir chmod; do
|
|
251
|
+
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
252
|
+
missing+=("$cmd")
|
|
253
|
+
fi
|
|
254
|
+
done
|
|
255
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
256
|
+
die "Required command(s) not found: ${missing[*]}. Install them and retry."
|
|
201
257
|
fi
|
|
202
258
|
}
|
|
203
259
|
|
|
@@ -219,8 +275,13 @@ get_download_urls() {
|
|
|
219
275
|
github) urls+=("GitHub Releases|${gh_url}") ;;
|
|
220
276
|
oss) urls+=("Aliyun OSS|${oss_url}") ;;
|
|
221
277
|
*)
|
|
222
|
-
|
|
223
|
-
|
|
278
|
+
if [[ "$CHINA_DETECTED" == true ]]; then
|
|
279
|
+
urls+=("Aliyun OSS|${oss_url}")
|
|
280
|
+
urls+=("GitHub Releases|${gh_url}")
|
|
281
|
+
else
|
|
282
|
+
urls+=("GitHub Releases|${gh_url}")
|
|
283
|
+
urls+=("Aliyun OSS|${oss_url}")
|
|
284
|
+
fi
|
|
224
285
|
;;
|
|
225
286
|
esac
|
|
226
287
|
|
|
@@ -240,10 +301,27 @@ download_file() {
|
|
|
240
301
|
return 0
|
|
241
302
|
fi
|
|
242
303
|
|
|
243
|
-
local
|
|
304
|
+
local curl_stderr
|
|
305
|
+
curl_stderr=$(mktemp)
|
|
306
|
+
local http_code curl_exit
|
|
307
|
+
|
|
244
308
|
http_code=$(curl -sSfL -w "%{http_code}" -o "$dest" \
|
|
245
309
|
${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} \
|
|
246
|
-
"$url" 2
|
|
310
|
+
"$url" 2>"$curl_stderr")
|
|
311
|
+
curl_exit=$?
|
|
312
|
+
|
|
313
|
+
if [[ $curl_exit -ne 0 ]]; then
|
|
314
|
+
local stderr_msg
|
|
315
|
+
stderr_msg=$(<"$curl_stderr")
|
|
316
|
+
rm -f "$curl_stderr" "$dest"
|
|
317
|
+
if [[ -n "$stderr_msg" ]]; then
|
|
318
|
+
warn "curl error (exit ${curl_exit}): ${stderr_msg}"
|
|
319
|
+
else
|
|
320
|
+
warn "curl failed with exit code ${curl_exit} (no stderr output)"
|
|
321
|
+
fi
|
|
322
|
+
return 1
|
|
323
|
+
fi
|
|
324
|
+
rm -f "$curl_stderr"
|
|
247
325
|
|
|
248
326
|
if [[ "$http_code" == "200" ]] || [[ "$http_code" == "302" ]]; then
|
|
249
327
|
local size
|
|
@@ -297,12 +375,38 @@ add_to_shell_rc() {
|
|
|
297
375
|
return
|
|
298
376
|
fi
|
|
299
377
|
|
|
378
|
+
# Ensure the file ends with a newline before appending, so the new
|
|
379
|
+
# content isn't glued to the last existing line.
|
|
380
|
+
if [[ -s "$rc_file" ]]; then
|
|
381
|
+
local last_byte
|
|
382
|
+
last_byte=$(tail -c1 "$rc_file" 2>/dev/null || true)
|
|
383
|
+
if [[ -n "$last_byte" ]]; then
|
|
384
|
+
echo "" >> "$rc_file"
|
|
385
|
+
fi
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# Use a lock file to prevent concurrent append races (best-effort).
|
|
389
|
+
local lock_file="${rc_file}.browser4-install.lock"
|
|
390
|
+
local lock_fd=9
|
|
391
|
+
# Wait up to 10 seconds for another install process to release the lock.
|
|
392
|
+
local waited=0
|
|
393
|
+
while ! (umask 0002 && command -v flock >/dev/null 2>&1 && flock -n 9 2>/dev/null); do
|
|
394
|
+
if [[ $waited -ge 10 ]]; then
|
|
395
|
+
warn "Could not acquire lock on ${rc_file} after 10s; appending anyway"
|
|
396
|
+
break
|
|
397
|
+
fi
|
|
398
|
+
sleep 0.5
|
|
399
|
+
waited=$((waited + 1))
|
|
400
|
+
done 9>"$lock_file"
|
|
401
|
+
|
|
300
402
|
{
|
|
301
|
-
echo ""
|
|
302
403
|
echo "# browser4-cli"
|
|
303
404
|
echo "export PATH=\"$dir:\$PATH\""
|
|
304
405
|
} >> "$rc_file"
|
|
305
406
|
|
|
407
|
+
# Clean up (flock auto-releases when fd 9 is closed)
|
|
408
|
+
rm -f "$lock_file" 2>/dev/null || true
|
|
409
|
+
|
|
306
410
|
ok "Added to PATH in $rc_file"
|
|
307
411
|
say " Reload with: source $rc_file"
|
|
308
412
|
}
|
|
@@ -361,6 +465,14 @@ main() {
|
|
|
361
465
|
check_commands
|
|
362
466
|
header
|
|
363
467
|
|
|
468
|
+
# Auto-detect China mainland locale when no explicit source is given
|
|
469
|
+
if [[ -z "$SOURCE" ]]; then
|
|
470
|
+
if detect_china_locale; then
|
|
471
|
+
CHINA_DETECTED=true
|
|
472
|
+
step "China mainland locale detected: preferring Aliyun OSS mirror."
|
|
473
|
+
fi
|
|
474
|
+
fi
|
|
475
|
+
|
|
364
476
|
# Detect platform
|
|
365
477
|
local platform_key binary_name
|
|
366
478
|
platform_key=$(get_platform_key)
|
package/scripts/postinstall.js
CHANGED
|
@@ -45,9 +45,65 @@ const packageJson = JSON.parse(
|
|
|
45
45
|
const version = packageJson.version;
|
|
46
46
|
const commandNames = Object.keys(packageJson.bin || { 'browser4-cli': './bin/browser4-cli.js' });
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// China mainland locale detection (zero-network — uses only local env / Intl)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
function isChinaLocale() {
|
|
53
|
+
// 1 — Locale env vars (Unix, Git Bash, WSL, Docker)
|
|
54
|
+
const lang = process.env.LC_ALL
|
|
55
|
+
|| process.env.LANG
|
|
56
|
+
|| process.env.LC_CTYPE
|
|
57
|
+
|| process.env.LC_MESSAGES
|
|
58
|
+
|| '';
|
|
59
|
+
if (/^(?:zh_CN|zh-CN|Chinese \(Simplified\)_China)/i.test(lang)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2 — TZ env var (Docker, CI)
|
|
64
|
+
const tz = process.env.TZ || '';
|
|
65
|
+
if (/^Asia\/(Shanghai|Chongqing|Urumqi|Harbin)$/.test(tz)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3 — Intl timezone API (V8/Node.js, no filesystem access needed)
|
|
70
|
+
try {
|
|
71
|
+
const resolvedTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
72
|
+
if (/^Asia\/(Shanghai|Chongqing|Urumqi|Harbin)$/.test(resolvedTz)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// Intl not available (very old Node) — skip
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Download URLs (locale-aware ordering)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
49
86
|
const GITHUB_REPO = 'platonai/Browser4';
|
|
50
|
-
const
|
|
87
|
+
const DOWNLOAD_URL_GITHUB = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${binaryName}`;
|
|
88
|
+
const DOWNLOAD_URL_OSS = `https://browser4.oss-cn-beijing.aliyuncs.com/releases/download/v${version}/${binaryName}`;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Return download URLs ordered by locale preference. `BROWSER4_RELEASES_BASE_URL`
|
|
92
|
+
* takes precedence; otherwise China mainland users get OSS-first ordering.
|
|
93
|
+
*/
|
|
94
|
+
function getDownloadUrls() {
|
|
95
|
+
// Single-source override
|
|
96
|
+
const envOverride = (process.env.BROWSER4_RELEASES_BASE_URL || '').trim();
|
|
97
|
+
if (envOverride) {
|
|
98
|
+
const base = envOverride.replace(/\/+$/, '');
|
|
99
|
+
return [`${base}/download/v${version}/${binaryName}`];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isChinaLocale()) {
|
|
103
|
+
return [DOWNLOAD_URL_OSS, DOWNLOAD_URL_GITHUB];
|
|
104
|
+
}
|
|
105
|
+
return [DOWNLOAD_URL_GITHUB, DOWNLOAD_URL_OSS];
|
|
106
|
+
}
|
|
51
107
|
|
|
52
108
|
async function downloadFile(url, dest) {
|
|
53
109
|
return new Promise((resolve, reject) => {
|
|
@@ -130,19 +186,31 @@ async function main() {
|
|
|
130
186
|
}
|
|
131
187
|
|
|
132
188
|
console.log(`Downloading native binary for ${platformKey}...`);
|
|
133
|
-
console.log(`URL: ${DOWNLOAD_URL}`);
|
|
134
189
|
|
|
135
|
-
|
|
136
|
-
await downloadFile(DOWNLOAD_URL, binaryPath);
|
|
190
|
+
const urls = getDownloadUrls();
|
|
137
191
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
let lastError = null;
|
|
193
|
+
for (const url of urls) {
|
|
194
|
+
console.log(`URL: ${url}`);
|
|
195
|
+
try {
|
|
196
|
+
await downloadFile(url, binaryPath);
|
|
197
|
+
|
|
198
|
+
// Make executable on Unix
|
|
199
|
+
if (platform() !== 'win32') {
|
|
200
|
+
chmodSync(binaryPath, 0o755);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`✓ Downloaded native binary: ${binaryName}`);
|
|
204
|
+
lastError = null;
|
|
205
|
+
break;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.log(`Download failed: ${err.message}`);
|
|
208
|
+
lastError = err;
|
|
141
209
|
}
|
|
210
|
+
}
|
|
142
211
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.log(`Could not download native binary: ${err.message}`);
|
|
212
|
+
if (lastError) {
|
|
213
|
+
console.log(`Could not download native binary from any mirror: ${lastError.message}`);
|
|
146
214
|
console.log('');
|
|
147
215
|
console.log('To build the native binary locally:');
|
|
148
216
|
console.log(' 1. Install Rust: https://rustup.rs');
|