mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: add in-app update flow and native e2e verification (#1112)
* feat(app-update): add in-app update flow with native e2e coverage * docs(ai-workflow): infer closed-issue labels automatically * fix(app-update): address native updater review findings * fix(android): validate updater redirect hosts * fix(electron): harden updater file names
This commit is contained in:
@@ -3,6 +3,7 @@ import http from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
||||
@@ -179,7 +180,19 @@ const startFixtureServer = async () => {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Cache-Control': 'no-store',
|
||||
});
|
||||
fs.createReadStream(matchedAsset.filePath).pipe(response);
|
||||
try {
|
||||
await pipeline(fs.createReadStream(matchedAsset.filePath), response);
|
||||
} catch (error) {
|
||||
logStep(`fixture asset stream failed for ${matchedAsset.filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
if (!response.headersSent) {
|
||||
response.writeHead(500, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
});
|
||||
}
|
||||
if (!response.writableEnded) {
|
||||
response.end('asset stream failed');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,6 +212,8 @@ const startFixtureServer = async () => {
|
||||
throw new Error('Could not resolve fixture server port');
|
||||
}
|
||||
|
||||
logStep(`fixture server listening at http://127.0.0.1:${address.port} (android emulator: http://10.0.2.2:${address.port})`);
|
||||
|
||||
return {
|
||||
port: address.port,
|
||||
setRelease(version, assets) {
|
||||
|
||||
@@ -233,6 +233,7 @@ const main = async () => {
|
||||
} finally {
|
||||
await sleep(1000);
|
||||
await fixtureServer.close();
|
||||
await fs.promises.rm(workspace, { recursive: true, force: true }).catch(() => undefined);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { copyPath, createTempWorkspace, findFirstMatchingPath, logStep, repoRoot
|
||||
const OLD_VERSION = '0.7.1';
|
||||
const NEW_VERSION = '0.7.3';
|
||||
const SETTINGS_HASH = '#/all/settings#interface-settings';
|
||||
const MAC_ARCH = process.arch === 'x64' ? 'x64' : 'arm64';
|
||||
|
||||
const findPackagedMacApp = async () => {
|
||||
const outDirectory = path.join(repoRoot, 'out');
|
||||
@@ -57,7 +58,7 @@ const main = async () => {
|
||||
fixturePort: fixtureServer.port,
|
||||
});
|
||||
|
||||
const zippedNewAppPath = path.join(workspace, `5chan-darwin-arm64-v${NEW_VERSION}.zip`);
|
||||
const zippedNewAppPath = path.join(workspace, `5chan-darwin-${MAC_ARCH}-v${NEW_VERSION}.zip`);
|
||||
await zipMacApp(packagedNewAppPath, zippedNewAppPath);
|
||||
|
||||
fixtureServer.setRelease(NEW_VERSION, [
|
||||
@@ -69,6 +70,7 @@ const main = async () => {
|
||||
|
||||
const versionMetadataPath = path.join(installedAppPath, 'Contents', 'Resources', 'app', 'build', 'version.json');
|
||||
const sandboxHome = path.join(workspace, 'home');
|
||||
const updaterDebugLogPath = path.join(workspace, 'app-updater.log');
|
||||
const plebbitDataPath = path.join(sandboxHome, 'Library', 'Application Support', 'plebbit');
|
||||
await fs.promises.mkdir(plebbitDataPath, { recursive: true });
|
||||
await fs.promises.writeFile(path.join(plebbitDataPath, 'auth-key'), 'e2e-auth-key', 'utf8');
|
||||
@@ -78,11 +80,17 @@ const main = async () => {
|
||||
env: {
|
||||
...process.env,
|
||||
APP_UPDATE_ALLOWED_DOWNLOAD_HOSTS: '127.0.0.1',
|
||||
APP_UPDATE_DEBUG: '1',
|
||||
APP_UPDATE_DEBUG_LOG_PATH: updaterDebugLogPath,
|
||||
HOME: sandboxHome,
|
||||
},
|
||||
});
|
||||
|
||||
const firstWindow = await electronApp.firstWindow();
|
||||
firstWindow.on('dialog', async (dialog) => {
|
||||
logStep(`electron app dialog: ${dialog.message()}`);
|
||||
await dialog.dismiss();
|
||||
});
|
||||
await firstWindow.waitForLoadState('domcontentloaded');
|
||||
await firstWindow.getByRole('button', { name: 'Check' }).waitFor({
|
||||
timeout: 120000,
|
||||
@@ -94,7 +102,9 @@ const main = async () => {
|
||||
await firstWindow.getByText(`v${NEW_VERSION}`).waitFor({
|
||||
timeout: 120000,
|
||||
});
|
||||
await firstWindow.getByRole('button', { name: 'Download' }).click();
|
||||
await firstWindow.getByRole('button', { name: 'Download' }).evaluate((button) => {
|
||||
button.click();
|
||||
});
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
@@ -130,6 +140,7 @@ const main = async () => {
|
||||
} finally {
|
||||
await sleep(2000);
|
||||
await fixtureServer.close();
|
||||
await fs.promises.rm(workspace, { recursive: true, force: true }).catch(() => undefined);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user