2026-05-20 21:39:12 +08:00

236 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// 检测 netabrowser-cli chromium 是否就位;缺失则下载 + 解压到 packages/netabrowser-cli/chromium/win64/
// 覆盖变量:
// NETA_CHROMIUM_PATH 已就位的 chrome.exe 绝对路径(优先级最高,存在即直接复用)
// NETA_CHROMIUM_URL 自定义下载 zip 地址
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const https = require('node:https');
const http = require('node:http');
const { URL } = require('node:url');
const { spawnSync } = require('node:child_process');
const DEFAULT_URL =
'https://ai-flow.bj.bcebos.com/browers-cli/ungoogled-chromium_144.0.7559.132-1.1_windows_x64.zip?responseContentDisposition=attachment';
function log(msg) {
process.stderr.write(`[ensure-chromium] ${msg}\n`);
}
function fail(msg, code = 1) {
process.stderr.write(`[ensure-chromium] ERROR: ${msg}\n`);
process.exit(code);
}
function resolveTargetDir() {
if (process.env.NETA_CHROMIUM_PATH) {
// 用户指定了 chrome.exe 路径,目标目录 = 其上级
return path.dirname(process.env.NETA_CHROMIUM_PATH);
}
// dev 模式scripts/ → skill dir → skills/ → backend/ → packages/ → packages/netabrowser-cli/chromium/win64
return path.resolve(__dirname, '../../../../netabrowser-cli/chromium/win64');
}
function chromeExeIn(dir) {
return path.join(dir, 'chrome.exe');
}
function alreadyInstalled(dir) {
try {
const stat = fs.statSync(chromeExeIn(dir));
return stat.isFile() && stat.size > 1024 * 1024; // > 1MB 才算有效
} catch {
return false;
}
}
function httpGet(urlStr, redirectsLeft = 5) {
return new Promise((resolve, reject) => {
const url = new URL(urlStr);
const mod = url.protocol === 'http:' ? http : https;
const req = mod.get(
urlStr,
{
headers: {
'User-Agent': 'netabrowser-cli/ensure-chromium',
Accept: '*/*',
},
},
(res) => {
const status = res.statusCode || 0;
if (status >= 300 && status < 400 && res.headers.location) {
res.resume();
if (redirectsLeft <= 0) return reject(new Error('redirect loop'));
const next = new URL(res.headers.location, urlStr).toString();
log(`redirect → ${next}`);
resolve(httpGet(next, redirectsLeft - 1));
return;
}
if (status < 200 || status >= 300) {
res.resume();
return reject(new Error(`HTTP ${status} from ${urlStr}`));
}
resolve(res);
}
);
req.on('error', reject);
req.setTimeout(60_000, () => {
req.destroy(new Error('connect timeout'));
});
});
}
function humanBytes(n) {
if (!Number.isFinite(n)) return '?';
if (n > 1024 ** 3) return (n / 1024 ** 3).toFixed(2) + ' GiB';
if (n > 1024 ** 2) return (n / 1024 ** 2).toFixed(2) + ' MiB';
if (n > 1024) return (n / 1024).toFixed(2) + ' KiB';
return n + ' B';
}
async function download(url, destFile) {
log(`downloading ${url}`);
log(`${destFile}`);
const res = await httpGet(url);
const total = parseInt(String(res.headers['content-length'] || '0'), 10);
const out = fs.createWriteStream(destFile);
let received = 0;
let lastReport = Date.now();
await new Promise((resolve, reject) => {
res.on('data', (chunk) => {
received += chunk.length;
const now = Date.now();
if (now - lastReport > 2000) {
lastReport = now;
const pct = total ? ((received / total) * 100).toFixed(1) + '%' : '';
log(` ${humanBytes(received)}${total ? ' / ' + humanBytes(total) : ''} ${pct}`);
}
});
res.on('error', reject);
out.on('error', reject);
out.on('finish', resolve);
res.pipe(out);
});
log(`downloaded ${humanBytes(received)}`);
}
function extractZip(zipFile, destDir) {
fs.mkdirSync(destDir, { recursive: true });
if (process.platform === 'win32') {
// Windows直接用 PowerShell Expand-Archive避开 Git Bash 里 tar 把 C: 当主机的老问题
log(`extracting (PowerShell Expand-Archive) → ${destDir}`);
const ps = [
'-NoProfile',
'-ExecutionPolicy',
'Bypass',
'-Command',
`Expand-Archive -Path '${zipFile.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(
/'/g,
"''"
)}' -Force`,
];
const r = spawnSync('powershell.exe', ps, {
stdio: ['ignore', 'inherit', 'inherit'],
});
if (r.status === 0) return;
fail(`extract failed: powershell exit=${r.status}`);
return;
}
// 非 Windows优先 unzip缺失再回落 tarlibarchive 支持 zip
log(`extracting (unzip) → ${destDir}`);
const r1 = spawnSync('unzip', ['-q', '-o', zipFile, '-d', destDir], {
stdio: ['ignore', 'inherit', 'inherit'],
});
if (r1.status === 0) return;
log(`unzip failed (status=${r1.status}); fallback tar`);
const r2 = spawnSync('tar', ['-xf', zipFile, '-C', destDir], {
stdio: ['ignore', 'inherit', 'inherit'],
});
if (r2.status === 0) return;
fail(`extract failed: unzip exit=${r1.status}, tar exit=${r2.status}`);
}
function findChromeDir(root) {
// 压缩包里 chrome.exe 可能在 root/ 或 root/<子目录>/
const direct = chromeExeIn(root);
if (fs.existsSync(direct)) return root;
const entries = fs.readdirSync(root, { withFileTypes: true });
for (const ent of entries) {
if (!ent.isDirectory()) continue;
const sub = path.join(root, ent.name);
if (fs.existsSync(chromeExeIn(sub))) return sub;
}
return null;
}
function moveContents(srcDir, targetDir) {
fs.mkdirSync(targetDir, { recursive: true });
for (const name of fs.readdirSync(srcDir)) {
const from = path.join(srcDir, name);
const to = path.join(targetDir, name);
try {
fs.rmSync(to, { recursive: true, force: true });
} catch {}
fs.renameSync(from, to);
}
}
function rmrf(p) {
try {
fs.rmSync(p, { recursive: true, force: true });
} catch {}
}
async function main() {
const targetDir = resolveTargetDir();
if (alreadyInstalled(targetDir)) {
log(`already installed: ${chromeExeIn(targetDir)}`);
return;
}
if (process.env.NETA_CHROMIUM_PATH) {
// 用户显式指定路径但文件缺失 — 不自动下载到别处,直接报错
fail(
`NETA_CHROMIUM_PATH=${process.env.NETA_CHROMIUM_PATH} but file missing. ` +
`Unset it to trigger auto-download to the default location.`
);
}
const url = process.env.NETA_CHROMIUM_URL || DEFAULT_URL;
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-chromium-'));
const zipFile = path.join(tmpRoot, 'chromium.zip');
const extractDir = path.join(tmpRoot, 'extract');
try {
await download(url, zipFile);
extractZip(zipFile, extractDir);
const chromeDir = findChromeDir(extractDir);
if (!chromeDir) {
fail(`chrome.exe not found in extracted archive: ${extractDir}`);
}
log(`moving ${chromeDir}${targetDir}`);
moveContents(chromeDir, targetDir);
if (!alreadyInstalled(targetDir)) {
fail(`post-install check failed: ${chromeExeIn(targetDir)} missing or too small`);
}
log(`installed ok → ${chromeExeIn(targetDir)}`);
} finally {
rmrf(tmpRoot);
}
}
main().catch((err) => {
fail(err && err.stack ? err.stack : String(err));
});