mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-26 23:35:17 -07:00
feat(plugins): add JavaScript plugin system with overlay panel
- Load plugins from <config dir>/plugins/<id>/ when enabled under `plugins` in config - Expose ctx with mpv events/commands, timers, subtitle context, actions, notify, exec, and panel - Bind plugin actions via `__plugin-action:<id>/<action>` keybindings (runPluginAction session action) - Add plugin-panel modal with markdown rendering; closing aborts the panel signal - Add ai-explain example plugin that queries `codex exec` for the current line - Add e2e harness `files` option and plugin e2e scenario; document plugins in docs-site and internal docs
This commit is contained in:
65 files changed
+2555
-39
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { execPluginCommand } from './exec';
|
||||
|
||||
// The test runtime (bun or node) doubles as a portable child process.
|
||||
const runtime = process.execPath;
|
||||
|
||||
test('pipes stdin to the child and collects stdout', async () => {
|
||||
const result = await execPluginCommand(
|
||||
runtime,
|
||||
['-e', 'process.stdin.on("data", (d) => process.stdout.write(String(d).toUpperCase()))'],
|
||||
{ stdin: '字幕 line' },
|
||||
);
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.stdout, '字幕 LINE');
|
||||
});
|
||||
|
||||
test('resolves with a non-zero exit code and stderr', async () => {
|
||||
const result = await execPluginCommand(runtime, [
|
||||
'-e',
|
||||
'process.stderr.write("bad things"); process.exit(3)',
|
||||
]);
|
||||
|
||||
assert.equal(result.exitCode, 3);
|
||||
assert.equal(result.stderr, 'bad things');
|
||||
});
|
||||
|
||||
test('drops the AppImage library path from the child environment', async () => {
|
||||
const previous = process.env.LD_LIBRARY_PATH;
|
||||
process.env.LD_LIBRARY_PATH = '/tmp/.mount_SubMinXXXXXX/usr/lib';
|
||||
try {
|
||||
const result = await execPluginCommand(
|
||||
runtime,
|
||||
['-e', 'process.stdout.write(String(process.env.LD_LIBRARY_PATH) + "|" + process.env.EXTRA)'],
|
||||
{ env: { EXTRA: 'kept' } },
|
||||
);
|
||||
assert.equal(result.stdout, 'undefined|kept');
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.LD_LIBRARY_PATH;
|
||||
else process.env.LD_LIBRARY_PATH = previous;
|
||||
}
|
||||
});
|
||||
|
||||
test('aborting kills the child and rejects with AbortError', async () => {
|
||||
const controller = new AbortController();
|
||||
const pending = execPluginCommand(runtime, ['-e', 'setTimeout(() => {}, 60_000)'], {
|
||||
signal: controller.signal,
|
||||
});
|
||||
setTimeout(() => controller.abort(), 50);
|
||||
|
||||
await assert.rejects(pending, { name: 'AbortError' });
|
||||
});
|
||||
|
||||
test('rejects when the binary does not exist', async () => {
|
||||
await assert.rejects(
|
||||
execPluginCommand('subminer-definitely-missing-binary', []),
|
||||
(error: NodeJS.ErrnoException) => error.code === 'ENOENT',
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user