diff --git a/.gitea/scripts/attach-release-assets.sh b/.gitea/scripts/attach-release-assets.sh index 221b77b..4c18cd0 100755 --- a/.gitea/scripts/attach-release-assets.sh +++ b/.gitea/scripts/attach-release-assets.sh @@ -4,8 +4,8 @@ # Usage : attach-release-assets.sh # Env : RELEASE_TOKEN (token Gitea avec write:repository), GITHUB_SERVER_URL, GITHUB_REPOSITORY. # -# Partagé par tous les jobs de release desktop (Linux, Windows, canal flottant) : la logique était -# dupliquée dans chaque job, et toute correction devait être faite trois fois. +# Partagé par tous les jobs de release desktop et par le VSIX : la logique était dupliquée, et toute +# correction devait être faite trois fois. set -uo pipefail tag="${1:?tag manquant}" @@ -23,40 +23,74 @@ fi api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" auth="Authorization: token ${RELEASE_TOKEN}" +body=$(mktemp) +trap 'rm -f "$body"' EXIT -release_id=$(curl -fsSL -H "$auth" "${api}/releases/tags/${tag}" \ - | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).id || ''" || true) +# Lecture d'un champ JSON TOLÉRANTE : une réponse vide ou non-JSON (401, 403, 404) doit donner une +# chaîne vide, pas une pile d'appels Node. Sans ça, deux `SyntaxError: Unexpected end of JSON input` +# s'affichaient avant le vrai message d'erreur et noyaient le diagnostic. +json_field() { + node -e "let s='';process.stdin.on('data',(d)=>{s+=d}).on('end',()=>{try{const o=JSON.parse(s);const v=o?.[process.argv[1]];process.stdout.write(v==null?'':String(v))}catch{process.stdout.write('')}})" "$1" +} + +# `curl` silencieux qui écrit le corps dans $body et renvoie le code HTTP sur stdout. +http_call() { + curl -sS -o "$body" -w '%{http_code}' "$@" +} + +# --- résolution de la release (existante, sinon création) ------------------------------------- +code=$(http_call -H "$auth" "${api}/releases/tags/${tag}") +release_id=$(json_field id < "$body") if [ -z "$release_id" ]; then - release_id=$(curl -fsSL -X POST -H "$auth" -H 'Content-Type: application/json' \ - -d "{\"tag_name\":\"${tag}\",\"name\":\"${release_name}\"}" \ - "${api}/releases" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).id || ''") -fi - -if [ -z "$release_id" ]; then - echo "::error::impossible de résoudre ou créer la release ${tag} avec le token fourni." - echo "::error::vérifier que le secret porte les portées write:repository et write:package, et qu'il n'a pas expiré." - exit 1 + # 401/403 sur une simple lecture : inutile de tenter la création, le token est en cause. + case "$code" in + 401) + echo "::error::le token de release est refusé (HTTP 401) : il est invalide, révoqué ou expiré." + echo "::error::régénérer un token Gitea et mettre à jour le secret NPM_TOKEN du dépôt." + exit 1 + ;; + 403) + echo "::error::le token de release manque de droits (HTTP 403) sur ${GITHUB_REPOSITORY}." + echo "::error::portées attendues : write:repository (releases et assets) et write:package (publication npm)." + exit 1 + ;; + esac + create_code=$(http_call -X POST -H "$auth" -H 'Content-Type: application/json' \ + -d "{\"tag_name\":\"${tag}\",\"name\":\"${release_name}\"}" "${api}/releases") + release_id=$(json_field id < "$body") + if [ -z "$release_id" ]; then + echo "::error::impossible de créer la release ${tag} (HTTP ${create_code})." + echo "::error::réponse de l'API : $(head -c 300 "$body")" + exit 1 + fi + echo "release ${tag} créée (id ${release_id})." +else + echo "release ${tag} trouvée (id ${release_id})." fi +# --- attache des fichiers ---------------------------------------------------------------------- +failed=0 for f in "$@"; do [ -f "$f" ] || continue name=$(basename "$f") # L'API Gitea refuse un asset de même nom : on supprime l'ancien pour que le dernier build gagne. - existing=$(curl -fsSL -H "$auth" "${api}/releases/${release_id}/assets" \ - | node -e "const a=JSON.parse(require('fs').readFileSync(0,'utf8'));const m=Array.isArray(a)?a.find(x=>x.name===process.argv[1]):null;process.stdout.write(m?String(m.id):'')" "$name" || true) + http_call -H "$auth" "${api}/releases/${release_id}/assets" > /dev/null + existing=$(node -e "let s='';process.stdin.on('data',(d)=>{s+=d}).on('end',()=>{try{const a=JSON.parse(s);const m=Array.isArray(a)?a.find((x)=>x.name===process.argv[1]):null;process.stdout.write(m?String(m.id):'')}catch{process.stdout.write('')}})" "$name" < "$body") if [ -n "$existing" ]; then - echo "replacing existing $name (asset $existing)" - curl -fsSL -X DELETE -H "$auth" "${api}/releases/${release_id}/assets/${existing}" || true + echo "remplacement de $name (asset $existing)" + http_call -X DELETE -H "$auth" "${api}/releases/${release_id}/assets/${existing}" > /dev/null fi - echo "attaching $name" - if ! curl -fsSL -X POST -H "$auth" -F "attachment=@${f}" "${api}/releases/${release_id}/assets?name=${name}"; then - echo "::error::échec de l'upload de ${name}" + upload_code=$(http_call -X POST -H "$auth" -F "attachment=@${f}" "${api}/releases/${release_id}/assets?name=${name}") + if [ "$upload_code" -ge 200 ] && [ "$upload_code" -lt 300 ]; then + echo "attaché : $name" + else + echo "::error::échec de l'upload de ${name} (HTTP ${upload_code}) : $(head -c 200 "$body")" failed=1 fi done -if [ "${failed:-0}" != "0" ]; then +if [ "$failed" != "0" ]; then echo "::error::au moins un asset n'a pas pu être attaché à ${tag}." exit 1 fi