-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathbump-version.sh
More file actions
executable file
·286 lines (262 loc) · 11.6 KB
/
Copy pathbump-version.sh
File metadata and controls
executable file
·286 lines (262 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#!/usr/bin/env bash
#
# Bump every product's version string in one pass.
#
# Usage:
# scripts/bump-version.sh 0.2.0
#
# Rewrites the version field in every manifest that carries one
# (ten Cargo.toml / pyproject.toml files, plus four JSON manifests
# — fourteen files total). Also rewrites the engine npm dep pin in
# `examples/nodejs-notes/package.json` so the published example
# resolves the matching engine. Then you run `cargo build` yourself
# to refresh Cargo.lock. Idempotent: running twice with the same version
# is a no-op; running twice with different versions lands on the
# second.
#
# Used by:
# - `release-pr.yml` GitHub Actions workflow as the version-bump
# step before opening a Release PR.
# - Humans locally to rehearse a bump without GitHub in the loop:
#
# ./scripts/bump-version.sh 0.2.0
# cargo build # refresh Cargo.lock
# git diff # inspect before committing
# git checkout . # or back out if it looks wrong
#
# Portability: targets both BSD sed (macOS) and GNU sed (Linux) by
# writing through a temp file instead of using `-i` (which takes a
# mandatory argument on BSD but not GNU).
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "usage: $(basename "$0") X.Y.Z[-prerelease][+build]" >&2
exit 1
fi
VERSION="$1"
# Validate semver — allows standard X.Y.Z plus optional -prerelease and
# +build metadata segments per semver.org. A release workflow won't
# normally use pre-releases for a public publish, but the format is
# handy for testing ("0.2.0-rc.1" against TestPyPI, etc.).
SEMVER_REGEX='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
if ! [[ "$VERSION" =~ $SEMVER_REGEX ]]; then
echo "error: '$VERSION' is not a valid semver (X.Y.Z[-pre][+build])" >&2
exit 1
fi
# Find the repo root — this script lives at <root>/scripts/, so one
# directory up from the script's own dir. Bash-specific
# `$BASH_SOURCE[0]` works regardless of how the script was invoked
# (relative path, symlink, `bash scripts/bump-version.sh`).
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
# ---------------------------------------------------------------------------
# TOML files — match `^version = "..."` anchored at line start.
#
# This catches exactly the `[package]` / `[project]` version line in
# every file. Dependency versions in Cargo.toml are always indented
# inside tables (`[dependencies.foo]` or inline `foo = { version = ... }`),
# so the `^` anchor rules them out.
#
# One gotcha for the future: if we ever adopt workspace-unified
# versioning via `[workspace.package]`, that section also has a
# `^version = ...` line and the script would catch it. We don't use
# that pattern today; revisit if we do.
TOML_FILES=(
"Cargo.toml"
"sqlrite-ffi/Cargo.toml"
"sqlrite-ask/Cargo.toml"
"sqlrite-mcp/Cargo.toml"
"sdk/python/Cargo.toml"
"sdk/python/pyproject.toml"
"sdk/nodejs/Cargo.toml"
"sdk/wasm/Cargo.toml"
"desktop/src-tauri/Cargo.toml"
# `sqlrite-journal` is the second Tauri desktop app (the
# concurrent-writes journal example, SQLR-43). It's a workspace
# member that pins `sqlrite-engine` via a path+version dep, so —
# exactly like `desktop/src-tauri` — both its package version and
# that dep pin have to track the bump. Omitting it survived patch
# releases (a `^0.10.1` pin still matched `0.10.x`) but broke the
# first MINOR bump: `0.11.0` falls outside `^0.10.1`, so the
# workspace lock refresh failed to select a version (release-pr run
# 26737256013). Keep it here so the dep pin never lags the engine.
"examples/desktop-journal/src-tauri/Cargo.toml"
)
# Files that pin `sqlrite-engine` (or any other workspace member) but
# stay at their own fixed version because they're never published to a
# registry. The bench crate is `version = "0.0.0"` on purpose
# (internal-only workspace member); we DON'T want to bump its package
# version, but its inter-workspace dep pin still has to track main or
# `cargo build` fails to resolve (SQLR-23 release was the first run that
# tripped this — see GH Action run 25559263224 for the failure mode).
#
# For these files we run only the path-pinned dep sweep (which rewrites
# `version = "0.8.0"` → `version = "0.9.0"` on lines that also carry
# `path = "..."`), not the top-level `version = "..."` sed.
PIN_ONLY_TOML_FILES=(
"benchmarks/Cargo.toml"
)
for file in "${TOML_FILES[@]}"; do
if [[ ! -f "$file" ]]; then
echo "error: $file not found (are you in the repo root?)" >&2
exit 1
fi
sed "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "$file" > "$file.tmp"
mv "$file.tmp" "$file"
# Inter-workspace dep pins — lines like:
# sqlrite-ask = { version = "0.3", path = "sqlrite-ask", ... }
# sqlrite = { package = "sqlrite-engine", path = "..", version = "0.3", ... }
#
# These carry BOTH version and path because crates.io publishing
# rejects path-only deps (see PR #58 retrospective). The version
# field has to track the workspace bump or `cargo build` fails to
# resolve a candidate (SQLR-9; failed run for v0.3.0 hit exactly
# this — `failed to select a version for the requirement
# sqlrite-ask = "^0.2"`).
#
# Detection: any line containing both `version = "..."` and
# `path = "..."`. The package-level `^version = "..."` line at
# the top of each manifest has no `path` on it and can't match.
# Both inline-table orderings (version-first and path-first) work
# because sed acts per-line, not per-token-order.
sed -E '/path *= *"[^"]*"/ s/version *= *"[^"]*"/version = "'"${VERSION}"'"/' "$file" > "$file.tmp"
mv "$file.tmp" "$file"
done
# Pin-only sweep: rewrite the inter-workspace dep version on these files
# without touching their package-level `version = "..."` (see comment on
# PIN_ONLY_TOML_FILES above).
for file in "${PIN_ONLY_TOML_FILES[@]}"; do
if [[ ! -f "$file" ]]; then
echo "error: $file not found (are you in the repo root?)" >&2
exit 1
fi
sed -E '/path *= *"[^"]*"/ s/version *= *"[^"]*"/version = "'"${VERSION}"'"/' "$file" > "$file.tmp"
mv "$file.tmp" "$file"
done
# ---------------------------------------------------------------------------
# JSON files — match ` "version": "..."` with exactly two leading spaces.
#
# All three of our JSON manifests use 2-space indentation and put
# `"version"` as a top-level object key. Dependency version pins in
# `package.json` use the package *name* as the key (e.g.,
# `"rustyline": "^18.0.0"`), never the literal string `"version"`, so
# there's no ambiguity.
#
# We use `sed -E` (extended regex) rather than jq to avoid adding a
# dependency on a tool that isn't on every CI runner by default.
JSON_FILES=(
"sdk/nodejs/package.json"
"desktop/package.json"
"desktop/src-tauri/tauri.conf.json"
"examples/nodejs-notes/package.json"
)
for file in "${JSON_FILES[@]}"; do
if [[ ! -f "$file" ]]; then
echo "error: $file not found (are you in the repo root?)" >&2
exit 1
fi
sed -E "s/^( \"version\"): *\"[^\"]*\"/\\1: \"${VERSION}\"/" "$file" > "$file.tmp"
mv "$file.tmp" "$file"
done
# ---------------------------------------------------------------------------
# npm dep-pin sweep: the `examples/nodejs-notes` package depends on
# `@joaoh82/sqlrite` and the pin has to track the engine release or
# `npx sqlrite-notes@<new>` would still resolve the *previous* engine.
# Rewrite the `"@joaoh82/sqlrite": "^X.Y.Z"` line to the new caret pin.
#
# Symmetric with the inter-workspace Cargo path-dep sweep above. We
# use a caret (`^`) rather than an exact pin so an engine patch release
# can ship without re-publishing the example.
NPM_DEP_PIN_FILES=(
"examples/nodejs-notes/package.json"
)
for file in "${NPM_DEP_PIN_FILES[@]}"; do
sed -E "s|(\"@joaoh82/sqlrite\"): *\"\\^[0-9]+\\.[0-9]+\\.[0-9]+\"|\\1: \"^${VERSION}\"|" "$file" > "$file.tmp"
mv "$file.tmp" "$file"
done
# ---------------------------------------------------------------------------
# Verify every file actually updated. Catches future refactors that
# change manifest shape (e.g., someone reformats package.json to
# 4-space indent — our 2-space-anchored regex would silently
# no-op; this loop catches that immediately).
echo
echo "Bumped to ${VERSION}. Verifying…"
FAILURES=0
for file in "${TOML_FILES[@]}"; do
expected="version = \"${VERSION}\""
actual="$(grep -E '^version = ' "$file" | head -1)"
if [[ "$actual" != "$expected" ]]; then
echo " ✗ $file — expected: $expected got: $actual" >&2
FAILURES=$((FAILURES + 1))
else
echo " ✓ $file"
fi
done
for file in "${JSON_FILES[@]}"; do
# grep catches the line; we verify the version substring matches.
# Trailing comma / closing brace handled by not matching beyond the
# version value.
if grep -qE "^ \"version\": \"${VERSION}\"" "$file"; then
echo " ✓ $file"
else
actual="$(grep -E '^ "version": ' "$file" | head -1)"
echo " ✗ $file — expected version \"${VERSION}\", got: $actual" >&2
FAILURES=$((FAILURES + 1))
fi
done
# Inter-workspace pin sweep — any surviving `version = "X"` on a TOML
# line that also has `path = "..."` and isn't already at $VERSION is a
# pin we missed. Catches future refactors that change pin shape (e.g.
# someone splits a long dep line across multiple TOML lines, where the
# single-line address would no longer match).
#
# Runs over both the published manifests (`TOML_FILES`) and the
# pin-only manifests (`PIN_ONLY_TOML_FILES`) — the bench crate is
# unpublished but its `sqlrite-engine` dep still has to track main.
for file in "${TOML_FILES[@]}" "${PIN_ONLY_TOML_FILES[@]}"; do
bad="$(grep -nE 'path *= *"[^"]*"' "$file" \
| grep -E 'version *= *"[^"]*"' \
| grep -vE "version *= *\"${VERSION}\"" || true)"
if [[ -n "$bad" ]]; then
echo " ✗ $file — inter-workspace pin not at ${VERSION}:" >&2
echo "$bad" | sed 's/^/ /' >&2
FAILURES=$((FAILURES + 1))
fi
done
# npm dep-pin verification — any `"@joaoh82/sqlrite": "^X.Y.Z"` that
# isn't at the new $VERSION is a pin we missed. Mirrors the
# inter-workspace Cargo sweep above.
for file in "${NPM_DEP_PIN_FILES[@]}"; do
bad="$(grep -nE '"@joaoh82/sqlrite": *"\^[0-9]+\.[0-9]+\.[0-9]+"' "$file" \
| grep -vE "\"@joaoh82/sqlrite\": *\"\\^${VERSION}\"" || true)"
if [[ -n "$bad" ]]; then
echo " ✗ $file — engine npm pin not at ^${VERSION}:" >&2
echo "$bad" | sed 's/^/ /' >&2
FAILURES=$((FAILURES + 1))
fi
done
if [[ $FAILURES -gt 0 ]]; then
echo
echo "error: $FAILURES file(s) did not update as expected." >&2
echo "Run 'git diff' to inspect, 'git checkout .' to back out." >&2
exit 1
fi
echo
echo "Done. Next steps:"
echo " cargo build # refresh Cargo.lock with the new versions"
echo " git diff # inspect the thirteen-file bump"
echo " git checkout . # or back out if it looks wrong"
echo
echo "When the diff looks right, commit + tag with the EXACT message the"
echo "release workflow's detect job expects (regex"
echo " ^release: v[0-9]+\\.[0-9]+\\.[0-9]+\$"
echo "in .github/workflows/release.yml). Any other message and the"
echo "release pipeline silently skips publish:"
echo
echo " git commit -am 'release: v${VERSION}'"
echo " git tag v${VERSION}"
echo " git push && git push --tags"
echo
echo "If a push lands with a different commit-message format, the"
echo "release workflow can be retriggered manually:"
echo
echo " gh workflow run release.yml -f version=${VERSION}"