super-release 0.4.0 → 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.
Files changed (2) hide show
  1. package/README.md +152 -162
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,10 @@
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
 
@@ -10,13 +12,16 @@ Analyzes [conventional commits](https://www.conventionalcommits.org/) to determi
10
12
  - Prerelease branches (`beta`, `next`, or dynamic from branch name)
11
13
  - Maintenance branches (`1.x`, `2.x`) with major-version capping
12
14
  - Changelog generation powered by [git-cliff](https://git-cliff.org/)
15
+ - Auto-detects package manager (npm, yarn, pnpm)
13
16
  - Configurable tag format templates
14
- - 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
15
20
  - Dry-run mode with pretty, truncated output
16
21
 
17
22
  ## Installation
18
23
 
19
- The easiest way no install needed:
24
+ The easiest way -- no install needed:
20
25
 
21
26
  ```bash
22
27
  npx -y super-release --dry-run
@@ -25,7 +30,6 @@ npx -y super-release --dry-run
25
30
  Or install as a dev dependency:
26
31
 
27
32
  ```bash
28
- # npm / yarn / pnpm
29
33
  pnpm add -D super-release
30
34
  ```
31
35
 
@@ -75,91 +79,104 @@ Options:
75
79
 
76
80
  ### `--show-next-version`
77
81
 
78
- Outputs only the next version (or the current version if no bump is needed) and exits silently. Useful for CI scripts that need the version for downstream steps:
82
+ Outputs only the next version (or the current version if no bump is needed) and exits silently. Useful for CI scripts:
79
83
 
80
84
  ```bash
81
- # Use in CI to set a version variable
82
85
  VERSION=$(super-release --show-next-version)
83
- echo "Next version: $VERSION"
84
-
85
- # Build with the correct version baked in
86
86
  SUPER_RELEASE_VERSION=$VERSION cargo build --release
87
-
88
- # In a monorepo, query a specific package
89
- super-release --show-next-version -p @acme/core
90
87
  ```
91
88
 
92
- In single-package repos, no `--package` flag is needed. In monorepos, if `--package` is omitted, the root package is used. If there's no root package, an error lists the available packages.
89
+ In monorepos, use `--package` to select which package: `super-release --show-next-version -p @acme/core`
93
90
 
94
91
  ## How It Works
95
92
 
96
- 1. **Discover packages** -- finds all directories with a `package.json`
97
- 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)
98
95
  3. **Walk commits** -- only analyzes commits since the oldest tag (not the entire history)
99
- 4. **Associate commits to packages** -- maps changed files to their owning package
100
- 5. **Calculate versions** -- uses git-cliff's conventional commit analysis to determine bump levels
101
- 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
102
101
 
103
102
  ## Conventional Commits
104
103
 
105
- super-release follows the [Conventional Commits](https://www.conventionalcommits.org/) specification:
106
-
107
- | Commit | Bump |
108
- |----------------------------------------------|------------|
109
- | `fix: ...` | patch |
110
- | `feat: ...` | minor |
111
- | `feat!: ...` or `BREAKING CHANGE:` in footer | major |
112
- | `perf: ...` | patch |
113
- | `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 |
114
111
 
115
112
  ## Configuration
116
113
 
117
- 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
+ ```
118
122
 
119
123
  ### Full Example
120
124
 
121
125
  ```yaml
122
- # Branch configurations
123
126
  branches:
124
- # Stable branches (simple string = stable, no prerelease)
125
127
  - main
126
128
  - master
127
-
128
- # Prerelease with a fixed channel name
129
129
  - name: beta
130
- prerelease: beta # -> 2.0.0-beta.1, 2.0.0-beta.2, ...
131
-
132
- - name: next
133
- prerelease: next # -> 2.0.0-next.1, ...
134
-
135
- # Prerelease using the branch name as the channel (for branch patterns)
130
+ prerelease: beta
136
131
  - name: "test-*"
137
- prerelease: true # branch test-foo -> 2.0.0-test-foo.1, ...
138
-
139
- # Maintenance branches (caps major version, breaking changes -> minor)
132
+ prerelease: true
140
133
  - name: "1.x"
141
- maintenance: true # -> 1.5.1, 1.6.0 (never 2.0.0)
134
+ maintenance: true
135
+
136
+ tag_format: "v{version}"
137
+ tag_format_package: "{name}/v{version}"
142
138
 
143
- # Tag format templates (use {version} and {name} placeholders)
144
- tag_format: "v{version}" # root package: v1.2.3
145
- tag_format_package: "{name}/v{version}" # sub-packages: @acme/core/v1.2.3
139
+ packages:
140
+ - "@acme/*"
146
141
 
147
- # Packages to exclude from releasing (substring match on package name)
148
142
  exclude:
149
- - 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"
150
155
 
151
- # Plugins run in order: prepare phase first, then publish phase
152
156
  plugins:
153
157
  - name: changelog
154
158
  - name: npm
155
- - 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
156
172
  ```
157
173
 
158
174
  ### Reference
159
175
 
160
176
  #### `branches`
161
177
 
162
- 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.
163
180
 
164
181
  | Form | Type | Example versions |
165
182
  |--------------------------------------------|-------------------------------------|---------------------------------|
@@ -168,144 +185,124 @@ Defines which branches can produce releases and what kind.
168
185
  | `- name: "test-*"`<br>` prerelease: true` | Prerelease (branch name as channel) | `2.0.0-test-my-feature.1` |
169
186
  | `- name: "1.x"`<br>` maintenance: true` | Maintenance | `1.5.1`, `1.6.0` (major capped) |
170
187
 
171
- **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.
172
190
 
173
- **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"]`
174
192
 
175
- **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`
176
194
 
177
- Default: `["main", "master"]`
195
+ Templates for git tag names. Placeholders: `{version}`, `{name}`.
178
196
 
179
- #### `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
+ ```
180
202
 
181
- Template for root package tags. Placeholders:
182
- - `{version}` -- the semver version (e.g. `1.2.3`, `2.0.0-beta.1`)
183
- - `{name}` -- the package name from `package.json`
203
+ #### `dependencies`
184
204
 
185
- Default: `"v{version}"`
205
+ Global file dependency patterns. When a commit changes any matching file, ALL packages are considered affected.
186
206
 
187
- Examples:
188
207
  ```yaml
189
- tag_format: "v{version}" # -> v1.2.3
190
- tag_format: "release-{version}" # -> release-1.2.3
191
- tag_format: "{name}-v{version}" # -> my-app-v1.2.3
208
+ dependencies:
209
+ - yarn.lock
210
+ - pnpm-lock.yaml
211
+ - package.json
212
+ - ".github/**"
192
213
  ```
193
214
 
194
- #### `tag_format_package`
195
-
196
- Template for sub-package tags in a monorepo.
215
+ #### `ignore`
197
216
 
198
- 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.
199
219
 
200
- Examples:
201
220
  ```yaml
202
- tag_format_package: "{name}/v{version}" # -> @acme/core/v1.2.3
203
- tag_format_package: "{name}@{version}" # -> @acme/core@1.2.3 (semantic-release compat)
221
+ ignore:
222
+ - "README.md"
223
+ - "docs/**"
224
+ - "**/*.md"
225
+ - ".prettierrc"
204
226
  ```
205
227
 
206
- To migrate from semantic-release's tag format, set `tag_format_package: "{name}@{version}"`.
228
+ #### `packages` / `exclude`
207
229
 
208
- #### `plugins`
230
+ Filter which packages are released. `packages` is an allow-list (glob patterns), `exclude` is a deny-list.
209
231
 
210
- Ordered list of plugins to execute. Each plugin runs its `prepare` phase, then its `publish` phase. Each plugin accepts an `options` object for customization.
232
+ ```yaml
233
+ packages:
234
+ - "@acme/*"
235
+ exclude:
236
+ - my-monorepo-root
237
+ ```
211
238
 
212
- Default: `[changelog, npm, git-commit, git-tag]`
239
+ #### `plugins`
240
+
241
+ Ordered list of plugins. Each plugin has a `name`, optional `packages` filter (glob), and `options`.
213
242
 
214
243
  ```yaml
215
244
  plugins:
216
245
  - name: changelog
217
246
  options:
218
- filename: CHANGELOG.md # output file per package (default: CHANGELOG.md)
219
- preview_lines: 20 # max lines shown in dry-run (default: 20)
247
+ filename: CHANGELOG.md
248
+ preview_lines: 20
220
249
 
221
250
  - name: npm
251
+ packages: [ "@acme/*" ] # only publish @acme packages
222
252
  options:
223
- access: public # npm access level (default: "public")
224
- registry: https://registry.npmjs.org # custom registry URL
225
- tag: next # dist-tag override (default: auto from prerelease channel)
226
- publish_args: # extra args passed to the publish command
227
- - "--otp=123456"
228
- 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)
229
259
 
230
260
  - name: exec
261
+ packages: [ "my-rust-lib" ]
231
262
  options:
232
- # Run for all packages (omit `packages` to run for all)
233
- prepare_cmd: "echo Releasing {name} v{version}"
234
- # Or filter to specific packages:
235
- # packages: ["@acme/core"]
236
-
237
- # Multiple exec blocks for different packages:
238
- # - name: exec
239
- # options:
240
- # packages: ["my-rust-project"]
241
- # prepare_cmd: "sed -i'' -e 's/^version = .*/version = \"{version}\"/' Cargo.toml"
242
- # - name: exec
243
- # options:
244
- # packages: ["@acme/*"]
245
- # publish_cmd: "deploy.sh {name} {version}"
246
-
247
- - name: git-commit
248
- options:
249
- # Commit message template. Placeholders:
250
- # {releases} - comma-separated list: "@acme/core@1.1.0, @acme/utils@1.0.1"
251
- # {summary} - one per line: " - @acme/core 1.0.0 -> 1.1.0"
252
- # {count} - number of packages released
253
- message: "chore(release): {releases} [skip ci]"
254
- push: false # push after commit (default: false)
255
- remote: origin # git remote (default: "origin")
256
- paths: # paths to stage (default: ["."])
257
- - "."
258
-
259
- - name: git-tag
260
- options:
261
- push: false # push tags to remote after creation (default: false)
262
- 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
263
266
  ```
264
267
 
265
- | Plugin | Prepare | Publish |
266
- |--------------|--------------------------------------------------------------|-----------------------------------------------------------------------|
267
- | `changelog` | Generates/updates changelog per package (parallel) | -- |
268
- | `npm` | Updates `package.json` versions (auto-detects npm/yarn/pnpm) | Publishes packages (parallel within dependency levels) |
269
- | `exec` | Runs custom shell command per package | Runs custom shell command per package |
270
- | `git-commit` | -- | Stages changed files, commits with release message, optionally pushes |
271
- | `git-tag` | -- | Creates annotated git tags, optionally pushes |
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 |
272
273
 
273
- The default plugin order ensures: changelogs and version bumps are written first, then committed, then tagged.
274
+ Plugins return the files they modified. The core git step stages exactly those files for the commit -- no `git add .`.
274
275
 
275
- #### `packages`
276
+ Default: `[changelog, npm]`
276
277
 
277
- 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.
278
-
279
- ```yaml
280
- # Only release packages in the @acme scope
281
- packages:
282
- - "@acme/*"
278
+ #### `git`
283
279
 
284
- # Release specific packages
285
- packages:
286
- - "@acme/core"
287
- - "@acme/utils"
280
+ Core git behavior after all plugins run. Not a plugin -- always runs.
288
281
 
289
- # Multiple scopes
290
- packages:
291
- - "{@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
292
287
  ```
293
288
 
294
- Default: all discovered packages.
289
+ Commit message placeholders:
295
290
 
296
- #### `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
297
294
 
298
- List of glob patterns to exclude from releasing. Applied after `packages`.
295
+ The git step:
299
296
 
300
- ```yaml
301
- exclude:
302
- - my-monorepo-root
303
- - "@acme/internal-*"
304
- ```
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`
305
302
 
306
- ## Monorepo Structure
303
+ Tags are idempotent -- existing tags are skipped. Already-published npm packages are detected and skipped on rerun.
307
304
 
308
- 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
309
306
 
310
307
  ```
311
308
  my-monorepo/
@@ -320,30 +317,23 @@ my-monorepo/
320
317
  src/
321
318
  ```
322
319
 
323
- **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.
324
322
 
325
323
  ## Performance
326
324
 
327
- super-release is designed to be fast:
328
-
329
- - **Parallel diff computation**: commit diffs are computed across multiple threads (thread-local git repo handles)
330
- - **Tag-bounded history walk**: only walks commits since the oldest package tag, not the entire history
331
- - **Single-pass commit collection**: commits are fetched once and partitioned per package
332
- - **Precomputed file mapping**: file-to-package association is computed once, not per-package
333
-
334
- Benchmark (2001 commits, 8 packages, Apple Silicon):
335
-
336
- | Scenario | Time |
337
- |-------------------------------|--------|
338
- | All commits since initial tag | 0.11s |
339
- | 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
340
329
 
341
330
  ## Acknowledgements
342
331
 
343
332
  super-release is inspired by and builds on the ideas of:
344
333
 
345
- - **[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.
346
- - **[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`
347
337
 
348
338
  ## License
349
339
 
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.4.0",
31
+ "version": "0.5.0",
32
32
  "scripts": {
33
33
  "super-release": "node npm/bin/super-release.js"
34
34
  }