diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01ba13c..e9a114c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,6 @@ jobs: with: bun-version: 1.3.5 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Cache dependencies uses: actions/cache@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74d24f6..bf46008 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,11 +26,6 @@ jobs: with: bun-version: 1.3.5 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Cache dependencies uses: actions/cache@v4 with: @@ -78,11 +73,6 @@ jobs: with: bun-version: 1.3.5 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Cache dependencies uses: actions/cache@v4 with: @@ -128,11 +118,6 @@ jobs: with: bun-version: 1.3.5 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Cache dependencies uses: actions/cache@v4 with: diff --git a/README.md b/README.md index 71b8322..8b12165 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,14 @@ subminer video.mkv ## Requirements -| Required | Optional | -| ------------------------------------------ | ---------------------------- | -| `mpv` with IPC socket | `yt-dlp` | +| Required | Optional | +| ------------------------------------------ | -------------------------------------------------- | +| `bun` | | +| `mpv` with IPC socket | `yt-dlp` | | `ffmpeg` | `guessit` (better AniSkip title/episode detection) | -| `mecab` + `mecab-ipadic` | `fzf` / `rofi` | -| Linux: `hyprctl` or `xdotool` + `xwininfo` | `chafa`, `ffmpegthumbnailer` | -| macOS: Accessibility permission | | +| `mecab` + `mecab-ipadic` | `fzf` / `rofi` | +| Linux: `hyprctl` or `xdotool` + `xwininfo` | `chafa`, `ffmpegthumbnailer` | +| macOS: Accessibility permission | | ## Documentation diff --git a/backlog/tasks/task-115 - Migrate-repository-workflows-to-Bun-only-JavaScript-runtime.md b/backlog/tasks/task-115 - Migrate-repository-workflows-to-Bun-only-JavaScript-runtime.md new file mode 100644 index 0000000..3f986cf --- /dev/null +++ b/backlog/tasks/task-115 - Migrate-repository-workflows-to-Bun-only-JavaScript-runtime.md @@ -0,0 +1,61 @@ +--- +id: TASK-115 +title: Migrate repository workflows to Bun-only JavaScript runtime +status: Done +assignee: [] +created_date: '2026-02-23 04:26' +updated_date: '2026-02-23 04:42' +labels: + - tooling + - build + - ci +dependencies: [] +references: + - package.json + - .github/workflows/ci.yml + - .github/workflows/release.yml + - docs/development.md + - docs/installation.md + - README.md +documentation: + - docs/plans/2026-02-23-bun-only-toolchain-migration.md +priority: high +--- + +## Description + + +Transition the project from mixed Bun/Node tooling to a Bun-only contributor and CI workflow so setup is simpler and runtime behavior is consistent across local and automation lanes. + + +## Acceptance Criteria + +- [x] #1 A contributor can install dependencies, build, run source tests, run dist smoke tests, and build docs using Bun without requiring a system Node.js install. +- [x] #2 CI and release workflows no longer require Node setup steps for standard project verification and packaging jobs. +- [x] #3 Dist and utility verification lanes remain covered and pass with Bun-based commands. +- [x] #4 Repository documentation clearly states the Bun-only prerequisite and removes conflicting Node requirement guidance. + + +## Implementation Notes + + +Execution started in-session after user selected Subagent-Driven option. Tracking sequence: TASK-115.2 -> TASK-115.3/TASK-115.4 -> TASK-115.1. + +Completed child sequence TASK-115.2 -> TASK-115.3/TASK-115.4 -> TASK-115.1 with Bun-only command/workflow migration. + +Direct Node command usage removed from package dist and utility script lanes; CI/release Node setup steps removed; contributor docs aligned to Bun-only prerequisites. + +Regression hardening: refactored AniList token-store tests and storage seam to remain stable under Bun dist test execution while preserving runtime behavior. + +Post-migration docs polish: updated `README.md` requirements table to list Bun as a required dependency and promoted Bun from optional-tools section into system dependencies in `docs/installation.md` to remove ambiguity. + + +## Final Summary + + +Migrated repository verification and workflow surfaces to Bun-first execution by replacing `node --test` dist lanes and remaining direct Node utility invocation (`generate:config-example`) with Bun commands. + +Removed `actions/setup-node` from CI and release jobs and aligned docs (`docs/development.md`, `docs/installation.md`) to Bun-only setup guidance; launcher smoke fixtures now use Bun shebangs so smoke tests no longer depend on Node. + +Validated full gate set under Bun-only command path: launcher smoke, fast source suite, build, dist config/core/smoke suites, docs build, plus config-example generation command. + diff --git a/backlog/tasks/task-115.1 - Remove-Node-setup-from-workflows-and-finalize-Bun-only-docs.md b/backlog/tasks/task-115.1 - Remove-Node-setup-from-workflows-and-finalize-Bun-only-docs.md new file mode 100644 index 0000000..8a00b07 --- /dev/null +++ b/backlog/tasks/task-115.1 - Remove-Node-setup-from-workflows-and-finalize-Bun-only-docs.md @@ -0,0 +1,49 @@ +--- +id: TASK-115.1 +title: Remove Node setup from workflows and finalize Bun-only docs +status: Done +assignee: [] +created_date: '2026-02-23 04:26' +updated_date: '2026-02-23 04:36' +labels: + - ci + - docs + - tooling +dependencies: + - TASK-115.2 + - TASK-115.3 + - TASK-115.4 +references: + - .github/workflows/ci.yml + - .github/workflows/release.yml + - docs/development.md + - docs/installation.md + - README.md +documentation: + - docs/plans/2026-02-23-bun-only-toolchain-migration.md +parent_task_id: TASK-115 +priority: high +--- + +## Description + + +After script/test migration parity is verified, remove Node setup from CI/release workflows and finalize documentation so contributors and automation use a consistent Bun-only prerequisite. + + +## Acceptance Criteria + +- [x] #1 CI and release workflows no longer include Node setup steps for routine build/test/package jobs. +- [x] #2 Primary local verification gates pass with the Bun-only command set. +- [x] #3 README and setup/install docs consistently describe Bun as the JS runtime prerequisite without conflicting Node requirement text. + + +## Implementation Notes + + +Removed `actions/setup-node` from `.github/workflows/ci.yml` and `.github/workflows/release.yml` (quality-gate, build-linux, build-macos jobs). + +Updated docs to align Bun-only prerequisite: removed Node from `docs/development.md` prerequisites and switched stale pnpm source-build snippet in `docs/installation.md` to Bun. + +Validation gates: `bun run test:launcher:smoke:src && bun run test:fast && bun run build && bun run test:config:dist && bun run test:core:dist && bun run test:smoke:dist && bun run docs:build` all pass. + diff --git a/backlog/tasks/task-115.2 - Map-Node-touchpoints-and-define-Bun-parity-matrix.md b/backlog/tasks/task-115.2 - Map-Node-touchpoints-and-define-Bun-parity-matrix.md new file mode 100644 index 0000000..163f77f --- /dev/null +++ b/backlog/tasks/task-115.2 - Map-Node-touchpoints-and-define-Bun-parity-matrix.md @@ -0,0 +1,41 @@ +--- +id: TASK-115.2 +title: Map Node touchpoints and define Bun parity matrix +status: Done +assignee: [] +created_date: '2026-02-23 04:27' +updated_date: '2026-02-23 04:36' +labels: + - tooling + - planning +dependencies: [] +references: + - package.json + - .github/workflows/ci.yml + - .github/workflows/release.yml +documentation: + - docs/plans/2026-02-23-bun-only-toolchain-migration.md +parent_task_id: TASK-115 +priority: medium +--- + +## Description + + +Document all Node-specific command and workflow touchpoints, define Bun replacements, and establish migration guardrails so follow-up implementation tasks have clear scope and risk controls. + + +## Acceptance Criteria + +- [x] #1 A complete inventory of Node-dependent scripts and workflow steps is documented with proposed Bun equivalents. +- [x] #2 Migration risks and compatibility checkpoints are explicitly documented for dist and Electron-related lanes. +- [x] #3 Follow-up implementation tasks have clear boundaries and ordering based on the parity matrix. + + +## Implementation Notes + + +Created `docs/plans/bun-only-parity-matrix.md` with direct Node touchpoint inventory, Bun replacements, risk checkpoints, and migration sequencing. + +Parity matrix now links implementation ordering for TASK-115.3/115.4/115.1 and flags follow-up risk handling for third-party implicit Node assumptions. + diff --git a/backlog/tasks/task-115.3 - Migrate-dist-test-lanes-from-Node-runner-to-Bun.md b/backlog/tasks/task-115.3 - Migrate-dist-test-lanes-from-Node-runner-to-Bun.md new file mode 100644 index 0000000..2660d3a --- /dev/null +++ b/backlog/tasks/task-115.3 - Migrate-dist-test-lanes-from-Node-runner-to-Bun.md @@ -0,0 +1,44 @@ +--- +id: TASK-115.3 +title: Migrate dist test lanes from Node runner to Bun +status: Done +assignee: [] +created_date: '2026-02-23 04:27' +updated_date: '2026-02-23 04:36' +labels: + - test + - tooling +dependencies: + - TASK-115.2 +references: + - package.json + - docs/development.md + - docs/installation.md +documentation: + - docs/plans/2026-02-23-bun-only-toolchain-migration.md +parent_task_id: TASK-115 +priority: high +--- + +## Description + + +Replace Node-runner dist test commands with Bun-based dist verification while preserving existing test scope and confidence in compiled artifact behavior. + + +## Acceptance Criteria + +- [x] #1 Dist test scripts execute via Bun instead of node --test while preserving the current dist test file coverage. +- [x] #2 Dist smoke and full dist verification lanes pass under Bun-based commands. +- [x] #3 Documentation for dist verification commands reflects the updated Bun-based workflow. + + +## Implementation Notes + + +Updated `package.json` dist lanes from `node --test` to `bun test` (`test:config:dist`, `test:config:smoke:dist`, `test:core:dist`, `test:core:smoke:dist`). + +Resolved Bun dist compatibility gap by injecting `SafeStorageLike` into AniList token store and replacing brittle Electron global monkey-patching in `anilist-token-store` tests. + +Validation: `bun run build && bun run test:config:dist && bun run test:core:dist && bun run test:smoke:dist` all pass. + diff --git a/backlog/tasks/task-115.4 - Migrate-Node-invoked-utility-script-commands-to-Bun.md b/backlog/tasks/task-115.4 - Migrate-Node-invoked-utility-script-commands-to-Bun.md new file mode 100644 index 0000000..ae3d405 --- /dev/null +++ b/backlog/tasks/task-115.4 - Migrate-Node-invoked-utility-script-commands-to-Bun.md @@ -0,0 +1,44 @@ +--- +id: TASK-115.4 +title: Migrate Node-invoked utility script commands to Bun +status: Done +assignee: [] +created_date: '2026-02-23 04:27' +updated_date: '2026-02-23 04:36' +labels: + - tooling + - scripts +dependencies: + - TASK-115.2 +references: + - package.json + - scripts/get_frequency.ts + - scripts/test-yomitan-parser.ts +documentation: + - docs/plans/2026-02-23-bun-only-toolchain-migration.md +parent_task_id: TASK-115 +priority: medium +--- + +## Description + + +Update utility command entrypoints that still rely on Node invocation so maintenance and diagnostics workflows can be run entirely through Bun. + + +## Acceptance Criteria + +- [x] #1 Utility script commands that currently invoke Node have Bun-based equivalents as the default project commands. +- [x] #2 Electron-targeted utility flows remain functional after command migration. +- [x] #3 Contributor docs reflect the updated Bun-based utility command usage. + + +## Implementation Notes + + +Updated `generate:config-example` command from `node dist/generate-config-example.js` to `bun dist/generate-config-example.js`. + +Updated launcher smoke fixture executables from `#!/usr/bin/env node` to `#!/usr/bin/env bun` so smoke tests do not assume Node presence. + +Validation: `bun run test:launcher:smoke:src` and `bun run generate:config-example` pass with Bun-first command path. + diff --git a/docs/development.md b/docs/development.md index 3ae6b86..1a8b3ba 100644 --- a/docs/development.md +++ b/docs/development.md @@ -2,7 +2,6 @@ ## Prerequisites -- [Node.js](https://nodejs.org/) (LTS) - [Bun](https://bun.sh) ## Setup @@ -150,22 +149,22 @@ Run `make help` for a full list of targets. Key ones: ## Environment Variables -| Variable | Description | -| ---------------------------------- | ------------------------------------------------------------------------------- | -| `SUBMINER_APPIMAGE_PATH` | Override SubMiner app binary path for launcher playback commands | -| `SUBMINER_BINARY_PATH` | Alias for `SUBMINER_APPIMAGE_PATH` | -| `SUBMINER_ROFI_THEME` | Override rofi theme path for launcher picker | +| Variable | Description | +| ---------------------------------- | ------------------------------------------------------------------------------ | +| `SUBMINER_APPIMAGE_PATH` | Override SubMiner app binary path for launcher playback commands | +| `SUBMINER_BINARY_PATH` | Alias for `SUBMINER_APPIMAGE_PATH` | +| `SUBMINER_ROFI_THEME` | Override rofi theme path for launcher picker | | `SUBMINER_LOG_LEVEL` | Override app logger level (`debug`, `info`, `warn`, `error`) | -| `SUBMINER_MPV_LOG` | Override mpv/app shared log file path | -| `SUBMINER_YT_SUBGEN_MODE` | Override `youtubeSubgen.mode` for launcher | -| `SUBMINER_WHISPER_BIN` | Override `youtubeSubgen.whisperBin` for launcher | -| `SUBMINER_WHISPER_MODEL` | Override `youtubeSubgen.whisperModel` for launcher | -| `SUBMINER_YT_SUBGEN_OUT_DIR` | Override generated subtitle output directory | -| `SUBMINER_YT_SUBGEN_AUDIO_FORMAT` | Override extraction format used for whisper fallback | -| `SUBMINER_YT_SUBGEN_KEEP_TEMP` | Set to `1` to keep temporary subtitle-generation workspace | -| `SUBMINER_JIMAKU_API_KEY` | Override Jimaku API key for launcher subtitle downloads | -| `SUBMINER_JIMAKU_API_KEY_COMMAND` | Command used to resolve Jimaku API key at runtime | -| `SUBMINER_JIMAKU_API_BASE_URL` | Override Jimaku API base URL | -| `SUBMINER_JELLYFIN_ACCESS_TOKEN` | Override Jellyfin access token (used before stored encrypted session fallback) | -| `SUBMINER_JELLYFIN_USER_ID` | Optional Jellyfin user ID override | +| `SUBMINER_MPV_LOG` | Override mpv/app shared log file path | +| `SUBMINER_YT_SUBGEN_MODE` | Override `youtubeSubgen.mode` for launcher | +| `SUBMINER_WHISPER_BIN` | Override `youtubeSubgen.whisperBin` for launcher | +| `SUBMINER_WHISPER_MODEL` | Override `youtubeSubgen.whisperModel` for launcher | +| `SUBMINER_YT_SUBGEN_OUT_DIR` | Override generated subtitle output directory | +| `SUBMINER_YT_SUBGEN_AUDIO_FORMAT` | Override extraction format used for whisper fallback | +| `SUBMINER_YT_SUBGEN_KEEP_TEMP` | Set to `1` to keep temporary subtitle-generation workspace | +| `SUBMINER_JIMAKU_API_KEY` | Override Jimaku API key for launcher subtitle downloads | +| `SUBMINER_JIMAKU_API_KEY_COMMAND` | Command used to resolve Jimaku API key at runtime | +| `SUBMINER_JIMAKU_API_BASE_URL` | Override Jimaku API base URL | +| `SUBMINER_JELLYFIN_ACCESS_TOKEN` | Override Jellyfin access token (used before stored encrypted session fallback) | +| `SUBMINER_JELLYFIN_USER_ID` | Optional Jellyfin user ID override | | `SUBMINER_SKIP_MACOS_HELPER_BUILD` | Set to `1` to skip building the macOS helper binary during `bun run build` | diff --git a/docs/installation.md b/docs/installation.md index 3371b24..7061095 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -6,6 +6,7 @@ | Dependency | Required | Notes | | -------------------- | ---------- | -------------------------------------------------------- | +| Bun | Yes | Required for `subminer` wrapper and source workflows | | mpv | Yes | Must support IPC sockets (`--input-ipc-server`) | | ffmpeg | For media | Audio extraction and screenshot generation | | MeCab + mecab-ipadic | No | Optional fallback tokenizer for Japanese | @@ -24,16 +25,15 @@ ### Optional Tools -| Tool | Purpose | -| ----------------- | ------------------------------------------ | -| fzf | Terminal-based video picker (default) | -| rofi | GUI-based video picker | -| chafa | Thumbnail previews in fzf | -| ffmpegthumbnailer | Generate video thumbnails for picker | +| Tool | Purpose | +| ----------------- | ------------------------------------------------------------- | +| fzf | Terminal-based video picker (default) | +| rofi | GUI-based video picker | +| chafa | Thumbnail previews in fzf | +| ffmpegthumbnailer | Generate video thumbnails for picker | | guessit | Better AniSkip title/season/episode parsing for file playback | -| alass | Subtitle sync engine (preferred) | -| ffsubsync | Subtitle sync engine (fallback) | -| Bun | Required for the `subminer` wrapper script | +| alass | Subtitle sync engine (preferred) | +| ffsubsync | Subtitle sync engine (fallback) | ## Linux @@ -88,7 +88,7 @@ brew install mpv mecab mecab-ipadic git clone https://github.com/ksyasuda/SubMiner.git cd SubMiner bun install -cd vendor/texthooker-ui && pnpm install --frozen-lockfile && pnpm run build && cd ../.. +cd vendor/texthooker-ui && bun install --frozen-lockfile && bun run build && cd ../.. bun run build:mac ``` diff --git a/docs/subagents/INDEX.md b/docs/subagents/INDEX.md index 1a08d61..91b23c8 100644 --- a/docs/subagents/INDEX.md +++ b/docs/subagents/INDEX.md @@ -94,3 +94,5 @@ Read first. Keep concise. | `codex-architecture-doc-refresh-20260223T033941Z-d6se` | `codex-architecture-doc-refresh` | `Review repository architecture surfaces and refresh docs/architecture.md content to match current code state.` | `handoff` | `docs/subagents/agents/codex-architecture-doc-refresh-20260223T033941Z-d6se.md` | `2026-02-23T03:44:17Z` | | `codex-docs-video-thumb-cache-20260223T033929Z-k8p2` | `codex-docs-video-thumb-cache` | `Fix docs landing page demo video thumbnail staleness after direct asset replacement.` | `handoff` | `docs/subagents/agents/codex-docs-video-thumb-cache-20260223T033929Z-k8p2.md` | `2026-02-23T03:44:04Z` | | `codex-development-docs-review-20260223T034520Z-2ebb` | `codex-development-docs-review` | `Review codebase and refresh docs/development.md to match current project state.` | `done` | `docs/subagents/agents/codex-development-docs-review-20260223T034520Z-2ebb.md` | `2026-02-23T03:49:16Z` | +| `opencode-bun-migration-20260223T043000Z-k9m2` | `opencode-bun-migration` | `Execute TASK-115 Bun-only migration plan: parity map, dist/utility script migration, CI/docs cutover.` | `handoff` | `docs/subagents/agents/opencode-bun-migration-20260223T043000Z-k9m2.md` | `2026-02-23T04:36:00Z` | +| `opencode-initial-release-plan-20260223T044059Z-p7k2` | `opencode-initial-release-plan` | `Analyze main history and draft copy/paste initial-release history-cleanup plan.` | `planning` | `docs/subagents/agents/opencode-initial-release-plan-20260223T044059Z-p7k2.md` | `2026-02-23T04:40:59Z` | diff --git a/docs/subagents/agents/opencode-bun-migration-20260223T043000Z-k9m2.md b/docs/subagents/agents/opencode-bun-migration-20260223T043000Z-k9m2.md new file mode 100644 index 0000000..ab456ba --- /dev/null +++ b/docs/subagents/agents/opencode-bun-migration-20260223T043000Z-k9m2.md @@ -0,0 +1,61 @@ +# Agent Log: opencode-bun-migration-20260223T043000Z-k9m2 + +- alias: `opencode-bun-migration` +- mission: `Execute TASK-115 Bun-only migration plan: parity map, dist/utility script migration, CI/docs cutover.` +- status: `handoff` +- started_utc: `2026-02-23T04:30:00Z` +- backlog: `TASK-115` (children: `TASK-115.2`, `TASK-115.3`, `TASK-115.4`, `TASK-115.1`) + +## Intent + +- Execute user-selected Subagent-Driven option for Bun-only migration. +- Sequence: `TASK-115.2` -> (`TASK-115.3` + `TASK-115.4`) -> `TASK-115.1`. + +## Planned Files + +- `package.json` +- `.github/workflows/ci.yml` +- `.github/workflows/release.yml` +- `docs/development.md` +- `docs/installation.md` +- `README.md` +- `docs/plans/bun-only-parity-matrix.md` + +## Assumptions + +- Bun can execute dist test files currently run by `node --test`. +- Electron-targeted utility scripts can keep Electron runtime while shifting command wrappers to Bun. +- No behavior changes intended; tooling/runtime-command migration only. + +## Phase Updates + +- `2026-02-23T04:30:00Z` start: loaded subagent index/collaboration, created agent row/file, beginning implementation on `TASK-115.2`. +- `2026-02-23T04:36:00Z` completed: migrated dist/utility scripts from Node invocations to Bun, removed Node setup from CI/release workflows, updated setup docs, added Bun parity matrix, and validated full Bun command gate suite. + +## Files Touched + +- `.github/workflows/ci.yml` +- `.github/workflows/release.yml` +- `package.json` +- `docs/development.md` +- `docs/installation.md` +- `docs/plans/bun-only-parity-matrix.md` +- `launcher/smoke.e2e.test.ts` +- `src/core/services/anilist/anilist-token-store.ts` +- `src/core/services/anilist/anilist-token-store.test.ts` + +## Validation + +- `bun run test:launcher:smoke:src` +- `bun run test:fast` +- `bun run build` +- `bun run test:config:dist` +- `bun run test:core:dist` +- `bun run test:smoke:dist` +- `bun run docs:build` +- `bun run generate:config-example` + +## Handoff + +- Backlog status synced: `TASK-115`, `TASK-115.1`, `TASK-115.2`, `TASK-115.3`, `TASK-115.4` set Done with AC evidence. +- Note: `config.example.jsonc` and `docs/public/config.example.jsonc` were regenerated by `bun run generate:config-example` during validation. diff --git a/docs/subagents/collaboration.md b/docs/subagents/collaboration.md index 635193b..45a4e89 100644 --- a/docs/subagents/collaboration.md +++ b/docs/subagents/collaboration.md @@ -166,3 +166,6 @@ Shared notes. Append-only. - [2026-02-23T03:44:17Z] [codex-architecture-doc-refresh-20260223T033941Z-d6se|codex-architecture-doc-refresh] completed architecture drift pass: refreshed `docs/architecture.md` structure/service/composition/lifecycle content against current code (`src`, `launcher`, `plugin`), left mermaid sections untouched, and verified `bun run docs:build`; moved backlog linkage to `TASK-113` to avoid active `TASK-112` collision. - [2026-02-23T03:46:06Z] [codex-development-docs-review-20260223T034520Z-2ebb|codex-development-docs-review] starting user-requested thorough codebase + `docs/development.md` drift audit; scope docs refresh + verification only, no runtime behavior changes expected. - [2026-02-23T03:49:16Z] [codex-development-docs-review-20260223T034520Z-2ebb|codex-development-docs-review] completed `docs/development.md` refresh: setup/deps/submodule instructions corrected, CI-parity testing lane documented, placeholder subtitle test status clarified, Makefile reference adjusted, env variable table expanded to active launcher/runtime overrides; `bun run docs:build` passed. +- [2026-02-23T04:30:00Z] [opencode-bun-migration-20260223T043000Z-k9m2|opencode-bun-migration] starting TASK-115 Bun-only migration execution; initial scope `package.json`, CI/release workflows, and setup docs to remove Node requirements after parity checks. +- [2026-02-23T04:36:00Z] [opencode-bun-migration-20260223T043000Z-k9m2|opencode-bun-migration] completed TASK-115 Bun-only migration pass: dist/utility commands moved off direct Node invocation, CI/release Node setup removed, Bun parity matrix + docs updates landed, full Bun validation gate suite passed, and TASK-115 + child tasks finalized Done in Backlog. +- [2026-02-23T04:40:59Z] [opencode-initial-release-plan-20260223T044059Z-p7k2|opencode-initial-release-plan] starting user-requested release-history cleanup planning pass; scope git-history analysis + current-state review + `initial-release.md` command playbook. diff --git a/launcher/smoke.e2e.test.ts b/launcher/smoke.e2e.test.ts index ea208bd..4135e7d 100644 --- a/launcher/smoke.e2e.test.ts +++ b/launcher/smoke.e2e.test.ts @@ -62,7 +62,7 @@ function createSmokeCase(name: string): SmokeCase { writeExecutable( fakeMpvPath, - `#!/usr/bin/env node + `#!/usr/bin/env bun const fs = require('node:fs'); const net = require('node:net'); const path = require('node:path'); @@ -101,7 +101,7 @@ process.on('SIGTERM', closeAndExit); writeExecutable( fakeAppPath, - `#!/usr/bin/env node + `#!/usr/bin/env bun const fs = require('node:fs'); const logPath = ${JSON.stringify(fakeAppLogPath)}; diff --git a/package.json b/package.json index 5b35b43..1f7cc68 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,13 @@ "format": "prettier --write .", "format:check": "prettier --check .", "test:config:src": "bun test src/config/config.test.ts src/config/path-resolution.test.ts src/config/resolve/anki-connect.test.ts src/config/resolve/subtitle-style.test.ts src/config/resolve/jellyfin.test.ts src/config/definitions/domain-registry.test.ts", - "test:config:dist": "node --test dist/config/config.test.js dist/config/path-resolution.test.js dist/config/resolve/anki-connect.test.js dist/config/resolve/subtitle-style.test.js dist/config/resolve/jellyfin.test.js dist/config/definitions/domain-registry.test.js", - "test:config:smoke:dist": "node --test dist/config/path-resolution.test.js", + "test:config:dist": "bun test dist/config/config.test.js dist/config/path-resolution.test.js dist/config/resolve/anki-connect.test.js dist/config/resolve/subtitle-style.test.js dist/config/resolve/jellyfin.test.js dist/config/definitions/domain-registry.test.js", + "test:config:smoke:dist": "bun test dist/config/path-resolution.test.js", "test:launcher:smoke:src": "bun test launcher/smoke.e2e.test.ts", "test:launcher:src": "bun test launcher/config.test.ts launcher/config-domain-parsers.test.ts launcher/parse-args.test.ts launcher/main.test.ts launcher/commands/command-modules.test.ts launcher/smoke.e2e.test.ts", "test:core:src": "bun test src/cli/args.test.ts src/cli/help.test.ts src/core/services/cli-command.test.ts src/core/services/field-grouping-overlay.test.ts src/core/services/numeric-shortcut-session.test.ts src/core/services/secondary-subtitle.test.ts src/core/services/mpv-render-metrics.test.ts src/core/services/overlay-content-measurement.test.ts src/core/services/mpv-control.test.ts src/core/services/mpv.test.ts src/core/services/runtime-options-ipc.test.ts src/core/services/runtime-config.test.ts src/core/services/config-hot-reload.test.ts src/core/services/discord-presence.test.ts src/core/services/tokenizer.test.ts src/core/services/tokenizer/annotation-stage.test.ts src/core/services/tokenizer/parser-selection-stage.test.ts src/core/services/tokenizer/parser-enrichment-stage.test.ts src/core/services/subsync.test.ts src/core/services/overlay-bridge.test.ts src/core/services/overlay-shortcut-handler.test.ts src/core/services/mining.test.ts src/core/services/anki-jimaku.test.ts src/core/services/jellyfin.test.ts src/core/services/jellyfin-remote.test.ts src/core/services/immersion-tracker-service.test.ts src/core/services/app-ready.test.ts src/core/services/startup-bootstrap.test.ts src/core/services/subtitle-processing-controller.test.ts src/core/services/anilist/anilist-update-queue.test.ts src/core/utils/shortcut-config.test.ts src/renderer/error-recovery.test.ts src/subsync/utils.test.ts src/main/anilist-url-guard.test.ts src/window-trackers/x11-tracker.test.ts launcher/config.test.ts launcher/config-domain-parsers.test.ts launcher/parse-args.test.ts launcher/main.test.ts launcher/commands/command-modules.test.ts", - "test:core:dist": "node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command.test.js dist/core/services/ipc.test.js dist/core/services/anki-jimaku-ipc.test.js dist/core/services/field-grouping-overlay.test.js dist/core/services/numeric-shortcut-session.test.js dist/core/services/secondary-subtitle.test.js dist/core/services/mpv-render-metrics.test.js dist/core/services/overlay-content-measurement.test.js dist/core/services/mpv-control.test.js dist/core/services/mpv.test.js dist/core/services/runtime-options-ipc.test.js dist/core/services/runtime-config.test.js dist/core/services/config-hot-reload.test.js dist/core/services/discord-presence.test.js dist/core/services/tokenizer.test.js dist/core/services/tokenizer/annotation-stage.test.js dist/core/services/tokenizer/parser-selection-stage.test.js dist/core/services/tokenizer/parser-enrichment-stage.test.js dist/core/services/subsync.test.js dist/core/services/overlay-bridge.test.js dist/core/services/overlay-manager.test.js dist/core/services/overlay-shortcut-handler.test.js dist/core/services/mining.test.js dist/core/services/anki-jimaku.test.js dist/core/services/jellyfin.test.js dist/core/services/jellyfin-remote.test.js dist/core/services/immersion-tracker-service.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js dist/core/services/subtitle-processing-controller.test.js dist/core/services/anilist/anilist-token-store.test.js dist/core/services/anilist/anilist-update-queue.test.js dist/renderer/error-recovery.test.js dist/subsync/utils.test.js dist/main/anilist-url-guard.test.js dist/window-trackers/x11-tracker.test.js", - "test:core:smoke:dist": "node --test dist/cli/help.test.js dist/core/services/runtime-config.test.js dist/core/services/ipc.test.js dist/core/services/overlay-manager.test.js dist/core/services/anilist/anilist-token-store.test.js dist/core/services/startup-bootstrap.test.js dist/renderer/error-recovery.test.js dist/main/anilist-url-guard.test.js dist/window-trackers/x11-tracker.test.js", + "test:core:dist": "bun test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command.test.js dist/core/services/ipc.test.js dist/core/services/anki-jimaku-ipc.test.js dist/core/services/field-grouping-overlay.test.js dist/core/services/numeric-shortcut-session.test.js dist/core/services/secondary-subtitle.test.js dist/core/services/mpv-render-metrics.test.js dist/core/services/overlay-content-measurement.test.js dist/core/services/mpv-control.test.js dist/core/services/mpv.test.js dist/core/services/runtime-options-ipc.test.js dist/core/services/runtime-config.test.js dist/core/services/config-hot-reload.test.js dist/core/services/discord-presence.test.js dist/core/services/tokenizer.test.js dist/core/services/tokenizer/annotation-stage.test.js dist/core/services/tokenizer/parser-selection-stage.test.js dist/core/services/tokenizer/parser-enrichment-stage.test.js dist/core/services/subsync.test.js dist/core/services/overlay-bridge.test.js dist/core/services/overlay-manager.test.js dist/core/services/overlay-shortcut-handler.test.js dist/core/services/mining.test.js dist/core/services/anki-jimaku.test.js dist/core/services/jellyfin.test.js dist/core/services/jellyfin-remote.test.js dist/core/services/immersion-tracker-service.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js dist/core/services/subtitle-processing-controller.test.js dist/core/services/anilist/anilist-token-store.test.js dist/core/services/anilist/anilist-update-queue.test.js dist/renderer/error-recovery.test.js dist/subsync/utils.test.js dist/main/anilist-url-guard.test.js dist/window-trackers/x11-tracker.test.js", + "test:core:smoke:dist": "bun test dist/cli/help.test.js dist/core/services/runtime-config.test.js dist/core/services/ipc.test.js dist/core/services/overlay-manager.test.js dist/core/services/anilist/anilist-token-store.test.js dist/core/services/startup-bootstrap.test.js dist/renderer/error-recovery.test.js dist/main/anilist-url-guard.test.js dist/window-trackers/x11-tracker.test.js", "test:smoke:dist": "bun run test:config:smoke:dist && bun run test:core:smoke:dist", "test:subtitle:dist": "echo \"Subtitle tests are currently not configured\"", "test": "bun run test:config && bun run test:core", @@ -32,7 +32,7 @@ "test:core": "bun run test:core:src", "test:subtitle": "bun run build && bun run test:subtitle:dist", "test:fast": "bun run test:config:src && bun run test:core:src", - "generate:config-example": "bun run build && node dist/generate-config-example.js", + "generate:config-example": "bun run build && bun dist/generate-config-example.js", "start": "bun run build && electron . --start", "dev": "bun run build && electron . --start --dev", "stop": "electron . --stop", diff --git a/src/core/services/anilist/anilist-token-store.test.ts b/src/core/services/anilist/anilist-token-store.test.ts index e7f9008..cf9a4e7 100644 --- a/src/core/services/anilist/anilist-token-store.test.ts +++ b/src/core/services/anilist/anilist-token-store.test.ts @@ -3,9 +3,8 @@ import assert from 'node:assert/strict'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { safeStorage } from 'electron'; -import { createAnilistTokenStore } from './anilist-token-store'; +import { createAnilistTokenStore, type SafeStorageLike } from './anilist-token-store'; function createTempTokenFile(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-anilist-token-')); @@ -20,151 +19,67 @@ function createLogger() { }; } -type SafeStorageLike = { - isEncryptionAvailable: () => boolean; - encryptString: (value: string) => Buffer; - decryptString: (value: Buffer) => string; -}; - -const safeStorageApi = safeStorage as unknown as Partial; -const hasSafeStorage = - typeof safeStorageApi?.isEncryptionAvailable === 'function' && - typeof safeStorageApi?.encryptString === 'function' && - typeof safeStorageApi?.decryptString === 'function'; - -const originalSafeStorage: SafeStorageLike | null = hasSafeStorage - ? { - isEncryptionAvailable: safeStorageApi.isEncryptionAvailable as () => boolean, - encryptString: safeStorageApi.encryptString as (value: string) => Buffer, - decryptString: safeStorageApi.decryptString as (value: Buffer) => string, - } - : null; - -function mockSafeStorage(encryptionAvailable: boolean): void { - if (!hasSafeStorage) return; - ( - safeStorage as unknown as { - isEncryptionAvailable: typeof safeStorage.isEncryptionAvailable; - encryptString: typeof safeStorage.encryptString; - decryptString: typeof safeStorage.decryptString; - } - ).isEncryptionAvailable = () => encryptionAvailable; - ( - safeStorage as unknown as { - encryptString: typeof safeStorage.encryptString; - decryptString: typeof safeStorage.decryptString; - } - ).encryptString = (value: string) => Buffer.from(`enc:${value}`, 'utf-8'); - ( - safeStorage as unknown as { - decryptString: typeof safeStorage.decryptString; - } - ).decryptString = (value: Buffer) => { - const raw = value.toString('utf-8'); - return raw.startsWith('enc:') ? raw.slice(4) : raw; +function createStorage(encryptionAvailable: boolean): SafeStorageLike { + return { + isEncryptionAvailable: () => encryptionAvailable, + encryptString: (value: string) => Buffer.from(`enc:${value}`, 'utf-8'), + decryptString: (value: Buffer) => { + const raw = value.toString('utf-8'); + return raw.startsWith('enc:') ? raw.slice(4) : raw; + }, }; } -function restoreSafeStorage(): void { - if (!hasSafeStorage || !originalSafeStorage) return; - ( - safeStorage as unknown as { - isEncryptionAvailable: typeof safeStorage.isEncryptionAvailable; - encryptString: typeof safeStorage.encryptString; - decryptString: typeof safeStorage.decryptString; - } - ).isEncryptionAvailable = originalSafeStorage.isEncryptionAvailable; - ( - safeStorage as unknown as { - encryptString: typeof safeStorage.encryptString; - decryptString: typeof safeStorage.decryptString; - } - ).encryptString = originalSafeStorage.encryptString; - ( - safeStorage as unknown as { - decryptString: typeof safeStorage.decryptString; - } - ).decryptString = originalSafeStorage.decryptString; -} +test('anilist token store saves and loads encrypted token', () => { + const filePath = createTempTokenFile(); + const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true)); + store.saveToken(' demo-token '); -test('anilist token store saves and loads encrypted token', { skip: !hasSafeStorage }, () => { - mockSafeStorage(true); - try { - const filePath = createTempTokenFile(); - const store = createAnilistTokenStore(filePath, createLogger()); - store.saveToken(' demo-token '); - - const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { - encryptedToken?: string; - plaintextToken?: string; - }; - assert.equal(typeof payload.encryptedToken, 'string'); - assert.equal(payload.plaintextToken, undefined); - assert.equal(store.loadToken(), 'demo-token'); - } finally { - restoreSafeStorage(); - } + const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { + encryptedToken?: string; + plaintextToken?: string; + }; + assert.equal(typeof payload.encryptedToken, 'string'); + assert.equal(payload.plaintextToken, undefined); + assert.equal(store.loadToken(), 'demo-token'); }); -test( - 'anilist token store falls back to plaintext when encryption unavailable', - { skip: !hasSafeStorage }, - () => { - mockSafeStorage(false); - try { - const filePath = createTempTokenFile(); - const store = createAnilistTokenStore(filePath, createLogger()); - store.saveToken('plain-token'); +test('anilist token store falls back to plaintext when encryption unavailable', () => { + const filePath = createTempTokenFile(); + const store = createAnilistTokenStore(filePath, createLogger(), createStorage(false)); + store.saveToken('plain-token'); - const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { - plaintextToken?: string; - }; - assert.equal(payload.plaintextToken, 'plain-token'); - assert.equal(store.loadToken(), 'plain-token'); - } finally { - restoreSafeStorage(); - } - }, -); - -test( - 'anilist token store migrates legacy plaintext to encrypted', - { skip: !hasSafeStorage }, - () => { - const filePath = createTempTokenFile(); - fs.writeFileSync( - filePath, - JSON.stringify({ plaintextToken: 'legacy-token', updatedAt: Date.now() }), - 'utf-8', - ); - - mockSafeStorage(true); - try { - const store = createAnilistTokenStore(filePath, createLogger()); - assert.equal(store.loadToken(), 'legacy-token'); - - const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { - encryptedToken?: string; - plaintextToken?: string; - }; - assert.equal(typeof payload.encryptedToken, 'string'); - assert.equal(payload.plaintextToken, undefined); - } finally { - restoreSafeStorage(); - } - }, -); - -test('anilist token store clears persisted token file', { skip: !hasSafeStorage }, () => { - mockSafeStorage(true); - try { - const filePath = createTempTokenFile(); - const store = createAnilistTokenStore(filePath, createLogger()); - store.saveToken('to-clear'); - assert.equal(fs.existsSync(filePath), true); - store.clearToken(); - assert.equal(fs.existsSync(filePath), false); - } finally { - restoreSafeStorage(); - } + const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { + plaintextToken?: string; + }; + assert.equal(payload.plaintextToken, 'plain-token'); + assert.equal(store.loadToken(), 'plain-token'); +}); + +test('anilist token store migrates legacy plaintext to encrypted', () => { + const filePath = createTempTokenFile(); + fs.writeFileSync( + filePath, + JSON.stringify({ plaintextToken: 'legacy-token', updatedAt: Date.now() }), + 'utf-8', + ); + + const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true)); + assert.equal(store.loadToken(), 'legacy-token'); + + const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as { + encryptedToken?: string; + plaintextToken?: string; + }; + assert.equal(typeof payload.encryptedToken, 'string'); + assert.equal(payload.plaintextToken, undefined); +}); + +test('anilist token store clears persisted token file', () => { + const filePath = createTempTokenFile(); + const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true)); + store.saveToken('to-clear'); + assert.equal(fs.existsSync(filePath), true); + store.clearToken(); + assert.equal(fs.existsSync(filePath), false); }); diff --git a/src/core/services/anilist/anilist-token-store.ts b/src/core/services/anilist/anilist-token-store.ts index 83d7dd1..d89ded1 100644 --- a/src/core/services/anilist/anilist-token-store.ts +++ b/src/core/services/anilist/anilist-token-store.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { safeStorage } from 'electron'; +import * as electron from 'electron'; interface PersistedTokenPayload { encryptedToken?: string; @@ -14,6 +14,12 @@ export interface AnilistTokenStore { clearToken: () => void; } +export interface SafeStorageLike { + isEncryptionAvailable: () => boolean; + encryptString: (value: string) => Buffer; + decryptString: (value: Buffer) => string; +} + function ensureDirectory(filePath: string): void { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { @@ -33,6 +39,7 @@ export function createAnilistTokenStore( warn: (message: string, details?: unknown) => void; error: (message: string, details?: unknown) => void; }, + storage: SafeStorageLike = electron.safeStorage, ): AnilistTokenStore { return { loadToken(): string | null { @@ -44,11 +51,11 @@ export function createAnilistTokenStore( const parsed = JSON.parse(raw) as PersistedTokenPayload; if (typeof parsed.encryptedToken === 'string' && parsed.encryptedToken.length > 0) { const encrypted = Buffer.from(parsed.encryptedToken, 'base64'); - if (!safeStorage.isEncryptionAvailable()) { + if (!storage.isEncryptionAvailable()) { logger.warn('AniList token encryption is not available on this system.'); return null; } - const decrypted = safeStorage.decryptString(encrypted).trim(); + const decrypted = storage.decryptString(encrypted).trim(); return decrypted.length > 0 ? decrypted : null; } if (typeof parsed.plaintextToken === 'string' && parsed.plaintextToken.trim().length > 0) { @@ -70,7 +77,7 @@ export function createAnilistTokenStore( return; } try { - if (!safeStorage.isEncryptionAvailable()) { + if (!storage.isEncryptionAvailable()) { logger.warn('AniList token encryption unavailable; storing token in plaintext fallback.'); writePayload(filePath, { plaintextToken: trimmed, @@ -78,7 +85,7 @@ export function createAnilistTokenStore( }); return; } - const encrypted = safeStorage.encryptString(trimmed); + const encrypted = storage.encryptString(trimmed); writePayload(filePath, { encryptedToken: encrypted.toString('base64'), updatedAt: Date.now(),