chore(deps): bump vue from 3.5.35 to 3.5.38 in /EssentialCSharp.Web (… #2293
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build, Test, and Deploy EssentialCSharp.Web | |
| on: | |
| push: | |
| branches: ["main"] | |
| workflow_dispatch: | |
| permissions: | |
| id-token: write | |
| contents: read | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| environment: "BuildAndUploadImage" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up .NET Core | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| global-json-file: global.json | |
| source-url: https://pkgs.dev.azure.com/intelliTect/_packaging/EssentialCSharp/nuget/v3/index.json | |
| env: | |
| NUGET_AUTH_TOKEN: ${{ secrets.AZURE_DEVOPS_PAT }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: EssentialCSharp.Web/package-lock.json | |
| - name: Set up dependency caching for faster builds | |
| uses: actions/cache@v5 | |
| id: nuget-cache | |
| with: | |
| path: | | |
| ~/.nuget/packages | |
| ${{ github.workspace }}/**/obj/project.assets.json | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} | |
| ${{ runner.os }}-nuget- | |
| - name: Restore with dotnet | |
| run: dotnet restore | |
| - name: Build with dotnet | |
| run: dotnet build -p:ContinuousIntegrationBuild=True -p:ReleaseDateAttribute=True --configuration Release --no-restore | |
| - name: Expose GitHub Actions Runtime | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']); | |
| core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']); | |
| - name: Run .NET Tests | |
| run: dotnet test --no-build --configuration Release | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| id: buildx | |
| - name: Restore BuildKit cache mounts | |
| uses: actions/cache@v5 | |
| id: buildkit-cache | |
| with: | |
| path: buildkit-cache | |
| key: buildkit-cache-${{ hashFiles('EssentialCSharp.Web/Dockerfile', 'Directory.Packages.props', '**/*.csproj', 'EssentialCSharp.Web/package-lock.json') }} | |
| restore-keys: buildkit-cache- | |
| - name: Inject BuildKit cache mounts | |
| uses: reproducible-containers/buildkit-cache-dance@v3 | |
| with: | |
| builder: ${{ steps.buildx.outputs.name }} | |
| dockerfile: EssentialCSharp.Web/Dockerfile | |
| cache-dir: buildkit-cache | |
| skip-extraction: false | |
| - name: Generate Docker NuGet credentials config | |
| env: | |
| NUGET_FEED_TOKEN: ${{ secrets.AZURE_DEVOPS_PAT }} | |
| run: | | |
| mkdir -p "${RUNNER_TEMP}/nuget" | |
| bash scripts/nuget/emit-creds-config.sh > "${RUNNER_TEMP}/nuget/NuGet.Config" | |
| chmod 600 "${RUNNER_TEMP}/nuget/NuGet.Config" | |
| # Only build for dev registry — prod gets the image via az acr import in deploy-production | |
| - name: Build Container Image | |
| if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| tags: ${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:${{ github.sha }},${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:latest | |
| file: ./EssentialCSharp.Web/Dockerfile | |
| context: . | |
| secret-files: | | |
| "nuget_config=${{ runner.temp }}/nuget/NuGet.Config" | |
| outputs: type=docker,dest=${{ github.workspace }}/essentialcsharpwebimage.tar | |
| cache-from: type=gha,scope=essentialcsharpweb-main | |
| cache-to: type=gha,mode=max,scope=essentialcsharpweb-main | |
| - name: Remove Docker NuGet credentials config | |
| if: always() | |
| run: rm -f "${RUNNER_TEMP}/nuget/NuGet.Config" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: essentialcsharpwebimage | |
| path: ${{ github.workspace }}/essentialcsharpwebimage.tar | |
| deploy-development: | |
| if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: build-and-test | |
| concurrency: | |
| group: deploy-development | |
| cancel-in-progress: false | |
| environment: | |
| name: "Development" | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Azure Login | |
| uses: azure/login@v3 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Download artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: essentialcsharpwebimage | |
| path: ${{ github.workspace }} | |
| - name: Load image | |
| run: | | |
| docker load --input ${{ github.workspace }}/essentialcsharpwebimage.tar | |
| docker image ls -a | |
| - name: Log in to container registry | |
| run: | | |
| REGISTRY="${{ vars.DEVCONTAINER_REGISTRY }}" | |
| az acr login --name "${REGISTRY%.azurecr.io}" | |
| - name: Push Image to Dev Container Registry | |
| run: docker push --all-tags ${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb | |
| - name: Deploy to Container App | |
| env: | |
| CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }} | |
| RESOURCEGROUP: ${{ vars.RESOURCEGROUP }} | |
| run: | | |
| az extension add --name containerapp --upgrade --only-show-errors | |
| az containerapp update \ | |
| --name "$CONTAINER_APP_NAME" \ | |
| --resource-group "$RESOURCEGROUP" \ | |
| --image "${{ vars.DEVCONTAINER_REGISTRY }}/essentialcsharpweb:${{ github.sha }}" | |
| - name: Logout of Azure CLI | |
| if: always() | |
| uses: azure/CLI@v3 | |
| with: | |
| inlineScript: | | |
| az logout | |
| az cache purge | |
| az account clear | |
| deploy-production: | |
| if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [deploy-development] | |
| concurrency: | |
| group: deploy-production | |
| cancel-in-progress: true | |
| environment: | |
| name: "Production" | |
| permissions: | |
| id-token: write | |
| contents: write # needed for git deploy tag | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Azure Login | |
| uses: azure/login@v3 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| # Server-side copy from dev ACR to prod ACR — no artifact download needed. | |
| # PREREQUISITE: prod OIDC identity must have AcrPull on the dev ACR (Terraform RBAC). | |
| - name: Import image from dev ACR to prod ACR | |
| id: import | |
| run: | | |
| DEV_ACR="${{ vars.DEVCONTAINER_REGISTRY }}" | |
| PROD_ACR="${{ vars.PRODCONTAINER_REGISTRY }}" | |
| az acr import \ | |
| --name "${PROD_ACR%.azurecr.io}" \ | |
| --source "${DEV_ACR}/essentialcsharpweb:${{ github.sha }}" \ | |
| --image "essentialcsharpweb:${{ github.sha }}" \ | |
| --image "essentialcsharpweb:latest" \ | |
| --force | |
| DIGEST=$(az acr repository show \ | |
| --name "${PROD_ACR%.azurecr.io}" \ | |
| --image "essentialcsharpweb:${{ github.sha }}" \ | |
| --query "digest" -o tsv) | |
| if [ -z "$DIGEST" ]; then | |
| echo "::error::Failed to capture image digest from prod ACR after import" | |
| exit 1 | |
| fi | |
| echo "digest=$DIGEST" >> $GITHUB_OUTPUT | |
| - name: Deploy to Container App | |
| env: | |
| CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }} | |
| RESOURCEGROUP: ${{ vars.RESOURCEGROUP }} | |
| run: | | |
| az extension add --name containerapp --upgrade --only-show-errors | |
| az containerapp update \ | |
| --name "$CONTAINER_APP_NAME" \ | |
| --resource-group "$RESOURCEGROUP" \ | |
| --image "${{ vars.PRODCONTAINER_REGISTRY }}/essentialcsharpweb@${{ steps.import.outputs.digest }}" | |
| - name: Verify deployed image | |
| env: | |
| CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }} | |
| RESOURCEGROUP: ${{ vars.RESOURCEGROUP }} | |
| run: | | |
| DEPLOYED=$(az containerapp show \ | |
| --name "$CONTAINER_APP_NAME" \ | |
| --resource-group "$RESOURCEGROUP" \ | |
| --query "properties.template.containers[0].image" -o tsv) | |
| EXPECTED="${{ vars.PRODCONTAINER_REGISTRY }}/essentialcsharpweb@${{ steps.import.outputs.digest }}" | |
| if [ "$DEPLOYED" != "$EXPECTED" ]; then | |
| echo "::error::Image mismatch! Expected $EXPECTED but found $DEPLOYED" | |
| exit 1 | |
| fi | |
| echo "Deployed image verified: $DEPLOYED" | |
| - name: Smoke test | |
| env: | |
| CONTAINER_APP_NAME: ${{ vars.CONTAINER_APP_NAME }} | |
| RESOURCEGROUP: ${{ vars.RESOURCEGROUP }} | |
| run: | | |
| FQDN=$(az containerapp show \ | |
| --name "$CONTAINER_APP_NAME" \ | |
| --resource-group "$RESOURCEGROUP" \ | |
| --query "properties.configuration.ingress.fqdn" -o tsv) | |
| # --retry-all-errors ensures HTTP 5xx (cold-start 503s) also trigger retries | |
| curl --fail --retry 10 --retry-delay 15 --retry-all-errors --max-time 30 "https://$FQDN/health" | |
| - name: Cancel pending deployment workflow runs | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| // Cancel pending workflow runs queued from earlier commits. | |
| // Uses context.workflow instead of hardcoded filename for resilience. | |
| let totalCancelledCount = 0; | |
| let page = 1; | |
| try { | |
| while (true) { | |
| const { data } = await github.rest.actions.listWorkflowRuns({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| workflow_id: context.workflow, | |
| status: 'queued', | |
| per_page: 100, | |
| page: page | |
| }); | |
| const runs = data.workflow_runs; | |
| if (runs.length === 0) { | |
| break; | |
| } | |
| for (const run of runs) { | |
| if (run.id === context.runId) { | |
| core.debug(`Skipping current run #${run.id}`); | |
| continue; | |
| } | |
| try { | |
| core.info(`Cancelling queued run #${run.id} (commit: ${run.head_sha.substring(0, 7)}, branch: ${run.head_branch}, created: ${run.created_at})`); | |
| await github.rest.actions.cancelWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: run.id | |
| }); | |
| totalCancelledCount++; | |
| } catch (error) { | |
| core.warning(`Failed to cancel run #${run.id}: ${error.message}`); | |
| } | |
| } | |
| // Last page has fewer results than requested (pagination) | |
| if (runs.length < 100) { | |
| break; | |
| } | |
| page++; | |
| } | |
| } catch (error) { | |
| core.warning(`Failed to list workflow runs: ${error.message}`); | |
| core.info('Continuing with deployment despite cancellation script error'); | |
| } | |
| core.info(`Cancelled ${totalCancelledCount} pending workflow run(s)`); | |
| - name: Tag commit as deployed | |
| run: | | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git config user.name "github-actions[bot]" | |
| # -f allows re-tagging the same SHA on workflow re-runs | |
| git tag -f "deployed/prod/${{ github.sha }}" | |
| git push origin "deployed/prod/${{ github.sha }}" --force | |
| - name: Logout of Azure CLI | |
| if: always() | |
| uses: azure/CLI@v3 | |
| with: | |
| inlineScript: | | |
| az logout | |
| az cache purge | |
| az account clear |