Skip to content

Commit 24c427c

Browse files
authored
Merge pull request #61 from fulll/feat/version-management-ux
[EPIC #60] Enrich version management UX: upgrade output, release blog, contextual help
2 parents fe02d13 + f366ee3 commit 24c427c

12 files changed

Lines changed: 768 additions & 33 deletions

File tree

.github/instructions/bug-fixing.instructions.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,19 @@ Add a one-line comment above the fix if the root cause is non-obvious:
8282
- Steps to reproduce (before the fix).
8383
- Steps to verify (after the fix).
8484
- Reference to the issue number.
85+
86+
## 8. Release after merge
87+
88+
Once the PR is merged into `main`, publish a **patch** release:
89+
90+
```bash
91+
bun pm version patch # bumps package.json: 1.2.4 → 1.2.5
92+
git checkout -b release/$(jq -r .version package.json)
93+
git add package.json
94+
git commit -S -m "v$(jq -r .version package.json)"
95+
git tag v$(jq -r .version package.json)
96+
git push origin release/$(jq -r .version package.json) --tags
97+
```
98+
99+
The tag push triggers `cd.yaml` which builds all-platform binaries and creates the GitHub Release automatically.
100+
See the full release guide in `AGENTS.md § Release process`.

.github/instructions/implement-feature.instructions.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,28 @@ bun run build.ts # binary compiles without errors
6969
- **All commits must be signed** (GPG or SSH). Configure once with `git config --global commit.gpgsign true`.
7070
Commits pushed via the GitHub API (Copilot Coding Agent, MCP tools) are automatically Verified by GitHub.
7171
- PR description: motivation, what changed, how to test manually.
72+
73+
## 8. Release after merge
74+
75+
Once the PR is merged into `main`, publish a **minor** (new feature) or **major** (breaking change) release:
76+
77+
```bash
78+
bun pm version minor # new feature: 1.2.4 → 1.3.0
79+
# or
80+
bun pm version major # breaking change: 1.2.4 → 2.0.0
81+
82+
git checkout -b release/$(jq -r .version package.json)
83+
git add package.json
84+
git commit -S -m "v$(jq -r .version package.json)"
85+
git tag v$(jq -r .version package.json)
86+
git push origin release/$(jq -r .version package.json) --tags
87+
```
88+
89+
The tag push triggers `cd.yaml` which builds all-platform binaries and creates the GitHub Release automatically.
90+
91+
For **minor and major releases**, also write the blog post **before pushing the tag**:
92+
93+
- Create `docs/blog/release-v<X-Y-Z>.md` with feature highlights
94+
- Add a row in `docs/blog/index.md`
95+
96+
See the full release guide in `AGENTS.md § Release process`.

.github/workflows/docs.yml

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,74 @@ jobs:
194194
docs/public/versions.json > /tmp/versions_new.json
195195
mv /tmp/versions_new.json docs/public/versions.json
196196
197-
- name: Commit versions.json to main
198-
uses: stefanzweifel/git-auto-commit-action@v5.0.1
197+
- name: Generate blog post stub for new major version
198+
run: |
199+
set -euo pipefail
200+
# Convert vX.0.0 → v<maj>-0-0 for the file name (e.g. v3.0.0 → v3-0-0)
201+
TAG="$GITHUB_REF_NAME"
202+
SLUG="${TAG//./-}" # v3.0.0 → v3-0-0
203+
BLOG_FILE="docs/blog/release-${SLUG}.md"
204+
RELEASE_DATE="$(date -u +%Y-%m-%d)"
205+
# Idempotent — skip creation if the file already exists (manually authored).
206+
if [ -f "$BLOG_FILE" ]; then
207+
echo "Blog post $BLOG_FILE already exists — skipping stub generation."
208+
else
209+
# Fix: use Python to write the file so heredoc indentation never
210+
# leaks into the generated Markdown (which would break frontmatter).
211+
python3 - <<PY
212+
import pathlib
213+
tag = "$TAG"
214+
release_date = "$RELEASE_DATE"
215+
blog_file = "$BLOG_FILE"
216+
content = (
217+
f'---\ntitle: "What\'s new in {tag}"\n'
218+
f'description: "Highlights of github-code-search {tag}"\n'
219+
f'date: {release_date}\n---\n\n'
220+
f'# What\'s new in github-code-search {tag}\n\n'
221+
f'> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/{tag}>\n\n'
222+
'<!-- TODO: fill in feature highlights, usage examples and screenshots. -->\n'
223+
)
224+
pathlib.Path(blog_file).write_text(content, encoding="utf-8")
225+
print(f"Created blog stub: {blog_file}")
226+
PY
227+
fi
228+
229+
- name: Update blog/index.md table with new major version
230+
run: |
231+
set -euo pipefail
232+
TAG="$GITHUB_REF_NAME"
233+
SLUG="${TAG//./-}"
234+
# Add a row to the blog index table only if the version isn't already listed.
235+
if grep -qF "release-${SLUG}" docs/blog/index.md; then
236+
echo "Blog index already contains ${TAG} — skipping."
237+
else
238+
python3 - <<PY
239+
import re, pathlib
240+
241+
path = pathlib.Path("docs/blog/index.md")
242+
content = path.read_text()
243+
tag = "$TAG"
244+
slug = "$SLUG"
245+
new_row = f"| [{tag}](./release-{slug}) | <!-- TODO: add summary --> |"
246+
# Insert the new row after the last markdown table row (line ending with |).
247+
updated = re.sub(
248+
r"(\|[^\n]+\|)(\s*\Z)",
249+
lambda m: m.group(1) + "\n" + new_row + m.group(2),
250+
content,
251+
count=1,
252+
flags=re.DOTALL,
253+
)
254+
path.write_text(updated)
255+
print(f"Inserted row for {tag} into blog/index.md")
256+
PY
257+
fi
258+
259+
- name: Commit blog stub and versions.json to main
260+
# Pin to exact commit SHA to prevent supply-chain attacks.
261+
uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
199262
with:
200263
# No [skip ci] — the push to main matches paths: docs/** and re-triggers
201264
# the deploy job, which merges the new snapshot into the Pages artifact.
202-
commit_message: "docs: add ${{ steps.ver.outputs.major }} to versions.json"
203-
file_pattern: docs/public/versions.json
265+
commit_message: "docs: add ${{ steps.ver.outputs.major }} to versions.json and blog"
266+
file_pattern: docs/public/versions.json docs/blog/
204267
branch: main

AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,73 @@ For epics spanning multiple PRs, create a long-lived **feature branch** (`feat/<
155155

156156
---
157157

158+
## Release process
159+
160+
### Deciding the version bump
161+
162+
This project follows [Semantic Versioning](https://semver.org/):
163+
164+
| Change type | Bump | Example |
165+
| ------------------------------------------ | ------- | ------------- |
166+
| Bug fix (no new behaviour, no API change) | `patch` | 1.2.4 → 1.2.5 |
167+
| New feature, backward-compatible | `minor` | 1.2.4 → 1.3.0 |
168+
| Breaking change (CLI flag removed/renamed) | `major` | 1.2.4 → 2.0.0 |
169+
170+
### Step-by-step
171+
172+
```bash
173+
# 1. Bump the version in package.json (pick one)
174+
bun pm version patch # bug fix
175+
bun pm version minor # new feature
176+
bun pm version major # breaking change
177+
178+
# 2. Create the release branch and commit
179+
git checkout -b release/$(jq -r .version package.json)
180+
git add package.json
181+
git commit -S -m "v$(jq -r .version package.json)"
182+
183+
# 3. Write (or update) the blog post for the release
184+
# • Required for minor and major releases.
185+
# • Patch releases: optional — a brief note in the GitHub Release is sufficient.
186+
# File: docs/blog/release-v<X-Y-Z>.md (e.g. docs/blog/release-v1-3-0.md)
187+
# Update docs/blog/index.md table too.
188+
189+
# 4. Tag and push — this triggers the CD pipeline
190+
git tag v$(jq -r .version package.json)
191+
git push origin release/$(jq -r .version package.json) --tags
192+
```
193+
194+
### What the CI does automatically
195+
196+
Pushing a tag `vX.Y.Z` triggers **`cd.yaml`**:
197+
198+
1. Compiles the binary for all six targets (linux-x64, linux-arm64, linux-x64-baseline, darwin-x64, darwin-arm64, windows-x64).
199+
2. Creates a **GitHub Release** with all binaries attached.
200+
`generate_release_notes: true` — GitHub auto-populates the release body from merged PR titles and commit messages since the previous tag.
201+
3. Legacy platform aliases are also published for backward-compat with pre-v1.2.1 binaries.
202+
203+
Pushing a **major** tag (`vX.0.0`) additionally triggers **`docs.yml` → snapshot job**:
204+
205+
1. Builds a versioned docs snapshot at `/github-code-search/vX/`.
206+
2. Auto-generates `docs/blog/release-vX-0-0.md` stub if it does not exist yet.
207+
3. Prepends the new entry to `docs/public/versions.json` and commits back to `main`.
208+
209+
### Blog post requirement
210+
211+
| Release type | Blog post | Location |
212+
| ------------ | ----------------------------------------------------------- | --------------------------------- |
213+
| **Major** | Required (written by hand — CI stub automates the skeleton) | `docs/blog/release-vX-0-0.md` |
214+
| **Minor** | Required | `docs/blog/release-vX-Y-0.md` |
215+
| **Patch** | Optional | GitHub Release body is sufficient |
216+
217+
For minor/major releases update `docs/blog/index.md` to add a row in the version table:
218+
219+
```markdown
220+
| [vX.Y.Z](./release-vX-Y-Z) | One-line highlights |
221+
```
222+
223+
---
224+
158225
## Development notes
159226

160227
- **TypeScript throughout** — no `.js` files in `src/`.

docs/.vitepress/config.mts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
11
import { defineConfig } from "vitepress";
2+
import { readdirSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
24
import versionsData from "../public/versions.json";
35

6+
// ─── Semantic version helpers for blog sidebar sort ───────────────────────────
7+
function parseBlogVersion(filename: string): number[] {
8+
// "release-v1-2-3.md" → [1, 2, 3]
9+
return filename
10+
.replace(/^release-v/, "")
11+
.replace(/\.md$/, "")
12+
.split("-")
13+
.map(Number);
14+
}
15+
16+
function compareVersionArrays(a: number[], b: number[]): number {
17+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
18+
const diff = (b[i] ?? 0) - (a[i] ?? 0);
19+
if (diff !== 0) return diff;
20+
}
21+
return 0;
22+
}
23+
24+
// ── Blog sidebar — built dynamically from docs/blog/*.md files ────────────────
25+
// Files are sorted newest-first using semantic version comparison so that
26+
// multi-digit components (e.g. v1.10.0) sort correctly before v1.9.0.
27+
// The index.md is excluded from the per-post list since it is the section root.
28+
function buildBlogSidebarItems(): { text: string; link: string }[] {
29+
// Fix: use import.meta.url instead of __dirname which may be undefined in ESM
30+
const blogDir = fileURLToPath(new URL("../blog", import.meta.url));
31+
let files: string[] = [];
32+
try {
33+
files = readdirSync(blogDir)
34+
.filter((f) => f.endsWith(".md") && f !== "index.md")
35+
.toSorted((a, b) => compareVersionArrays(parseBlogVersion(a), parseBlogVersion(b)));
36+
} catch {
37+
// blog dir may not exist during the very first build
38+
}
39+
return files.map((f) => {
40+
const slug = f.replace(/\.md$/, "");
41+
// slug: release-v1-0-0 → display: v1.0.0
42+
const label = slug.replace(/^release-/, "").replace(/-/g, ".");
43+
return { text: label, link: `/blog/${slug}` };
44+
});
45+
}
46+
447
// ── Versioning convention ────────────────────────────────────────────────────
548
// • main branch → always publishes the "latest" docs at /github-code-search/
649
// • Major release tag (vX.0.0) → CI takes a snapshot:
@@ -68,6 +111,11 @@ export default defineConfig({
68111
activeMatch: "^/getting-started/",
69112
},
70113
{ text: "Usage", link: "/usage/search-syntax", activeMatch: "^/usage/" },
114+
{
115+
text: "What's New",
116+
link: "/blog/",
117+
activeMatch: "^/blog/",
118+
},
71119
{
72120
text: "Reference",
73121
link: "/reference/cli-options",
@@ -146,6 +194,12 @@ export default defineConfig({
146194
],
147195
},
148196
],
197+
"/blog/": [
198+
{
199+
text: "What's New",
200+
items: [{ text: "All releases", link: "/blog/" }, ...buildBlogSidebarItems()],
201+
},
202+
],
149203
"/architecture/": [
150204
{
151205
text: "Architecture",

docs/blog/index.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# What's New
2+
3+
Release highlights for every version of `github-code-search`.
4+
Each post covers the main features, fixes, and upgrade notes for that release.
5+
Full release notes and changelogs are always available on
6+
[GitHub Releases](https://github.com/fulll/github-code-search/releases).
7+
8+
---
9+
10+
## v1 series
11+
12+
| Release | Highlights |
13+
| -------------------------- | --------------------------------------------------------------------------------------------------- |
14+
| [v1.3.0](./release-v1-3-0) | Richer upgrade output, update-available notice, colorized `--help`, deep doc links, What's New blog |
15+
| [v1.0.0](./release-v1-0-0) | Initial public release — interactive TUI, per-repo aggregation, markdown / JSON output |
16+
17+
---
18+
19+
::: tip Upgrading?
20+
Run `github-code-search upgrade` to update to the latest version in one command.
21+
:::

docs/blog/release-v1-0-0.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
title: "What's new in v1.0.0"
3+
description: "Initial public release of github-code-search — interactive TUI, per-repo aggregation, markdown/JSON output."
4+
date: 2025-01-01
5+
---
6+
7+
# What's new in github-code-search v1.0.0
8+
9+
> Full release notes: <https://github.com/fulll/github-code-search/releases/tag/v1.0.0>
10+
11+
## The beginning
12+
13+
`github-code-search` is an interactive CLI for searching GitHub code across an entire organisation.
14+
It wraps the [GitHub Code Search API](https://docs.github.com/en/rest/search/search#search-code)
15+
and adds a keyboard-driven TUI on top, so you can browse results, fold/unfold extracts, and select
16+
the ones you care about before printing structured output.
17+
18+
## Highlights
19+
20+
### Keyboard-driven TUI
21+
22+
Search results are loaded and displayed in an interactive terminal UI.
23+
Navigate with <kbd>↑</kbd> / <kbd>↓</kbd>, fold/unfold extracts with <kbd>→</kbd> / <kbd>←</kbd>,
24+
select results with <kbd>Space</kbd>, and confirm with <kbd>Enter</kbd>.
25+
Press <kbd>?</kbd> to show the full keyboard shortcut reference.
26+
27+
[Interactive mode guide](/usage/interactive-mode)
28+
29+
### Per-repository aggregation
30+
31+
All code matches for the same repository are grouped together, with per-file headings.
32+
No more scanning through a flat list of individual matches.
33+
34+
### Markdown and JSON output
35+
36+
Use `--format markdown` (default) for human-readable output, or `--format json` for
37+
machine-readable output suitable for further processing.
38+
39+
Use `--output-type repo-only` to print only repository names (no code extracts).
40+
41+
[Output formats guide](/usage/output-formats)
42+
43+
### Filtering
44+
45+
Exclude noisy repositories with `--exclude-repositories` or specific extracts with
46+
`--exclude-extracts`. Both flags accept short (`repoName`) and long (`org/repoName`) forms.
47+
48+
[Filtering guide](/usage/filtering)
49+
50+
### Team grouping
51+
52+
Group results by team prefix using `--group-by-team-prefix`. Results are arranged in sections
53+
per team, making it easy to see which squads own the code that matches your search.
54+
55+
[Team grouping guide](/usage/team-grouping)
56+
57+
### Non-interactive / CI mode
58+
59+
Use `--no-interactive` (or set `CI=true`) to pipe output directly to downstream tools.
60+
Syntax highlighting and TUI chrome are stripped so the output is clean.
61+
62+
[Non-interactive mode guide](/usage/non-interactive-mode)
63+
64+
### Built-in upgrade command
65+
66+
```bash
67+
github-code-search upgrade
68+
```
69+
70+
Checks for a newer binary on GitHub Releases and replaces the running binary in-place.
71+
No package manager required.
72+
73+
[Upgrade guide](/usage/upgrade)

0 commit comments

Comments
 (0)