'use strict'; const path = require('path'); const { listReleaseAttachmentNames } = require('./github'); /** Legacy launcher.json rows — ignored when merging explicit files. */ const DEPRECATED_SOURCES = new Set(['patch-Z.MPQ', 'Wow-patched.exe']); function filterExplicitFiles(files) { if (!Array.isArray(files)) return []; return files .filter((e) => e && String(e.source || '').trim()) .filter((e) => !DEPRECATED_SOURCES.has(String(e.source).trim())) .map((e) => ({ source: String(e.source).trim(), dest: String(e.dest || '').trim(), backup: e.backup !== false, from_release: e.from_release !== false, })) .filter((e) => e.dest); } function manifestLooksUsable(m) { return !!(m && m.files && typeof m.files === 'object' && Object.keys(m.files).length > 0); } /** Launcher / updater attachments — never copied into the WoW folder. */ function isExcludedFromGameSync(fileName) { const n = String(fileName || ''); const lower = n.toLowerCase(); if (lower === 'patch-manifest.json') return true; if (/^fractured-launcher/i.test(n)) return true; if (/\.blockmap$/i.test(n)) return true; if (/^latest.*\.ya?ml$/i.test(n) || lower === 'latest.yml') return true; if (lower.includes('builder-debug')) return true; if (/\.appimage$/i.test(n)) return true; return false; } function destForReleaseSource(source, cfg) { const base = path.basename(String(source || '')); if (/\.mpq$/i.test(base)) return `Data/${base}`; if (/\.exe$/i.test(base)) return (cfg.launch && cfg.launch.exe) || 'Wow.exe'; return base; } /** * Explicit `files` in config wins. Otherwise use patch-manifest keys if present, * else discover attachments on the release (excluding launcher artifacts). */ async function buildResolvedReleaseFiles(cfg, manifestMaybeNull) { const explicit = filterExplicitFiles(cfg.files); if (explicit.length) return explicit; const manifest = manifestMaybeNull; if (manifestLooksUsable(manifest)) { const keys = Object.keys(manifest.files).filter((k) => k && !isExcludedFromGameSync(k)); if (!keys.length) { throw new Error('patch-manifest.json has no file entries — add files or attach assets to the release.'); } return keys.map((source) => ({ source, dest: destForReleaseSource(source, cfg), backup: true, from_release: true, })); } const names = await listReleaseAttachmentNames(cfg); const game = names.filter((n) => n && !isExcludedFromGameSync(n)); if (!game.length) { throw new Error( 'No patch files on this release (after excluding launcher installers). ' + 'Attach MPQ/exe assets or ship patch-manifest.json listing filenames.' ); } const exes = game.filter((n) => /\.exe$/i.test(n)); const mpqs = game.filter((n) => /\.mpq$/i.test(n)); const rest = game.filter((n) => !/\.(exe|mpq)$/i.test(n)); if (exes.length > 1) { throw new Error( `Release has multiple .exe files (${exes.join(', ')}). ` + 'Remove extras or publish patch-manifest.json with the exact filenames to install.' ); } const out = []; for (const n of mpqs) { out.push({ source: n, dest: path.posix.join('Data', path.basename(n)), backup: true, from_release: true, }); } if (exes.length === 1) { out.push({ source: exes[0], dest: (cfg.launch && cfg.launch.exe) || 'Wow.exe', backup: true, from_release: true, }); } for (const n of rest) { out.push({ source: n, dest: path.basename(n), backup: true, from_release: true, }); } return out; } module.exports = { buildResolvedReleaseFiles, filterExplicitFiles, isExcludedFromGameSync, DEPRECATED_SOURCES, };