mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-21 17:16:20 -07:00
feat(launcher): bundle Bun and use it across all launchers (#243)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import test from 'node:test';
|
||||
import { posixLauncherBootstrapContent, shellQuote } from './posix-launcher-bootstrap';
|
||||
|
||||
test('downloaded launcher prepares once, survives unmount, and refreshes after app replacement', (t) => {
|
||||
if (process.platform !== 'linux') return;
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "subminer bootstrap's "));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const appPath = path.join(root, 'SubMiner.AppImage');
|
||||
const resources = path.join(root, 'mount', 'resources');
|
||||
fs.mkdirSync(path.join(resources, 'launcher'), { recursive: true });
|
||||
fs.mkdirSync(path.join(resources, 'bun', 'licenses'), { recursive: true });
|
||||
fs.symlinkSync(process.execPath, path.join(resources, 'bun', 'bun'));
|
||||
fs.writeFileSync(path.join(resources, 'bun', 'licenses', 'notice'), 'Bun notice');
|
||||
fs.writeFileSync(path.join(resources, 'launcher', 'version'), '1.0.0\n');
|
||||
fs.writeFileSync(
|
||||
path.join(resources, 'launcher', 'prepare.cjs'),
|
||||
`exports.prepareLauncherRuntime = require(${JSON.stringify(path.join(__dirname, 'prepare-launcher-runtime.ts'))}).prepareLauncherRuntime;`,
|
||||
);
|
||||
const script = (version: number) =>
|
||||
`console.log(JSON.stringify({version:${version},args:process.argv.slice(2)}));`;
|
||||
fs.writeFileSync(path.join(resources, 'launcher', 'subminer.js'), script(1));
|
||||
const prepares = path.join(root, 'preparations');
|
||||
const appContent = `#!/bin/sh\necho prepared >> ${shellQuote(prepares)}\nexport APPDIR=${shellQuote(path.dirname(resources))}\nexec ${shellQuote(process.execPath)} "$@"\n`;
|
||||
fs.writeFileSync(appPath, appContent, { mode: 0o755 });
|
||||
const wrapper = path.join(root, 'subminer');
|
||||
fs.writeFileSync(wrapper, posixLauncherBootstrapContent(), { mode: 0o755 });
|
||||
const env = { HOME: root, PATH: '', SUBMINER_BINARY_PATH: appPath };
|
||||
const args = ['space here', "a'b", 'a&b', '$(touch nope)', '日本語'];
|
||||
const run = (extraEnv = {}) =>
|
||||
spawnSync(wrapper, args, { env: { ...env, ...extraEnv }, encoding: 'utf8' });
|
||||
const first = run();
|
||||
assert.equal(first.status, 0, first.stderr);
|
||||
assert.deepEqual(JSON.parse(first.stdout), { version: 1, args });
|
||||
const cache = path.join(root, '.local', 'share', 'SubMiner', 'launcher');
|
||||
assert.equal(fs.readFileSync(path.join(cache, 'licenses', 'notice'), 'utf8'), 'Bun notice');
|
||||
|
||||
fs.renameSync(resources, `${resources}.unmounted`);
|
||||
const warm = run({ SUBMINER_BINARY_PATH: '' }); // Finds the app recorded by preparation.
|
||||
assert.equal(warm.status, 0, warm.stderr);
|
||||
assert.deepEqual(JSON.parse(warm.stdout), { version: 1, args });
|
||||
assert.equal(fs.readFileSync(prepares, 'utf8'), 'prepared\n');
|
||||
fs.renameSync(`${resources}.unmounted`, resources);
|
||||
|
||||
// Same version and path, new inode: manual replacement must still refresh.
|
||||
fs.writeFileSync(`${appPath}.new`, appContent, { mode: 0o755 });
|
||||
fs.renameSync(`${appPath}.new`, appPath);
|
||||
fs.writeFileSync(path.join(resources, 'launcher', 'subminer.js'), script(2));
|
||||
const updated = run();
|
||||
assert.equal(updated.status, 0, updated.stderr);
|
||||
assert.deepEqual(JSON.parse(updated.stdout), { version: 2, args });
|
||||
assert.equal(fs.readFileSync(prepares, 'utf8'), 'prepared\nprepared\n');
|
||||
|
||||
fs.unlinkSync(path.join(cache, 'bun'));
|
||||
assert.equal(run().status, 0);
|
||||
assert.equal(fs.readFileSync(prepares, 'utf8'), 'prepared\nprepared\nprepared\n');
|
||||
|
||||
fs.writeFileSync(appPath, '#!/bin/sh\nexit 42\n', { mode: 0o755 });
|
||||
const failed = run();
|
||||
assert.equal(failed.status, 42);
|
||||
assert.equal(failed.stdout, ''); // Never silently execute stale CLI after failed preparation.
|
||||
});
|
||||
Reference in New Issue
Block a user