rlsbl 0.5.0 → 0.5.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.
- package/README.md +7 -1
- package/package.json +1 -1
- package/rlsbl/utils.py +52 -19
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ rlsbl release major --dry-run --registry npm
|
|
|
62
62
|
|
|
63
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.
|
|
64
64
|
|
|
65
|
-
If `scripts/pre-release.sh` exists, it runs before any changes are made. A non-zero exit aborts the release.
|
|
65
|
+
If `scripts/pre-release.sh` exists, it runs before any changes are made. A non-zero exit aborts the release. If `scripts/post-release.sh` exists, it runs after the release completes (non-fatal). See [Release flow](#release-flow) for details.
|
|
66
66
|
|
|
67
67
|
### status
|
|
68
68
|
|
|
@@ -127,6 +127,8 @@ When you run `release`, the following happens in order:
|
|
|
127
127
|
10. Pushes the branch to `origin`
|
|
128
128
|
11. Creates a GitHub Release tagged `v<new-version>` with the changelog entry as notes
|
|
129
129
|
12. The GitHub Release triggers `publish.yml`, which publishes to the registry
|
|
130
|
+
13. Runs `scripts/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.
|
|
130
132
|
|
|
131
133
|
## What scaffold creates
|
|
132
134
|
|
|
@@ -141,11 +143,14 @@ When you run `release`, the following happens in order:
|
|
|
141
143
|
| `.claude/settings.json` | Shared | Claude Code settings |
|
|
142
144
|
| `scripts/check-prs.sh` | Shared | PR review helper |
|
|
143
145
|
| `scripts/pre-release.sh` | Shared | Pre-release hook (runs before each release) |
|
|
146
|
+
| `scripts/post-release.sh` | Shared | Post-release hook (runs after each release, non-fatal) |
|
|
144
147
|
| `scripts/record-gif.sh` | Shared | Terminal recording helper |
|
|
145
148
|
| `scripts/pre-push-hook.sh` | Shared | Pre-push changelog enforcement |
|
|
146
149
|
|
|
147
150
|
All `.sh` files in `scripts/` are made executable automatically. The pre-push hook is installed into `.git/hooks/pre-push` during scaffold.
|
|
148
151
|
|
|
152
|
+
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`.
|
|
153
|
+
|
|
149
154
|
## Pre-push hook
|
|
150
155
|
|
|
151
156
|
The scaffolded `scripts/pre-push-hook.sh` is installed as a git pre-push hook during `scaffold`. It prevents pushing when `CHANGELOG.md` lacks an entry for the current version.
|
|
@@ -180,6 +185,7 @@ After configuration, all subsequent releases are handled by CI when `rlsbl relea
|
|
|
180
185
|
| Variable | Default | Description |
|
|
181
186
|
|----------|---------|-------------|
|
|
182
187
|
| `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. |
|
|
188
|
+
| `RLSBL_VERSION` | -- | Set automatically when running `scripts/post-release.sh`. Contains the just-released version string. |
|
|
183
189
|
|
|
184
190
|
## Requirements
|
|
185
191
|
|
package/package.json
CHANGED
package/rlsbl/utils.py
CHANGED
|
@@ -109,27 +109,42 @@ def find_commit_tool():
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def spawn_ci_watcher(commit_sha, tag):
|
|
112
|
-
"""Spawn a detached background process that watches CI and
|
|
112
|
+
"""Spawn a detached background process that watches CI and prints results to stderr.
|
|
113
|
+
|
|
114
|
+
The spawned process inherits the parent's stderr so output appears in the
|
|
115
|
+
same terminal/stream -- important for AI agents that read stderr.
|
|
116
|
+
Desktop notifications are sent as a secondary channel when available.
|
|
117
|
+
"""
|
|
118
|
+
repo_slug = ""
|
|
119
|
+
try:
|
|
120
|
+
repo_slug = run("gh", ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"])
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
113
124
|
repo_name = ""
|
|
114
125
|
try:
|
|
115
126
|
repo_name = run("gh", ["repo", "view", "--json", "name", "-q", ".name"])
|
|
116
127
|
except Exception:
|
|
117
128
|
pass
|
|
118
129
|
|
|
119
|
-
notify_cmd = _notify_command()
|
|
120
|
-
if not notify_cmd:
|
|
121
|
-
return
|
|
122
|
-
|
|
123
130
|
label = f"{repo_name} {tag}" if repo_name else tag
|
|
124
131
|
|
|
132
|
+
# Build the notification snippet based on what's available on this platform
|
|
133
|
+
notify_snippet = _notify_snippet()
|
|
134
|
+
|
|
125
135
|
script = f"""
|
|
126
136
|
import subprocess, sys, time
|
|
127
137
|
|
|
138
|
+
commit_sha = {commit_sha!r}
|
|
139
|
+
label = {label!r}
|
|
140
|
+
repo_slug = {repo_slug!r}
|
|
141
|
+
|
|
142
|
+
# Find the CI run by commit SHA (retry up to 30s)
|
|
128
143
|
run_id = None
|
|
129
144
|
for _ in range(15):
|
|
130
145
|
try:
|
|
131
146
|
r = subprocess.run(
|
|
132
|
-
["gh", "run", "list", "--commit",
|
|
147
|
+
["gh", "run", "list", "--commit", commit_sha, "--limit", "1",
|
|
133
148
|
"--json", "databaseId", "-q", ".[0].databaseId"],
|
|
134
149
|
capture_output=True, text=True, timeout=10)
|
|
135
150
|
if r.returncode == 0 and r.stdout.strip():
|
|
@@ -142,43 +157,61 @@ for _ in range(15):
|
|
|
142
157
|
if not run_id:
|
|
143
158
|
sys.exit(0)
|
|
144
159
|
|
|
160
|
+
# Watch the run until it completes
|
|
145
161
|
result = subprocess.run(
|
|
146
162
|
["gh", "run", "watch", run_id, "--exit-status"],
|
|
147
163
|
capture_output=True, text=True, timeout=3600)
|
|
148
164
|
|
|
165
|
+
ok = result.returncode == 0
|
|
166
|
+
|
|
167
|
+
# Extract last non-empty line as summary for desktop notification
|
|
149
168
|
summary = ""
|
|
150
169
|
for line in reversed(result.stdout.strip().splitlines()):
|
|
151
170
|
if line.strip():
|
|
152
171
|
summary = line.strip()
|
|
153
172
|
break
|
|
154
173
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
174
|
+
# Print result to stderr so AI agents and terminal users can see it
|
|
175
|
+
if ok:
|
|
176
|
+
print(f"rlsbl: {{label}}: CI passed", file=sys.stderr)
|
|
177
|
+
else:
|
|
178
|
+
print(f"rlsbl: {{label}}: CI FAILED", file=sys.stderr)
|
|
179
|
+
if repo_slug and run_id:
|
|
180
|
+
print(f"rlsbl: https://github.com/{{repo_slug}}/actions/runs/{{run_id}}", file=sys.stderr)
|
|
181
|
+
|
|
182
|
+
# Desktop notification (optional, non-fatal)
|
|
183
|
+
title = f"{{label}}: CI passed" if ok else f"{{label}}: CI FAILED"
|
|
184
|
+
try:
|
|
185
|
+
{notify_snippet}
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
158
188
|
"""
|
|
159
189
|
subprocess.Popen(
|
|
160
190
|
[sys.executable, "-c", script],
|
|
161
191
|
start_new_session=True,
|
|
162
192
|
stdin=subprocess.DEVNULL,
|
|
163
|
-
stdout=subprocess.DEVNULL,
|
|
164
|
-
stderr=subprocess.DEVNULL,
|
|
165
193
|
)
|
|
166
194
|
|
|
167
195
|
|
|
168
|
-
def
|
|
169
|
-
"""Return
|
|
196
|
+
def _notify_snippet():
|
|
197
|
+
"""Return an indented Python code snippet for sending a desktop notification.
|
|
198
|
+
|
|
199
|
+
Returns a pass statement if no notification tool is available.
|
|
200
|
+
The snippet is intended to be embedded inside a try/except block.
|
|
201
|
+
"""
|
|
202
|
+
indent = " "
|
|
170
203
|
if sys.platform == "darwin":
|
|
171
204
|
return (
|
|
172
|
-
'subprocess.run(["osascript", "-e"
|
|
173
|
-
'
|
|
174
|
-
'
|
|
205
|
+
f'{indent}subprocess.run(["osascript", "-e",\n'
|
|
206
|
+
f'{indent} f\'display notification "{{summary}}" with title "{{title}}"\'],\n'
|
|
207
|
+
f'{indent} timeout=5)'
|
|
175
208
|
)
|
|
176
209
|
if shutil.which("notify-send"):
|
|
177
210
|
return (
|
|
178
|
-
'urgency = "normal" if ok else "critical"\n'
|
|
179
|
-
'subprocess.run(["notify-send", "-u", urgency, title, summary], timeout=5)'
|
|
211
|
+
f'{indent}urgency = "normal" if ok else "critical"\n'
|
|
212
|
+
f'{indent}subprocess.run(["notify-send", "-u", urgency, title, summary], timeout=5)'
|
|
180
213
|
)
|
|
181
|
-
return
|
|
214
|
+
return f"{indent}pass"
|
|
182
215
|
|
|
183
216
|
|
|
184
217
|
def bump_version(version, bump_type):
|