Compare commits

...

3 Commits

Author SHA1 Message Date
Rui Chen d100e75a45 docs: mark v2 as unsupported (#821)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2026-07-13 13:15:35 -04:00
Rui Chen 81bcd9dd59 test: cover release lookup after tag replacement (#820)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2026-07-13 12:39:38 -04:00
Rui Chen b05950fac2 docs: update the release verification workflow (#819)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2026-07-13 10:53:12 -04:00
5 changed files with 137 additions and 15 deletions
+6 -3
View File
@@ -38,8 +38,8 @@ claim a reproducible release-creation fix.
`3.0.0` is a major release that moves the action runtime from Node 20 to Node 24.
Use `v3` on GitHub-hosted runners and self-hosted fleets that already support the
Node 24 Actions runtime. If you still need the last Node 20-compatible line, stay on
`v2.6.2`.
Node 24 Actions runtime. `v2.6.2` was the final Node 20-compatible release and is
no longer maintained or supported.
## What's Changed
@@ -47,10 +47,13 @@ Node 24 Actions runtime. If you still need the last Node 20-compatible line, sta
* Move the action runtime and bundle target to Node 24
* Update `@types/node` to the Node 24 line and allow future Dependabot updates
* Keep the floating major tag on `v3`; `v2` remains pinned to the latest `2.x` release
* Keep the floating major tag on `v3`; freeze `v2` at the final `v2.6.2` release
## 2.6.2
`2.6.2` is the final `v2` release and is no longer maintained or supported. Upgrade
to `v3` for the supported Node 24 runtime and current fixes.
## What's Changed
### Other Changes 🔄
+5 -2
View File
@@ -39,8 +39,11 @@ Typically usage of this action involves adding a step to a build that
is gated pushes to git tags. You may find `step.if` field helpful in accomplishing this
as it maximizes the reuse value of your workflow for non-tag pushes.
`v3` requires a GitHub Actions runtime that supports Node 24. If you still need the
last Node 20-compatible line, stay on `v2.6.2`.
`v2.6.2` is the final `v2` release and is no longer maintained or supported. It
uses the [Node 20 runtime deprecated by GitHub Actions](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/).
Upgrade to `v3`, which runs on Node 24. If a problem still reproduces on the
latest `v3`, open a new issue with the exact action ref, workflow run URL,
runner, and relevant logs.
Below is a simple example of `step.if` tag gating
+23 -10
View File
@@ -23,21 +23,34 @@ Use this checklist when cutting a new `action-gh-release` release.
- `npm run build`
- `npm test`
5. Commit the release prep.
- Use a plain release commit message like `release 3.0.0`.
6. Create the annotated tag for the release commit.
- Example: `git tag -a v3.0.0 -m "v3.0.0"`
7. Push the commit and tag.
- Example: `git push origin master && git push origin v3.0.0`
8. Move the floating major tag to the new release tag.
- Use `git commit -s` so the release commit carries a DCO sign-off.
- Use a plain release commit message like `release X.Y.Z`.
6. Push a release branch and open a pull request against `master`.
- Wait for required checks and reviews.
- Do not bypass branch protection or tag an unmerged release branch.
7. After merge, fetch `origin/master` and resolve the exact merged release commit.
- Confirm that commit contains the expected package version and top changelog entry.
- When the PR is squash-merged, do not assume the release branch commit is the release commit.
8. Create and push the full annotated version tag on the merged release commit.
- Example: `git tag -a vX.Y.Z -m "vX.Y.Z" <release-commit>`
- Push only the full version tag first, then wait for its tag-triggered CI to pass.
9. Move the floating major tag to the same merged release commit.
- For the current major line, run `npm run updatetag` to move `v3`.
- Keep `v2` pinned to the latest `2.x` release for consumers that still need the Node 20 runtime.
- Verify the floating tag points at the same commit as the new full tag.
9. Create the GitHub release from the new tag.
- Do not move `v2`; it is frozen at the final, unsupported `v2.6.2` release.
- Verify `v3` and the full version tag are annotated and peel to the same commit.
- Verify `v2` did not move, then wait for the separate `v3` tag-triggered CI run to pass.
10. Create the GitHub release from the full version tag.
- Prefer the release body from [CHANGELOG.md](CHANGELOG.md), then let GitHub append generated notes only if they add value.
- Verify the release shows the expected tag, title, notes, and attached artifacts.
- Verify the release shows the expected tag, title, notes, draft/prerelease state, and attached artifacts.
11. Run post-release consumer verification in `ruitest2/action-gh-release-test`.
- Run the generic smoke against both the full version tag and `v3`.
- Run regression workflows relevant to the fixes in the release.
- Confirm that every disposable release, tag, discussion, container, volume, and temporary credential was cleaned up.
## Notes
- Behavior changes should already have matching updates in [README.md](README.md), [action.yml](action.yml), tests, and `dist/index.js` before release prep begins.
- Docs-only releases still need an intentional changelog entry and version bump decision.
- If a release is mainly bug fixes, keep the title and summary patch-oriented; do not bury the actual fixes under dependency noise.
- Do not move the floating major tag or publish the GitHub release until the full
version tag's CI passes.
+55
View File
@@ -1063,6 +1063,61 @@ describe('github', () => {
);
});
it('reuses a published prerelease found by listing after direct tag lookup returns 404', async () => {
const existingRelease: Release = {
id: 77,
upload_url: 'existing-upload',
html_url: 'existing-html',
tag_name: 'v1.0.0',
name: 'nightly',
body: 'previous nightly body',
target_commitish: 'old-commit',
draft: false,
prerelease: true,
assets: [{ id: 5, name: 'nightly.zip' }],
};
const updatedRelease: Release = {
...existingRelease,
name: 'updated nightly',
body: 'updated nightly body',
target_commitish: 'new-commit',
};
const createRelease = unexpected('createRelease');
const updateRelease = vi.fn().mockResolvedValue({ data: updatedRelease });
const releaser = createReleaser({
getReleaseByTag: vi.fn().mockRejectedValue({ status: 404 }),
allReleases: async function* () {
yield { data: [existingRelease] };
},
createRelease,
updateRelease,
});
const result = await release(
{
...config,
input_name: 'updated nightly',
input_body: 'updated nightly body',
input_prerelease: true,
input_draft: false,
input_target_commitish: 'new-commit',
},
releaser,
);
expect(result).toEqual({ release: updatedRelease, created: false });
expect(updateRelease).toHaveBeenCalledWith(
expect.objectContaining({
release_id: existingRelease.id,
tag_name: existingRelease.tag_name,
target_commitish: 'new-commit',
draft: false,
prerelease: true,
}),
);
expect(createRelease).not.toHaveBeenCalled();
});
it.each([
['an omitted draft input', undefined],
['a null-expression draft input', undefined],
+48
View File
@@ -286,6 +286,54 @@ describe('run', () => {
);
});
it('uses the selected moving-tag release ID for upload, finalization, and outputs', async () => {
config = {
...config,
input_tag_name: 'nightly',
input_draft: false,
input_prerelease: true,
input_files: ['nightly.zip'],
};
const selectedRelease: Release = {
...initialRelease,
id: 77,
upload_url: 'https://uploads.example.test/releases/77/assets{?name,label}',
html_url: 'https://example.test/releases/77',
tag_name: 'nightly',
name: 'nightly',
prerelease: true,
};
const publishedRelease: Release = {
...selectedRelease,
draft: false,
};
mocks.release.mockResolvedValue({ release: selectedRelease, created: false });
mocks.paths.mockReturnValue(['nightly.zip']);
mocks.upload.mockResolvedValue({ id: 9 });
mocks.finalizeRelease.mockResolvedValue(publishedRelease);
mocks.listReleaseAssets.mockResolvedValue([{ id: 9, name: 'nightly.zip' }]);
await run();
expect(mocks.upload).toHaveBeenCalledWith(
config,
mocks.releaser,
'https://uploads.example.test/releases/77/assets',
'nightly.zip',
selectedRelease.assets,
77,
);
expect(mocks.finalizeRelease).toHaveBeenCalledWith(
config,
mocks.releaser,
selectedRelease,
false,
);
expect(mocks.listReleaseAssets).toHaveBeenCalledWith(config, mocks.releaser, publishedRelease);
expect(mocks.setOutput).toHaveBeenCalledWith('id', '77');
expect(mocks.setOutput).toHaveBeenCalledWith('url', publishedRelease.html_url);
});
it('leaves an existing draft recoverable when an upload fails', async () => {
config = {
...config,