diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml deleted file mode 100644 index 51c02b6f..00000000 --- a/.github/workflows/release-please.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Release Please - -on: - push: - branches: - - main - -permissions: {} - -jobs: - release-please: - name: Release Please - if: github.repository == 'zealdocs/zeal' - runs-on: ubuntu-latest - - steps: - - name: Generate App Token - id: app-token - uses: actions/create-github-app-token@v3 - with: - client-id: ${{ secrets.CI_APP_ID }} - private-key: ${{ secrets.CI_APP_PRIVATE_KEY }} - permission-contents: write - permission-pull-requests: write - - - uses: googleapis/release-please-action@v5 - with: - token: ${{ steps.app-token.outputs.token }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json diff --git a/.gitignore b/.gitignore index 6076e27b..98d5d42f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ CMakeUserPresets.json # Claude Code .claude/settings.local.json + +# DotENV +.env diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 02f17d9d..00000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.8.1" -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 67f98d52..718edc19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,16 +10,17 @@ set(CMAKE_ERROR_DEPRECATED TRUE) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") # VERSION is the next development version, bumped automatically after each release. -# ZEAL_RELEASE_VERSION is the last released version, updated by release-please. +# ZEAL_RELEASE_VERSION is the last released version, updated by `just release-prepare`. # When both match, the build is treated as a release; otherwise it's a dev build. +# The `# x-version` marker on each version line acts as a sed anchor for the bump. project(Zeal - VERSION 0.8.2 # x-release-please-version + VERSION 0.8.2 # x-version DESCRIPTION "A simple documentation browser." HOMEPAGE_URL "https://zealdocs.org" LANGUAGES CXX ) -set(ZEAL_RELEASE_VERSION "0.8.1") # x-release-please-version +set(ZEAL_RELEASE_VERSION "0.8.1") # x-version include(ZealHelpers) diff --git a/REUSE.toml b/REUSE.toml index 61abb339..54b46ae0 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -24,6 +24,7 @@ path = [ "**/*.cmake", "**/CMakeLists.txt", "CMakePresets.json", + "justfile", "pkg/**/*", "vcpkg.json", @@ -36,9 +37,8 @@ path = [ ".gitattributes", ".github/**/*", ".gitignore", - ".release-please-manifest.json", "CHANGELOG.md", - "release-please-config.json", + "tools/cliff.toml", ] SPDX-FileCopyrightText = "Oleg Shparber, et al. " SPDX-License-Identifier = "MIT" diff --git a/justfile b/justfile new file mode 100644 index 00000000..c481dd0d --- /dev/null +++ b/justfile @@ -0,0 +1,69 @@ +# Zeal dev and release recipes. +# Uses CMake presets defined in CMakePresets.json. +# Run `just` (no args) to see the recipe list. + +set shell := ["bash", "-cu"] +set windows-shell := ["sh", "-cu"] + +[private] +default: + @just --list + +# Configure a build directory using a preset. +[group('dev')] +configure preset="dev": + cmake --preset {{preset}} + +# Build using a preset. +[group('dev')] +build preset="dev": + cmake --build --preset {{preset}} + +# Configure, build, and run the test suite. +[group('dev')] +test: + cmake --preset testing + cmake --build --preset testing + ctest --preset testing + +# Apply clang-format across the tree (use -Fix to apply, -Staged for staged files only). +[group('dev')] +format *args: + pwsh tools/run-clang-format.ps1 {{args}} + +# Run clang-tidy (use -Fix to apply, -Staged for staged files only, -Check for a single check). +[group('dev')] +lint *args: configure + pwsh tools/run-clang-tidy.ps1 {{args}} + +# Remove all build directories. +[group('dev')] +clean: + rm -rf build + +# Generate release notes for the given version, insert the corresponding +# entry into the appdata, and bump CMakeLists.txt versions. +# Review the resulting changes with `git diff` before running `just release-push`. +# Maintainer use only. +[group('release')] +release-prepare version: + bash tools/release-prepare.sh {{version}} + +# Commit appdata + version bump, push, create the draft release with the +# generated notes, then tag and push the tag (which triggers build CI). +# The actual GitHub Publish click still happens manually in the UI. +# `remote` controls which remote (and matching GitHub repo) receives the push; +# default is `origin` (works for fork testing). For an upstream release, run +# `just release-push 0.8.2 upstream` (or whatever you've named the upstream remote). +# Maintainer use only. +[group('release')] +release-push version remote="origin": + git add assets/freedesktop/org.zealdocs.zeal.appdata.xml.in CMakeLists.txt + git commit -m "chore: release v{{version}}" + git push {{remote}} HEAD + gh release create v{{version}} --draft --notes-file build/release-notes.md \ + --repo "$(git remote get-url {{remote}} | sed -E 's|^.*github\.com[:/]||; s|\.git$||')" + git tag v{{version}} + git push {{remote}} v{{version}} + @echo "Pushed v{{version}} with draft release. CI will upload artifacts." + @echo "When CI is done, open the draft on GitHub and click Publish." diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index ce58dd85..00000000 --- a/release-please-config.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "release-type": "go", - "skip-changelog": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": true, - "draft": true, - "force-tag-creation": true, - "include-v-in-tag": true, - "pull-request-title-pattern": "chore: release v${version}", - "changelog-sections": [ - {"type": "feat", "section": "Features"}, - {"type": "fix", "section": "Bug Fixes"}, - {"type": "perf", "section": "Performance"}, - {"type": "docs", "section": "Documentation"}, - {"type": "build", "section": "Build System"}, - {"type": "ci", "section": "CI/CD"}, - {"type": "chore", "hidden": true}, - {"type": "refactor", "hidden": true}, - {"type": "style", "hidden": true}, - {"type": "test", "hidden": true} - ], - "extra-files": [ - "CMakeLists.txt" - ], - "packages": { - ".": {} - } -} diff --git a/.github/workflows/release/cliff.toml b/tools/cliff.toml similarity index 61% rename from .github/workflows/release/cliff.toml rename to tools/cliff.toml index a4adeea2..8b58cc34 100644 --- a/.github/workflows/release/cliff.toml +++ b/tools/cliff.toml @@ -1,40 +1,26 @@ # git-cliff ~ configuration file # https://git-cliff.org/docs/configuration -# Set via GITHUB_REPO and GITHUB_TOKEN environment variables. -#[remote.github] -#owner = "zealdocs" -#repo = "zeal" -#token = "" +[remote.github] +owner = "zealdocs" +repo = "zeal" +# Token is read from GITHUB_TOKEN env var (gh's auth) and is needed only to +# enrich commits with PR titles and contributor info. [changelog] # Template docs: https://keats.github.io/tera/docs/. body = """\ - -{% if version and previous.version %} - [Full Changelog]({{ self::diff_url() }}) | [Resolved Issues]({{ self::remote_url() }}/milestone/TBD?closed=1) -{% endif %}\ - {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | striptags | trim }} {% for commit in commits %} - {% if commit.github.pr_title -%} - {%- set commit_message = commit.github.pr_title -%} - {%- else -%} - {%- set commit_message = commit.message -%} - {%- endif -%} - {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\ {{ commit.message | split(pat="\n") | first | trim }} \ ({{ self::commit_link(id=commit.id) }})\ {% endfor %} {% endfor %} ---- +{%- if github and github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} -{%- if github -%}\ -{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} - {% raw %}\n{% endraw -%} - #### New Contributors -{%- endif %} +#### New Contributors {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} - @{{ contributor.username }} made their first contribution {%- if contributor.pr_number %} in \ @@ -42,6 +28,11 @@ body = """\ {%- endif %} {%- endfor -%} {%- endif -%} +{%- if version and previous.version %} +--- + +[Full Changelog]({{ self::diff_url() }}){% if extra and extra.milestone %} | [Resolved Issues]({{ self::remote_url() }}/milestone/{{ extra.milestone }}?closed=1){% endif %} +{% endif %}\ {%- macro remote_url() -%} https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} @@ -61,39 +52,37 @@ body = """\ """ # Remove the leading and trailing whitespace from the template. trim = true -# Template for the changelog footer. -footer = """ -""" -# Postprocessors. -postprocessors = [] [git] # Parse the commits based on https://www.conventionalcommits.org. conventional_commits = true # Filter out the commits that are not conventional. filter_unconventional = true -# Process each line of a commit as an individual commit. -split_commits = false # Regex for preprocessing the commit messages. commit_preprocessors = [ # Remove issue numbers from commits. { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, ] # Regex for parsing and grouping commits. +# The `` prefix on each group name is a sort hack: cliff orders groups +# alphabetically, the HTML comments establish the desired numeric order, and the +# `striptags` filter in the body template strips them before display. commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, { message = "^perf", group = "Performance" }, - { message = "^doc", group = "Documentation" }, + { message = "^docs", group = "Documentation" }, { message = "^build", group = "Build System" }, - { message = "^ci", group = "CI/CD" }, - # Skipped groups: + # Skipped groups (kept in git history but excluded from release notes): + { message = "^ci", skip = true }, { message = "^chore", skip = true }, { message = "^refactor", skip = true }, + { message = "^style", skip = true }, + { message = "^test", skip = true }, + { message = "^revert", skip = true }, ] -# Filter out the commits that are not matched by commit parsers. -filter_commits = false -# Sort the tags topologically. -topo_order = false -# Sort the commits inside sections by oldest/newest order. -sort_commits = "oldest" +# Drop commits whose type doesn't match any parser above (typos, weird types). +filter_commits = true +# Always include breaking-change commits (`feat!:`, `fix!:`, etc.) even if a +# skip rule above would otherwise drop them. +protect_breaking_commits = true diff --git a/tools/release-prepare.sh b/tools/release-prepare.sh new file mode 100644 index 00000000..d8c64bcc --- /dev/null +++ b/tools/release-prepare.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# +# release-prepare.sh - Generate release notes and stage version bumps locally. +# +# SPDX-FileCopyrightText: Oleg Shparber, et al. +# SPDX-License-Identifier: MIT +# +# Generates release notes via git-cliff and AppStream description bullets via +# Claude, inserts the new entry into the appdata, and bumps the +# version markers in CMakeLists.txt. Review the result with `git diff` before +# running `just release-push `. +# +# Usage: tools/release-prepare.sh +# + +set -euo pipefail +trap 'echo "ERROR: failed at line ${LINENO}: ${BASH_COMMAND}" >&2' ERR + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +VERSION="$1" + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version: '${VERSION}'. Expected major.minor.patch (e.g. 0.9.0)." >&2 + exit 2 +fi + +# Canonical upstream repo for milestone lookups, regardless of which fork the +# maintainer is currently working on. Cliff itself is hardcoded to the same +# repo via [remote.github] in tools/cliff.toml. +CANONICAL_REPO="zealdocs/zeal" + +# Lowercase to avoid clobbering the Windows env var of the same name (APPDATA +# is what gh uses to find its config). Local script vars stay lowercase. +appdata="assets/freedesktop/org.zealdocs.zeal.appdata.xml.in" +cmakelists="CMakeLists.txt" + +mkdir -p build + +if grep -qF "version=\"${VERSION}\"" "$appdata"; then + echo "${appdata} already has a for ${VERSION}; aborting." >&2 + echo "Run 'git restore .' to redo." >&2 + exit 1 +fi + +milestone=$(gh api "repos/${CANONICAL_REPO}/milestones?state=all" \ + --jq ".[] | select(.title == \"${VERSION}\") | .number" || true) + +if [ -n "$milestone" ]; then + echo "Linking upstream milestone #${milestone} (\"${VERSION}\")" + # cliff's --context flag in 2.x is a debug print, not a way to set template + # vars. The documented mechanism for custom template data is the `extra` + # field on release/commit objects: dump cliff's context, jq-inject the + # milestone into the release's `extra`, render via --from-context. + git cliff --tag "v${VERSION}" --unreleased --config tools/cliff.toml --context \ + > build/cliff-context.json + jq ".[0].extra.milestone = ${milestone}" build/cliff-context.json \ + > build/cliff-context.with-milestone.json + git cliff --config tools/cliff.toml --from-context build/cliff-context.with-milestone.json \ + > build/release-notes.md +else + echo "No upstream milestone titled \"${VERSION}\"; skipping milestone link." + git cliff --tag "v${VERSION}" --unreleased --config tools/cliff.toml > build/release-notes.md +fi + +claude -p "Read this git-cliff release changelog and summarize the \ +user-facing changes as up to 10
  • ...
  • bullets for an AppStream \ +. AppStream is read by Linux software centers, so skip \ +Windows-only and macOS-only changes. Each bullet should be concrete: the \ +specific problem fixed or capability added, in terms a non-developer \ +recognizes. Use past tense and start each bullet with one of: Added, \ +Changed, Removed, Deprecated, Fixed. Order bullets by category in that \ +sequence (Added first, Fixed last). No trailing period on bullets. Skip \ +internal refactors, build/CI/dependency commits, and changes a user \ +wouldn't notice. Avoid common AI cliches (em-dashes, 'delve into', \ +'leverage', 'robust', 'ensure', 'seamless', 'comprehensive'). Output \ +bullets only, no wrapping
      ." \ + < build/release-notes.md \ + > build/appdata-bullets.txt + +today=$(date -u +%Y-%m-%d) +indented=$(sed 's/^[[:space:]]*//; s/^/ /' build/appdata-bullets.txt) +cat > build/release-block.xml < + +
        +${indented} +
      +
      + https://github.com/${CANONICAL_REPO}/releases/tag/v${VERSION} + +EOF +sed -i "/@ZEAL_APPSTREAM_DEV_RELEASE@/r build/release-block.xml" "$appdata" + +sed -i "s/ VERSION [0-9.]\+ # x-version/ VERSION ${VERSION} # x-version/" "$cmakelists" +sed -i "s/set(ZEAL_RELEASE_VERSION \"[0-9.]\+\") # x-version/set(ZEAL_RELEASE_VERSION \"${VERSION}\") # x-version/" "$cmakelists" + +echo "---" +echo "Applied:" +printf ' %-55s %s\n' \ + "${appdata}" "new entry for v${VERSION}" \ + "${cmakelists}" "VERSION and ZEAL_RELEASE_VERSION bumped to ${VERSION}" \ + "build/release-notes.md" "used by 'just release-push' as --notes-file" +echo +echo "Review with: git diff" +echo "Then run: just release-push ${VERSION}" +echo +echo "To redo: git restore . && just release-prepare ${VERSION}"