Repository navigation
feat!: find the last release from tags, not package.json - #10
Merged
Merged
Conversation
resolveBuildIdentity and predictNextVersion took the last release from
package.json's version and looked up its tag. A repository whose
releases are only a tag and a GitHub Release, with no release commit
bumping package.json, therefore predicted from a stale version forever
and never resolved a release.
Both now start from findLastRelease: the highest major.minor.patch tag
in the tag template that git tag --merged HEAD lists, the way
semantic-release's core finds the last release. Prerelease tags, tags in
another format and tags on commits HEAD does not contain are ignored.
With no release tag reachable, prediction analyses the whole history and
gives 1.0.0, as semantic-release does for a first release.
BREAKING CHANGE: the tagName function option of resolveBuildIdentity
and predictNextVersion is replaced by tagTemplate, a string holding
{version} exactly once (the CLI's --tag-name template, unchanged).
readPackageVersion and package.json reading are removed; findLastRelease
gives the last released version from tags instead.
A git hook exports GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE for the repository it fired in, and git honours them over its working directory. Every git call here took repoRoot as that working directory, so a build or test run launched from a hook would inspect the hook's repository instead. Strip the three variables from the environment of every git call, the test fixtures' included.
resolveBuildIdentity only checked whether the single highest release reachable from HEAD was on HEAD, so a commit carrying a lower release tag (a backport tagged after a newer release) reported as a plain commit. It now lists the tags at HEAD (git tag --points-at HEAD) and reports the highest release among them. Only HEAD is inspected, so a shallow or tagless clone still resolves, to kind 'commit'.
With no release tag visible, predictNextVersion assumed a first release and predicted 1.0.0. A clone made without its tags looks exactly like that, so a repository already released several times got 1.0.0, a version that may already exist as a tag. findLastRelease now throws instead of returning undefined: in any shallow clone, since a higher tag could sit beyond the cut, and when no release tag is reachable from HEAD. Each message says what to fetch (fetch-depth: 0). predictNextVersion inherits both, and its log range names refs/tags/ in full.
…ng missing fields
Mearman
marked this pull request as ready for review
October 13, 2026 19:50
|
This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins. |
|
🎉 This PR is included in version 3.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
resolveBuildIdentityandpredictNextVersiontook the last release frompackage.json'sversionand then looked for its tag. That only works while every release also commits a version bump. A repository that releases with just a tag and a GitHub Release (no@semantic-release/gitor@semantic-release/npmcommit) keeps an oldpackage.jsonversion, so after its next release the prediction keeps analysing commits since the stale tag andresolveBuildIdentitynever reportskind: 'release'.Releases are now read from tags alone, and nothing reads
package.json.resolveBuildIdentityreportskind: 'release'when any release tag points at HEAD (git tag --points-at HEAD), with the version read out of the tag; with several, the highest wins, and a lower one on HEAD (a backport tagged after a newer release) still counts. It looks at HEAD only, so it works in a shallow clone, and a clone made without tags reportskind: 'commit'rather than throwing.findLastReleaseis the highestmajor.minor.patchtag in the tag template among the tagsgit tag --merged HEADlists, which is how semantic-release's own core picks the last release. Prerelease tags, tags in another format and tags on commits HEAD does not contain are ignored. It throws, saying what to fetch (fetch-depth: 0), in any shallow clone, since a higher tag could sit beyond the cut, and when no release tag is reachable.predictNextVersionanalyses the commits since that tag and inherits both errors. There is no first-release fallback: a clone made without its tags looks exactly like a repository that never released, and predicting1.0.0there names a version that may already exist.This is a breaking change and should release as a major. The
tagNamefunction option becomestagTemplate, a string with{version}exactly once, because a tag has to be read back into a version and a function cannot be inverted; the CLI's--tag-namealready took this template, so the CLI is unchanged.readPackageVersionis removed; a caller that used it for "the last released version" gets that fromfindLastRelease(root).version, which needs the full history and tags wherever it runs.Every git call the library makes now drops
GIT_DIR,GIT_WORK_TREEandGIT_INDEX_FILEfrom its environment. A git hook exports them for its own repository and git then ignoresrepoRoot; this repository's pre-push hook runs the test suite, so the fixtures were exposed to it too.Since the first push: the release check moved from "the highest reachable release is on HEAD" to "any release tag is on HEAD";
findLastReleasethrows instead of returningundefined, and the1.0.0first-release prediction is gone, because a shallow or tagless clone of a repository atv4.2.0predicted1.0.0; the hook environment fix; the log range namesrefs/tags/in full; version comparison parses typed parts instead of defaulting missing fields; and tests for shallow and tagless clones, annotated tags, a merged side-branch tag, a higher tag on an unmerged branch, a tag on an unreachable commit,vNext, the backport case and the hook environment. Each new test was checked by breaking the code it covers and watching it fail.Verified with the repository's own
pnpm typecheck,pnpm lint,pnpm test(including the CLI e2e suite against a fresh build),pnpm buildandattw --pack, on every commit.Known consumers, all private repositories, found by code search across the owning organisations and by reading each one's
package.json, call sites and workflow checkouts. None passes thetagNameoption. Two call onlyresolveBuildIdentity,predictNextVersionandloadCommitAnalyzerwith the default template and need nothing beyond the version bump; every job of theirs that predicts checks out withfetch-depth: 0, and their shallow jobs only resolve the identity, which still works there. One importsreadPackageVersionfor an API document's version; its bump replaces that call withfindLastRelease(root).version, and the jobs that build it already check out withfetch-depth: 0. Its exact pin means the major does not reach it until it is bumped on purpose.