mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
fix: restore Linux AppImage updater routing and fallback port reporting (#397)
This commit is contained in:
@@ -12,23 +12,28 @@ const PLATFORM_PATTERNS = {
|
||||
'linux-appimage': (name) => name.endsWith('_amd64.AppImage'),
|
||||
};
|
||||
|
||||
const VARIANT_PREFIXES = {
|
||||
full: ['world-monitor'],
|
||||
world: ['world-monitor'],
|
||||
tech: ['tech-monitor'],
|
||||
finance: ['finance-monitor'],
|
||||
const VARIANT_IDENTIFIERS = {
|
||||
full: ['worldmonitor'],
|
||||
world: ['worldmonitor'],
|
||||
tech: ['techmonitor'],
|
||||
finance: ['financemonitor'],
|
||||
};
|
||||
|
||||
function canonicalAssetName(name) {
|
||||
return String(name || '').toLowerCase().replace(/[^a-z0-9]+/g, '');
|
||||
}
|
||||
|
||||
function findAssetForVariant(assets, variant, platformMatcher) {
|
||||
const prefixes = VARIANT_PREFIXES[variant] ?? null;
|
||||
if (!prefixes) return null;
|
||||
const identifiers = VARIANT_IDENTIFIERS[variant] ?? null;
|
||||
if (!identifiers) return null;
|
||||
|
||||
return assets.find((asset) => {
|
||||
const assetName = String(asset?.name || '').toLowerCase();
|
||||
const hasVariantPrefix = prefixes.some((prefix) =>
|
||||
assetName.startsWith(`${prefix.toLowerCase()}_`) || assetName.startsWith(`${prefix.toLowerCase()}-`)
|
||||
const assetName = String(asset?.name || '');
|
||||
const normalizedAssetName = canonicalAssetName(assetName);
|
||||
const hasVariantIdentifier = identifiers.some((identifier) =>
|
||||
normalizedAssetName.includes(identifier)
|
||||
);
|
||||
return hasVariantPrefix && platformMatcher(String(asset?.name || ''));
|
||||
return hasVariantIdentifier && platformMatcher(assetName);
|
||||
}) ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1245,6 +1245,7 @@ export async function createLocalApiServer(options = {}) {
|
||||
|
||||
const address = server.address();
|
||||
const boundPort = typeof address === 'object' && address?.port ? address.port : context.port;
|
||||
context.port = boundPort;
|
||||
|
||||
const portFile = process.env.LOCAL_API_PORT_FILE;
|
||||
if (portFile) {
|
||||
|
||||
@@ -1347,3 +1347,37 @@ test('traffic log strips query strings from entries to protect privacy', async (
|
||||
await localApi.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('service-status reports bound fallback port after EADDRINUSE recovery', async () => {
|
||||
const blocker = createServer((_req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/plain' });
|
||||
res.end('occupied');
|
||||
});
|
||||
await listen(blocker, '127.0.0.1', 46123);
|
||||
|
||||
const localApi = await setupApiDir({});
|
||||
const app = await createLocalApiServer({
|
||||
port: 46123,
|
||||
apiDir: localApi.apiDir,
|
||||
logger: { log() {}, warn() {}, error() {} },
|
||||
});
|
||||
const { port } = await app.start();
|
||||
|
||||
try {
|
||||
assert.notEqual(port, 46123);
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/service-status`);
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
|
||||
assert.equal(body.local.port, port);
|
||||
const localService = body.services.find((service) => service.id === 'local-api');
|
||||
assert.equal(localService.description, `Running on 127.0.0.1:${port}`);
|
||||
} finally {
|
||||
await app.close();
|
||||
await localApi.cleanup();
|
||||
await new Promise((resolve, reject) => {
|
||||
blocker.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,6 +133,10 @@ export class DesktopUpdater implements AppModule {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (normalizedOs === 'linux') {
|
||||
return normalizedArch === 'x86_64' ? 'linux-appimage' : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
82
tests/download-handler.test.mjs
Normal file
82
tests/download-handler.test.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import test from 'node:test';
|
||||
import handler from '../api/download.js';
|
||||
|
||||
const RELEASES_PAGE = 'https://github.com/koala73/worldmonitor/releases/latest';
|
||||
|
||||
function makeGitHubReleaseResponse(assets) {
|
||||
return new Response(JSON.stringify({ assets }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
test('matches full variant for dotted World.Monitor AppImage asset names', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => makeGitHubReleaseResponse([
|
||||
{
|
||||
name: 'World.Monitor_2.5.7_amd64.AppImage',
|
||||
browser_download_url: 'https://downloads.example/World.Monitor_2.5.7_amd64.AppImage',
|
||||
},
|
||||
]);
|
||||
|
||||
try {
|
||||
const response = await handler(
|
||||
new Request('https://worldmonitor.app/api/download?platform=linux-appimage&variant=full')
|
||||
);
|
||||
assert.equal(response.status, 302);
|
||||
assert.equal(
|
||||
response.headers.get('location'),
|
||||
'https://downloads.example/World.Monitor_2.5.7_amd64.AppImage'
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('matches tech variant for dashed Tech-Monitor AppImage asset names', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => makeGitHubReleaseResponse([
|
||||
{
|
||||
name: 'Tech-Monitor_2.5.7_amd64.AppImage',
|
||||
browser_download_url: 'https://downloads.example/Tech-Monitor_2.5.7_amd64.AppImage',
|
||||
},
|
||||
{
|
||||
name: 'World.Monitor_2.5.7_amd64.AppImage',
|
||||
browser_download_url: 'https://downloads.example/World.Monitor_2.5.7_amd64.AppImage',
|
||||
},
|
||||
]);
|
||||
|
||||
try {
|
||||
const response = await handler(
|
||||
new Request('https://worldmonitor.app/api/download?platform=linux-appimage&variant=tech')
|
||||
);
|
||||
assert.equal(response.status, 302);
|
||||
assert.equal(
|
||||
response.headers.get('location'),
|
||||
'https://downloads.example/Tech-Monitor_2.5.7_amd64.AppImage'
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('falls back to release page when requested variant has no matching asset', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => makeGitHubReleaseResponse([
|
||||
{
|
||||
name: 'World.Monitor_2.5.7_amd64.AppImage',
|
||||
browser_download_url: 'https://downloads.example/World.Monitor_2.5.7_amd64.AppImage',
|
||||
},
|
||||
]);
|
||||
|
||||
try {
|
||||
const response = await handler(
|
||||
new Request('https://worldmonitor.app/api/download?platform=linux-appimage&variant=finance')
|
||||
);
|
||||
assert.equal(response.status, 302);
|
||||
assert.equal(response.headers.get('location'), RELEASES_PAGE);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user