Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,54 @@ jobs:
sudo chown -R $(whoami) .git
sudo chmod -R u+w .git

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
Comment on lines +28 to +32

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pin GitHub Actions to a full commit SHA to prevent supply-chain risks from tag changes. Example: uses: actions/setup-node@ with a comment for the pinned version.

Copilot uses AI. Check for mistakes.

- name: Validate package-lock.json
id: validate-lockfile
run: |
echo "Validating lock file sync..."
if bash scripts/validate-lockfile.sh; then
echo "validation=success" >> $GITHUB_OUTPUT
else
echo "validation=failed" >> $GITHUB_OUTPUT
echo "::error::Lock file validation failed. Please regenerate package-lock.json by running 'npm install'"
exit 1
fi

- name: Install dependencies with retry
id: npm-install
run: |
MAX_ATTEMPTS=3
ATTEMPT=1

while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "πŸ“¦ Attempt $ATTEMPT of $MAX_ATTEMPTS: Running npm ci..."

if npm ci; then
echo "βœ… npm ci succeeded"
exit 0
else
echo "❌ npm ci failed on attempt $ATTEMPT"

if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "::error::npm ci failed after $MAX_ATTEMPTS attempts"
echo "::error::This might indicate:"
echo "::error:: 1. Lock file is out of sync (run validate-lockfile.sh locally)"
echo "::error:: 2. Network issues with npm registry"
echo "::error:: 3. Corrupted cache"
exit 1
fi

echo "Retrying in 5 seconds..."
sleep 5
ATTEMPT=$((ATTEMPT + 1))
fi
done

- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
Comment on lines 76 to 78

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pin Azure/static-web-apps-deploy to a specific commit SHA instead of a floating tag to reduce the risk of unexpected changes in CI.

Copilot uses AI. Check for mistakes.
Expand All @@ -39,6 +87,15 @@ jobs:
output_location: 'dist' # Built app content directory - optional
###### End of Repository/Build Configurations ######

- name: Deployment Summary
if: always()
run: |
echo "## πŸš€ Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Lock File Validation**: ${{ steps.validate-lockfile.outputs.validation }}" >> $GITHUB_STEP_SUMMARY
echo "- **npm ci**: ${{ steps.npm-install.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- **Build and Deploy**: ${{ steps.builddeploy.outcome }}" >> $GITHUB_STEP_SUMMARY

close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '20.x' # Matches @tsconfig/node20
cache: 'npm'

- name: Validate package-lock.json
run: |
echo "Validating lock file sync..."
bash scripts/validate-lockfile.sh

- name: Install dependencies
run: npm ci
Expand Down
158 changes: 158 additions & 0 deletions .github/workflows/cleanup-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
name: Cleanup Staging Environments

on:
# Run weekly on Sundays at 2 AM UTC
schedule:
- cron: '0 2 * * 0'
# Allow manual trigger
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run mode (show what would be deleted without deleting)'
required: false
default: 'false'
type: choice
options:
- 'true'
- 'false'

jobs:
cleanup-staging:
name: Clean up unused staging environments
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Azure Login
uses: azure/login@v2

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For action security, pin azure/login to a commit SHA rather than the v2 tag.

Suggested change
uses: azure/login@v2
uses: azure/login@v2.0.0

Copilot uses AI. Check for mistakes.
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: List and Clean Staging Environments
id: cleanup
run: |
echo "πŸ” Checking for staging environments to clean up..."

DRY_RUN="${{ github.event.inputs.dry_run || 'false' }}"

# Get the Static Web App name from the workflow file
# This assumes the resource name matches the subdomain in the URL
SWA_NAME="thankful-mushroom-08ecc5d1e"
RESOURCE_GROUP="${{ secrets.AZURE_RESOURCE_GROUP || 'bleedy-rg' }}"
Comment on lines +40 to +43

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SWA_NAME is hard-coded, which makes the workflow brittle if the resource name changes or multiple environments are used. Consider passing SWA_NAME and RESOURCE_GROUP as workflow inputs or env variables (defaulting via shell parameter expansion), so they can be configured without code changes.

Copilot uses AI. Check for mistakes.

echo "Static Web App: $SWA_NAME"
echo "Resource Group: $RESOURCE_GROUP"
echo "Dry Run Mode: $DRY_RUN"

# List all environments for this Static Web App
echo "Fetching environment list..."
ENVIRONMENTS=$(az staticwebapp environment list \
--name "$SWA_NAME" \
--resource-group "$RESOURCE_GROUP" \
--query "[].{name:name, kind:kind, hostname:hostname}" \
-o json 2>/dev/null || echo "[]")

if [ "$ENVIRONMENTS" = "[]" ]; then
echo "⚠️ Could not fetch environments. This might be due to:"
echo " 1. Missing AZURE_CREDENTIALS secret"
echo " 2. Incorrect AZURE_RESOURCE_GROUP secret"
echo " 3. Static Web App name changed"
echo ""
echo "Please configure the following secrets:"
echo " - AZURE_CREDENTIALS: Service principal credentials"
echo " - AZURE_RESOURCE_GROUP: Resource group name (optional, defaults to 'bleedy-rg')"
exit 1
fi

echo "Found environments:"
echo "$ENVIRONMENTS" | jq -r '.[] | " - \(.name) (\(.kind))"'

# Get list of open PRs
echo ""
echo "Fetching open pull requests..."
OPEN_PRS=$(gh pr list --json number --jq '.[].number' | tr '\n' ' ')
echo "Open PRs: $OPEN_PRS"
Comment on lines +72 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fetch all open PRs before deciding deletions

The cleanup job relies on gh pr list --json number --jq '.[].number' to decide whether a staging environment belongs to an open pull request, but the command’s default --limit is 30. Repositories with more than 30 open PRs will omit older ones from this list, so their environments will be treated as closed and deleted even though the PRs remain open. Pass an explicit --limit (or paginate) so every open PR is considered before deleting environments.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fetch all open PRs before deciding deletions

The cleanup job relies on gh pr list --json number --jq '.[].number' to decide whether a staging environment belongs to an open pull request, but the command’s default --limit is 30. Repositories with more than 30 open PRs will omit older ones from this list, so their environments will be treated as closed and deleted even though the PRs remain open. Pass an explicit --limit (or paginate) so every open PR is considered before deleting environments.

Useful? React with πŸ‘Β / πŸ‘Ž.

@copilot Implement this as you see fit


# Track cleanup actions
CLEANED=0
KEPT=0

# Process each staging environment
echo ""
echo "Processing environments..."
echo "$ENVIRONMENTS" | jq -c '.[]' | while read -r env; do
ENV_NAME=$(echo "$env" | jq -r '.name')
ENV_KIND=$(echo "$env" | jq -r '.kind')

# Skip production environment
if [ "$ENV_KIND" = "Production" ] || [ "$ENV_NAME" = "default" ]; then
echo "ℹ️ Keeping production environment: $ENV_NAME"
KEPT=$((KEPT + 1))
continue
fi

# Extract PR number from environment name (usually format: pr-<number>)
PR_NUM=$(echo "$ENV_NAME" | grep -oP 'pr-\K\d+' || echo "")

if [ -z "$PR_NUM" ]; then
echo "⚠️ Cannot determine PR number for environment: $ENV_NAME"
KEPT=$((KEPT + 1))
continue
fi

# Check if PR is still open
if echo "$OPEN_PRS" | grep -q "\b$PR_NUM\b"; then
echo "ℹ️ Keeping staging for open PR #$PR_NUM: $ENV_NAME"
KEPT=$((KEPT + 1))
else
if [ "$DRY_RUN" = "true" ]; then
echo "πŸ” [DRY RUN] Would delete staging environment: $ENV_NAME (closed PR #$PR_NUM)"
else
echo "πŸ—‘οΈ Deleting staging environment: $ENV_NAME (closed PR #$PR_NUM)"
az staticwebapp environment delete \
--name "$SWA_NAME" \
--resource-group "$RESOURCE_GROUP" \
--environment-name "$ENV_NAME" \
--yes \
|| echo "⚠️ Failed to delete $ENV_NAME"
fi
CLEANED=$((CLEANED + 1))
fi
done
Comment on lines +84 to +123

Copilot AI Oct 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLEANED and KEPT are incremented inside a while loop that's in a subshell due to the upstream pipe. Their final values will remain 0 outside the loop, producing an inaccurate summary. Fix by avoiding the pipeline, e.g., use process substitution: while read -r env; do ... done < <(jq -c '.[]' <<< "$ENVIRONMENTS").

Copilot uses AI. Check for mistakes.

echo ""
echo "## 🧹 Cleanup Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [ "$DRY_RUN" = "true" ]; then
echo "**Mode**: Dry Run (no actual deletions)" >> $GITHUB_STEP_SUMMARY
else
echo "**Mode**: Live Cleanup" >> $GITHUB_STEP_SUMMARY
fi

echo "- **Environments kept**: $KEPT" >> $GITHUB_STEP_SUMMARY
echo "- **Environments cleaned**: $CLEANED" >> $GITHUB_STEP_SUMMARY

if [ $CLEANED -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "βœ… Successfully cleaned up $CLEANED staging environment(s)" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "ℹ️ No staging environments needed cleanup" >> $GITHUB_STEP_SUMMARY
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Notify on failure
if: failure()
run: |
echo "## ❌ Staging Cleanup Failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Please check the workflow logs for details." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Common issues:" >> $GITHUB_STEP_SUMMARY
echo "- Missing or invalid AZURE_CREDENTIALS secret" >> $GITHUB_STEP_SUMMARY
echo "- Incorrect AZURE_RESOURCE_GROUP configuration" >> $GITHUB_STEP_SUMMARY
echo "- Insufficient permissions in Azure" >> $GITHUB_STEP_SUMMARY
Loading
Loading