mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-30 04:40:37 +00:00
Publish order exists to make a partial publication self-consistent: an interrupted run should leave a prefix whose packages never point at a version absent from the registry. It read only dependencies and optionalDependencies, so peer declarations — how sibling harness packages reference each other, 1088 edges in the dsh family — constrained nothing. Peer edges now order the publication too. devDependencies still do not: a dev dependency is absent from the published package. Peers cannot constrain it absolutely. Sibling packages declare each other as peers, which is what closes the two cycles here, and npm treats an unmet peer as a warning rather than a resolution failure. Install edges therefore win: a peer edge is dropped where the peer installs the member declaring it, or where following it would revisit a member already being visited. One peer edge is dropped in the dsh family and two in the vendored family; every install edge is honoured. A cycle among install edges stays a defect rather than something to order around, and release:verify now reports it before the build instead of letting it surface once pack is already writing tarballs. Install-edge acyclicity is checked on its own graph, because a peer edge leading into an install edge otherwise reads as a cycle where the install edges are perfectly orderable.
80 lines
3.3 KiB
TypeScript
80 lines
3.3 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/implemented/process/2026-08-10-npm-release-sequences.md)).
|
|
*/
|
|
|
|
import { parseArgs } from 'node:util'
|
|
import { isEntry } from './process.ts'
|
|
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)
|
|
// Resolve the publish order here, before the build: an install-edge cycle
|
|
// makes the order unrepresentable, and that has to surface at the first gate
|
|
// rather than when pack is already writing tarballs.
|
|
const ordered = family.publishOrder(members)
|
|
if (ordered.length !== members.length) {
|
|
throw new Error(
|
|
`release family ${family.id}: publish order covers ${String(ordered.length)} of ${String(members.length)} 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}, publish order resolved${publishing ? ', publish gates passed' : ''}`)
|
|
}
|
|
|
|
if (isEntry(import.meta.url)) main()
|