mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 16:49:51 -07:00
feat(sync-ui): sync window renderer, snapshots in /tmp/subminer-db-snapshots, docs + changelog
This commit is contained in:
@@ -33,7 +33,11 @@ test('tick triggers a due auto-sync host with its saved direction', () => {
|
||||
|
||||
test('tick skips hosts synced more recently than the interval', () => {
|
||||
let state = makeState();
|
||||
state = recordSyncResult(state, 'auto-box', { atMs: 90 * 60_000, status: 'success', detail: null });
|
||||
state = recordSyncResult(state, 'auto-box', {
|
||||
atMs: 90 * 60_000,
|
||||
status: 'success',
|
||||
detail: null,
|
||||
});
|
||||
const triggered: string[] = [];
|
||||
const scheduler = createSyncAutoScheduler({
|
||||
readState: () => state,
|
||||
|
||||
@@ -119,3 +119,29 @@ test('runSyncLauncher surfaces spawn errors', async () => {
|
||||
assert.equal(result.ok, false);
|
||||
assert.match(result.error ?? '', /ENOENT/);
|
||||
});
|
||||
|
||||
test('resolveSyncLauncherCommand prefers the bundled launcher over PATH', async () => {
|
||||
const { resolveSyncLauncherCommand } = await import('./sync-launcher-client');
|
||||
const bundled = resolveSyncLauncherCommand({
|
||||
findCommand: (name: string) =>
|
||||
name === 'bun' ? '/usr/bin/bun' : name === 'subminer' ? '/home/u/.local/bin/subminer' : null,
|
||||
resolveResourcePath: () => '/opt/app/resources/launcher/subminer',
|
||||
existsSync: () => true,
|
||||
});
|
||||
assert.deepEqual(bundled.command, ['/usr/bin/bun', '/opt/app/resources/launcher/subminer']);
|
||||
|
||||
const fallback = resolveSyncLauncherCommand({
|
||||
findCommand: (name: string) => (name === 'subminer' ? '/home/u/.local/bin/subminer' : null),
|
||||
resolveResourcePath: () => '/missing',
|
||||
existsSync: () => false,
|
||||
});
|
||||
assert.deepEqual(fallback.command, ['/home/u/.local/bin/subminer']);
|
||||
|
||||
const none = resolveSyncLauncherCommand({
|
||||
findCommand: () => null,
|
||||
resolveResourcePath: () => '/missing',
|
||||
existsSync: () => false,
|
||||
});
|
||||
assert.equal(none.command, null);
|
||||
assert.match(none.error ?? '', /launcher/i);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { spawn as nodeSpawn } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import { parseSyncProgressLine, type SyncProgressEvent } from '../../shared/sync/sync-events';
|
||||
import { findCommand } from './command-line-launcher-deps';
|
||||
import { resolveLauncherResourcePath } from './command-line-launcher';
|
||||
@@ -28,8 +29,9 @@ export interface SyncLauncherResolution {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// The launcher is a bun script: prefer the PATH-installed `subminer`, fall
|
||||
// back to running the bundled resource through bun directly.
|
||||
// The launcher is a bun script. Prefer the bundled launcher resource (it
|
||||
// always matches this app version's sync protocol) and only fall back to a
|
||||
// PATH-installed `subminer`, which may be older.
|
||||
export function resolveSyncLauncherCommand(
|
||||
deps: {
|
||||
findCommand?: typeof findCommand;
|
||||
@@ -38,14 +40,17 @@ export function resolveSyncLauncherCommand(
|
||||
} = {},
|
||||
): SyncLauncherResolution {
|
||||
const find = deps.findCommand ?? findCommand;
|
||||
const installed = find('subminer', {});
|
||||
if (installed) return { command: [installed], error: null };
|
||||
|
||||
const exists = deps.existsSync ?? fs.existsSync;
|
||||
const resourcePath = deps.resolveResourcePath
|
||||
? deps.resolveResourcePath()
|
||||
: resolveLauncherResourcePath({});
|
||||
const bunPath = find('bun', {});
|
||||
if (bunPath && resourcePath) return { command: [bunPath, resourcePath], error: null };
|
||||
if (bunPath && resourcePath && exists(resourcePath)) {
|
||||
return { command: [bunPath, resourcePath], error: null };
|
||||
}
|
||||
|
||||
const installed = find('subminer', {});
|
||||
if (installed) return { command: [installed], error: null };
|
||||
|
||||
return {
|
||||
command: null,
|
||||
|
||||
@@ -138,13 +138,7 @@ test('run-sync spawns the launcher, forwards progress, and rejects concurrent ru
|
||||
force: true,
|
||||
})) as { started: boolean; runId: number };
|
||||
assert.equal(start.started, true);
|
||||
assert.deepEqual(launcherCalls[0]!.args, [
|
||||
'sync',
|
||||
'media-box',
|
||||
'--push',
|
||||
'--force',
|
||||
'--json',
|
||||
]);
|
||||
assert.deepEqual(launcherCalls[0]!.args, ['sync', 'media-box', '--push', '--force', '--json']);
|
||||
|
||||
const second = (await invoke('sync-ui:run-sync', { host: 'other' })) as {
|
||||
started: boolean;
|
||||
|
||||
@@ -213,9 +213,7 @@ export function createSyncUiRuntime(deps: SyncUiRuntimeDeps) {
|
||||
if (result.started) {
|
||||
// Remember the host (and the direction used) before the launcher's own
|
||||
// bookkeeping lands, so it shows up in the UI immediately.
|
||||
writeState(
|
||||
upsertSyncHost(readState(), { host, direction: request.direction }, deps.nowMs()),
|
||||
);
|
||||
writeState(upsertSyncHost(readState(), { host, direction: request.direction }, deps.nowMs()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -298,9 +296,7 @@ export function createSyncUiRuntime(deps: SyncUiRuntimeDeps) {
|
||||
const channels = IPC_CHANNELS.request;
|
||||
deps.ipcMain.handle(channels.syncUiGetSnapshot, () => getSnapshot());
|
||||
deps.ipcMain.handle(channels.syncUiSaveHost, (_event, update) =>
|
||||
writeState(
|
||||
upsertSyncHost(readState(), update as SyncUiHostUpdateRequest, deps.nowMs()),
|
||||
),
|
||||
writeState(upsertSyncHost(readState(), update as SyncUiHostUpdateRequest, deps.nowMs())),
|
||||
);
|
||||
deps.ipcMain.handle(channels.syncUiRemoveHost, (_event, host) =>
|
||||
writeState(removeSyncHost(readState(), String(host))),
|
||||
|
||||
Reference in New Issue
Block a user