Merge pull request 'fix(ci): harden release asset uploads for reruns' (#7) from fix/release-upload-rerun-hardening into master
Reviewed-on: #7
This commit is contained in:
commit
42120cb140
@ -36,6 +36,7 @@ jobs:
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG="$GITHUB_REF_NAME"
|
||||
echo "Creating release for $TAG..."
|
||||
@ -57,10 +58,33 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
echo "Uploading $(basename $f)..."
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$(basename $f)" && echo "✓ Uploaded $(basename $f)" || echo "✗ Upload failed: $f"
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python - "$RESP_FILE" <<'PY'
|
||||
import pathlib, sys
|
||||
print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-windows-amd64:
|
||||
@ -97,6 +121,7 @@ jobs:
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG="$GITHUB_REF_NAME"
|
||||
echo "Creating release for $TAG..."
|
||||
@ -111,12 +136,40 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
find src-tauri/target/x86_64-pc-windows-gnu/release/bundle \
|
||||
\( -name "*.exe" -o -name "*.msi" \) 2>/dev/null | while read f; do
|
||||
echo "Uploading $(basename $f)..."
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
ARTIFACTS=$(find src-tauri/target/x86_64-pc-windows-gnu/release/bundle -type f \
|
||||
\( -name "*.exe" -o -name "*.msi" \) 2>/dev/null)
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No Windows amd64 artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$(basename $f)" && echo "✓ Uploaded $(basename $f)" || echo "✗ Upload failed: $f"
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python - "$RESP_FILE" <<'PY'
|
||||
import pathlib, sys
|
||||
print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-macos-arm64:
|
||||
@ -153,6 +206,7 @@ jobs:
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG="$GITHUB_REF_NAME"
|
||||
# Create release (idempotent)
|
||||
@ -171,12 +225,39 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
# Upload DMG
|
||||
find src-tauri/target/aarch64-apple-darwin/release/bundle -name "*.dmg" | while read f; do
|
||||
echo "Uploading $(basename $f)..."
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
ARTIFACTS=$(find src-tauri/target/aarch64-apple-darwin/release/bundle -type f -name "*.dmg")
|
||||
if [ -z "$ARTIFACTS" ]; then
|
||||
echo "ERROR: No macOS arm64 DMG artifacts were found to upload."
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$(basename $f)" && echo "✓ Uploaded $(basename $f)" || echo "✗ Upload failed: $f"
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python - "$RESP_FILE" <<'PY'
|
||||
import pathlib, sys
|
||||
print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
build-linux-arm64:
|
||||
@ -210,6 +291,7 @@ jobs:
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
set -eu
|
||||
API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
TAG="$GITHUB_REF_NAME"
|
||||
echo "Creating release for $TAG..."
|
||||
@ -231,8 +313,31 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$ARTIFACTS" | while IFS= read -r f; do
|
||||
echo "Uploading $(basename $f)..."
|
||||
curl -sf -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
NAME=$(basename "$f")
|
||||
echo "Uploading $NAME..."
|
||||
EXISTING_IDS=$(curl -sf "$API/releases/$RELEASE_ID" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$(basename $f)" && echo "✓ Uploaded $(basename $f)" || echo "✗ Upload failed: $f"
|
||||
| jq -r --arg name "$NAME" '.assets[]? | select(.name == $name) | .id')
|
||||
if [ -n "$EXISTING_IDS" ]; then
|
||||
printf '%s\n' "$EXISTING_IDS" | while IFS= read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
echo "Deleting existing asset id=$id name=$NAME before upload..."
|
||||
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$id" \
|
||||
-H "Authorization: token $RELEASE_TOKEN"
|
||||
done
|
||||
fi
|
||||
RESP_FILE=$(mktemp)
|
||||
HTTP_CODE=$(curl -sS -o "$RESP_FILE" -w "%{http_code}" -X POST "$API/releases/$RELEASE_ID/assets" \
|
||||
-H "Authorization: token $RELEASE_TOKEN" \
|
||||
-F "attachment=@$f;filename=$NAME")
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✓ Uploaded $NAME"
|
||||
else
|
||||
echo "✗ Upload failed for $NAME (HTTP $HTTP_CODE)"
|
||||
python - "$RESP_FILE" <<'PY'
|
||||
import pathlib, sys
|
||||
print(pathlib.Path(sys.argv[1]).read_text(errors="replace")[:2000])
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
@ -73,12 +73,16 @@ steps:
|
||||
Jobs (run in parallel):
|
||||
build-linux-amd64 → cargo tauri build (x86_64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
build-windows-amd64 → cargo tauri build (x86_64-pc-windows-gnu) via mingw-w64
|
||||
→ {.exe, .msi} uploaded to Gitea release
|
||||
→ fails fast if no Windows artifacts are produced
|
||||
build-linux-arm64 → cargo tauri build (aarch64-unknown-linux-gnu)
|
||||
→ {.deb, .rpm, .AppImage} uploaded to Gitea release
|
||||
→ fails fast if no Linux artifacts are produced
|
||||
build-macos-arm64 → cargo tauri build (aarch64-apple-darwin) — runs on local Mac
|
||||
→ {.dmg} uploaded to Gitea release
|
||||
→ existing same-name assets are deleted before upload (rerun-safe)
|
||||
→ unsigned; after install run: xattr -cr /Applications/TFTSR.app
|
||||
```
|
||||
|
||||
|
||||
@ -21,4 +21,19 @@ describe("release workflow cross-platform artifact handling", () => {
|
||||
expect(workflow).toContain("ERROR: No Linux amd64 artifacts were found to upload.");
|
||||
expect(workflow).toContain("ERROR: No Linux arm64 artifacts were found to upload.");
|
||||
});
|
||||
|
||||
it("fails windows uploads when no artifacts are found", () => {
|
||||
const workflow = readFileSync(releaseWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain(
|
||||
"ERROR: No Windows amd64 artifacts were found to upload.",
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces existing release assets before uploading reruns", () => {
|
||||
const workflow = readFileSync(releaseWorkflowPath, "utf-8");
|
||||
|
||||
expect(workflow).toContain("Deleting existing asset id=$id name=$NAME before upload...");
|
||||
expect(workflow).toContain("-X DELETE \"$API/releases/$RELEASE_ID/assets/$id\"");
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user