From 5096ca217b61c66c94ad3f8541e66473a9222089 Mon Sep 17 00:00:00 2001 From: sudacode Date: Wed, 23 Sep 2026 21:50:19 -0700 Subject: [PATCH] chore(release): remove package size reports - Drop the afterAllArtifactBuild size reporting hook and the previous-release baseline downloads in CI - Stop uploading, checksumming, and publishing package-size-*.json - Keep the afterPack package content audit, now without size tracking - Update RELEASING.md and the verification docs, and add a changelog fragment --- .github/workflows/package-release.yml | 36 --------- .github/workflows/prerelease.yml | 2 - .github/workflows/release.yml | 2 - changes/remove-package-size-reports.md | 4 + docs/RELEASING.md | 18 +---- docs/workflow/verification.md | 6 +- package.json | 3 +- scripts/package-audit.cjs | 104 ++----------------------- scripts/package-audit.test.ts | 34 +------- src/prerelease-workflow.test.ts | 9 --- src/release-workflow.test.ts | 1 - 11 files changed, 24 insertions(+), 195 deletions(-) create mode 100644 changes/remove-package-size-reports.md diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index 62feb3f5..c04894ca 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -59,17 +59,6 @@ jobs: bun install --frozen-lockfile bun run build - - name: Download previous package size reports - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p .tmp/package-baseline - previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty') - if [ -n "$previous" ]; then - gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.' - fi - - name: Build AppImage run: bun run build:appimage env: @@ -98,7 +87,6 @@ jobs: release/*.AppImage release/latest*.yml release/*.blockmap - release/package-size-*.json if-no-files-found: error build-macos: @@ -159,17 +147,6 @@ jobs: bun install --frozen-lockfile bun run build - - name: Download previous package size reports - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p .tmp/package-baseline - previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty') - if [ -n "$previous" ]; then - gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.' - fi - - name: Build signed + notarized macOS artifacts run: bun run build:mac env: @@ -193,7 +170,6 @@ jobs: release/*.zip release/latest*.yml release/*.blockmap - release/package-size-*.json if-no-files-found: error build-windows: @@ -235,17 +211,6 @@ jobs: bun install --frozen-lockfile bun run build - - name: Download previous package size reports - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mkdir -p .tmp/package-baseline - previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty') - if [ -n "$previous" ]; then - gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.' - fi - - name: Verify managed Windows launcher run: bun test src/main/runtime/managed-launcher.test.ts @@ -270,5 +235,4 @@ jobs: release/*.zip release/latest*.yml release/*.blockmap - release/package-size-*.json if-no-files-found: error diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index fc64e1b9..6135476e 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -105,7 +105,6 @@ jobs: 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+=(release/package-size-*.json) if [ "${#files[@]}" -eq 0 ]; then echo "No release artifacts found for checksum generation." exit 1 @@ -152,7 +151,6 @@ jobs: release/latest*.yml release/*.blockmap release/SHA256SUMS.txt - release/package-size-*.json dist/launcher/subminer dist/launcher/subminer.cmd ) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6271d1d9..1bb0e414 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,7 +106,6 @@ jobs: 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+=(release/package-size-*.json) if [ "${#files[@]}" -eq 0 ]; then echo "No release artifacts found for checksum generation." exit 1 @@ -171,7 +170,6 @@ jobs: release/latest*.yml release/*.blockmap release/SHA256SUMS.txt - release/package-size-*.json dist/launcher/subminer dist/launcher/subminer.cmd ) diff --git a/changes/remove-package-size-reports.md b/changes/remove-package-size-reports.md new file mode 100644 index 00000000..e21b3c44 --- /dev/null +++ b/changes/remove-package-size-reports.md @@ -0,0 +1,4 @@ +type: changed +area: release + +- Removed package-size JSON reports from future releases and their CI size comparisons. Package-content validation remains enabled. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 5fb21b66..e11b7e91 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -11,7 +11,7 @@ `ANTHROPIC_API_KEY` works. Install from if you don't already have it. -## Package contents and size checks +## Package contents checks Stable and prerelease workflows share `.github/workflows/package-release.yml`. Both callers explicitly pass the five required macOS signing/notarization @@ -19,11 +19,9 @@ secrets plus the optional `SUBMINER_TMDB_API_KEY` (the project TMDB key that `scripts/prepare-build-assets.mjs` stages into `dist/bundled-integration-keys.json`; artifacts built without it simply require users to set `tmdb.apiKey`). `GITHUB_TOKEN` remains automatically available to the reusable workflow. -Each platform verifies its ASAR and external resources before signing, then -measures the signed app and installers before upload. Missing runtime assets, -foreign SQLite/Koffi binaries, duplicate UI fonts, demo media, source maps, -TypeScript files, and nested test or fixture directories -fail the build. Size measurements are informational and do not block releases. +Each platform verifies its ASAR and external resources before signing. Missing +runtime assets, foreign SQLite/Koffi binaries, duplicate UI fonts, demo media, +source maps, TypeScript files, and nested test or fixture directories fail the build. Current targets are Linux x64, macOS arm64, and Windows x64. The runtime allowlist includes `dist/`, `stats/dist/`, and @@ -34,14 +32,6 @@ exclusion-only platform list as a separate include-all matcher. Windows keeps only its target Koffi binary; other platforms omit Koffi. Desktop UIs share the original M PLUS 1 TTF in `dist/fonts/`. -`release/package-size--.json` reports unpacked bytes, largest -files inside and outside ASAR, native binaries, and compressed artifact sizes. -Framework symlinks are not counted twice. Reports are checksummed and published. -CI downloads the preceding release's reports for comparison; older releases -without reports skip comparison. Review the inventory and reason for growth -when comparing releases. An AppImage normally -runs compressed; its extracted size is a separate measurement. - The shared workflow runs `bun run test:package ` with the pinned Electron runtime and temporary user data. On headless Linux, prefix it with `xvfb-run -a`. This checks packaged SQLite, Windows FFI loading/polling, diff --git a/docs/workflow/verification.md b/docs/workflow/verification.md index f39191ea..ed780f20 100644 --- a/docs/workflow/verification.md +++ b/docs/workflow/verification.md @@ -60,9 +60,9 @@ bun run docs:build - 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`. - Content checks and informational size reporting run inside electron-builder hooks. See the - [release guide](../RELEASING.md#package-contents-and-size-checks) for size reports - and the installed-app verification checklist. + Content checks run inside the electron-builder afterPack hook. See the + [release guide](../RELEASING.md#package-contents-checks) for the + installed-app verification checklist. - Coverage for the maintained source lane: `bun run test:coverage:src` - Deep/local full gate: default handoff gate above diff --git a/package.json b/package.json index 4f83711c..bf31a0a2 100644 --- a/package.json +++ b/package.json @@ -269,8 +269,7 @@ "from": "CHANGELOG.md", "to": "CHANGELOG.md" } - ], - "afterAllArtifactBuild": "scripts/package-audit.cjs" + ] }, "patchedDependencies": { "@discordjs/rest@2.6.1": "patches/@discordjs%2Frest@2.6.1.patch" diff --git a/scripts/package-audit.cjs b/scripts/package-audit.cjs index 94a6ee4b..ad83e40a 100644 --- a/scripts/package-audit.cjs +++ b/scripts/package-audit.cjs @@ -4,8 +4,6 @@ const assert = require('node:assert/strict'); const asar = require('@electron/asar'); const { Arch } = require('builder-util'); -const MIB = 1024 * 1024; -const currentReports = new Set(); const REQUIRED_APP_FILES = [ 'package.json', 'LICENSE', @@ -41,13 +39,13 @@ const REQUIRED_RESOURCES = [ 'CHANGELOG.md', ]; -// Do not follow framework symlinks or count ASAR unpacked entries twice. +// Skip symlinks when checking resource contents. function listFiles(root, prefix = '') { return fs.readdirSync(path.join(root, prefix), { withFileTypes: true }).flatMap((entry) => { const name = prefix ? `${prefix}/${entry.name}` : entry.name; if (entry.isSymbolicLink()) return []; if (entry.isDirectory()) return listFiles(root, name); - return [{ path: name, bytes: fs.statSync(path.join(root, name)).size }]; + return [name]; }); } @@ -58,7 +56,7 @@ function listAppFiles(archive) { const native = entry.replace(/^[\\/]/, ''); const stat = asar.statFile(archive, native); const name = native.replaceAll('\\', '/'); - return 'size' in stat ? [{ path: name, bytes: stat.size }] : []; + return 'size' in stat ? [name] : []; }); } @@ -99,13 +97,13 @@ function verifyAppPath(name, platform, arch) { function verifyContents(archive, resources, platform, arch) { const entries = listAppFiles(archive); - const names = new Set(entries.map((entry) => entry.path)); + const names = new Set(entries); for (const name of REQUIRED_APP_FILES) assert(names.has(name), `Missing app file: ${name}`); for (const name of REQUIRED_RESOURCES) { assert(fs.statSync(path.join(resources, name)).size > 0, `Empty resource: ${name}`); } assert(listFiles(path.join(resources, 'yomitan-jlpt-vocab')).length > 0, 'Missing JLPT data'); - for (const { path: name } of entries) verifyAppPath(name, platform, arch); + for (const name of entries) verifyAppPath(name, platform, arch); const libsqlPlatform = { linux: `linux-${arch}-gnu`, darwin: `darwin-${arch}`, @@ -129,7 +127,7 @@ function verifyContents(archive, resources, platform, arch) { } } for (const name of listFiles(path.join(resources, 'assets'))) { - assert(!name.path.startsWith('minecard'), `Demo media shipped: ${name.path}`); + assert(!name.startsWith('minecard'), `Demo media shipped: ${name}`); } for (const ui of ['renderer', 'settings', 'syncui']) { const css = asar.extractFile(archive, path.join('dist', ui, 'style.css')).toString(); @@ -147,92 +145,8 @@ async function auditPackage(context) { ? path.join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`) : context.appOutDir; const resources = path.join(appRoot, platform === 'darwin' ? 'Contents/Resources' : 'resources'); - const appFiles = verifyContents(path.join(resources, 'app.asar'), resources, platform, arch); - const files = listFiles(appRoot); - const unpackedBytes = files.reduce((sum, entry) => sum + entry.bytes, 0); - const report = { - version: context.packager.appInfo.version, - platform, - arch, - unpackedBytes, - appDirectory: path.relative(context.outDir, appRoot), - largestFiles: [...files].sort((a, b) => b.bytes - a.bytes).slice(0, 25), - largestAppFiles: [...appFiles].sort((a, b) => b.bytes - a.bytes).slice(0, 25), - nativeBinaries: files.filter((entry) => /\.(node|dll|dylib)$|\.so(?:\.|$)/.test(entry.path)), - artifacts: [], - }; - const output = path.join(context.outDir, `package-size-${key}.json`); - fs.mkdirSync(path.dirname(output), { recursive: true }); - fs.writeFileSync(output, `${JSON.stringify(report, null, 2)}\n`); - currentReports.add(output); - console.log( - `Package contents verified: ${key}, ${(unpackedBytes / MIB).toFixed(2)} MiB unpacked`, - ); -} - -function artifactKind(name) { - if (name.endsWith('-mac.zip')) return 'mac.zip'; - if (name.endsWith('-win.zip')) return 'win.zip'; - const extension = path.extname(name).slice(1); - return ['AppImage', 'dmg', 'exe'].includes(extension) ? extension : undefined; -} - -function compareSizes(report, previous) { - assert.equal(previous.platform, report.platform); - assert.equal(previous.arch, report.arch); - assert(Number.isFinite(previous.unpackedBytes), 'Invalid previous size report'); - const previousArtifacts = Array.isArray(previous.artifacts) ? previous.artifacts : []; - return { - version: previous.version, - unpackedDeltaBytes: report.unpackedBytes - previous.unpackedBytes, - artifacts: report.artifacts.flatMap((artifact) => { - const old = previousArtifacts.find( - (entry) => entry && entry.kind === artifact.kind && Number.isFinite(entry.bytes), - ); - return old ? [{ kind: artifact.kind, deltaBytes: artifact.bytes - old.bytes }] : []; - }), - }; -} - -// Runs after signing and installer creation, before release upload. -async function afterAllArtifactBuild(result) { - const reports = []; - for (const reportPath of currentReports) { - const filename = path.basename(reportPath); - const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); - const key = `${report.platform}-${report.arch}`; - const files = listFiles(path.join(result.outDir, report.appDirectory)); - report.unpackedBytes = files.reduce((sum, entry) => sum + entry.bytes, 0); - report.largestFiles = [...files].sort((a, b) => b.bytes - a.bytes).slice(0, 25); - report.artifacts = result.artifactPaths.flatMap((file) => { - const kind = artifactKind(file); - if (!kind) return []; - const bytes = fs.statSync(file).size; - return [{ name: path.basename(file), kind, bytes }]; - }); - const previousPath = path.join(result.outDir, '..', '.tmp', 'package-baseline', filename); - if (fs.existsSync(previousPath)) { - const previous = JSON.parse(fs.readFileSync(previousPath, 'utf8')); - report.comparison = compareSizes(report, previous); - } - fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); - const summary = [ - `### Package size: ${key}`, - '', - `Unpacked: ${(report.unpackedBytes / MIB).toFixed(2)} MiB`, - ...report.artifacts.map((entry) => `${entry.name}: ${(entry.bytes / MIB).toFixed(2)} MiB`), - report.comparison - ? `Change from ${report.comparison.version}: ${(report.comparison.unpackedDeltaBytes / MIB).toFixed(2)} MiB unpacked` - : 'No previous size report available.', - '', - ].join('\n'); - console.log(summary); - if (process.env.GITHUB_STEP_SUMMARY) - fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary); - reports.push(reportPath); - } - assert(reports.length > 0, 'No package size reports generated by afterPack'); - return reports; + verifyContents(path.join(resources, 'app.asar'), resources, platform, arch); + console.log(`Package contents verified: ${key}`); } module.exports = { @@ -241,6 +155,4 @@ module.exports = { verifyAppPath, listFiles, listAppFiles, - compareSizes, - default: afterAllArtifactBuild, }; diff --git a/scripts/package-audit.test.ts b/scripts/package-audit.test.ts index 548c2440..285ccd84 100644 --- a/scripts/package-audit.test.ts +++ b/scripts/package-audit.test.ts @@ -6,7 +6,7 @@ import test from 'node:test'; import { createPackageFromStreams } from '@electron/asar'; import { FileMatcher, getFileMatchers } from 'app-builder-lib/out/fileMatcher'; import config from '../package.json'; -import { listAppFiles, listFiles, compareSizes, verifyAppPath } from './package-audit.cjs'; +import { listAppFiles, listFiles, verifyAppPath } from './package-audit.cjs'; test('platform packaging preserves the runtime allowlist after builder normalizes global filters', () => { const root = process.cwd(); @@ -125,7 +125,7 @@ test('content audit rejects development files beneath approved roots', () => { } }); -test('archive inventory handles native files without counting them twice on disk', async () => { +test('content inventory includes packed and unpacked native files', async () => { const root = mkdtempSync(path.join(tmpdir(), 'subminer-audit-')); try { const input = path.join(root, 'input'); @@ -147,35 +147,9 @@ test('archive inventory handles native files without counting them twice on disk streamGenerator: () => createReadStream(path.join(input, name)), })), ); - assert.deepEqual(listAppFiles(archive), [ - { path: 'main.js', bytes: 5 }, - { path: 'native.node', bytes: 6 }, - { path: 'dist/ai/client.js', bytes: 6 }, - ]); - assert.equal( - listFiles(output).reduce((sum: number, entry: { bytes: number }) => sum + entry.bytes, 0), - statSync(archive).size + 6, - ); + assert.deepEqual(listAppFiles(archive), ['main.js', 'native.node', 'dist/ai/client.js']); + assert.deepEqual(listFiles(output).sort(), ['app.asar', 'app.asar.unpacked/native.node']); } finally { rmSync(root, { recursive: true, force: true }); } }); - -test('size comparison tolerates older reports without artifact measurements', () => { - const previous = { version: '0.19.6', platform: 'linux', arch: 'x64', unpackedBytes: 100 }; - const current = { ...previous, unpackedBytes: 80, artifacts: [{ kind: 'AppImage', bytes: 40 }] }; - assert.deepEqual(compareSizes(current, previous), { - version: '0.19.6', - unpackedDeltaBytes: -20, - artifacts: [], - }); - assert.deepEqual( - compareSizes(current, { ...previous, artifacts: [null, { kind: 'AppImage', bytes: 50 }] }) - .artifacts, - [{ kind: 'AppImage', deltaBytes: -10 }], - ); - assert.throws( - () => compareSizes(current, { ...previous, unpackedBytes: 'unknown' }), - /Invalid previous size report/, - ); -}); diff --git a/src/prerelease-workflow.test.ts b/src/prerelease-workflow.test.ts index 2d884663..29127f0b 100644 --- a/src/prerelease-workflow.test.ts +++ b/src/prerelease-workflow.test.ts @@ -3,7 +3,6 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { - executableRunLines, jobSteps, readWorkflow, stepRunsCommand, @@ -109,20 +108,12 @@ test('prerelease workflow builds and uploads all release platforms', () => { ...paths, 'release/latest*.yml', 'release/*.blockmap', - 'release/package-size-*.json', ]); const download = jobSteps(parsedPrereleaseWorkflow, 'release').find( (step) => step.uses === 'actions/download-artifact@v4' && step.with?.name === name, ); assert.equal(download?.with?.path, 'release'); } - const steps = jobSteps(parsedPrereleaseWorkflow, 'release'); - const checksum = steps.find((step) => step.name === 'Generate checksums'); - const publish = steps.find((step) => step.name === 'Publish Prerelease'); - assert.ok(checksum); - assert.ok(publish); - assert.ok(executableRunLines(checksum).includes('files+=(release/package-size-*.json)')); - assert.ok(executableRunLines(publish).includes('release/package-size-*.json')); }); test('release callers pass only the declared packaging secrets', () => { diff --git a/src/release-workflow.test.ts b/src/release-workflow.test.ts index 8f796f01..55d4ee9b 100644 --- a/src/release-workflow.test.ts +++ b/src/release-workflow.test.ts @@ -289,7 +289,6 @@ test('stable and prerelease builds use the same packaging gate', () => { for (const workflow of [releaseWorkflow, prerelease]) { assert.match(workflow, /uses: \.\/\.github\/workflows\/package-release\.yml/); assert.match(workflow, /needs: \[package\]/); - assert.match(workflow, /release\/package-size-\*\.json/); } assert.deepEqual( templateExpressionsInRunBodies(