import fs from 'fs'
import path from 'path'
/**
* 依案例宣告找出標準圖目錄中之孤兒圖(靜態比對宣告,與實際執行了哪些案例無關)
*
* 目錄與檔名前後綴由pathOf(lang,key)推得(圖鍵須在檔名內);目錄內符合「前綴…後綴」之檔案,若其圖鍵不是任何案例(含只比對案例)於該語系所宣告之圖鍵即為孤兒。
* 預期圖鍵:案例之語系(langs,預設全部)含該語系者之stages;未宣告stages者以案例鍵為單張圖鍵,並視以其編號(E2E-NNN)為前綴之圖鍵為該案所有(執行後才決定之多階段圖)。
* 以`_`開頭之貼圖參考片段不符前綴而自然排除。因不依賴執行紀錄,--grep/--names局部執行、案例失敗、knownDefect皆不致誤報。
* why:改名後重產只寫新名、從不刪舊名,舊圖成為無人產出亦無人比對之孤兒(PERM 2026-09-17 改版留下12張,至09-28才被發現)。
*
* @param {Object} opt 輸入設定物件
* @param {Array} opt.cases 輸入案例宣告陣列[{name,stages,langs}]或案例鍵字串
* @param {Array} opt.langs 輸入已宣告之語系陣列
* @param {Function} opt.pathOf 輸入標準圖路徑函數(lang,key)=>路徑字串,須與runBaselineCase之pathOf相同
* @param {RegExp} [opt.idPattern=/^(E2E-\d+)(?=-|$)/] 輸入自圖鍵取出案例編號之樣式,預設/^(E2E-\d+)(?=-|$)/
* @returns {Array} 回傳孤兒圖路徑陣列(依語系、檔名排序)
* @example
*
* import findOrphanBaselines from 'w-package-tools-e2e/src/findOrphanBaselines.mjs'
*
* let orphans = findOrphanBaselines({ cases, langs: ['eng', 'cht'], pathOf: (lang, key) => `./test/pics/login/login-${lang}-${key}.png` })
* console.log(orphans)
* // => [] 或 ['test\\pics\\login\\login-eng-E2E-099-old.png', ...]
*
*/
function findOrphanBaselines(opt = {}) {
let { cases, langs, pathOf, idPattern = /^(E2E-\d+)(?=-|$)/ } = opt
if (!Array.isArray(cases)) {
throw new Error('findOrphanBaselines: cases 須為陣列')
}
if (!Array.isArray(langs) || langs.length === 0) {
throw new Error('findOrphanBaselines: langs 須為非空陣列')
}
if (typeof pathOf !== 'function') {
throw new Error('findOrphanBaselines: pathOf 須為函數')
}
let decls = cases.map((c) => (typeof c === 'string' ? { name: c } : c))
let idOf = (key) => {
let m = String(key).match(idPattern)
return m ? m[1] : null
}
let MARK = '__ORPHAN_KEY_MARK__'
let out = []
for (let lang of langs) {
let tpl = String(pathOf(lang, MARK))
let base = path.basename(tpl)
let j = base.indexOf(MARK)
if (j < 0) {
throw new Error(`findOrphanBaselines: pathOf(${lang}, key) 之圖鍵須在檔名內 (實得 ${tpl})`)
}
let dir = path.dirname(tpl)
let prefix = base.slice(0, j)
let suffix = base.slice(j + MARK.length)
//預期圖鍵與開放編號
let expected = new Set()
let openIds = new Set()
for (let c of decls) {
let cl = Array.isArray(c.langs) ? c.langs : langs
if (!cl.includes(lang)) {
continue
}
if (Array.isArray(c.stages)) {
for (let s of c.stages) {
expected.add(s)
}
}
else {
expected.add(c.name)
let id = idOf(c.name)
if (id) {
openIds.add(id)
}
}
}
if (!fs.existsSync(dir)) {
continue
}
for (let f of fs.readdirSync(dir).sort()) {
if (!f.startsWith(prefix) || !f.endsWith(suffix) || f.length <= prefix.length + suffix.length) {
continue
}
let key = f.slice(prefix.length, f.length - suffix.length)
if (expected.has(key)) {
continue
}
let id = idOf(key)
if (id && openIds.has(id)) {
continue
}
out.push(path.join(dir, f))
}
}
return out
}
export default findOrphanBaselines