255 lines
8.3 KiB
YAML
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
|