Skip to content

Commit 4a9d71e

Browse files
committed
docs: add guided next-step hints for release make flow
1 parent 5daad32 commit 4a9d71e

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,11 @@ check-security: check-tools-security ## Security/dependency checks
172172
typos
173173

174174
.PHONY: check-release
175-
check-release: check-core ## Release readiness checks
175+
check-release: ## Release readiness checks
176+
ENVGEN_HINTS=0 $(MAKE) check-core
176177
cargo publish --dry-run --locked --allow-dirty
178+
@python3 $(VERSION_BUMP_SCRIPT) status | awk -F= '/^crate_version=/{print "✓ Release readiness checks passed for crate v"$$2}'
179+
@python3 $(VERSION_BUMP_SCRIPT) next-step --stage crate-after-check-release
177180

178181
.PHONY: install
179182
install: ## Install envgen to ~/.cargo/bin
@@ -245,6 +248,7 @@ check-yaml-fixtures: yaml-lint-fixtures yaml-fmt-check-fixtures ## Lint + format
245248

246249
check-schema: check-schema-biome check-schema-meta ## Validate the JSON Schema file (all layers)
247250
@echo "✓ Schema passed all checks"
251+
@python3 $(VERSION_BUMP_SCRIPT) next-step --stage schema-after-check-schema
248252

249253
check-schema-biome: ## Layer 1: JSON lint/format check (Biome)
250254
@echo "Checking schema formatting and linting (Biome)... (first run may download the tool)"

RELEASING.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,71 @@ This mapping must match the publish job in `.github/workflows/release.yml` exact
5454
- `make tag-schema`
5555
- `make push-tag-schema`
5656

57+
## Guided hints
58+
59+
Release-flow commands print a `Hint:` + `Next:` block to guide the next command in the sequence.
60+
61+
- Default behavior:
62+
- Hints are shown for local interactive runs (TTY).
63+
- Hints are suppressed in CI/non-interactive output.
64+
- Override behavior:
65+
- `ENVGEN_HINTS=1` forces hints on.
66+
- `ENVGEN_HINTS=0` forces hints off.
67+
68+
Commands with guided next-step output include:
69+
70+
- `make bump-crate*`
71+
- `make check-release`
72+
- `make tag-crate`
73+
- `make push-tag-crate`
74+
- `make bump-schema*`
75+
- `make check-schema`
76+
- `make tag-schema`
77+
- `make push-tag-schema`
78+
79+
Example (crate flow):
80+
81+
```text
82+
$ make bump-crate-patch
83+
...
84+
Hint: Crate release prep updated to vX.Y.Z.
85+
Next:
86+
$ make check-release
87+
```
88+
89+
```text
90+
$ make check-release
91+
...
92+
✓ Release readiness checks passed for crate vX.Y.Z
93+
Hint: Release readiness checks passed for crate vX.Y.Z.
94+
Next:
95+
$ git add Cargo.toml Cargo.lock CHANGELOG.md
96+
$ git commit -m "chore(release): bump crate to vX.Y.Z"
97+
$ git push origin main
98+
$ make tag-crate
99+
```
100+
101+
Example (schema flow):
102+
103+
```text
104+
$ make bump-schema-patch
105+
...
106+
Hint: Schema release prep updated to vA.B.C.
107+
Next:
108+
$ make check-schema
109+
```
110+
111+
```text
112+
$ make check-schema
113+
...
114+
Hint: Schema checks passed for artifact vA.B.C.
115+
Next:
116+
$ git add SCHEMA_VERSION SCHEMA_CHANGELOG.md schemas/envgen.schema.vA.B.C.json
117+
$ git commit -m "chore(schema): schema-vA.B.C"
118+
$ git push origin main
119+
$ make tag-schema
120+
```
121+
57122
## Quality gate matrix
58123

59124
| Entry point | Canonical target | Purpose |

scripts/version_bump.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@
3737
"Compatibility",
3838
]
3939

40+
TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"}
41+
FALSY_ENV_VALUES = {"", "0", "false", "no", "off"}
42+
43+
NEXT_STEP_STAGES = (
44+
"crate-after-bump",
45+
"crate-after-check-release",
46+
"crate-after-tag",
47+
"crate-after-push-tag",
48+
"schema-after-bump",
49+
"schema-after-check-schema",
50+
"schema-after-tag",
51+
"schema-after-push-tag",
52+
)
53+
54+
RELEASE_WORKFLOW_URL = (
55+
"https://github.com/smorinlabs/envgen/actions/workflows/release.yml"
56+
)
57+
4058

4159
class BumpError(RuntimeError):
4260
"""Raised when the bump flow should fail with a user-facing error."""
@@ -46,6 +64,145 @@ def fail(message: str) -> None:
4664
raise BumpError(message)
4765

4866

67+
def env_var_truthy(name: str) -> bool:
68+
value = os.environ.get(name)
69+
if value is None:
70+
return False
71+
return value.strip().lower() not in FALSY_ENV_VALUES
72+
73+
74+
def parse_hint_override() -> bool | None:
75+
value = os.environ.get("ENVGEN_HINTS")
76+
if value is None:
77+
return None
78+
79+
normalized = value.strip().lower()
80+
if normalized in TRUTHY_ENV_VALUES:
81+
return True
82+
if normalized in FALSY_ENV_VALUES:
83+
return False
84+
return None
85+
86+
87+
def hints_enabled() -> bool:
88+
override = parse_hint_override()
89+
if override is not None:
90+
return override
91+
if env_var_truthy("CI"):
92+
return False
93+
return sys.stdout.isatty()
94+
95+
96+
def render_next_step(
97+
stage: str,
98+
*,
99+
crate_version: str | None = None,
100+
schema_version: str | None = None,
101+
tag_name: str | None = None,
102+
) -> tuple[str, list[str]]:
103+
if stage == "crate-after-bump":
104+
resolved_crate_version = crate_version or read_cargo_version()
105+
return (
106+
f"Crate release prep updated to v{resolved_crate_version}.",
107+
["$ make check-release"],
108+
)
109+
110+
if stage == "crate-after-check-release":
111+
resolved_crate_version = crate_version or read_cargo_version()
112+
return (
113+
f"Release readiness checks passed for crate v{resolved_crate_version}.",
114+
[
115+
"$ git add Cargo.toml Cargo.lock CHANGELOG.md",
116+
f'$ git commit -m "chore(release): bump crate to v{resolved_crate_version}"',
117+
"$ git push origin main",
118+
"$ make tag-crate",
119+
],
120+
)
121+
122+
if stage == "crate-after-tag":
123+
resolved_crate_version = crate_version or read_cargo_version()
124+
resolved_tag_name = tag_name or f"v{resolved_crate_version}"
125+
return (
126+
f"Local crate tag created: {resolved_tag_name}.",
127+
["$ make push-tag-crate"],
128+
)
129+
130+
if stage == "crate-after-push-tag":
131+
resolved_crate_version = crate_version or read_cargo_version()
132+
resolved_tag_name = tag_name or f"v{resolved_crate_version}"
133+
return (
134+
f"Crate tag pushed to origin: {resolved_tag_name}.",
135+
[
136+
"Release workflow should trigger automatically from this tag push.",
137+
f"Monitor: {RELEASE_WORKFLOW_URL}",
138+
],
139+
)
140+
141+
if stage == "schema-after-bump":
142+
resolved_schema_version = schema_version or read_schema_version_file()
143+
return (
144+
f"Schema release prep updated to v{resolved_schema_version}.",
145+
["$ make check-schema"],
146+
)
147+
148+
if stage == "schema-after-check-schema":
149+
resolved_schema_version = schema_version or read_schema_version_file()
150+
schema_file = f"schemas/envgen.schema.v{resolved_schema_version}.json"
151+
return (
152+
f"Schema checks passed for artifact v{resolved_schema_version}.",
153+
[
154+
f"$ git add SCHEMA_VERSION SCHEMA_CHANGELOG.md {schema_file}",
155+
f'$ git commit -m "chore(schema): schema-v{resolved_schema_version}"',
156+
"$ git push origin main",
157+
"$ make tag-schema",
158+
],
159+
)
160+
161+
if stage == "schema-after-tag":
162+
resolved_schema_version = schema_version or read_schema_version_file()
163+
resolved_tag_name = tag_name or f"schema-v{resolved_schema_version}"
164+
return (
165+
f"Local schema tag created: {resolved_tag_name}.",
166+
["$ make push-tag-schema"],
167+
)
168+
169+
if stage == "schema-after-push-tag":
170+
resolved_schema_version = schema_version or read_schema_version_file()
171+
resolved_tag_name = tag_name or f"schema-v{resolved_schema_version}"
172+
return (
173+
f"Schema tag pushed to origin: {resolved_tag_name}.",
174+
[
175+
"Schema tag pushes do not trigger crates.io publishing.",
176+
"Create and push a crate tag (vX.Y.Z) when you want a crate release.",
177+
],
178+
)
179+
180+
fail(f"Unsupported next-step stage: {stage}")
181+
182+
183+
def emit_next_step(
184+
stage: str,
185+
*,
186+
crate_version: str | None = None,
187+
schema_version: str | None = None,
188+
tag_name: str | None = None,
189+
) -> None:
190+
if not hints_enabled():
191+
return
192+
193+
summary, lines = render_next_step(
194+
stage,
195+
crate_version=crate_version,
196+
schema_version=schema_version,
197+
tag_name=tag_name,
198+
)
199+
print("")
200+
print(f"Hint: {summary}")
201+
print("Next:")
202+
for line in lines:
203+
print(f" {line}")
204+
205+
49206
def write_atomic(path: Path, content: str) -> None:
50207
tmp_path = path.with_suffix(path.suffix + ".tmp")
51208
tmp_path.write_text(content, encoding="utf-8")
@@ -344,6 +501,7 @@ def do_bump_crate(args: argparse.Namespace) -> None:
344501
print(f"crate version: {old} -> {new}")
345502
print(f"updated: {CARGO_TOML}")
346503
print(f"updated: {CHANGELOG}")
504+
emit_next_step("crate-after-bump", crate_version=new)
347505

348506

349507
def do_bump_schema(args: argparse.Namespace) -> None:
@@ -386,34 +544,43 @@ def do_bump_schema(args: argparse.Namespace) -> None:
386544
print(f"updated: {SCHEMA_VERSION_FILE}")
387545
print(f"updated: {SCHEMA_CHANGELOG}")
388546
print(f"renamed: {old_schema_path} -> {new_schema_path}")
547+
emit_next_step("schema-after-bump", schema_version=new)
389548

390549

391550
def do_tag_crate(args: argparse.Namespace) -> None:
392551
version = resolve_tag_crate_version(require_release_section=True)
393552
tag_name = f"v{version}"
394553
create_tag(tag_name, f"release {tag_name}", args.dry_run)
395554
print(f"created local tag: {tag_name}")
555+
emit_next_step("crate-after-tag", crate_version=version, tag_name=tag_name)
396556

397557

398558
def do_push_tag_crate(args: argparse.Namespace) -> None:
399559
version = resolve_tag_crate_version(require_release_section=False)
400560
tag_name = f"v{version}"
401561
push_tag(tag_name, args.dry_run)
402562
print(f"pushed tag: {tag_name}")
563+
emit_next_step("crate-after-push-tag", crate_version=version, tag_name=tag_name)
403564

404565

405566
def do_tag_schema(args: argparse.Namespace) -> None:
406567
version = resolve_tag_schema_version(require_release_section=True)
407568
tag_name = f"schema-v{version}"
408569
create_tag(tag_name, f"schema release {tag_name}", args.dry_run)
409570
print(f"created local tag: {tag_name}")
571+
emit_next_step("schema-after-tag", schema_version=version, tag_name=tag_name)
410572

411573

412574
def do_push_tag_schema(args: argparse.Namespace) -> None:
413575
version = resolve_tag_schema_version(require_release_section=False)
414576
tag_name = f"schema-v{version}"
415577
push_tag(tag_name, args.dry_run)
416578
print(f"pushed tag: {tag_name}")
579+
emit_next_step("schema-after-push-tag", schema_version=version, tag_name=tag_name)
580+
581+
582+
def do_next_step(args: argparse.Namespace) -> None:
583+
emit_next_step(args.stage)
417584

418585

419586
def build_parser() -> argparse.ArgumentParser:
@@ -423,6 +590,13 @@ def build_parser() -> argparse.ArgumentParser:
423590
status = subparsers.add_parser("status", help="Show current crate/schema versions")
424591
status.set_defaults(func=do_status)
425592

593+
next_step = subparsers.add_parser(
594+
"next-step",
595+
help="Print guided next-step release hints for a flow stage",
596+
)
597+
next_step.add_argument("--stage", required=True, choices=NEXT_STEP_STAGES)
598+
next_step.set_defaults(func=do_next_step)
599+
426600
bump_crate = subparsers.add_parser("bump-crate", help="Bump crate version + CHANGELOG")
427601
bump_crate.add_argument("--level", choices=["patch", "minor", "major"])
428602
bump_crate.add_argument("--version")

0 commit comments

Comments
 (0)