mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 05:16:19 -07:00
feat(launcher): bundle Bun and use it across all launchers (#243)
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { runUpdateCommand } from './update-command';
|
||||
import type { LauncherCommandContext } from './context';
|
||||
|
||||
@@ -62,6 +66,24 @@ test('runUpdateCommand updates directly on Linux without launching Electron', as
|
||||
]);
|
||||
});
|
||||
|
||||
test('runUpdateCommand sends symlinked AUR installs to the package helper without network access', async () => {
|
||||
const calls: string[] = [];
|
||||
const handled = await runUpdateCommand(makeContext({ appPath: '/usr/bin/SubMiner.AppImage' }), {
|
||||
resolveRealPath: () => '/opt/SubMiner/SubMiner.AppImage',
|
||||
runDirectReleaseUpdate: async () => {
|
||||
throw new Error('must not check GitHub releases for an AUR install');
|
||||
},
|
||||
log: (level, _configured, message) => {
|
||||
calls.push(`${level}:${message}`);
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(calls, [
|
||||
'warn:SubMiner is installed through subminer-bin. Update it with your AUR helper, for example: yay -S subminer-bin.',
|
||||
]);
|
||||
});
|
||||
|
||||
test('runUpdateCommand skips Linux asset replacement when release is not newer', async () => {
|
||||
const calls: string[] = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
@@ -118,6 +140,65 @@ test('runUpdateCommand skips Linux asset replacement when release is not newer',
|
||||
}
|
||||
});
|
||||
|
||||
test('Linux update does not replace the launcher after an AppImage hash failure', async () => {
|
||||
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-update-order-'));
|
||||
const appImagePath = path.join(workspace, 'SubMiner.AppImage');
|
||||
const launcherPath = path.join(workspace, 'subminer');
|
||||
fs.writeFileSync(appImagePath, 'old app');
|
||||
fs.writeFileSync(launcherPath, '#!/bin/sh\n# SubMiner launcher\n');
|
||||
|
||||
const fetched: string[] = [];
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async (input: string | URL | Request) => {
|
||||
const url = input instanceof Request ? input.url : String(input);
|
||||
fetched.push(url);
|
||||
if (url.endsWith('/releases')) {
|
||||
return Response.json([
|
||||
{
|
||||
tag_name: 'v999.0.0',
|
||||
prerelease: false,
|
||||
draft: false,
|
||||
assets: [
|
||||
{
|
||||
name: 'SHA256SUMS.txt',
|
||||
browser_download_url: 'https://example.test/SHA256SUMS.txt',
|
||||
},
|
||||
{
|
||||
name: 'SubMiner.AppImage',
|
||||
browser_download_url: 'https://example.test/SubMiner.AppImage',
|
||||
},
|
||||
{ name: 'subminer', browser_download_url: 'https://example.test/subminer' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (url.endsWith('/SHA256SUMS.txt')) {
|
||||
return new Response(
|
||||
`${createHash('sha256').update('expected app').digest('hex')} SubMiner.AppImage\n${createHash('sha256').update('new launcher').digest('hex')} subminer\n`,
|
||||
);
|
||||
}
|
||||
if (url.endsWith('/SubMiner.AppImage')) {
|
||||
return new Response('corrupt app');
|
||||
}
|
||||
throw new Error(`launcher asset should not be fetched: ${url}`);
|
||||
}) as typeof globalThis.fetch;
|
||||
|
||||
try {
|
||||
const handled = await runUpdateCommand(
|
||||
makeContext({ appPath: appImagePath, scriptPath: launcherPath }),
|
||||
{ readMainConfig: () => null, log: () => {} },
|
||||
);
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.equal(fs.readFileSync(appImagePath, 'utf8'), 'old app');
|
||||
assert.equal(fs.readFileSync(launcherPath, 'utf8'), '#!/bin/sh\n# SubMiner launcher\n');
|
||||
assert.equal(fetched.includes('https://example.test/subminer'), false);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('runUpdateCommand keeps app-mediated update path on non-Linux', async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
@@ -148,3 +229,32 @@ test('runUpdateCommand keeps app-mediated update path on non-Linux', async () =>
|
||||
'remove:/tmp/subminer-update-test',
|
||||
]);
|
||||
});
|
||||
|
||||
test('managed launcher passes its wrapper to app updates, protecting signed resources', async () => {
|
||||
const previous = process.env.SUBMINER_LAUNCHER_PATH;
|
||||
process.env.SUBMINER_LAUNCHER_PATH = '/Users/tester/.local/bin/subminer';
|
||||
try {
|
||||
let forwarded: string[] = [];
|
||||
await runUpdateCommand(
|
||||
makeContext({
|
||||
processAdapter: { ...makeContext().processAdapter, platform: () => 'darwin' },
|
||||
scriptPath: '/Applications/SubMiner.app/Contents/Resources/launcher/subminer',
|
||||
appPath: '/Applications/SubMiner.app/Contents/MacOS/SubMiner',
|
||||
}),
|
||||
{
|
||||
createTempDir: () => '/tmp/subminer-update-test',
|
||||
joinPath: (...parts) => parts.join('/'),
|
||||
runAppCommandCaptureOutput: (_app, args) => {
|
||||
forwarded = args;
|
||||
return { status: 0, stdout: '', stderr: '' };
|
||||
},
|
||||
waitForUpdateResponse: async () => ({ ok: true }),
|
||||
removeDir: () => {},
|
||||
},
|
||||
);
|
||||
assert.equal(forwarded[2], '/Users/tester/.local/bin/subminer');
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.SUBMINER_LAUNCHER_PATH;
|
||||
else process.env.SUBMINER_LAUNCHER_PATH = previous;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user