You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(release): add a command-surface regression check to the release gate (PRINFRA-499) (#284)
The command surface is generated from an upstream OpenAPI spec this repo does
not control, so a resync can remove a command, rename a flag, flip one to
required, or reroute what the CLI sends, with no commit in this repo saying so.
The resync lands as a single "codegen: resync gen/" line and /changelog-cli
collapses it into Internal, so the commit log and the changelog — the two things
a releaser reads — are exactly where such a break is invisible.
Adds a required pre-release step that reduces gen/ to a normalized manifest of
contract-bearing fields at two refs and diffs them. The grep is an allowlist
rather than a denylist: a field omitted from a denylist is invisible forever,
and the first draft of this check proved the point by filtering out Source,
BodyEncoding and JSONName, each of which reroutes the request. The allowlist now
covers every field gen/ emits except the four that are pure prose. Request and
response schemas are collapsed to a presence marker instead of dropped, since
their contents churn constantly but their presence decides whether
--request-schema and --response-schema exist.
Verified against a worktree carrying six simultaneous breaks — a newly added
already-required flag, Source query->body, a deleted RequestSchema, BodyEncoding
json->multipart, a dropped Destructive, and a JSONName rename: the manifest diff
surfaces all six. The v0.6.0..main range correctly produces empty output.
A shape diff cannot see a flag that keeps its signature and stops doing
anything, which is what just happened to --enable-caption on video-translate
and lipsync, so a second pass greps new help text for deprecation language.
Also corrects the bump rule, which listed codegen resyncs as patch-worthy
without qualification and would have mislabeled a breaking resync as a patch.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: RELEASE.md
+50-8Lines changed: 50 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,23 +33,33 @@ gh workflow run dev-release.yml
33
33
34
34
### Pre-release checklist
35
35
36
+
Every step compares against `origin/main` and the last stable tag, so start from a fetched checkout:
37
+
38
+
```bash
39
+
git fetch --tags origin
40
+
LAST_STABLE=$(git tag --list 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'| head -n 1)
41
+
```
42
+
43
+
Keep `$LAST_STABLE` set for the rest of the checklist; steps 4 and 6 reuse it. It filters to release tags, so a run of `-dev.*` prereleases can't shadow the stable one.
44
+
36
45
1.**Review commits since last stable.** Check what's new and confirm nothing is half-finished:
2.**Check open PRs.** Decide if any should merge first (e.g. pending codegen resyncs, small fixes):
41
50
```bash
42
51
gh pr list --state open
43
52
```
44
53
3.**Confirm CI is green on main.** All checks should pass on the latest commit.
45
-
4.**Run E2E smoke test.** With `HEYGEN_API_KEY` set, run `/e2e-cli-test` in Claude Code from the repo root. Confirm all phases pass (no FAIL). WARN on Phase 3 means the account lacks data for some get/detail commands and should be investigated. This builds the binary and exercises it against the live API (costs a small number of credits).
46
-
5.**Pick the version number.** Check the last stable tag and bump according to the rules below:
54
+
4.**Diff the generated command surface for regressions.** See [Checking for Regressions](#checking-for-regressions) below. Required on every stable release, not just ones that look risky — a resync that breaks the CLI looks identical in `git log` to one that doesn't. It covers `gen/` only; hand-written commands in `cmd/heygen/` are reviewed the normal way, through their PRs.
55
+
5.**Run E2E smoke test.** With `HEYGEN_API_KEY` set, run `/e2e-cli-test` in Claude Code from the repo root. Confirm all phases pass (no FAIL). WARN on Phase 3 means the account lacks data for some get/detail commands and should be investigated. This builds the binary and exercises it against the live API (costs a small number of credits).
56
+
6.**Pick the version number.** Check the last stable tag and bump according to the rules below:
47
57
- Patch (`v0.0.x`) for bug fixes, UX polish, codegen resyncs, and additive schema changes.
48
-
- Minor (`v0.x.0`) for new command groups or significant new capabilities.
58
+
- Minor (`v0.x.0`) for new command groups, significant new capabilities, or **any breaking surface change found in step 4** — a resync is only a patch when it is purely additive.
49
59
```bash
50
-
gh release list --limit 3
60
+
echo"$LAST_STABLE"
51
61
```
52
-
6.**Generate changelog.** Run `/changelog-cli v0.x.y` in Claude Code. Review the output and save it for the release notes.
62
+
7.**Generate changelog.** Run `/changelog-cli v0.x.y` in Claude Code. Review the output and save it for the release notes. The skill reads `git log`, so it cannot see the step 4 findings — add those to the release notes yourself, at the top, under **Breaking changes** if any was breaking and **Deprecated** otherwise.
53
63
54
64
### Trigger the release
55
65
@@ -69,16 +79,48 @@ installer, checksums, and platform archives to S3 for CDN-backed installs.
69
79
CDN propagation takes up to 1 minute for the version pointer and 5 minutes
70
80
for the install script.
71
81
72
-
5.**Verify the release was published:**
82
+
1.**Verify the release was published:**
73
83
```bash
74
84
gh release view v0.0.5
75
85
```
76
-
6.**Verify the install script picks up the new version** (after CDN propagation):
86
+
2.**Verify the install script picks up the new version** (after CDN propagation):
`gen/` is generated from HeyGen's OpenAPI spec, which lives upstream. A resync lands as one `codegen: resync gen/ from EF <sha>` commit and `/changelog-cli` files it under Internal — so the commit log and the changelog, the two things a releaser reads, are exactly where a breaking change is invisible. Diff the generated surface instead. This covers `gen/` only; hand-written commands in `cmd/heygen/` and the hidden-endpoint list in `internal/command/hidden.go` are reviewed through their own PRs.
The script reduces `gen/` at both refs to the fields that decide what a user can type, then diffs the two. It filters with an allowlist, since a field left out is invisible forever; `codegen/surface_allowlist_test.go` fails the build if codegen gains a field the allowlist misses, so the list cannot rot. Request and response schemas are compared by presence rather than content: their bodies churn on every resync, but whether a command *has* one decides whether `--request-schema` and `--response-schema` exist.
101
+
102
+
Empty output means the surface is unchanged. The script exits non-zero and says so if its own reduction matched nothing, because "no changes" and "the check is broken" otherwise look identical. Read the `<` lines (the old side) first — a removal is a break, an addition usually isn't.
103
+
104
+
| A `<` line showing... | Effect | Action |
105
+
|---|---|---|
106
+
| a command or flag `Name` gone |`unknown command` / `unknown flag`, exit 2 | For a command, re-register the old path in `cmd/heygen/aliases.go`. There is no flag equivalent, so call it out. |
107
+
| a stricter input: `Required` false→true, a dropped `Enum` value, narrowed `Min`/`Max`, changed `Type`| previously valid invocations now rejected before the request is sent | Breaking. Name the command, the flag, and the old vs new constraint. |
108
+
|`Args` losing an entry, or a changed `Param`| positional arity changed, or the same argument now fills a different URL slot | Breaking. Show the old and new call shape. |
109
+
| a changed `Source`, `JSONName`, `Default`, or `SendDefaultWhenOmitted`| same input, different request — routing, wire key, or whether a value is sent at all | Confirm it is intended; none of these change the help text, so nothing else will surface them. |
110
+
|`BodyEncoding` leaving `json`|`-d/--data` is no longer registered — the builder adds it only when `BodyEncoding` is exactly `json`| Breaking for anyone passing a raw body. |
111
+
| a `RequestSchema`/`ResponseSchema``<present>` line gone |`--request-schema` / `--response-schema` no longer exist there | Breaking for agents that introspect before calling. |
112
+
|`Destructive`, `Endpoint`, or `Method` changing |`--force` and the confirmation prompt appear or disappear; or the command now calls something else | Rarely intended. Confirm before releasing. |
113
+
114
+
Additions are usually safe, with two exceptions that show up only as `>` lines: a new flag that is already `Required: true`, and a new `Args` entry. Both make every prior invocation of that command exit 2. (`Deprecated: true` appearing is not a break — the flag still works and still sends its value — but it is worth a release note.)
115
+
116
+
Finally, a flag can stop doing anything without changing shape at all, which the diff above cannot see because it excludes help text:
Each line names a command and the flag that went quiet, e.g. `video-translate create --enable-caption`. It matches loosely on purpose; a false positive is obvious once you can see which flag it named. A real hit belongs in the release notes, because a user whose script sets that flag gets no error and no warning — just different output than they asked for.
123
+
82
124
## Version Scheme
83
125
84
126
All versions use semver with a `v` prefix. The `v` prefix is required everywhere: git tags, `--version` output, `heygen update --version` input, JSON responses, and install script flags.
0 commit comments