diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index 0f8091a9..40497698 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -87,20 +87,37 @@ jobs: include: - platform: windows-latest target_args: "" + artifact_prefix: "" portable: true portable_name: windows-x64 + - platform: windows-latest + target_args: --debug --bundles nsis + artifact_prefix: debug- + portable: false - platform: macos-latest target_args: --target x86_64-apple-darwin + artifact_prefix: "" + portable: false + - platform: macos-latest + target_args: --target x86_64-apple-darwin --debug --bundles dmg + artifact_prefix: debug- portable: false - platform: macos-latest target_args: --target aarch64-apple-darwin + artifact_prefix: "" + portable: false + - platform: macos-latest + target_args: --target aarch64-apple-darwin --debug --bundles dmg + artifact_prefix: debug- portable: false - platform: ubuntu-22.04 target_args: --bundles appimage,deb,rpm + artifact_prefix: "" portable: true portable_name: linux-x64 - platform: ubuntu-22.04-arm target_args: --bundles deb,rpm + artifact_prefix: "" portable: true portable_name: linux-arm64 @@ -166,7 +183,7 @@ jobs: uploadWorkflowArtifacts: true uploadUpdaterJson: false uploadUpdaterSignatures: false - workflowArtifactNamePattern: 'TauriTavern-${{ needs.prepare.outputs.version }}-[platform]-[arch]-[bundle]' + workflowArtifactNamePattern: 'TauriTavern-${{ needs.prepare.outputs.version }}-${{ matrix.artifact_prefix }}[platform]-[arch]-[bundle]' - name: Build portable binary if: matrix.portable @@ -441,7 +458,7 @@ jobs: path: dist - name: Validate and collect release assets - run: node source/scripts/ci/collect-release-assets.mjs dist release-assets "$ASSET_PREFIX" + run: node source/scripts/ci/collect-release-assets.mjs dist release-assets "$ASSET_PREFIX" --require-debug - name: Upload assets without changing release notes shell: bash diff --git a/scripts/ci/collect-release-assets.mjs b/scripts/ci/collect-release-assets.mjs index b3067c09..83c4823f 100755 --- a/scripts/ci/collect-release-assets.mjs +++ b/scripts/ci/collect-release-assets.mjs @@ -12,6 +12,9 @@ const RELEASE_ASSETS = new Map([ ['android-arm64-apk', ['android-arm64-v8a.apk', '.apk']], ['darwin-aarch64-dmg', ['macos-arm64.dmg', '.dmg']], ['darwin-x64-dmg', ['macos-x64.dmg', '.dmg']], + ['debug-darwin-aarch64-dmg', ['macos-arm64-DEBUG.dmg', '.dmg']], + ['debug-darwin-x64-dmg', ['macos-x64-DEBUG.dmg', '.dmg']], + ['debug-windows-x64-nsis', ['windows-x64-setup-DEBUG.exe', '.exe']], ['ios-arm64-ipa', ['ios-arm64.ipa', '.ipa']], ['ios-arm64-TestFlight-ipa', ['ios-arm64-TestFlight.ipa', '.ipa']], ['linux-aarch64-rpm', ['linux-arm64.rpm', '.rpm']], @@ -29,6 +32,14 @@ const RELEASE_ASSETS = new Map([ const IGNORED_ARTIFACTS = new Set([ 'darwin-aarch64-app', 'darwin-x64-app', + 'debug-darwin-aarch64-app', + 'debug-darwin-x64-app', +]); + +const DEBUG_ARTIFACTS = new Set([ + 'debug-darwin-aarch64-dmg', + 'debug-darwin-x64-dmg', + 'debug-windows-x64-nsis', ]); async function listFiles(directory) { @@ -66,6 +77,7 @@ export async function collectReleaseAssets({ inputDirectory, outputDirectory, artifactPrefix, + requireDebug = false, }) { validatePrefix(artifactPrefix); @@ -109,7 +121,8 @@ export async function collectReleaseAssets({ }); } - const missing = [...RELEASE_ASSETS.keys()].filter((suffix) => !found.has(suffix)); + const missing = [...RELEASE_ASSETS.keys()] + .filter((suffix) => !found.has(suffix) && (requireDebug || !DEBUG_ARTIFACTS.has(suffix))); if (missing.length > 0) { throw new Error(`Missing workflow artifacts: ${missing.map((suffix) => artifactPrefix + suffix).join(', ')}`); } @@ -132,10 +145,17 @@ export async function collectReleaseAssets({ } async function main() { - const [inputDirectory, outputDirectory, artifactPrefix] = process.argv.slice(2); - if (!inputDirectory || !outputDirectory || !artifactPrefix || process.argv.length !== 5) { + const [inputDirectory, outputDirectory, artifactPrefix, option] = process.argv.slice(2); + if ( + !inputDirectory + || !outputDirectory + || !artifactPrefix + || (option !== undefined && option !== '--require-debug') + || process.argv.length < 5 + || process.argv.length > 6 + ) { throw new Error( - 'Usage: collect-release-assets.mjs ', + 'Usage: collect-release-assets.mjs [--require-debug]', ); } @@ -143,6 +163,7 @@ async function main() { inputDirectory: resolve(inputDirectory), outputDirectory: resolve(outputDirectory), artifactPrefix, + requireDebug: option === '--require-debug', }); process.stdout.write(`${assets.join('\n')}\n`); } diff --git a/tests/ci-release-assets.test.mjs b/tests/ci-release-assets.test.mjs index 41ef3096..93d7cae9 100644 --- a/tests/ci-release-assets.test.mjs +++ b/tests/ci-release-assets.test.mjs @@ -13,6 +13,11 @@ const ARTIFACTS = new Map([ ['darwin-aarch64-dmg', 'TauriTavern_aarch64.dmg'], ['darwin-x64-app', 'TauriTavern.app/Contents/MacOS/TauriTavern'], ['darwin-x64-dmg', 'TauriTavern_x64.dmg'], + ['debug-darwin-aarch64-app', 'TauriTavern.app/Contents/MacOS/TauriTavern'], + ['debug-darwin-aarch64-dmg', 'TauriTavern_aarch64.dmg'], + ['debug-darwin-x64-app', 'TauriTavern.app/Contents/MacOS/TauriTavern'], + ['debug-darwin-x64-dmg', 'TauriTavern_x64.dmg'], + ['debug-windows-x64-nsis', 'TauriTavern_2.2.0_x64-setup.exe'], ['ios-arm64-ipa', 'TauriTavern.ipa'], ['ios-arm64-TestFlight-ipa', 'TauriTavern.ipa'], ['linux-aarch64-rpm', 'TauriTavern-2.2.0-1.aarch64.rpm'], @@ -39,16 +44,20 @@ const EXPECTED_RELEASE_ASSETS = [ 'TauriTavern-2.2.0-linux-x64.AppImage', 'TauriTavern-2.2.0-linux-x64.deb', 'TauriTavern-2.2.0-linux-x64.rpm', + 'TauriTavern-2.2.0-macos-arm64-DEBUG.dmg', 'TauriTavern-2.2.0-macos-arm64.dmg', + 'TauriTavern-2.2.0-macos-x64-DEBUG.dmg', 'TauriTavern-2.2.0-macos-x64.dmg', 'TauriTavern-2.2.0-windows-x64-portable.exe', + 'TauriTavern-2.2.0-windows-x64-setup-DEBUG.exe', 'TauriTavern-2.2.0-windows-x64-setup.exe', 'TauriTavern-2.2.0-windows-x64.msi', ].sort(); -async function createArtifacts(root, prefix, omittedSuffix = null) { +async function createArtifacts(root, prefix, omittedSuffixes = []) { + const omitted = new Set(Array.isArray(omittedSuffixes) ? omittedSuffixes : [omittedSuffixes]); for (const [suffix, relativeFile] of ARTIFACTS) { - if (suffix === omittedSuffix) { + if (omitted.has(suffix)) { continue; } const file = join(root, `${prefix}${suffix}`, relativeFile); @@ -57,7 +66,7 @@ async function createArtifacts(root, prefix, omittedSuffix = null) { } } -test('collectReleaseAssets publishes the shared Stable and Canary naming contract', async (t) => { +test('collectReleaseAssets publishes the complete Stable naming contract', async (t) => { const root = await mkdtemp(join(tmpdir(), 'tauritavern-release-assets-')); t.after(() => rm(root, { recursive: true, force: true })); @@ -70,6 +79,7 @@ test('collectReleaseAssets publishes the shared Stable and Canary naming contrac inputDirectory, outputDirectory, artifactPrefix, + requireDebug: true, }); assert.deepEqual(assets.map((asset) => basename(asset)), EXPECTED_RELEASE_ASSETS); @@ -83,7 +93,14 @@ test('collectReleaseAssets fails before publishing an incomplete release set', a const inputDirectory = join(root, 'dist'); const outputDirectory = join(root, 'release-assets'); const artifactPrefix = 'TauriTavern-20260726-canary-'; - await createArtifacts(inputDirectory, artifactPrefix, 'android-arm64-apk'); + await createArtifacts(inputDirectory, artifactPrefix, [ + 'android-arm64-apk', + 'debug-darwin-aarch64-app', + 'debug-darwin-aarch64-dmg', + 'debug-darwin-x64-app', + 'debug-darwin-x64-dmg', + 'debug-windows-x64-nsis', + ]); await assert.rejects( collectReleaseAssets({ @@ -91,7 +108,22 @@ test('collectReleaseAssets fails before publishing an incomplete release set', a outputDirectory, artifactPrefix, }), - /Missing workflow artifacts: TauriTavern-20260726-canary-android-arm64-apk/, + { message: 'Missing workflow artifacts: TauriTavern-20260726-canary-android-arm64-apk' }, ); await assert.rejects(readdir(outputDirectory), { code: 'ENOENT' }); }); + +test('collectReleaseAssets requires every Stable debug artifact', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'tauritavern-release-assets-')); + t.after(() => rm(root, { recursive: true, force: true })); + + const inputDirectory = join(root, 'dist'); + const outputDirectory = join(root, 'release-assets'); + const artifactPrefix = 'TauriTavern-2.2.0-'; + await createArtifacts(inputDirectory, artifactPrefix, 'debug-windows-x64-nsis'); + + await assert.rejects( + collectReleaseAssets({ inputDirectory, outputDirectory, artifactPrefix, requireDebug: true }), + /Missing workflow artifacts: TauriTavern-2\.2\.0-debug-windows-x64-nsis/, + ); +}); diff --git a/tests/stable-release-workflow.test.mjs b/tests/stable-release-workflow.test.mjs index b2695954..51d3b338 100644 --- a/tests/stable-release-workflow.test.mjs +++ b/tests/stable-release-workflow.test.mjs @@ -18,6 +18,26 @@ test('stable release workflow preserves manually written release notes', () => { assert.match(workflowSource, /Upload assets without changing release notes/); }); +test('stable release builds Windows and macOS debug installers in parallel', () => { + const debugBuilds = workflow.jobs.desktop.strategy.matrix.include + .filter((entry) => entry.artifact_prefix === 'debug-') + .map(({ platform, target_args: targetArgs, portable }) => ({ platform, targetArgs, portable })); + + assert.deepEqual(debugBuilds, [ + { platform: 'windows-latest', targetArgs: '--debug --bundles nsis', portable: false }, + { + platform: 'macos-latest', + targetArgs: '--target x86_64-apple-darwin --debug --bundles dmg', + portable: false, + }, + { + platform: 'macos-latest', + targetArgs: '--target aarch64-apple-darwin --debug --bundles dmg', + portable: false, + }, + ]); +}); + test('stable release workflow publishes release assets before optional repositories', () => { assert.deepEqual(workflow.jobs['publish-release'].needs, ['prepare', 'desktop', 'mobile']);