/** * 下载 Chrome for Testing(Stable / win64)完整目录到 resources/chrome-cft/chrome-win64/, * 与 chrome.exe 同级的 DLL 等依赖一并保留,随安装包分发,开箱即用。 * 设置 SKIP_CHROME=1 可跳过(CI 无网环境)。 */ import fs from 'fs' import https from 'https' import path from 'path' import { fileURLToPath } from 'url' import AdmZip from 'adm-zip' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const root = path.resolve(__dirname, '..') const resDir = path.join(root, 'resources', 'chrome-cft') const CFT_JSON_URL = 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json' const PLATFORM_KEY = 'win64' const markerName = '.chrome_cft_bundle_ok' if (process.env.SKIP_CHROME === '1') { console.log('[chrome-cft] SKIP_CHROME=1,跳过下载') process.exit(0) } function downloadBuffer(url) { return new Promise((resolve, reject) => { https .get(url, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { downloadBuffer(res.headers.location).then(resolve).catch(reject) return } if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode} ${url}`)) return } const chunks = [] res.on('data', (c) => chunks.push(c)) res.on('end', () => resolve(Buffer.concat(chunks))) res.on('error', reject) }) .on('error', reject) }) } function downloadToFile(url, dest) { return new Promise((resolve, reject) => { const file = fs.createWriteStream(dest) https .get(url, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { file.close() try { fs.unlinkSync(dest) } catch { /* ignore */ } downloadToFile(res.headers.location, dest).then(resolve).catch(reject) return } if (res.statusCode !== 200) { file.close() reject(new Error(`HTTP ${res.statusCode}`)) return } res.pipe(file) file.on('finish', () => { file.close() resolve() }) }) .on('error', (e) => { try { file.close() } catch { /* ignore */ } reject(e) }) }) } const chromeExe = path.join(resDir, 'chrome-win64', 'chrome.exe') const markerPath = path.join(resDir, markerName) fs.mkdirSync(resDir, { recursive: true }) if (fs.existsSync(chromeExe) && fs.existsSync(markerPath)) { console.log('[chrome-cft] 已存在完整包,跳过:', chromeExe) process.exit(0) } console.log('[chrome-cft] 获取元数据:', CFT_JSON_URL) const metaBuf = await downloadBuffer(CFT_JSON_URL) const doc = JSON.parse(metaBuf.toString('utf-8')) const stable = doc.channels?.Stable const list = stable?.downloads?.chrome ?? [] const hit = list.find((x) => x.platform === PLATFORM_KEY) if (!hit?.url) { console.error('[chrome-cft] 元数据中无', PLATFORM_KEY) process.exit(1) } const zipPath = path.join(resDir, '_chrome-download.zip') console.log('[chrome-cft] 下载(体积较大):', hit.url) await downloadToFile(hit.url, zipPath) console.log('[chrome-cft] 解压中…') const zip = new AdmZip(zipPath) const tmp = path.join(resDir, '_extract_tmp') if (fs.existsSync(tmp)) fs.rmSync(tmp, { recursive: true }) fs.mkdirSync(tmp, { recursive: true }) zip.extractAllTo(tmp, true) const inner = path.join(tmp, 'chrome-win64') if (!fs.existsSync(path.join(inner, 'chrome.exe'))) { console.error('[chrome-cft] zip 内未找到 chrome-win64/chrome.exe') process.exit(1) } const destDir = path.join(resDir, 'chrome-win64') if (fs.existsSync(destDir)) fs.rmSync(destDir, { recursive: true }) fs.cpSync(inner, destDir, { recursive: true }) fs.rmSync(tmp, { recursive: true }) try { fs.unlinkSync(zipPath) } catch { /* ignore */ } fs.writeFileSync( path.join(resDir, 'NOTICE-ChromeForTesting.txt'), [ '本目录包含 Chrome for Testing(Stable / Windows x64)。', '清单来源:' + CFT_JSON_URL, '与 chrome.exe 同目录文件均为官方 zip 原样复制,未修改二进制。', '', ].join('\n'), 'utf-8', ) fs.writeFileSync(markerPath, `${stable.version}\n${new Date().toISOString()}\n`, 'utf-8') console.log('[chrome-cft] 完成:', chromeExe)