diff --git a/RELEASE.md b/RELEASE.md index dc97c2c..f4b8954 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -11,7 +11,7 @@ Targets: ## Distribution policy -Firelink does not use an Apple Developer account. macOS releases are unsigned and not notarized. Users must explicitly approve the downloaded app through Finder or macOS Privacy & Security. Release copy must never describe these builds as signed, notarized, or Gatekeeper-approved. +Firelink does not use an Apple Developer account. macOS releases are ad-hoc signed but not notarized or Gatekeeper-approved. Users may still need to explicitly approve the downloaded app through Finder or macOS Privacy & Security. Release copy must not describe these builds as Developer ID signed, notarized, or Gatekeeper-approved. Windows releases are currently unsigned. SmartScreen may warn until code signing is added. @@ -58,10 +58,12 @@ node scripts/verify-binaries.js --search-root "$APP" --target aarch64-apple-darw node scripts/smoke-packaged-app.js --executable "$APP/Contents/MacOS/firelink" ``` -GitHub release publication is intentionally manual. Tag pushes build and upload -artifacts, but the `publish` job only runs from a `workflow_dispatch` on a `v*` -tag when both release-certification inputs are checked after Windows, Linux, -and macOS clean-machine QA. +GitHub release publication follows `.github/workflows/release.yml`. A `v*` tag +push builds, verifies, and publishes the GitHub release after the platform jobs +pass. A `workflow_dispatch` on a `v*` tag also publishes when its +`publish_release` input is enabled. The current workflow has no separate +release-certification inputs; clean-machine QA remains a release-owner gate +before pushing the tag. ## Automated release builds @@ -74,7 +76,6 @@ git push origin v GitHub Actions builds all targets on native runners, verifies engines inside final package contents, performs packaged launch smoke where supported, and -uploads artifacts. Publish the GitHub Release with a manual `workflow_dispatch` -run on the same tag after clean-machine QA is complete. +publishes the GitHub Release after the build matrix passes. No target may silently skip missing engines, failed extraction, checksum mismatch, or missing package output. diff --git a/engine-sources.lock.json b/engine-sources.lock.json index ad3d2fd..0171af1 100644 --- a/engine-sources.lock.json +++ b/engine-sources.lock.json @@ -14,8 +14,8 @@ }, "ffmpeg": { "version": "8.1.2-22-g94138f6973", - "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-06-14-19/ffmpeg-n8.1.2-22-g94138f6973-win64-gpl-8.1.zip", - "sha256": "a758e2836a8f33c5f21fc44270cb00392acc6d0085dd0ba14fe14ae75935813d" + "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-14-13-19/ffmpeg-n8.1.2-22-g94138f6973-win64-gpl-8.1.zip", + "sha256": "fb627ebbf9b16a3142fa48b8acd27cd9196d57aee1f719e1233416e95da7e877" }, "aria2c": { "version": "1.37.0", @@ -36,8 +36,8 @@ }, "ffmpeg": { "version": "8.1.2-22-g94138f6973", - "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-06-14-19/ffmpeg-n8.1.2-22-g94138f6973-linux64-gpl-8.1.tar.xz", - "sha256": "df99ffb3803ee56dc68954f43f950ea9f33685a3595a5da8a3e73ef4bef37e3c" + "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-14-13-19/ffmpeg-n8.1.2-22-g94138f6973-linux64-gpl-8.1.tar.xz", + "sha256": "96cf2585a4b2874044320cbc5d4078af57e266f0c86cca9888507c5faab3aee8" }, "aria2c": { "version": "1.37.0", diff --git a/engines.lock.json b/engines.lock.json index 5621055..2e1a822 100644 --- a/engines.lock.json +++ b/engines.lock.json @@ -16,10 +16,11 @@ "sha256": "111b2f5ed760f1e1a2ec06117c4e8094fcde336ba16122dda1c5e7209bf1862d" }, "ffmpeg": { - "version": "N-125450-gfad2e0bc50", + "version": "N-125610-g312c830916", "source": "https://ffmpeg.org/", "build": "GPLv3 build identified by binary as https://www.martin-riedl.de", - "sha256": "be2c39e5c9ef923f60da6cb62f5a209ed98b4da8a732d9f06de4355d5ea99e58" + "url": "https://ffmpeg.martin-riedl.de/download/macos/arm64/1784052810_N-125610-g312c830916/ffmpeg.zip", + "sha256": "f65b4ba782b25e9f4374765d421793c6613f692d8e50b2888079657793619034" }, "deno": { "version": "2.9.2", diff --git a/scripts/check-updates.js b/scripts/check-updates.js index ece8853..b457e54 100644 --- a/scripts/check-updates.js +++ b/scripts/check-updates.js @@ -83,22 +83,38 @@ async function latestMartinRiedlMacArm64Release() { async function latestMartinRiedlMacArm64Snapshot() { const html = await fetchText('https://ffmpeg.martin-riedl.de/'); const snapshotSection = html.split('Download Snapshot Build')[1]?.split('Download Release Build')[0] || ''; + const card = snapshotSection.match(/

macOS \(Apple Silicon\/arm64\)<\/h3>[\s\S]*?<\/div>/)?.[0] || ''; const match = - snapshotSection.match(/macOS \(Apple Silicon\/arm64\)[\s\S]*?Release:\s*<\/b>\s*([A-Za-z0-9.-]+)/) || - snapshotSection.match(/macOS \(Apple Silicon\/arm64\)[\s\S]*?Release:\s*([A-Za-z0-9.-]+)/); - return match?.[1]; + card.match(/Release:\s*<\/b>\s*([A-Za-z0-9.-]+)/) || + card.match(/Release:\s*([A-Za-z0-9.-]+)/); + const url = card.match(/href="([^"]+\/ffmpeg\.zip)"/)?.[1]; + return match?.[1] + ? { version: match[1], url: url ? new URL(url, 'https://ffmpeg.martin-riedl.de').href : undefined } + : undefined; } async function latestBtbnFfmpegN81Build() { const releases = await fetchJson('https://api.github.com/repos/BtbN/FFmpeg-Builds/releases?per_page=10'); for (const release of releases) { if (release.tag_name === 'latest') continue; - const versions = (release.assets || []) - .map(asset => asset.name.match(/^ffmpeg-n(8\.1\.\d+-\d+-g[0-9a-f]+)-(?:win64-gpl-8\.1\.zip|linux64-gpl-8\.1\.tar\.xz)$/)?.[1]) + const assets = (release.assets || []) + .map(asset => { + const match = asset.name.match(/^ffmpeg-n(8\.1\.\d+-\d+-g[0-9a-f]+)-(win64|linux64)-gpl-8\.1\.(?:zip|tar\.xz)$/); + if (!match) return undefined; + return { + target: match[2] === 'win64' ? 'windows' : 'linux', + version: match[1], + url: asset.browser_download_url, + }; + }) .filter(Boolean); - const unique = [...new Set(versions)]; - if (unique.length === 1 && versions.length >= 2) { - return unique[0]; + const unique = [...new Set(assets.map(asset => asset.version))]; + const byTarget = Object.fromEntries(assets.map(asset => [asset.target, asset])); + if (unique.length === 1 && byTarget.windows && byTarget.linux) { + return { + version: unique[0], + urls: { windows: byTarget.windows.url, linux: byTarget.linux.url }, + }; } } return undefined; @@ -131,22 +147,26 @@ function packagedEngineVersions(engineLock) { const rows = []; for (const [target, targetLock] of Object.entries(engineLock.targets || {})) { for (const [engine, meta] of Object.entries(targetLock.engines || {})) { - rows.push({ target, engine, version: meta.version }); + rows.push({ target, engine, version: meta.version, url: meta.url }); } } return rows; } -function checkRows(rows, latestByEngine, latestByTargetEngine = {}) { +function checkRows(rows, latestByEngine, latestByTargetEngine = {}, latestUrlsByTargetEngine = {}) { let outdated = 0; for (const row of rows) { const latest = latestByTargetEngine[`${row.target}:${row.engine}`] || latestByEngine[row.engine]; if (!latest) continue; const current = normalizeVersion(row.version); const wanted = normalizeVersion(latest); - const status = compareVersions(current, wanted) < 0 ? 'outdated' : 'current'; - if (status === 'outdated') outdated += 1; + const latestUrl = latestUrlsByTargetEngine[`${row.target}:${row.engine}`]; + const versionOutdated = compareVersions(current, wanted) < 0; + const sourceOutdated = Boolean(latestUrl && row.url && row.url !== latestUrl); + const status = versionOutdated ? 'outdated' : sourceOutdated ? 'source-outdated' : 'current'; + if (status !== 'current') outdated += 1; console.log(` ${row.target} ${row.engine}: ${current} -> ${wanted} ${status}`); + if (sourceOutdated) console.log(` source: ${row.url} -> ${latestUrl}`); } return outdated; } @@ -184,9 +204,14 @@ async function main() { ffmpeg, }; const latestByTargetEngine = { - 'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build || ffmpeg, - 'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build || ffmpeg, - 'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot || martinRiedlMacArm64Ffmpeg, + 'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build?.version || ffmpeg, + 'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build?.version || ffmpeg, + 'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot?.version || martinRiedlMacArm64Ffmpeg, + }; + const latestUrlsByTargetEngine = { + 'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build?.urls.windows, + 'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build?.urls.linux, + 'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot?.url, }; console.log('\nlatest engines:'); @@ -194,21 +219,23 @@ async function main() { console.log(` ${engine}: ${normalizeVersion(version)}`); } console.log('\nlatest engine provider builds:'); - console.log(` BtbN FFmpeg n8.1 Windows/Linux: ${normalizeVersion(btbnFfmpegN81Build || ffmpeg)}`); - console.log(` Martin Riedl FFmpeg macOS arm64 snapshot: ${normalizeVersion(martinRiedlMacArm64Snapshot || martinRiedlMacArm64Ffmpeg)}`); + console.log(` BtbN FFmpeg n8.1 Windows/Linux: ${normalizeVersion(btbnFfmpegN81Build?.version || ffmpeg)}`); + console.log(` Martin Riedl FFmpeg macOS arm64 snapshot: ${normalizeVersion(martinRiedlMacArm64Snapshot?.version || martinRiedlMacArm64Ffmpeg)}`); console.log('\nengine source lock:'); outdatedCount += checkRows( sourceEngineVersions(parseJsonFile('engine-sources.lock.json')), latestByEngine, - latestByTargetEngine + latestByTargetEngine, + latestUrlsByTargetEngine ); console.log('\npackaged engine lock:'); outdatedCount += checkRows( packagedEngineVersions(parseJsonFile('engines.lock.json')), latestByEngine, - latestByTargetEngine + latestByTargetEngine, + latestUrlsByTargetEngine ); if (outdatedCount > 0) { diff --git a/src-tauri/binaries/ffmpeg-aarch64-apple-darwin b/src-tauri/binaries/ffmpeg-aarch64-apple-darwin index 81aa497..8a38491 100755 Binary files a/src-tauri/binaries/ffmpeg-aarch64-apple-darwin and b/src-tauri/binaries/ffmpeg-aarch64-apple-darwin differ