import fs from 'node:fs'; const html = fs.readFileSync(new URL('./index.html', import.meta.url).pathname,'utf8'); // 抽出 daemonPick 相關函式(從 DAEMON_BASE_DEFAULT 到 daemonHint 結尾) // // 🔴 2026-08-05:結尾標記本來寫死 daemonHint 的**整句文案**,於是同日改 Mac 提示語 // (zip→DMG 的步驟不同)就讓這支自測直接炸「抽不到函式區塊」,而且沒人發現。 // ⇒ 改成錨定「函式結束」這個結構,不再綁文案——文案本來就會改,測試不該為此壞掉。 const start = html.indexOf('var DAEMON_BASE_DEFAULT'); const hintAt = html.indexOf('function daemonHint', start); const endMark = '\n }'; const end = hintAt < 0 ? -1 : html.indexOf(endMark, hintAt) + endMark.length; if (start < 0 || hintAt < 0 || end < start) throw new Error('抽不到函式區塊'); const src = html.slice(start, end); const cases = [ ['Windows', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36'], ['Mac', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15'], ['iPhone', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Safari/604.1'], ['Linux', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120 Safari/537.36'], ]; let pass=0, fail=0; const chk=(l,c,extra='')=>{ if(c){console.log('PASS:',l);pass++;} else {console.log('FAIL:',l,extra);fail++;} }; for (const [name, ua] of cases) { const fn = new Function('navigator','window', src + '; return {daemonPick:daemonPick, daemonHint:daemonHint, daemonBase:daemonBase};'); const api = fn({userAgent: ua}, {}); const d = api.daemonPick(); const label = d.sure ? d.pick.label : '(兩個都給)'; const url = d.sure ? d.pick.url : d.mac.url + ' + ' + d.win.url; console.log(`\n[${name}] sure=${d.sure} → ${label}`); console.log(` url: ${url}`); if (name==='Windows') { chk('Windows 給 win zip', d.sure && d.pick.url.endsWith('ArcrunRAG-win-unsigned.zip'), d.pick&&d.pick.url); chk('Windows 另一版是 Mac', d.other && d.other.url.endsWith('ArcrunRAG-mac.dmg')); chk('Windows 話術提 藍色視窗', api.daemonHint('win').includes('仍要執行')); } if (name==='Mac') { // 2026-08-05:Mac 一律給 DMG(拖進 Applications 的標準安裝畫面),不再給 zip // ——zip 解開就是一個裸 .app,使用者會直接在「下載」資料夾雙擊執行,自更新會蓋錯位置。 chk('Mac 給 dmg(不是 zip)', d.sure && d.pick.url.endsWith('ArcrunRAG-mac.dmg')); chk('Mac 另一版是 Windows', d.other && d.other.url.endsWith('win-unsigned.zip')); chk('Mac 話術提 右鍵打開', api.daemonHint('mac').includes('右鍵')); } if (name==='iPhone' || name==='Linux') { // iPhone 含 "Mac OS X" 但不是桌機 Mac;Linux 兩者皆非 → 都該落在「不確定=兩個都給」 if (name==='Linux') chk('Linux 判不出來→兩個都給', d.sure===false); if (name==='iPhone') chk('iPhone 不該被判成 Mac(手機→兩個都給)', d.sure===false, 'sure='+d.sure); } } // 舊 key 相容 const fn2 = new Function('navigator','window', src + '; return daemonBase();'); console.log('\n[相容] daemonDownload 舊 key →', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonDownload:'https://x.dev/d/ArcrunRAG-mac-unsigned.zip'}})); chk('舊 key 推得出目錄', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonDownload:'https://x.dev/d/ArcrunRAG-mac-unsigned.zip'}})==='https://x.dev/d/'); chk('daemonBase 新 key 優先', fn2({userAgent:''},{ARCRUN_CONFIG:{daemonBase:'https://y.dev/z'}})==='https://y.dev/z/'); console.log(`\n=== ${pass} passed, ${fail} failed ===`); process.exit(fail?1:0);