Skip to content

Hold back what the package manager's minimum release age holds back - #14

Merged
hazeliscoding merged 69 commits into
mainfrom
feat/m8-release-age
Nov 9, 2026
Merged

hazeliscoding merged 69 commits into
mainfrom
feat/m8-release-age

Conversation

@hazeliscoding

Copy link
Copy Markdown
Owner

Summary

In M7 the plan called Angular 22 reachable while the team's libraries' 22.0.0 was minutes old, and ng update refused them. The plan now reads the package manager's minimum release age and holds back what it holds back. It reads pnpm's and yarn's settings from their files and the environment, and runs nothing from the repo.

  • Release age. The plan reads pnpm's minimumReleaseAge, yarn's npmMinimalAgeGate, npm's before and min-release-age, and bun's minimumReleaseAge. It picks the newest release that's old enough, for Angular's own packages too, and names a younger one it passed over. A major that only waits gets waiting until a time and no Run: line, with a Sooner command for the person to approve, or the reason there's no sooner way.
  • Settings read, not run. The age comes from the environment and the settings files each manager reads, in its order, as pnpm 10.34.9, 11.28.11 and 12.16.1 and yarn 4.10.1 to 4.18.1 did when tried. A line the plan can't read, an age too long to give a date for, or a release dated past the year 9999 leaves the age unknown, with the reason. The plan never says a manager refuses a value it reads.
  • The version question. The version comes from packageManager, an exact devEngines version or the yarnPath file's name, and a devEngines range only limits which version may answer, as Corepack 0.35.0 reads it. Only a manager nothing pins is asked, only for --version, from an empty folder the plan makes in the home folder and removes, with Corepack reading no pin and using no network. A pnpm, yarn or npm found inside the repo never runs. Folders are compared by identity, so a junction or a short name doesn't get past that.
  • npm. npm runs by its absolute path, with its log and cache in a folder of the plan's own, its update check off, and --force, since npm 11 refuses config and view in a project whose devEngines names another manager.
  • The runner. update.mjs runs every command with pnpm_config_pm_on_fail=ignore. pnpm 12.16.1 reads that setting only under that name, and in a pinned project ng update's own pnpm list and the installs wrote packageManagerDependencies into the lockfile, which then stopped every hop at ng update's clean check.
  • Stalls. The new readers take time in line with the file: workspace patterns match part by part, and the YAML, bunfig.toml and pnpm 10 .npmrc readers no longer slow down on long runs of blank lines or many keys. An older .npmrc pattern that still does is in M8.
  • Docs. SKILL.md, blocked.md, troubleshooting.md, libraries.md, SECURITY.md and the enterprise example say what the plan reads and runs, and where, what to do with a waiting major, and when to plan again or ask the person.
  • CI. The tests also run on Node 24, whose npm has min-release-age.
  • The roadmap. M8's release-age item is ticked, with three decisions: release age, reading settings without running anything from the repo, and the runner's lockfile fix. What the reviews left is in M8.

Checked

  • npm test on Windows (Node 24.20.0, npm 11.19.0): 356 tests, 347 pass, 9 skipped (the 8 evals-login tests and the POSIX-only pnpm 12 updater test). plan.test.mjs alone: 213 of 213.
  • plan.test.mjs on Linux, in Docker node:22.23.4-bookworm with no network, at 6fbb751, before the last one-line fix: 209 pass, 4 skipped (the 3 Windows-only tests, and npm's min-release-age test, since npm 10.9.9 doesn't have it).
  • Every behaviour change had a test that failed first, or a hand mutation of plan.mjs that the new test caught.
  • Real tools, offline, in scratch home folders: pnpm 10.34.9, 11.28.11, 12.4.2 and 12.16.1, yarn 4.9.4 to 4.18.1, npm 11.19.0 and Corepack 0.35.0. The settings order, a stray pin above the home folder, a missing or read-only home folder, ages at and past the date limits, and each YAML form were compared with what the tools printed.
  • A security review and a fidelity review, then a last review that tried to break the fixes and ran 34 single-line mutants. Its 9 findings are fixed. One of them, a test stand-in that called dirname, would have failed the Linux and macOS test jobs.
  • Not run: the evals, or anything on macOS. One full run on Windows failed once in test/smoke.test.mjs ("the server is stopped afterwards"), which this branch doesn't touch, and passed on the rerun.

…ing else

The plan is to ask pnpm and yarn for their minimum release age the way update.mjs runs them, so managerCommand moves into scripts/manager.mjs for both to import. The structural tests failed any relative import and any script SKILL.md doesn't name. A script another script imports now counts as reachable, and ./manager.mjs is the one relative import allowed, so the scripts still need no install and no dependencies. On a copy with the move made, the old tests failed and these pass, and a stray script or another relative import still fails them.
…n share

The plan is about to ask pnpm and yarn for their minimum release age the way ng update asks them, so it has to find them the way update.mjs does: through npm_execpath, Corepack, or a shim read without a shell. managerCommand moves to scripts/manager.mjs unchanged, and update.mjs imports it.
…g update does

From Angular CLI 22.1, ng update skips a release younger than the package manager's minimum release age without a word, and in M7's run the plan called Angular 22 reachable with the team's 22.0.0 minutes old. The plan now asks pnpm and yarn the way ng update asks them, reads npm's before and min-release-age and bun's bunfig.toml, and reports the answer as releaseAge, or under Could not check when the manager can't be asked. It asks with pnpm_config_pm_on_fail=ignore and Corepack offline, since pnpm 12.16.1's --version wrote the lockfile of a project that pins pnpm, and drops the person's own spelling of those names, which on Windows would win. The pnpm workspace test that runs the plan directly gets the fake manager, so no real pnpm runs.
…r, and name the younger one

Every manager's install and, from Angular CLI 22.1, ng update treat a release younger than the minimum release age as not out yet, so a pick that ignored the age printed a command that failed. Libraries, siblings, followers, tooling that follows Angular's major and Angular itself now come from releases old enough; a younger one passed over is named with when it's old enough, and a release with no publish time counts as old enough, as ng update and pnpm count it. The installed release is never held back, since the lockfile has it. Below CLI 22.1, where ng update takes Angular's newest whatever its age, the command names the newest core and CLI old enough.
…il a time, with how to go sooner

In M7 the plan called Angular 22 reachable while the team's 22.0.0 was minutes old, and ng update skipped it, so the Run: line failed. When nothing old enough fits, the plan now works the major out as it will stand once the first fitting release is old enough, gives it every check a runnable major gets, and puts it in plan.waiting with a too-young blocker per release, exit 2 and no Run: line, since plan.hops means runnable. It gives the time, which npm's before doesn't have, and the command for going sooner once the person approves an exemption: --install-first where ng update skips young releases itself, the command as it is where only the install does, and none when Angular itself or a library with migrations is young. A library held back only by age is upgraded already, so it gets no note to upgrade it first. On a --next hop the CLI's prerelease can be the young one, so core and the CLI are each held on their own times.
On a --next hop the CLI's prerelease can come out hours after core's, as CLI 22.1.0 did after core 22.1.0. Holding the hop on core's times there gave a wait that had already ended, so the test checks the waiting major names the CLI and when it's old enough.
…d name the younger one

Already on the target major, the plan offered the newest patch however young, and the package manager's install then refused it, or on pnpm under its default age let it in by writing an exemption nobody approved. It now offers the newest patch old enough for the manager and names a younger one with when it's old enough, or lists it alone under patchesHeld when nothing newer is old enough yet.
…releases it passes over

An agent acts on the printed plan, and a waiting major printed as a blocked one with no time reads as something to fix or force. The result now says until when the project stays where it is, or that npm's before has to move, and the waiting major prints after the runnable ones with a From line for that time and a Sooner or No sooner way line in place of Run:. Each line that passes over a younger release, Angular's own in a hop's header, a library's and a patch's, says which and until when, for pnpm or yarn and for ng update too where it skips young releases itself.
…g on young releases

In M7 the plan called Angular 22 reachable with the team's 22.0.0 minutes old, and ng update from CLI 22.1 skipped it without a word. The plan now prints such a major as waiting until a time, with no Run: line, and a Sooner command for once the person approves an exemption. Going early relaxes a guard against a compromised release, so the skill goes as far as the major before and asks, even for the team's own library, and blocked.md gives each manager's exemption setting. An exemption already committed that covers the releases counts as that approval given before.
… back by its age

The section only covered pnpm and yarn, and told the agent to run a Run: line that a waiting major no longer has. npm's min-release-age or before stops its install with ETARGET, and ng update, which reads only before, then fails at Installing packages with node_modules deleted. bun's minimumReleaseAge stops it with blocked by minimum-release-age. pnpm 12.16.1 refuses with ERR_PNPM_NO_MATURE_MATCHING_VERSION once the team sets the age itself, which makes it strict, so step 4e now says pnpm add writes the exemption only under the default age, and an install into an empty node_modules fails with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION when the lockfile holds a young release nothing exempts. ng update never says it skipped a release, and exits 0 when the old release's peers raise no error, so the section says to check package.json.
Its name said yarn 4.15 and newer hold a release back and yarn before 4.10 doesn't, cut-offs nobody checked here. The test runs yarn 4.18.1, which printed 1440 with nothing set, and 4.9.4, which doesn't know npmMinimalAgeGate, so the name says that.
…g a hop

pnpm 12.16.1 reads pm-on-fail only under the pnpm_config_ name, so update.mjs's npm_config_pm_on_fail=error never reached it. In a project that pins pnpm, with no record of that pin in pnpm-lock.yaml, ng update's own pnpm list and the installs wrote packageManagerDependencies into the lockfile. With a devEngines pin, ng update then stopped every hop at its own clean check, and the retry was refused as not clean.
…while its new major is too young

Whether angular-eslint declares Angular peers was read from the releases old enough. With the new major's releases young, that fell back to the whole history, found the @angular/cli peer older releases had, and sent the package down the library path, where a peerless young release judged as no support. So the major read as blocked by angular-eslint, with the text troubleshooting.md calls a planner error, instead of noting the release is too young until a time. Those packages' X.0.0 came out 3 to 9 days after Angular's.
… core

Only core's publish time could hold Angular back on a stable hop, and the CLI comes out after core: 30 minutes later at 22.0.0, 3 hours 41 at 22.2.0. So once a new major's core was old enough the plan called the hop ready and printed a Run: line, while ng update gave the young CLI no target and failed on @angular/build's peers, and a waiting major's time was core's, early by that gap. With an age set, the plan now reads the CLI's full document, about 3.4 MB, and holds the CLI on every hop the way it did on --next.
"No sooner way: Angular N itself is too young until then" took then from the From line, which a later library's release can set. Angular itself is old enough sooner, and from then a library can be exempted and installed first, so the line was wrong in between.
"Nothing to upgrade until" took the latest of the held patches' times. Each patch is an upgrade on its own, and the CLI comes out after core, by 3 hours 41 at 22.2.0, so the headline could overstate the wait by hours.
…bling's move passes over

Only a library's first pick named a newer release too young to take, and when it's old enough. Tooling that follows Angular's major, a follower moving with its peer, and a library a sibling made move took the newest release old enough and said nothing, though ng update would take the newer one once it's old enough. The sibling pass now names it against its own pick, and keeps nothing when that pick is itself too young.
… on as a before, as a wait with a time

Those npms turn min-release-age into a before that many days back as they load, and 11.10 to 11.13 list it in min-release-age's place. So the plan said the major waits on npm's before, with no time, until that setting moves, for a setting the project never set and that moves by itself. Node 24.14.1 to 24.17.0 bundle npm 11.11 to 11.13. ng update reads that before too, so the plan now says it skips the release as well.
…ude, and exempt a package on npm and bun

npm 11.10 to 11.16 apply min-release-age, and npm 10 applies before, but only npm 11.17 added min-release-age-exclude. On those, the plan offered a Sooner command after an exemption npm only warns about as an unknown setting, so the command still failed at install. Node 24.18 bundles npm 11.16. The line also named a release, while npm's and bun's exclusions take a package and let every release of it through, which is what the person approves.
…ain once dependencies are installed

The reason was cut at 80 characters, mid-URL for Corepack's offline error, and quoted update.mjs's advice to run the printed command when pnpm wasn't found. The entry sits under a header that says to check things in the running app, which can't check an age, and nothing sent the agent back to plan after step 3's install fetched the pnpm the project pins, so the first plan's Run: line could fail on a young release.
… its place, so it proves the keep

Since the major's wait went in, later() picks a young installed release straight back from the whole document, so the test passed with the keep in aged() removed. An installed release that came out after a higher one, as a backport does, now fails without the keep and passes with it.
A review reverted the new release-age code one piece at a time, and these passed every test: the wait taking the first held time instead of the last, the blocker's ng update clause, an answer that isn't a version, yarn's units, a failed question counting as could-not-check, and the Corepack controls. In M7 ui-forms came out 0.6 s after ui-core, and the fixture now does too, so the first and last times differ.
…hen release age went unchecked

The skill's waiting-major bullet matched only waiting until, and said a No sooner way line meant only waiting gets past it, which is wrong under npm's before, where only moving that setting does. It also called the From line what the plan will print as Run: then, though a newer release can be old enough by then. When the plan couldn't ask pnpm or yarn, as before step 3's install fetches the pinned one, nothing sent the agent back to plan, so a major it called reachable could fail on a young release. blocked.md now gives every reason the plan prints for no sooner way.
…e, and quote ng update's npm failure

pnpm 12.16.1 failed with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION with node_modules present too, frozen or not, so the narrower wording let an agent think a local install was fine. On npm, ng update with min-release-age alone prints Installing packages [FAILED: Unable to install packages] and Restored package.json to its original state., not npm's ETARGET, which is what an agent will search for. npm 11.10 to 11.14 hand min-release-age to ng update as a before.
…me from

The plan asks the project's own pnpm or yarn for its age now, which its header didn't say. The age rules copy Angular CLI's parser, so the comment names the CLI they were read from, 22.2.6, and the first pnpm and yarn majors whose unset age is a day sit in one place instead of two.
… stands in for npm 11.16

The shortcut said 2 minutes 39 with no unit for the last number. The npm 11.17 test sets npm-version in the project's .npmrc, which its comment didn't explain, while the test before it does.
…npm test can't override it

The two tests that stand in for npm 11.13 and 11.16 set npm-version in the project's .npmrc. npm sets npm_config_npm_version for the scripts it runs, so under npm test the running npm's own version beat the .npmrc and both tests failed, though they passed under node --test. The environment beats the .npmrc on npm 11.19.0 and 10.9.10.
…s and runs nothing from the repo

A security review of 0fd0ae5 found the plan ran pnpm or yarn in the project to ask for the minimum release age, and a yarn repo's yarnPath release or plugin is code the repo commits. The plan now reads those settings from pnpm-workspace.yaml, .yarnrc.yml, their global files and the environment, and asks an installed pnpm or yarn only for its version, from an empty temporary folder, when nothing pins it.
…nd runs nothing from the repo

The plan no longer starts pnpm or yarn in the project, since a yarn repo's yarnPath release or plugin is code the repo commits. A pinned manager's version now comes from the pin, so step 3 no longer says the install fetches the pinned one: only a pnpm or yarn whose version the plan couldn't learn leaves release age unchecked. What ng update asks the manager stays as it was.
…s' minutes-old release

The plan now reads pnpm's minimum release age from pnpm-workspace.yaml and takes pnpm's version from the portal's packageManager pin, so it no longer calls Angular 22 clear while the libraries' 22.0.0 is minutes old. The agent runs the plan's command that installs them first. The start-over note now says only their peer ranges stop blocking, since a run within a day of the last one is still waiting.
…un, not a pin, and a packageManager above it first, as Corepack does
…age.json pin, so a stray one above the home folder can't choose its version
…and what to do, instead of waiting for pnpm or yarn to be installed
…aming where it's set, instead of crashing the plan
…refuses or passes over, as the probes printed them
… from pnpm 12, since 11.28.11 matched it as written
…in a file indented as a whole, and leave a line the plan can't read unknown, not refused
… sets any key twice, not only minimumReleaseAge
…he person's own, outside the repo, not only that the repo's never ran
…, since the relative-PATH test's PATH has no dirname on Linux and macOS
…g.yaml with a Set, since comparing each key with every other stalled the plan for 33 s over 100,000 keys
…with spaces or tabs only, since a run of blank lines before another line stalled the plan for 24 s
…olders have on disk, as fs.realpath.native gives them, whatever the spelling the plan was run with
… one, or "" with a value below it, which every pnpm and yarn command failed on, instead of reading the age
…ull and NULL, compare 2^64 - 1 as a BigInt, and leave 1_440 or an escape in double quotes unknown
…dn't be asked, and ask the person when the reason names a file, a value, the home folder or a manager in the repo
…in a too-long age, and read a quoted key while leaving two lines below a key and an ignorePath the plan can't read unknown
…e registry dates one so late that the age ends past the year 9999, instead of crashing or printing a garbled time
…stry gives as a list stopped the plan with a TypeError
@hazeliscoding
hazeliscoding merged commit 0d74a53 into main Nov 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant