Skip to content

Commit ef4f98f

Browse files
committed
fix(release-sweep): handle ts/node draft shape (.tgz + checksums)
The post-publish draft sweep used a single asset-count threshold (`>= 10 = un-draft, else delete`), which made sense when the only release shape was rust/go (9 binaries + checksums.txt = 10 assets). After plugin-publish.yml started producing ts/node releases via `npm pack` (commit c079171), a complete ts/node release has only 2 assets: `<binary_name>.tgz` + `checksums.txt`. The old threshold would classify any such draft as `incomplete` and delete it + the git tag — wiping a perfectly valid release. Rewritten policy is shape-aware. We can't read plugin.yaml at sweep time (the release page is the only signal), so the type is inferred from the assets themselves: - `checksums.txt` is required in both shapes (consumer-side SHA256 verify in the inject pre-flight depends on it). - If any `*.tgz` is present → ts/node shape: complete = .tgz + checksums.txt (≥ 2 assets). - Else → rust/go shape: complete = 9 binary-named assets + checksums.txt (≥ 10 total, ≥ 9 non-checksum). - Anything else → incomplete, deleted + tag cleaned up. Log line now records which shape was matched, plus the counts that drove the decision, so investigations don't need to re-pull the release. This bug only surfaces once the ts/node publish path actually generates drafts. Today's audited workflow never sets `--draft`, so the in-flight failure mode is the same as before (no false deletes today). But the sweep is the safety net for the day something *does* leave a draft behind, and the safety net itself should not silently destroy valid releases.
1 parent 1550a3e commit ef4f98f

1 file changed

Lines changed: 49 additions & 11 deletions

File tree

.github/workflows/plugin-publish.yml

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -766,12 +766,32 @@ jobs:
766766
# a draft-then-un-draft pattern with `|| true` (which silently
767767
# swallowed failures).
768768
#
769-
# Policy per Draft tag:
770-
# - complete (assets >= 10 = 9 binaries + checksums.txt)
771-
# → un-draft (promote to Published)
772-
# - incomplete (assets < 10)
773-
# → delete release AND tag (clean break, no half-shipped
774-
# binary visible to consumers)
769+
# Policy per Draft tag — completeness is shape-dependent because
770+
# different plugin types produce different release shapes:
771+
#
772+
# rust / go (native binary):
773+
# complete = 9 platform binaries + checksums.txt → 10 assets
774+
# incomplete = anything less
775+
#
776+
# typescript / node (npm package):
777+
# complete = 1 `<bin>.tgz` + checksums.txt → 2 assets
778+
# (the .tgz is platform-agnostic JavaScript; no per-target build)
779+
#
780+
# We can't read plugin.yaml here (the release page is the only
781+
# signal at sweep time), so we infer plugin type from the assets:
782+
# - any `*.tgz` present → typescript/node release
783+
# - no `*.tgz` → rust/go release (expect the 9+1 shape)
784+
#
785+
# In both shapes, `checksums.txt` MUST be present in a complete
786+
# release (consumer-side SHA256 verify in the inject pre-flight
787+
# depends on it). A draft without checksums.txt is treated as
788+
# incomplete and deleted.
789+
#
790+
# Result:
791+
# complete (un-draft):
792+
# - has checksums.txt AND has .tgz (ts/node shape) → 2+ assets
793+
# - has checksums.txt AND has 9 binary-named assets (rust/go) → 10 assets
794+
# incomplete (delete + cleanup tag): every other shape
775795
#
776796
# Final assertion: 0 drafts must remain or the job fails loudly.
777797
# No `|| true` here — un-draft / delete errors propagate.
@@ -798,13 +818,31 @@ jobs:
798818
799819
echo "::group::per-draft action"
800820
echo "$DRAFTS" | while IFS=$'\t' read -r TAG CREATED; do
801-
ASSETS=$(gh release view "$TAG" --repo "$REPO" \
802-
--json assets --jq '.assets | length')
803-
if [ "$ASSETS" -ge 10 ]; then
804-
echo " un-draft $TAG (${ASSETS} assets, complete, created=${CREATED})"
821+
# Pull asset names (one per line) for shape detection.
822+
ASSET_NAMES=$(gh release view "$TAG" --repo "$REPO" \
823+
--json assets --jq -r '.assets[].name')
824+
TOTAL=$(echo "$ASSET_NAMES" | grep -c . || true)
825+
HAS_CHECKSUMS=$(echo "$ASSET_NAMES" | grep -cFx 'checksums.txt' || true)
826+
HAS_TGZ=$(echo "$ASSET_NAMES" | grep -cE '\.tgz$' || true)
827+
NON_CHECKSUM_COUNT=$(echo "$ASSET_NAMES" | grep -cvFx 'checksums.txt' || true)
828+
829+
COMPLETE=false
830+
REASON=""
831+
if [ "$HAS_CHECKSUMS" -ge 1 ] && [ "$HAS_TGZ" -ge 1 ]; then
832+
# ts/node shape: .tgz + checksums.txt (≥ 2 assets)
833+
COMPLETE=true
834+
REASON="ts/node (.tgz + checksums.txt)"
835+
elif [ "$HAS_CHECKSUMS" -ge 1 ] && [ "$NON_CHECKSUM_COUNT" -ge 9 ]; then
836+
# rust/go shape: 9 binaries + checksums.txt
837+
COMPLETE=true
838+
REASON="rust/go (9 binaries + checksums.txt)"
839+
fi
840+
841+
if [ "$COMPLETE" = "true" ]; then
842+
echo " un-draft $TAG (${TOTAL} assets, ${REASON}, created=${CREATED})"
805843
gh release edit "$TAG" --repo "$REPO" --draft=false
806844
else
807-
echo " delete+tag $TAG (${ASSETS} assets, incomplete, created=${CREATED})"
845+
echo " delete+tag $TAG (${TOTAL} assets, incomplete: checksums=${HAS_CHECKSUMS}, tgz=${HAS_TGZ}, other=${NON_CHECKSUM_COUNT}, created=${CREATED})"
808846
gh release delete "$TAG" --repo "$REPO" --yes --cleanup-tag
809847
fi
810848
done

0 commit comments

Comments
 (0)