diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5897388..6e72215 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,9 @@ jobs: needs: build runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/download-artifact@v4 with: path: dist @@ -74,4 +77,47 @@ jobs: - name: Publish release assets env: GH_TOKEN: ${{ github.token }} - run: gh release create "${GITHUB_REF_NAME}" dist/* --repo "${GITHUB_REPOSITORY}" --generate-notes --verify-tag + shell: bash + run: | + previous_tag="$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)" + if [[ -n "$previous_tag" ]]; then + rewritten_notes=() + while IFS= read -r note; do + rewritten_notes+=("$note") + done < <(git diff --name-only --diff-filter=MD --no-renames \ + "$previous_tag" "$GITHUB_REF_NAME" -- 'release-notes/*.md') + release_notes=() + while IFS= read -r note; do + release_notes+=("$note") + done < <(git diff --name-only --diff-filter=A --no-renames \ + "$previous_tag" "$GITHUB_REF_NAME" -- 'release-notes/*.md' | LC_ALL=C sort) + else + rewritten_notes=() + release_notes=() + while IFS= read -r note; do + release_notes+=("$note") + done < <(git ls-tree -r --name-only "$GITHUB_REF_NAME" -- \ + 'release-notes/*.md' | LC_ALL=C sort) + fi + if (( ${#rewritten_notes[@]} > 0 )); then + echo "BLOCKED: Boatstack release notes are append-only:" >&2 + printf ' %s\n' "${rewritten_notes[@]}" >&2 + exit 1 + fi + if (( ${#release_notes[@]} == 0 )); then + echo "BLOCKED: this tag contains no release-level Boatstack message." >&2 + exit 1 + fi + release_body="$(mktemp)" + { + echo "## What's in this release" + echo + for note in "${release_notes[@]}"; do + cat "$note" + echo + done + } > "$release_body" + gh release create "${GITHUB_REF_NAME}" dist/* \ + --repo "$GITHUB_REPOSITORY" \ + --notes-file "$release_body" \ + --verify-tag diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 94b9f3b..c6960a8 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -49,6 +49,42 @@ jobs: echo "Boatstack already matches Intelligence Flow." exit 0 fi + git add -A + rewritten_notes=() + while IFS= read -r note; do + rewritten_notes+=("$note") + done < <(git diff --cached --name-only --diff-filter=MD --no-renames -- \ + 'release-notes/*.md') + if (( ${#rewritten_notes[@]} > 0 )); then + echo "BLOCKED: Boatstack release notes are append-only:" >&2 + printf ' %s\n' "${rewritten_notes[@]}" >&2 + exit 1 + fi + added_notes=() + while IFS= read -r note; do + added_notes+=("$note") + done < <(git diff --cached --name-only --diff-filter=A --no-renames -- \ + 'release-notes/*.md' | LC_ALL=C sort) + if (( ${#added_notes[@]} == 0 )); then + echo "BLOCKED: projected Boatstack changes require a release message." >&2 + exit 1 + fi + body_file="$(mktemp)" + { + echo "## What this sync releases" + echo + for note in "${added_notes[@]}"; do + cat "$note" + echo + done + echo "
" + echo "Projection provenance" + echo + echo "Generated from \`operatorstack/intelligence-flow@$SOURCE_COMMIT\`." + echo "Review provenance, tests, examples, and context-cost changes before merging." + echo + echo "
" + } > "$body_file" short="${SOURCE_COMMIT:0:12}" branch="sync/intelligence-flow-$short" existing="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url')" @@ -59,11 +95,10 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git switch -c "$branch" - git add -A git commit -m "Sync Boatstack from Intelligence Flow $short" git push --set-upstream origin "$branch" gh pr create \ --base main \ --head "$branch" \ --title "Sync Boatstack from Intelligence Flow $short" \ - --body "Generated from operatorstack/intelligence-flow@$SOURCE_COMMIT. Review provenance, tests, examples, and context-cost changes before merging." + --body-file "$body_file"