mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
chore: migrate repo workflows to Bun-only runtime
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -20,11 +20,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: 1.3.5
|
bun-version: 1.3.5
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -26,11 +26,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: 1.3.5
|
bun-version: 1.3.5
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -78,11 +73,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: 1.3.5
|
bun-version: 1.3.5
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -128,11 +118,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bun-version: 1.3.5
|
bun-version: 1.3.5
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ subminer video.mkv
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
| Required | Optional |
|
| Required | Optional |
|
||||||
| ------------------------------------------ | ---------------------------- |
|
| ------------------------------------------ | -------------------------------------------------- |
|
||||||
|
| `bun` | |
|
||||||
| `mpv` with IPC socket | `yt-dlp` |
|
| `mpv` with IPC socket | `yt-dlp` |
|
||||||
| `ffmpeg` | `guessit` (better AniSkip title/episode detection) |
|
| `ffmpeg` | `guessit` (better AniSkip title/episode detection) |
|
||||||
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
|
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [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.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [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.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [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.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Replace Node-runner dist test commands with Bun-based dist verification while preserving existing test scope and confidence in compiled artifact behavior.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [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.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Update utility command entrypoints that still rely on Node invocation so maintenance and diagnostics workflows can be run entirely through Bun.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [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.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
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.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/) (LTS)
|
|
||||||
- [Bun](https://bun.sh)
|
- [Bun](https://bun.sh)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
@@ -151,7 +150,7 @@ Run `make help` for a full list of targets. Key ones:
|
|||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| ---------------------------------- | ------------------------------------------------------------------------------- |
|
| ---------------------------------- | ------------------------------------------------------------------------------ |
|
||||||
| `SUBMINER_APPIMAGE_PATH` | Override SubMiner app binary path for launcher playback commands |
|
| `SUBMINER_APPIMAGE_PATH` | Override SubMiner app binary path for launcher playback commands |
|
||||||
| `SUBMINER_BINARY_PATH` | Alias for `SUBMINER_APPIMAGE_PATH` |
|
| `SUBMINER_BINARY_PATH` | Alias for `SUBMINER_APPIMAGE_PATH` |
|
||||||
| `SUBMINER_ROFI_THEME` | Override rofi theme path for launcher picker |
|
| `SUBMINER_ROFI_THEME` | Override rofi theme path for launcher picker |
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
| Dependency | Required | Notes |
|
| Dependency | Required | Notes |
|
||||||
| -------------------- | ---------- | -------------------------------------------------------- |
|
| -------------------- | ---------- | -------------------------------------------------------- |
|
||||||
|
| Bun | Yes | Required for `subminer` wrapper and source workflows |
|
||||||
| mpv | Yes | Must support IPC sockets (`--input-ipc-server`) |
|
| mpv | Yes | Must support IPC sockets (`--input-ipc-server`) |
|
||||||
| ffmpeg | For media | Audio extraction and screenshot generation |
|
| ffmpeg | For media | Audio extraction and screenshot generation |
|
||||||
| MeCab + mecab-ipadic | No | Optional fallback tokenizer for Japanese |
|
| MeCab + mecab-ipadic | No | Optional fallback tokenizer for Japanese |
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
### Optional Tools
|
### Optional Tools
|
||||||
|
|
||||||
| Tool | Purpose |
|
| Tool | Purpose |
|
||||||
| ----------------- | ------------------------------------------ |
|
| ----------------- | ------------------------------------------------------------- |
|
||||||
| fzf | Terminal-based video picker (default) |
|
| fzf | Terminal-based video picker (default) |
|
||||||
| rofi | GUI-based video picker |
|
| rofi | GUI-based video picker |
|
||||||
| chafa | Thumbnail previews in fzf |
|
| chafa | Thumbnail previews in fzf |
|
||||||
@@ -33,7 +34,6 @@
|
|||||||
| guessit | Better AniSkip title/season/episode parsing for file playback |
|
| guessit | Better AniSkip title/season/episode parsing for file playback |
|
||||||
| alass | Subtitle sync engine (preferred) |
|
| alass | Subtitle sync engine (preferred) |
|
||||||
| ffsubsync | Subtitle sync engine (fallback) |
|
| ffsubsync | Subtitle sync engine (fallback) |
|
||||||
| Bun | Required for the `subminer` wrapper script |
|
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ brew install mpv mecab mecab-ipadic
|
|||||||
git clone https://github.com/ksyasuda/SubMiner.git
|
git clone https://github.com/ksyasuda/SubMiner.git
|
||||||
cd SubMiner
|
cd SubMiner
|
||||||
bun install
|
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
|
bun run build:mac
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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-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-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` |
|
| `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` |
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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: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: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-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.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ function createSmokeCase(name: string): SmokeCase {
|
|||||||
|
|
||||||
writeExecutable(
|
writeExecutable(
|
||||||
fakeMpvPath,
|
fakeMpvPath,
|
||||||
`#!/usr/bin/env node
|
`#!/usr/bin/env bun
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const net = require('node:net');
|
const net = require('node:net');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
@@ -101,7 +101,7 @@ process.on('SIGTERM', closeAndExit);
|
|||||||
|
|
||||||
writeExecutable(
|
writeExecutable(
|
||||||
fakeAppPath,
|
fakeAppPath,
|
||||||
`#!/usr/bin/env node
|
`#!/usr/bin/env bun
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
|
|
||||||
const logPath = ${JSON.stringify(fakeAppLogPath)};
|
const logPath = ${JSON.stringify(fakeAppLogPath)};
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -17,13 +17,13 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"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: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: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": "node --test dist/config/path-resolution.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: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: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: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: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": "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: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: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:subtitle:dist": "echo \"Subtitle tests are currently not configured\"",
|
||||||
"test": "bun run test:config && bun run test:core",
|
"test": "bun run test:config && bun run test:core",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"test:core": "bun run test:core:src",
|
"test:core": "bun run test:core:src",
|
||||||
"test:subtitle": "bun run build && bun run test:subtitle:dist",
|
"test:subtitle": "bun run build && bun run test:subtitle:dist",
|
||||||
"test:fast": "bun run test:config:src && bun run test:core:src",
|
"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",
|
"start": "bun run build && electron . --start",
|
||||||
"dev": "bun run build && electron . --start --dev",
|
"dev": "bun run build && electron . --start --dev",
|
||||||
"stop": "electron . --stop",
|
"stop": "electron . --stop",
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import assert from 'node:assert/strict';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
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 {
|
function createTempTokenFile(): string {
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-anilist-token-'));
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-anilist-token-'));
|
||||||
@@ -20,78 +19,20 @@ function createLogger() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type SafeStorageLike = {
|
function createStorage(encryptionAvailable: boolean): SafeStorageLike {
|
||||||
isEncryptionAvailable: () => boolean;
|
return {
|
||||||
encryptString: (value: string) => Buffer;
|
isEncryptionAvailable: () => encryptionAvailable,
|
||||||
decryptString: (value: Buffer) => string;
|
encryptString: (value: string) => Buffer.from(`enc:${value}`, 'utf-8'),
|
||||||
};
|
decryptString: (value: Buffer) => {
|
||||||
|
|
||||||
const safeStorageApi = safeStorage as unknown as Partial<SafeStorageLike>;
|
|
||||||
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');
|
const raw = value.toString('utf-8');
|
||||||
return raw.startsWith('enc:') ? raw.slice(4) : raw;
|
return raw.startsWith('enc:') ? raw.slice(4) : raw;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreSafeStorage(): void {
|
test('anilist token store saves and loads encrypted token', () => {
|
||||||
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', { skip: !hasSafeStorage }, () => {
|
|
||||||
mockSafeStorage(true);
|
|
||||||
try {
|
|
||||||
const filePath = createTempTokenFile();
|
const filePath = createTempTokenFile();
|
||||||
const store = createAnilistTokenStore(filePath, createLogger());
|
const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true));
|
||||||
store.saveToken(' demo-token ');
|
store.saveToken(' demo-token ');
|
||||||
|
|
||||||
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
||||||
@@ -101,19 +42,11 @@ test('anilist token store saves and loads encrypted token', { skip: !hasSafeStor
|
|||||||
assert.equal(typeof payload.encryptedToken, 'string');
|
assert.equal(typeof payload.encryptedToken, 'string');
|
||||||
assert.equal(payload.plaintextToken, undefined);
|
assert.equal(payload.plaintextToken, undefined);
|
||||||
assert.equal(store.loadToken(), 'demo-token');
|
assert.equal(store.loadToken(), 'demo-token');
|
||||||
} finally {
|
|
||||||
restoreSafeStorage();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test('anilist token store falls back to plaintext when encryption unavailable', () => {
|
||||||
'anilist token store falls back to plaintext when encryption unavailable',
|
|
||||||
{ skip: !hasSafeStorage },
|
|
||||||
() => {
|
|
||||||
mockSafeStorage(false);
|
|
||||||
try {
|
|
||||||
const filePath = createTempTokenFile();
|
const filePath = createTempTokenFile();
|
||||||
const store = createAnilistTokenStore(filePath, createLogger());
|
const store = createAnilistTokenStore(filePath, createLogger(), createStorage(false));
|
||||||
store.saveToken('plain-token');
|
store.saveToken('plain-token');
|
||||||
|
|
||||||
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
||||||
@@ -121,16 +54,9 @@ test(
|
|||||||
};
|
};
|
||||||
assert.equal(payload.plaintextToken, 'plain-token');
|
assert.equal(payload.plaintextToken, 'plain-token');
|
||||||
assert.equal(store.loadToken(), 'plain-token');
|
assert.equal(store.loadToken(), 'plain-token');
|
||||||
} finally {
|
});
|
||||||
restoreSafeStorage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
test('anilist token store migrates legacy plaintext to encrypted', () => {
|
||||||
'anilist token store migrates legacy plaintext to encrypted',
|
|
||||||
{ skip: !hasSafeStorage },
|
|
||||||
() => {
|
|
||||||
const filePath = createTempTokenFile();
|
const filePath = createTempTokenFile();
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
filePath,
|
filePath,
|
||||||
@@ -138,9 +64,7 @@ test(
|
|||||||
'utf-8',
|
'utf-8',
|
||||||
);
|
);
|
||||||
|
|
||||||
mockSafeStorage(true);
|
const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true));
|
||||||
try {
|
|
||||||
const store = createAnilistTokenStore(filePath, createLogger());
|
|
||||||
assert.equal(store.loadToken(), 'legacy-token');
|
assert.equal(store.loadToken(), 'legacy-token');
|
||||||
|
|
||||||
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
const payload = JSON.parse(fs.readFileSync(filePath, 'utf-8')) as {
|
||||||
@@ -149,22 +73,13 @@ test(
|
|||||||
};
|
};
|
||||||
assert.equal(typeof payload.encryptedToken, 'string');
|
assert.equal(typeof payload.encryptedToken, 'string');
|
||||||
assert.equal(payload.plaintextToken, undefined);
|
assert.equal(payload.plaintextToken, undefined);
|
||||||
} finally {
|
});
|
||||||
restoreSafeStorage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test('anilist token store clears persisted token file', { skip: !hasSafeStorage }, () => {
|
test('anilist token store clears persisted token file', () => {
|
||||||
mockSafeStorage(true);
|
|
||||||
try {
|
|
||||||
const filePath = createTempTokenFile();
|
const filePath = createTempTokenFile();
|
||||||
const store = createAnilistTokenStore(filePath, createLogger());
|
const store = createAnilistTokenStore(filePath, createLogger(), createStorage(true));
|
||||||
store.saveToken('to-clear');
|
store.saveToken('to-clear');
|
||||||
assert.equal(fs.existsSync(filePath), true);
|
assert.equal(fs.existsSync(filePath), true);
|
||||||
store.clearToken();
|
store.clearToken();
|
||||||
assert.equal(fs.existsSync(filePath), false);
|
assert.equal(fs.existsSync(filePath), false);
|
||||||
} finally {
|
|
||||||
restoreSafeStorage();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { safeStorage } from 'electron';
|
import * as electron from 'electron';
|
||||||
|
|
||||||
interface PersistedTokenPayload {
|
interface PersistedTokenPayload {
|
||||||
encryptedToken?: string;
|
encryptedToken?: string;
|
||||||
@@ -14,6 +14,12 @@ export interface AnilistTokenStore {
|
|||||||
clearToken: () => void;
|
clearToken: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SafeStorageLike {
|
||||||
|
isEncryptionAvailable: () => boolean;
|
||||||
|
encryptString: (value: string) => Buffer;
|
||||||
|
decryptString: (value: Buffer) => string;
|
||||||
|
}
|
||||||
|
|
||||||
function ensureDirectory(filePath: string): void {
|
function ensureDirectory(filePath: string): void {
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
@@ -33,6 +39,7 @@ export function createAnilistTokenStore(
|
|||||||
warn: (message: string, details?: unknown) => void;
|
warn: (message: string, details?: unknown) => void;
|
||||||
error: (message: string, details?: unknown) => void;
|
error: (message: string, details?: unknown) => void;
|
||||||
},
|
},
|
||||||
|
storage: SafeStorageLike = electron.safeStorage,
|
||||||
): AnilistTokenStore {
|
): AnilistTokenStore {
|
||||||
return {
|
return {
|
||||||
loadToken(): string | null {
|
loadToken(): string | null {
|
||||||
@@ -44,11 +51,11 @@ export function createAnilistTokenStore(
|
|||||||
const parsed = JSON.parse(raw) as PersistedTokenPayload;
|
const parsed = JSON.parse(raw) as PersistedTokenPayload;
|
||||||
if (typeof parsed.encryptedToken === 'string' && parsed.encryptedToken.length > 0) {
|
if (typeof parsed.encryptedToken === 'string' && parsed.encryptedToken.length > 0) {
|
||||||
const encrypted = Buffer.from(parsed.encryptedToken, 'base64');
|
const encrypted = Buffer.from(parsed.encryptedToken, 'base64');
|
||||||
if (!safeStorage.isEncryptionAvailable()) {
|
if (!storage.isEncryptionAvailable()) {
|
||||||
logger.warn('AniList token encryption is not available on this system.');
|
logger.warn('AniList token encryption is not available on this system.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const decrypted = safeStorage.decryptString(encrypted).trim();
|
const decrypted = storage.decryptString(encrypted).trim();
|
||||||
return decrypted.length > 0 ? decrypted : null;
|
return decrypted.length > 0 ? decrypted : null;
|
||||||
}
|
}
|
||||||
if (typeof parsed.plaintextToken === 'string' && parsed.plaintextToken.trim().length > 0) {
|
if (typeof parsed.plaintextToken === 'string' && parsed.plaintextToken.trim().length > 0) {
|
||||||
@@ -70,7 +77,7 @@ export function createAnilistTokenStore(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!safeStorage.isEncryptionAvailable()) {
|
if (!storage.isEncryptionAvailable()) {
|
||||||
logger.warn('AniList token encryption unavailable; storing token in plaintext fallback.');
|
logger.warn('AniList token encryption unavailable; storing token in plaintext fallback.');
|
||||||
writePayload(filePath, {
|
writePayload(filePath, {
|
||||||
plaintextToken: trimmed,
|
plaintextToken: trimmed,
|
||||||
@@ -78,7 +85,7 @@ export function createAnilistTokenStore(
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const encrypted = safeStorage.encryptString(trimmed);
|
const encrypted = storage.encryptString(trimmed);
|
||||||
writePayload(filePath, {
|
writePayload(filePath, {
|
||||||
encryptedToken: encrypted.toString('base64'),
|
encryptedToken: encrypted.toString('base64'),
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
|
|||||||
Reference in New Issue
Block a user