mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 06:06:18 -08:00
feat(updater): add GitHub releases desktop updater
Adds the GitHub Releases desktop updater with release notes, startup notification, packaging metadata, tests, and CI fixes for Electron E2E.
This commit is contained in:
1 parent
3cb228d931
commit
b1119189e2
54 files changed
+3569
-134
No files matched your search
@@ -40,6 +40,10 @@ export const defaultStalkerPortalName = 'Mock Stalker Portal';
|
||||
export const defaultXtreamUsername = 'user1';
|
||||
export const defaultXtreamPassword = 'pass1';
|
||||
export const defaultStalkerMacAddress = '00:1A:79:00:00:01';
|
||||
const electronAppCloseTimeoutMs = Number(
|
||||
process.env['IPTVNATOR_E2E_CLOSE_TIMEOUT_MS'] ?? '10000'
|
||||
);
|
||||
const electronAppKillWaitMs = 2000;
|
||||
|
||||
export type PortalProvider = 'stalker' | 'xtream';
|
||||
|
||||
@@ -153,6 +157,7 @@ export async function launchElectronApp(
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
});
|
||||
attachElectronProcessDiagnostics(electronApp);
|
||||
|
||||
const mainWindow = await findMainWindow(electronApp);
|
||||
await waitForAppReady(mainWindow);
|
||||
@@ -166,16 +171,83 @@ export async function launchElectronApp(
|
||||
};
|
||||
}
|
||||
|
||||
function attachElectronProcessDiagnostics(electronApp: ElectronApplication): void {
|
||||
if (!process.env['CI']) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childProcess = electronApp.process();
|
||||
|
||||
childProcess.stdout?.on('data', (chunk: Buffer) => {
|
||||
console.log(`[electron stdout] ${chunk.toString().trimEnd()}`);
|
||||
});
|
||||
childProcess.stderr?.on('data', (chunk: Buffer) => {
|
||||
console.error(`[electron stderr] ${chunk.toString().trimEnd()}`);
|
||||
});
|
||||
childProcess.once('exit', (code, signal) => {
|
||||
console.log(
|
||||
`[electron process exit] code=${code ?? '<null>'} signal=${
|
||||
signal ?? '<null>'
|
||||
}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function closeElectronApp(
|
||||
app: LaunchedElectronApp
|
||||
): Promise<void> {
|
||||
try {
|
||||
await app.electronApp.close();
|
||||
const closePromise = app.electronApp.close();
|
||||
const closed = await waitForPromiseWithTimeout(
|
||||
closePromise,
|
||||
electronAppCloseTimeoutMs
|
||||
);
|
||||
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`Electron app did not close within ${electronAppCloseTimeoutMs}ms; killing process`
|
||||
);
|
||||
const childProcess = app.electronApp.process();
|
||||
|
||||
if (!childProcess.killed) {
|
||||
childProcess.kill();
|
||||
}
|
||||
|
||||
await waitForPromiseWithTimeout(
|
||||
closePromise.catch(() => undefined),
|
||||
electronAppKillWaitMs
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Failed to close Electron app cleanly:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForPromiseWithTimeout(
|
||||
promise: Promise<unknown>,
|
||||
timeoutMs: number
|
||||
): Promise<boolean> {
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
try {
|
||||
return await Promise.race([
|
||||
promise.then(() => true),
|
||||
new Promise<boolean>((resolvePromise) => {
|
||||
timeoutId = setTimeout(
|
||||
() => resolvePromise(false),
|
||||
timeoutMs
|
||||
);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertPackagedRendererBuildIsElectronSafe(): void {
|
||||
if (!existsSync(packagedRendererIndexPath)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -41,6 +41,30 @@ const epgFixtureXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
`;
|
||||
|
||||
test.describe('Electron Settings', () => {
|
||||
test('@settings @electron shows manual app update fallback when self-update is unavailable', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(app.mainWindow);
|
||||
|
||||
await expect(app.mainWindow.getByTestId('app-update-status')).toBeVisible();
|
||||
await expect(app.mainWindow.getByTestId('app-update-check')).toBeVisible();
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('app-update-open-release')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('app-update-download')
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
app.mainWindow.getByTestId('app-update-install')
|
||||
).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('@settings @electron gates external MPV playback behind double-click when enabled', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
|
||||
Reference in new issue
Block a user