juspay / juspay/blend-rescript

release.yml: the -N re-release scheme can silently move 'latest' backward, and -N outranks later prereleases

Open
#140 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
ReScript
Stars
1
Forks
0
Avg merge
2h 48m
Merged PRs (30d)
4

Description

Two independent defects in the `-N` binding-only re-release scheme, both surfaced while cutting `0.0.37-1`. Filing together because they live in the same mechanism.

---

## 1. The prerelease guard doesn't recognise the `-N` form the workflow itself generates

`.github/workflows/release.yml`:

```bash
if [ -z "$DTAG" ]; then
if [[ "$VERSION" =~ -(beta|alpha|rc) ]]; then DTAG=beta; else DTAG=latest; fi
fi
if [[ "$DTAG" == "latest" && "$VERSION" =~ -(beta|alpha|rc) ]]; then
echo "Refusing to publish prerelease $VERSION under 'latest'."; exit 1
fi
```

`0.0.37-1` is a **prerelease** in semver — everything after `-` is a prerelease identifier — but it does not match `-(beta|alpha|rc)`. So:

- the auto-tag branch resolves it to **`latest`**
- the guard on the next line uses the same regex, so it does not catch it either

A dispatch with no explicit `dist_tag` would therefore point `latest` at a version that sorts **below** the current `latest`:

```
0.0.37-1 < 0.0.37
```

We avoided it this time by passing `dist_tag=next` by hand. The next person won't know to.

**Fix:** test for a prerelease properly rather than by name. `semver.prerelease(v) !== null`, or at minimum widen the regex to catch a bare `-` suffix.

---

## 2. `-N` on a numbered prerelease outranks every later prerelease

Appending `-N` to a base like `0.0.37-beta.5` yields the prerelease identifier `beta.5-1`. Semver ranks **numeric identifiers below alphanumeric ones**, and `5-1` is alphanumeric — so it sorts above `5`, `6`, `7`, `8`.

This is already live on npm. The actual published `0.0.37` line, semver-sorted:

```
0.0.37-beta.4
0.0.37-beta.5
0.0.37-beta.6
0.0.37-beta.7
0.0.37-beta.8
0.0.37-beta.5-1 ← re-releases of beta.5 rank ABOVE beta.8
0.0.37-beta.5-5
0.0.37
```

So the maximum prerelease on the line is `0.0.37-beta.5-5`, not `beta.8` — even though `beta.8` shipped later. The `beta` dist-tag papers over it (tags are manual pointers), but anything resolving by semver max picks the wrong one.

**Fix:** use a dot instead of a hyphen — `0.0.37-beta.5.1` sorts correctly, below `0.0.37-beta.6`, because `1` is then a numeric identifier in its own position. That also keeps `sed -E 's/-[0-9]+$//'`-style base extraction working with a small tweak.

---

## Note on the stable-base case

Worth documenting either way: `-N` on a **stable** base (`0.0.37-1`) is a prerelease that sorts *below* its own base, and `^0.0.37` does not match it — so a re-release published this way is unreachable to anyone on a range, by design. That is fine for a preview build consumed via an exact pin or a dist-tag, but it is not obvious from the scheme's name and is worth a line in the workflow header.

Verified with `semver`:

```
0.0.37-1 < 0.0.37-beta.8 < 0.0.37 < 0.0.38-beta.0

satisfies('0.0.37-1', '^0.0.37') -> false
satisfies('0.0.37', '^0.0.37') -> true
```

## Context

Surfaced while publishing `0.0.37-1` (bindings regenerated with `@juspay/rescript-bindgen@1.4.0-beta.0`, `@tag` variants) under the `next` tag.

Contributor guide

Open the contributing guide

Research direction

Start in .github/workflows/release.yml by tracing the dist_tag selection and prerelease guard, then review the semver ordering examples in the issue. Verify that generated -N versions are classified consistently and that re-releases of numbered prereleases do not outrank later prereleases; document the stable-base behavior in the workflow header.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, shell
Domain
release
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.