Files
Twitch-VOD-Manager/scripts/smoke-test-installer.js
T
Sucukdeluxe 49033f9ff2
Windows CI / verify (push) Failing after 8s
release: publish 1.0.7 Windows shell fixes
Stabilize Start menu and desktop shortcut migration with persistent versioned icon resources, stale-registration recovery, and explicit Windows shell notifications. Keep development, packaged, and shortcut identities aligned, and add regression coverage for orphaned per-user installs, reduced-motion changelog transitions, and update-popover pointer transit.
2026-08-12 12:55:46 +02:00

89 lines
3.8 KiB
JavaScript

const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const root = path.resolve(__dirname, '..');
const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
const appGuid = '08429788-303d-53b6-a4f9-894401712c7e';
function run(command, args, options = {}) {
const result = spawnSync(command, args, {
...options,
encoding: 'utf8',
timeout: 240000,
windowsHide: true
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`${path.basename(command)} failed: ${JSON.stringify({ status: result.status, signal: result.signal, stdout: result.stdout, stderr: result.stderr })}`);
}
return result;
}
function findUninstaller(installationDirectory) {
return fs.readdirSync(installationDirectory)
.filter((name) => /^uninstall.*\.exe$/i.test(name))
.map((name) => path.join(installationDirectory, name))[0] || '';
}
function assertCleanInstallerSmokeSurface() {
const userInstallKey = `HKCU\\Software\\${appGuid}`;
const machineInstallKey = `HKLM\\SOFTWARE\\${appGuid}`;
const query = (key) => spawnSync('reg', ['query', key], { encoding: 'utf8', windowsHide: true });
const existingInstallations = [userInstallKey, machineInstallKey]
.filter((key) => query(key).status === 0);
if (existingInstallations.length > 0) {
throw new Error(`Installer smoke requires a clean Windows registration surface: ${existingInstallations.join(', ')}`);
}
}
async function waitForPathRemoval(targetPath, timeoutMs = 10000) {
const deadline = Date.now() + timeoutMs;
while (fs.existsSync(targetPath)) {
if (Date.now() >= deadline) return false;
await new Promise((resolve) => setTimeout(resolve, 100));
}
return true;
}
async function main() {
if (process.platform !== 'win32') throw new Error('Installer smoke requires Windows');
if (process.env.CI !== 'true' && process.env.TWITCH_VOD_MANAGER_INSTALLER_SMOKE !== '1') {
throw new Error('Installer smoke is restricted to CI or explicit TWITCH_VOD_MANAGER_INSTALLER_SMOKE=1 opt-in');
}
const installerPath = path.join(root, 'release', `Twitch-VOD-Manager-Setup-${packageJson.version}.exe`);
if (!fs.statSync(installerPath).isFile()) throw new Error(`Installer is missing: ${installerPath}`);
assertCleanInstallerSmokeSurface();
const smokeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'tvm-installer-'));
const installationDirectory = path.join(smokeRoot, 'app');
const executablePath = path.join(installationDirectory, `${packageJson.build.productName}.exe`);
let uninstallerPath = '';
try {
run(installerPath, ['/S', '/currentuser', `/D=${installationDirectory}`], { cwd: smokeRoot });
if (!fs.statSync(executablePath).isFile()) throw new Error(`Installed executable is missing: ${executablePath}`);
uninstallerPath = findUninstaller(installationDirectory);
if (!uninstallerPath) throw new Error('Installed uninstaller is missing');
run(process.execPath, [path.join(__dirname, 'smoke-test-packaged-launch.js')], {
cwd: root,
env: { ...process.env, PACKAGED_APP_PATH: executablePath }
});
run(uninstallerPath, ['/S'], { cwd: smokeRoot });
if (!await waitForPathRemoval(executablePath)) throw new Error('Silent uninstall left the packaged executable installed');
console.log(JSON.stringify({ failures: [], installerPath }, null, 2));
} finally {
if (uninstallerPath && fs.existsSync(uninstallerPath)) {
spawnSync(uninstallerPath, ['/S'], { cwd: smokeRoot, timeout: 240000, windowsHide: true, stdio: 'ignore' });
}
await fs.promises.rm(smokeRoot, { recursive: true, force: true, maxRetries: 10, retryDelay: 250 });
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});