Find the first release containing a git commit.
Paste a commit SHA, a merged PR number, or a GitHub commit URL — get back the first release tag that contains it, plus a copy-pasteable permalink for Slack, GitHub PR comments, or just a plain link.
- Web: https://released.blabberate.com
- CLI:
npx released github.com/facebook/react/commit/a1b2c3d(orgit released a1b2c3d)
This is a pnpm monorepo with four packages:
| Package | What | Where |
|---|---|---|
@released/core |
Pure-TS library: algorithm, GitHub client, sanitizer, parser. Web Platform APIs only — runs in Node 20+ and Cloudflare Workers unchanged. | packages/core/ |
released |
Node CLI (released + git-released bin aliases). |
packages/cli/ |
@released/web |
Cloudflare Worker — homepage + permalink page + JSON API. | packages/web/ |
@released/web-og |
Cloudflare Worker — OG-image PNG renderer (isolated bundle weight). Service Binding to @released/web. |
packages/web-og/ |
pnpm install
pnpm -r test # 70+ tests across packages
pnpm -r typecheck
pnpm -r buildPer-package dev:
pnpm --filter @released/web dev # wrangler dev for web
pnpm --filter @released/web-og dev # wrangler dev for web-og
pnpm --filter released dev -- <input> # tsx-run the CLI in placeOrder matters — web-og has a Service Binding to web, so web deploys first.
-
GitHub token for the web Worker (gives the shared anonymous fast path 5000 req/hr instead of 60):
cd packages/web wrangler secret put GITHUB_TOKEN -
Internal secret shared between
webandweb-og(used bywebto reject direct public hits to/internal/result/*and only accept calls coming through the Service Binding):cd packages/web && wrangler secret put INTERNAL_SECRET cd packages/web-og && wrangler secret put INTERNAL_SECRET # same value
-
Rate-limiting rule in the Cloudflare dashboard (D13). Documented here so it's reproducible — Cloudflare doesn't yet expose this as wrangler config:
- Zone → Security → WAF → Rate limiting rules → Create rule
- Name:
released-api-per-ip - Match:
(http.request.uri.path matches "^/api/lookup") - Counting: by source IP
- Rate: 60 requests per 1 minute
- Action: Block, with 1-minute timeout
- Response: 429 with JSON
{"error":"rate_limited"}
# Web first (web-og depends on the service binding existing):
pnpm --filter @released/web deploy
pnpm --filter @released/web-og deploy.github/workflows/release.yml runs the above sequence automatically on push
to main after the Changesets "Version Packages" PR is merged. Required
secrets:
NPM_TOKEN— npm publish access for the CLICLOUDFLARE_API_TOKEN— Workers deployCLOUDFLARE_ACCOUNT_ID- (per-Worker secrets above must already be set via
wrangler secret put)
packages/core (pure TS — runs in Node + Workers)
│
├─► packages/cli (Node CLI; auth = --token | GITHUB_TOKEN | gh auth)
│
└─► packages/web (Cloudflare Worker — homepage, /r/:o/:r/c/:sha, /api/*)
│
└── service binding ──► packages/web-og (PNG renderer, isolated bundle)
Algorithm (in packages/core/src/find-release.ts):
- Resolve PR → merge commit, or validate the commit SHA.
- List all repo tags via GitHub GraphQL (paginated). Per-tag, use the best
available date: GitHub Release
published_atif any, else annotated tag tagger date, else the tagged commit's committer date. - Sort by date ascending — NOT filter. Git dates aren't reliably monotonic with topology (clock skew, manually-set dates, cherry-picks); filtering on date would silently drop containing tags. Date is ordering only; ancestry is the sole containment test.
- Check
/compare/{tag}...{commit}for each tag in date order, in parallel batches of 5, stop at the first hit. Honor soft + hard deadlines (defaults 20s / 25s); soft → partial state, hard →LookupTimeoutError. - Build "also in" list from the next ~5 newer tags.
- Fetch + sanitize the GitHub Release notes (via
micromarksafe profile, then attribute scrub forjavascript:/vbscript:/data:text/htmlURIs).
MIT.