super-release 0.3.1 → 0.5.0

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.
package/README.md CHANGED
@@ -1,32 +1,44 @@
1
1
  # super-release
2
2
 
3
- A fast and opinionated [semantic-release](https://semantic-release.gitbook.io/semantic-release) alternative for monorepos, written in Rust.
3
+ A fast and opinionated [semantic-release](https://semantic-release.gitbook.io/semantic-release) alternative for
4
+ monorepos, written in Rust.
4
5
 
5
- Analyzes [conventional commits](https://www.conventionalcommits.org/) to determine version bumps, generate changelogs, update `package.json` files, publish to npm, and create git tags -- across all packages in a monorepo, in parallel.
6
+ Analyzes [conventional commits](https://www.conventionalcommits.org/) to determine version bumps, generate changelogs,
7
+ update `package.json` files, publish to npm, and create git tags -- across all packages in a monorepo, in parallel.
6
8
 
7
9
  ## Features
8
10
 
9
11
  - Monorepo-first: discovers all `package.json` packages and associates commits by changed files
10
- - Parallel commit analysis using rayon (configurable with `-j`)
11
12
  - Prerelease branches (`beta`, `next`, or dynamic from branch name)
12
13
  - Maintenance branches (`1.x`, `2.x`) with major-version capping
13
14
  - Changelog generation powered by [git-cliff](https://git-cliff.org/)
14
- - Plugin system: changelog, npm, git-tag (extensible)
15
+ - Auto-detects package manager (npm, yarn, pnpm)
15
16
  - Configurable tag format templates
16
- - Dependency-aware npm publish (topological order)
17
+ - Plugin system: changelog, npm, exec (extensible)
18
+ - Global file dependencies and ignore patterns
19
+ - Idempotent: safe to rerun after partial failures
17
20
  - Dry-run mode with pretty, truncated output
18
21
 
19
22
  ## Installation
20
23
 
24
+ The easiest way -- no install needed:
25
+
21
26
  ```bash
22
- cargo install --path .
27
+ npx -y super-release --dry-run
23
28
  ```
24
29
 
25
- Or build a release binary:
30
+ Or install as a dev dependency:
26
31
 
27
32
  ```bash
28
- cargo build --release
29
- # Binary at target/release/super-release
33
+ pnpm add -D super-release
34
+ ```
35
+
36
+ The npm package automatically downloads the prebuilt native binary for your platform on first run.
37
+
38
+ Alternatively, build from source:
39
+
40
+ ```bash
41
+ cargo install --path .
30
42
  ```
31
43
 
32
44
  ## Quick Start
@@ -38,6 +50,12 @@ super-release --dry-run
38
50
  # Run a release
39
51
  super-release
40
52
 
53
+ # Get the next version for a package (useful in CI scripts)
54
+ super-release --show-next-version
55
+
56
+ # Get the next version for a specific package in a monorepo
57
+ super-release --show-next-version --package @acme/core
58
+
41
59
  # Use 4 threads for commit analysis
42
60
  super-release -j 4
43
61
  ```
@@ -48,230 +66,243 @@ super-release -j 4
48
66
  Usage: super-release [OPTIONS]
49
67
 
50
68
  Options:
51
- -n, --dry-run Show what would happen without making changes
52
- -C, --path <PATH> Repository root [default: .]
53
- -c, --config <CONFIG> Path to config file [default: .release.yaml]
54
- -v, --verbose Verbose output
55
- -j, --jobs <JOBS> Parallel jobs for commit analysis [default: 50% of CPUs]
56
- -h, --help Print help
57
- -V, --version Print version
69
+ -n, --dry-run Show what would happen without making changes
70
+ -C, --path <PATH> Repository root [default: .]
71
+ -c, --config <CONFIG> Path to config file [default: .release.yaml]
72
+ --show-next-version Print the next version and exit
73
+ -p, --package <PACKAGE> Filter to a specific package (for --show-next-version)
74
+ -v, --verbose Verbose output
75
+ -j, --jobs <JOBS> Parallel jobs for commit analysis [default: 50% of CPUs]
76
+ -h, --help Print help
77
+ -V, --version Print version
78
+ ```
79
+
80
+ ### `--show-next-version`
81
+
82
+ Outputs only the next version (or the current version if no bump is needed) and exits silently. Useful for CI scripts:
83
+
84
+ ```bash
85
+ VERSION=$(super-release --show-next-version)
86
+ SUPER_RELEASE_VERSION=$VERSION cargo build --release
58
87
  ```
59
88
 
89
+ In monorepos, use `--package` to select which package: `super-release --show-next-version -p @acme/core`
90
+
60
91
  ## How It Works
61
92
 
62
- 1. **Discover packages** -- finds all directories with a `package.json`
63
- 2. **Resolve tags** -- finds the latest release tag per package (filtered by branch context)
93
+ 1. **Discover packages** -- finds all directories with a `package.json` (respects `.gitignore`)
94
+ 2. **Resolve tags** -- finds the latest release tag per package (filtered by branch context, only reachable from HEAD)
64
95
  3. **Walk commits** -- only analyzes commits since the oldest tag (not the entire history)
65
- 4. **Associate commits to packages** -- maps changed files to their owning package
66
- 5. **Calculate versions** -- uses git-cliff's conventional commit analysis to determine bump levels
67
- 6. **Run plugins** -- changelog, npm publish, git tag (in configured order)
96
+ 4. **Associate commits to packages** -- maps changed files to their owning package (respects `dependencies` and `ignore`
97
+ config)
98
+ 5. **Calculate versions** -- determines bump levels from conventional commits
99
+ 6. **Run plugins** -- changelog, npm publish, exec commands
100
+ 7. **Git finalize** -- commits modified files, creates tags, optionally pushes
68
101
 
69
102
  ## Conventional Commits
70
103
 
71
- super-release follows the [Conventional Commits](https://www.conventionalcommits.org/) specification:
72
-
73
- | Commit | Bump |
74
- |---|---|
75
- | `fix: ...` | patch |
76
- | `feat: ...` | minor |
77
- | `feat!: ...` or `BREAKING CHANGE:` in footer | major |
78
- | `perf: ...` | patch |
79
- | `chore: ...`, `docs: ...`, `ci: ...` | no release |
104
+ | Commit | Bump |
105
+ |-------------------------------------------------------|------------|
106
+ | `fix: ...` | patch |
107
+ | `feat: ...` | minor |
108
+ | `feat!: ...` or `BREAKING CHANGE:` in footer | major |
109
+ | `perf: ...` | patch |
110
+ | `chore: ...`, `docs: ...`, `ci: ...`, `refactor: ...` | no release |
80
111
 
81
112
  ## Configuration
82
113
 
83
- Create a `.release.yaml` (or `.release.yml`, `.super-release.yaml`) in your repository root. All fields are optional and have sensible defaults.
114
+ Create a `.release.yaml` (or `.release.yml`, `.super-release.yaml`) in your repository root. All fields are optional
115
+ with sensible defaults.
116
+
117
+ A [JSON Schema](schema.json) is available for editor autocompletion:
118
+
119
+ ```yaml
120
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/bowlingx/super-release/main/schema.json
121
+ ```
84
122
 
85
123
  ### Full Example
86
124
 
87
125
  ```yaml
88
- # Branch configurations
89
126
  branches:
90
- # Stable branches (simple string = stable, no prerelease)
91
127
  - main
92
128
  - master
93
-
94
- # Prerelease with a fixed channel name
95
129
  - name: beta
96
- prerelease: beta # -> 2.0.0-beta.1, 2.0.0-beta.2, ...
97
-
98
- - name: next
99
- prerelease: next # -> 2.0.0-next.1, ...
100
-
101
- # Prerelease using the branch name as the channel (for branch patterns)
130
+ prerelease: beta
102
131
  - name: "test-*"
103
- prerelease: true # branch test-foo -> 2.0.0-test-foo.1, ...
104
-
105
- # Maintenance branches (caps major version, breaking changes -> minor)
132
+ prerelease: true
106
133
  - name: "1.x"
107
- maintenance: true # -> 1.5.1, 1.6.0 (never 2.0.0)
134
+ maintenance: true
108
135
 
109
- # Tag format templates (use {version} and {name} placeholders)
110
- tag_format: "v{version}" # root package: v1.2.3
111
- tag_format_package: "{name}/v{version}" # sub-packages: @acme/core/v1.2.3
136
+ tag_format: "v{version}"
137
+ tag_format_package: "{name}/v{version}"
138
+
139
+ packages:
140
+ - "@acme/*"
112
141
 
113
- # Packages to exclude from releasing (substring match on package name)
114
142
  exclude:
115
- - my-private-pkg
143
+ - my-monorepo-root
144
+
145
+ # Files that trigger ALL packages when changed
146
+ dependencies:
147
+ - yarn.lock
148
+ - pnpm-lock.yaml
149
+
150
+ # Files to ignore -- commits touching only these won't trigger releases
151
+ ignore:
152
+ - "README.md"
153
+ - "docs/**"
154
+ - "**/*.md"
116
155
 
117
- # Plugins run in order: prepare phase first, then publish phase
118
156
  plugins:
119
157
  - name: changelog
120
158
  - name: npm
121
- - name: git-tag
159
+ options:
160
+ provenance: true
161
+ - name: exec
162
+ options:
163
+ prepare_cmd: "sed -i'' -e 's/^version = .*/version = \"{version}\"/' Cargo.toml"
164
+ files:
165
+ - Cargo.toml
166
+ - Cargo.lock
167
+
168
+ git:
169
+ commit_message: "chore(release): {releases} [skip ci]"
170
+ push: false
171
+ remote: origin
122
172
  ```
123
173
 
124
174
  ### Reference
125
175
 
126
176
  #### `branches`
127
177
 
128
- Defines which branches can produce releases and what kind.
178
+ Defines which branches can produce releases. Only configured branches are allowed -- running on an unconfigured branch
179
+ exits cleanly.
129
180
 
130
- | Form | Type | Example versions |
131
- |---|---|---|
132
- | `- main` | Stable | `1.0.0`, `1.1.0`, `2.0.0` |
133
- | `- name: beta`<br>` prerelease: beta` | Prerelease (fixed channel) | `2.0.0-beta.1`, `2.0.0-beta.2` |
134
- | `- name: "test-*"`<br>` prerelease: true` | Prerelease (branch name as channel) | `2.0.0-test-my-feature.1` |
135
- | `- name: "1.x"`<br>` maintenance: true` | Maintenance | `1.5.1`, `1.6.0` (major capped) |
181
+ | Form | Type | Example versions |
182
+ |--------------------------------------------|-------------------------------------|---------------------------------|
183
+ | `- main` | Stable | `1.0.0`, `1.1.0`, `2.0.0` |
184
+ | `- name: beta`<br>` prerelease: beta` | Prerelease (fixed channel) | `2.0.0-beta.1`, `2.0.0-beta.2` |
185
+ | `- name: "test-*"`<br>` prerelease: true` | Prerelease (branch name as channel) | `2.0.0-test-my-feature.1` |
186
+ | `- name: "1.x"`<br>` maintenance: true` | Maintenance | `1.5.1`, `1.6.0` (major capped) |
136
187
 
137
- **Tag filtering by branch**: Stable branches only see stable tags. Prerelease branches see their own channel's tags plus stable tags. This prevents a `v2.0.0-beta.1` tag from affecting version calculation on `main`.
188
+ **Tag filtering by branch**: Stable branches only see stable tags. Prerelease branches see their own channel's tags plus
189
+ stable tags. Tags on other branches that haven't been merged are ignored.
138
190
 
139
- **Prerelease behavior**: If the latest tag for a package is already on the same prerelease channel (e.g. `v2.0.0-beta.3`), the next release increments the prerelease number (`v2.0.0-beta.4`). If coming from a stable version, it computes the next stable bump and appends the channel (`v1.1.0-beta.1`).
191
+ Default: `["main", "master"]`
140
192
 
141
- **Maintenance behavior**: Breaking changes (`feat!:`) are demoted to minor bumps so the major version never increases on a maintenance branch.
193
+ #### `tag_format` / `tag_format_package`
142
194
 
143
- Default: `["main", "master"]`
195
+ Templates for git tag names. Placeholders: `{version}`, `{name}`.
144
196
 
145
- #### `tag_format`
197
+ ```yaml
198
+ tag_format: "v{version}" # root: v1.2.3 (default)
199
+ tag_format_package: "{name}/v{version}" # sub-packages: @acme/core/v1.2.3 (default)
200
+ tag_format_package: "{name}@{version}" # semantic-release compat
201
+ ```
146
202
 
147
- Template for root package tags. Placeholders:
148
- - `{version}` -- the semver version (e.g. `1.2.3`, `2.0.0-beta.1`)
149
- - `{name}` -- the package name from `package.json`
203
+ #### `dependencies`
150
204
 
151
- Default: `"v{version}"`
205
+ Global file dependency patterns. When a commit changes any matching file, ALL packages are considered affected.
152
206
 
153
- Examples:
154
207
  ```yaml
155
- tag_format: "v{version}" # -> v1.2.3
156
- tag_format: "release-{version}" # -> release-1.2.3
157
- tag_format: "{name}-v{version}" # -> my-app-v1.2.3
208
+ dependencies:
209
+ - yarn.lock
210
+ - pnpm-lock.yaml
211
+ - package.json
212
+ - ".github/**"
158
213
  ```
159
214
 
160
- #### `tag_format_package`
161
-
162
- Template for sub-package tags in a monorepo.
215
+ #### `ignore`
163
216
 
164
- Default: `"{name}/v{version}"`
217
+ Glob patterns for files to ignore. Commits that only touch ignored files will not trigger a release. If a commit touches
218
+ both ignored and non-ignored files, only the non-ignored files determine which packages are affected.
165
219
 
166
- Examples:
167
220
  ```yaml
168
- tag_format_package: "{name}/v{version}" # -> @acme/core/v1.2.3
169
- tag_format_package: "{name}@{version}" # -> @acme/core@1.2.3 (semantic-release compat)
221
+ ignore:
222
+ - "README.md"
223
+ - "docs/**"
224
+ - "**/*.md"
225
+ - ".prettierrc"
170
226
  ```
171
227
 
172
- To migrate from semantic-release's tag format, set `tag_format_package: "{name}@{version}"`.
228
+ #### `packages` / `exclude`
173
229
 
174
- #### `plugins`
230
+ Filter which packages are released. `packages` is an allow-list (glob patterns), `exclude` is a deny-list.
231
+
232
+ ```yaml
233
+ packages:
234
+ - "@acme/*"
235
+ exclude:
236
+ - my-monorepo-root
237
+ ```
175
238
 
176
- Ordered list of plugins to execute. Each plugin runs its `prepare` phase, then its `publish` phase. Each plugin accepts an `options` object for customization.
239
+ #### `plugins`
177
240
 
178
- Default: `[changelog, npm, git-commit, git-tag]`
241
+ Ordered list of plugins. Each plugin has a `name`, optional `packages` filter (glob), and `options`.
179
242
 
180
243
  ```yaml
181
244
  plugins:
182
245
  - name: changelog
183
246
  options:
184
- filename: CHANGELOG.md # output file per package (default: CHANGELOG.md)
185
- preview_lines: 20 # max lines shown in dry-run (default: 20)
247
+ filename: CHANGELOG.md
248
+ preview_lines: 20
186
249
 
187
250
  - name: npm
251
+ packages: [ "@acme/*" ] # only publish @acme packages
188
252
  options:
189
- access: public # npm access level (default: "public")
190
- registry: https://registry.npmjs.org # custom registry URL
191
- tag: next # dist-tag override (default: auto from prerelease channel)
192
- publish_args: # extra args passed to the publish command
193
- - "--otp=123456"
194
- package_manager: yarn # force specific PM (default: auto-detect)
253
+ access: public
254
+ provenance: true
255
+ registry: https://registry.npmjs.org
256
+ tag: next # dist-tag (default: auto from prerelease channel)
257
+ publish_args: [ "--otp=123456" ]
258
+ package_manager: yarn # force specific PM (default: auto-detect)
195
259
 
196
260
  - name: exec
261
+ packages: [ "my-rust-lib" ]
197
262
  options:
198
- # Run for all packages (omit `packages` to run for all)
199
- prepare_cmd: "echo Releasing {name} v{version}"
200
- # Or filter to specific packages:
201
- # packages: ["@acme/core"]
202
-
203
- # Multiple exec blocks for different packages:
204
- # - name: exec
205
- # options:
206
- # packages: ["my-rust-project"]
207
- # prepare_cmd: "sed -i'' -e 's/^version = .*/version = \"{version}\"/' Cargo.toml"
208
- # - name: exec
209
- # options:
210
- # packages: ["@acme/*"]
211
- # publish_cmd: "deploy.sh {name} {version}"
212
-
213
- - name: git-commit
214
- options:
215
- # Commit message template. Placeholders:
216
- # {releases} - comma-separated list: "@acme/core@1.1.0, @acme/utils@1.0.1"
217
- # {summary} - one per line: " - @acme/core 1.0.0 -> 1.1.0"
218
- # {count} - number of packages released
219
- message: "chore(release): {releases} [skip ci]"
220
- push: false # push after commit (default: false)
221
- remote: origin # git remote (default: "origin")
222
- paths: # paths to stage (default: ["."])
223
- - "."
224
-
225
- - name: git-tag
226
- options:
227
- push: false # push tags to remote after creation (default: false)
228
- remote: origin # git remote to push to (default: "origin")
263
+ prepare_cmd: "sed -i'' -e 's/^version = .*/version = \"{version}\"/' Cargo.toml"
264
+ publish_cmd: "cargo publish"
265
+ files: [ Cargo.toml, Cargo.lock ] # include in git commit
229
266
  ```
230
267
 
231
- | Plugin | Prepare | Publish |
232
- |---|---|---|
233
- | `changelog` | Generates/updates changelog per package (parallel) | -- |
234
- | `npm` | Updates `package.json` versions (auto-detects npm/yarn/pnpm) | Publishes packages (parallel within dependency levels) |
235
- | `exec` | Runs custom shell command per package | Runs custom shell command per package |
236
- | `git-commit` | -- | Stages changed files, commits with release message, optionally pushes |
237
- | `git-tag` | -- | Creates annotated git tags, optionally pushes |
238
-
239
- The default plugin order ensures: changelogs and version bumps are written first, then committed, then tagged.
268
+ | Plugin | Prepare | Publish |
269
+ |-------------|--------------------------------------------------------------|--------------------------------------------------------|
270
+ | `changelog` | Generates/updates changelog per package (parallel) | -- |
271
+ | `npm` | Updates `package.json` versions (auto-detects npm/yarn/pnpm) | Publishes packages (parallel within dependency levels) |
272
+ | `exec` | Runs custom shell command per package | Runs custom shell command per package |
240
273
 
241
- #### `packages`
274
+ Plugins return the files they modified. The core git step stages exactly those files for the commit -- no `git add .`.
242
275
 
243
- Optional list of glob patterns to include. When set, only packages whose name matches at least one pattern are released. Supports `*`, `?`, `[...]`, and `{a,b}` alternation.
276
+ Default: `[changelog, npm]`
244
277
 
245
- ```yaml
246
- # Only release packages in the @acme scope
247
- packages:
248
- - "@acme/*"
278
+ #### `git`
249
279
 
250
- # Release specific packages
251
- packages:
252
- - "@acme/core"
253
- - "@acme/utils"
280
+ Core git behavior after all plugins run. Not a plugin -- always runs.
254
281
 
255
- # Multiple scopes
256
- packages:
257
- - "{@acme/*,@tools/*}"
282
+ ```yaml
283
+ git:
284
+ commit_message: "chore(release): {releases} [skip ci]"
285
+ push: false # push commit + tags to remote
286
+ remote: origin
258
287
  ```
259
288
 
260
- Default: all discovered packages.
289
+ Commit message placeholders:
261
290
 
262
- #### `exclude`
291
+ - `{releases}` -- comma-separated: `@acme/core@1.1.0, @acme/utils@1.0.1`
292
+ - `{summary}` -- one per line: ` - @acme/core 1.0.0 -> 1.1.0`
293
+ - `{count}` -- number of packages released
263
294
 
264
- List of glob patterns to exclude from releasing. Applied after `packages`.
295
+ The git step:
265
296
 
266
- ```yaml
267
- exclude:
268
- - my-monorepo-root
269
- - "@acme/internal-*"
270
- ```
297
+ 1. Stages only files reported by plugins
298
+ 2. Also stages any tracked-file changes (for exec commands that can't report files)
299
+ 3. Commits (or skips if nothing changed)
300
+ 4. Creates annotated tags for each release
301
+ 5. Pushes commit + tags if `push: true`
271
302
 
272
- ## Monorepo Structure
303
+ Tags are idempotent -- existing tags are skipped. Already-published npm packages are detected and skipped on rerun.
273
304
 
274
- super-release discovers packages by finding `package.json` files recursively (skipping `node_modules`, `.git`, `dist`, `build`). Each commit is associated to a package based on which files it changed.
305
+ ## Monorepo Structure
275
306
 
276
307
  ```
277
308
  my-monorepo/
@@ -286,29 +317,23 @@ my-monorepo/
286
317
  src/
287
318
  ```
288
319
 
289
- **Dependency-aware publishing**: The npm plugin builds a dependency graph from `dependencies`, `devDependencies`, and `peerDependencies`. Packages are published in topological order (dependencies before dependents), and interdependency version ranges are updated automatically (preserving `^`/`~` prefixes).
320
+ Packages are discovered by finding `package.json` files recursively (respects `.gitignore`). Each commit is associated
321
+ to a package based on which files it changed.
290
322
 
291
323
  ## Performance
292
324
 
293
- super-release is designed to be fast:
294
-
295
- - **Parallel diff computation**: commit diffs are computed across multiple threads (thread-local git repo handles)
296
- - **Tag-bounded history walk**: only walks commits since the oldest package tag, not the entire history
297
- - **Single-pass commit collection**: commits are fetched once and partitioned per package
298
- - **Precomputed file mapping**: file-to-package association is computed once, not per-package
299
-
300
- Benchmark (2001 commits, 8 packages, Apple Silicon):
301
- | Scenario | Time |
302
- |---|---|
303
- | All commits since initial tag | 0.11s |
304
- | 100 commits since recent tags | 0.035s |
325
+ - **Parallel diff computation**: commit diffs computed across multiple threads
326
+ - **Tag-bounded history walk**: only walks commits since the oldest package tag
327
+ - **Single-pass commit collection**: commits fetched once, partitioned per package
328
+ - **Reachable-only tags**: single revwalk to check tag reachability, stops early
305
329
 
306
330
  ## Acknowledgements
307
331
 
308
332
  super-release is inspired by and builds on the ideas of:
309
333
 
310
- - **[semantic-release](https://github.com/semantic-release/semantic-release)** the original automated release tool that pioneered conventional-commit-based versioning. super-release follows the same philosophy but reimagines it in Rust with first-class monorepo support.
311
- - **[git-cliff](https://github.com/orhun/git-cliff)** — powers the changelog generation via `git-cliff-core`. An excellent standalone changelog generator with rich templating support.
334
+ - **[semantic-release](https://github.com/semantic-release/semantic-release)** -- the original automated release tool
335
+ that pioneered conventional-commit-based versioning
336
+ - **[git-cliff](https://github.com/orhun/git-cliff)** -- powers changelog generation via `git-cliff-core`
312
337
 
313
338
  ## License
314
339
 
@@ -26,7 +26,10 @@ if (!existsSync(binPath)) {
26
26
  }
27
27
 
28
28
  try {
29
- execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
29
+ execFileSync(binPath, process.argv.slice(2), {
30
+ stdio: "inherit",
31
+ env: { ...process.env, SUPER_RELEASE_VERSION: pkg.version },
32
+ });
30
33
  process.exit(0);
31
34
  } catch (err) {
32
35
  process.exit(err.status ?? 1);
package/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "url": "git+https://github.com/BowlingX/super-release.git"
29
29
  },
30
30
  "type": "module",
31
- "version": "0.3.1",
31
+ "version": "0.5.0",
32
32
  "scripts": {
33
33
  "super-release": "node npm/bin/super-release.js"
34
34
  }