rlsbl 0.9.0 → 0.9.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 (2) hide show
  1. package/README.md +117 -112
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,161 +4,156 @@
4
4
 
5
5
  # rlsbl
6
6
 
7
- Release orchestration and project scaffolding CLI for npm, PyPI, and Go.
7
+ Release orchestration and project scaffolding CLI for npm, PyPI, and Go. Pure Python, no dependencies.
8
8
 
9
9
  ## Install
10
10
 
11
- From npm:
11
+ From PyPI:
12
12
 
13
13
  ```
14
- npm i -g rlsbl
14
+ uv tool install rlsbl
15
15
  ```
16
16
 
17
- From PyPI (requires Node.js 18+):
17
+ From npm (wrapper):
18
18
 
19
19
  ```
20
- uv tool install rlsbl
20
+ npm i -g rlsbl
21
21
  ```
22
22
 
23
23
  ## Quick start
24
24
 
25
25
  ```
26
- rlsbl scaffold
27
- rlsbl release minor
26
+ rlsbl scaffold # set up CI/CD, hooks, changelog
27
+ # ... develop, commit ...
28
+ rlsbl release minor # bump, tag, push, create GitHub Release
29
+ rlsbl watch <sha> # monitor CI for that release
28
30
  ```
29
31
 
30
32
  ## Commands
31
33
 
32
- All commands work at the top level -- registries are auto-detected from project files (`package.json`, `pyproject.toml`, `go.mod`). Use `--registry <npm|pypi|go>` when you need to target a specific registry.
34
+ All commands auto-detect registries from project files (`package.json`, `pyproject.toml`, `go.mod`). Use `--registry <npm|pypi|go>` to target a specific one.
35
+
36
+ | Command | Description |
37
+ |---------|-------------|
38
+ | `release [patch\|minor\|major]` | Bump version, commit, tag, push, create GitHub Release |
39
+ | `scaffold [--force] [--update]` | Scaffold CI/CD, hooks, and release infrastructure |
40
+ | `status` | Show version, branch, last tag, changelog coverage, CI presence |
41
+ | `check <name>` | Check name availability on npm/PyPI (parallel variant queries) |
42
+ | `config [show\|init\|migrate\|status]` | Manage project configuration and schema migrations |
43
+ | `undo [--yes]` | Revert the last release (tag, commit, GitHub Release) |
44
+ | `discover [--mine]` | List rlsbl ecosystem projects via GitHub topic search |
45
+ | `watch [<sha>]` | Monitor CI runs for a commit (parallel polling), notify on completion |
46
+ | `unreleased [--json]` | List commits since last tag, report changelog coverage |
47
+ | `prs` | List open pull requests for the current repo |
48
+ | `record-gif` | Record a demo GIF with vhs |
49
+ | `pre-push-check` | Verify CHANGELOG entry exists for the current version |
50
+
51
+ Global flags: `--help`, `--version`, `--registry <npm|pypi|go>`, `--no-tag`.
33
52
 
34
- ### scaffold [--force] [--update]
53
+ ## Release flow
35
54
 
36
- Scaffolds CI/CD infrastructure and release tooling for all detected registries.
55
+ When you run `rlsbl release [patch|minor|major]`:
37
56
 
38
- ```
39
- rlsbl scaffold
40
- rlsbl scaffold --registry npm # target npm only
41
- rlsbl scaffold --registry pypi --force # overwrite existing files
42
- rlsbl scaffold --registry go # target Go only
43
- ```
57
+ 1. Verifies `gh` CLI is installed and authenticated
58
+ 2. Checks working tree is clean
59
+ 3. Reads the current version from the primary project file
60
+ 4. Computes the new version; confirms the tag does not already exist
61
+ 5. Validates `CHANGELOG.md` contains a `## <new-version>` section
62
+ 6. Runs `.rlsbl/hooks/pre-release.sh` if present (non-zero exit aborts; receives `RLSBL_VERSION`)
63
+ 7. Acquires advisory lockfile (`.rlsbl/lock`) to prevent concurrent operations
64
+ 8. Writes the new version to all detected project files and `.rlsbl/version`
65
+ 9. Adds `rlsbl` keyword to manifests if ecosystem tagging is enabled
66
+ 10. Verifies no unexpected files were modified (race condition guard)
67
+ 11. Commits the version bump (uses `safegit` if available)
68
+ 12. Tags and pushes to `origin`
69
+ 13. Creates a GitHub Release with the changelog entry as notes
70
+ 14. Adds `rlsbl` topic to the GitHub repo (if tagging enabled)
71
+ 15. Runs `.rlsbl/hooks/post-release.sh` if present (non-fatal; receives `RLSBL_VERSION`)
72
+ 16. Prints `Watch CI: rlsbl watch <sha>`
44
73
 
45
- Context-aware behavior when files already exist (without `--force`):
74
+ Use `--dry-run` to preview without changes. Use `--yes` for non-interactive mode (CI, AI agents).
46
75
 
47
- | File | Behavior |
48
- |---|---|
49
- | `CLAUDE.md` | Appends rlsbl sections if the marker is not present |
50
- | `.gitignore` | Merges missing entries from the template |
51
- | `.github/workflows/ci.yml` | Preserves existing file, prints a note to review manually |
52
- | All others | Skipped |
76
+ First release: if the current version has never been tagged, `release` publishes it as-is (bump type is ignored).
53
77
 
54
- ### release [patch|minor|major] [--dry-run] [--quiet]
78
+ Pre-release versions (e.g. `1.0.0-beta.1`) are supported.
55
79
 
56
- Bumps version, commits, pushes, and creates a GitHub Release. Defaults to `patch`.
80
+ ## Scaffold
57
81
 
58
82
  ```
59
- rlsbl release minor
60
- rlsbl release major --dry-run --registry npm
83
+ rlsbl scaffold # create CI/CD for all detected registries
84
+ rlsbl scaffold --update # three-way merge template updates with user customizations
85
+ rlsbl scaffold --force # overwrite managed files (user-owned files still preserved)
86
+ rlsbl scaffold --no-commit # skip auto-commit of scaffolded files
61
87
  ```
62
88
 
63
- The version is synced across all detected project files (`package.json`, `pyproject.toml`, `VERSION`) regardless of which registry is primary. Go projects use a plain `VERSION` file as the version source.
89
+ Created files are committed automatically by default.
64
90
 
65
- If `.rlsbl/hooks/pre-release.sh` exists, it runs before any changes are made. A non-zero exit aborts the release. If `.rlsbl/hooks/post-release.sh` exists, it runs after the release completes (non-fatal). See [Release flow](#release-flow) for details.
91
+ | File | Purpose |
92
+ |------|---------|
93
+ | `.github/workflows/ci.yml` | CI workflow (lint, test) |
94
+ | `.github/workflows/publish.yml` | Publish on GitHub Release (OIDC) |
95
+ | `CHANGELOG.md` | Version changelog |
96
+ | `LICENSE` | MIT license (author and year filled in) |
97
+ | `.gitignore` | Standard ignores for the ecosystem |
98
+ | `CLAUDE.md` | AI assistant instructions |
99
+ | `.claude/settings.json` | Claude Code settings |
100
+ | `.rlsbl/hooks/pre-release.sh` | User-customizable pre-release validation |
101
+ | `.rlsbl/hooks/post-release.sh` | User-customizable post-release actions |
102
+ | `.git/hooks/pre-push` | One-liner: `exec rlsbl pre-push-check "$@"` |
103
+ | `.rlsbl/bases/` | Three-way merge bases for `--update` |
66
104
 
67
- ### status
105
+ **Three-way merge (`--update`):** Bases are stored at scaffold time. On `--update`, user customizations and template updates merge via `git merge-file`. Conflicts get git-style conflict markers.
68
106
 
69
- Shows project status: package name, version (per registry), git branch, last tag, working tree state, changelog coverage, and CI workflow presence.
107
+ **User-owned files** (CHANGELOG.md, LICENSE, hooks) are never overwritten, even with `--force`.
70
108
 
71
- ```
72
- rlsbl status
73
- rlsbl status --registry pypi
74
- ```
109
+ **Runs config migrations** when `.rlsbl/config-schema.json` exists.
75
110
 
76
- ### check \<name\>
111
+ ## Config management
77
112
 
78
- Checks name availability on both npm and PyPI, and warns about confusingly similar names.
113
+ Schema-driven configuration migration system for projects that ship user-facing config files.
79
114
 
80
115
  ```
81
- rlsbl check my-cool-lib
82
- rlsbl check my-cool-lib --registry npm # npm only
116
+ rlsbl config init # scaffold config migration infrastructure
117
+ rlsbl config migrate # run pending migrations
118
+ rlsbl config status # show migration status
119
+ rlsbl config show # show resolved project configuration
83
120
  ```
84
121
 
85
- npm checks variant spellings (hyphens, underscores, dots, no separator). PyPI normalizes per PEP 503 and checks common alternatives.
122
+ `config init` creates `.rlsbl/config-schema.json` and a `defaults/` directory. Migrations support deep merge, flat merge, and list-by-key merge strategies with versioned schema tracking and atomic writes.
86
123
 
87
- ### discover [--mine]
124
+ ### Library API
88
125
 
89
- Lists all projects in the rlsbl ecosystem by querying GitHub for repositories with the `rlsbl` topic.
126
+ ```python
127
+ from rlsbl.lib import ConfigMigrator, load_schema, migrate
90
128
 
129
+ # One-liner: load schema and run all pending migrations
130
+ result = migrate(".") # returns {filename: was_written} or None
91
131
  ```
92
- rlsbl discover
93
- rlsbl discover --mine # only your repos
94
- ```
95
-
96
- Uses GitHub token if available (higher rate limit). Works unauthenticated for public repos.
97
132
 
98
- ### Ecosystem tagging
133
+ ## Undo
99
134
 
100
- By default, `scaffold` and `release` add an `"rlsbl"` keyword to `package.json` and/or `pyproject.toml`, and set the `rlsbl` topic on the GitHub repository. This makes projects discoverable via `rlsbl discover`.
101
-
102
- To disable tagging:
103
-
104
- | Method | Scope |
105
- |---|---|
106
- | `--no-tag` flag | Single invocation |
107
- | `{"tag": false}` in `.rlsbl/config.json` | This project |
108
- | `{"tag": false}` in `~/.rlsbl/config.json` | All your projects |
109
-
110
- Precedence: CLI flag > project config > user config > default (enabled).
111
-
112
- Global flags: `--help`, `--version`.
135
+ ```
136
+ rlsbl undo # interactive: confirms before each destructive step
137
+ rlsbl undo --yes # non-interactive: auto-confirms, auto-pushes
138
+ ```
113
139
 
114
- ## Release flow
140
+ Reverts the last release:
115
141
 
116
- When you run `release`, the following happens in order:
142
+ 1. Deletes the GitHub Release
143
+ 2. Deletes the git tag (remote + local)
144
+ 3. Reverts the version bump commit (if HEAD matches the tag)
145
+ 4. Pushes the revert commit (with confirmation, or automatic with `--yes`)
117
146
 
118
- 1. Verifies `gh` CLI is installed and authenticated
119
- 2. Checks that the git working tree is clean
120
- 3. Reads the current version from the primary project file
121
- 4. Computes the new version and confirms the git tag does not already exist
122
- 5. Validates that `CHANGELOG.md` contains a `## <new-version>` section
123
- 6. Runs `.rlsbl/hooks/pre-release.sh` if present (non-zero exit aborts)
124
- 7. Writes the new version to the primary project file
125
- 8. Syncs the new version to all other detected project files
126
- 9. Commits the version bump (uses `safegit` if available, otherwise `git`)
127
- 10. Pushes the branch to `origin`
128
- 11. Creates a GitHub Release tagged `v<new-version>` with the changelog entry as notes
129
- 12. The GitHub Release triggers `publish.yml`, which publishes to the registry
130
- 13. Runs `.rlsbl/hooks/post-release.sh` if present (non-fatal -- the release is already complete). The `RLSBL_VERSION` env var is set to the released version. Useful for local install (`go install ./cmd/myapp/`), deploy, or notifications.
131
- 14. Spawns a background process that watches CI via `gh run watch`. When CI finishes, it prints the result to stderr (so AI agents can read it) and sends a desktop notification (`notify-send` on Linux, `osascript` on macOS). On CI failure, it also prints the GitHub Actions run URL. This happens automatically -- no configuration needed.
132
-
133
- ## What scaffold creates
134
-
135
- | File | Source | Purpose |
136
- |---|---|---|
137
- | `.github/workflows/ci.yml` | Registry-specific | CI workflow (lint, test) |
138
- | `.github/workflows/publish.yml` | Registry-specific | Publish on GitHub Release (OIDC) |
139
- | `CHANGELOG.md` | Shared | Version changelog |
140
- | `LICENSE` | Shared | MIT license (author and year filled in) |
141
- | `.gitignore` | Shared | Standard ignores for the ecosystem |
142
- | `CLAUDE.md` | Shared | AI assistant instructions |
143
- | `.claude/settings.json` | Shared | Claude Code settings |
144
- | `.rlsbl/hooks/pre-release.sh` | Shared | User-customizable pre-release validation |
145
- | `.rlsbl/hooks/post-release.sh` | Shared | User-customizable post-release actions |
146
- | `.git/hooks/pre-push` | Shared | One-liner that calls `rlsbl pre-push-check` |
147
-
148
- Hook files are made executable automatically. The `record-gif` and `pre-push-check` functionality is provided as built-in subcommands (`rlsbl record-gif`, `rlsbl pre-push-check`) rather than scaffolded scripts.
149
-
150
- The scaffolded `.gitignore` includes a `*.local-only` pattern. Create a `.local-only/` directory or rename files with a `.local-only` suffix to keep them out of version control -- useful for local-only assets, experiments, and keeping the working tree clean for tools that check `git status`.
147
+ On partial failure, prints a structured summary table with remediation commands for each failed step.
151
148
 
152
149
  ## Pre-push hook
153
150
 
154
- The `rlsbl pre-push-check` subcommand enforces changelog coverage. During `scaffold`, a one-liner git hook is installed at `.git/hooks/pre-push` that calls this subcommand. It prevents pushing when `CHANGELOG.md` lacks an entry for the current version.
155
-
156
- How it works:
151
+ The `.git/hooks/pre-push` hook calls `rlsbl pre-push-check`, which:
157
152
 
158
153
  1. Detects project type (`package.json`, `pyproject.toml`, or `VERSION`)
159
154
  2. Extracts the current version
160
- 3. Checks that `CHANGELOG.md` contains a heading `## <version>`
161
- 4. Blocks the push with an error if the entry is missing
155
+ 3. Checks that `CHANGELOG.md` contains a `## <version>` heading
156
+ 4. Blocks the push if the entry is missing
162
157
 
163
158
  To reinstall manually:
164
159
 
@@ -166,30 +161,40 @@ To reinstall manually:
166
161
  echo '#!/bin/sh' > .git/hooks/pre-push && echo 'exec rlsbl pre-push-check "$@"' >> .git/hooks/pre-push && chmod +x .git/hooks/pre-push
167
162
  ```
168
163
 
169
- ## First publish
164
+ ## Ecosystem tagging
170
165
 
171
- The first version must be published manually before CI can take over:
166
+ `scaffold` and `release` add an `"rlsbl"` keyword to project manifests and set the `rlsbl` topic on the GitHub repository, making projects discoverable via `rlsbl discover`.
172
167
 
173
- | Registry | Manual first publish | Then configure |
174
- |---|---|---|
175
- | npm | Add an `NPM_TOKEN` secret to your GitHub repo (Settings > Secrets > Actions), then push a release | CI handles subsequent publishes |
176
- | PyPI | Run `uv publish` | Set up [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on pypi.org |
177
- | Go | Push to GitHub and create a release -- Go modules are published by the tag itself | No secrets needed; `pkg.go.dev` indexes automatically |
168
+ To disable:
178
169
 
179
- After configuration, all subsequent releases are handled by CI when `rlsbl release` creates a GitHub Release. Go projects use GoReleaser in CI (via GitHub Actions) to build cross-platform binaries.
170
+ | Method | Scope |
171
+ |--------|-------|
172
+ | `--no-tag` flag | Single invocation |
173
+ | `{"tag": false}` in `.rlsbl/config.json` | This project |
174
+ | `{"tag": false}` in `~/.rlsbl/config.json` | All projects |
180
175
 
181
176
  ## Environment variables
182
177
 
183
178
  | Variable | Default | Description |
184
179
  |----------|---------|-------------|
185
- | `RLSBL_PUSH_TIMEOUT` | `120` | Timeout in seconds for `git push` operations. Increase if your pre-push hooks (e.g. test suites) take longer than 2 minutes. |
186
- | `RLSBL_VERSION` | -- | Set automatically when running `.rlsbl/hooks/post-release.sh`. Contains the just-released version string. |
180
+ | `RLSBL_PUSH_TIMEOUT` | `120` | Timeout in seconds for `git push` operations |
181
+ | `RLSBL_VERSION` | -- | Set when running pre-release and post-release hooks; contains the version being released |
182
+ | `GITHUB_TOKEN` | -- | Used by `gh` CLI for GitHub API calls; `discover` works unauthenticated for public repos |
183
+
184
+ ## First publish
185
+
186
+ | Registry | Setup | Then |
187
+ |----------|-------|------|
188
+ | npm | Add `NPM_TOKEN` secret to GitHub repo (Settings > Secrets > Actions) | CI publishes on GitHub Release |
189
+ | PyPI | Run `uv publish` once, then set up [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) | CI publishes via OIDC |
190
+ | Go | Push tag -- Go modules are published by the tag itself | `pkg.go.dev` indexes automatically |
187
191
 
188
192
  ## Requirements
189
193
 
190
- - Node 18+
194
+ - Python 3.11+
191
195
  - [GitHub CLI](https://cli.github.com) (`gh`), installed and authenticated
192
196
  - git
197
+ - Node 24+ (for npm CI/publish templates)
193
198
 
194
199
  ## License
195
200
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlsbl",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Release orchestration and project scaffolding for npm, PyPI, and Go",
5
5
  "license": "MIT",
6
6
  "bin": {