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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: CI
on:
pull_request:
push:
branches: [main]
branches: [main, develop, "release/**", "hotfix/**"]
workflow_call:
workflow_dispatch:

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ name: Release Please

on:
push:
branches: [main]
branches: ["release/**", "hotfix/**"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-please-main
group: release-please-${{ github.ref }}
cancel-in-progress: false

jobs:
prepare:
if: github.ref == 'refs/heads/main'
if: startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/hotfix/')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand All @@ -28,7 +28,7 @@ jobs:
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
with:
token: ${{ github.token }}
target-branch: main
target-branch: ${{ github.ref_name }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: true
Expand Down
54 changes: 48 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,52 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
export RELEASE_COMMIT="$(git rev-parse HEAD)"
prs=$(gh pr list --repo "$GITHUB_REPOSITORY" --state merged --base main \
release_commit=$(git rev-parse HEAD)
gh pr list --repo "$GITHUB_REPOSITORY" --state merged \
--label "autorelease: pending" --limit 100 --json number,mergeCommit \
--jq '.[] | select(.mergeCommit.oid == env.RELEASE_COMMIT) | .number')
for pr in $prs; do
gh pr edit "$pr" --repo "$GITHUB_REPOSITORY" --remove-label "autorelease: pending"
done
--jq '.[] | [.number, .mergeCommit.oid] | @tsv' > "$RUNNER_TEMP/release-prs.tsv"
while IFS=$'\t' read -r pr commit; do
if [ -n "$commit" ] && git merge-base --is-ancestor "$commit" "$release_commit"; then
gh pr edit "$pr" --repo "$GITHUB_REPOSITORY" --remove-label "autorelease: pending"
fi
done < "$RUNNER_TEMP/release-prs.tsv"

deploy:
name: Deploy published release
needs: publish
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
concurrency:
group: production-deployment
cancel-in-progress: false
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
RENDER_DEPLOY_HOOK_URL: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Verify published release is current main
shell: bash
run: |
set -euo pipefail
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \
--json isDraft,isPrerelease,tagName \
--jq 'select(.isDraft == false and .isPrerelease == false) | .tagName' \
| grep -Fx -- "$RELEASE_TAG"
# A stale release retry must not roll production back.
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
- name: Request Render deployment of the tagged commit
shell: bash
run: |
set -euo pipefail
: "${RENDER_DEPLOY_HOOK_URL:?Add the RENDER_DEPLOY_HOOK_URL repository secret}"
release_commit=$(git rev-parse HEAD)
curl --fail --silent --show-error --max-time 60 \
--get --request POST --data-urlencode "ref=$release_commit" \
"$RENDER_DEPLOY_HOOK_URL" --output "$RUNNER_TEMP/render-deploy.json"
echo "Render accepted deployment of $RELEASE_TAG ($release_commit). Verify completion and commit SHA in Render before claiming the release is live." >> "$GITHUB_STEP_SUMMARY"
2 changes: 2 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Deployment checklist

Production deploys only after publication of a release tag. Feature work integrates on `develop`; `main` holds release history. Configure hosting and GitHub secrets using [the GitFlow release guide](releases.md#hosting-setup).

This guide is for operators hosting their own Wida instance. The source is available under the [MIT License](../LICENSE); Google, Azure, and hosting services require your own configuration and may incur costs. The repository does not provision infrastructure or promise a hosted service.

For the optional Render Free/Supabase deployment, use the [step-by-step configuration and secrets guide](render-free.md). Its sleep behavior and classic-queue guarantees differ from an always-running VPS.
Expand Down
53 changes: 32 additions & 21 deletions docs/releases.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
# API releases
# API releases with GitFlow

The API and frontend have independent versions and tags. Release Please uses the `simple` strategy to update `version.txt`, `.release-please-manifest.json` and `CHANGELOG.md`. `version.txt` is release metadata, not an override of .NET assembly versions or Docker image tags.
The API and frontend have independent versions. Production deploys from published release tags, not ordinary PR merges. GitFlow uses `develop` for integration and `main` for release history.

## First release
## Branches and merge rules

The API release history starts at `0.1.0`. `0.0.0` was an unpublished bootstrap marker. The bootstrap commit `36a04fdb52708c6a82cc627adeaf1e407272e379` excludes earlier history while including the already-deployed document deletion change. Do not tag `0.0.0` or invent historical release dates. The bootstrap SHA only applies before the first published release.
- `feature/*` starts from `develop` and returns to `develop` through a PR. Use Conventional Commit titles (`feat:`, `fix:`) and squash feature PRs.
- `release/*` starts from `develop` when the release scope is frozen. Only stabilization fixes and release metadata belong here; new features continue on `develop`.
- `hotfix/*` starts from `main` for urgent production fixes.
- Merge a completed release or hotfix into `main` with **Create a merge commit**, not squash or rebase. After publication, merge `main` back into `develop` with a merge commit, and into any active release branch as needed. This preserves ancestry and carries fixes, version files and changelog updates forward.
- Keep `main` and `develop` permanently. Delete feature/release/hotfix branches only after their work is merged back. There is no `production` branch.

## Automated preparation
Protect both long-lived branches with CI and reviews appropriate to the team. Ensure merge commits are enabled for release and back-merge PRs; a linear-history rule conflicts with this GitFlow history. The repository can retain `main` as its default branch; always select `develop` explicitly for feature PRs.

1. Allow GitHub Actions to create pull requests in this API repository's Settings → Actions → General. This setting is independent from the frontend repository.
2. Use Conventional Commit titles and squash-merge normal PRs. `feat:` increments the minor version; `fix:` increments the patch version. Breaking changes increment minor versions during `0.x`, and major versions after `1.0`. Hidden maintenance commits do not independently request a release.
3. On pushes to `main`, Release Please opens or updates a release PR. Manual retry is available through Actions → Release Please → Run workflow on `main`.
4. The workflow uses `GITHUB_TOKEN` and explicitly dispatches CI on the generated release branch because bot-created PRs do not automatically trigger PR workflows. No personal token or production secret is required. Empty PR outputs are handled safely.
5. Review the proposed version and notes. Move applicable `[Unreleased]` entries into the generated version section, remove duplicates and retain deployment caveats. Finish editorial changes after the last feature merge, as the bot can regenerate notes.
## Prepare a release

CI checks release metadata, restores/builds the .NET 10 solution, runs the API tests and publishes the API into a temporary directory. PostgreSQL/RabbitMQ integration tests remain skipped unless their documented `WIDA_QUEUE_TEST_POSTGRES` and `WIDA_QUEUE_TEST_RABBITMQ` test-service environment variables are configured. CI never uses the production services.
1. Finish feature PRs into `develop`, then create a release branch (for example `release/next`) from its tested tip and push it. Keep only one normal release in preparation at a time.
2. The Release Please workflow runs on `release/*` and `hotfix/*`, with that branch as its target. It opens a metadata PR **into the release/hotfix branch**, not into `develop` or `main`. It does not run on ordinary pushes to either long-lived branch.
3. Review the generated version and dated changelog. Conventional `feat:` and `fix:` commits determine the version; branch names do not set it. Add compatibility notes and move relevant Unreleased entries into the version section. Merge the metadata PR into the release branch once its CI passes. Do not manually publish or tag the metadata PR commit.
4. Stabilize and test the release branch, then open its PR into `main`. Finish stabilization before merging the metadata PR when possible. If fixes are added afterward, update that version’s changelog on the release branch before finishing; a merged metadata PR remains pending until publication.
5. Merge the release PR into `main` with a merge commit. Wait for CI on that exact merge commit. Do not merge the next release into `main` until this one's deployment is complete.

## Validate, tag and publish
Release Please uses `GITHUB_TOKEN` and `skip-github-release: true`. Its generated metadata PR explicitly dispatches CI because token-created PRs do not trigger ordinary PR workflows. Manual preparation is available from Actions → Release Please → Run workflow, selecting the release/hotfix branch. Enable GitHub Actions PR creation in repository settings.

Release Please prepares PRs only (`skip-github-release: true`). After CI passes, merge the reviewed release PR under the repository's normal protections. Wait for main CI on that exact merge commit.
## Publish and deploy

```sh
git fetch origin main --tags
node scripts/check-release.mjs
node scripts/release-notes.mjs vX.Y.Z
git tag -a vX.Y.Z <validated-release-merge-commit> -m "Wida API vX.Y.Z"
# Use the exact release merge SHA whose CI passed, not an arbitrary later main commit.
git tag -a vX.Y.Z <validated-release-merge-sha> -m "Wida API vX.Y.Z"
git push origin vX.Y.Z
```

The tag workflow reruns CI, requires a commit on main, verifies the tag against `version.txt`, extracts only that version's dated changelog entry and publishes a GitHub Release. It then removes the matching release PR's `autorelease: pending` label so preparation can continue. The unpublished `0.0.0` marker has no dated notes and cannot pass publication.
The tag workflow reruns CI, verifies main ancestry, validates the version and dated changelog, and publishes the GitHub Release. It clears pending labels on merged Release Please metadata PRs included in the tag. A deployment job then checks the release is published (not draft/prerelease) and that the tagged SHA is still the current main tip before deploying that exact revision. A release event generated with `GITHUB_TOKEN` cannot trigger another ordinary Actions workflow, so deployment is part of the same tag workflow after publication.

Keep published tags immutable. If publication or label cleanup fails, inspect logs and rerun: existing published releases are reused, while existing drafts require explicit attention. If tagging a later commit that supersedes an unpublished release PR, reconcile that older PR's pending label only after successful publication.
Never move published tags. For a transient publication or deployment failure, rerun the failed job for the same tag. Deployment jobs are serialized. An older release whose SHA no longer equals main fails instead of rolling production back. A retry may create another hosting deployment of the same commit. For emergency rollback, restore the previous successful deployment in the host dashboard, then ship a hotfix release; do not force-push main or reuse a tag.

## Deployment
After publishing, open `main` → `develop` and merge it with a merge commit. Merge hotfixes into an active release branch too. This back-merge is required before preparing the next release so Release Please sees the previous version and history.

A GitHub Release does not itself deploy the API. Render's existing Git integration can deploy when main changes. Verify the deployed commit and `/healthz`, and perform appropriate authenticated smoke tests before claiming an endpoint works in production. Document skipped checks honestly. Deploy backward-compatible API changes before dependent frontend features.
## Hosting setup

No migration, authentication configuration, production credentials or Render settings are changed by this release setup. Keep the previous successful Render deployment for rollback. Publish fixes under a new version instead of moving an existing tag.
1. Render → wida-api → Settings: keep Branch `main` and set **Auto-Deploy → Off**. The Blueprint also declares `branch: main` and `autoDeployTrigger: off`; manually created services need the dashboard change.
2. Copy the Render service's Deploy Hook URL into the API repository's GitHub Actions secret `RENDER_DEPLOY_HOOK_URL`. Do not commit it or paste it in a PR.
3. The release job calls the hook with `ref=<tagged commit SHA>`. A successful hook response means accepted/queued, not that the service is live. Verify Render's deployed SHA and `/healthz`, then appropriate authenticated smoke tests.

See [Render deploy hooks](https://render.com/docs/deploy-hooks). No database or authentication settings change. The API's PostgreSQL/RabbitMQ integration tests remain skipped unless their documented test-service variables are configured. Release the backward-compatible API before a dependent frontend release.

## Activating this setup

Disable the host's automatic production deploys and add the required repository secrets before publishing the next release. Merge this infrastructure setup into main, then merge main into develop with a merge commit so future feature/release branches inherit it. This one-time setup PR is the exception to normal release-only main merges. Existing published tags contain the old workflow; rerunning them does not use this new deployment job. Verify the next newly published release end to end before declaring automated release deployment operational.
2 changes: 2 additions & 0 deletions render.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ services:
- type: web
name: wida-api
runtime: docker
branch: main
autoDeployTrigger: "off"
plan: free
dockerfilePath: ./Dockerfile
healthCheckPath: /healthz
Expand Down
Loading