ci(release): automate plugin releases via workflow_dispatch
CI / editor tests (push) Successful in 41s
CI / editor tests (push) Successful in 41s
Adds a dispatch-triggered Release workflow for the plugin: enter a version in the Actions UI and it stamps g:nuwiki_version in plugin/nuwiki.vim, sanity-checks the plugin still sources (+ exposes the stamped version), commits chore(release): X.Y.Z, pushes the vX.Y.Z tag, and publishes a Gitea release with notes. No binary build — the plugin ships none; nuwiki-ls is fetched from nuwiki-rs at install time. Dispatch-only so the self-created tag doesn't double-fire. Updates ONBOARDING (tree, CI/CD, "Releasing the plugin") to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Cut a plugin release from the Actions UI: "Run workflow" → enter the
|
||||||
|
# version (e.g. 0.5.1). This repo is the Vim/Neovim plugin only and ships no
|
||||||
|
# binary (the nuwiki-ls server lives in nuwiki-rs and is downloaded at install
|
||||||
|
# time), so a release is just: stamp g:nuwiki_version, commit, tag vX.Y.Z, and
|
||||||
|
# publish a Gitea release entry. No build / cross-compile.
|
||||||
|
#
|
||||||
|
# Dispatch-only on purpose: the job creates the tag itself, so a push-tag
|
||||||
|
# trigger would double-fire.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version, no leading v (e.g. 0.5.1)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: tag + release ${{ inputs.version }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
# Push the release commit + tag back with a write-capable token.
|
||||||
|
token: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
|
||||||
|
- name: Validate version + stamp
|
||||||
|
id: stamp
|
||||||
|
env:
|
||||||
|
VERSION: ${{ inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ver="${VERSION#v}" # tolerate a leading v
|
||||||
|
if ! printf '%s' "$ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'; then
|
||||||
|
echo "::error::'$ver' is not a semver version (expected e.g. 0.5.1)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tag="v$ver"
|
||||||
|
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null \
|
||||||
|
|| git ls-remote --exit-code --tags origin "$tag" >/dev/null 2>&1; then
|
||||||
|
echo "::error::tag $tag already exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# g:nuwiki_version in plugin/nuwiki.vim is the plugin's only version
|
||||||
|
# location (surfaced by :NuwikiShowVersion / health).
|
||||||
|
sed -i -E "s/(let g:nuwiki_version = ')[^']*(')/\1$ver\2/" plugin/nuwiki.vim
|
||||||
|
if ! grep -q "let g:nuwiki_version = '$ver'" plugin/nuwiki.vim; then
|
||||||
|
echo "::error::failed to stamp g:nuwiki_version in plugin/nuwiki.vim"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "version=$ver" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Sanity-check the plugin sources cleanly
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y --no-install-recommends vim
|
||||||
|
# plugin/nuwiki.vim must source without error (silent Ex mode exits
|
||||||
|
# non-zero on any error), and expose the stamped version.
|
||||||
|
vim -e -s -u NONE -N \
|
||||||
|
-c 'set runtimepath+=.' \
|
||||||
|
-c 'runtime plugin/nuwiki.vim' \
|
||||||
|
-c "if get(g:, 'nuwiki_version', '') !=# '${{ steps.stamp.outputs.version }}' | cquit 1 | endif" \
|
||||||
|
-c 'qall!'
|
||||||
|
|
||||||
|
- name: Commit, tag, push
|
||||||
|
env:
|
||||||
|
VER: ${{ steps.stamp.outputs.version }}
|
||||||
|
TAG: ${{ steps.stamp.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git config user.name "gitea-actions"
|
||||||
|
git config user.email "gitea-actions@users.noreply.code.gfran.co"
|
||||||
|
git add plugin/nuwiki.vim
|
||||||
|
git commit -m "chore(release): $VER"
|
||||||
|
git tag -a "$TAG" -m "nuwiki plugin $VER"
|
||||||
|
git push origin "HEAD:${GITHUB_REF_NAME}"
|
||||||
|
git push origin "$TAG"
|
||||||
|
|
||||||
|
- name: Ensure jq + curl
|
||||||
|
run: |
|
||||||
|
command -v jq >/dev/null 2>&1 || { apt-get update && apt-get install -y --no-install-recommends jq; }
|
||||||
|
command -v curl >/dev/null 2>&1 || { apt-get update && apt-get install -y --no-install-recommends curl; }
|
||||||
|
|
||||||
|
- name: Generate release notes
|
||||||
|
id: notes
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.stamp.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git fetch --tags --force origin >/dev/null 2>&1 || true
|
||||||
|
# Previous tag = newest tag that isn't the one we just cut.
|
||||||
|
prev_tag=$(git tag --sort=-version:refname | grep -vxF "$TAG" | head -n1 || echo "")
|
||||||
|
if [ -n "$prev_tag" ]; then
|
||||||
|
log=$(git log --oneline "$prev_tag..$TAG")
|
||||||
|
compare="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${prev_tag}...${TAG}"
|
||||||
|
else
|
||||||
|
log=$(git log --oneline "$TAG")
|
||||||
|
compare="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commits/tag/${TAG}"
|
||||||
|
fi
|
||||||
|
cat > release-notes.md <<EOF
|
||||||
|
Vim/Neovim plugin release. The \`nuwiki-ls\` server binary is downloaded
|
||||||
|
automatically from [nuwiki-rs](${GITHUB_SERVER_URL}/gffranco/nuwiki-rs)
|
||||||
|
at install time — no binary assets are attached here by design.
|
||||||
|
|
||||||
|
**Full Changelog**: ${compare}
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
${log}
|
||||||
|
EOF
|
||||||
|
echo "wrote release notes"
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
env:
|
||||||
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
GITEA_SERVER: ${{ github.server_url }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TAG: ${{ steps.stamp.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${RELEASE_TOKEN:-}" ]; then
|
||||||
|
echo "::error::RELEASE_TOKEN secret is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Idempotent: skip if a release for this tag already exists.
|
||||||
|
existing=$(curl --silent -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: token $RELEASE_TOKEN" \
|
||||||
|
"$GITEA_SERVER/api/v1/repos/$REPO/releases/tags/$TAG" || true)
|
||||||
|
if [ "$existing" = "200" ]; then
|
||||||
|
echo "release $TAG already exists — nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
notes=$(cat release-notes.md 2>/dev/null || echo "")
|
||||||
|
payload=$(jq -n --arg tag "$TAG" --arg name "$TAG" --arg body "$notes" \
|
||||||
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
||||||
|
code=$(curl --silent -o /tmp/rel.json -w "%{http_code}" \
|
||||||
|
-H "Authorization: token $RELEASE_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
"$GITEA_SERVER/api/v1/repos/$REPO/releases")
|
||||||
|
if [ "$code" != "201" ]; then
|
||||||
|
echo "::error::failed to create release (HTTP $code)"; cat /tmp/rel.json; exit 1
|
||||||
|
fi
|
||||||
|
echo "created release $TAG"
|
||||||
+16
-14
@@ -59,11 +59,13 @@ nuwiki/
|
|||||||
│
|
│
|
||||||
└── .gitea/
|
└── .gitea/
|
||||||
└── workflows/
|
└── workflows/
|
||||||
└── ci.yaml # Editor (Vim/Neovim) harnesses on every push/PR
|
├── ci.yaml # Editor (Vim/Neovim) harnesses on every push/PR
|
||||||
|
└── release.yaml # Dispatch → stamp version, tag, Gitea release (no build)
|
||||||
```
|
```
|
||||||
|
|
||||||
(The Rust server's build/release workflow lives in the separate
|
(The Rust server's *binary* build/cross-compile workflow lives in the separate
|
||||||
[nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) repository.)
|
[nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) repository; this repo's
|
||||||
|
release just tags the plugin.)
|
||||||
|
|
||||||
## Server Component
|
## Server Component
|
||||||
|
|
||||||
@@ -200,23 +202,23 @@ To modify the LSP server itself, clone the [nuwiki-rs](https://code.gfran.co/gff
|
|||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
This repo (editor client) uses Gitea Actions:
|
This repo (editor client) uses Gitea Actions:
|
||||||
- CI (`.gitea/workflows/ci.yaml`): Runs editor tests on every push/PR.
|
- CI (`.gitea/workflows/ci.yaml`): Runs the editor harnesses on every push/PR.
|
||||||
|
- Release (`.gitea/workflows/release.yaml`): `workflow_dispatch` with a `version` input — stamps `g:nuwiki_version`, tags `vX.Y.Z`, and publishes a Gitea release. No binary build (the plugin ships none).
|
||||||
|
|
||||||
The Rust LSP server lives in [nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) which handles:
|
The Rust LSP server lives in [nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) which has its own release workflow — it cross-compiles `nuwiki-ls` for Linux targets and publishes the downloadable binaries the plugin fetches.
|
||||||
- Release (`.gitea/workflows/release.yaml`): Triggers on `v*` tag — cross-compiles for Linux targets and creates a Gitea release with downloadable binaries.
|
|
||||||
|
|
||||||
### Releasing the plugin
|
### Releasing the plugin
|
||||||
|
|
||||||
This repo has no release workflow — the plugin is distributed by plugin
|
Releases are cut from the Actions UI — **Release** workflow
|
||||||
managers cloning the git repo, so a "release" is just a git tag. There is no
|
(`.gitea/workflows/release.yaml`) → **Run workflow** → enter the version
|
||||||
automated version stamping (the old `scripts/set-version.sh` moved to
|
(e.g. `0.5.1`). It stamps `g:nuwiki_version` in `plugin/nuwiki.vim`,
|
||||||
nuwiki-rs with the Rust crates), so cut a plugin release by hand:
|
sanity-checks that the plugin still sources, commits `chore(release): X.Y.Z`,
|
||||||
|
pushes the `vX.Y.Z` tag, and publishes a Gitea release entry. The plugin ships
|
||||||
1. Bump `g:nuwiki_version` in `plugin/nuwiki.vim`.
|
no binary, so the release carries no assets by design.
|
||||||
2. Commit, then `git tag vX.Y.Z && git push --tags`.
|
|
||||||
|
|
||||||
The plugin and the `nuwiki-ls` server version independently; the binary is
|
The plugin and the `nuwiki-ls` server version independently; the binary is
|
||||||
fetched from nuwiki-rs's `latest` release at install time.
|
fetched from nuwiki-rs's `latest` release at install time. To do it by hand
|
||||||
|
instead: bump `g:nuwiki_version`, commit, then `git tag vX.Y.Z && git push --tags`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user