A release family owns its member discovery, version baseline, tag naming, and packed-payload rule; the dsh family shares one version across packages/ and apps/, while every vendor/ package keeps its own version line. Publish order is topological over runtime dependencies so no package reaches the registry before one it depends on. pack packs the whole family into one directory and records the upload order; publish decides per package against the registry, skipping a version whose published tarball has the same integrity and failing when it differs, which is what makes re-running publish over one artifact safe. The vendored packages keep upstream's payload: their manifests export ./src/*, so the harness rule that rejects sources and declaration maps would publish an export map pointing at absent files.
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
/**
|
|
* Verify a release family's version baseline, and — when publishing — that the
|
|
* run comes from the family's tag and its members are publishable.
|
|
*
|
|
* Publication happens only from GitHub Actions, so the tag and publishability
|
|
* checks are gates on the workflow, not advisory local warnings
|
|
* ([rationale](../../.agents/notes/proposed/process/2026-08-10-npm-release-sequences.md)).
|
|
*/
|
|
|
|
import { parseArgs } from 'node:util'
|
|
import { releaseFamily, type ReleaseFamily, type ReleaseMember } from './families.ts'
|
|
|
|
/**
|
|
* Assert every member may be published: npm refuses a `private` package.
|
|
* @param members - the family's members.
|
|
*/
|
|
function verifyPublishable(members: readonly ReleaseMember[]): void {
|
|
const priv = members.filter(member => member.manifest.private === true)
|
|
if (priv.length > 0) {
|
|
throw new Error(`publishing requires removing "private": true from:\n${priv.map(member => member.directory).join('\n')}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert the workflow runs from a tag this family publishes from, and that the
|
|
* tag names a version the family actually carries.
|
|
* @param family - the release family.
|
|
* @param members - the family's members.
|
|
* @param ref - the `GITHUB_REF` value.
|
|
*/
|
|
function verifyTag(family: ReleaseFamily, members: readonly ReleaseMember[], ref: string): void {
|
|
const prefix = 'refs/tags/'
|
|
if (!ref.startsWith(prefix)) {
|
|
throw new Error(`publishing release family ${family.id} requires running from a ${family.tagPrefix}* tag, got ${ref || '(no ref)'}`)
|
|
}
|
|
const tag = ref.slice(prefix.length)
|
|
if (!tag.startsWith(family.tagPrefix)) {
|
|
throw new Error(`tag ${tag} does not belong to release family ${family.id} (expected ${family.tagPrefix}*)`)
|
|
}
|
|
const expected = members.map(member => family.tagFor(member))
|
|
if (!expected.includes(tag)) {
|
|
throw new Error(`tag ${tag} names no version this family carries; its members would tag as:\n${[...new Set(expected)].join('\n')}`)
|
|
}
|
|
}
|
|
|
|
/** Run the verification for the family named by `--family`. */
|
|
function main(): void {
|
|
const { values } = parseArgs({
|
|
options: { family: { type: 'string' } },
|
|
allowPositionals: false,
|
|
})
|
|
if (values.family === undefined) throw new Error('usage: verify.ts --family <dsh|vendor>')
|
|
|
|
const family = releaseFamily(values.family)
|
|
const members = family.members(process.cwd())
|
|
family.verifyVersions(members)
|
|
|
|
const publishing = process.env.RELEASE_PUBLISH === 'true'
|
|
if (publishing) {
|
|
verifyPublishable(members)
|
|
verifyTag(family, members, process.env.GITHUB_REF ?? '')
|
|
}
|
|
|
|
const versions = [...new Set(members.map(member => member.version))]
|
|
const summary = versions.length === 1 ? versions[0] : `${String(versions.length)} versions`
|
|
console.log(`release verify: family ${family.id}, ${String(members.length)} member(s), ${summary}${publishing ? ', publish gates passed' : ''}`)
|
|
}
|
|
|
|
main()
|