|
|
|
|
@@ -51,14 +51,22 @@
|
|
|
|
|
#
|
|
|
|
|
# Prerequisites (Settings → Actions → Secrets on RunicGateway/servuo-plugins):
|
|
|
|
|
# REGISTRY_TOKEN — Gitea access token with `write:repository`, to push the
|
|
|
|
|
# tag and create the release.
|
|
|
|
|
# tag and create the release. The final step also dispatches
|
|
|
|
|
# RunicGateway/installer's bundle workflow, so the token
|
|
|
|
|
# ideally has write there too — a nicety, not a requirement:
|
|
|
|
|
# without it the step warns and that repo's nightly cron
|
|
|
|
|
# picks the release up instead.
|
|
|
|
|
# REGISTRY_USER — the Gitea username that token belongs to.
|
|
|
|
|
#
|
|
|
|
|
# TODO (Phase 0 item 3): once the installer repo's bundle workflow exists, append
|
|
|
|
|
# a final step here that POSTs to its workflow-dispatch endpoint, so a new
|
|
|
|
|
# overlay release recomposes the bundle immediately instead of waiting for the
|
|
|
|
|
# nightly cron (PLAN.md §7.2). Deliberately absent until there is something to
|
|
|
|
|
# dispatch — a step that 404s every release is worse than no step.
|
|
|
|
|
# These are checked by an explicit preflight step rather than left to fail
|
|
|
|
|
# wherever they happen to be used first — see the comment on that step for why
|
|
|
|
|
# an absent token does NOT simply fail the tag push.
|
|
|
|
|
#
|
|
|
|
|
# The final step POSTs to the installer repo's bundle workflow, so a new overlay
|
|
|
|
|
# release recomposes the compat matrix immediately instead of waiting for that
|
|
|
|
|
# repo's nightly cron (PLAN.md §7.2). It was deliberately absent until Phase 0
|
|
|
|
|
# item 3 landed something to dispatch — a step that 404s on every release is
|
|
|
|
|
# worse than no step.
|
|
|
|
|
|
|
|
|
|
name: Release overlay
|
|
|
|
|
|
|
|
|
|
@@ -80,6 +88,9 @@ env:
|
|
|
|
|
# house style set by link (pre-1.0; the release version is independent of the
|
|
|
|
|
# protocol version, which lives in overlay.toml).
|
|
|
|
|
SEED_VERSION: "0.1.0"
|
|
|
|
|
# Notified after a release so the installer's compat matrix picks up this
|
|
|
|
|
# overlay immediately rather than at its next nightly run (PLAN.md §7.2).
|
|
|
|
|
INSTALLER_REPO: RunicGateway/installer
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
release:
|
|
|
|
|
@@ -93,6 +104,8 @@ jobs:
|
|
|
|
|
# ── RELEASE ENGINE: decide the next version + changelog ──────────────
|
|
|
|
|
- name: Plan the release (version + changelog)
|
|
|
|
|
id: plan
|
|
|
|
|
env:
|
|
|
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
mkdir -p dist
|
|
|
|
|
@@ -129,28 +142,101 @@ jobs:
|
|
|
|
|
VERSION="$(bump "${LAST_TAG#v}" "$BUMP")"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# An existing tag is NOT automatically "nothing to do". A tag with no
|
|
|
|
|
# release behind it means a previous run tagged and then died before
|
|
|
|
|
# publishing — which is exactly what happened on the first run here,
|
|
|
|
|
# when the missing REGISTRY_* secrets took the release API call to 401
|
|
|
|
|
# after the tag had already been pushed. Standing down on the tag alone
|
|
|
|
|
# would make that state permanent: every later run would see the tag,
|
|
|
|
|
# set RELEASE=false, and the release would never appear. So distinguish
|
|
|
|
|
# the two cases and finish the job the earlier run started.
|
|
|
|
|
# Note this OVERRIDES the RELEASE=false decided just above. With the tag
|
|
|
|
|
# already in place there are no releasable commits after it, so the
|
|
|
|
|
# normal path stands down — which is precisely why the stuck state
|
|
|
|
|
# could never clear itself. Recovery has to be able to say "yes,
|
|
|
|
|
# publish" for a version the bump logic considers already done.
|
|
|
|
|
REUSE_TAG=false
|
|
|
|
|
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
|
|
|
|
echo "Tag v${VERSION} already exists — nothing to release."
|
|
|
|
|
RELEASE=false
|
|
|
|
|
REL_HTTP="$(curl -s -o /dev/null -w '%{http_code}' \
|
|
|
|
|
-H "Authorization: token $(printf '%s' "${REGISTRY_TOKEN:-}" | tr -d '\r\n')" \
|
|
|
|
|
"https://${GITEA_HOST}/api/v1/repos/${REPO}/releases/tags/v${VERSION}" || echo 000)"
|
|
|
|
|
if [ "$REL_HTTP" = "200" ]; then
|
|
|
|
|
echo "Tag v${VERSION} already has a release — nothing to do."
|
|
|
|
|
RELEASE=false
|
|
|
|
|
elif [ "$REL_HTTP" = "404" ]; then
|
|
|
|
|
echo "::warning::Tag v${VERSION} exists but has no release — a previous run failed after tagging. Reusing the tag and publishing the release it is missing."
|
|
|
|
|
REUSE_TAG=true
|
|
|
|
|
RELEASE=true
|
|
|
|
|
else
|
|
|
|
|
# Anything else (000 from a network failure, 401/403 from a bad
|
|
|
|
|
# token) is not evidence of absence. Guessing "no release" here
|
|
|
|
|
# would re-publish over a good one, so refuse instead.
|
|
|
|
|
echo "::error::Could not determine whether a release exists for v${VERSION} (HTTP ${REL_HTTP}). Refusing to guess."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Changelog range. A recovery run has nothing after the tag, so
|
|
|
|
|
# summarize what the tag itself contains rather than emitting an empty
|
|
|
|
|
# list: the range that produced it, i.e. previous-tag..this-tag.
|
|
|
|
|
if [ "$REUSE_TAG" = true ]; then
|
|
|
|
|
PREV_TAG="$(git describe --tags --match 'v*' --abbrev=0 "v${VERSION}^" 2>/dev/null || true)"
|
|
|
|
|
if [ -n "$PREV_TAG" ]; then CL_RANGE="${PREV_TAG}..v${VERSION}"; else CL_RANGE="v${VERSION}"; fi
|
|
|
|
|
SINCE="$PREV_TAG"
|
|
|
|
|
else
|
|
|
|
|
CL_RANGE="$RANGE"
|
|
|
|
|
SINCE="$LAST_TAG"
|
|
|
|
|
fi
|
|
|
|
|
CL_SUBJECTS="$(git log --no-merges --format='%s' $CL_RANGE || true)"
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
echo "## ${ARTIFACT} v${VERSION}"
|
|
|
|
|
echo
|
|
|
|
|
FEATS="$(echo "$SUBJECTS" | grep -E '^feat' || true)"
|
|
|
|
|
FIXES="$(echo "$SUBJECTS" | grep -E '^(fix|perf)' || true)"
|
|
|
|
|
FEATS="$(echo "$CL_SUBJECTS" | grep -E '^feat' || true)"
|
|
|
|
|
FIXES="$(echo "$CL_SUBJECTS" | grep -E '^(fix|perf)' || true)"
|
|
|
|
|
[ -n "$FEATS" ] && { echo "### Features"; echo "$FEATS" | sed 's/^/- /'; echo; }
|
|
|
|
|
[ -n "$FIXES" ] && { echo "### Fixes"; echo "$FIXES" | sed 's/^/- /'; echo; }
|
|
|
|
|
echo "### All changes"
|
|
|
|
|
if [ -n "$LAST_TAG" ]; then echo "Since ${LAST_TAG}:"; fi
|
|
|
|
|
echo "$SUBJECTS" | sed 's/^/- /'
|
|
|
|
|
if [ -n "$SINCE" ]; then echo "Since ${SINCE}:"; fi
|
|
|
|
|
echo "$CL_SUBJECTS" | sed 's/^/- /'
|
|
|
|
|
} > dist/CHANGELOG.md
|
|
|
|
|
|
|
|
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "release=${RELEASE}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "bump=${BUMP}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "==> release=${RELEASE} version=${VERSION} bump=${BUMP} last_tag=${LAST_TAG:-<none>}"
|
|
|
|
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "release=${RELEASE}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "bump=${BUMP}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "reuse_tag=${REUSE_TAG}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
echo "==> release=${RELEASE} version=${VERSION} bump=${BUMP} reuse_tag=${REUSE_TAG} last_tag=${LAST_TAG:-<none>}"
|
|
|
|
|
|
|
|
|
|
# ── Credential preflight ─────────────────────────────────────────────
|
|
|
|
|
# Runs BEFORE anything is built or pushed, and only when this run intends
|
|
|
|
|
# to publish, so a docs:/chore:-only merge stays green on a repo that has
|
|
|
|
|
# no secrets.
|
|
|
|
|
#
|
|
|
|
|
# This exists because of how the first run failed. REGISTRY_USER and
|
|
|
|
|
# REGISTRY_TOKEN were empty, but the tag push SUCCEEDED anyway:
|
|
|
|
|
# actions/checkout leaves an `http.<host>.extraheader` credential in the
|
|
|
|
|
# local git config, so `git remote set-url` to a URL with empty
|
|
|
|
|
# credentials still authenticated through that leftover header. The
|
|
|
|
|
# release API call had no such fallback and returned 401 — so the run
|
|
|
|
|
# tagged the repo and then failed, which is the worst of both outcomes.
|
|
|
|
|
# Checking the secrets up front turns that into an immediate, legible
|
|
|
|
|
# failure instead of a half-published release.
|
|
|
|
|
- name: Verify release credentials are configured
|
|
|
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
|
|
|
env:
|
|
|
|
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
|
|
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
MISSING=""
|
|
|
|
|
[ -n "$(printf '%s' "${REGISTRY_USER:-}" | tr -d '\r\n')" ] || MISSING="${MISSING} REGISTRY_USER"
|
|
|
|
|
[ -n "$(printf '%s' "${REGISTRY_TOKEN:-}" | tr -d '\r\n')" ] || MISSING="${MISSING} REGISTRY_TOKEN"
|
|
|
|
|
if [ -n "$MISSING" ]; then
|
|
|
|
|
echo "::error::Missing Actions secret(s):${MISSING}. Set them under Settings → Actions → Secrets on ${REPO}. REGISTRY_TOKEN needs the write:repository scope to push the tag and create the release."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "Release credentials present."
|
|
|
|
|
|
|
|
|
|
- name: Install jq
|
|
|
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
|
|
|
@@ -177,6 +263,10 @@ jobs:
|
|
|
|
|
# needing the target files present.
|
|
|
|
|
# • each patch's companion .cs must exist, since it references symbols
|
|
|
|
|
# the patch introduces and is meaningless without it (PLAN.md §2.2).
|
|
|
|
|
# • patches/tier.json must describe every .patch and nothing but. That
|
|
|
|
|
# table is what tells the installer which patches form one unit, which
|
|
|
|
|
# companion follows which, and whether a CORE rebuild is needed — a
|
|
|
|
|
# patch added without it would be shipped and silently never offered.
|
|
|
|
|
- name: Validate the overlay and patch tier
|
|
|
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
|
|
|
run: |
|
|
|
|
|
@@ -197,11 +287,46 @@ jobs:
|
|
|
|
|
git apply --stat "$p" || fail "${p} is not a parseable unified diff"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Companion files that can only be copied after their patch lands.
|
|
|
|
|
for f in patches/BridgeVendorSale.cs patches/BridgeModerationAudit.cs; do
|
|
|
|
|
[ -f "$f" ] || fail "${f} is missing (a patch's companion source)"
|
|
|
|
|
# The tier table, checked in BOTH directions. A patch missing from
|
|
|
|
|
# tier.json ships but is never offered to an operator; a tier.json
|
|
|
|
|
# entry naming a file that is not there makes the installer report a
|
|
|
|
|
# feature it cannot apply. Neither surfaces until someone runs the
|
|
|
|
|
# tier on a live shard, so both fail the release here instead.
|
|
|
|
|
[ -f patches/tier.json ] || fail "patches/tier.json is missing (the patch-tier declaration)"
|
|
|
|
|
jq -e . patches/tier.json >/dev/null || fail "patches/tier.json is not valid JSON"
|
|
|
|
|
|
|
|
|
|
DESCRIBED="$(jq -r '.features[].patches[].file' patches/tier.json | LC_ALL=C sort)"
|
|
|
|
|
PRESENT="$(cd patches && ls *.patch | LC_ALL=C sort)"
|
|
|
|
|
if [ "$DESCRIBED" != "$PRESENT" ]; then
|
|
|
|
|
echo "described by tier.json:"; echo "$DESCRIBED" | sed 's/^/ /'
|
|
|
|
|
echo "present in patches/:"; echo "$PRESENT" | sed 's/^/ /'
|
|
|
|
|
fail "patches/tier.json and patches/*.patch disagree — every patch must be described by exactly one feature"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Each patch's declared target must be the file its diff actually
|
|
|
|
|
# edits. The installer cross-checks the same pair at install time and
|
|
|
|
|
# refuses on a mismatch, so catching it here saves an operator the run.
|
|
|
|
|
while IFS=$'\t' read -r PFILE PTARGET; do
|
|
|
|
|
DIFF_TARGET="$(sed -n 's|^+++ b/||p' "patches/${PFILE}" | head -1 | tr -d '\r')"
|
|
|
|
|
[ "$DIFF_TARGET" = "$PTARGET" ] \
|
|
|
|
|
|| fail "patches/${PFILE} edits ${DIFF_TARGET} but tier.json declares ${PTARGET}"
|
|
|
|
|
done < <(jq -r '.features[].patches[] | [.file, .target] | @tsv' patches/tier.json)
|
|
|
|
|
|
|
|
|
|
# Companions can only be copied after their feature's patches land, so
|
|
|
|
|
# they live here rather than in overlay/ — and a missing one turns a
|
|
|
|
|
# successfully patched shard into one that does not compile.
|
|
|
|
|
for f in $(jq -r '.features[].companions[].file' patches/tier.json); do
|
|
|
|
|
[ -f "patches/${f}" ] || fail "patches/${f} is missing (a feature's companion source)"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
for r in $(jq -r '.features[].rebuild' patches/tier.json); do
|
|
|
|
|
case "$r" in
|
|
|
|
|
core|scripts) ;;
|
|
|
|
|
*) fail "tier.json declares rebuild=\"${r}\"; only \"core\" or \"scripts\" are understood" ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
echo "patch tier: $(jq -r '.features | length' patches/tier.json) feature(s), $(echo "$PRESENT" | wc -l) patch(es)"
|
|
|
|
|
|
|
|
|
|
[ -f overlay.toml ] || fail "overlay.toml is missing (protocol + ServUO declarations)"
|
|
|
|
|
|
|
|
|
|
# ── OVERLAY ADAPTER: stage, manifest, package ────────────────────────
|
|
|
|
|
@@ -224,6 +349,12 @@ jobs:
|
|
|
|
|
cp -r overlay "${STAGE}/overlay"
|
|
|
|
|
cp -r patches "${STAGE}/patches"
|
|
|
|
|
|
|
|
|
|
# tier.json is folded into manifest.json below, so the staged copy is
|
|
|
|
|
# removed: shipping it twice would give the tarball two statements of
|
|
|
|
|
# the same table, one of which nothing reads and both of which are
|
|
|
|
|
# free to drift.
|
|
|
|
|
rm -f "${STAGE}/patches/tier.json"
|
|
|
|
|
|
|
|
|
|
# Declarations from overlay.toml. Read, don't hardcode — the point of
|
|
|
|
|
# that file is that the protocol number lives in one place.
|
|
|
|
|
PROTOCOL="$(grep -m1 -E '^protocol[[:space:]]*=' overlay.toml | sed -E 's/[^0-9]//g')"
|
|
|
|
|
@@ -234,6 +365,18 @@ jobs:
|
|
|
|
|
[ -n "$PATCHED_AGAINST" ] || { echo "::error::could not read patches_verified_against from overlay.toml"; exit 1; }
|
|
|
|
|
echo "==> protocol=${PROTOCOL} min_servuo=${MIN_SERVUO} patches_verified_against=${PATCHED_AGAINST}"
|
|
|
|
|
|
|
|
|
|
# The patch tier, folded in verbatim minus its comment block. Paths are
|
|
|
|
|
# rewritten to be relative to the tarball root (`patches/<file>`), which
|
|
|
|
|
# is where the installer will find them after extraction — tier.json
|
|
|
|
|
# names them relative to patches/ because that is where a maintainer
|
|
|
|
|
# editing it is looking.
|
|
|
|
|
TIER="$(jq '
|
|
|
|
|
del(._comment)
|
|
|
|
|
| .features |= map(
|
|
|
|
|
.patches |= map(.file |= "patches/" + .)
|
|
|
|
|
| .companions |= map(.file |= "patches/" + .)
|
|
|
|
|
)' patches/tier.json)"
|
|
|
|
|
|
|
|
|
|
# Per-file SHA256 of everything shipped, as a {path: sha} object. The
|
|
|
|
|
# installer records these in install.json so a later `doctor` can tell
|
|
|
|
|
# "operator edited a deployed file" from "the overlay drifted".
|
|
|
|
|
@@ -257,6 +400,7 @@ jobs:
|
|
|
|
|
--argjson protocol "${PROTOCOL}" \
|
|
|
|
|
--arg min_servuo "${MIN_SERVUO}" \
|
|
|
|
|
--arg patched_against "${PATCHED_AGAINST}" \
|
|
|
|
|
--argjson tier "${TIER}" \
|
|
|
|
|
--argjson files "${FILES}" \
|
|
|
|
|
'{
|
|
|
|
|
component: $component,
|
|
|
|
|
@@ -268,6 +412,7 @@ jobs:
|
|
|
|
|
min_version: $min_servuo,
|
|
|
|
|
patches_verified_against: $patched_against
|
|
|
|
|
},
|
|
|
|
|
patch_tier: $tier,
|
|
|
|
|
files: $files
|
|
|
|
|
}' > "${STAGE}/manifest.json"
|
|
|
|
|
|
|
|
|
|
@@ -303,7 +448,18 @@ jobs:
|
|
|
|
|
git config user.email "ci@whitlocktech.com"
|
|
|
|
|
git remote set-url origin \
|
|
|
|
|
"https://${CI_USER}:${CI_TOKEN}@${GITEA_HOST}/${REPO}.git"
|
|
|
|
|
git tag "${TAG}"
|
|
|
|
|
|
|
|
|
|
# The tag may already exist when we are finishing a run that died after
|
|
|
|
|
# tagging (see the plan step). `git tag` on an existing name fails under
|
|
|
|
|
# `set -e`, and pushing an identical existing tag is a harmless no-op —
|
|
|
|
|
# so create it only if it is new, then push either way. A push that
|
|
|
|
|
# fails here means the remote tag points somewhere else, which SHOULD
|
|
|
|
|
# stop the run.
|
|
|
|
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
|
|
|
|
echo "Tag ${TAG} already exists — reusing it."
|
|
|
|
|
else
|
|
|
|
|
git tag "${TAG}"
|
|
|
|
|
fi
|
|
|
|
|
git push origin "${TAG}"
|
|
|
|
|
|
|
|
|
|
# ── RELEASE ENGINE: create the Gitea release + upload assets ─────────
|
|
|
|
|
@@ -335,3 +491,45 @@ jobs:
|
|
|
|
|
-F "attachment=@dist/${f}" >/dev/null
|
|
|
|
|
echo " uploaded ${f}"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# ── Recompose the installer's bundle manifest ────────────────────────
|
|
|
|
|
# The installer does not resolve "latest" at run time — it deploys the
|
|
|
|
|
# exact overlay named by a published bundle (docs/installer/PLAN.md §7.1).
|
|
|
|
|
# An overlay release that nobody recomposes around is therefore a release
|
|
|
|
|
# no operator will ever be offered. This tells the installer repo to
|
|
|
|
|
# rebuild that manifest now rather than leaving the new version invisible
|
|
|
|
|
# until its nightly cron.
|
|
|
|
|
#
|
|
|
|
|
# That job re-reads this tarball's manifest.json and checks its declared
|
|
|
|
|
# `protocol` against the sidecar's PROTOCOL_VERSION before publishing
|
|
|
|
|
# anything (PLAN.md §7.1, gate 1) — which is the check this repo cannot
|
|
|
|
|
# perform for itself, since the C# plugin announces no version on the wire.
|
|
|
|
|
#
|
|
|
|
|
# DISPATCH, DON'T WAIT (PLAN.md §7.3). Gitea's workflow-dispatch endpoint
|
|
|
|
|
# returns no run handle, so there is nothing to poll: a waiting step would
|
|
|
|
|
# have to guess which run is its own and hold a runner idle to do it.
|
|
|
|
|
#
|
|
|
|
|
# A failure here is a WARNING, never a failure of this job. The release is
|
|
|
|
|
# already published and correct by this point, and failing the run would
|
|
|
|
|
# misreport that. The installer's nightly cron recomposes from whatever the
|
|
|
|
|
# latest releases actually are, so a dropped dispatch costs latency, not
|
|
|
|
|
# correctness.
|
|
|
|
|
- name: Ask the installer repo to recompose its bundle
|
|
|
|
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
|
|
|
|
env:
|
|
|
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')"
|
|
|
|
|
HTTP="$(curl -s -o /dev/null -w '%{http_code}' -X POST \
|
|
|
|
|
-H "Authorization: token ${CI_TOKEN}" \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-d '{"ref":"main"}' \
|
|
|
|
|
"https://${GITEA_HOST}/api/v1/repos/${INSTALLER_REPO}/actions/workflows/bundle.yml/dispatches" || echo 000)"
|
|
|
|
|
case "$HTTP" in
|
|
|
|
|
20*) echo "Dispatched ${INSTALLER_REPO} bundle.yml (HTTP ${HTTP}) — not waiting for it." ;;
|
|
|
|
|
403|404)
|
|
|
|
|
echo "::warning::Could not dispatch ${INSTALLER_REPO} bundle.yml (HTTP ${HTTP}). REGISTRY_TOKEN likely lacks write:repository on that repo. Release ${{ steps.plan.outputs.tag }} is published and fine; its bundle will be composed by the installer's nightly cron instead." ;;
|
|
|
|
|
*)
|
|
|
|
|
echo "::warning::Dispatching ${INSTALLER_REPO} bundle.yml returned HTTP ${HTTP}. Release ${{ steps.plan.outputs.tag }} is published and fine; the nightly cron will recompose the bundle." ;;
|
|
|
|
|
esac
|
|
|
|
|
|