From 59edf4af8f1be58c45529199443f0af2285b2b5c Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Tue, 6 Oct 2026 12:07:40 -0700 Subject: [PATCH 1/3] feat(netlify): add deploy wait scripts with tests Poll logic for netlify-site-checks.yml, tested with stubs. Advances smartwatermelon/dev-env#193 --- netlify/wait-for-preview.sh | 66 ++++++++++++++++++ netlify/wait-for-production.sh | 107 ++++++++++++++++++++++++++++++ tests/stub-netlify/curl | 36 ++++++++++ tests/stub-netlify/gh | 31 +++++++++ tests/test-wait-for-preview.sh | 90 +++++++++++++++++++++++++ tests/test-wait-for-production.sh | 94 ++++++++++++++++++++++++++ 6 files changed, 424 insertions(+) create mode 100755 netlify/wait-for-preview.sh create mode 100755 netlify/wait-for-production.sh create mode 100755 tests/stub-netlify/curl create mode 100755 tests/stub-netlify/gh create mode 100755 tests/test-wait-for-preview.sh create mode 100755 tests/test-wait-for-production.sh diff --git a/netlify/wait-for-preview.sh b/netlify/wait-for-preview.sh new file mode 100755 index 0000000..f5dfef6 --- /dev/null +++ b/netlify/wait-for-preview.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Wait for the Netlify deploy preview of one commit; print its URL on stdout. +# Used by netlify-site-checks.yml. See README.md for details. + +# Env: GITHUB_REPOSITORY, COMMIT_SHA, NETLIFY_SITE, GH_TOKEN (required). +# NETLIFY_POLL_INTERVAL / NETLIFY_POLL_DEADLINE: seconds (15 / 600). + +# Exit 0 ready, 1 failed / no target_url / timeout, 2 bad input. +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" +: "${COMMIT_SHA:?COMMIT_SHA is required}" +: "${NETLIFY_SITE:?NETLIFY_SITE is required}" +interval="${NETLIFY_POLL_INTERVAL:-15}" +deadline_secs="${NETLIFY_POLL_DEADLINE:-600}" + +# The site name goes into a --jq string (no --arg there), so allow only +# Netlify's name charset. +if [[ ! "${NETLIFY_SITE}" =~ ^[a-z0-9-]+$ ]]; then + echo "::error::netlify-site '${NETLIFY_SITE}' is not a Netlify site name ([a-z0-9-]+)" >&2 + exit 2 +fi +if [[ ! "${COMMIT_SHA}" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::COMMIT_SHA '${COMMIT_SHA}' is not a 40-hex commit SHA" >&2 + exit 2 +fi + +# The exact context Netlify posts, keyed by SITE name (not repo name). +status_context="netlify/${NETLIFY_SITE}/deploy-preview" + +deadline=$((SECONDS + deadline_secs)) + +while ((SECONDS < deadline)); do + status_json="$(gh api \ + "repos/${GITHUB_REPOSITORY}/commits/${COMMIT_SHA}/status" \ + --jq ".statuses[] | select(.context == \"${status_context}\")" || + true)" + + # Defensive, not load-bearing on singular /status. Plural /statuses returns + # history, so keep head -1. amelia-boone#50; tested. + state="$(printf '%s' "${status_json}" | jq -r '.state // empty' | head -1)" + target="$(printf '%s' "${status_json}" | jq -r '.target_url // empty' | head -1)" + + case "${state}" in + success) + if [[ -z "${target}" ]]; then + echo "::error::Deploy preview succeeded but reported no target_url." >&2 + exit 1 + fi + echo "Deploy preview ready: ${target}" >&2 + printf '%s\n' "${target}" + exit 0 + ;; + failure | error) + echo "::error::Netlify deploy preview failed (state=${state})." >&2 + exit 1 + ;; + *) + echo "Waiting for deploy preview (state=${state:-none})..." >&2 + sleep "${interval}" + ;; + esac +done + +echo "::error::Timed out after ${deadline_secs}s waiting for the deploy preview (context ${status_context})." >&2 +exit 1 diff --git a/netlify/wait-for-production.sh b/netlify/wait-for-production.sh new file mode 100755 index 0000000..6c66092 --- /dev/null +++ b/netlify/wait-for-production.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Wait for Netlify's production deploy of one commit; print the site URL. +# Used by netlify-site-checks.yml. See README.md for details. + +# Env: NETLIFY_SITE, COMMIT_SHA, NETLIFY_AUTH_TOKEN (required). +# NETLIFY_POLL_INTERVAL / NETLIFY_POLL_DEADLINE: seconds (15 / 600). + +# Exit 0 ready, 1 deploy error / timeout / no URL, 2 bad input or no token. + +# API fields checked 2026-10-06 against open-api.netlify.com/swagger.json +# and live ameliabooneracing responses. README.md has the list. +set -euo pipefail + +: "${NETLIFY_SITE:?NETLIFY_SITE is required}" +: "${COMMIT_SHA:?COMMIT_SHA is required}" +interval="${NETLIFY_POLL_INTERVAL:-15}" +deadline_secs="${NETLIFY_POLL_DEADLINE:-600}" +api="https://api.netlify.com/api/v1" + +if [[ -z "${NETLIFY_AUTH_TOKEN:-}" ]]; then + echo "::error::NETLIFY_AUTH_TOKEN is empty. Production mode needs it: pass" \ + "'secrets: NETLIFY_AUTH_TOKEN: \${{ secrets.NETLIFY_AUTH_TOKEN }}' from the" \ + "caller, and add the secret to the repo (or org)." >&2 + exit 2 +fi +if [[ ! "${NETLIFY_SITE}" =~ ^[a-z0-9-]+$ ]]; then + echo "::error::netlify-site '${NETLIFY_SITE}' is not a Netlify site name ([a-z0-9-]+)" >&2 + exit 2 +fi +if [[ ! "${COMMIT_SHA}" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::COMMIT_SHA '${COMMIT_SHA}' is not a 40-hex commit SHA" >&2 + exit 2 +fi + +# The API accepts .netlify.app in place of the site UUID. +site_id="${NETLIFY_SITE}.netlify.app" + +# Token goes to curl as a config on stdin via builtin printf: never in argv, +# never on disk. -H "Authorization: ..." would put it in argv. +netlify_get() { + printf 'header = "Authorization: Bearer %s"\n' "${NETLIFY_AUTH_TOKEN}" | + curl -K - -fsS --max-time 30 "${api}/$1" +} + +deadline=$((SECONDS + deadline_secs)) +seen=() + +_saw() { # record each state once, in order, for the timeout message + local s + for s in "${seen[@]}"; do [[ "${s}" == "$1" ]] && return 0; done + seen+=("$1") +} + +ready=0 +while ((SECONDS < deadline)); do + if deploys="$(netlify_get "sites/${site_id}/deploys?per_page=20")"; then + # The list is newest first, so a retry after an error wins. + match="$(jq -c --arg sha "${COMMIT_SHA}" \ + '[.[] | select(.context == "production" and .commit_ref == $sha)] | first // empty' \ + <<<"${deploys}")" + if [[ -z "${match}" ]]; then + state="not-yet-present" + else + state="$(jq -r '.state // "unknown"' <<<"${match}")" + fi + else + # Not a verdict on the deploy. Keep waiting; the timeout names it. + state="request-failed" + fi + _saw "${state}" + + case "${state}" in + ready) + echo "Production deploy of ${COMMIT_SHA} is ready." >&2 + ready=1 + break + ;; + error) + msg="$(jq -r '.error_message // "(no error_message)"' <<<"${match}")" + echo "::error::Netlify production deploy of ${COMMIT_SHA} failed: ${msg}" >&2 + exit 1 + ;; + *) + echo "Waiting for production deploy of ${COMMIT_SHA} (state=${state})..." >&2 + sleep "${interval}" + ;; + esac +done + +if ((ready == 0)); then + echo "::error::Timed out after ${deadline_secs}s waiting for the production deploy of" \ + "${COMMIT_SHA} on ${site_id}. States seen: $(IFS=,; echo "${seen[*]}")." >&2 + exit 1 +fi + +if ! site="$(netlify_get "sites/${site_id}")"; then + echo "::error::Could not read site ${site_id} from the Netlify API." >&2 + exit 1 +fi +# ssl_url is the primary custom domain over https; url is the fallback. +url="$(jq -r '[.ssl_url, .url] | map(select(. != null and . != "")) | first // empty' <<<"${site}")" +if [[ -z "${url}" ]]; then + echo "::error::Site ${site_id} reported neither ssl_url nor url." >&2 + exit 1 +fi +echo "Production URL: ${url}" >&2 +printf '%s\n' "${url}" diff --git a/tests/stub-netlify/curl b/tests/stub-netlify/curl new file mode 100755 index 0000000..f24b79d --- /dev/null +++ b/tests/stub-netlify/curl @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Stub curl for test-wait-for-production.sh. Fixtures: token, site.json, +# deploys..json (nth call; last repeats). argv goes to calls.log. + +# The token must arrive as `-K -` config on stdin, else HTTP 401 (exit 22). +set -euo pipefail +: "${STUB_DIR:?}" +printf '%s\n' "$*" >>"${STUB_DIR}/calls.log" +config="" url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -K) [[ "$2" == "-" ]] && config="$(cat)"; shift 2 ;; + --max-time) shift 2 ;; + -*) shift ;; + *) url="$1"; shift ;; + esac +done +want="header = \"Authorization: Bearer $(cat "${STUB_DIR}/token")\"" +if [[ "${config}" != "${want}" ]]; then + echo "curl: (22) The requested URL returned error: 401" >&2 + exit 22 +fi +api="https://api.netlify.com/api/v1" +case "${url}" in + "${api}/sites/"*".netlify.app/deploys?per_page=20") + n=$(($(cat "${STUB_DIR}/deploys.count" 2>/dev/null || echo 0) + 1)) + echo "${n}" >"${STUB_DIR}/deploys.count" + f="${STUB_DIR}/deploys.${n}.json" + while [[ ! -f "${f}" && "${n}" -gt 1 ]]; do n=$((n - 1)); f="${STUB_DIR}/deploys.${n}.json"; done + cat "${f}" ;; + "${api}/sites/"*".netlify.app") + cat "${STUB_DIR}/site.json" ;; + *) + echo "curl: (22) The requested URL returned error: 404" >&2 + exit 22 ;; +esac diff --git a/tests/stub-netlify/gh b/tests/stub-netlify/gh new file mode 100755 index 0000000..30fa6f0 --- /dev/null +++ b/tests/stub-netlify/gh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Stub gh for test-wait-for-preview.sh. status..json answers the nth +# call (last one repeats); argv goes to calls.log. + +# --jq runs through real jq -r, like gh. Plural /statuses is refused. +set -euo pipefail +: "${STUB_DIR:?}" +printf '%s\n' "$*" >>"${STUB_DIR}/calls.log" +[[ "$1" == "api" ]] || { echo "stub gh: unhandled $*" >&2; exit 99; } +shift +path="" filter="" +while [[ $# -gt 0 ]]; do + case "$1" in + --jq) filter="$2"; shift 2 ;; + -*) shift ;; + *) path="$1"; shift ;; + esac +done +case "${path}" in + repos/*/commits/*/statuses) + echo "stub gh: plural /statuses is not what wait-for-preview.sh may call" >&2 + exit 99 ;; + repos/*/commits/*/status) ;; + *) echo "stub gh: unhandled path ${path}" >&2; exit 99 ;; +esac +n=$(($(cat "${STUB_DIR}/status.count" 2>/dev/null || echo 0) + 1)) +echo "${n}" >"${STUB_DIR}/status.count" +f="${STUB_DIR}/status.${n}.json" +while [[ ! -f "${f}" && "${n}" -gt 1 ]]; do n=$((n - 1)); f="${STUB_DIR}/status.${n}.json"; done +[[ -f "${f}" ]] || { echo "stub gh: no fixture ${f}" >&2; exit 98; } +if [[ -n "${filter}" ]]; then jq -r "${filter}" "${f}"; else cat "${f}"; fi diff --git a/tests/test-wait-for-preview.sh b/tests/test-wait-for-preview.sh new file mode 100755 index 0000000..cb76f3d --- /dev/null +++ b/tests/test-wait-for-preview.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Hermetic tests for netlify/wait-for-preview.sh against tests/stub-netlify/gh. +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +script="${here}/../netlify/wait-for-preview.sh" +# An exported gh/curl function or BASH_ENV would shadow the PATH stubs. +unset -f gh curl 2>/dev/null || true +unset BASH_ENV +pass=0; fail=0 +_ok() { echo " ok $1"; pass=$((pass + 1)); } +_bad() { echo " FAIL $1"; fail=$((fail + 1)); } + +sha="0123456789abcdef0123456789abcdef01234567" +ctx="netlify/examplesite/deploy-preview" +url="https://deploy-preview-7--examplesite.netlify.app" + +_fixture() { # fresh STUB_DIR; status fixtures are written by each case + STUB_DIR="$(mktemp -d)"; export STUB_DIR + : >"${STUB_DIR}/calls.log" +} +# _status N STATE [TARGET]: the nth poll sees one row for the context. +_status() { + jq -n --arg c "${ctx}" --arg s "$2" --arg t "${3-}" \ + '{state: $s, statuses: [{context: "other/check", state: "success", target_url: "x"}, + {context: $c, state: $s, target_url: (if $t == "" then null else $t end)}]}' \ + >"${STUB_DIR}/status.$1.json" +} +# run: sets rc, out (stdout), err (stderr). +run() { + rc=0 + out="$(PATH="${here}/stub-netlify:${PATH}" GITHUB_REPOSITORY=acme/site \ + COMMIT_SHA="${sha}" NETLIFY_SITE=examplesite \ + NETLIFY_POLL_INTERVAL=0.1 NETLIFY_POLL_DEADLINE="${DEADLINE:-3}" \ + bash "${script}" 2>"${STUB_DIR}/err")" || rc=$? + err="$(cat "${STUB_DIR}/err")" +} + +# 1. success after pending: prints target_url and only that on stdout +_fixture +_status 1 pending +_status 2 success "${url}" +run +if [[ "${rc}" -eq 0 && "${out}" == "${url}" ]]; then _ok "pending then success prints the preview URL"; else _bad "success case: rc=${rc} out='${out}' err=${err}"; fi +if grep -q "repos/acme/site/commits/${sha}/status " "${STUB_DIR}/calls.log"; then _ok "polls the singular /status endpoint"; else _bad "endpoint: $(cat "${STUB_DIR}/calls.log")"; fi + +# 2. failure: exits 1 at once, prints nothing on stdout +for s in failure error; do + _fixture + _status 1 "${s}" + run + if [[ "${rc}" -eq 1 && -z "${out}" ]] && grep -q "state=${s}" <<<"${err}"; then _ok "state ${s} fails"; else _bad "${s} case: rc=${rc} out='${out}' err=${err}"; fi +done + +# 3. success without target_url fails loudly rather than checking nothing +_fixture +_status 1 success +run +if [[ "${rc}" -eq 1 ]] && grep -q 'no target_url' <<<"${err}"; then _ok "success with no target_url fails"; else _bad "no-target case: rc=${rc} err=${err}"; fi + +# 4. timeout: never leaves pending, gives up at the deadline +_fixture +_status 1 pending +DEADLINE=1 run +if [[ "${rc}" -eq 1 ]] && grep -q 'Timed out' <<<"${err}"; then _ok "pending forever times out"; else _bad "timeout case: rc=${rc} err=${err}"; fi + +# 5. no status for the context yet (other contexts only), then success +_fixture +jq -n '{state: "pending", statuses: [{context: "other/check", state: "pending"}]}' >"${STUB_DIR}/status.1.json" +_status 2 success "${url}" +run +if [[ "${rc}" -eq 0 && "${out}" == "${url}" ]]; then _ok "absent context waits, then succeeds"; else _bad "absent-context case: rc=${rc} out='${out}' err=${err}"; fi + +# 6. plural-statuses regression (amelia-boone#50): two rows, newest first. +# Without head -1 this reads "success\npending" and times out. +_fixture +jq -n --arg c "${ctx}" --arg t "${url}" \ + '{statuses: [{context: $c, state: "success", target_url: $t}, + {context: $c, state: "pending", target_url: $t}]}' >"${STUB_DIR}/status.1.json" +DEADLINE=2 run +if [[ "${rc}" -eq 0 && "${out}" == "${url}" ]]; then _ok "two rows for the context: newest (success) wins"; else _bad "two-row case: rc=${rc} out='${out}' err=${err}"; fi + +# 7. bad site name is refused before any API call +_fixture +rc=0 +PATH="${here}/stub-netlify:${PATH}" GITHUB_REPOSITORY=acme/site COMMIT_SHA="${sha}" \ + NETLIFY_SITE='bad"site' bash "${script}" >/dev/null 2>&1 || rc=$? +if [[ "${rc}" -eq 2 && ! -s "${STUB_DIR}/calls.log" ]]; then _ok "invalid site name exits 2 with no API call"; else _bad "bad-site case: rc=${rc}"; fi + +echo "${pass} passed, ${fail} failed" +[[ "${fail}" -eq 0 ]] diff --git a/tests/test-wait-for-production.sh b/tests/test-wait-for-production.sh new file mode 100755 index 0000000..09e10a3 --- /dev/null +++ b/tests/test-wait-for-production.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Hermetic tests for netlify/wait-for-production.sh against +# tests/stub-netlify/curl. +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +script="${here}/../netlify/wait-for-production.sh" +# An exported gh/curl function or BASH_ENV would shadow the PATH stubs. +unset -f gh curl 2>/dev/null || true +unset BASH_ENV +pass=0; fail=0 +_ok() { echo " ok $1"; pass=$((pass + 1)); } +_bad() { echo " FAIL $1"; fail=$((fail + 1)); } + +sha="0123456789abcdef0123456789abcdef01234567" +other="fedcba9876543210fedcba9876543210fedcba98" +token="nfp_TESTTOKEN_must_never_be_printed" + +_fixture() { + STUB_DIR="$(mktemp -d)"; export STUB_DIR + : >"${STUB_DIR}/calls.log" + printf '%s' "${token}" >"${STUB_DIR}/token" + printf '{"name":"examplesite","url":"http://example.com","ssl_url":"https://example.com"}' >"${STUB_DIR}/site.json" +} +# _deploys N JSON-ARRAY: body of the nth deploys-list poll +_deploys() { printf '%s' "$2" >"${STUB_DIR}/deploys.$1.json"; } +_d() { # _d CONTEXT SHA STATE [ERROR]: one deploy record + jq -nc --arg c "$1" --arg r "$2" --arg s "$3" --arg e "${4-}" \ + '{context: $c, commit_ref: $r, state: $s, error_message: (if $e == "" then null else $e end)}' +} +run() { + rc=0 + out="$(PATH="${here}/stub-netlify:${PATH}" NETLIFY_SITE=examplesite COMMIT_SHA="${sha}" \ + NETLIFY_AUTH_TOKEN="${TOKEN-${token}}" \ + NETLIFY_POLL_INTERVAL=0.1 NETLIFY_POLL_DEADLINE="${DEADLINE:-3}" \ + bash "${script}" 2>"${STUB_DIR}/err")" || rc=$? + err="$(cat "${STUB_DIR}/err")" +} + +# 1. ready on first poll: prints ssl_url, and only that, on stdout +_fixture +_deploys 1 "[$(_d deploy-preview "${sha}" ready),$(_d production "${sha}" ready)]" +run +if [[ "${rc}" -eq 0 && "${out}" == "https://example.com" ]]; then _ok "ready prints the site ssl_url"; else _bad "ready case: rc=${rc} out='${out}' err=${err}"; fi +if grep -qF -- "${token}" "${STUB_DIR}/calls.log" || grep -qF -- "${token}" <<<"${out}${err}"; then _bad "token leaked into argv or output"; else _ok "token absent from argv and output"; fi + +# 2. error: fails at once and prints error_message +_fixture +_deploys 1 "[$(_d production "${sha}" error "Build script returned non-zero exit code: 2")]" +run +if [[ "${rc}" -eq 1 && -z "${out}" ]] && grep -q 'non-zero exit code: 2' <<<"${err}"; then _ok "error fails with error_message"; else _bad "error case: rc=${rc} out='${out}' err=${err}"; fi + +# 3. absent, building, then ready. A same-SHA deploy-preview is not production. +_fixture +_deploys 1 "[$(_d deploy-preview "${sha}" ready),$(_d production "${other}" ready)]" +_deploys 2 "[$(_d production "${sha}" building),$(_d production "${other}" ready)]" +_deploys 3 "[$(_d production "${sha}" ready),$(_d production "${other}" ready)]" +run +polls="$(cat "${STUB_DIR}/deploys.count")" +if [[ "${rc}" -eq 0 && "${out}" == "https://example.com" && "${polls}" -eq 3 ]]; then _ok "not-yet-present, building, then ready (3 polls)"; else _bad "late case: rc=${rc} polls=${polls} out='${out}' err=${err}"; fi + +# 4. missing token: exits 2 with a clear message, no API call +_fixture +TOKEN="" run +if [[ "${rc}" -eq 2 && ! -s "${STUB_DIR}/calls.log" ]] && grep -q 'NETLIFY_AUTH_TOKEN is empty' <<<"${err}"; then _ok "missing token fails clearly, no API call"; else _bad "missing-token case: rc=${rc} err=${err}"; fi + +# 5. timeout names the states it saw +_fixture +_deploys 1 "[$(_d production "${other}" ready)]" +_deploys 2 "[$(_d production "${sha}" building)]" +# 2s: SECONDS is whole seconds, so 1s can end after one poll; this needs two. +DEADLINE=2 run +if [[ "${rc}" -eq 1 ]] && grep -q 'States seen: not-yet-present,building\.' <<<"${err}"; then _ok "timeout lists states seen"; else _bad "timeout case: rc=${rc} err=${err}"; fi + +# 6. a wrong token reads as request-failed in the timeout, not a silent hang +_fixture +_deploys 1 "[$(_d production "${sha}" ready)]" +TOKEN="wrong" DEADLINE=1 run +if [[ "${rc}" -eq 1 ]] && grep -q 'States seen: request-failed\.' <<<"${err}"; then _ok "rejected token surfaces as request-failed"; else _bad "wrong-token case: rc=${rc} err=${err}"; fi + +# 7. no ssl_url: falls back to url +_fixture +printf '{"name":"examplesite","url":"https://examplesite.netlify.app","ssl_url":null}' >"${STUB_DIR}/site.json" +_deploys 1 "[$(_d production "${sha}" ready)]" +run +if [[ "${rc}" -eq 0 && "${out}" == "https://examplesite.netlify.app" ]]; then _ok "falls back to url when ssl_url is absent"; else _bad "fallback case: rc=${rc} out='${out}' err=${err}"; fi + +# 8. newest record for the SHA wins: a retried deploy after an error +_fixture +_deploys 1 "[$(_d production "${sha}" ready),$(_d production "${sha}" error "old failure")]" +run +if [[ "${rc}" -eq 0 ]]; then _ok "newest production record for the SHA wins"; else _bad "retry case: rc=${rc} err=${err}"; fi + +echo "${pass} passed, ${fail} failed" +[[ "${fail}" -eq 0 ]] From c7f1e33b12f9ce5d7e8bcfa934879cde744a620e Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Tue, 6 Oct 2026 12:08:53 -0700 Subject: [PATCH 2/3] feat(netlify): add netlify-site-checks workflow Replaces hand-copied verify-deploy-preview.yml files and adds a production check on push. Advances smartwatermelon/dev-env#193 --- .github/actionlint.yaml | 5 + .github/workflows/netlify-site-checks.yml | 127 ++++++++++++++++++++++ README.md | 125 +++++++++++++++++++++ netlify/caller-stub.yml | 16 +++ tests/test-wait-for-preview.sh | 3 + tests/test-wait-for-production.sh | 3 + 6 files changed, 279 insertions(+) create mode 100644 .github/workflows/netlify-site-checks.yml create mode 100644 netlify/caller-stub.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index eb984cd..8d14396 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -32,3 +32,8 @@ paths: # Scoped to this one message in this one file: any other expression # error, in this or any other workflow, still fires. - 'property "workflow_sha" is not defined in object type' + .github/workflows/netlify-site-checks.yml: + ignore: + # Same gap as standards-check.yml above: it checks out netlify/ at + # job.workflow_sha. Remove with the entry above. + - 'property "workflow_sha" is not defined in object type' diff --git a/.github/workflows/netlify-site-checks.yml b/.github/workflows/netlify-site-checks.yml new file mode 100644 index 0000000..bd54697 --- /dev/null +++ b/.github/workflows/netlify-site-checks.yml @@ -0,0 +1,127 @@ +name: Netlify Site Checks + +# Acceptance checks on the deploy Netlify published: preview on pull_request, +# production on push (dev-env#193). README.md has the details. + +# Caller stub: netlify/caller-stub.yml. The caller grants contents: read and +# statuses: read, and passes NETLIFY_AUTH_TOKEN by name. + +on: + workflow_call: + inputs: + netlify-site: + description: Netlify site name (the in .netlify.app), e.g. ameliabooneracing + type: string + required: true + check-script: + description: Path in the CALLER repo to the check script, invoked as `