All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m4s
Follow-ups of the review of PR #418. - check-latest-json.jq wants exactly one document and release.yml calls it with -s: under jq 1.6, the release container's, an empty file passed the check (no output, status 0) and two documents were judged on the last one, so the job would have deleted the published manifest and uploaded the bad one. Two tests cover both cases. - build-latest-json.test.mjs runs the text of release.yml's "Publish latest.json to package registry" step against a fake curl: DELETE then PUT on a valid manifest, exit 1 and no request on a refused one. - check-frontend no longer ignores .claude/** and release.yml (decision of Max): the test reads verify-release.sh and runs the Publish step, so "on every pull request" holds. REVIEW.md, CLAUDE.md and architecture.md follow. - release.yml: stale ".deb fallback" comment fixed. - qa-update-cycle.md: a tauri dev build on Linux shows TargetsNotFound, expected since #418. Refs #461 Resolves #419 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
240 lines
10 KiB
YAML
240 lines
10 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
jobs:
|
|
build-and-release:
|
|
runs-on: ubuntu
|
|
container: ubuntu:22.04
|
|
env:
|
|
PATH: /root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
steps:
|
|
- name: Install base tools and runtimes
|
|
run: |
|
|
apt-get update
|
|
apt-get install -y curl wget git sudo ca-certificates gnupg
|
|
# Install Node.js 24 (#376). Trust note, kept on purpose without a
|
|
# pinned tarball: NodeSource's script runs as root in this
|
|
# container, and the Node.js it installs then runs `npx tauri build`
|
|
# with TAURI_SIGNING_PRIVATE_KEY in its environment. A compromised
|
|
# script or package would reach the updater signing key, and the
|
|
# publishing tokens of the later steps. See docs/architecture.md
|
|
# (CI/CD, release.yml).
|
|
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
|
apt-get install -y nodejs
|
|
# Install Rust
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
rustc --version
|
|
cargo --version
|
|
node --version
|
|
npm --version
|
|
|
|
- name: Checkout
|
|
uses: https://github.com/actions/checkout@v4
|
|
|
|
- name: Install Linux dependencies
|
|
run: |
|
|
apt-get install -y build-essential libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf jq libssl-dev xdg-utils libdbus-1-dev
|
|
|
|
- name: Install Windows cross-compile dependencies
|
|
run: |
|
|
apt-get install -y lld llvm clang nsis
|
|
rustup target add x86_64-pc-windows-msvc
|
|
cargo install --locked cargo-xwin
|
|
|
|
- name: Install frontend dependencies
|
|
run: npm ci
|
|
|
|
- name: Build Tauri
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
npx tauri build --bundles deb,rpm
|
|
|
|
- name: Build Tauri Windows
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
npx tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc
|
|
|
|
- name: Collect release files
|
|
run: |
|
|
mkdir -p release-assets
|
|
cp src-tauri/target/release/bundle/deb/*.deb release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/deb/*.deb.sig release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/rpm/*.rpm release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/rpm/*.rpm.sig release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig release-assets/ 2>/dev/null || true
|
|
ls -la release-assets/
|
|
|
|
- name: Copy changelogs to public
|
|
run: |
|
|
cp CHANGELOG.md public/CHANGELOG.md
|
|
cp CHANGELOG.fr.md public/CHANGELOG.fr.md
|
|
|
|
- name: Extract changelog
|
|
id: changelog
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME#v}"
|
|
NOTES=$(sed -n "/^## \[${TAG}\]/,/^## /{/^## \[${TAG}\]/d;/^## /d;p}" CHANGELOG.md)
|
|
if [ -z "$NOTES" ]; then
|
|
NOTES=$(sed -n "/^## ${TAG}/,/^## /{/^## ${TAG}/d;/^## /d;p}" CHANGELOG.md)
|
|
fi
|
|
NOTES=$(echo "$NOTES" | sed -e '/./,$!d' -e :a -e '/^\n*$/{$d;N;ba}')
|
|
# Random delimiter (#320): a changelog line equal to a fixed one
|
|
# would end the value there and turn the lines after it into step
|
|
# outputs.
|
|
DELIM="CHANGELOG_EOF_$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')"
|
|
{
|
|
echo "notes<<${DELIM}"
|
|
echo "$NOTES"
|
|
echo "${DELIM}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Generate latest.json
|
|
env:
|
|
CHANGELOG_NOTES: ${{ steps.changelog.outputs.notes }}
|
|
run: |
|
|
# Three keys: one per Linux package (.deb, .rpm) and the NSIS
|
|
# installer, no generic linux-x86_64 key (#320); fails unless it finds
|
|
# exactly one .deb, .rpm and -setup.exe, each with a non-empty .sig.
|
|
# Notes reach jq as a file, as data.
|
|
# Called as scripts/build-latest-json.test.mjs calls it.
|
|
printf '%s' "$CHANGELOG_NOTES" > /tmp/release-notes.md
|
|
bash -e -o pipefail scripts/build-latest-json.sh \
|
|
release-assets \
|
|
"${GITHUB_REF_NAME#v}" \
|
|
/tmp/release-notes.md \
|
|
"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}" \
|
|
> release-assets/latest.json
|
|
|
|
echo "Generated latest.json:"
|
|
cat release-assets/latest.json
|
|
|
|
- name: Create release and upload assets
|
|
env:
|
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CHANGELOG_NOTES: ${{ steps.changelog.outputs.notes }}
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME}"
|
|
API_URL="${GITHUB_SERVER_URL}/api/v1"
|
|
REPO="${GITHUB_REPOSITORY}"
|
|
|
|
BODY="${CHANGELOG_NOTES}
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
**Windows** : Téléchargez le fichier \`.exe\` ci-dessous.
|
|
**Linux** : Téléchargez le fichier \`.deb\` (Debian, Ubuntu) ou \`.rpm\` (Fedora, openSUSE) ci-dessous."
|
|
|
|
# Delete existing release for this tag (if any) to allow re-creation
|
|
EXISTING=$(curl -s "${API_URL}/repos/${REPO}/releases/tags/${TAG}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}")
|
|
EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
|
|
if [ -n "$EXISTING_ID" ]; then
|
|
echo "Deleting existing release ID: $EXISTING_ID"
|
|
curl -s -X DELETE \
|
|
"${API_URL}/repos/${REPO}/releases/${EXISTING_ID}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}"
|
|
fi
|
|
|
|
# Create release
|
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
|
"${API_URL}/repos/${REPO}/releases" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n \
|
|
--arg tag "$TAG" \
|
|
--arg name "Simpl'Résultat ${TAG}" \
|
|
--arg body "$BODY" \
|
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')")
|
|
|
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
|
echo "Created release ID: $RELEASE_ID"
|
|
|
|
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
|
|
echo "ERROR: Failed to create release"
|
|
echo "$RELEASE_RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
# Upload all assets
|
|
for file in release-assets/*; do
|
|
FILENAME=$(basename "$file")
|
|
ENCODED_FILENAME=$(printf '%s' "$FILENAME" | jq -sRr @uri)
|
|
FILESIZE=$(stat -c%s "$file")
|
|
echo "Uploading: $FILENAME (${FILESIZE} bytes)"
|
|
HTTP_CODE=$(curl -w "%{http_code}" --max-time 300 -X POST \
|
|
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${ENCODED_FILENAME}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@${file}" \
|
|
-o /tmp/upload_response.json)
|
|
echo "HTTP $HTTP_CODE"
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "Upload failed:"
|
|
cat /tmp/upload_response.json
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
echo "Release created: ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
|
|
|
|
- name: Publish latest.json to package registry
|
|
env:
|
|
FORGEJO_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
|
run: |
|
|
# This text is run by scripts/build-latest-json.test.mjs, with a fake
|
|
# curl: keep it self-contained, no workflow expressions (dollar,
|
|
# double brace) in run:, pass values through env: (#419).
|
|
#
|
|
# Check the manifest before the DELETE below (#320): once the old
|
|
# version is deleted, installed apps find no update until a valid
|
|
# one is uploaded. A failed check stops the job, registry untouched.
|
|
# The filter lives in scripts/check-latest-json.jq; -s makes it
|
|
# refuse an empty file or one with two documents (#419).
|
|
if ! jq -e -s --arg version "${GITHUB_REF_NAME#v}" \
|
|
-f scripts/check-latest-json.jq release-assets/latest.json > /dev/null; then
|
|
echo "ERROR: release-assets/latest.json failed scripts/check-latest-json.jq, registry left as is:"
|
|
cat release-assets/latest.json
|
|
exit 1
|
|
fi
|
|
|
|
# DELETE uses API v1, PUT uses the package upload API
|
|
DELETE_URL="${GITHUB_SERVER_URL}/api/v1/packages/${GITHUB_REPOSITORY_OWNER}/generic/simpl-resultat/latest"
|
|
UPLOAD_URL="${GITHUB_SERVER_URL}/api/packages/${GITHUB_REPOSITORY_OWNER}/generic/simpl-resultat/latest"
|
|
|
|
# Delete the old package version to avoid 409 conflicts
|
|
echo "Deleting old package version (if any)..."
|
|
DEL_CODE=$(curl -s -w "%{http_code}" -X DELETE \
|
|
"${DELETE_URL}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-o /tmp/del_response.json)
|
|
echo "Delete HTTP $DEL_CODE"
|
|
# 204 = deleted, 404 = didn't exist (both OK)
|
|
if [ "$DEL_CODE" != "204" ] && [ "$DEL_CODE" != "404" ]; then
|
|
echo "WARNING: Unexpected delete response:"
|
|
cat /tmp/del_response.json
|
|
fi
|
|
|
|
echo "Uploading latest.json to package registry..."
|
|
HTTP_CODE=$(curl -w "%{http_code}" -X PUT \
|
|
"${UPLOAD_URL}/latest.json" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary "@release-assets/latest.json" \
|
|
-o /tmp/pkg_response.json)
|
|
echo "Upload HTTP $HTTP_CODE"
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "ERROR: Failed to publish latest.json:"
|
|
cat /tmp/pkg_response.json
|
|
exit 1
|
|
fi
|