feat/release-page #1
2 changed files with 330 additions and 283 deletions
330
.forgejo/workflows/release.yml
Normal file
330
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
# Build and publish a postit-debian release on the Forgejo instance.
|
||||
#
|
||||
# Triggered by a push of a git tag. Validates the tag/changelog pair,
|
||||
# builds the .deb for amd64 and arm64 (sequential cross-RID .NET
|
||||
# publishes on a single amd64 runner container — matrix is not used
|
||||
# here because the runner image pazof/yavsc-build-env has no Node,
|
||||
# so actions/upload-artifact and actions/download-artifact (which
|
||||
# require Node) cannot be used to pass the .deb files between jobs.
|
||||
# All in one job, like yavsc's .forgejo/workflows/release.yml.),
|
||||
# then publishes a Forgejo release via the REST API and uploads both
|
||||
# .deb files as assets.
|
||||
#
|
||||
# Authentication: the runner auto-provides a token scoped to the
|
||||
# repository. We read it once into the local env var FORGEJO_TOKEN
|
||||
# and never reference the runtime-level name again.
|
||||
#
|
||||
# Why bash + jq + curl, no third-party actions: the runner's docker
|
||||
# label points at pazof/yavsc-build-env, a Debian image with jq but
|
||||
# without Node.js or python3. Any action like actions/checkout,
|
||||
# rasterstate/forgejo-release-action, actions/upload-artifact,
|
||||
# actions/download-artifact, etc. fails with "executable file not
|
||||
# found in $PATH". Same constraint as yavsc's
|
||||
# .forgejo/workflows/release.yml.
|
||||
#
|
||||
# Re-tag policy (cf. AGENTS.md "Re-tag = le mal") : on push de tag
|
||||
# ou dispatch, on *réutilise* la release existante (via PATCH) au
|
||||
# lieu d'en créer une nouvelle. Un tag Git pointe vers un commit
|
||||
# fixe ; si le binaire change (rebuild après modif du packaging),
|
||||
# on met à jour la release existante plutôt que d'en multiplier
|
||||
# pour un même tag. Le permalien /releases/tag/<tag> reste stable.
|
||||
#
|
||||
# Inter-step state: we persist values between steps via a plain
|
||||
# env file under /tmp, sourced at the top of each step that needs
|
||||
# it. This keeps the workflow self-contained and avoids any
|
||||
# runtime variable names we did not choose.
|
||||
name: Forgejo Release postit-deb
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag pazof/yavsc à packager (requis en dispatch, ex. 1.0.6 ou 1.0.7-rc1).'
|
||||
required: true
|
||||
type: string
|
||||
force_unstable:
|
||||
description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# Job unique : validation tag/CHANGELOG + build amd64 + build
|
||||
# arm64 + publication via l'API REST Forgejo (pas d'actions
|
||||
# tierces Node).
|
||||
release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/pazof/yavsc-build-env:debian12-dotnet10-android36-v2
|
||||
env:
|
||||
STATE_FILE: /tmp/release-state.env
|
||||
steps:
|
||||
- name: Installer les pré-requis de build (debhelper + icônes)
|
||||
# L'image runner fournit déjà dotnet-sdk-10.0, git, jq,
|
||||
# curl. On ajoute les outils spécifiques au packaging
|
||||
# Debian (debhelper, imagemagick pour les icônes .png
|
||||
# via `convert`, librsvg2-bin pour le SVG).
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential debhelper imagemagick librsvg2-bin \
|
||||
ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
: > "$STATE_FILE"
|
||||
|
||||
- name: Clone du repo au tag demandé
|
||||
env:
|
||||
TAG: ${{ forgejo.event_name == 'push' && forgejo.ref_name || inputs.tag }}
|
||||
run: |
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::No tag provided. In workflow_dispatch, set the 'tag' input."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd /src
|
||||
if [[ ! -d _src/.git ]]; then
|
||||
# Clone unshallow pour préserver l'historique — utile
|
||||
# si un futur test en a besoin. Le coût est marginal
|
||||
# pour ce repo (< 50 commits).
|
||||
git clone https://forgejo.pschneider.fr/notazof/postit-debian.git _src
|
||||
fi
|
||||
|
||||
cd _src
|
||||
git fetch --tags --force --prune origin
|
||||
git checkout "$TAG"
|
||||
|
||||
echo "Checked out at $(git rev-parse HEAD) on tag $TAG"
|
||||
echo "TAG=$TAG" >> "$STATE_FILE"
|
||||
|
||||
- name: Valider le tag et la section CHANGELOG
|
||||
run: |
|
||||
source "$STATE_FILE"
|
||||
cd /src/_src
|
||||
echo "Validating tag $TAG"
|
||||
|
||||
# Parse semver : MAJOR.MINOR.PATCH[-SUFFIX]
|
||||
if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
||||
echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
PATCH="${BASH_REMATCH[3]}"
|
||||
SUFFIX="${BASH_REMATCH[4]}"
|
||||
|
||||
# Classification du canal par parité du patch.
|
||||
if [[ -n "$SUFFIX" ]]; then
|
||||
CHANNEL="unstable"
|
||||
elif (( PATCH % 2 == 0 )); then
|
||||
CHANNEL="stable"
|
||||
else
|
||||
CHANNEL="preview"
|
||||
fi
|
||||
|
||||
echo "Tag $TAG classifié comme channel=$CHANNEL"
|
||||
|
||||
# Fail-fast sur instable sauf opt-in explicite.
|
||||
if [[ "$CHANNEL" == "unstable" && "${FORCE_UNSTABLE:-false}" != "true" ]]; then
|
||||
echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish."
|
||||
echo "Set force_unstable=true via workflow_dispatch to override."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Lecture du CHANGELOG.md (doit exister à la racine du repo).
|
||||
if [[ ! -f CHANGELOG.md ]]; then
|
||||
echo "::error::CHANGELOG.md not found at repo root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extraction de la section [TAG]. On garde le titre
|
||||
# (ligne `## [TAG] - channel`) pour la vérification du
|
||||
# canal, puis on l'exclut du body envoyé à la release.
|
||||
BODY=$(awk -v tag="[$TAG]" '
|
||||
/^## \[/ {
|
||||
if (in_section) exit
|
||||
if (index($0, tag) > 0) {
|
||||
in_section=1
|
||||
print
|
||||
next
|
||||
}
|
||||
}
|
||||
in_section { print }
|
||||
' CHANGELOG.md)
|
||||
|
||||
if [[ -z "$BODY" ]]; then
|
||||
echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md."
|
||||
echo "Add a '## [$TAG] - $CHANNEL' section before tagging."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérification cohérence du canal déclaré dans le titre.
|
||||
TITLE=$(echo "$BODY" | head -1)
|
||||
if [[ "$TITLE" != *" - $CHANNEL"* ]]; then
|
||||
echo "::error::Section title '$TITLE' must declare suffix '- $CHANNEL' to match tag parity."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_BODY=$(echo "$BODY" | tail -n +2)
|
||||
IS_PRERELEASE=$([ "$CHANNEL" = "stable" ] && echo false || echo true)
|
||||
|
||||
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
||||
|
||||
# Persist values for the next steps via our local state file.
|
||||
{
|
||||
echo "RELEASE_BODY<<EOF"
|
||||
echo "$RELEASE_BODY"
|
||||
echo "EOF"
|
||||
echo "IS_PRERELEASE=$IS_PRERELEASE"
|
||||
} >> "$STATE_FILE"
|
||||
|
||||
- name: Build .deb amd64
|
||||
env:
|
||||
POSTIT_RUNTIME: linux-x64
|
||||
run: |
|
||||
source "$STATE_FILE"
|
||||
cd /src/_src
|
||||
echo "→ Building amd64 for POSTIT_GIT_TAG=$TAG"
|
||||
make deb POSTIT_GIT_TAG="$TAG" POSTIT_RUNTIME=linux-x64
|
||||
|
||||
- name: Build .deb arm64
|
||||
env:
|
||||
POSTIT_RUNTIME: linux-arm64
|
||||
run: |
|
||||
source "$STATE_FILE"
|
||||
cd /src/_src
|
||||
echo "→ Building arm64 for POSTIT_GIT_TAG=$TAG"
|
||||
# Cross-RID .NET depuis un hôte amd64 : standard, pas
|
||||
# besoin de runner arm64 natif.
|
||||
make deb POSTIT_GIT_TAG="$TAG" POSTIT_RUNTIME=linux-arm64
|
||||
|
||||
- name: Localiser les .deb produits
|
||||
run: |
|
||||
source "$STATE_FILE"
|
||||
cd /src
|
||||
DEB_AMD64=$(find . -maxdepth 3 -name "postit_*${TAG}-1_amd64.deb" \
|
||||
-not -path "./_src/debian/*" -printf '%p\n' | head -1)
|
||||
DEB_ARM64=$(find . -maxdepth 3 -name "postit_*${TAG}-1_arm64.deb" \
|
||||
-not -path "./_src/debian/*" -printf '%p\n' | head -1)
|
||||
if [[ -z "$DEB_AMD64" || -z "$DEB_ARM64" ]]; then
|
||||
echo "::error::Missing .deb files. amd64='$DEB_AMD64' arm64='$DEB_ARM64'"
|
||||
ls -la /src/ 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "DEB_AMD64=/src/$DEB_AMD64" >> "$STATE_FILE"
|
||||
echo "DEB_ARM64=/src/$DEB_ARM64" >> "$STATE_FILE"
|
||||
echo "✓ Found both .deb files"
|
||||
|
||||
- name: Publier la release Forgejo via l'API REST
|
||||
env:
|
||||
FORGEJO_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
FORGEJO_API_URL: ${{ forgejo.api_url }}
|
||||
FORGEJO_REPOSITORY: ${{ forgejo.repository }}
|
||||
run: |
|
||||
source "$STATE_FILE"
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::No tag resolved for the API call."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Le runner Forgejo expose l'API sur forgejo.api_url (par
|
||||
# défaut http://…/api/v1). On retire le suffixe /api/v1
|
||||
# s'il est présent pour dériver la base du serveur, puis
|
||||
# on reconstruit l'URL de l'API proprement.
|
||||
API_BASE="${FORGEJO_API_URL%/}"
|
||||
API_BASE="${API_BASE%/api/v1}"
|
||||
|
||||
# 1. Vérifier si la release existe déjà pour ce tag.
|
||||
# Politique : on réutilise (PATCH) plutôt que d'en
|
||||
# créer une nouvelle — cf. note "Re-tag policy" en
|
||||
# tête de fichier.
|
||||
echo "::group::Check existing release for tag $TAG"
|
||||
HTTP=$(curl -sS -o /tmp/existing.json -w '%{http_code}' \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
"$API_BASE/api/v1/repos/$FORGEJO_REPOSITORY/releases/tags/$TAG")
|
||||
echo "GET releases/tags/$TAG -> HTTP $HTTP"
|
||||
EXISTING_ID=""
|
||||
if [[ "$HTTP" == "200" ]]; then
|
||||
EXISTING_ID=$(jq -r '.id // empty' /tmp/existing.json)
|
||||
echo "Existing release id: ${EXISTING_ID:-none}"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
# 2. Créer ou mettre à jour la release.
|
||||
if [[ -n "$EXISTING_ID" ]]; then
|
||||
echo "::group::Update release id=$EXISTING_ID"
|
||||
jq -n \
|
||||
--arg body "$RELEASE_BODY" \
|
||||
--argjson prerelease "$IS_PRERELEASE" \
|
||||
'{body: $body, prerelease: $prerelease}' \
|
||||
> /tmp/patch.json
|
||||
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
|
||||
-X PATCH \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary @/tmp/patch.json \
|
||||
"$API_BASE/api/v1/repos/$FORGEJO_REPOSITORY/releases/$EXISTING_ID")
|
||||
echo "PATCH release -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
else
|
||||
echo "::group::Create release"
|
||||
jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg name "$TAG" \
|
||||
--arg body "$RELEASE_BODY" \
|
||||
--argjson prerelease "$IS_PRERELEASE" \
|
||||
'{tag_name: $tag, name: $name, body: $body, prerelease: $prerelease}' \
|
||||
> /tmp/post.json
|
||||
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary @/tmp/post.json \
|
||||
"$API_BASE/api/v1/repos/$FORGEJO_REPOSITORY/releases")
|
||||
echo "POST release -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
if [[ "$HTTP" != "200" && "$HTTP" != "201" ]]; then
|
||||
echo "::error::Release creation/update failed (HTTP $HTTP):"
|
||||
cat /tmp/release.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
||||
echo "Release id=$RELEASE_ID"
|
||||
|
||||
# 3. Upload les .deb en assets. Le nom du fichier passe
|
||||
# en query string (?name=...), pas en argument
|
||||
# positionnel entre --data-binary et l'URL.
|
||||
for entry in "amd64:$DEB_AMD64" "arm64:$DEB_ARM64"; do
|
||||
arch="${entry%%:*}"
|
||||
deb="${entry#*:}"
|
||||
echo "::group::Upload asset for arch=$arch: $deb"
|
||||
HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary "@$deb" \
|
||||
"$API_BASE/api/v1/repos/$FORGEJO_REPOSITORY/releases/$RELEASE_ID/assets?name=$(basename "$deb")")
|
||||
echo "POST asset ($arch) -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ "$HTTP" != "201" ]]; then
|
||||
echo "::error::Asset upload failed for $arch (HTTP $HTTP):"
|
||||
cat /tmp/asset.json
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Release publiée : $API_BASE/$FORGEJO_REPOSITORY/releases/tag/$TAG"
|
||||
283
.github/workflows/build-and-release-deb.yml
vendored
283
.github/workflows/build-and-release-deb.yml
vendored
|
|
@ -1,283 +0,0 @@
|
|||
name: Build and Release postit-deb
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag de pazof/yavsc à packager (ex. 1.0.6, 1.0.7-rc1). Requis pour un build ad-hoc.'
|
||||
required: true
|
||||
type: string
|
||||
force_unstable:
|
||||
description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
force_republish:
|
||||
description: 'Re-publier une release dont le tag existe déjà. Par défaut refusé (re-tag = le mal).'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# softprops/action-gh-release a besoin de contents: write
|
||||
# pour publier une release + uploader un asset.
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# Build matrix : un .deb par architecture. Le tag Git poussé sur
|
||||
# ce dépôt devient POSTIT_GIT_TAG pour `make deb`, qui clone
|
||||
# l'amont pazof/yavsc à ce tag et produit le .deb correspondant.
|
||||
# Sur amd64, la cross-compilation linux-arm64 marche nativement
|
||||
# (dotnet publish --runtime linux-arm64 depuis un hôte amd64).
|
||||
# On évite donc les runners arm64 natifs (qui existent mais sont
|
||||
# récents et plus chers en minutes).
|
||||
deb-build:
|
||||
name: Build .deb (${{ matrix.runtime }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runtime: linux-x64
|
||||
arch: amd64
|
||||
artifact_name: postit-amd64
|
||||
- runtime: linux-arm64
|
||||
arch: arm64
|
||||
artifact_name: postit-arm64
|
||||
steps:
|
||||
- name: Checkout postit-debian
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Installer les pré-requis de build (debhelper + icônes)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential debhelper imagemagick librsvg2-bin \
|
||||
git ca-certificates
|
||||
|
||||
- name: Installer .NET SDK 10
|
||||
uses: microsoft/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Déterminer POSTIT_GIT_TAG
|
||||
id: tag
|
||||
run: |
|
||||
# Sur un push de branche (pas un tag), github.ref_name est
|
||||
# 'main' — `make deb POSTIT_GIT_TAG=main` clone pazof/yavsc
|
||||
# sur la branche main et produit un .deb à jour. Sur un push
|
||||
# de tag, c'est le numéro de tag (ex. '1.0.6'). Sur
|
||||
# workflow_dispatch, on lit l'input `tag`.
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ inputs.tag }}"
|
||||
else
|
||||
TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::POSTIT_GIT_TAG is empty. Pour workflow_dispatch, l'input 'tag' est obligatoire."
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ POSTIT_GIT_TAG=$TAG"
|
||||
|
||||
- name: Build du .deb via make deb
|
||||
env:
|
||||
POSTIT_GIT_TAG: ${{ steps.tag.outputs.tag }}
|
||||
POSTIT_RUNTIME: ${{ matrix.runtime }}
|
||||
run: |
|
||||
echo "→ Building for POSTIT_GIT_TAG=$POSTIT_GIT_TAG POSTIT_RUNTIME=$POSTIT_RUNTIME"
|
||||
make deb POSTIT_GIT_TAG="$POSTIT_GIT_TAG" POSTIT_RUNTIME="$POSTIT_RUNTIME"
|
||||
|
||||
- name: Localiser le .deb produit
|
||||
id: locate
|
||||
run: |
|
||||
# Le Makefile mv les .deb vers $POSTIT_OUT_DIR (par défaut
|
||||
# le répertoire parent du repo). Sur GitHub Actions, c'est
|
||||
# le workspace parent : /home/runner/work/.. Le .deb est
|
||||
# nommé d'après le tag brut (avec ou sans 'v', tel quel
|
||||
# poussé sur le remote), on cherche donc avec ref_name.
|
||||
DEB=$(find /home/runner -maxdepth 4 -name "postit_*${{ github.ref_name }}-1_${{ matrix.arch }}.deb" \
|
||||
-not -path "*/debian/*" \
|
||||
-printf '%p\n' | head -1)
|
||||
if [[ -z "$DEB" ]]; then
|
||||
echo "::error::No .deb matching postit_*${{ github.ref_name }}-1_${{ matrix.arch }}.deb found."
|
||||
echo "Files in parent dir:"
|
||||
ls -la /home/runner/work/ 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "deb_path=$DEB" >> "$GITHUB_OUTPUT"
|
||||
echo "✓ Found $DEB"
|
||||
|
||||
- name: Téléverser le .deb en tant qu'Artéfact GitHub
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ steps.locate.outputs.deb_path }}
|
||||
retention-days: 7
|
||||
|
||||
# Validation : parse le tag, applique la parité patch (pair=stable /
|
||||
# impair=preview / suffixe=instable), vérifie que CHANGELOG.md
|
||||
# contient une section cohérente, et — point non négociable —
|
||||
# refuse de re-publier un tag qui existe déjà (re-tag = le mal).
|
||||
validate-release:
|
||||
# Tourne sur push de tag (release officielle) ou sur workflow_dispatch
|
||||
# avec un tag explicite (release ad-hoc). Sur push de branche, on
|
||||
# ne publie pas — les jobs de build suffisent (artefacts seulement).
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout postit-debian
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Déterminer le tag à publier
|
||||
id: pick_tag
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ inputs.tag }}"
|
||||
else
|
||||
TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::Tag is empty. Sur workflow_dispatch, l'input 'tag' est obligatoire."
|
||||
exit 1
|
||||
fi
|
||||
# Strip leading 'v' (git tag convention).
|
||||
if [[ "$TAG" =~ ^v(.*)$ ]]; then
|
||||
TAG="${BASH_REMATCH[1]}"
|
||||
echo "Stripped leading 'v' — using TAG=$TAG for validation."
|
||||
fi
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Valider le tag, le CHANGELOG et l'unicité du tag
|
||||
env:
|
||||
FORCE_UNSTABLE: ${{ inputs.force_unstable || github.event.inputs.force_unstable || 'false' }}
|
||||
FORCE_REPUBLISH: ${{ inputs.force_republish || github.event.inputs.force_republish || 'false' }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG="${{ steps.pick_tag.outputs.tag }}"
|
||||
|
||||
# Parse semver : MAJOR.MINOR.PATCH[-SUFFIX]
|
||||
if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
||||
echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
PATCH="${BASH_REMATCH[3]}"
|
||||
SUFFIX="${BASH_REMATCH[4]}"
|
||||
|
||||
# Classification du canal par parité du patch.
|
||||
if [[ -n "$SUFFIX" ]]; then
|
||||
CHANNEL="unstable"
|
||||
elif (( PATCH % 2 == 0 )); then
|
||||
CHANNEL="stable"
|
||||
else
|
||||
CHANNEL="preview"
|
||||
fi
|
||||
|
||||
echo "Tag $TAG classifié comme channel=$CHANNEL"
|
||||
|
||||
# Fail-fast sur instable sauf opt-in explicite.
|
||||
if [[ "$CHANNEL" == "unstable" && "$FORCE_UNSTABLE" != "true" ]]; then
|
||||
echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish."
|
||||
echo "Set force_unstable=true via workflow_dispatch to override."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Lecture du CHANGELOG.md (doit exister à la racine du repo).
|
||||
if [[ ! -f CHANGELOG.md ]]; then
|
||||
echo "::error::CHANGELOG.md not found at repo root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extraction de la section [TAG]. awk en mode paragraphe.
|
||||
BODY=$(awk -v tag="[$TAG]" '
|
||||
/^## \[/ {
|
||||
if (in_section) exit
|
||||
if (index($0, tag) > 0) in_section=1
|
||||
next
|
||||
}
|
||||
in_section { print }
|
||||
' CHANGELOG.md)
|
||||
|
||||
if [[ -z "$BODY" ]]; then
|
||||
echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md."
|
||||
echo "Add a '## [$TAG] - $CHANNEL' section before tagging."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérification cohérence du canal déclaré.
|
||||
HEADER=$(grep -m1 "^## \[$TAG\]" CHANGELOG.md)
|
||||
if [[ "$HEADER" != *" - $CHANNEL"* ]]; then
|
||||
echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity."
|
||||
echo "Current section header: $HEADER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
||||
|
||||
# Anti-re-tag : refuse de publier si une release existe déjà
|
||||
# pour ce tag. softprops/action-gh-release créerait sinon une
|
||||
# nouvelle release par-dessus (re-tag = le mal). Opt-in via
|
||||
# workflow_dispatch + force_republish=true uniquement.
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
if [[ "$FORCE_REPUBLISH" != "true" ]]; then
|
||||
echo "::error::Release for tag '$TAG' already exists. Refusing to re-tag."
|
||||
echo "Set force_republish=true via workflow_dispatch to override."
|
||||
exit 1
|
||||
else
|
||||
echo "::warning::Release '$TAG' already exists — force_republish=true, proceeding."
|
||||
fi
|
||||
else
|
||||
echo "✓ No existing release for tag '$TAG'."
|
||||
fi
|
||||
|
||||
# Exposition aux étapes suivantes via $GITHUB_ENV.
|
||||
{
|
||||
echo "RELEASE_BODY<<EOF"
|
||||
echo "$BODY"
|
||||
echo "EOF"
|
||||
echo "RELEASE_TAG=$TAG"
|
||||
echo "RELEASE_CHANNEL=$CHANNEL"
|
||||
if [[ "$CHANNEL" == "stable" ]]; then
|
||||
echo "IS_PRERELEASE=false"
|
||||
else
|
||||
echo "IS_PRERELEASE=true"
|
||||
fi
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
publish-release:
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
needs: [deb-build, validate-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Récupérer les .deb depuis les artefacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./
|
||||
merge-multiple: true
|
||||
|
||||
- name: Lister les .deb téléchargés
|
||||
run: ls -la ./
|
||||
|
||||
- name: Publier la release GitHub et uploader les .deb
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
files: |
|
||||
./postit-amd64/*.deb
|
||||
./postit-arm64/*.deb
|
||||
body: ${{ env.RELEASE_BODY }}
|
||||
prerelease: ${{ env.IS_PRERELEASE }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue