From 3549e0ed939b7bced17f9812e3e0ff8253d0885f Mon Sep 17 00:00:00 2001 From: sudacode Date: Sat, 24 Oct 2026 23:28:38 -0700 Subject: [PATCH] chore(build): consolidate packaging inputs and build output layout (#299) --- .github/workflows/package-release.yml | 34 ++++++------- .github/workflows/prerelease.yml | 48 ++++++++++--------- .github/workflows/release.yml | 40 ++++++++-------- .gitignore | 8 +--- .prettierignore | 4 +- .prettierrc.json | 5 -- Makefile | 25 ++++------ changes/README.md | 4 +- changes/repo-root-packaging-layout.md | 5 ++ docs-site/installation.md | 2 +- docs-site/troubleshooting.md | 2 +- docs/RELEASING.md | 20 ++++---- docs/workflow/verification.md | 2 +- package.json | 19 +++++--- .../bun}/bun-runtime-manifest.json | 2 +- .../bun}/bun-source-manifest.json | 0 .../bun/licenses/Bun-LICENSE.md | 0 .../bun/licenses/LGPL-2.0.txt | 0 .../bun/licenses/LGPL-2.1.txt | 0 .../bun/licenses/SOURCE.md | 2 +- .../bun/licenses/THIRD-PARTY-NOTICES.md | 2 +- .../electron-builder}/entitlements.mac.plist | 0 .../electron-builder}/installer.nsh | 0 .../signpath-windows-artifact-config.xml | 0 .../release-notes}/prerelease-notes.md | 0 .../release-notes}/release-notes.md | 0 scripts/build-changelog.test.ts | 32 ++++++++----- scripts/build-changelog.ts | 8 ++-- scripts/build-hachidori.mjs | 2 +- scripts/build-hachidori.test.ts | 6 +-- scripts/build-yomitan.mjs | 2 +- scripts/check-hachidori-parser.cjs | 2 +- scripts/package-audit.test.ts | 5 +- scripts/package-bun-source.mjs | 12 +++-- scripts/package-bun-source.test.ts | 23 ++++++++- scripts/patch-yomitan.sh | 2 +- scripts/prettier-scope.sh | 4 +- scripts/stage-bun-runtime.mjs | 9 +++- scripts/stage-bun-runtime.test.ts | 4 +- src/core/services/hachidori-extension.test.ts | 4 +- src/core/services/hachidori-extension.ts | 2 +- .../services/yomitan-extension-paths.test.ts | 8 ++-- src/core/services/yomitan-extension-paths.ts | 6 +-- 43 files changed, 195 insertions(+), 160 deletions(-) delete mode 100644 .prettierrc.json create mode 100644 changes/repo-root-packaging-layout.md rename {build => packaging/bun}/bun-runtime-manifest.json (94%) rename {build => packaging/bun}/bun-source-manifest.json (100%) rename {resources => packaging}/bun/licenses/Bun-LICENSE.md (100%) rename {resources => packaging}/bun/licenses/LGPL-2.0.txt (100%) rename {resources => packaging}/bun/licenses/LGPL-2.1.txt (100%) rename {resources => packaging}/bun/licenses/SOURCE.md (95%) rename {resources => packaging}/bun/licenses/THIRD-PARTY-NOTICES.md (99%) rename {build => packaging/electron-builder}/entitlements.mac.plist (100%) rename {build => packaging/electron-builder}/installer.nsh (100%) rename {build => packaging/electron-builder}/signpath-windows-artifact-config.xml (100%) rename {release => packaging/release-notes}/prerelease-notes.md (100%) rename {release => packaging/release-notes}/release-notes.md (100%) diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index c04894ca..3008dd2f 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -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 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 6135476e..c7a1b043 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -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 ' 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 ' 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bb0e414..f09af985 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 ) diff --git a/.gitignore b/.gitignore index af0fe6c8..5027c4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/.prettierignore b/.prettierignore index ca33279c..eb96a9f6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -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 diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 5e2863a1..00000000 --- a/.prettierrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "printWidth": 100, - "singleQuote": true, - "trailingComma": "all" -} diff --git a/Makefile b/Makefile index 59adc5e3..f9bcc922 100644 --- a/Makefile +++ b/Makefile @@ -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)" diff --git a/changes/README.md b/changes/README.md index 3a04f5f5..6120101b 100644 --- a/changes/README.md +++ b/changes/README.md @@ -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 `
` 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 ` 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` diff --git a/changes/repo-root-packaging-layout.md b/changes/repo-root-packaging-layout.md new file mode 100644 index 00000000..2c7541d3 --- /dev/null +++ b/changes/repo-root-packaging-layout.md @@ -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/`. diff --git a/docs-site/installation.md b/docs-site/installation.md index b169d401..f4d71227 100644 --- a/docs-site/installation.md +++ b/docs-site/installation.md @@ -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`.
diff --git a/docs-site/troubleshooting.md b/docs-site/troubleshooting.md index 13c1952a..75612edd 100644 --- a/docs-site/troubleshooting.md +++ b/docs-site/troubleshooting.md @@ -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" diff --git a/docs/RELEASING.md b/docs/RELEASING.md index e11b7e91..5ebd4c31 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -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 --date ` - - 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 ` @@ -73,9 +73,9 @@ files before signing and generating updater hashes, never from a signed app. `bun-v-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 ` 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 ` 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 ` 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 `
Internal changes` 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 @ in #`, 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//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 `
Internal changes` 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 @ in #`, 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//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 ` 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. diff --git a/docs/workflow/verification.md b/docs/workflow/verification.md index f46ddc5d..06a6960e 100644 --- a/docs/workflow/verification.md +++ b/docs/workflow/verification.md @@ -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 `. - 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. diff --git a/package.json b/package.json index 1bc87195..05b7f9a2 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/build/bun-runtime-manifest.json b/packaging/bun/bun-runtime-manifest.json similarity index 94% rename from build/bun-runtime-manifest.json rename to packaging/bun/bun-runtime-manifest.json index e0dcd726..75a3b1b4 100644 --- a/build/bun-runtime-manifest.json +++ b/packaging/bun/bun-runtime-manifest.json @@ -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" } diff --git a/build/bun-source-manifest.json b/packaging/bun/bun-source-manifest.json similarity index 100% rename from build/bun-source-manifest.json rename to packaging/bun/bun-source-manifest.json diff --git a/resources/bun/licenses/Bun-LICENSE.md b/packaging/bun/licenses/Bun-LICENSE.md similarity index 100% rename from resources/bun/licenses/Bun-LICENSE.md rename to packaging/bun/licenses/Bun-LICENSE.md diff --git a/resources/bun/licenses/LGPL-2.0.txt b/packaging/bun/licenses/LGPL-2.0.txt similarity index 100% rename from resources/bun/licenses/LGPL-2.0.txt rename to packaging/bun/licenses/LGPL-2.0.txt diff --git a/resources/bun/licenses/LGPL-2.1.txt b/packaging/bun/licenses/LGPL-2.1.txt similarity index 100% rename from resources/bun/licenses/LGPL-2.1.txt rename to packaging/bun/licenses/LGPL-2.1.txt diff --git a/resources/bun/licenses/SOURCE.md b/packaging/bun/licenses/SOURCE.md similarity index 95% rename from resources/bun/licenses/SOURCE.md rename to packaging/bun/licenses/SOURCE.md index 8fa0a336..47cde1d7 100644 --- a/resources/bun/licenses/SOURCE.md +++ b/packaging/bun/licenses/SOURCE.md @@ -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. diff --git a/resources/bun/licenses/THIRD-PARTY-NOTICES.md b/packaging/bun/licenses/THIRD-PARTY-NOTICES.md similarity index 99% rename from resources/bun/licenses/THIRD-PARTY-NOTICES.md rename to packaging/bun/licenses/THIRD-PARTY-NOTICES.md index d4a64575..8534c1b3 100644 --- a/resources/bun/licenses/THIRD-PARTY-NOTICES.md +++ b/packaging/bun/licenses/THIRD-PARTY-NOTICES.md @@ -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 diff --git a/build/entitlements.mac.plist b/packaging/electron-builder/entitlements.mac.plist similarity index 100% rename from build/entitlements.mac.plist rename to packaging/electron-builder/entitlements.mac.plist diff --git a/build/installer.nsh b/packaging/electron-builder/installer.nsh similarity index 100% rename from build/installer.nsh rename to packaging/electron-builder/installer.nsh diff --git a/build/signpath-windows-artifact-config.xml b/packaging/electron-builder/signpath-windows-artifact-config.xml similarity index 100% rename from build/signpath-windows-artifact-config.xml rename to packaging/electron-builder/signpath-windows-artifact-config.xml diff --git a/release/prerelease-notes.md b/packaging/release-notes/prerelease-notes.md similarity index 100% rename from release/prerelease-notes.md rename to packaging/release-notes/prerelease-notes.md diff --git a/release/release-notes.md b/packaging/release-notes/release-notes.md similarity index 100% rename from release/release-notes.md rename to packaging/release-notes/release-notes.md diff --git a/scripts/build-changelog.test.ts b/scripts/build-changelog.test.ts index 77661893..176bbf87 100644 --- a/scripts/build-changelog.test.ts +++ b/scripts/build-changelog.test.ts @@ -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, /
/); 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
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, /
/); 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/); diff --git a/scripts/build-changelog.ts b/scripts/build-changelog.ts index b15615e6..f43a89e4 100644 --- a/scripts/build-changelog.ts +++ b/scripts/build-changelog.ts @@ -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.`, ); } } diff --git a/scripts/build-hachidori.mjs b/scripts/build-hachidori.mjs index c6d4a388..8a1f462c 100644 --- a/scripts/build-hachidori.mjs +++ b/scripts/build-hachidori.mjs @@ -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 diff --git a/scripts/build-hachidori.test.ts b/scripts/build-hachidori.test.ts index 3332bbcb..5323850f 100644 --- a/scripts/build-hachidori.test.ts +++ b/scripts/build-hachidori.test.ts @@ -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, { diff --git a/scripts/build-yomitan.mjs b/scripts/build-yomitan.mjs index a52d10e0..d9184d43 100644 --- a/scripts/build-yomitan.mjs +++ b/scripts/build-yomitan.mjs @@ -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'; diff --git a/scripts/check-hachidori-parser.cjs b/scripts/check-hachidori-parser.cjs index 50c5ad34..c3fe747c 100644 --- a/scripts/check-hachidori-parser.cjs +++ b/scripts/check-hachidori-parser.cjs @@ -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 diff --git a/scripts/package-audit.test.ts b/scripts/package-audit.test.ts index 285ccd84..cf66e935 100644 --- a/scripts/package-audit.test.ts +++ b/scripts/package-audit.test.ts @@ -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', ]) { diff --git a/scripts/package-bun-source.mjs b/scripts/package-bun-source.mjs index 1d88d3c6..58c01bce 100644 --- a/scripts/package-bun-source.mjs +++ b/scripts/package-bun-source.mjs @@ -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) { diff --git a/scripts/package-bun-source.test.ts b/scripts/package-bun-source.test.ts index 0bd341c2..af6251d7 100644 --- a/scripts/package-bun-source.test.ts +++ b/scripts/package-bun-source.test.ts @@ -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 diff --git a/scripts/patch-yomitan.sh b/scripts/patch-yomitan.sh index 577cb7a8..b83ed4f2 100755 --- a/scripts/patch-yomitan.sh +++ b/scripts/patch-yomitan.sh @@ -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 diff --git a/scripts/prettier-scope.sh b/scripts/prettier-scope.sh index adfc3528..add27c76 100644 --- a/scripts/prettier-scope.sh +++ b/scripts/prettier-scope.sh @@ -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" diff --git a/scripts/stage-bun-runtime.mjs b/scripts/stage-bun-runtime.mjs index e18ce7ec..84648408 100644 --- a/scripts/stage-bun-runtime.mjs +++ b/scripts/stage-bun-runtime.mjs @@ -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', diff --git a/scripts/stage-bun-runtime.test.ts b/scripts/stage-bun-runtime.test.ts index 1d166d2d..869ebb3b 100644 --- a/scripts/stage-bun-runtime.test.ts +++ b/scripts/stage-bun-runtime.test.ts @@ -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', diff --git a/src/core/services/hachidori-extension.test.ts b/src/core/services/hachidori-extension.test.ts index 10b71887..010bef5f 100644 --- a/src/core/services/hachidori-extension.test.ts +++ b/src/core/services/hachidori-extension.test.ts @@ -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({ diff --git a/src/core/services/hachidori-extension.ts b/src/core/services/hachidori-extension.ts index 5fde9c7d..987d5e23 100644 --- a/src/core/services/hachidori-extension.ts +++ b/src/core/services/hachidori-extension.ts @@ -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) => diff --git a/src/core/services/yomitan-extension-paths.test.ts b/src/core/services/yomitan-extension-paths.test.ts index d4e32806..00d51d32 100644 --- a/src/core/services/yomitan-extension-paths.test.ts +++ b/src/core/services/yomitan-extension-paths.test.ts @@ -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([ - 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', () => { diff --git a/src/core/services/yomitan-extension-paths.ts b/src/core/services/yomitan-extension-paths.ts index d6863edc..85061d2e 100644 --- a/src/core/services/yomitan-extension-paths.ts +++ b/src/core/services/yomitan-extension-paths.ts @@ -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,