Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/rules/60-deployment.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ When the user says to deploy:
4. Wait for the `check` job in the CI workflow. It runs `npm ci`, `npm test`, and `npm run build`, rejects `dist` above 20,000 files or any file over 25 MiB, then uploads a preview. `npm run smoke` is not in the job.
5. Open the preview at `https://<branch>.detro.pages.dev`. A slash in the branch name becomes a hyphen. Confirm `/`, `/map`, `/city`, `/saved`, and `/help` load. If the check fails, stop and report the check and the URL.
6. Merge into `main` only after that job is green. Branch protection requires the `check` job.
7. The merge publishes production at `https://detro.pages.dev`.
7. The merge publishes production at `https://detro.pages.dev`. After that upload succeeds, CI deletes the merged feature branch. Do not delete `main`. If the production upload fails, the branch stays.

The Pages project `detro` already exists and is Direct Upload. Do not create a second project, do not connect Pages to Git, and do not upload with Wrangler from this machine.

Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,47 @@ jobs:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=detro --branch=${{ github.head_ref || github.ref_name }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

delete-merged-branch:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: check
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- name: Delete the merged branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
repo="${GITHUB_REPOSITORY}"
sha="${GITHUB_SHA}"
branches=$(gh api "repos/${repo}/commits/${sha}/pulls" \
| jq -r --arg repo "$repo" \
'.[] | select(.base.ref == "main" and (.head.repo.full_name // $repo) == $repo) | .head.ref' \
| sort -u)
if [ -z "${branches}" ]; then
message=$(gh api "repos/${repo}/commits/${sha}" --jq .commit.message)
if [[ "${message}" =~ Merge\ pull\ request\ #([0-9]+) ]]; then
branches=$(gh pr view "${BASH_REMATCH[1]}" --repo "$repo" --json headRefName --jq .headRefName)
fi
fi
if [ -z "${branches}" ]; then
echo "No merged branch on this commit"
exit 0
fi
while IFS= read -r branch; do
[ -z "${branch}" ] && continue
if [ "${branch}" = "main" ]; then
echo "Refusing to delete main"
continue
fi
encoded=$(jq -nr --arg branch "$branch" '$branch | @uri')
if gh api "repos/${repo}/git/refs/heads/${encoded}" >/dev/null 2>&1; then
gh api --method DELETE "repos/${repo}/git/refs/heads/${encoded}"
echo "Deleted ${branch}"
else
echo "${branch} is already gone"
fi
done <<< "${branches}"
45 changes: 45 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,50 @@ jobs:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=detro --branch=${{ github.head_ref || github.ref_name }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

delete-merged-branch:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: check
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- name: Delete the merged branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
repo="${GITHUB_REPOSITORY}"
sha="${GITHUB_SHA}"
branches=$(gh api "repos/${repo}/commits/${sha}/pulls" \
| jq -r --arg repo "$repo" \
'.[] | select(.base.ref == "main" and (.head.repo.full_name // $repo) == $repo) | .head.ref' \
| sort -u)
if [ -z "${branches}" ]; then
message=$(gh api "repos/${repo}/commits/${sha}" --jq .commit.message)
if [[ "${message}" =~ Merge\ pull\ request\ #([0-9]+) ]]; then
branches=$(gh pr view "${BASH_REMATCH[1]}" --repo "$repo" --json headRefName --jq .headRefName)
fi
fi
if [ -z "${branches}" ]; then
echo "No merged branch on this commit"
exit 0
fi
while IFS= read -r branch; do
[ -z "${branch}" ] && continue
if [ "${branch}" = "main" ]; then
echo "Refusing to delete main"
continue
fi
encoded=$(jq -nr --arg branch "$branch" '$branch | @uri')
if gh api "repos/${repo}/git/refs/heads/${encoded}" >/dev/null 2>&1; then
gh api --method DELETE "repos/${repo}/git/refs/heads/${encoded}"
echo "Deleted ${branch}"
else
echo "${branch} is already gone"
fi
done <<< "${branches}"
```

A push to `main` publishes production. A pull request from this repository publishes a preview at `https://<branch>.detro.pages.dev`. A slash in the branch name becomes a hyphen. A pull request from a fork does not receive the secrets, so the deploy step must not run for it. `npm run smoke` is not in this job.
Expand All @@ -104,6 +148,7 @@ Two free steps, and a deploy cannot start unless the tests pass.
2. The same job counts the files in `dist` and measures the largest file. It fails at 20,000 files or at a file over 25 MiB, which are the Pages free limits.
3. On a pull request from this repository, the job uploads `dist` with Wrangler as a Pages preview. The address looks like `https://<branch>.detro.pages.dev`.
4. On a push to `main`, after the same tests, the job uploads `dist` as production.
5. After that production upload succeeds, a second job deletes the merged feature branch. It does not run on a pull request, and it does not delete `main`. If the production upload fails, the branch stays.

Cloudflare Pages is a Direct Upload project. It does not also build from Git. A Git-connected build would deploy even when the tests failed, and it would spend the 500 builds a month. A Wrangler upload does not use that build quota. GitHub-hosted runners are free for a public repository, and a private repository includes 2,000 minutes a month, which this test-and-build job will not use up.

Expand Down
Loading