Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/release-notes.md.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# coderef @TAG@

## Release resources

- [Release website](https://mboworks.github.io/coderef/site/tag/@TAG@/)
- [Changelog](https://github.com/mboworks/coderef/blob/@TAG@/CHANGELOG.md)
- [JSON Schema v1](https://mboworks.github.io/coderef/site/tag/@TAG@/schema/v1.json)
22 changes: 21 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ permissions:
contents: read

jobs:
release-site-tests:
name: Release site tests
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.13'
- run: python3 -m unittest discover -s tools -p release_site_test.py
- run: python3 -m unittest discover -s tools -p release_notes_test.py
- name: Verify configured documentation and generated links
env:
GH_TOKEN: ${{ github.token }}
run: |
# This is a disposable build on the runner, never a Pages publication.
python3 tools/release_site.py . "${RUNNER_TEMP}/release-site-check" \
--repository "${GITHUB_REPOSITORY}" --tag 0.0.0-verification --latest ""

version:
name: Release version invariant
runs-on: ubuntu-latest
Expand Down Expand Up @@ -507,7 +527,7 @@ jobs:
# results, so an elaborate result-table step doesn't add
# anything reviewers can't see one click away.
done:
needs: [version, rust, wasm, extension, npm-wrapper, schema, docs]
needs: [release-site-tests, version, rust, wasm, extension, npm-wrapper, schema, docs]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
160 changes: 134 additions & 26 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,154 @@
name: Pages
# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors
# SPDX-License-Identifier: Apache-2.0
name: Publish release site

on:
push:
branches: [main]
# Releases created with GITHUB_TOKEN do not trigger release events.
workflow_run:
workflows: [Release]
types: [completed]
workflow_dispatch:
inputs:
tag:
description: Published release tag (empty selects latest)
type: string
required: false

permissions:
contents: read
pages: write
id-token: write
config_path:
description: Optional config file on main for backfill (empty uses the tag config)
type: string
required: false

permissions: {}

# Share the lock and retained branch with coverage: every deployment includes both.
concurrency:
group: pages
cancel-in-progress: true
group: coverage-pages
queue: max
cancel-in-progress: false

jobs:
deploy:
publish:
if: >-
github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
runs-on: ubuntu-latest
permissions:
contents: write
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Build Pages artifact
with:
ref: main
path: source
- name: Resolve published release
if: github.event_name != 'push'
id: release
env:
GH_TOKEN: ${{ github.token }}
REQUESTED_TAG: ${{ inputs.tag }}
RUN_TAG: ${{ github.event.workflow_run.head_branch }}
run: |
mkdir -p _site/schema
cp schema/coderef.schema.json _site/schema/v1.json
cp schema/README.md _site/schema/README.md
printf '%s\n' \
'<!doctype html>' \
'<meta charset="utf-8">' \
'<title>coderef</title>' \
'<h1>coderef</h1>' \
'<p>Regex-driven source-code references from MBO Works.</p>' \
'<p><a href="schema/v1.json">JSON Schema v1</a> · <a href="https://github.com/mboworks/coderef">GitHub</a></p>' \
> _site/index.html

set -euo pipefail
if gh api "repos/${GITHUB_REPOSITORY}/releases/latest" > latest.json; then
latest="$(jq -r .tag_name latest.json)"
elif jq --exit-status '.status == "404"' latest.json >/dev/null; then
latest="" # A repository may have only prereleases so far.
else
exit 1
fi
tag="${REQUESTED_TAG:-${RUN_TAG:-${latest}}}"
if [[ ! "${tag}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid release tag: ${tag}" >&2
exit 1
fi
gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" > release.json
jq --exit-status '.draft == false' release.json
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
echo "latest=${latest}" >> "${GITHUB_OUTPUT}"
- uses: actions/checkout@v7
if: github.event_name != 'push'
with:
ref: refs/tags/${{ steps.release.outputs.tag }}
path: release
persist-credentials: false
- name: Restore retained Pages tree
working-directory: source
run: |
set -euo pipefail
git fetch origin
if git show-ref --verify --quiet refs/remotes/origin/coverage-pages; then
git worktree add ../site -B coverage-pages origin/coverage-pages
else
git worktree add --detach ../site
git -C ../site checkout --orphan coverage-pages
git -C ../site rm -rf .
fi
- uses: actions/setup-python@v7
with:
python-version: "3.13"
- name: Test site builder
working-directory: source
run: python3 -m unittest discover -s tools -p release_site_test.py
- name: Convert release documentation and update latest redirect
if: github.event_name != 'push'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.release.outputs.tag }}
LATEST_TAG: ${{ steps.release.outputs.latest }}
CONFIG_PATH: ${{ inputs.config_path }}
run: |
set -euo pipefail
config_args=()
if [[ -n "${CONFIG_PATH}" ]]; then
config_file="$(realpath --canonicalize-existing "source/${CONFIG_PATH}")"
if [[ "${config_file}" != "${GITHUB_WORKSPACE}/source/"* ]]; then
echo "Configuration must be a tracked file inside the main checkout" >&2
exit 1
fi
git -C source ls-files --error-unmatch -- "${CONFIG_PATH}" >/dev/null
config_args=(--config "${config_file}")
fi
python3 source/tools/release_site.py release site \
--repository "${GITHUB_REPOSITORY}" --tag "${RELEASE_TAG}" --latest "${LATEST_TAG}" \
"${config_args[@]}"
- name: Preserve the public schema endpoint
run: |
mkdir -p site/schema
cp source/schema/coderef.schema.json site/schema/v1.json
cp source/schema/README.md site/schema/README.md
# Preserve the existing landing page until the first versioned site ships.
if [[ ! -f site/index.html ]]; then
printf '%s\n' \
'<!doctype html><html lang="en"><meta charset="utf-8"><title>coderef</title>' \
'<h1>coderef</h1><p>Regex-driven source-code references from MBO Works.</p>' \
'<p><a href="schema/v1.json">JSON Schema v1</a> · <a href="https://github.com/mboworks/coderef">GitHub</a></p></html>' \
> site/index.html
fi
- name: Retain and stage complete Pages tree
env:
RELEASE_TAG: ${{ steps.release.outputs.tag }}
run: |
set -euo pipefail
git -C site config user.name 'github-actions[bot]'
git -C site config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git -C site add --all
if ! git -C site diff --cached --quiet; then
git -C site commit -m "site: publish ${RELEASE_TAG}"
git -C site push origin HEAD:coverage-pages
fi
mkdir -p public
rsync --archive --exclude='.git' site/ public/
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: _site
- name: Deploy GitHub Pages
path: public
- name: Deploy complete site
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${GITHUB_REF#refs/tags/}"
bash tools/release_notes.sh "$tag" > release-notes.md
gh release view "$tag" >/dev/null 2>&1 || \
gh release create "$tag" \
--title "coderef $tag" \
--notes "Automated release. See CHANGELOG when one ships, or DESIGN.md §20 for the planning horizon."
--notes-file release-notes.md
gh release upload "$tag" _dist/* --clobber

npm:
Expand Down
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# coderef

[Release website](https://mboworks.github.io/coderef/)

Regex-driven references in source code — resolved, click-opened, and
verified identically from VSCode and from CI. A `.coderef.jsonc` config
declares the patterns; the same engine runs inside the editor (via WASM,
Expand Down Expand Up @@ -87,3 +89,90 @@ the reasoning.
## License

Apache License 2.0. See [`LICENSE`](./LICENSE).

## Release website

Release notes use `.github/release-notes.md.template`, rendered by
`tools/release_notes.sh TAG`, to link to that tag's versioned website and related
release resources, including the changelog and versioned JSON schema.

The [website](https://mboworks.github.io/coderef/) forwards to the latest published
stable release at `site/tag/<tag>/`, preserving the exact Git tag name.
Each release keeps its converted HTML, images, and configured files. Retrying
publication leaves an existing snapshot unchanged; a different commit cannot
replace it. Older versions remain directly accessible.

[`release-site.json`](release-site.json) defines the layout. Source names are
relative to the repository root; destinations are relative to that release's
site directory. For example:

```json
{
"pages": {
"README.md": "index.html",
"docs/guide.md": "guide/index.html"
},
"files": {
"schema/example.json": "schema/v1.json"
},
"links": [
{
"label": "Release",
"href": "https://github.com/{owner}/{repo}/releases/tag/{tag}"
}
]
}
```

Use existing source files in the actual configuration. `pages` converts Markdown;
optional `files` copies other files unchanged. `README.md` must map to `index.html`.
The generated `documents.html`, `release.json`, `release-site.json`, and `assets/`
paths are reserved. Destination paths cannot have hidden components (names starting
with a dot), because the Pages artifact uploader excludes them. Hidden source
paths remain valid; for example, `.github/workflows/README.md` maps to
`workflows/index.html`.
Navigation links support `{owner}`, `{repo}`, `{tag}`, `{version}`, and `{commit}`.
`{version}` omits a leading `v` for compatibility with coverage report paths.
By default, the configuration and content come from the release tag. Every linked
local Markdown page (including directory README links) must have a `pages` mapping.
Publication fails for an omitted mapping, a missing generated file, or a broken
anchor within the snapshot. Links to configured pages follow their destination
mappings; other local source links use the exact release commit. Embedded images are copied, including remote badges. Markdown
conversion uses the [GitHub Markdown API](https://docs.github.com/en/rest/markdown/markdown)
at publication time; browsing the result requires no Markdown renderer or CDN.

After the Release workflow succeeds, `Publish release site` retains the snapshot
on `coverage-pages` and deploys the complete Pages tree. Coverage and site
publication share a concurrency group to preserve both trees. GitHub's latest
stable release selects the root redirect; backfilling an older release does not
make it latest. The workflow can also be dispatched with a published tag to retry
publication. Enable GitHub Pages with
**GitHub Actions** as its source, and set the repository's About website to
`https://mboworks.github.io/coderef/`.

### Backfill a historical release

No new release or tag change is needed. Manually dispatch `Publish release site`
with `tag` set to the historical release and `config_path` set to a tracked JSON
file on `main`. Leave `config_path` empty to use a configuration already in the tag.
For example, after selecting a compatible configuration and an existing tag:

```sh
gh workflow run pages.yml --repo mboworks/coderef --ref main \
-f tag="$RELEASE_TAG" -f config_path=release-site.json
```

The override controls only publication layout; all Markdown and copied files come
from the selected tag. Each new snapshot retains the exact configuration as
`release-site.json`, with its SHA-256, origin, and source commit in `release.json`.
A configuration can serve several historical tags when its sources exist in each.
For another layout, commit another configuration and select its path. Missing
sources or links fail publication instead of using newer content. Retrying a
published tag preserves its original HTML and configuration.

Local regression tests: `python3 -m unittest discover -s tools -p release_site_test.py`.
CI also converts the configured documentation and checks the generated links in
a disposable runner directory. It never commits, retains, or deploys that preview.

Main-branch pushes continue to update `/coderef/schema/v1.json`; each release
also retains and links to its own frozen schema copy inside its site directory.
4 changes: 4 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,7 @@ For a release candidate, use the SemVer pre-release form: tag as
`v0.2.0-rc1`, bump all five `version` fields to `0.2.0-rc.1` (note
the `.` before the number for npm/SemVer; Cargo accepts both). Test
publish to a private npm scope or skip npm entirely for rcs.

Release notes are rendered from `.github/release-notes.md.template` by
`tools/release_notes.sh TAG`. They link to the immutable website and JSON schema
for that exact tag, together with its changelog.
25 changes: 25 additions & 0 deletions release-site.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"pages": {
"README.md": "index.html",
"CHANGELOG.md": "CHANGELOG.html",
"docs/README.md": "docs/README.html",
"docs/release.md": "docs/release.html",
"docs/test-plan.md": "docs/test-plan.html",
"extension/README.md": "extension/README.html",
"npm/coderef/README.md": "npm/coderef/README.html",
"schema/README.md": "schema/README.html",
"DESIGN.md": "DESIGN.html",
"AGENTS.md": "AGENTS.html",
"CLAUDE.md": "CLAUDE.html",
"extension/CHANGELOG.md": "extension/CHANGELOG.html"
},
"links": [
{
"label": "JSON Schema v1",
"href": "/{repo}/site/tag/{tag}/schema/v1.json"
}
],
"files": {
"schema/coderef.schema.json": "schema/v1.json"
}
}
28 changes: 28 additions & 0 deletions tools/release_notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash

# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Render release-note links without building, publishing, or changing repository state.
set -euo pipefail

TAG="${1:?Usage: release_notes.sh TAG}"
if [[ ! "${TAG}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid release tag: ${TAG}" >&2
exit 1
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
sed -e "s|@TAG@|${TAG}|g" -e "s|@VERSION@|${TAG#v}|g" \
"${ROOT}/.github/release-notes.md.template"
Loading