diff --git a/scripts/release/families.spec.ts b/scripts/release/families.spec.ts index 22eb0b064a..46ce6d92d0 100644 --- a/scripts/release/families.spec.ts +++ b/scripts/release/families.spec.ts @@ -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 { + const root = mkdtempSync(join(tmpdir(), 'dsh-release-build-')) + roots.push(root) + write(join(root, 'apps/web/dist/index.html'), '
') + 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 = [ diff --git a/scripts/release/families.ts b/scripts/release/families.ts index 7ce5566831..a847371631 100644 --- a/scripts/release/families.ts +++ b/scripts/release/families.ts @@ -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. diff --git a/scripts/release/pack.ts b/scripts/release/pack.ts index 5d2b9b4e64..3b68a1e49c 100644 --- a/scripts/release/pack.ts +++ b/scripts/release/pack.ts @@ -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 })