mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
fix(release): align Linux package smoke artifacts
Match Electron Builder Linux output by accepting linux-*-unpacked directories, treating AppImage blockmaps as optional, and validating release asset names against the generated x86_64/amd64 and arm64 artifacts. Tested: bun test scripts/quality-gate/package-smoke/index.test.ts Tested: bun test scripts/pr/release-workflow.test.ts scripts/quality-gate/package-smoke/index.test.ts Tested: bun run check:policy Tested: bun run check:native Tested: bun run verify Confidence: high Scope-risk: moderate
This commit is contained in:
parent
79d8468580
commit
d7eef0ea51
14
.github/workflows/release-desktop.yml
vendored
14
.github/workflows/release-desktop.yml
vendored
@ -230,18 +230,16 @@ jobs:
|
|||||||
;;
|
;;
|
||||||
Linux-x64)
|
Linux-x64)
|
||||||
expected=(
|
expected=(
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage"
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage.blockmap"
|
"Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.deb"
|
|
||||||
"latest-linux.yml"
|
"latest-linux.yml"
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
Linux-ARM64)
|
Linux-ARM64)
|
||||||
expected=(
|
expected=(
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage"
|
"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}-linux-arm64.deb"
|
||||||
"latest-linux.yml"
|
"latest-linux-arm64.yml"
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
Windows-x64)
|
Windows-x64)
|
||||||
@ -366,11 +364,9 @@ jobs:
|
|||||||
"Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap"
|
"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"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap"
|
"Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage"
|
"Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage.blockmap"
|
"Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-x64.deb"
|
|
||||||
"Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage"
|
"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}-linux-arm64.deb"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe"
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe"
|
||||||
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap"
|
"Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap"
|
||||||
|
|||||||
@ -190,11 +190,9 @@ describe('release desktop workflow', () => {
|
|||||||
`Claude-Code-Haha-${version}-mac-x64.dmg.blockmap`,
|
`Claude-Code-Haha-${version}-mac-x64.dmg.blockmap`,
|
||||||
`Claude-Code-Haha-${version}-mac-x64.zip`,
|
`Claude-Code-Haha-${version}-mac-x64.zip`,
|
||||||
`Claude-Code-Haha-${version}-mac-x64.zip.blockmap`,
|
`Claude-Code-Haha-${version}-mac-x64.zip.blockmap`,
|
||||||
`Claude-Code-Haha-${version}-linux-x64.AppImage`,
|
`Claude-Code-Haha-${version}-linux-x86_64.AppImage`,
|
||||||
`Claude-Code-Haha-${version}-linux-x64.AppImage.blockmap`,
|
`Claude-Code-Haha-${version}-linux-amd64.deb`,
|
||||||
`Claude-Code-Haha-${version}-linux-x64.deb`,
|
|
||||||
`Claude-Code-Haha-${version}-linux-arm64.AppImage`,
|
`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}-linux-arm64.deb`,
|
||||||
`Claude-Code-Haha-${version}-win-x64.exe`,
|
`Claude-Code-Haha-${version}-win-x64.exe`,
|
||||||
`Claude-Code-Haha-${version}-win-x64.exe.blockmap`,
|
`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('.AppImage')).length).toBe(2)
|
||||||
expect(expectedReleaseAssets.filter((name) => name.endsWith('.deb')).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.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']) {
|
for (const platform of ['mac', 'linux', 'win']) {
|
||||||
expect(expectedReleaseAssets.some((name) => name.includes(`-${platform}-`))).toBe(true)
|
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-arm64.zip',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg',
|
'Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-mac-x64.zip',
|
'Claude-Code-Haha-${APP_VERSION}-mac-x64.zip',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-linux-x64.AppImage',
|
'Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-linux-x64.deb',
|
'Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage',
|
'Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb',
|
'Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb',
|
||||||
'Claude-Code-Haha-${APP_VERSION}-win-x64.exe',
|
'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(`artifacts/update-metadata-standard/$file`)
|
||||||
expect(publishJob).toContain(metadata)
|
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(buildJob).toContain('Missing release assets for %s')
|
||||||
expect(publishJob).toContain('Missing complete release assets')
|
expect(publishJob).toContain('Missing complete release assets')
|
||||||
expect(publishJob).toContain('Missing standard update metadata')
|
expect(publishJob).toContain('Missing standard update metadata')
|
||||||
|
|||||||
@ -392,6 +392,52 @@ describe('packaged artifact inspection', () => {
|
|||||||
expect(report.passedChecks.some((check) => check.label.includes('update metadata referenced artifact'))).toBe(true)
|
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 () => {
|
test('passes Linux directory-only checks for electron-builder --dir output', async () => {
|
||||||
const rootDir = createRepoRoot()
|
const rootDir = createRepoRoot()
|
||||||
tempDirs.push(rootDir)
|
tempDirs.push(rootDir)
|
||||||
|
|||||||
@ -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) {
|
function firstDiagnosticLine(output: string) {
|
||||||
return output
|
return output
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
@ -614,23 +630,24 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) {
|
|||||||
report.artifactsDir,
|
report.artifactsDir,
|
||||||
(candidate) => candidate.endsWith('.AppImage') || candidate.endsWith('.deb'),
|
(candidate) => candidate.endsWith('.AppImage') || candidate.endsWith('.deb'),
|
||||||
)
|
)
|
||||||
const unpackedDir = findMatches(report.artifactsDir, (candidate) => normalizePath(candidate).endsWith('/linux-unpacked'), { directoriesOnly: true })[0]
|
const unpackedDir = findLinuxUnpackedDir(report.artifactsDir)
|
||||||
const electronDir = join(report.artifactsDir, 'electron')
|
|
||||||
const updateMetadata = findMatches(report.artifactsDir, (candidate) => /latest-linux(?:-[a-z0-9]+)?\.yml$/.test(candidate))
|
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))
|
const releaseMode = report.packageKind === 'release' || (report.packageKind === 'auto' && (packagedArtifacts.length > 0 || updateMetadata.length > 0))
|
||||||
|
|
||||||
report.packagedArtifacts.push(...packagedArtifacts.map((candidate) => ({
|
report.packagedArtifacts.push(...packagedArtifacts.map((candidate) => ({
|
||||||
label: candidate.endsWith('.deb') ? 'Linux deb package' : 'Linux AppImage',
|
label: candidate.endsWith('.deb') ? 'Linux deb package' : 'Linux AppImage',
|
||||||
path: toRelative(rootDir, candidate),
|
path: toRelative(rootDir, candidate),
|
||||||
})))
|
})))
|
||||||
|
report.optionalArtifacts.push(...appImageBlockmaps.map((candidate) => ({
|
||||||
|
label: 'Linux AppImage blockmap',
|
||||||
|
path: toRelative(rootDir, candidate),
|
||||||
|
})))
|
||||||
if (releaseMode) {
|
if (releaseMode) {
|
||||||
addUpdateMetadataChecks(report, rootDir, updateMetadata)
|
addUpdateMetadataChecks(report, rootDir, updateMetadata)
|
||||||
addBlockmapChecks(
|
if (appImageBlockmaps.length === 0 && packagedArtifacts.some(candidate => candidate.endsWith('.AppImage'))) {
|
||||||
report,
|
report.notes.push('Linux AppImage blockmaps were not required because Electron Builder did not emit them for this artifact set.')
|
||||||
rootDir,
|
}
|
||||||
'Linux AppImage update artifact',
|
|
||||||
packagedArtifacts.filter(candidate => candidate.endsWith('.AppImage')),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (releaseMode) {
|
if (releaseMode) {
|
||||||
@ -639,12 +656,12 @@ function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) {
|
|||||||
rootDir,
|
rootDir,
|
||||||
'linux packaged artifact (.AppImage or .deb)',
|
'linux packaged artifact (.AppImage or .deb)',
|
||||||
packagedArtifacts,
|
packagedArtifacts,
|
||||||
electronDir,
|
report.artifactsDir,
|
||||||
)
|
)
|
||||||
} else if (!unpackedDir) {
|
} else if (!unpackedDir) {
|
||||||
report.missingChecks.push({
|
report.missingChecks.push({
|
||||||
label: 'linux packaged artifact (.AppImage or .deb)',
|
label: 'linux packaged artifact (.AppImage or .deb)',
|
||||||
path: toRelative(rootDir, electronDir),
|
path: toRelative(rootDir, report.artifactsDir),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
report.notes.push('No .AppImage or .deb was found; treating linux-unpacked as a directory-only development package.')
|
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 {
|
} else {
|
||||||
if (releaseMode) {
|
if (releaseMode) {
|
||||||
report.missingChecks.push({
|
report.missingChecks.push({
|
||||||
label: 'Linux unpacked directory (linux-unpacked) for static resource inspection',
|
label: 'Linux unpacked directory (linux-unpacked or linux-*-unpacked) for static resource inspection',
|
||||||
path: toRelative(rootDir, join(electronDir, 'linux-unpacked')),
|
path: toRelative(rootDir, join(report.artifactsDir, 'linux-unpacked')),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
report.notes.push('linux-unpacked was not found, so this check only verified packaged artifact presence.')
|
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) {
|
if (releaseMode && updateMetadata.length === 0) {
|
||||||
report.missingChecks.push({
|
report.missingChecks.push({
|
||||||
label: 'Linux update metadata (latest-linux*.yml)',
|
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') {
|
if (report.hostPlatform !== 'linux') {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user