release-suite 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.editorconfig +9 -0
  2. package/.gitattributes +11 -0
  3. package/.github/workflows/create-and-publish.yml +251 -0
  4. package/.prettierrc +5 -5
  5. package/CHANGELOG.md +27 -32
  6. package/LICENSE +21 -21
  7. package/README.md +60 -47
  8. package/bin/changelog.js +88 -0
  9. package/bin/dry-run.js +54 -0
  10. package/bin/release-notes.js +79 -0
  11. package/bin/tag.js +73 -0
  12. package/bin/{compute-version.js → version.js} +89 -82
  13. package/docs/api.md +25 -11
  14. package/docs/changelog.md +266 -0
  15. package/docs/ci.md +162 -112
  16. package/docs/config.md +139 -0
  17. package/docs/dry-run.md +229 -0
  18. package/docs/release-notes.md +149 -0
  19. package/docs/release-process.md +167 -0
  20. package/docs/tag.md +193 -0
  21. package/docs/{compute-version.md → version.md} +37 -67
  22. package/eslint.config.js +89 -89
  23. package/lib/changelog/generate.js +145 -0
  24. package/lib/{changelog.js → changelog/helpers.js} +207 -307
  25. package/lib/changelog/rebuild.js +85 -0
  26. package/lib/commits.js +241 -231
  27. package/lib/config.js +51 -0
  28. package/lib/constants.js +28 -28
  29. package/lib/dry-run.js +79 -0
  30. package/lib/git.js +94 -77
  31. package/lib/github.js +112 -112
  32. package/lib/{release-notes.js → release-notes/generate.js} +151 -142
  33. package/lib/tag/create.js +91 -0
  34. package/lib/utils.js +94 -45
  35. package/lib/{compute-version.js → version/compute.js} +96 -89
  36. package/lib/versioning.js +81 -81
  37. package/package.json +60 -62
  38. package/release.config.js +10 -0
  39. package/.github/workflows/create-release-pr.yml +0 -101
  40. package/.github/workflows/publish-on-merge.yml +0 -95
  41. package/bin/create-tag.js +0 -110
  42. package/bin/generate-changelog.js +0 -66
  43. package/bin/generate-release-notes.js +0 -70
  44. package/bin/preview.js +0 -75
  45. package/docs/generate-changelog.md +0 -107
  46. package/docs/generate-release-notes.md +0 -111
package/.editorconfig ADDED
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ charset = utf-8
7
+
8
+ [*.md]
9
+ trim_trailing_whitespace = false
package/.gitattributes ADDED
@@ -0,0 +1,11 @@
1
+ # Force LF in executable files (CLI)
2
+ bin/* text eol=lf
3
+
4
+ # JS / ESM code
5
+ *.js text eol=lf
6
+ *.mjs text eol=lf
7
+
8
+ # Configurations
9
+ *.json text eol=lf
10
+ *.yml text eol=lf
11
+ *.yaml text eol=lf
@@ -0,0 +1,251 @@
1
+ name: Create & Publish Release
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ id-token: write # 🔐 REQUIRED for OIDC
11
+ contents: write
12
+ pull-requests: write
13
+ packages: write
14
+
15
+ concurrency:
16
+ group: release-main
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ # ------------------------------------------------------------
21
+ # JOB 1 — CREATE RELEASE PR + TAG DECISION
22
+ # ------------------------------------------------------------
23
+ create:
24
+ runs-on: ubuntu-latest
25
+ timeout-minutes: 15
26
+ env:
27
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28
+
29
+ # Job-level outputs = contract with publish job
30
+ outputs:
31
+ should_publish: ${{ steps.compute.outputs.should_publish }}
32
+ version: ${{ steps.compute.outputs.version }}
33
+ tagPrefix: ${{ steps.compute.outputs.tagPrefix }}
34
+ tag: ${{ steps.tag.outputs.tag }}
35
+
36
+ # Only run when:
37
+ # - manual dispatch OR
38
+ # - PR merged into main AND it's NOT a release PR
39
+ if: >
40
+ github.event_name == 'workflow_dispatch' ||
41
+ (
42
+ github.event.pull_request.merged == true &&
43
+ !contains(join(github.event.pull_request.labels.*.name, ','), 'release')
44
+ )
45
+
46
+ steps:
47
+ - name: Checkout
48
+ uses: actions/checkout@v4
49
+ with:
50
+ fetch-depth: 0
51
+ persist-credentials: true
52
+
53
+ - name: Setup Node
54
+ uses: actions/setup-node@v4
55
+ with:
56
+ node-version: 24
57
+
58
+ - name: Install dependencies
59
+ run: npm ci
60
+
61
+ - name: Install release-suite (self)
62
+ run: npm install .
63
+
64
+ # --------------------------------------------------------
65
+ # Decide if a release should happen
66
+ # --------------------------------------------------------
67
+ - name: Compute next version
68
+ id: compute
69
+ run: |
70
+ set +e
71
+ RESULT=$(node bin/version.js compute)
72
+ STATUS=$?
73
+ VERSION=$(echo "$RESULT" | jq -r '.nextVersion // empty')
74
+ TAG_PREFIX=$(echo "$RESULT" | jq -r '.tagPrefix // ""')
75
+
76
+ echo "$RESULT"
77
+
78
+ if [ "$STATUS" -ne 0 ] || [ -z "$VERSION" ]; then
79
+ echo "should_publish=false" >> $GITHUB_OUTPUT
80
+ else
81
+ echo "should_publish=true" >> $GITHUB_OUTPUT
82
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
83
+ echo "tagPrefix=$TAG_PREFIX" >> $GITHUB_OUTPUT
84
+ fi
85
+
86
+ # --------------------------------------------------------
87
+ # Prepare release artifacts
88
+ # --------------------------------------------------------
89
+ - name: Bump package.json
90
+ if: steps.compute.outputs.should_publish == 'true'
91
+ run: npm version ${{ steps.compute.outputs.version }} --no-git-tag-version
92
+
93
+ - name: Sync package-lock.json
94
+ if: steps.compute.outputs.should_publish == 'true'
95
+ run: npm install --package-lock-only
96
+
97
+ - name: Build
98
+ run: npm run build --if-present
99
+
100
+ - name: Generate changelog
101
+ if: steps.compute.outputs.should_publish == 'true'
102
+ run: node bin/changelog.js generate
103
+
104
+ # --------------------------------------------------------
105
+ # Stage files (dist is optional)
106
+ # --------------------------------------------------------
107
+ - name: Stage release files
108
+ if: steps.compute.outputs.should_publish == 'true'
109
+ run: |
110
+ git add package.json package-lock.json CHANGELOG.md
111
+ [ -d dist ] && git add dist || true
112
+ git status --short
113
+
114
+ # --------------------------------------------------------
115
+ # Create Release PR
116
+ # --------------------------------------------------------
117
+ - name: Create Release PR
118
+ id: pr
119
+ if: steps.compute.outputs.should_publish == 'true'
120
+ uses: peter-evans/create-pull-request@v6
121
+ with:
122
+ token: ${{ secrets.GITHUB_TOKEN }}
123
+ branch: release/${{ steps.compute.outputs.tagPrefix }}${{ steps.compute.outputs.version }}
124
+ title: ":bricks: chore(release): ${{ steps.compute.outputs.tagPrefix }}${{ steps.compute.outputs.version }} [skip ci]"
125
+ commit-message: ":bricks: chore(release): prepare version ${{ steps.compute.outputs.tagPrefix }}${{ steps.compute.outputs.version }} [skip ci]"
126
+ labels: release
127
+ delete-branch: true
128
+ assignees: ${{ github.event.pull_request.merged_by.login }}
129
+ body: |
130
+ Automated release PR.
131
+
132
+ - Version bump: ${{ steps.compute.outputs.tagPrefix }}${{ steps.compute.outputs.version }}
133
+ - Updated changelog
134
+ - Build artifacts included if present
135
+
136
+ Merging this PR will trigger publish automatically.
137
+
138
+ # --------------------------------------------------------
139
+ # Merge Release PR (best-effort)
140
+ # --------------------------------------------------------
141
+ - name: Merge Release PR
142
+ if: steps.pr.outputs.pull-request-number != ''
143
+ run: |
144
+ PR=${{ steps.pr.outputs.pull-request-number }}
145
+ echo "Merging PR #$PR"
146
+ if ! gh pr merge "$PR" --squash --admin; then
147
+ echo "Immediate merge failed, attempting to enable auto-merge"
148
+ gh pr merge "$PR" --auto --squash
149
+ fi
150
+
151
+ # --------------------------------------------------------
152
+ # Validate merge (soft)
153
+ # --------------------------------------------------------
154
+ - name: Validate release commit
155
+ id: validate
156
+ run: |
157
+ git fetch origin main --depth=1
158
+ git checkout main
159
+ git reset --hard origin/main
160
+
161
+ if git log -1 --pretty=%s | grep -q "^:bricks: chore(release):"; then
162
+ echo "ok=true" >> $GITHUB_OUTPUT
163
+ else
164
+ echo "ok=false" >> $GITHUB_OUTPUT
165
+ fi
166
+
167
+ # --------------------------------------------------------
168
+ # Create Git tag (soft)
169
+ # --------------------------------------------------------
170
+ - name: Configure Git identity (for tags)
171
+ run: |
172
+ git config user.name "github-actions[bot]"
173
+ git config user.email "github-actions[bot]@users.noreply.github.com"
174
+
175
+ - name: Create tag
176
+ id: tag
177
+ if: steps.validate.outputs.ok == 'true'
178
+ run: |
179
+ set +e
180
+ RESULT=$(node bin/tag.js create)
181
+ STATUS=$?
182
+ TAG=$(echo "$RESULT" | jq -r '.tag // empty')
183
+
184
+ echo "$RESULT"
185
+
186
+ if [ "$STATUS" -eq 0 ] && [ -n "$TAG" ]; then
187
+ echo "tag=$TAG" >> $GITHUB_OUTPUT
188
+ else
189
+ echo "Tag not created (soft-fail)"
190
+ fi
191
+
192
+ # ------------------------------------------------------------
193
+ # JOB 2 — PUBLISH (only if tag exists)
194
+ # ------------------------------------------------------------
195
+ publish:
196
+ needs: create
197
+ runs-on: ubuntu-latest
198
+ timeout-minutes: 15
199
+ env:
200
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
201
+
202
+ if: needs.create.outputs.tag != ''
203
+
204
+ steps:
205
+ - name: Checkout
206
+ uses: actions/checkout@v4
207
+ with:
208
+ fetch-depth: 0
209
+ persist-credentials: true
210
+
211
+ - name: Setup Node (npm OIDC)
212
+ uses: actions/setup-node@v4
213
+ with:
214
+ node-version: 24
215
+ registry-url: https://registry.npmjs.org
216
+ always-auth: true
217
+
218
+ - name: Install dependencies
219
+ run: npm ci
220
+
221
+ - name: Install release-suite (self)
222
+ run: npm install .
223
+
224
+ # --------------------------------------------------------
225
+ # Publish to npm using Trusted Publishing (OIDC)
226
+ # --------------------------------------------------------
227
+ - name: Publish to npm (Trusted Publishing)
228
+ run: npm publish
229
+
230
+ # --------------------------------------------------------
231
+ # Generate release notes for GitHub Release
232
+ # --------------------------------------------------------
233
+ - name: Generate GitHub Release Notes
234
+ run: node bin/release-notes.js generate
235
+
236
+ # --------------------------------------------------------
237
+ # Create GitHub Release with notes and attach built assets
238
+ # --------------------------------------------------------
239
+ - name: Create GitHub Release
240
+ env:
241
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
242
+ run: |
243
+ ASSETS=()
244
+ if [ -d dist ]; then
245
+ ASSETS=(dist/**)
246
+ fi
247
+
248
+ gh release create "${{ needs.create.outputs.tag }}" \
249
+ --title "${{ needs.create.outputs.tag }}" \
250
+ --notes-file RELEASE_NOTES.md \
251
+ "${ASSETS[@]}"
package/.prettierrc CHANGED
@@ -1,6 +1,6 @@
1
- {
2
- "tabWidth": 2,
3
- "useTabs": false,
4
- "printWidth": 100,
5
- "trailingComma": "none"
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "printWidth": 100,
5
+ "trailingComma": "none"
6
6
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ All notable changes to this project will be documented in this file.
4
+ The format follows the conventions of [Conventional Commits](https://www.conventionalcommits.org) and semantic versioning (SemVer).
5
+
6
+ ## 3.0.1
7
+
8
+ ### 🐛 Fixes
9
+ - Enforce LF line endings (#24)
10
+
11
+ ### 🤖 CI
12
+ - Add Git identity before creating the tag (#23)
13
+
14
+ ## 3.0.0
15
+
16
+ ### 💥 Breaking Changes
17
+ - Introduce action-based CLI and reorganize release architecture (#21)
18
+
19
+ ### 📚 Documentation
20
+ - Update README.md and ci.md with GitHub CLI usage (#20)
21
+
22
+ ### 🤖 CI
23
+ - Simplify GitHub CLI usage and make authentication explicit (#19)
24
+ - Unify release automation into a single resilient workflow (#18)
25
+
3
26
  ## 2.0.0
4
27
 
5
28
  ### 💥 Breaking Changes
@@ -11,52 +34,24 @@
11
34
  ## 1.0.1
12
35
 
13
36
  ### 🐛 Fixes
14
-
15
37
  - Harden changelog generation for squash commits (#13)
16
38
 
17
- ### 📚 Docs
18
-
19
- - Update ci/cd examples with trigger adjustment
20
-
21
- ### 🔁 CI
22
-
23
- - Adjust trigger in workflow
39
+ ### 🤖 CI
40
+ - Harden workflow triggers and merge conditions (#12)
24
41
 
25
42
  ## 1.0.0
26
43
 
27
44
  ### 💥 Breaking Changes
28
-
29
45
  - Harden computeVersion contract and CLI behavior (#4)
30
46
 
31
47
  ### 🐛 Fixes
32
-
33
48
  - Normalize path resolution for all CLI scripts (#2)
34
49
 
35
- ### 🔧 Chore
36
-
37
- -
38
- -
39
- -
40
- -
41
- -
42
- -
43
- -
44
- -
45
- -
46
- -
47
- -
48
- -
49
- -
50
- -
51
- -
52
-
53
- ### 🔁 CI
54
-
50
+ ### 🤖 CI
55
51
  - Harden local script execution and dist resolution (#10)
56
52
  - Binary resolution (#3)
57
53
 
58
54
  ## 0.1.0
59
55
 
60
- ### 🔧 Chore
61
-
56
+ ### 📦 Other
62
57
  - Init: project created
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Jonas Souza
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jonas Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -9,8 +9,8 @@ Semantic versioning tools for Git-based projects, providing automated version co
9
9
  - Automatic version bump based on commit messages
10
10
  - Conventional commit parsing (custom prefixes supported)
11
11
  - Auto-generated `CHANGELOG.md`
12
- - Auto-generated `RELEASE_NOTES.md` using GitHub CLI (gh)
13
- - Local preview mode (`CHANGELOG.preview.md`, `RELEASE_NOTES.preview.md`)
12
+ - Auto-generated `RELEASE_NOTES.md` using GitHub API
13
+ - Local dry-run mode (`CHANGELOG.dry-run.md`, `RELEASE_NOTES.dry-run.md`)
14
14
  - CI/CD ready for GitHub Actions
15
15
  - No commit rules enforced on the main project
16
16
  - Trusted Publishing (OIDC) — no npm tokens required
@@ -26,78 +26,94 @@ Add to your project's `package.json`:
26
26
  ```json
27
27
  {
28
28
  "scripts": {
29
- "preview": "rs-preview create",
30
- "preview:clean": "rs-preview remove",
31
- "compute-version": "rs-compute-version",
32
- "compute-version:ci": "rs-compute-version --ci --json",
33
- "compute-version:json": "rs-compute-version --json",
34
- "changelog": "rs-generate-changelog",
35
- "release-notes": "rs-generate-release-notes"
29
+ "dry-run": "rs-dry-run create",
30
+ "dry-run:clean": "rs-dry-run remove",
31
+ "version:compute": "rs-version compute",
32
+ "changelog": "rs-changelog generate",
33
+ "release-notes": "rs-release-notes generate",
34
+ "tag:create": "rs-tag create"
36
35
  }
37
36
  }
38
37
  ```
39
38
 
40
- Generate preview files without touching your real changelog:
39
+ Generate dry-run files without touching your real changelog:
41
40
 
42
41
  ```bash
43
- npm run preview
42
+ npm run dry-run
44
43
  ```
45
44
 
46
- Remove previews:
45
+ Remove dry-run files:
47
46
 
48
47
  ```bash
49
- npm run preview:clear
48
+ npm run dry-run:clear
50
49
  ```
51
50
 
51
+ ## ⚙️ Configuration
52
+
53
+ Release Suite can be configured using a `release.config.js` file.
54
+
55
+ This file controls:
56
+
57
+ - Git tag prefix (`v1.2.3` vs `1.2.3`)
58
+ - Emoji usage in changelog rendering
59
+
60
+ See [`docs/config.md`](docs/config.md) for full documentation.
61
+
62
+ ---
63
+
52
64
  ## 🖥️ CLI Commands
53
65
 
54
- | Command | Description |
55
- | --------------------------- | --------------------------------------------------------- |
56
- | `rs-compute-version` | Computes next semantic version based on git commits |
57
- | `rs-generate-changelog` | Generates `CHANGELOG.md` |
58
- | `rs-generate-release-notes` | Generates `RELEASE_NOTES.md` using GitHub PRs |
59
- | `rs-preview` | Generates preview changelog & release notes |
60
- | `rs-create-tag` | Create and push a git tag based on `package.json` version |
66
+ | Command | Description |
67
+ | --------------------------- | ---------------------------------------------------------- |
68
+ | `rs-version compute` | Computes next semantic version based on git commits |
69
+ | `rs-changelog generate` | Generates a new changelog entry for the next release |
70
+ | `rs-changelog rebuild` ⚠️ | Fully rebuilds CHANGELOG.md from git history (Danger Zone) |
71
+ | `rs-release-notes generate` | Generates RELEASE_NOTES.md |
72
+ | `rs-dry-run` | Generates dry-run changelog & release notes |
73
+ | `rs-tag create` | Creates and pushes a git tag |
61
74
 
62
75
  Each command follows a strict and predictable CLI contract (exit codes, stdout, JSON mode).
63
76
 
64
77
  > 💡 **Note about execution**
65
78
  >
79
+ > ⚠️ `rs-changelog rebuild` is a destructive operation.
80
+ > Always use `--dry-run` first.
81
+ >
66
82
  > - When using these commands via `npm run`, they can be referenced directly (`rs-*`).
67
83
  > - In CI/CD environments (e.g. GitHub Actions), always invoke them using `npx`
68
- > (e.g. `npx rs-generate-changelog`) to ensure proper binary resolution.
84
+ > (e.g. `npx rs-changelog generate`) to ensure proper binary resolution.
69
85
 
70
86
  ## 🔁 Release Flow
71
87
 
72
- This project follows a **two-step release strategy** designed for safety,
73
- automation and reusability.
88
+ This project uses an automated, PR-based release strategy
89
+ designed for safety and traceability.
74
90
 
75
- ### 1️⃣ Prepare Release (Create Release PR)
91
+ See [Release Process](docs/release-process.md) for details.
92
+
93
+ ### 1️⃣ Create Release PR
76
94
 
77
95
  Triggered when:
78
96
 
79
97
  - A PR is merged into `main`
98
+ - Or the workflow is manually dispatched
80
99
 
81
100
  Actions:
82
101
 
83
102
  - Computes next semantic version
84
- - Updates `package.json`
85
- - Generates `CHANGELOG.md`
103
+ - Updates `package.json` and `CHANGELOG.md`
86
104
  - Builds the project (if applicable)
87
- - Opens a **Release PR** (`release/x.y.z`)
105
+ - Opens and auto-merges a Release PR
106
+ - Creates a Git tag
88
107
 
89
108
  ### 2️⃣ Publish Release
90
109
 
91
- Triggered when:
92
-
93
- - A Release PR (`release/x.y.z`) with `release` label is merged into `main`
110
+ Triggered automatically after the Release PR merge (only if tag exists).
94
111
 
95
112
  Actions:
96
113
 
97
- - Creates a Git tag
98
114
  - Publishes to npm using **Trusted Publishing (OIDC)**
99
115
  - Generates GitHub Release Notes
100
- - Uploads build artifacts (`dist/**`)
116
+ - Uploads build artifacts (`dist/**` if present)
101
117
 
102
118
  ---
103
119
 
@@ -105,10 +121,13 @@ Actions:
105
121
 
106
122
  ```mermaid
107
123
  flowchart TD
108
- A[Feature / Fix PR] -->|Merge| B[main]
109
- B -->|create-release-pr.yml| C[Create Release PR]
110
- C -->|Manual Review & Merge| D[pull_request.closed]
111
- D -->|publish-on-merge.yml| E[Publish Release]
124
+ A[Feature / Fix PR] -->|merge| B[main]
125
+ B -->|workflow trigger| C[Compute Version]
126
+ C -->|release needed| D[Create Release PR]
127
+ D -->|auto-merge| E[main updated]
128
+ E -->|create tag| F[Git Tag]
129
+ F -->|publish| G[npm Registry]
130
+ F -->|release| H[GitHub Release]
112
131
  ```
113
132
 
114
133
  ✔️ Fully automated releases
@@ -123,12 +142,6 @@ flowchart TD
123
142
 
124
143
  This project is designed to be used in automated pipelines.
125
144
 
126
- Typical flow:
127
-
128
- 1. Create a Release PR (compute version, changelog, build)
129
- 2. Review and merge the Release PR into `main`
130
- 3. Publish the release (tag, npm, GitHub Release)
131
-
132
145
  👉 See full examples in [`docs/ci.md`](./docs/ci.md)
133
146
 
134
147
  ## 📦 Publishing to npm (Trusted Publishing)
@@ -154,11 +167,11 @@ are **not available via npm or npx**, since they are not installed as a dependen
154
167
  In this case, run the scripts directly with Node.js:
155
168
 
156
169
  ```bash
157
- node bin/compute-version.js
158
- node bin/generate-changelog.js
159
- node bin/generate-release-notes.js
160
- node bin/preview.js create
161
- node bin/create-tag.js
170
+ node bin/version.js compute
171
+ node bin/changelog.js generate
172
+ node bin/release-notes.js generate
173
+ node bin/dry-run.js create
174
+ node bin/tag.js create
162
175
  ```
163
176
 
164
177
  To test the CLI as a real consumer, you can use:
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { generateChangelog } from "../lib/changelog/generate.js";
4
+ import { rebuildChangelog } from "../lib/changelog/rebuild.js";
5
+ import { parseFlags } from "../lib/utils.js";
6
+
7
+ /* ===========================
8
+ * CLI
9
+ * =========================== */
10
+
11
+ /**
12
+ * Main CLI entrypoint for changelog (generate/rebuild).
13
+ *
14
+ * Behavior:
15
+ * - Calls generateChangelog({ dryRun: flags.dryRun }).
16
+ * - Calls rebuildChangelog({ dryRun: flags.dryRun }).
17
+ *
18
+ * Exit codes (contract):
19
+ * - 0 -> changelog generated/rebuilt
20
+ * - 10 -> no release/no-tags
21
+ * - 2 -> no-commits/no-valid-commits
22
+ * - 11 -> already exists
23
+ * - 1 -> unexpected error
24
+ */
25
+ function main() {
26
+ const action = process.argv[2];
27
+ const flags = parseFlags(process.argv.slice(3), {
28
+ dryRun: "--dry-run",
29
+ });
30
+
31
+ if (!["generate", "rebuild"].includes(action)) {
32
+ console.error(
33
+ JSON.stringify(
34
+ {
35
+ "error": "invalid-usage",
36
+ "message": "Invalid action. Usage: npx rs-changelog [generate|rebuild]"
37
+ },
38
+ null,
39
+ 2
40
+ )
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ try {
46
+
47
+ let result = null;
48
+ if (action === "generate") {
49
+ result = generateChangelog({ dryRun: flags.dryRun });
50
+ }
51
+ else if (action === "rebuild") {
52
+ result = rebuildChangelog({ dryRun: flags.dryRun });
53
+ }
54
+
55
+ console.log(JSON.stringify(result, null, 2));
56
+
57
+ if (action === "generate") {
58
+ if (result.generated) process.exit(0);
59
+ if (result.reason === "no-release") process.exit(10);
60
+ if (result.reason === "no-commits") process.exit(2);
61
+ if (result.reason === "already-exists") process.exit(11);
62
+ }
63
+ else if (action === "rebuild") {
64
+ if (result.rebuilt) process.exit(0);
65
+ if (result.reason === "no-tags") process.exit(10);
66
+ if (result.reason === "no-valid-commits") process.exit(2);
67
+ }
68
+
69
+ process.exit(1);
70
+ } catch (err) {
71
+ console.error(
72
+ JSON.stringify(
73
+ {
74
+ error: "unexpected-error",
75
+ message: err.message || String(err),
76
+ },
77
+ null,
78
+ 2
79
+ )
80
+ );
81
+ process.exit(1);
82
+ }
83
+ }
84
+
85
+ const __filename = fileURLToPath(import.meta.url);
86
+ if (process.argv[1] === __filename) {
87
+ main();
88
+ }