Skip to content

feat: support prefixed tags for monorepo multi-product releases#844

Open
BYK wants to merge 1 commit into
masterfrom
feat/prefixed-tags
Open

feat: support prefixed tags for monorepo multi-product releases#844
BYK wants to merge 1 commit into
masterfrom
feat/prefixed-tags

Conversation

@BYK

@BYK BYK commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Enables several independently-versioned products to live in one repository (e.g. cli@1.2.3, mcp@2.0.0) without cross-contaminating each other's latest-tag/changelog-base detection or release branches. Part of the getsentry/toolkit monorepo work (#842) — the second of two PRs (after the cloudflare target in #843).

The write path already honored github.tagPrefix (setting tagPrefix: "cli@" already produces cli@1.2.3). This PR fills the gaps on the read paths.

Changes

  • getLatestTag(git, tagPrefix) (src/utils/git.ts) — scopes git describe --tags --abbrev=0 with --match '<prefix>*' so the latest tag is resolved per-product instead of picking whatever is newest across all interleaved tags. Backward compatible (empty prefix → current behavior).
  • Thread the prefix through read pathsprepare.ts auto-version base (:762) and changelog/oldVersion base (:870) now pass getGitTagPrefix(); the changelog command (changelog.ts) does too, guarded by a config-file check so it still runs standalone.
  • getGitTagPrefix() (src/config.ts) — now warns when a single config declares multiple github targets with differing tagPrefix values (ambiguous), returning the first. The intended layout is one .craft.yml per product.
  • findReleaseBranches (src/utils/git.ts) — handles slashed release-branch prefixes (release/clirelease/cli/1.2.3) so the failed-checkout error hints work with the per-product releaseBranchPrefix collision fix. Verified regression-free for single-segment prefixes.

Already prefix-aware, no code change (now covered by tests): CalVer scan (calver.ts), versionToTag, and getVersion/parseVersion (which correctly extract 1.2.3 from cli@1.2.3, mcp@2.0.0-dev.1, sentry-cli@10.20.30, etc.).

Monorepo model

One .craft.yml per product, each with its own tagPrefix + slashed releaseBranchPrefix:

# CLI
releaseBranchPrefix: release/cli
targets:
  - name: github
    tagPrefix: "cli@"
# MCP
releaseBranchPrefix: release/mcp
targets:
  - name: github
    tagPrefix: "mcp@"

→ tags cli@1.2.3 / mcp@1.2.3 on branches release/cli/1.2.3 / release/mcp/1.2.3. No collisions.

Known limitation (documented)

GitHub tracks a single repo-wide "Latest" release; isLatestRelease is not prefix-scoped, so the "Latest" badge can move between products. Tags, changelogs, release branches, and version detection remain correctly per-product. Documented in github.md.

Testing

  • pnpm test — full suite green (1055 passed). New/updated tests: getLatestTag with/without prefix + --match assertion; slashed-prefix findReleaseBranches (incl. the versionless-branch edge); getVersion/parseVersion on prefixed tags; getGitTagPrefix single/same/conflicting/mixed-undefined cases.
  • pnpm lint / tsc --noEmit clean. Docs build clean.

Review

Adversarial subagent review: no CRITICAL/MAJOR bugs in changed code; the findReleaseBranches refactor verified regression-free case-by-case. Findings M1 (repo-wide "Latest" limitation) and M3 (mixed-prefix test) were addressed in this PR.

🤖 Generated with opencode

Enable two (or more) independently-versioned products to live in one
repository (e.g. `cli@1.2.3`, `mcp@2.0.0`) without cross-contaminating
each other's latest-tag/changelog-base detection or release branches.

The write path already honored `github.tagPrefix`; this fills the gaps
on the read paths:

- `getLatestTag(git, tagPrefix)` now scopes `git describe` with
  `--match '<prefix>*'` so the latest tag is resolved per-product.
- Thread the configured prefix through the auto-version base and
  changelog base in `prepare.ts`, and through the `changelog` command
  (guarded by a config-file check).
- `getGitTagPrefix()` now warns when a single config declares multiple
  `github` targets with differing prefixes (ambiguous), returning the
  first. The intended layout is one `.craft.yml` per product.
- `findReleaseBranches` handles slashed release-branch prefixes
  (`release/cli` -> `release/cli/1.2.3`) so the error-hint suggestions
  work for the per-product `releaseBranchPrefix` collision fix.

CalVer and `versionToTag` were already prefix-aware; `getVersion`/
`parseVersion` already extract the version out of a prefixed tag — both
are now covered by explicit tests.

Docs: document the monorepo pattern (per-product `.craft.yml` with its
own `tagPrefix` + slashed `releaseBranchPrefix`) and note the known
limitation that GitHub's single per-repo "Latest" badge is not
prefix-scoped.
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://craft.sentry.dev/pr-preview/pr-844/

Built to branch gh-pages at 2026-07-16 13:00 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@BYK
BYK deployed to false July 16, 2026 13:01 — with GitHub Actions Active

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8aa8586. Configure here.

Comment thread src/commands/changelog.ts
// the correct base. Only read the prefix when a config file is present;
// the changelog command can run standalone without one.
const tagPrefix = findConfigFile() ? getGitTagPrefix() : '';
since = await getLatestTag(git, tagPrefix);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog lacks config error guard

Medium Severity

getGitTagPrefix() runs whenever a config file exists, but unlike the versioningPolicy path below it is not wrapped in try/catch. An unreadable or invalid .craft.yml now aborts craft changelog before generating output, including standalone runs that previously only needed git history.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8aa8586. Configure here.

Comment thread src/utils/git.ts
// Only consider branches that actually have a version part after the
// prefix (i.e. more segments than the prefix), mirroring the original
// single-segment behavior.
if (!branchPrefix || branchSegments.length <= prefixSegmentCount) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fuzzy matches confuse monorepo products

Low Severity

For slashed prefixes, Levenshtein distance is computed on the full prefix including the shared release/ segment. Short product names then fall within distance 3 of each other, so failed checkouts suggest other products' branches as typos instead of only near-miss prefixes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8aa8586. Configure here.

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