diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 81d64189..8ec05f55 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -230,18 +230,16 @@ jobs: ;; Linux-x64) expected=( - "Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage" - "Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage.blockmap" - "Claude-Code-Haha-${APP_VERSION}-linux-x64.deb" + "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage" + "Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb" "latest-linux.yml" ) ;; Linux-ARM64) expected=( "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage" - "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage.blockmap" "Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb" - "latest-linux.yml" + "latest-linux-arm64.yml" ) ;; Windows-x64) @@ -366,11 +364,9 @@ jobs: "Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap" "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip" "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap" - "Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage" - "Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage.blockmap" - "Claude-Code-Haha-${APP_VERSION}-linux-x64.deb" + "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage" + "Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb" "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage" - "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage.blockmap" "Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb" "Claude-Code-Haha-${APP_VERSION}-win-x64.exe" "Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap" diff --git a/scripts/pr/release-workflow.test.ts b/scripts/pr/release-workflow.test.ts index 4d94676b..4f1da454 100644 --- a/scripts/pr/release-workflow.test.ts +++ b/scripts/pr/release-workflow.test.ts @@ -190,11 +190,9 @@ describe('release desktop workflow', () => { `Claude-Code-Haha-${version}-mac-x64.dmg.blockmap`, `Claude-Code-Haha-${version}-mac-x64.zip`, `Claude-Code-Haha-${version}-mac-x64.zip.blockmap`, - `Claude-Code-Haha-${version}-linux-x64.AppImage`, - `Claude-Code-Haha-${version}-linux-x64.AppImage.blockmap`, - `Claude-Code-Haha-${version}-linux-x64.deb`, + `Claude-Code-Haha-${version}-linux-x86_64.AppImage`, + `Claude-Code-Haha-${version}-linux-amd64.deb`, `Claude-Code-Haha-${version}-linux-arm64.AppImage`, - `Claude-Code-Haha-${version}-linux-arm64.AppImage.blockmap`, `Claude-Code-Haha-${version}-linux-arm64.deb`, `Claude-Code-Haha-${version}-win-x64.exe`, `Claude-Code-Haha-${version}-win-x64.exe.blockmap`, @@ -224,6 +222,7 @@ describe('release desktop workflow', () => { expect(expectedReleaseAssets.filter((name) => name.endsWith('.AppImage')).length).toBe(2) expect(expectedReleaseAssets.filter((name) => name.endsWith('.deb')).length).toBe(2) expect(expectedReleaseAssets.filter((name) => name.endsWith('.exe')).length).toBe(1) + expect(expectedReleaseAssets.some((name) => name.includes('-linux-') && name.endsWith('.blockmap'))).toBe(false) for (const platform of ['mac', 'linux', 'win']) { expect(expectedReleaseAssets.some((name) => name.includes(`-${platform}-`))).toBe(true) } @@ -244,8 +243,8 @@ describe('release desktop workflow', () => { 'Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip', 'Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg', 'Claude-Code-Haha-${APP_VERSION}-mac-x64.zip', - 'Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage', - 'Claude-Code-Haha-${APP_VERSION}-linux-x64.deb', + 'Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage', + 'Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb', 'Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage', 'Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb', 'Claude-Code-Haha-${APP_VERSION}-win-x64.exe', @@ -259,6 +258,9 @@ describe('release desktop workflow', () => { expect(publishJob).toContain(`artifacts/update-metadata-standard/$file`) expect(publishJob).toContain(metadata) } + expect(buildJob).not.toContain('linux-x64.AppImage.blockmap') + expect(buildJob).not.toContain('linux-arm64.AppImage.blockmap') + expect(buildJob).toContain('latest-linux-arm64.yml') expect(buildJob).toContain('Missing release assets for %s') expect(publishJob).toContain('Missing complete release assets') expect(publishJob).toContain('Missing standard update metadata') diff --git a/scripts/quality-gate/package-smoke/index.test.ts b/scripts/quality-gate/package-smoke/index.test.ts index 05bb0ef4..98c2aade 100644 --- a/scripts/quality-gate/package-smoke/index.test.ts +++ b/scripts/quality-gate/package-smoke/index.test.ts @@ -392,6 +392,52 @@ describe('packaged artifact inspection', () => { expect(report.passedChecks.some((check) => check.label.includes('update metadata referenced artifact'))).toBe(true) }) + test('passes Linux release checks for Electron Builder output without AppImage blockmap', async () => { + const rootDir = createRepoRoot() + tempDirs.push(rootDir) + + writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-x86_64.AppImage') + writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-amd64.deb') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app-update.yml') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-unknown-linux-gnu') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/build/Release/pty.node') + writeFile(rootDir, 'desktop/build-artifacts/electron/latest-linux.yml', 'path: Claude-Code-Haha-0.3.1-linux-x86_64.AppImage\n') + + const report = await inspectPackagedArtifacts(rootDir, { + platform: 'linux', + packageKind: 'release', + artifactsDir: 'desktop/build-artifacts/electron', + }) + + expect(report.passed).toBe(true) + expect(report.missingChecks.some((check) => check.label.includes('blockmap'))).toBe(false) + }) + + test('accepts Electron Builder linux-arm64-unpacked output directory', async () => { + const rootDir = createRepoRoot() + tempDirs.push(rootDir) + + writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-arm64.AppImage') + writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-arm64.deb') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app-update.yml') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-unknown-linux-gnu') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json') + writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/linux-arm64/pty.node') + writeFile(rootDir, 'desktop/build-artifacts/electron/latest-linux-arm64.yml', 'path: Claude-Code-Haha-0.3.1-linux-arm64.AppImage\n') + + const report = await inspectPackagedArtifacts(rootDir, { + platform: 'linux', + packageKind: 'release', + artifactsDir: 'desktop/build-artifacts/electron', + }) + + expect(report.passed).toBe(true) + expect(report.passedChecks.some((check) => check.path.includes('linux-arm64-unpacked/resources/app.asar'))).toBe(true) + }) + test('passes Linux directory-only checks for electron-builder --dir output', async () => { const rootDir = createRepoRoot() tempDirs.push(rootDir) diff --git a/scripts/quality-gate/package-smoke/index.ts b/scripts/quality-gate/package-smoke/index.ts index 76eb1109..c70206a8 100644 --- a/scripts/quality-gate/package-smoke/index.ts +++ b/scripts/quality-gate/package-smoke/index.ts @@ -283,6 +283,22 @@ function addBlockmapChecks( } } +function findLinuxUnpackedDir(artifactsDir: string) { + const unpackedDirs = findMatches( + artifactsDir, + (candidate) => /\/linux(?:-[a-z0-9_]+)?-unpacked$/.test(normalizePath(candidate)), + { directoriesOnly: true }, + ) + + return unpackedDirs.sort((left, right) => { + const leftName = normalizePath(left).split('/').pop() + const rightName = normalizePath(right).split('/').pop() + if (leftName === 'linux-unpacked') return -1 + if (rightName === 'linux-unpacked') return 1 + return left.localeCompare(right) + })[0] +} + function firstDiagnosticLine(output: string) { return output .split(/\r?\n/) @@ -614,23 +630,24 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) { report.artifactsDir, (candidate) => candidate.endsWith('.AppImage') || candidate.endsWith('.deb'), ) - const unpackedDir = findMatches(report.artifactsDir, (candidate) => normalizePath(candidate).endsWith('/linux-unpacked'), { directoriesOnly: true })[0] - const electronDir = join(report.artifactsDir, 'electron') + const unpackedDir = findLinuxUnpackedDir(report.artifactsDir) const updateMetadata = findMatches(report.artifactsDir, (candidate) => /latest-linux(?:-[a-z0-9]+)?\.yml$/.test(candidate)) + const appImageBlockmaps = findMatches(report.artifactsDir, (candidate) => candidate.endsWith('.AppImage.blockmap')) const releaseMode = report.packageKind === 'release' || (report.packageKind === 'auto' && (packagedArtifacts.length > 0 || updateMetadata.length > 0)) report.packagedArtifacts.push(...packagedArtifacts.map((candidate) => ({ label: candidate.endsWith('.deb') ? 'Linux deb package' : 'Linux AppImage', path: toRelative(rootDir, candidate), }))) + report.optionalArtifacts.push(...appImageBlockmaps.map((candidate) => ({ + label: 'Linux AppImage blockmap', + path: toRelative(rootDir, candidate), + }))) if (releaseMode) { addUpdateMetadataChecks(report, rootDir, updateMetadata) - addBlockmapChecks( - report, - rootDir, - 'Linux AppImage update artifact', - packagedArtifacts.filter(candidate => candidate.endsWith('.AppImage')), - ) + if (appImageBlockmaps.length === 0 && packagedArtifacts.some(candidate => candidate.endsWith('.AppImage'))) { + report.notes.push('Linux AppImage blockmaps were not required because Electron Builder did not emit them for this artifact set.') + } } if (releaseMode) { @@ -639,12 +656,12 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) { rootDir, 'linux packaged artifact (.AppImage or .deb)', packagedArtifacts, - electronDir, + report.artifactsDir, ) } else if (!unpackedDir) { report.missingChecks.push({ label: 'linux packaged artifact (.AppImage or .deb)', - path: toRelative(rootDir, electronDir), + path: toRelative(rootDir, report.artifactsDir), }) } else { report.notes.push('No .AppImage or .deb was found; treating linux-unpacked as a directory-only development package.') @@ -680,8 +697,8 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) { } else { if (releaseMode) { report.missingChecks.push({ - label: 'Linux unpacked directory (linux-unpacked) for static resource inspection', - path: toRelative(rootDir, join(electronDir, 'linux-unpacked')), + label: 'Linux unpacked directory (linux-unpacked or linux-*-unpacked) for static resource inspection', + path: toRelative(rootDir, join(report.artifactsDir, 'linux-unpacked')), }) } else { report.notes.push('linux-unpacked was not found, so this check only verified packaged artifact presence.') @@ -692,7 +709,7 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) { if (releaseMode && updateMetadata.length === 0) { report.missingChecks.push({ label: 'Linux update metadata (latest-linux*.yml)', - path: toRelative(rootDir, join(electronDir, 'latest-linux.yml')), + path: toRelative(rootDir, join(report.artifactsDir, 'latest-linux.yml')), }) } if (report.hostPlatform !== 'linux') {