From 71c390bde12aacb5ca9cdaa43d4e2b63c9e9f7ff Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Thu, 15 Oct 2026 13:45:49 -0300 Subject: [PATCH 1/3] ci: prepend new releases to CHANGELOG.md instead of regenerating it The release workflow lets conventional-changelog-action regenerate CHANGELOG.md from git history on every release. With its default release-count of 5, releases older than the last five are dropped from the file, and any entry edited by hand is overwritten. Raising release-count only trades this for regenerating an ever-growing history on each release, and still discards manual edits, such as a hand-written changelog for the 0.x initial development phase. Pass the changelog as input-file. The action then generates only the new release and prepends it to the existing content, leaving older entries untouched. Create an empty file when it is missing, since the action reads input-file unconditionally; it is left without a title because anything already in the file ends up below the prepended release. --- .github/workflows/tag_and_release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tag_and_release.yml b/.github/workflows/tag_and_release.yml index fb4a636..2d464d3 100644 --- a/.github/workflows/tag_and_release.yml +++ b/.github/workflows/tag_and_release.yml @@ -25,6 +25,9 @@ jobs: with: node-version-file: '.nvmrc' + - name: Ensure CHANGELOG.md exists + run: touch CHANGELOG.md + - name: Generate Changelog and Tag id: changelog uses: TriPSs/conventional-changelog-action@v5 @@ -37,6 +40,8 @@ jobs: preset: 'conventionalcommits' skip-ci: 'false' tag-prefix: 'v' + input-file: './CHANGELOG.md' + output-file: './CHANGELOG.md' version-file: './package.json, ./package-lock.json' pre-commit: './.github/scripts/pre-commit.js' From 019f4e3cdbb54252ff4c9db88f5403397dfd68fb Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Thu, 15 Oct 2026 13:45:55 -0300 Subject: [PATCH 2/3] ci: allow tagging without a changelog and forcing the bump type The release workflow always writes CHANGELOG.md and always infers the version bump from the commits. During initial development (0.x) no changelog is wanted, yet tags and releases should still go through the workflow instead of being made by hand. Inference is also unsafe there: the conventionalcommits preset bumps breaking changes to major even on 0.x, so a single `!` commit in the range would ship 1.0.0. Add a generate_changelog input, off by default, that sets the action's output-file to 'false' when disabled; the action still bumps, commits, tags and runs the lockfile pre-commit hook, it just writes no file. Add a release_type input that forces patch, minor or major through `npm version` plus skip-bump, and reject 'auto' while the version is 0.x. Once 1.0.0 is out, 'auto' works without further changes. Also bump softprops/action-gh-release from v1 to v3, since the v1 runtime is too old for GitHub Actions to run and the "Create GitHub Release" step can't work as is. v3 only moves the runtime to Node 24; the tag_name, body and generate_release_notes inputs used here are unchanged. A separate tag-and-push path built on `npm version` alone was considered and discarded: it would skip the lockfile pre-commit hook and duplicate what the action already does. --- .github/workflows/tag_and_release.yml | 41 +++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tag_and_release.yml b/.github/workflows/tag_and_release.yml index 2d464d3..19bd989 100644 --- a/.github/workflows/tag_and_release.yml +++ b/.github/workflows/tag_and_release.yml @@ -1,4 +1,4 @@ -name: Manual Tag & Release +name: Tag & Release (Manual Trigger) on: workflow_dispatch: @@ -8,6 +8,21 @@ on: required: true type: boolean default: false + release_type: + description: 'Version bump: auto (infer from commits) or force patch/minor/major' + required: true + type: choice + default: 'auto' + options: + - auto + - patch + - minor + - major + generate_changelog: + description: 'Generate/update CHANGELOG.md for this release?' + required: true + type: boolean + default: false jobs: release: @@ -25,9 +40,24 @@ jobs: with: node-version-file: '.nvmrc' + # The conventionalcommits preset bumps breaking changes to major even on + # 0.x, so an inferred bump could ship 1.0.0 by accident. + - name: Require an explicit bump during initial development + if: ${{ inputs.release_type == 'auto' }} + run: | + if node -p "require('./package.json').version" | grep -q '^0\.'; then + echo "::error::release_type must be patch, minor or major while the version is 0.x." + exit 1 + fi + - name: Ensure CHANGELOG.md exists + if: ${{ inputs.generate_changelog }} run: touch CHANGELOG.md + - name: Force version bump + if: ${{ inputs.release_type != 'auto' }} + run: npm version ${{ inputs.release_type }} --no-git-tag-version + - name: Generate Changelog and Tag id: changelog uses: TriPSs/conventional-changelog-action@v5 @@ -37,17 +67,18 @@ jobs: git-user-email: 'github-actions[bot]@users.noreply.github.com' git-message: 'chore(release): {version}' skip-on-empty: 'false' + skip-bump: ${{ inputs.release_type != 'auto' }} preset: 'conventionalcommits' skip-ci: 'false' tag-prefix: 'v' - input-file: './CHANGELOG.md' - output-file: './CHANGELOG.md' + input-file: ${{ inputs.generate_changelog && './CHANGELOG.md' || '' }} + output-file: ${{ inputs.generate_changelog && './CHANGELOG.md' || 'false' }} version-file: './package.json, ./package-lock.json' pre-commit: './.github/scripts/pre-commit.js' - name: Create GitHub Release - if: ${{ github.event.inputs.create_github_release == 'true' && steps.changelog.outputs.skipped == 'false' }} - uses: softprops/action-gh-release@v1 + if: ${{ inputs.create_github_release && steps.changelog.outputs.skipped == 'false' }} + uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.changelog.outputs.tag }} body: ${{ steps.changelog.outputs.clean_changelog }} From 78a48830c03fa278ba0360d63f3fce00d0a72853 Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Thu, 15 Oct 2026 13:46:00 -0300 Subject: [PATCH 3/3] ci: make npm publishing manual-only The publish workflow runs on every pushed v* tag, so pushing a tag publishes to npm right away, without a deliberate decision to release that version. Tagging and publishing should be independent steps, each triggered by hand. Drop the tag push trigger and keep workflow_dispatch only. Since a manual run can now be started from any ref, fail unless it runs from a tag matching the version in package.json, so a branch or a mismatched tag is never published. --- .github/workflows/publish.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 19f8df4..b3cae4b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,9 +2,6 @@ name: Publish to NPM on: workflow_dispatch: - push: - tags: - - 'v*' permissions: contents: read @@ -17,6 +14,14 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Require a release tag matching package.json + run: | + VERSION="v$(node -p "require('./package.json').version")" + if [ "${{ github.ref_type }}" != "tag" ] || [ "${{ github.ref_name }}" != "$VERSION" ]; then + echo "::error::Run this workflow from the release tag (expected $VERSION, got ${{ github.ref_type }} ${{ github.ref_name }})." + exit 1 + fi + - name: Setup Node.js uses: actions/setup-node@v5 with: