diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..7eb9516 --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -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 </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" diff --git a/development/ONBOARDING.md b/development/ONBOARDING.md index 8e733d8..de1a8f2 100644 --- a/development/ONBOARDING.md +++ b/development/ONBOARDING.md @@ -59,11 +59,13 @@ nuwiki/ │ └── .gitea/ └── 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 -[nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) repository.) +(The Rust server's *binary* build/cross-compile workflow lives in the separate +[nuwiki-rs](https://code.gfran.co/gffranco/nuwiki-rs) repository; this repo's +release just tags the plugin.) ## Server Component @@ -200,23 +202,23 @@ To modify the LSP server itself, clone the [nuwiki-rs](https://code.gfran.co/gff ## CI/CD 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: -- Release (`.gitea/workflows/release.yaml`): Triggers on `v*` tag — cross-compiles for Linux targets and creates a Gitea release with downloadable binaries. +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. ### Releasing the plugin -This repo has no release workflow — the plugin is distributed by plugin -managers cloning the git repo, so a "release" is just a git tag. There is no -automated version stamping (the old `scripts/set-version.sh` moved to -nuwiki-rs with the Rust crates), so cut a plugin release by hand: - -1. Bump `g:nuwiki_version` in `plugin/nuwiki.vim`. -2. Commit, then `git tag vX.Y.Z && git push --tags`. +Releases are cut from the Actions UI — **Release** workflow +(`.gitea/workflows/release.yaml`) → **Run workflow** → enter the version +(e.g. `0.5.1`). It stamps `g:nuwiki_version` in `plugin/nuwiki.vim`, +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 +no binary, so the release carries no assets by design. 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