amalgolopoly/.forgejo/workflows/deploy.yaml
Lukas Schaefer 45e1257d54
All checks were successful
Deploy / build (push) Successful in 7s
Deploy / deploy (push) Successful in 2s
Improve deploy workflow
Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>
2026-08-02 21:16:41 -04:00

255 lines
8.3 KiB
YAML

name: Deploy
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: selfhosted
permissions:
contents: read
container:
options: "-v tmp:/ci-share"
outputs:
dist_hash: ${{ steps.stage.outputs.dist_hash }}
share_path: ${{ steps.stage.outputs.share_path }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
node-modules-${{ runner.os }}-
- name: Check Node
run: |
set -euo pipefail
command -v node
command -v npm
node -v
npm -v
- name: Install
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci --prefer-offline --no-audit --no-fund
- name: Lint
id: lint
continue-on-error: true
run: npm run lint
- name: Build
id: build
continue-on-error: true
env:
BASE_PATH: ${{ github.event_name == 'pull_request' && format('/pr-{0}/', github.event.pull_request.number) || '/' }}
run: npm run build
- name: Test
id: test
continue-on-error: true
run: npm test
- name: Check results
run: |
set -euo pipefail
failed=0
if [ "${{ steps.lint.outcome }}" != "success" ]; then
echo "Lint failed" >&2
failed=1
fi
if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "push" ]; then
if [ "${{ steps.build.outcome }}" != "success" ]; then
echo "Build failed" >&2
failed=1
fi
fi
if [ "${{ steps.test.outcome }}" != "success" ]; then
echo "Test failed" >&2
failed=1
fi
exit "$failed"
- name: Stage dist for deploy
id: stage
if: github.event_name == 'push' || github.event_name == 'pull_request'
run: |
set -euo pipefail
share_path="/ci-share/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
rm -rf "$share_path"
mkdir -p "$share_path/dist"
cp -a "${GITHUB_WORKSPACE}/dist/." "$share_path/dist/"
dist_hash="$(tar -C "$share_path/dist" -cf - . | sha256sum | awk '{print $1}')"
echo "share_path=$share_path" >> "$GITHUB_OUTPUT"
echo "dist_hash=$dist_hash" >> "$GITHUB_OUTPUT"
echo "Staged dist at $share_path (sha256=$dist_hash)"
deploy:
needs: build
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: selfhosted
permissions:
contents: write
pull-requests: write
container:
options: "-v tmp:/ci-share"
steps:
- name: Verify staged dist
env:
SHARE_PATH: ${{ needs.build.outputs.share_path }}
EXPECTED_HASH: ${{ needs.build.outputs.dist_hash }}
run: |
set -euo pipefail
if [ -z "${SHARE_PATH}" ] || [ -z "${EXPECTED_HASH}" ]; then
echo "Missing share path or hash from build job" >&2
exit 1
fi
if [ ! -d "${SHARE_PATH}/dist" ]; then
echo "Staged dist not found at ${SHARE_PATH}/dist" >&2
exit 1
fi
actual="$(tar -C "${SHARE_PATH}/dist" -cf - . | sha256sum | awk '{print $1}')"
if [ "$actual" != "$EXPECTED_HASH" ]; then
echo "Dist hash mismatch: expected ${EXPECTED_HASH}, got ${actual}" >&2
exit 1
fi
echo "Verified dist hash ${actual}"
- name: Publish dist branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BUILD_SRC: ${{ needs.build.outputs.share_path }}/dist
run: |
set -euo pipefail
if [ -z "${GITHUB_TOKEN}" ]; then
echo "GITHUB_TOKEN is required to push the dist branch" >&2
exit 1
fi
server="${GITHUB_SERVER_URL:?}"
server="${server#https://}"
server="${server#http://}"
remote="https://oauth2:${GITHUB_TOKEN}@${server}/${GITHUB_REPOSITORY}.git"
build_src="${BUILD_SRC:?}"
if [ "${EVENT_NAME}" = "pull_request" ]; then
target_dir="pr-${PR_NUMBER:?}"
else
target_dir=""
fi
max_attempts=5
attempt=0
while [ "$attempt" -lt "$max_attempts" ]; do
attempt=$((attempt + 1))
echo "Publish attempt ${attempt}/${max_attempts}"
expected="$(git ls-remote "$remote" refs/heads/dist | awk '{print $1}')"
workdir="$(mktemp -d)"
if [ -n "${expected}" ]; then
git clone --depth 1 --branch dist "$remote" "$workdir"
fi
if [ -n "${target_dir}" ]; then
rm -rf "${workdir}/${target_dir}"
mkdir -p "${workdir}/${target_dir}"
cp -a "${build_src}/." "${workdir}/${target_dir}/"
else
# Replace site root but keep PR preview folders
if [ -d "$workdir" ]; then
find "$workdir" -mindepth 1 -maxdepth 1 \
! -name 'pr-*' \
! -name '.git' \
-exec rm -rf {} +
fi
mkdir -p "$workdir"
cp -a "${build_src}/." "${workdir}/"
fi
rm -rf "${workdir}/.git"
git -C "$workdir" init -b dist
git -C "$workdir" config user.name "forgejo-actions[bot]"
git -C "$workdir" config user.email "forgejo-actions[bot]@noreply.localhost"
git -C "$workdir" add -A
git -C "$workdir" commit -m "Deploy ${GITHUB_SHA}${target_dir:+ (${target_dir})}"
set +e
if [ -n "${expected}" ]; then
git -C "$workdir" push --force-with-lease="refs/heads/dist:${expected}" "$remote" HEAD:dist
else
git -C "$workdir" push "$remote" HEAD:dist
fi
push_status=$?
set -e
rm -rf "$workdir"
if [ "$push_status" -eq 0 ]; then
echo "Published dist successfully"
exit 0
fi
echo "Push rejected (likely concurrent deploy); retrying..." >&2
sleep 1
done
echo "Failed to publish dist after ${max_attempts} attempts" >&2
exit 1
- name: Ping update server
env:
UPDATE_SECRET: ${{ secrets.UPDATE_SECRET }}
run: |
set -euo pipefail
if [ -z "${UPDATE_SECRET}" ]; then
echo "UPDATE_SECRET secret is required" >&2
exit 1
fi
git_repo="${GITHUB_SERVER_URL:?}/${GITHUB_REPOSITORY:?}.git"
curl -fsS -X POST https://update.lschaefer.xyz/update-project \
--data-urlencode "secret=${UPDATE_SECRET}" \
--data-urlencode "git_repo=${git_repo}" \
--data-urlencode "output_directory=amalgolopoly" \
--data-urlencode "branch=dist"
- name: Checkout comment script from main
if: github.event_name == 'pull_request'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
sparse-checkout: .forgejo/scripts/comment-preview.mjs
sparse-checkout-cone-mode: false
- name: Comment preview link
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: node .forgejo/scripts/comment-preview.mjs
- name: Cleanup share
if: always()
env:
SHARE_PATH: ${{ needs.build.outputs.share_path }}
run: |
set -euo pipefail
if [ -n "${SHARE_PATH:-}" ]; then
rm -rf "${SHARE_PATH}"
fi