mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Prepare Windows release and signing process (#16)
This commit is contained in:
@@ -7,6 +7,8 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
return {
|
||||
background: false,
|
||||
start: false,
|
||||
launchMpv: false,
|
||||
launchMpvTargets: [],
|
||||
stop: false,
|
||||
toggle: false,
|
||||
toggleVisibleOverlay: false,
|
||||
|
||||
@@ -7,6 +7,8 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
return {
|
||||
background: false,
|
||||
start: false,
|
||||
launchMpv: false,
|
||||
launchMpvTargets: [],
|
||||
stop: false,
|
||||
toggle: false,
|
||||
toggleVisibleOverlay: false,
|
||||
|
||||
@@ -33,9 +33,30 @@ function makeDbPath(): string {
|
||||
|
||||
function cleanupDbPath(dbPath: string): void {
|
||||
const dir = path.dirname(dbPath);
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
if (!fs.existsSync(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bunRuntime = globalThis as typeof globalThis & {
|
||||
Bun?: {
|
||||
gc?: (force?: boolean) => void;
|
||||
};
|
||||
};
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
if (process.platform !== 'win32' || err.code !== 'EBUSY') {
|
||||
throw error;
|
||||
}
|
||||
bunRuntime.Bun?.gc?.(true);
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
||||
}
|
||||
}
|
||||
|
||||
// libsql keeps Windows file handles alive after close when prepared statements were used.
|
||||
}
|
||||
|
||||
test('seam: resolveBoundedInt keeps fallback for invalid values', () => {
|
||||
|
||||
@@ -20,9 +20,30 @@ function makeDbPath(): string {
|
||||
|
||||
function cleanupDbPath(dbPath: string): void {
|
||||
const dir = path.dirname(dbPath);
|
||||
if (fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
if (!fs.existsSync(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bunRuntime = globalThis as typeof globalThis & {
|
||||
Bun?: {
|
||||
gc?: (force?: boolean) => void;
|
||||
};
|
||||
};
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
if (process.platform !== 'win32' || err.code !== 'EBUSY') {
|
||||
throw error;
|
||||
}
|
||||
bunRuntime.Bun?.gc?.(true);
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
||||
}
|
||||
}
|
||||
|
||||
// libsql keeps Windows file handles alive after close when prepared statements were used.
|
||||
}
|
||||
|
||||
test('ensureSchema creates immersion core tables', () => {
|
||||
|
||||
@@ -90,6 +90,9 @@ export function initializeOverlayRuntime(options: {
|
||||
windowTracker.onGeometryChange = (geometry: WindowGeometry) => {
|
||||
options.updateVisibleOverlayBounds(geometry);
|
||||
};
|
||||
windowTracker.onTargetWindowFocusChange = () => {
|
||||
options.syncOverlayShortcuts();
|
||||
};
|
||||
windowTracker.onWindowFound = (geometry: WindowGeometry) => {
|
||||
options.updateVisibleOverlayBounds(geometry);
|
||||
if (options.isVisibleOverlayVisible()) {
|
||||
|
||||
@@ -21,8 +21,8 @@ function createMainWindowRecorder() {
|
||||
focus: () => {
|
||||
calls.push('focus');
|
||||
},
|
||||
setIgnoreMouseEvents: () => {
|
||||
calls.push('mouse-ignore');
|
||||
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
|
||||
calls.push(`mouse-ignore:${ignore}:${options?.forward === true ? 'forward' : 'plain'}`);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -122,6 +122,85 @@ test('non-macOS keeps fallback visible overlay behavior when tracker is not read
|
||||
assert.ok(!calls.includes('osd'));
|
||||
});
|
||||
|
||||
test('Windows visible overlay stays click-through and does not steal focus while tracked', () => {
|
||||
const { window, calls } = createMainWindowRecorder();
|
||||
const tracker: WindowTrackerStub = {
|
||||
isTracking: () => true,
|
||||
getGeometry: () => ({ x: 0, y: 0, width: 1280, height: 720 }),
|
||||
};
|
||||
|
||||
updateVisibleOverlayVisibility({
|
||||
visibleOverlayVisible: true,
|
||||
mainWindow: window as never,
|
||||
windowTracker: tracker as never,
|
||||
trackerNotReadyWarningShown: false,
|
||||
setTrackerNotReadyWarningShown: () => {},
|
||||
updateVisibleOverlayBounds: () => {
|
||||
calls.push('update-bounds');
|
||||
},
|
||||
ensureOverlayWindowLevel: () => {
|
||||
calls.push('ensure-level');
|
||||
},
|
||||
syncPrimaryOverlayWindowLayer: () => {
|
||||
calls.push('sync-layer');
|
||||
},
|
||||
enforceOverlayLayerOrder: () => {
|
||||
calls.push('enforce-order');
|
||||
},
|
||||
syncOverlayShortcuts: () => {
|
||||
calls.push('sync-shortcuts');
|
||||
},
|
||||
isMacOSPlatform: false,
|
||||
isWindowsPlatform: true,
|
||||
} as never);
|
||||
|
||||
assert.ok(calls.includes('mouse-ignore:true:forward'));
|
||||
assert.ok(calls.includes('show'));
|
||||
assert.ok(!calls.includes('focus'));
|
||||
});
|
||||
|
||||
test('Windows keeps visible overlay hidden while tracker is not ready', () => {
|
||||
const { window, calls } = createMainWindowRecorder();
|
||||
let trackerWarning = false;
|
||||
const tracker: WindowTrackerStub = {
|
||||
isTracking: () => false,
|
||||
getGeometry: () => null,
|
||||
};
|
||||
|
||||
updateVisibleOverlayVisibility({
|
||||
visibleOverlayVisible: true,
|
||||
mainWindow: window as never,
|
||||
windowTracker: tracker as never,
|
||||
trackerNotReadyWarningShown: trackerWarning,
|
||||
setTrackerNotReadyWarningShown: (shown: boolean) => {
|
||||
trackerWarning = shown;
|
||||
},
|
||||
updateVisibleOverlayBounds: () => {
|
||||
calls.push('update-bounds');
|
||||
},
|
||||
ensureOverlayWindowLevel: () => {
|
||||
calls.push('ensure-level');
|
||||
},
|
||||
syncPrimaryOverlayWindowLayer: () => {
|
||||
calls.push('sync-layer');
|
||||
},
|
||||
enforceOverlayLayerOrder: () => {
|
||||
calls.push('enforce-order');
|
||||
},
|
||||
syncOverlayShortcuts: () => {
|
||||
calls.push('sync-shortcuts');
|
||||
},
|
||||
isMacOSPlatform: false,
|
||||
isWindowsPlatform: true,
|
||||
resolveFallbackBounds: () => ({ x: 12, y: 24, width: 640, height: 360 }),
|
||||
} as never);
|
||||
|
||||
assert.equal(trackerWarning, true);
|
||||
assert.ok(calls.includes('hide'));
|
||||
assert.ok(!calls.includes('show'));
|
||||
assert.ok(!calls.includes('update-bounds'));
|
||||
});
|
||||
|
||||
test('macOS keeps visible overlay hidden while tracker is not initialized yet', () => {
|
||||
const { window, calls } = createMainWindowRecorder();
|
||||
let trackerWarning = false;
|
||||
|
||||
@@ -14,6 +14,7 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
enforceOverlayLayerOrder: () => void;
|
||||
syncOverlayShortcuts: () => void;
|
||||
isMacOSPlatform?: boolean;
|
||||
isWindowsPlatform?: boolean;
|
||||
showOverlayLoadingOsd?: (message: string) => void;
|
||||
resolveFallbackBounds?: () => WindowGeometry;
|
||||
}): void {
|
||||
@@ -21,9 +22,24 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainWindow = args.mainWindow;
|
||||
|
||||
const showPassiveVisibleOverlay = (): void => {
|
||||
if (args.isWindowsPlatform) {
|
||||
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
||||
} else {
|
||||
mainWindow.setIgnoreMouseEvents(false);
|
||||
}
|
||||
args.ensureOverlayWindowLevel(mainWindow);
|
||||
mainWindow.show();
|
||||
if (!args.isWindowsPlatform) {
|
||||
mainWindow.focus();
|
||||
}
|
||||
};
|
||||
|
||||
if (!args.visibleOverlayVisible) {
|
||||
args.setTrackerNotReadyWarningShown(false);
|
||||
args.mainWindow.hide();
|
||||
mainWindow.hide();
|
||||
args.syncOverlayShortcuts();
|
||||
return;
|
||||
}
|
||||
@@ -35,31 +51,27 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
args.updateVisibleOverlayBounds(geometry);
|
||||
}
|
||||
args.syncPrimaryOverlayWindowLayer('visible');
|
||||
args.mainWindow.setIgnoreMouseEvents(false);
|
||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||
args.mainWindow.show();
|
||||
args.mainWindow.focus();
|
||||
showPassiveVisibleOverlay();
|
||||
args.enforceOverlayLayerOrder();
|
||||
args.syncOverlayShortcuts();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.windowTracker) {
|
||||
if (args.isMacOSPlatform) {
|
||||
if (args.isMacOSPlatform || args.isWindowsPlatform) {
|
||||
if (!args.trackerNotReadyWarningShown) {
|
||||
args.setTrackerNotReadyWarningShown(true);
|
||||
args.showOverlayLoadingOsd?.('Overlay loading...');
|
||||
if (args.isMacOSPlatform) {
|
||||
args.showOverlayLoadingOsd?.('Overlay loading...');
|
||||
}
|
||||
}
|
||||
args.mainWindow.hide();
|
||||
mainWindow.hide();
|
||||
args.syncOverlayShortcuts();
|
||||
return;
|
||||
}
|
||||
args.setTrackerNotReadyWarningShown(false);
|
||||
args.syncPrimaryOverlayWindowLayer('visible');
|
||||
args.mainWindow.setIgnoreMouseEvents(false);
|
||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||
args.mainWindow.show();
|
||||
args.mainWindow.focus();
|
||||
showPassiveVisibleOverlay();
|
||||
args.enforceOverlayLayerOrder();
|
||||
args.syncOverlayShortcuts();
|
||||
return;
|
||||
@@ -72,8 +84,8 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
}
|
||||
}
|
||||
|
||||
if (args.isMacOSPlatform) {
|
||||
args.mainWindow.hide();
|
||||
if (args.isMacOSPlatform || args.isWindowsPlatform) {
|
||||
mainWindow.hide();
|
||||
args.syncOverlayShortcuts();
|
||||
return;
|
||||
}
|
||||
@@ -83,10 +95,7 @@ export function updateVisibleOverlayVisibility(args: {
|
||||
|
||||
args.updateVisibleOverlayBounds(fallbackBounds);
|
||||
args.syncPrimaryOverlayWindowLayer('visible');
|
||||
args.mainWindow.setIgnoreMouseEvents(false);
|
||||
args.ensureOverlayWindowLevel(args.mainWindow);
|
||||
args.mainWindow.show();
|
||||
args.mainWindow.focus();
|
||||
showPassiveVisibleOverlay();
|
||||
args.enforceOverlayLayerOrder();
|
||||
args.syncOverlayShortcuts();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import electron from 'electron';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import * as path from 'path';
|
||||
import { WindowGeometry } from '../../types';
|
||||
import { createLogger } from '../../logger';
|
||||
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
||||
|
||||
const { BrowserWindow: ElectronBrowserWindow } = electron;
|
||||
const logger = createLogger('main:overlay-window');
|
||||
const overlayWindowLayerByInstance = new WeakMap<BrowserWindow, OverlayWindowKind>();
|
||||
|
||||
@@ -20,7 +18,7 @@ function loadOverlayWindowLayer(window: BrowserWindow, layer: OverlayWindowKind)
|
||||
.loadFile(htmlPath, {
|
||||
query: { layer },
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
.catch((err) => {
|
||||
logger.error('Failed to load HTML file:', err);
|
||||
});
|
||||
}
|
||||
@@ -65,6 +63,11 @@ export function ensureOverlayWindowLevel(window: BrowserWindow): void {
|
||||
window.setFullScreenable(false);
|
||||
return;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
window.setAlwaysOnTop(true, 'screen-saver', 1);
|
||||
window.moveTop();
|
||||
return;
|
||||
}
|
||||
window.setAlwaysOnTop(true);
|
||||
}
|
||||
|
||||
@@ -92,7 +95,8 @@ export function createOverlayWindow(
|
||||
onWindowClosed: (kind: OverlayWindowKind) => void;
|
||||
},
|
||||
): BrowserWindow {
|
||||
const window = new ElectronBrowserWindow({
|
||||
const showNativeDebugFrame = process.platform === 'win32' && options.isDev;
|
||||
const window = new BrowserWindow({
|
||||
show: false,
|
||||
width: 800,
|
||||
height: 600,
|
||||
@@ -106,6 +110,7 @@ export function createOverlayWindow(
|
||||
hasShadow: false,
|
||||
focusable: true,
|
||||
acceptFirstMouse: true,
|
||||
...(process.platform === 'win32' ? { thickFrame: showNativeDebugFrame } : {}),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '..', '..', 'preload.js'),
|
||||
contextIsolation: true,
|
||||
@@ -162,6 +167,9 @@ export function createOverlayWindow(
|
||||
window.on('blur', () => {
|
||||
if (!window.isDestroyed()) {
|
||||
options.ensureOverlayWindowLevel(window);
|
||||
if (kind === 'visible' && window.isVisible()) {
|
||||
window.moveTop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
return {
|
||||
background: false,
|
||||
start: false,
|
||||
launchMpv: false,
|
||||
launchMpvTargets: [],
|
||||
stop: false,
|
||||
toggle: false,
|
||||
toggleVisibleOverlay: false,
|
||||
|
||||
@@ -147,6 +147,28 @@ function writeExecutableScript(filePath: string, content: string): void {
|
||||
fs.chmodSync(filePath, 0o755);
|
||||
}
|
||||
|
||||
function toShellPath(filePath: string): string {
|
||||
if (process.platform !== 'win32') {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
return filePath.replace(/\\/g, '/').replace(/^([A-Za-z]):\//, (_, driveLetter: string) => {
|
||||
return `/mnt/${driveLetter.toLowerCase()}/`;
|
||||
});
|
||||
}
|
||||
|
||||
function fromShellPath(filePath: string): string {
|
||||
if (process.platform !== 'win32') {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
return filePath
|
||||
.replace(/^\/mnt\/([a-z])\//, (_, driveLetter: string) => {
|
||||
return `${driveLetter.toUpperCase()}:/`;
|
||||
})
|
||||
.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
test('runSubsyncManual constructs ffsubsync command and returns success', async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subsync-ffsubsync-'));
|
||||
const ffsubsyncLogPath = path.join(tmpDir, 'ffsubsync-args.log');
|
||||
@@ -162,7 +184,7 @@ test('runSubsyncManual constructs ffsubsync command and returns success', async
|
||||
writeExecutableScript(alassPath, '#!/bin/sh\nexit 0\n');
|
||||
writeExecutableScript(
|
||||
ffsubsyncPath,
|
||||
`#!/bin/sh\n: > "${ffsubsyncLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${ffsubsyncLogPath}"; done\nout=\"\"\nprev=\"\"\nfor arg in \"$@\"; do\n if [ \"$prev\" = \"-o\" ]; then out=\"$arg\"; fi\n prev=\"$arg\"\ndone\nif [ -n \"$out\" ]; then : > \"$out\"; fi\nexit 0\n`,
|
||||
`#!/bin/sh\n: > "${toShellPath(ffsubsyncLogPath)}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${toShellPath(ffsubsyncLogPath)}"; done\nout=\"\"\nprev=\"\"\nfor arg in \"$@\"; do\n if [ \"$prev\" = \"-o\" ]; then out=\"$arg\"; fi\n prev=\"$arg\"\ndone\nif [ -n \"$out\" ]; then : > \"$out\"; fi\nexit 0\n`,
|
||||
);
|
||||
|
||||
const sentCommands: Array<Array<string | number>> = [];
|
||||
@@ -204,14 +226,14 @@ test('runSubsyncManual constructs ffsubsync command and returns success', async
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.message, 'Subtitle synchronized with ffsubsync');
|
||||
const ffArgs = fs.readFileSync(ffsubsyncLogPath, 'utf8').trim().split('\n');
|
||||
assert.equal(ffArgs[0], videoPath);
|
||||
assert.equal(ffArgs[0], toShellPath(videoPath));
|
||||
assert.ok(ffArgs.includes('-i'));
|
||||
assert.ok(ffArgs.includes(primaryPath));
|
||||
assert.ok(ffArgs.includes(toShellPath(primaryPath)));
|
||||
assert.ok(ffArgs.includes('--reference-stream'));
|
||||
assert.ok(ffArgs.includes('0:2'));
|
||||
const ffOutputFlagIndex = ffArgs.indexOf('-o');
|
||||
assert.equal(ffOutputFlagIndex >= 0, true);
|
||||
assert.equal(ffArgs[ffOutputFlagIndex + 1], primaryPath);
|
||||
assert.equal(ffArgs[ffOutputFlagIndex + 1], toShellPath(primaryPath));
|
||||
assert.equal(sentCommands[0]?.[0], 'sub_add');
|
||||
assert.deepEqual(sentCommands[1], ['set_property', 'sub-delay', 0]);
|
||||
});
|
||||
@@ -231,7 +253,7 @@ test('runSubsyncManual writes deterministic _retimed filename when replace is fa
|
||||
writeExecutableScript(alassPath, '#!/bin/sh\nexit 0\n');
|
||||
writeExecutableScript(
|
||||
ffsubsyncPath,
|
||||
`#!/bin/sh\n: > "${ffsubsyncLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${ffsubsyncLogPath}"; done\nout=\"\"\nprev=\"\"\nfor arg in \"$@\"; do\n if [ \"$prev\" = \"-o\" ]; then out=\"$arg\"; fi\n prev=\"$arg\"\ndone\nif [ -n \"$out\" ]; then : > \"$out\"; fi\nexit 0\n`,
|
||||
`#!/bin/sh\n: > "${toShellPath(ffsubsyncLogPath)}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${toShellPath(ffsubsyncLogPath)}"; done\nout=\"\"\nprev=\"\"\nfor arg in \"$@\"; do\n if [ \"$prev\" = \"-o\" ]; then out=\"$arg\"; fi\n prev=\"$arg\"\ndone\nif [ -n \"$out\" ]; then : > \"$out\"; fi\nexit 0\n`,
|
||||
);
|
||||
|
||||
const deps = makeDeps({
|
||||
@@ -273,7 +295,7 @@ test('runSubsyncManual writes deterministic _retimed filename when replace is fa
|
||||
const ffOutputFlagIndex = ffArgs.indexOf('-o');
|
||||
assert.equal(ffOutputFlagIndex >= 0, true);
|
||||
const outputPath = ffArgs[ffOutputFlagIndex + 1];
|
||||
assert.equal(outputPath, path.join(tmpDir, 'episode.ja_retimed.srt'));
|
||||
assert.equal(outputPath, toShellPath(path.join(tmpDir, 'episode.ja_retimed.srt')));
|
||||
});
|
||||
|
||||
test('runSubsyncManual reports ffsubsync command failures with details', async () => {
|
||||
@@ -346,7 +368,7 @@ test('runSubsyncManual constructs alass command and returns failure on non-zero
|
||||
writeExecutableScript(ffsubsyncPath, '#!/bin/sh\nexit 0\n');
|
||||
writeExecutableScript(
|
||||
alassPath,
|
||||
`#!/bin/sh\n: > "${alassLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${alassLogPath}"; done\nexit 1\n`,
|
||||
`#!/bin/sh\n: > "${toShellPath(alassLogPath)}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${toShellPath(alassLogPath)}"; done\nexit 1\n`,
|
||||
);
|
||||
|
||||
const deps = makeDeps({
|
||||
@@ -393,8 +415,8 @@ test('runSubsyncManual constructs alass command and returns failure on non-zero
|
||||
assert.equal(typeof result.message, 'string');
|
||||
assert.equal(result.message.startsWith('alass synchronization failed'), true);
|
||||
const alassArgs = fs.readFileSync(alassLogPath, 'utf8').trim().split('\n');
|
||||
assert.equal(alassArgs[0], sourcePath);
|
||||
assert.equal(alassArgs[1], primaryPath);
|
||||
assert.equal(alassArgs[0], toShellPath(sourcePath));
|
||||
assert.equal(alassArgs[1], toShellPath(primaryPath));
|
||||
});
|
||||
|
||||
test('runSubsyncManual keeps internal alass source file alive until sync finishes', async () => {
|
||||
@@ -482,7 +504,7 @@ test('runSubsyncManual resolves string sid values from mpv stream properties', a
|
||||
writeExecutableScript(alassPath, '#!/bin/sh\nexit 0\n');
|
||||
writeExecutableScript(
|
||||
ffsubsyncPath,
|
||||
`#!/bin/sh\nmkdir -p "${tmpDir}"\n: > "${ffsubsyncLogPath}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${ffsubsyncLogPath}"; done\nprev=""\nout=""\nfor arg in "$@"; do\n if [ "$prev" = "--reference-stream" ]; then :; fi\n if [ "$prev" = "-o" ]; then out="$arg"; fi\n prev="$arg"\ndone\nif [ -n "$out" ]; then : > "$out"; fi`,
|
||||
`#!/bin/sh\nmkdir -p "${toShellPath(tmpDir)}"\n: > "${toShellPath(ffsubsyncLogPath)}"\nfor arg in "$@"; do printf '%s\\n' "$arg" >> "${toShellPath(ffsubsyncLogPath)}"; done\nprev=""\nout=""\nfor arg in "$@"; do\n if [ "$prev" = "--reference-stream" ]; then :; fi\n if [ "$prev" = "-o" ]; then out="$arg"; fi\n prev="$arg"\ndone\nif [ -n "$out" ]; then : > "$out"; fi`,
|
||||
);
|
||||
|
||||
const deps = makeDeps({
|
||||
@@ -526,5 +548,5 @@ test('runSubsyncManual resolves string sid values from mpv stream properties', a
|
||||
const outputPath = ffArgs[syncOutputIndex + 1];
|
||||
assert.equal(typeof outputPath, 'string');
|
||||
assert.ok(outputPath!.length > 0);
|
||||
assert.equal(fs.readFileSync(outputPath!, 'utf8'), '');
|
||||
assert.equal(fs.readFileSync(fromShellPath(outputPath!), 'utf8'), '');
|
||||
});
|
||||
|
||||
@@ -8,18 +8,21 @@ import {
|
||||
} from './yomitan-extension-paths';
|
||||
|
||||
test('getYomitanExtensionSearchPaths prioritizes generated build output before packaged fallbacks', () => {
|
||||
const repoRoot = path.resolve('repo');
|
||||
const resourcesPath = path.join(path.sep, 'opt', 'SubMiner', 'resources');
|
||||
const userDataPath = path.join(path.sep, 'Users', 'kyle', '.config', 'SubMiner');
|
||||
const searchPaths = getYomitanExtensionSearchPaths({
|
||||
cwd: '/repo',
|
||||
moduleDir: '/repo/dist/core/services',
|
||||
resourcesPath: '/opt/SubMiner/resources',
|
||||
userDataPath: '/Users/kyle/.config/SubMiner',
|
||||
cwd: repoRoot,
|
||||
moduleDir: path.join(repoRoot, 'dist', 'core', 'services'),
|
||||
resourcesPath,
|
||||
userDataPath,
|
||||
});
|
||||
|
||||
assert.deepEqual(searchPaths, [
|
||||
path.join('/repo', 'build', 'yomitan'),
|
||||
path.join('/opt/SubMiner/resources', 'yomitan'),
|
||||
path.join(repoRoot, 'build', 'yomitan'),
|
||||
path.join(resourcesPath, 'yomitan'),
|
||||
'/usr/share/SubMiner/yomitan',
|
||||
path.join('/Users/kyle/.config/SubMiner', 'yomitan'),
|
||||
path.join(userDataPath, 'yomitan'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user