2424 TUIST_DEVELOPMENT_TEAM : ${{ secrets.DEVELOPMENT_TEAM }}
2525 TUIST_SPARKLE_PUBLIC_ED_KEY : ${{ secrets.SPARKLE_PUBLIC_ED_KEY }}
2626 steps :
27- - uses : actions/checkout@v4
27+ - uses : actions/checkout@v6
2828
2929 - name : Resolve version
3030 id : version
3838 fi
3939 echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"
4040
41- - uses : jdx/mise-action@v2
41+ - uses : jdx/mise-action@v4
4242
4343 - name : Import Developer ID certificate
4444 env :
@@ -159,33 +159,82 @@ jobs:
159159 --download-url-prefix "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/" \
160160 "$RUNNER_TEMP/release"
161161
162- - name : Add release- notes links to appcast
162+ - name : Embed release notes in appcast
163163 env :
164164 APPCAST : ${{ runner.temp }}/release/appcast.xml
165- REPO : ${{ github.repository }}
165+ VERSION : ${{ steps.version.outputs.version }}
166166 run : |
167- python3 - <<'PY'
168- import os, xml.etree.ElementTree as ET
169- sparkle = "http://www.andymatuschak.org/xml-namespaces/sparkle"
170- ET.register_namespace("sparkle", sparkle)
171- path, repo = os.environ["APPCAST"], os.environ["REPO"]
172- tree = ET.parse(path)
173- for item in tree.getroot().iter("item"):
174- version = item.find(f"{{{sparkle}}}shortVersionString")
175- if version is None or item.find(f"{{{sparkle}}}releaseNotesLink") is not None:
176- continue
177- link = ET.SubElement(item, f"{{{sparkle}}}releaseNotesLink")
178- link.text = f"https://github.com/{repo}/releases/tag/v{version.text}"
179- tree.write(path, xml_declaration=True, encoding="utf-8")
180- print("appcast: release-notes links added")
167+ set -euo pipefail
168+ # This version's changelog section (### promoted to ##), without the
169+ # install boilerplate — Sparkle shows the actual changes inline.
170+ awk -v h="## ${VERSION} " 'index($0, h) == 1 { g = 1; next } g && /^## / { exit } g { print }' \
171+ CHANGELOG.md | sed 's/^### /## /' > "$RUNNER_TEMP/changelog-section.md"
172+ SECTION="$RUNNER_TEMP/changelog-section.md" python3 - <<'PY'
173+ import os, re, html as H
174+ def inline(s):
175+ s = H.escape(s)
176+ s = re.sub(r"`([^`]+)`", r"<code>\1</code>", s)
177+ s = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", s)
178+ s = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<a href="\2">\1</a>', s)
179+ s = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", s)
180+ return s
181+ out, in_ul = [], False
182+ for line in open(os.environ["SECTION"], encoding="utf-8").read().splitlines():
183+ if line.startswith("## "):
184+ if in_ul: out.append("</ul>"); in_ul = False
185+ out.append("<h2>" + inline(line[3:].strip()) + "</h2>")
186+ elif line.startswith("- "):
187+ if not in_ul: out.append("<ul>"); in_ul = True
188+ out.append("<li>" + inline(line[2:].strip()) + "</li>")
189+ elif line.strip() == "":
190+ if in_ul: out.append("</ul>"); in_ul = False
191+ else:
192+ if in_ul: out.append("</ul>"); in_ul = False
193+ out.append("<p>" + inline(line.strip()) + "</p>")
194+ if in_ul: out.append("</ul>")
195+ html = "\n".join(out).strip()
196+ appcast = os.environ["APPCAST"]
197+ xml = open(appcast, encoding="utf-8").read()
198+ if html and "<description>" not in xml:
199+ desc = "<description><![CDATA[\n" + html + "\n]]></description>\n "
200+ xml = xml.replace("</item>", desc + "</item>", 1)
201+ open(appcast, "w", encoding="utf-8").write(xml)
202+ print("appcast: embedded release notes")
203+ else:
204+ print("appcast: skipped (no notes or description already present)")
181205 PY
182206
207+ - name : Build release notes from CHANGELOG
208+ env :
209+ VERSION : ${{ steps.version.outputs.version }}
210+ TAG : ${{ github.ref_name }}
211+ REPO : ${{ github.repository }}
212+ run : |
213+ set -euo pipefail
214+ NOTES="$RUNNER_TEMP/release-notes.md"
215+ # Pull this version's section from CHANGELOG.md (the source of truth),
216+ # promoting its ### subheadings to ## for the GitHub release.
217+ awk -v h="## ${VERSION} " 'index($0, h) == 1 { g = 1; next } g && /^## / { exit } g { print }' \
218+ CHANGELOG.md | sed 's/^### /## /' > "$NOTES"
219+ if [ ! -s "$NOTES" ]; then
220+ echo "See the [changelog](https://github.com/${REPO}/blob/main/CHANGELOG.md)." > "$NOTES"
221+ fi
222+ # Append the install/update boilerplate (kept out of CHANGELOG.md).
223+ {
224+ echo ""
225+ echo "## Install / Update"
226+ echo ""
227+ echo "- Homebrew: \`brew upgrade --cask tatami\`"
228+ echo "- Direct: [Tatami-${VERSION}.dmg](https://github.com/${REPO}/releases/download/${TAG}/Tatami-${VERSION}.dmg)"
229+ echo "- In-app: Sparkle will pick this up on its next check."
230+ } >> "$NOTES"
231+
183232 - name : Publish GitHub Release
184233 run : |
185234 gh release create "${{ github.ref_name }}" \
186235 "$RUNNER_TEMP/Tatami-${{ steps.version.outputs.version }}.dmg" \
187236 --title "${{ github.ref_name }}" \
188- --generate- notes
237+ --notes-file "$RUNNER_TEMP/release- notes.md"
189238 env :
190239 GH_TOKEN : ${{ github.token }}
191240
@@ -214,13 +263,25 @@ jobs:
214263 git commit -am "tatami ${VERSION}"
215264 git push
216265
217- - name : Publish appcast to GitHub Pages
266+ - name : Assemble GitHub Pages site (landing page + appcast)
267+ env :
268+ VERSION : ${{ steps.version.outputs.version }}
218269 run : |
219270 set -euo pipefail
220271 mkdir -p "$RUNNER_TEMP/site"
272+ # Static pages (configuration.html / releases.html render their source
273+ # of truth at runtime; index.html gets the release version injected).
274+ cp web/*.html web/*.css "$RUNNER_TEMP/site/"
275+ sed "s/__VERSION__/${VERSION}/g" web/index.html > "$RUNNER_TEMP/site/index.html"
276+ cp Resources/Marketing/app-icon.png "$RUNNER_TEMP/site/icon.png"
277+ # Cache-bust the stylesheet so a CSS change is never masked by a
278+ # stale browser cache.
279+ BUST="${GITHUB_SHA:0:8}"
280+ sed -i "s#\"./style.css\"#\"./style.css?v=${BUST}\"#g" "$RUNNER_TEMP/site"/*.html
281+ # Sparkle feed.
221282 cp "$RUNNER_TEMP/release/appcast.xml" "$RUNNER_TEMP/site/appcast.xml"
222283
223- - uses : actions/upload-pages-artifact@v3
284+ - uses : actions/upload-pages-artifact@v5
224285 with :
225286 path : ${{ runner.temp }}/site
226287
@@ -232,4 +293,4 @@ jobs:
232293 url : ${{ steps.deployment.outputs.page_url }}
233294 steps :
234295 - id : deployment
235- uses : actions/deploy-pages@v4
296+ uses : actions/deploy-pages@v5
0 commit comments