feat(release): validate client build artifacts

This commit is contained in:
imccyu
2026-08-19 18:21:27 +08:00
parent 66a7081c15
commit 738dcced9b
3 changed files with 60 additions and 2 deletions
+43 -2
View File
@@ -1,7 +1,10 @@
/** Release family discovery, publish order, tag naming, and the bump judgements. */
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { officialClientBuildEnvironment, writeClientBuildRecord } from '../client-build-environment.ts'
import { releaseFamily, type ReleaseMember } from './families.ts'
import { compareVersions, nextVendorVersion, reachesPayload } from './bump.ts'
@@ -16,6 +19,27 @@ function member(directory: string, name: string, manifest: Record<string, unknow
return { directory, name, version: '0.0.1', manifest }
}
const roots: string[] = []
function write(path: string, content: string): void {
mkdirSync(dirname(path), { recursive: true })
writeFileSync(path, content)
}
function buildFixture(environment: Record<string, string>): string {
const root = mkdtempSync(join(tmpdir(), 'dsh-release-build-'))
roots.push(root)
write(join(root, 'apps/web/dist/index.html'), '<main></main>')
write(join(root, 'packages/client/example/lib/client.js'), 'module.exports = {}\n')
writeClientBuildRecord(root, environment)
return root
}
afterEach(() => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
vi.unstubAllEnvs()
})
describe('release families', () => {
it('excludes private experimental packages from the dsh release', () => {
const members = releaseFamily('dsh').members(resolve(import.meta.dirname, '../..'))
@@ -57,6 +81,23 @@ describe('release families', () => {
expect(() => { vendor.verifyVersions([{ ...members[0]!, version: 'latest' }]) }).toThrow(/unpublishable version/)
})
it('requires a current official client build only for dsh artifacts', () => {
const dsh = releaseFamily('dsh')
const vendor = releaseFamily('vendor')
const officialEnvironment = officialClientBuildEnvironment(resolve(import.meta.dirname, '../..'))
vi.stubEnv('DSH_CLIENT_COMMIT_HASH', officialEnvironment.DSH_CLIENT_COMMIT_HASH)
const official = buildFixture(officialEnvironment)
const defaultBuild = buildFixture({})
expect(() => { dsh.verifyBuildArtifacts(official) }).not.toThrow()
expect(() => { dsh.verifyBuildArtifacts(defaultBuild) }).toThrow(/DSH_CLIENT_TITLE/)
expect(() => { dsh.verifyBuildArtifacts(join(defaultBuild, 'missing')) }).toThrow(/record.*missing/)
expect(() => { vendor.verifyBuildArtifacts(join(defaultBuild, 'missing')) }).not.toThrow()
write(join(official, 'packages/client/example/lib/client.js'), 'module.exports = { changed: true }\n')
expect(() => { dsh.verifyBuildArtifacts(official) }).toThrow(/artifacts differ/)
})
it('publishes a dependency before its consumer, and orders ties by name', () => {
const dsh = releaseFamily('dsh')
const members = [
+16
View File
@@ -11,6 +11,10 @@
import { globSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import {
officialClientBuildEnvironment,
readClientBuildRecord,
} from '../client-build-environment.ts'
import { validateTarballPayload } from '../publication-payload.ts'
/**
@@ -111,6 +115,13 @@ export abstract class ReleaseFamily {
/** Git tag prefix this family publishes from. */
abstract readonly tagPrefix: string
/**
* Assert that built artifacts match this release family's required profile.
* Families without environment-selected artifacts accept every build tree.
* @param _root - repository root containing generated artifacts.
*/
verifyBuildArtifacts(_root: string): void {}
/**
* Discover this family's members.
* @param root - repository root.
@@ -311,6 +322,11 @@ class DshFamily extends ReleaseFamily {
readonly patterns = ['packages/!(experimental)/*/package.json', 'apps/*/package.json'] as const
readonly tagPrefix = 'dsh-v'
/** Require current artifacts from a complete official client build. */
override verifyBuildArtifacts(root: string): void {
readClientBuildRecord(root, officialClientBuildEnvironment(root))
}
/**
* Require one version across the family, the way a single tag can name it.
* @param members - this family's members.
+1
View File
@@ -46,6 +46,7 @@ function main(): void {
const root = process.cwd()
const destination = resolve(root, values.out ?? DEFAULT_OUTPUT)
const members = family.publishOrder(family.members(root)).order
family.verifyBuildArtifacts(root)
family.verifyVersions(members)
rmSync(destination, { recursive: true, force: true })