Compare commits

...
2 Commits
46 changed files with 239 additions and 161 deletions

No files matched your search

+17 -17
View File
@@ -67,26 +67,26 @@ jobs:
- name: Build unversioned AppImage
run: |
shopt -s nullglob
appimages=(release/SubMiner-*.AppImage)
appimages=(dist/release/SubMiner-*.AppImage)
if [ "${#appimages[@]}" -eq 0 ]; then
echo "No versioned AppImage found to create unversioned artifact."
ls -la release
ls -la dist/release
exit 1
fi
cp "${appimages[0]}" release/SubMiner.AppImage
cp "${appimages[0]}" dist/release/SubMiner.AppImage
- name: Smoke packaged runtime assets
shell: bash
run: xvfb-run -a bun run test:package "release/linux-unpacked/resources"
run: xvfb-run -a bun run test:package "dist/release/linux-unpacked/resources"
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: appimage
path: |
release/*.AppImage
release/latest*.yml
release/*.blockmap
dist/release/*.AppImage
dist/release/latest*.yml
dist/release/*.blockmap
if-no-files-found: error
build-macos:
@@ -159,17 +159,17 @@ jobs:
- name: Smoke packaged runtime assets
shell: bash
run: bun run test:package "release/mac-arm64/SubMiner.app/Contents/Resources"
run: bun run test:package "dist/release/mac-arm64/SubMiner.app/Contents/Resources"
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos
path: |
release/*.dmg
release/*.zip
release/latest*.yml
release/*.blockmap
dist/release/*.dmg
dist/release/*.zip
dist/release/latest*.yml
dist/release/*.blockmap
if-no-files-found: error
build-windows:
@@ -224,15 +224,15 @@ jobs:
- name: Smoke packaged runtime assets
shell: bash
run: bun run test:package "release/win-unpacked/resources"
run: bun run test:package "dist/release/win-unpacked/resources"
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows
path: |
release/*.exe
release/*.zip
release/latest*.yml
release/*.blockmap
dist/release/*.exe
dist/release/*.zip
dist/release/latest*.yml
dist/release/*.blockmap
if-no-files-found: error
+25 -23
View File
@@ -44,19 +44,19 @@ jobs:
uses: actions/download-artifact@v4
with:
name: appimage
path: release
path: dist/release
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos
path: release
path: dist/release
- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
name: windows
path: release
path: dist/release
- name: Setup Bun
uses: oven-sh/setup-bun@v2
@@ -90,13 +90,15 @@ jobs:
- name: Package optional assets bundle
run: |
tar -czf "release/subminer-assets.tar.gz" \
# Keep the archive layout stable for the AUR PKGBUILD.
tar -czf "dist/release/subminer-assets.tar.gz" \
--transform 's,^packaging/bun/licenses,resources/bun/licenses,' \
config.example.jsonc \
plugin/subminer \
plugin/subminer.conf \
assets/themes/subminer.rasi \
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
resources/bun/licenses
packaging/bun/licenses
- name: Package Bun corresponding source
run: bun scripts/package-bun-source.mjs
@@ -104,16 +106,16 @@ jobs:
- name: Generate checksums
run: |
shopt -s nullglob
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
files=(dist/release/*.AppImage dist/release/*.dmg dist/release/*.exe dist/release/*.zip dist/release/*.tar.gz dist/release/latest*.yml dist/release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
if [ "${#files[@]}" -eq 0 ]; then
echo "No release artifacts found for checksum generation."
exit 1
fi
: > release/SHA256SUMS.txt
: > dist/release/SHA256SUMS.txt
for file in "${files[@]}"; do
printf '%s %s\n' \
"$(sha256sum "$file" | awk '{print $1}')" \
"${file##*/}" >> release/SHA256SUMS.txt
"${file##*/}" >> dist/release/SHA256SUMS.txt
done
- name: Get version from tag
@@ -124,12 +126,12 @@ jobs:
env:
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
run: |
if [ ! -s release/prerelease-notes.md ]; then
echo "::error::release/prerelease-notes.md is missing or empty. Run 'bun run changelog:prerelease-notes --version <version>' locally and commit the file before tagging."
if [ ! -s packaging/release-notes/prerelease-notes.md ]; then
echo "::error::packaging/release-notes/prerelease-notes.md is missing or empty. Run 'bun run changelog:prerelease-notes --version <version>' locally and commit the file before tagging."
exit 1
fi
if ! bun run changelog:check-prerelease-notes --version "$RELEASE_VERSION"; then
echo "::error::release/prerelease-notes.md was not generated for $RELEASE_VERSION. Rerun 'bun run changelog:prerelease-notes --version $RELEASE_VERSION' locally, commit, and retag."
echo "::error::packaging/release-notes/prerelease-notes.md was not generated for $RELEASE_VERSION. Rerun 'bun run changelog:prerelease-notes --version $RELEASE_VERSION' locally, commit, and retag."
exit 1
fi
@@ -142,15 +144,15 @@ jobs:
shopt -s nullglob
artifacts=(
release/*.AppImage
release/*.dmg
release/*.exe
release/*.zip
release/*.tar.gz
release/*.tar.gz.sha256
release/latest*.yml
release/*.blockmap
release/SHA256SUMS.txt
dist/release/*.AppImage
dist/release/*.dmg
dist/release/*.exe
dist/release/*.zip
dist/release/*.tar.gz
dist/release/*.tar.gz.sha256
dist/release/latest*.yml
dist/release/*.blockmap
dist/release/SHA256SUMS.txt
dist/launcher/subminer
dist/launcher/subminer.cmd
)
@@ -165,14 +167,14 @@ jobs:
--draft \
--prerelease \
--title "$RELEASE_VERSION" \
--notes-file release/prerelease-notes.md
--notes-file packaging/release-notes/prerelease-notes.md
else
gh release create "$RELEASE_VERSION" \
--draft \
--latest=false \
--prerelease \
--title "$RELEASE_VERSION" \
--notes-file release/prerelease-notes.md
--notes-file packaging/release-notes/prerelease-notes.md
fi
for asset in "${artifacts[@]}"; do
@@ -183,4 +185,4 @@ jobs:
--draft=false \
--prerelease \
--title "$RELEASE_VERSION" \
--notes-file release/prerelease-notes.md
--notes-file packaging/release-notes/prerelease-notes.md
+21 -19
View File
@@ -45,19 +45,19 @@ jobs:
uses: actions/download-artifact@v4
with:
name: appimage
path: release
path: dist/release
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos
path: release
path: dist/release
- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
name: windows
path: release
path: dist/release
- name: Setup Bun
uses: oven-sh/setup-bun@v2
@@ -91,13 +91,15 @@ jobs:
- name: Package optional assets bundle
run: |
tar -czf "release/subminer-assets.tar.gz" \
# Keep the archive layout stable for the AUR PKGBUILD.
tar -czf "dist/release/subminer-assets.tar.gz" \
--transform 's,^packaging/bun/licenses,resources/bun/licenses,' \
config.example.jsonc \
plugin/subminer \
plugin/subminer.conf \
assets/themes/subminer.rasi \
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
resources/bun/licenses
packaging/bun/licenses
- name: Package Bun corresponding source
run: bun scripts/package-bun-source.mjs
@@ -105,16 +107,16 @@ jobs:
- name: Generate checksums
run: |
shopt -s nullglob
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
files=(dist/release/*.AppImage dist/release/*.dmg dist/release/*.exe dist/release/*.zip dist/release/*.tar.gz dist/release/latest*.yml dist/release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
if [ "${#files[@]}" -eq 0 ]; then
echo "No release artifacts found for checksum generation."
exit 1
fi
: > release/SHA256SUMS.txt
: > dist/release/SHA256SUMS.txt
for file in "${files[@]}"; do
printf '%s %s\n' \
"$(sha256sum "$file" | awk '{print $1}')" \
"${file##*/}" >> release/SHA256SUMS.txt
"${file##*/}" >> dist/release/SHA256SUMS.txt
done
- name: Get version from tag
@@ -152,24 +154,24 @@ jobs:
gh release edit "$RELEASE_VERSION" \
--draft=false \
--title "$RELEASE_VERSION" \
--notes-file release/release-notes.md
--notes-file packaging/release-notes/release-notes.md
else
gh release create "$RELEASE_VERSION" \
--title "$RELEASE_VERSION" \
--notes-file release/release-notes.md
--notes-file packaging/release-notes/release-notes.md
fi
shopt -s nullglob
artifacts=(
release/*.AppImage
release/*.dmg
release/*.exe
release/*.zip
release/*.tar.gz
release/*.tar.gz.sha256
release/latest*.yml
release/*.blockmap
release/SHA256SUMS.txt
dist/release/*.AppImage
dist/release/*.dmg
dist/release/*.exe
dist/release/*.zip
dist/release/*.tar.gz
dist/release/*.tar.gz.sha256
dist/release/latest*.yml
dist/release/*.blockmap
dist/release/SHA256SUMS.txt
dist/launcher/subminer
dist/launcher/subminer.cmd
)
+1 -7
View File
@@ -8,11 +8,6 @@ node_modules/
# Electron build output
out/
dist/
release/*
!release/
!release/release-notes.md
!release/prerelease-notes.md
build/yomitan/
coverage/
# Launcher build artifact (produced by make build-launcher)
@@ -20,8 +15,6 @@ coverage/
/main-entry.js
/main-entry.js.map
/build/hachidori/
# Logs
*.log
npm-debug.log*
@@ -43,6 +36,7 @@ environment.toml
.vitepress/dist/
docs/.vitepress/cache/
docs/.vitepress/dist/
# Local test media and scripts
tests/*
.worktrees/
.tmp/
+3 -1
View File
@@ -1,7 +1,9 @@
node_modules
dist
release
coverage
vendor
*.log
src/core/services/tokenizer/__fixtures__/golden/*.json
# Reviewed release text and vendored license texts; never reformat
packaging/release-notes
packaging/bun/licenses
-5
View File
@@ -1,5 +0,0 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
+8 -17
View File
@@ -21,11 +21,10 @@ MACOS_DATA_DIR ?= $(HOME)/Library/Application Support/SubMiner
MACOS_APP_DIR ?= $(HOME)/Applications
MACOS_APP_DEST ?= $(MACOS_APP_DIR)/SubMiner.app
# If building from source, the AppImage will typically land in release/.
APPIMAGE_SRC = $(firstword $(wildcard release/SubMiner-*.AppImage))
MACOS_APP_SRC = $(firstword $(wildcard release/*.app release/*/*.app))
MACOS_ZIP_SRC = $(firstword $(wildcard release/SubMiner-*.zip))
PRERELEASE_NOTES := release/prerelease-notes.md
# If building from source, the AppImage will typically land in dist/release/.
APPIMAGE_SRC = $(firstword $(wildcard dist/release/SubMiner-*.AppImage))
MACOS_APP_SRC = $(firstword $(wildcard dist/release/*.app dist/release/*/*.app))
MACOS_ZIP_SRC = $(firstword $(wildcard dist/release/SubMiner-*.zip))
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
ifeq ($(OS),Windows_NT)
@@ -57,7 +56,7 @@ help:
" build-linux Build Linux AppImage" \
" build-macos Build macOS DMG/ZIP (signed if configured)" \
" build-macos-unsigned Build macOS DMG/ZIP without signing/notarization" \
" clean Remove build artifacts (dist/, release/, AppImage, binary)" \
" clean Remove build artifacts (dist/, AppImage, binary)" \
" dev-start Build and launch local Electron app" \
" dev-start-macos Build and launch local Electron app with macOS tracker backend" \
" dev-watch Start fast watch loop (tsc + renderer + Electron dev app)" \
@@ -165,15 +164,7 @@ build-launcher:
clean:
@printf '%s\n' "[INFO] Removing build artifacts"
@if [ -f "$(PRERELEASE_NOTES)" ]; then \
PRERELEASE_NOTES_BACKUP="$$(mktemp -t subminer-prerelease-notes.XXXXXX)" && \
cp "$(PRERELEASE_NOTES)" "$$PRERELEASE_NOTES_BACKUP" && \
rm -rf dist release && \
install -d release && \
mv "$$PRERELEASE_NOTES_BACKUP" "$(PRERELEASE_NOTES)"; \
else \
rm -rf dist release; \
fi
@rm -rf dist
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
generate-config: ensure-bun
@@ -229,7 +220,7 @@ install-linux: build-launcher
@if [ -n "$(APPIMAGE_SRC)" ]; then \
install -m 0755 "$(APPIMAGE_SRC)" "$(BINDIR)/SubMiner.AppImage"; \
else \
printf '%s\n' "[WARN] No release/SubMiner-*.AppImage found; skipping AppImage install"; \
printf '%s\n' "[WARN] No dist/release/SubMiner-*.AppImage found; skipping AppImage install"; \
printf '%s\n' " Build one with: make build"; \
fi
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
@@ -252,7 +243,7 @@ install-macos: build-launcher
ditto -x -k "$(MACOS_ZIP_SRC)" "$(MACOS_APP_DIR)"; \
printf '%s\n' "[INFO] Installed app bundle from $(MACOS_ZIP_SRC)"; \
else \
printf '%s\n' "[WARN] No macOS app bundle or zip found in release/; skipping app install"; \
printf '%s\n' "[WARN] No macOS app bundle or zip found in dist/release/; skipping app install"; \
printf '%s\n' " Build one with: make build"; \
fi
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
+2 -2
View File
@@ -44,11 +44,11 @@ How fragments turn into a release:
- The polish step treats pending fragments as the final release outcome, not prerelease history. If a feature is added and then renamed or fixed before the stable cut, ship the final feature bullet instead of separate prerelease-only breaking/fix entries.
- `CHANGELOG.md`, GitHub release notes, and prerelease notes all use one item per feature or area, inline by default: `- **Name**: one or two short sentences.` Items with several separate changes may use a few short nested bullets instead of a run-on sentence. Items say what changed and how to reach it; implementation details, edge cases, and step-by-step usage belong in the docs, not the release body.
- `internal` fragments stay in `CHANGELOG.md` (inside a collapsed `<details>` block) but are dropped from the GitHub release notes entirely.
- The polished `CHANGELOG.md` and `release/release-notes.md` are committed and reviewed before tagging — edit the Markdown by hand if Claude misses something.
- The polished `CHANGELOG.md` and `packaging/release-notes/release-notes.md` are committed and reviewed before tagging — edit the Markdown by hand if Claude misses something.
Prerelease notes:
- prerelease tags like `v0.11.3-beta.1` and `v0.11.3-rc.1` reuse the current pending fragments to generate `release/prerelease-notes.md`
- prerelease tags like `v0.11.3-beta.1` and `v0.11.3-rc.1` reuse the current pending fragments to generate `packaging/release-notes/prerelease-notes.md`
- from the second prerelease of a base version onward, the notes also open with a `## Changes since <previous tag>` section generated from the fragment diff against the previous beta/RC tag; keep fragment edits meaningful. Editorial-only rewording is filtered out of that section, while genuinely changed behavior and deleted fragments (reverted changes) are reported
- existing prerelease notes are a reviewed baseline; later prerelease runs should replace stale beta/RC wording with the current outcome instead of appending fix churn
- prerelease note generation does not consume fragments and does not update `CHANGELOG.md` or `docs-site/changelog.md`
+5
View File
@@ -0,0 +1,5 @@
type: internal
area: build
- Moved packaging inputs (electron-builder resources, Bun runtime manifests and licenses, committed release notes) under `packaging/`.
- Build output now lands in `dist/`: packaged apps in `dist/release/`, staged Yomitan and Hachidori in `dist/yomitan/` and `dist/hachidori/`.
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- mpv keys and mouse buttons (such as `9`/`0` volume and double-click fullscreen) pass through the overlay again while the subtitle sidebar is open; only input inside the sidebar stays with it.
+1 -1
View File
@@ -226,7 +226,7 @@ make deps
make build-macos
```
The `.dmg` and `.zip` land in `release/`. For an unsigned local build, run `bun run build:mac:unsigned`.
The `.dmg` and `.zip` land in `dist/release/`. For an unsigned local build, run `bun run build:mac:unsigned`.
</details>
+1 -1
View File
@@ -86,7 +86,7 @@ SubMiner detected that a previously non-empty Yomitan profile suddenly appears e
## "Yomitan extension not found in any search path"
The bundled Yomitan is missing. Re-download the AppImage, or place an unpacked Yomitan extension in `~/.config/SubMiner/yomitan`. Source builds must run `bun run build` first to produce `build/yomitan`.
The bundled Yomitan is missing. Re-download the AppImage, or place an unpacked Yomitan extension in `~/.config/SubMiner/yomitan`. Source builds must run `bun run build` first to produce `dist/yomitan`.
## "MeCab not found on system"
+10 -10
View File
@@ -54,10 +54,10 @@ files before signing and generating updater hashes, never from a signed app.
4. Bump `package.json` to the release version.
5. Build release metadata before tagging (this calls `claude -p` locally):
`bun run changelog:build --version <version> --date <yyyy-mm-dd>`
- The polished `CHANGELOG.md` and `release/release-notes.md` are committed
- The polished `CHANGELOG.md` and `packaging/release-notes/release-notes.md` are committed
before tagging. Release CI no longer auto-builds the changelog; it fails
fast if `changes/*.md` fragments are still present on a tag-based run.
6. Review `CHANGELOG.md` and `release/release-notes.md`. Edit by hand if Claude
6. Review `CHANGELOG.md` and `packaging/release-notes/release-notes.md`. Edit by hand if Claude
missed something — the committed Markdown is what ships.
7. Run release gate locally:
`bun run changelog:check --version <version>`
@@ -73,9 +73,9 @@ files before signing and generating updater hashes, never from a signed app.
`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`.
`packaging/bun/bun-source-manifest.json`.
When validating auto-update metadata, also run the relevant platform package
build and confirm `release/` contains the generated updater metadata
build and confirm `dist/release/` contains the generated updater metadata
(`latest*.yml`) and blockmaps (`*.blockmap`).
8. If `docs-site/` changed, also run:
`bun run docs:test`
@@ -103,9 +103,9 @@ files before signing and generating updater hashes, never from a signed app.
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/`.
`latest*.yml` and `*.blockmap` files under `dist/release/`.
5. Commit the prerelease prep (package.json version bump + the generated
`release/prerelease-notes.md`). CI does not regenerate notes — it uses the
`packaging/release-notes/prerelease-notes.md`). CI does not regenerate notes — it uses the
committed file — so review it before committing. Rerun
`bun run changelog:prerelease-notes --version <version>` for every later
beta/RC, even if no fragments changed: the notes carry a hidden
@@ -128,14 +128,14 @@ Notes:
- Supported prerelease channels are `beta` and `rc`, with versions like `0.11.3-beta.1` and `0.11.3-rc.1`.
- Pass `--date` explicitly when you want the release stamped with the local cut date; otherwise the generator uses the current ISO date, which can roll over to the next UTC day late at night.
- `changelog:check` now rejects tag/package version mismatches.
- `changelog:prerelease-notes` also rejects tag/package version mismatches and writes `release/prerelease-notes.md` without mutating tracked changelog files. When that file already exists, the generator includes it in the Claude prompt so later beta/RC notes reuse the reviewed text instead of starting over.
- `changelog:prerelease-notes` also rejects tag/package version mismatches and writes `packaging/release-notes/prerelease-notes.md` without mutating tracked changelog files. When that file already exists, the generator includes it in the Claude prompt so later beta/RC notes reuse the reviewed text instead of starting over.
- From the second prerelease of a base version onward, the notes open with a `## Changes since <previous tag>` section above the cumulative `## Highlights`. The generator locates the newest preceding beta/RC tag for the same base version (semver order: all betas before all RCs), diffs `changes/*.md` between that tag and the working tree, and asks Claude to describe only the behavioral beta-to-beta differences — added fragments as new changes, modified fragments by their before/after difference (editorial-only edits are dropped), deleted fragments as removed/reverted changes. If no fragments changed (for example a packaging-only rebuild), the section states that explicitly without a Claude call. The delta section carries no separate contributor attribution; `## What's Changed` stays cumulative like `## Highlights`.
- `changelog:check-prerelease-notes --version <version>` verifies the committed notes' `prerelease-version` marker matches the version being tagged; the prerelease workflow runs it and fails the release on stale notes.
- `changelog:build` generates `CHANGELOG.md` + `release/release-notes.md` (both polished by `claude -p`) and removes the released `changes/*.md` fragments. The CHANGELOG keeps internal notes inside a `<details><summary>Internal changes</summary>` collapse; the release notes drop them entirely.
- `release/release-notes.md` (and `release/prerelease-notes.md`) include GitHub-style attribution after `## Highlights`: a `## What's Changed` list crediting each released fragment as `by @<author> in #<pr>`, plus a `## New Contributors` section for first-time authors. Attribution is resolved per fragment via `git log` (the commit that added the fragment) + `gh api .../commits/<sha>/pulls`, with one `gh` search per author for the first-contribution check. It needs `gh` installed and authenticated; if `gh` is unavailable or a lookup fails, the generator warns and emits notes without the attribution sections rather than failing. The CHANGELOG itself stays attribution-free.
- `changelog:build` generates `CHANGELOG.md` + `packaging/release-notes/release-notes.md` (both polished by `claude -p`) and removes the released `changes/*.md` fragments. The CHANGELOG keeps internal notes inside a `<details><summary>Internal changes</summary>` collapse; the release notes drop them entirely.
- `packaging/release-notes/release-notes.md` (and `packaging/release-notes/prerelease-notes.md`) include GitHub-style attribution after `## Highlights`: a `## What's Changed` list crediting each released fragment as `by @<author> in #<pr>`, plus a `## New Contributors` section for first-time authors. Attribution is resolved per fragment via `git log` (the commit that added the fragment) + `gh api .../commits/<sha>/pulls`, with one `gh` search per author for the first-contribution check. It needs `gh` installed and authenticated; if `gh` is unavailable or a lookup fails, the generator warns and emits notes without the attribution sections rather than failing. The CHANGELOG itself stays attribution-free.
- The release workflow no longer auto-runs `changelog:build`. If pending `changes/*.md` fragments are present on a tag-based run, CI exits with a clear `::error::` pointing at the local fix. Run `bun run changelog:build --version <version>` locally, commit the polished output, then tag.
- Do not tag while `changes/*.md` fragments still exist.
- Prerelease tags intentionally keep `changes/*.md` fragments in place so multiple prereleases can reuse the same cumulative pending notes until the final stable cut. `make clean` preserves `release/prerelease-notes.md` while deleting generated build artifacts.
- Prerelease tags intentionally keep `changes/*.md` fragments in place so multiple prereleases can reuse the same cumulative pending notes until the final stable cut.
- If you need to repair a published release body (for example, a prior version’s section was omitted), regenerate notes from `CHANGELOG.md` and re-edit the release with `gh release edit --notes-file`.
- Prerelease tags are handled by `.github/workflows/prerelease.yml`, which always publishes a GitHub prerelease with all current release platforms and never runs the AUR sync job.
- CI, stable release, and prerelease workflows call the shared `.github/workflows/quality-gate.yml`; update that reusable workflow when changing common test coverage. Its environment suite includes the Lua mpv plugin tests.
+1 -1
View File
@@ -62,7 +62,7 @@ bun run docs:build
then `bun run test:e2e` (see [Headless End-to-End Harness](#headless-end-to-end-harness))
- Build/release scripts (`scripts/**`): `bun run test:scripts`
- Packaging: build the platform package, then run `bun run test:package <resources-directory>`.
On headless Linux: `xvfb-run -a bun run test:package release/linux-unpacked/resources`.
On headless Linux: `xvfb-run -a bun run test:package dist/release/linux-unpacked/resources`.
Content checks run inside the electron-builder afterPack hook. See the
[release guide](../RELEASING.md#package-contents-checks) for the
installed-app verification checklist.
+13 -6
View File
@@ -146,7 +146,8 @@
"afterPack": "scripts/electron-builder-after-pack.cjs",
"icon": "assets/SubMiner-square.png",
"directories": {
"output": "release"
"buildResources": "packaging/electron-builder",
"output": "dist/release"
},
"publish": [
{
@@ -178,8 +179,8 @@
"category": "public.app-category.video",
"icon": "assets/SubMiner-square.png",
"hardenedRuntime": true,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"entitlements": "packaging/electron-builder/entitlements.mac.plist",
"entitlementsInherit": "packaging/electron-builder/entitlements.mac.plist",
"extraResources": [
{
"from": "dist/scripts/get-mpv-window-macos",
@@ -211,7 +212,7 @@
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"include": "build/installer.nsh"
"include": "packaging/electron-builder/installer.nsh"
},
"files": [
"dist/**/*",
@@ -225,17 +226,18 @@
"!**/{test,tests,__tests__,fixture,fixtures,__fixtures__}{,/**/*}",
"!dist/launcher{,/**/*}",
"!dist/scripts{,/**/*}",
"!dist/{yomitan,hachidori,release}{,/**/*}",
"!dist/{renderer,settings,syncui}/fonts{,/**/*}",
"!node_modules/koffi/{src,vendor,doc}{,/**/*}",
"!node_modules/@libsql/linux-x64-musl{,/**/*}"
],
"extraResources": [
{
"from": "build/yomitan",
"from": "dist/yomitan",
"to": "yomitan"
},
{
"from": "build/hachidori",
"from": "dist/hachidori",
"to": "hachidori"
},
{
@@ -277,6 +279,11 @@
}
]
},
"prettier": {
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
},
"patchedDependencies": {
"@discordjs/rest@2.6.1": "patches/@discordjs%2Frest@2.6.1.patch"
}
@@ -26,6 +26,6 @@
}
},
"licenseInventoryStatus": "source-and-notices-pinned-to-binary-revision",
"sourceManifest": "build/bun-source-manifest.json",
"sourceManifest": "packaging/bun/bun-source-manifest.json",
"correspondingSourceAsset": "bun-v1.3.5-source.tar.gz"
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,7 +10,7 @@ The SubMiner GitHub release containing this application also contains `bun-v1.3.
- every external source repository registered by that Bun revision's CMake build, at its exact commit
- Bun's dependency patches, build scripts, lockfiles, collected third-party license files, and instructions for rebuilding Bun against a modified JavaScriptCore
The machine-readable inventory lives at `SOURCE-INVENTORY.json` inside the source archive and at `build/bun-source-manifest.json` in SubMiner's source repository.
The machine-readable inventory lives at `SOURCE-INVENTORY.json` inside the source archive and at `packaging/bun/bun-source-manifest.json` in SubMiner's source repository.
The archive vendors the source repositories linked through Bun's CMake build. It preserves Bun's package-manager lockfiles and lol-html's `Cargo.lock`, but it does not vendor npm packages, crates.io packages, compilers, SDKs, or other build tools. Rebuilding needs network access for those package-manager and toolchain inputs. The archive's rebuild README records the known tool versions and the remaining unpinned Rust nightly input.
@@ -1,6 +1,6 @@
# Bun 1.3.5 third-party notices
This file collects the license texts shipped by the external source repositories used by Bun 1.3.5 at the revisions in build/bun-source-manifest.json. The corresponding-source release asset also preserves license notices embedded in individual Bun and WebKit source files. Bun’s own LICENSE.md lists additional embedded libraries and JavaScript polyfills whose source and per-file notices are included in that asset.
This file collects the license texts shipped by the external source repositories used by Bun 1.3.5 at the revisions in packaging/bun/bun-source-manifest.json. The corresponding-source release asset also preserves license notices embedded in individual Bun and WebKit source files. Bun’s own LICENSE.md lists additional embedded libraries and JavaScript polyfills whose source and per-file notices are included in that asset.
## BoringSSL
File renamed without changes.
File renamed without changes.
File renamed without changes.
+19 -13
View File
@@ -203,7 +203,7 @@ test('writeChangelogArtifacts ignores README, groups fragments by type, writes r
assert.match(changelog, /### Fixed\n- Polished: fixed entry\./);
assert.match(changelog, /## v0\.4\.0 \(2026-03-01\)\n- Existing fix\n$/);
const releaseNotes = project.read('release/release-notes.md');
const releaseNotes = project.read('packaging/release-notes/release-notes.md');
assert.match(releaseNotes, /## Highlights\n### Added\n- Polished: added entry\./);
assert.match(releaseNotes, /### Fixed\n- Polished: fixed entry\./);
assert.match(releaseNotes, /## Installation\n\nSee the README and docs\/installation guide/);
@@ -240,7 +240,7 @@ test('writeChangelogArtifacts skips changelog prepend when release section alrea
assert.equal(project.exists('changes/001.md'), false);
assert.equal(project.read('CHANGELOG.md'), existingChangelog);
assert.match(
project.read('release/release-notes.md'),
project.read('packaging/release-notes/release-notes.md'),
/## Highlights\n### Added\n- Existing release bullet\./,
);
},
@@ -268,7 +268,7 @@ test('writeStableReleaseArtifacts reuses the requested version and date for chan
});
assert.deepEqual(result.outputPaths, [project.at('CHANGELOG.md')]);
assert.equal(result.releaseNotesPath, project.at('release/release-notes.md'));
assert.equal(result.releaseNotesPath, project.at('packaging/release-notes/release-notes.md'));
assert.equal(result.docsChangelogPath, project.at('docs-site/changelog.md'));
assert.match(project.read('CHANGELOG.md'), /## v0\.4\.1 \(2026-03-07\)/);
@@ -471,7 +471,7 @@ test('writePrereleaseNotesForVersion writes cumulative beta notes without mutati
deps: { runClaude: stub.runClaude, listPrereleaseTags: () => [] },
});
assert.equal(outputPath, project.at('release/prerelease-notes.md'));
assert.equal(outputPath, project.at('packaging/release-notes/prerelease-notes.md'));
assert.equal(
project.read('CHANGELOG.md'),
existingChangelog,
@@ -527,7 +527,7 @@ test('writePrereleaseNotesForVersion reuses existing prerelease notes when addin
'prerelease-reuse-existing-notes',
{
'package.json': packageJson('0.11.3-beta.2'),
'release/prerelease-notes.md': existingNotes,
'packaging/release-notes/prerelease-notes.md': existingNotes,
'changes/001.md': fragment('fixed', 'launcher', '- Fixed launcher prerelease packaging.'),
},
(project) => {
@@ -574,7 +574,7 @@ test('writePrereleaseNotesForVersion ignores unmarked prerelease notes from an o
'prerelease-ignore-unmarked-old-notes',
{
'package.json': packageJson('0.17.0-beta.1'),
'release/prerelease-notes.md': existingNotes,
'packaging/release-notes/prerelease-notes.md': existingNotes,
'changes/001.md': fragment(
'changed',
'overlay',
@@ -785,7 +785,7 @@ test('writeChangelogArtifacts filters internal fragments from the release-notes
'release-notes mode drops internal fragments',
);
const releaseNotes = project.read('release/release-notes.md');
const releaseNotes = project.read('packaging/release-notes/release-notes.md');
assert.doesNotMatch(releaseNotes, /<details>/);
assert.doesNotMatch(releaseNotes, /### Internal/);
assert.match(
@@ -841,7 +841,7 @@ test('writeChangelogArtifacts appends contributor attribution and a new-contribu
project.at('changes/002.md'),
]);
const releaseNotes = project.read('release/release-notes.md');
const releaseNotes = project.read('packaging/release-notes/release-notes.md');
assert.match(releaseNotes, /## What's Changed\n\n/);
assert.match(releaseNotes, /- feat\(overlay\): add a feature by @ksyasuda in #110\n/);
assert.match(releaseNotes, /- fix\(jellyfin\): restart remote session by @bee-san in #112\n/);
@@ -904,7 +904,10 @@ test('writeChangelogArtifacts skips contributor attribution in GitHub Actions wi
});
assert.deepEqual(warnings, []);
assert.doesNotMatch(project.read('release/release-notes.md'), /## What's Changed/);
assert.doesNotMatch(
project.read('packaging/release-notes/release-notes.md'),
/## What's Changed/,
);
} finally {
console.warn = originalWarn;
for (const key of envKeys) {
@@ -990,7 +993,10 @@ test('writeReleaseNotesForVersion preserves committed contributor attribution be
await withProject(
'release-notes-preserve-attribution',
{ 'CHANGELOG.md': existingChangelog, 'release/release-notes.md': committedReleaseNotes },
{
'CHANGELOG.md': existingChangelog,
'packaging/release-notes/release-notes.md': committedReleaseNotes,
},
(project) => {
const outputPath = writeReleaseNotesForVersion({
cwd: project.projectRoot,
@@ -1055,7 +1061,7 @@ test('writeChangelogArtifacts strips <details> blocks from release notes when re
'no Claude calls should fire when the section already exists',
);
const releaseNotes = project.read('release/release-notes.md');
const releaseNotes = project.read('packaging/release-notes/release-notes.md');
assert.match(releaseNotes, /## Highlights\n### Added\n- Polished: previously committed\./);
assert.doesNotMatch(releaseNotes, /<details>/);
assert.doesNotMatch(releaseNotes, /### Internal/);
@@ -1237,7 +1243,7 @@ test('writePrereleaseNotesForVersion strips the stale delta section from the reu
'prerelease-reuse-strips-delta',
{
'package.json': packageJson('0.12.0-beta.3'),
'release/prerelease-notes.md': existingNotes,
'packaging/release-notes/prerelease-notes.md': existingNotes,
'changes/001.md': fragment('added', 'overlay', '- Added overlay coverage.'),
},
(project) => {
@@ -1271,7 +1277,7 @@ test('verifyPrereleaseNotesMatchVersion accepts matching notes and rejects stale
(project) => {
const verify = (version = '0.12.0-beta.2') =>
verifyPrereleaseNotesMatchVersion({ cwd: project.projectRoot, version });
const notesPath = 'release/prerelease-notes.md';
const notesPath = 'packaging/release-notes/prerelease-notes.md';
assert.throws(() => verify(), /Missing .*prerelease-notes\.md/);
+4 -4
View File
@@ -68,8 +68,8 @@ type PullRequestChangelogOptions = {
changedLabels?: string[];
};
const RELEASE_NOTES_PATH = path.join('release', 'release-notes.md');
const PRERELEASE_NOTES_PATH = path.join('release', 'prerelease-notes.md');
const RELEASE_NOTES_PATH = path.join('packaging', 'release-notes', 'release-notes.md');
const PRERELEASE_NOTES_PATH = path.join('packaging', 'release-notes', 'prerelease-notes.md');
const CHANGELOG_HEADER = '# Changelog';
const CHANGE_TYPES: FragmentType[] = ['added', 'changed', 'fixed', 'docs', 'internal'];
const SKIP_CHANGELOG_LABEL = 'skip-changelog';
@@ -1144,7 +1144,7 @@ function isFragmentPath(candidate: string): boolean {
function isIgnoredPullRequestPath(candidate: string): boolean {
return (
candidate === 'CHANGELOG.md' ||
candidate === 'release/release-notes.md' ||
candidate === 'packaging/release-notes/release-notes.md' ||
candidate === 'AGENTS.md' ||
candidate === 'README.md' ||
candidate.startsWith('changes/') ||
@@ -1405,7 +1405,7 @@ export function verifyPrereleaseNotesMatchVersion(options?: ChangelogOptions): v
const markerVersion = extractPrereleaseVersionMarker(readFileSync(prereleaseNotesPath, 'utf8'));
if (markerVersion !== version) {
throw new Error(
`release/prerelease-notes.md was generated for ${markerVersion ?? 'an unknown version (missing or legacy prerelease-version marker)'} but this release is ${version}. Rerun 'bun run changelog:prerelease-notes --version ${version}' and commit the result.`,
`packaging/release-notes/prerelease-notes.md was generated for ${markerVersion ?? 'an unknown version (missing or legacy prerelease-version marker)'} but this release is ${version}. Rerun 'bun run changelog:prerelease-notes --version ${version}' and commit the result.`,
);
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const source = path.join(repoRoot, 'vendor', 'hachidori');
const output = path.join(repoRoot, 'build', 'hachidori');
const output = path.join(repoRoot, 'dist', 'hachidori');
const provenance = JSON.parse(fs.readFileSync(path.join(source, 'SOURCE.json'), 'utf8'));
// Upstream commits the engine binaries. Verify the pinned bytes before staging
+3 -3
View File
@@ -19,7 +19,7 @@ test('Hachidori staging configures Electron without changing the fork source', a
files.map((file) => readFileSync(source(file), 'utf8')),
before,
);
const staged = await import(new URL('../build/hachidori/overlay-mode.js', import.meta.url).href);
const staged = await import(new URL('../dist/hachidori/overlay-mode.js', import.meta.url).href);
assert.equal(staged.OVERLAY_MODE, true);
assert.equal(staged.HOST_CAPABILITIES.customJavaScript, false);
const original = await import(source('overlay-mode.js').href);
@@ -28,13 +28,13 @@ test('Hachidori staging configures Electron without changing the fork source', a
// SubMiner mines media from mpv, not the overlay viewport, so the lookup
// highlight stays on for a first install.
const stagedSetup = await import(
new URL('../build/hachidori/setup-state.js', import.meta.url).href
new URL('../dist/hachidori/setup-state.js', import.meta.url).href
);
assert.equal(stagedSetup.OVERLAY_MODE_OPTIONS.sourceHighlightEnabled, true);
const originalSetup = await import(source('setup-state.js').href);
assert.equal(originalSetup.OVERLAY_MODE_OPTIONS.sourceHighlightEnabled, false);
const manifest = JSON.parse(
readFileSync(new URL('../build/hachidori/manifest.json', import.meta.url), 'utf8'),
readFileSync(new URL('../dist/hachidori/manifest.json', import.meta.url), 'utf8'),
);
const originalManifest = JSON.parse(readFileSync(source('manifest.json'), 'utf8'));
assert.deepEqual(manifest, {
+1 -1
View File
@@ -10,7 +10,7 @@ const repoRoot = path.resolve(dirname, '..');
const submoduleDir = path.join(repoRoot, 'vendor', 'subminer-yomitan');
const submodulePackagePath = path.join(submoduleDir, 'package.json');
const submodulePackageLockPath = path.join(submoduleDir, 'package-lock.json');
const buildOutputDir = path.join(repoRoot, 'build', 'yomitan');
const buildOutputDir = path.join(repoRoot, 'dist', 'yomitan');
const stampPath = path.join(buildOutputDir, '.subminer-build.json');
const zipPath = path.join(submoduleDir, 'builds', 'yomitan-chrome.zip');
const bunCommand = process.versions.bun ? process.execPath : 'bun';
+1 -1
View File
@@ -48,7 +48,7 @@ app
.then(async () => {
const targetSession = session.fromPartition('persist:hachidori-check');
const extension = await targetSession.extensions.loadExtension(
path.join(root, 'build/hachidori'),
path.join(root, 'dist/hachidori'),
{ allowFileAccess: true },
);
// Keep a host window alive while the dictionary importer opens/closes its
+4 -1
View File
@@ -18,7 +18,7 @@ test('platform packaging preserves the runtime allowlist after builder normalize
'/tmp/subminer-filter-output',
{
defaultSrc: root,
globalOutDir: path.join(root, 'release'),
globalOutDir: path.join(root, 'dist', 'release'),
customBuildOptions: { files: config.build[platform].files },
macroExpander: (value) => value.replaceAll('${arch}', 'x64'),
},
@@ -51,6 +51,9 @@ test('platform packaging preserves the runtime allowlist after builder normalize
'stats/dist/nested/fixtures/data.json',
'vendor/texthooker-ui/docs/nested/component.tsx',
'dist/launcher/subminer',
'dist/yomitan/manifest.json',
'dist/hachidori/manifest.json',
'dist/release/SubMiner-1.0.0.AppImage',
'dist/settings/fonts/MPLUS1[wght].ttf',
'vendor/subminer-yomitan/ext/manifest.json',
]) {
+9 -3
View File
@@ -10,14 +10,20 @@ import { Readable } from 'node:stream';
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, '..');
export const DEFAULT_MANIFEST_PATH = path.join(repoRoot, 'build', 'bun-source-manifest.json');
export const DEFAULT_MANIFEST_PATH = path.join(
repoRoot,
'packaging',
'bun',
'bun-source-manifest.json',
);
export const DEFAULT_RUNTIME_MANIFEST_PATH = path.join(
repoRoot,
'build',
'packaging',
'bun',
'bun-runtime-manifest.json',
);
export const DEFAULT_PACKAGE_JSON_PATH = path.join(repoRoot, 'package.json');
export const DEFAULT_OUTPUT_DIR = path.join(repoRoot, 'release');
export const DEFAULT_OUTPUT_DIR = path.join(repoRoot, 'dist', 'release');
export const DEFAULT_CACHE_DIR = path.join(repoRoot, '.tmp', 'bun-corresponding-source');
function isRecord(value) {
+21 -2
View File
@@ -4,6 +4,9 @@ import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {
DEFAULT_MANIFEST_PATH,
DEFAULT_PACKAGE_JSON_PATH,
DEFAULT_RUNTIME_MANIFEST_PATH,
downloadArchive,
parseRegisteredRepositories,
parseSourceManifest,
@@ -67,10 +70,26 @@ describe('Bun corresponding-source manifest', () => {
});
});
test('default manifest paths resolve to aligned checked-in manifests', async () => {
const [manifestText, runtimeManifestText, packageJsonText] = await Promise.all([
fs.readFile(DEFAULT_MANIFEST_PATH, 'utf8'),
fs.readFile(DEFAULT_RUNTIME_MANIFEST_PATH, 'utf8'),
fs.readFile(DEFAULT_PACKAGE_JSON_PATH, 'utf8'),
]);
expect(() =>
validateRuntimeAlignment(
parseSourceManifest(JSON.parse(manifestText)),
JSON.parse(packageJsonText),
JSON.parse(runtimeManifestText),
),
).not.toThrow();
});
test('rejects stale runtime or package-manager pins', async () => {
const manifest = parseSourceManifest(
JSON.parse(
await fs.readFile(path.join(projectRoot, 'build/bun-source-manifest.json'), 'utf8'),
await fs.readFile(path.join(projectRoot, 'packaging/bun/bun-source-manifest.json'), 'utf8'),
),
);
const runtimeManifest = {
@@ -100,7 +119,7 @@ describe('Bun corresponding-source manifest', () => {
test('rejects drift in CMake dependency and WebKit pins', async () => {
const manifest = parseSourceManifest(
JSON.parse(
await fs.readFile(path.join(projectRoot, 'build/bun-source-manifest.json'), 'utf8'),
await fs.readFile(path.join(projectRoot, 'packaging/bun/bun-source-manifest.json'), 'utf8'),
),
);
const registrations = manifest.sources
+1 -1
View File
@@ -6,7 +6,7 @@ cat <<'EOF'
patch-yomitan.sh is retired.
SubMiner now uses the forked source submodule at vendor/subminer-yomitan and builds the
Chromium extension artifact into build/yomitan.
Chromium extension artifact into dist/yomitan.
Use:
git submodule update --init --recursive
+2 -2
View File
@@ -9,9 +9,9 @@ paths=(
"tsconfig.json"
"tsconfig.renderer.json"
"tsconfig.typecheck.json"
".prettierrc.json"
".github"
"build"
"packaging/bun/bun-runtime-manifest.json"
"packaging/bun/bun-source-manifest.json"
"e2e"
"launcher"
"scripts"
+7 -2
View File
@@ -14,9 +14,14 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, '..');
export const DEFAULT_PACKAGE_JSON_PATH = path.join(repoRoot, 'package.json');
export const DEFAULT_MANIFEST_PATH = path.join(repoRoot, 'build', 'bun-runtime-manifest.json');
export const DEFAULT_MANIFEST_PATH = path.join(
repoRoot,
'packaging',
'bun',
'bun-runtime-manifest.json',
);
export const DEFAULT_CACHE_DIR = path.join(repoRoot, '.tmp', 'bun-runtime');
export const DEFAULT_LICENSES_SOURCE_DIR = path.join(repoRoot, 'resources', 'bun', 'licenses');
export const DEFAULT_LICENSES_SOURCE_DIR = path.join(repoRoot, 'packaging', 'bun', 'licenses');
export const STAGED_METADATA_FILE = 'metadata.json';
export const REQUIRED_LICENSE_FILES = [
'Bun-LICENSE.md',
+2 -2
View File
@@ -35,7 +35,7 @@ function runtimeConfig() {
'win32-x64': { file: 'bun-windows-x64-baseline.zip', sha256: '5'.repeat(64) },
},
licenseInventoryStatus: 'complete-for-bun-1.3.5-declared-linked-libraries',
sourceManifest: 'build/bun-source-manifest.json',
sourceManifest: 'packaging/bun/bun-source-manifest.json',
correspondingSourceAsset: 'bun-v1.3.5-source.tar.gz',
};
}
@@ -96,7 +96,7 @@ test('manifest version must match the exact packageManager Bun pin', () => {
releaseTagCommit: 'fa5a5bbe556a4bda5bde77b4013aa6c3bb4ec9ab',
artifacts: {},
licenseInventoryStatus: 'partial',
sourceManifest: 'build/bun-source-manifest.json',
sourceManifest: 'packaging/bun/bun-source-manifest.json',
correspondingSourceAsset: 'bun-v1.3.5-source.tar.gz',
},
'1.3.5',
@@ -11,9 +11,9 @@ test('Hachidori resolves development and packaged artifacts without falling back
assert.equal(
resolveHachidoriExtensionPath({
...options,
exists: (p) => p === '/app/build/hachidori/manifest.json',
exists: (p) => p === '/app/dist/hachidori/manifest.json',
}),
'/app/build/hachidori',
'/app/dist/hachidori',
);
assert.equal(
resolveHachidoriExtensionPath({
+1 -1
View File
@@ -15,7 +15,7 @@ export function resolveHachidoriExtensionPath(options: {
exists?: (candidate: string) => boolean;
}): string {
const candidates = [
path.resolve(options.moduleDir, '../../../build/hachidori'),
path.resolve(options.moduleDir, '../../../dist/hachidori'),
path.join(options.resourcesPath, 'hachidori'),
];
const found = candidates.find((candidate) =>
@@ -20,7 +20,7 @@ test('getYomitanExtensionSearchPaths prioritizes generated build output before p
});
assert.deepEqual(searchPaths, [
path.join(repoRoot, 'build', 'yomitan'),
path.join(repoRoot, 'dist', 'yomitan'),
path.join(resourcesPath, 'yomitan'),
'/usr/share/SubMiner/yomitan',
path.join(userDataPath, 'yomitan'),
@@ -29,19 +29,19 @@ test('getYomitanExtensionSearchPaths prioritizes generated build output before p
test('resolveExistingYomitanExtensionPath returns first manifest-backed candidate', () => {
const existing = new Set<string>([
path.join('/repo', 'build', 'yomitan', 'manifest.json'),
path.join('/repo', 'dist', 'yomitan', 'manifest.json'),
path.join('/repo', 'vendor', 'subminer-yomitan', 'ext', 'manifest.json'),
]);
const resolved = resolveExistingYomitanExtensionPath(
[
path.join('/repo', 'build', 'yomitan'),
path.join('/repo', 'dist', 'yomitan'),
path.join('/repo', 'vendor', 'subminer-yomitan', 'ext'),
],
(candidate) => existing.has(candidate),
);
assert.equal(resolved, path.join('/repo', 'build', 'yomitan'));
assert.equal(resolved, path.join('/repo', 'dist', 'yomitan'));
});
test('resolveExistingYomitanExtensionPath ignores source tree without built manifest', () => {
+2 -4
View File
@@ -22,12 +22,10 @@ export function getYomitanExtensionSearchPaths(
const searchPaths: string[] = [];
pushUnique(searchPaths, options.explicitPath ? path.resolve(options.explicitPath) : null);
pushUnique(searchPaths, options.cwd ? path.resolve(options.cwd, 'build', 'yomitan') : null);
pushUnique(searchPaths, options.cwd ? path.resolve(options.cwd, 'dist', 'yomitan') : null);
pushUnique(
searchPaths,
options.moduleDir
? path.resolve(options.moduleDir, '..', '..', '..', 'build', 'yomitan')
: null,
options.moduleDir ? path.resolve(options.moduleDir, '..', '..', '..', 'dist', 'yomitan') : null,
);
pushUnique(
searchPaths,
+38
View File
@@ -481,6 +481,8 @@ function installKeyboardTestGlobals() {
setSessionBindings: (value: CompiledSessionBinding[]) => {
sessionBindings = value;
},
// A plain overlay element over playback, outside any SubMiner UI.
createPlaybackTarget: () => new TestElement(),
createInteractiveTarget: () => {
const target = new TestElement();
target.closest = (selector: string) => (selector.includes('.modal') ? target : null);
@@ -1297,6 +1299,42 @@ test('Hachidori attention without an open popup pane still forwards imported mpv
}
});
test('an open subtitle sidebar only keeps imported mpv input aimed at the sidebar', async () => {
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
try {
testGlobals.setGetMpvInputBindings(async () => ({
keys: ['0', 'MBTN_LEFT_DBL'],
blockedKeys: [],
}));
await handlers.setupMpvInputForwarding();
await wait(0);
ctx.state.subtitleSidebarModalOpen = true;
const sidebarTarget = testGlobals.createInteractiveTarget();
const playbackTarget = testGlobals.createPlaybackTarget();
// Sidebar input runs first, while no key or button is held, so a held key cannot
// hide a forwarded press. Its release has no matching press to forward.
testGlobals.dispatchKeydown({ key: '0', code: 'Digit0', target: sidebarTarget });
testGlobals.dispatchMousedown({ button: 0, target: sidebarTarget });
testGlobals.dispatchMousedown({ button: 0, type: 'mouseup', target: sidebarTarget });
assert.deepEqual(testGlobals.mpvCommands, []);
testGlobals.dispatchKeydown({ key: '0', code: 'Digit0', target: playbackTarget });
testGlobals.dispatchMousedown({ button: 0, target: playbackTarget });
testGlobals.dispatchMousedown({ button: 0, type: 'mouseup', target: playbackTarget });
assert.deepEqual(testGlobals.mpvCommands, [
['keydown', '0'],
['keydown', 'MBTN_LEFT'],
['keyup', 'MBTN_LEFT'],
]);
} finally {
ctx.state.subtitleSidebarModalOpen = false;
testGlobals.restore();
}
});
test('keyboard mode: lookup toggle opens a lookup during Hachidori attention without a pane', async () => {
const { ctx, handlers, testGlobals } = createKeyboardHandlerHarness();
+2 -1
View File
@@ -123,12 +123,13 @@ export function createKeyboardHandlers(
}
// Imported mpv bindings only run when no SubMiner UI could be the input's target.
// The subtitle sidebar is a side panel over playback, so only input inside it (a
// `.modal` target) stays with SubMiner.
function canForwardToMpv(target: EventTarget | null): boolean {
return (
keyboardConfigLoaded &&
!ctx.state.playlistBrowserModalOpen &&
!ctx.state.youtubePickerModalOpen &&
!ctx.state.subtitleSidebarModalOpen &&
!isLookupPopupOpen(ctx.state, document) &&
!isInteractiveTarget(target)
);