feat(launcher): bundle Bun runtime with launcher artifacts

- Use private Bun runtimes for installed and downloadable launchers
- Package runtime licenses, source manifests, and corresponding source releases
- Update launcher workflows, setup, updates, and documentation
This commit is contained in:
2026-09-11 00:53:52 -07:00
81 changed files with 7637 additions and 343 deletions
+14
View File
@@ -31,6 +31,14 @@
`bun run test:fast`
`bun run test:env`
`bun run build`
Confirm `dist/launcher` contains only `subminer`, `subminer.cmd`,
`subminer.js`, `prepare.cjs`, and `version`. Release CI smoke-tests
`subminer.js` with Bun, then publishes both wrapper files and their checksums.
Tagged CI runs `bun scripts/package-bun-source.mjs` and must publish
`bun-v<version>-source.tar.gz` plus its `.sha256` file. The script
fails if Bun's CMake dependency pins, WebKit pin, source checksums, patch
inputs, or collected license files differ from
`build/bun-source-manifest.json`.
When validating auto-update metadata, also run the relevant platform package
build and confirm `release/` contains the generated updater metadata
(`latest*.yml`) and blockmaps (`*.blockmap`).
@@ -54,6 +62,11 @@
`bun run test:fast`
`bun run test:env`
`bun run build`
Confirm both launcher wrappers and their checksums are included in the
prerelease assets.
Prerelease CI also assembles and publishes the pinned Bun corresponding
source archive. A missing source repository or license file fails the
release instead of publishing only the executable.
When validating packaged updater output, confirm the platform build writes
`latest*.yml` and `*.blockmap` files under `release/`.
5. Commit the prerelease prep (package.json version bump + the generated
@@ -97,6 +110,7 @@ Notes:
- AUR publish is best-effort: the workflow retries transient SSH clone/push failures, then warns and leaves the GitHub Release green if AUR still fails. Follow up with a manual `git push aur master` from the AUR checkout when needed.
- Required GitHub Actions secret: `AUR_SSH_PRIVATE_KEY`. Add the matching public key to your AUR account before relying on the automation.
- Release and prerelease workflows upload updater metadata (`latest*.yml`) and blockmaps (`*.blockmap`) alongside platform artifacts. Do not remove those files while `electron-updater` is enabled.
- Release and prerelease workflows publish `subminer` for POSIX systems and `subminer.cmd` for Windows. Both locate a packaged app and use its private Bun runtime. Keep the corresponding-source archive named `bun-v1.3.5-source.tar.gz`.
- macOS tray app updates use the standard `electron-updater`/Squirrel path. Keep `latest-mac.yml`, the macOS `SubMiner-<version>-mac.zip`, and ZIP blockmap published; Squirrel uses the ZIP payload even when the DMG remains the user-facing installer.
- macOS update metadata and full ZIP downloads are routed through `/usr/bin/curl` before Squirrel installation to avoid Electron main-process network crashes on update checks.
- Windows tray app updates use the standard `electron-updater`/NSIS path. Keep `latest.yml`, the Windows NSIS installer, and installer blockmap published; updater HTTP is routed through main-process fetch to avoid Electron main-process network crashes during update checks.
+9 -1
View File
@@ -10,11 +10,15 @@ Read when: runtime ownership, composition boundaries, or layering questions
SubMiner runs as three cooperating runtimes:
- Electron desktop app in `src/`
- Launcher CLI in `launcher/`
- Launcher CLI in `launcher/`, with managed app-installed wrappers in `src/main/runtime/managed-launcher.ts`
- mpv Lua plugin in `plugin/subminer/`
The desktop app keeps `src/main.ts` as composition root and pushes behavior into small runtime/domain modules.
Packaged apps include a private Bun runtime. Setup and release assets provide bootstrap wrappers generated from `src/main/runtime/*-launcher-bootstrap.ts`. macOS runs Bun and the CLI from app resources. Windows stages a versioned private Bun copy under `%LOCALAPPDATA%\SubMiner\launcher-runtime/<version>` so a running launcher does not lock the updater-owned app executable. Linux stages Bun, the matching CLI, and licenses under `${XDG_DATA_HOME:-~/.local/share}/SubMiner/launcher`. Its steady-state path performs one app `stat` and starts the cache without Electron. A missing cache or changed app fingerprint runs `launcher/prepare.cjs` through Electron's Node mode to refresh it. Desktop startup migrates recognized writable legacy JavaScript launchers and refreshes managed payloads after app changes. Development commands still use system Bun.
Update checks and startup launcher migration share a serialized update-state store. Deferred launcher paths are acknowledged only after migration succeeds or the candidate is no longer eligible. Running Windows launchers and unreadable or unwritable candidates remain pending for a later startup.
## Read Next
- [Domains](./domains.md) - who owns what
@@ -41,3 +45,7 @@ The desktop app keeps `src/main.ts` as composition root and pushes behavior into
- Composition over monoliths
- Pure helpers where possible
- Stable user behavior while internals evolve
Startup resolves and creates the user-data directory in `src/main-entry-runtime.ts`
before the entry process requests Electron's single-instance lock. Main-process
config bootstrap then writes the default config only when no config file exists.
+2
View File
@@ -27,6 +27,8 @@ Read when: you need to find the owner module for a behavior or test surface
Includes stats storage/query schema such as `imm_videos`, `imm_media_art`, and `imm_youtube_videos` for per-video and YouTube-specific library metadata.
Library-entry identity aliases and merge recommendations are persisted alongside this schema; the stats HTTP and SPA layers only expose and present those domain decisions.
`delete-maintenance-scheduler.ts` coalesces and serializes stats deletes; the expensive work runs in `delete-maintenance-worker-thread.ts` while the tracker queues playback writes. Each batch uses one transaction, lexical update, rollup refresh, and incremental lifetime subtraction (`planLifetimeRemovals`/`applyLifetimeRemovals` in `lifetime.ts`). Merges, moves, AniList reassignments, and `stats cleanup -l` use `repairLifetimeSummariesFromMedia` (recompute from the per-video media ledger). The full lifetime rebuild survives only as the empty-table bootstrap; anywhere else it would collapse lifetime totals to the session retention window.
- Immersion sync: `src/core/services/stats-sync/`, bound by `src/main/sync-cli.ts`.
`snapshot-transfer.ts` selects compressed rsync or scp. `transfer-cache.ts` atomically retains the last successfully received snapshot per hashed peer/database identity under the config directory's `sync-transfer-cache/`. Cache copies seed isolated transfer directories; rsync verifies reconstructed files before the existing merge engine runs. The `--make-temp` / `--remove-temp` helpers accept an internal `--transfer-cache` key, with a fallback for older peers that do not recognize it.
- AniList tracking + character dictionary: `src/core/services/anilist/`, `src/main/runtime/composers/anilist-*`, `src/main/character-dictionary-runtime.ts`, `src/main/character-dictionary-runtime/`
- Jellyfin integration: `src/core/services/jellyfin*.ts`, `src/main/runtime/composers/jellyfin-*`
- Window trackers: `src/window-trackers/`
+1 -1
View File
@@ -23,7 +23,7 @@ Renderer, launcher, plugin, and stats each keep their own local layering and sho
- Keep side effects explicit and close to composition boundaries.
- Put reusable business logic in focused services, not in top-level lifecycle files.
- Keep renderer concerns in `src/renderer/`; avoid leaking DOM behavior into main-process code.
- Treat `launcher/*.ts` as source of truth for the launcher. Never hand-edit `dist/launcher/subminer`.
- Treat `launcher/*.ts` and the runtime bootstrap generators as source of truth for launcher behavior. `scripts/build-launcher.ts` creates `dist/launcher`; never hand-edit those artifacts.
## Smells
+2
View File
@@ -23,6 +23,8 @@ Read when: selecting the right verification lane for a change
there instead of copying them into caller workflows.
- The reusable gate installs Lua and runs `bun run test:env`, so the shipped mpv
plugin tests run for every pull request and tagged release.
Lua installation uses only the runner's Ubuntu package sources so unrelated
third-party repository failures do not block the gate.
## Default Handoff Gate