import { INSTALLER_TECHNOLOGIES } from '../data/installerCatalog.js'; // Installer intelligence: infer the installer technology from a file name and // return recommended silent install/uninstall commands plus a detection // suggestion. Pure + table-driven, so it is fully unit-tested. Binary signature // sniffing (reading the file) is Phase 5; this works from the name/extension. function extensionOf(fileName = '') { const match = String(fileName).toLowerCase().match(/(\.[a-z0-9]+)$/); return match ? match[1] : ''; } function render(template = '', { file = '', productCode = '' } = {}) { return String(template) .split('{file}').join(file) .split('{productCode}').join(productCode || '{productCode}'); } function shape(tech, ctx) { return { id: tech.id, label: tech.label, install: render(tech.install, ctx), uninstall: render(tech.uninstall, ctx), psadtInstall: render(tech.psadtInstall, ctx), psadtUninstall: tech.psadtUninstall ? render(tech.psadtUninstall, ctx) : '', detection: tech.detection, notes: tech.notes }; } export function listInstallerTechnologies() { return INSTALLER_TECHNOLOGIES.map(({ id, label, extensions, detection, notes }) => ({ id, label, extensions, detection, notes })); } // Analyze a file name. Returns the best-guess technology, a confidence level, // and the full set of candidate technologies for that extension so the operator // can pick when the name alone is ambiguous (common for .exe). export function analyzeInstaller({ fileName = '', productCode = '' } = {}) { const ext = extensionOf(fileName); const lower = String(fileName).toLowerCase(); const ctx = { file: fileName, productCode }; const byExt = INSTALLER_TECHNOLOGIES.filter((tech) => tech.extensions.includes(ext)); if (!byExt.length) { return { fileName, extension: ext, confidence: 'unknown', primary: null, candidates: [], message: ext ? `No installer technology is known for "${ext}".` : 'Provide an installer file name with an extension.' }; } // Unique extension (.msi/.msp/.msix...) → high confidence, single technology. if (byExt.length === 1) { return { fileName, extension: ext, confidence: 'high', primary: shape(byExt[0], ctx), candidates: [shape(byExt[0], ctx)] }; } // Ambiguous (.exe): try a name hint, otherwise fall back to generic EXE. const hinted = byExt.find((tech) => (tech.hints || []).some((hint) => lower.includes(hint)) && tech.id !== 'exe'); const generic = byExt.find((tech) => tech.id === 'exe') || byExt[0]; const primary = hinted || generic; return { fileName, extension: ext, confidence: hinted ? 'medium' : 'low', primary: shape(primary, ctx), // Offer all candidates (generic last) so the UI can present a picker. candidates: [...byExt.filter((t) => t.id !== 'exe'), ...byExt.filter((t) => t.id === 'exe')].map((t) => shape(t, ctx)), message: hinted ? `Matched "${hinted.label}" from the file name.` : 'Ambiguous EXE — pick the matching technology, or confirm the silent switch with the vendor.' }; }