Skip to content

build(deps-dev): bump @nuxt/test-utils from 3.23.0 to 4.0.3 in /src/Gatherstead.Web #104

build(deps-dev): bump @nuxt/test-utils from 3.23.0 to 4.0.3 in /src/Gatherstead.Web

build(deps-dev): bump @nuxt/test-utils from 3.23.0 to 4.0.3 in /src/Gatherstead.Web #104

Workflow file for this run

name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
# Serialize deploys per branch so two pushes to main can't race the same App Service / database.
concurrency:
group: ci-cd-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage" --settings coverlet.runsettings --results-directory coverage
- name: Generate coverage report
run: |
dotnet tool install --global dotnet-reportgenerator-globaltool
reportgenerator \
-reports:"coverage/**/coverage.cobertura.xml" \
-targetdir:"coverage/report" \
-reporttypes:"MarkdownSummaryGithub;Cobertura"
- name: Write coverage to job summary
run: cat coverage/report/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
- name: Upload coverage report
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: coverage/report/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
files: coverage/report/Cobertura.xml
fail_ci_if_error: false
build-frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Gatherstead.Web
steps:
- uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: '10.33.0'
- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: '24'
cache: pnpm
cache-dependency-path: src/Gatherstead.Web/pnpm-lock.yaml
- name: Install (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm run lint
- name: Test
run: pnpm run test:run
- name: Build
run: pnpm build
check-openapi-freshness:
needs: [build-backend]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: Build API (Release)
run: dotnet build src/Gatherstead.Api/Gatherstead.Api.csproj --no-restore --configuration Release
- name: Restore dotnet tools
run: dotnet tool restore
- name: Generate fresh OpenAPI spec
run: |
dotnet swagger tofile \
--output /tmp/openapi-fresh.json \
src/Gatherstead.Api/bin/Release/net10.0/Gatherstead.Api.dll v1
- name: Fail if spec is stale (run scripts/generate-openapi.sh to update)
run: |
if [ ! -f src/Gatherstead.Api/openapi.json ]; then
echo "::warning::openapi.json is not yet committed. Run scripts/generate-openapi.sh once to generate and commit it."
else
diff src/Gatherstead.Api/openapi.json /tmp/openapi-fresh.json
fi
# ---------------------------------------------------------------------------
# Deploy jobs — only on push to main, and only after build + test are green.
# Auth is GitHub OIDC → the id-gat-ci user-assigned managed identity (no secrets).
# Required repo secrets: AZURE_CLIENT_ID (ciIdentityClientId output), AZURE_TENANT_ID,
# AZURE_SUBSCRIPTION_ID, DEMO_APPINSIGHTS_CONNECTION_STRING.
# (The demo SWA token is fetched at runtime via the CI identity — no stored token.)
# Required repo variables (from Bicep outputs): AZURE_RESOURCE_GROUP, API_APP_NAME,
# WEB_APP_NAME, SQL_SERVER_NAME, SQL_DATABASE_NAME, DEMO_SWA_NAME.
# (KEYVAULT_CMK_ID is no longer used by CI — it's an input to the manual admin
# Always Encrypted setup; see docs/DEPLOYMENT.md.)
# Optional repo variables (public site URLs baked into the web/demo builds; unset = link/CTA hidden):
# PROJECT_GITHUB_URL, DOCS_URL, LIVE_URL (demo's "Go live" CTA), DEMO_URL (prod's "Try the Demo" CTA),
# CONTACT_EMAIL (mailto on the /contact page; unset = neutral fallback, no mailto link).
# ---------------------------------------------------------------------------
deploy-migrations:
name: Deploy DB migrations
needs: [build-backend, build-frontend]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: Build migration bundle
run: |
# dotnet-ef is pinned in .config/dotnet-tools.json alongside the EF Core packages.
dotnet tool restore
# Built offline (no DB connection). Unlike an --idempotent script, the bundle reads
# __EFMigrationsHistory at run time and sends ONLY pending migrations, so SQL from
# already-applied migrations is never re-parsed against a schema it no longer matches.
dotnet ef migrations bundle --project src/Gatherstead.Data --configuration Release --force -o efbundle
chmod +x efbundle
- name: Open SQL firewall for runner
run: |
RUNNER_IP=$(curl -fsSL https://api.ipify.org)
az sql server firewall-rule create \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--server "${{ vars.SQL_SERVER_NAME }}" \
--name "gh-actions-${{ github.run_id }}" \
--start-ip-address "$RUNNER_IP" --end-ip-address "$RUNNER_IP"
- name: Apply migrations
run: |
# Active Directory Default → DefaultAzureCredential picks up the azure/login (Azure CLI) session.
# Serverless DB may be auto-paused; the first connect wakes it, so allow a generous timeout.
# --verbose lists which migrations were pending, which is the record of what prod actually ran.
./efbundle --verbose \
--connection "Server=tcp:${{ vars.SQL_SERVER_NAME }}.database.windows.net,1433;Database=${{ vars.SQL_DATABASE_NAME }};Authentication=Active Directory Default;Encrypt=True;TrustServerCertificate=False;Connection Timeout=60;"
- name: Remove SQL firewall rule
if: always()
run: |
az sql server firewall-rule delete \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--server "${{ vars.SQL_SERVER_NAME }}" \
--name "gh-actions-${{ github.run_id }}" || true
# NOTE: Always Encrypted setup (CMK/CEK, column encryption, temporal retention) is intentionally
# NOT a CI job. Wrapping the CEK requires Key Vault Crypto access (which the CI identity does not
# have) and the setup is driven by the Gatherstead.Data.Setup tool rather than a raw script. It is
# run manually as a SQL admin; see docs/DEPLOYMENT.md.
deploy-api:
name: Deploy API
needs: [build-backend, build-frontend, deploy-migrations]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore --locked-mode
- name: Publish API
run: dotnet publish src/Gatherstead.Api/Gatherstead.Api.csproj --no-restore -c Release -o publish/api
- name: Package
run: cd publish/api && zip -r ../../api.zip .
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to App Service
run: |
az webapp deploy \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--name "${{ vars.API_APP_NAME }}" \
--src-path api.zip --type zip
deploy-web:
name: Deploy Web
needs: [deploy-api]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Gatherstead.Web
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: '10.33.0'
- uses: actions/setup-node@v7
with:
node-version: '24'
cache: pnpm
cache-dependency-path: src/Gatherstead.Web/pnpm-lock.yaml
- name: Install (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build (SSR)
env:
NUXT_PUBLIC_GITHUB_URL: ${{ vars.PROJECT_GITHUB_URL }}
NUXT_PUBLIC_DOCS_URL: ${{ vars.DOCS_URL }}
NUXT_PUBLIC_DEMO_URL: ${{ vars.DEMO_URL }}
NUXT_PUBLIC_CONTACT_EMAIL: ${{ vars.CONTACT_EMAIL }}
run: pnpm build
- name: Package
# Keep the .output directory in the archive — the App Service start command is
# `node .output/server/index.mjs` (see infrastructure/modules/appservice.bicep).
run: zip -r web.zip .output
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to App Service
run: |
az webapp deploy \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--name "${{ vars.WEB_APP_NAME }}" \
--src-path web.zip --type zip
deploy-demo:
name: Deploy demo site
needs: [deploy-api]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: '11.5.0'
- uses: actions/setup-node@v7
with:
node-version-file: '.node-version'
cache: 'pnpm'
cache-dependency-path: src/Gatherstead.Web/pnpm-lock.yaml
- name: Install dependencies
working-directory: src/Gatherstead.Web
run: pnpm install --frozen-lockfile
- name: Generate static demo site
working-directory: src/Gatherstead.Web
env:
NUXT_PUBLIC_DEMO_MODE: 'true'
NUXT_PUBLIC_LIVE_URL: ${{ vars.LIVE_URL }}
NUXT_PUBLIC_GITHUB_URL: ${{ vars.PROJECT_GITHUB_URL }}
NUXT_PUBLIC_DOCS_URL: ${{ vars.DOCS_URL }}
NUXT_PUBLIC_CONTACT_EMAIL: ${{ vars.CONTACT_EMAIL }}
# Demo App Insights (appi-gat-demo-*) — ingestion-only; safe to bake into the static build.
NUXT_PUBLIC_APP_INSIGHTS_CONNECTION_STRING: ${{ secrets.DEMO_APPINSIGHTS_CONNECTION_STRING }}
run: pnpm generate
- name: Azure login (OIDC)
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Fetch SWA deployment token
id: swa
run: |
TOKEN=$(az staticwebapp secrets list \
--name "${{ vars.DEMO_SWA_NAME }}" \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--query "properties.apiKey" -o tsv)
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ steps.swa.outputs.token }}
action: upload
app_location: src/Gatherstead.Web/.output/public
skip_app_build: true