openpencil/tools/release-packages/tests/native/artifacts.test.ts
Danila Poyarkov c4a3dae2a4
ci: unify verified release builds (#711)
* ci: fix desktop build cache ownership

* build: centralize native release artifact validation

Reuse package command execution and npm artifact paths. Share release identity and target metadata, consume explicit Tauri artifact outputs, and reject incomplete or mixed-run artifact sets before draft publication.

* refactor: use release package aliases across directories

* ci: coordinate verified native and npm releases

Build shared frontend inputs once and keep native targets parallel. Bind their complete artifact set to immutable source and workflow revisions, verify updater signatures and attestations, and replace draft assets only after preflight and verified npm publication.

* test: group native release tests by domain
2026-09-16 21:58:38 +03:00

84 lines
2.7 KiB
TypeScript

import { expect, test } from 'bun:test'
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { resolveReportedAssets } from '#release/native/artifacts'
import { desktopAssets } from '#release/native/catalog'
const expected = desktopAssets('aarch64-apple-darwin', '0.15.0')
async function withArtifacts(run: (root: string, reported: string[]) => Promise<void>) {
const root = await mkdtemp(join(tmpdir(), 'release-artifacts-'))
try {
const reported: string[] = []
for (const asset of expected) {
for (const name of asset.signed ? [asset.source, `${asset.source}.sig`] : [asset.source]) {
const path = join(root, name)
await writeFile(path, 'fixture')
reported.push(path)
}
}
await run(root, reported)
} finally {
await rm(root, { recursive: true, force: true })
}
}
test('uses reported paths and retains the stable macOS archive name', () =>
withArtifacts(async (root, reported) => {
const app = join(root, 'OpenPencil.app')
await mkdir(app)
await writeFile(join(root, 'unrelated-file.dmg'), 'not reported')
const assets = await resolveReportedAssets([...reported, app], expected, root)
expect(assets.map((asset) => asset.name).sort()).toEqual([
'OpenPencil_0.15.0_aarch64.dmg',
'OpenPencil_aarch64.app.tar.gz',
'OpenPencil_aarch64.app.tar.gz.sig'
])
}))
test('does not discover an existing signature missing from the action output', () =>
withArtifacts(async (root, reported) => {
await expect(
resolveReportedAssets(
reported.filter((path) => !path.endsWith('.sig')),
expected,
root
)
).rejects.toThrow('Missing reported artifacts')
}))
test('rejects duplicate reports', () =>
withArtifacts(async (root, reported) => {
await expect(resolveReportedAssets([...reported, ...reported], expected, root)).rejects.toThrow(
'Duplicate reported artifact'
)
}))
test('rejects additional reported files', () =>
withArtifacts(async (root, reported) => {
const extra = join(root, 'unexpected.exe')
await writeFile(extra, 'fixture')
await expect(resolveReportedAssets([...reported, extra], expected, root)).rejects.toThrow(
'Unexpected reported artifact'
)
}))
test('rejects symlink escapes', () =>
withArtifacts(async (root, reported) => {
const source = join(root, 'source')
await mkdir(source)
await symlink(reported[0] ?? '', join(source, 'outside.dmg'))
await expect(
resolveReportedAssets([join(source, 'outside.dmg')], expected, source)
).rejects.toThrow('escapes source checkout')
}))