fix(release): order publication by every installed dependency section

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.
This commit is contained in:
imccyu
2026-08-14 11:27:13 +08:00
parent 21d2433d97
commit 47399764c5
3 changed files with 154 additions and 22 deletions

View File

@@ -55,6 +55,15 @@ function main(): void {
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) {
@@ -64,7 +73,7 @@ function main(): void {
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' : ''}`)
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()