diff --git a/.github/workflows/azure-static-web-apps-thankful-mushroom-08ecc5d1e.yml b/.github/workflows/azure-static-web-apps-thankful-mushroom-08ecc5d1e.yml index 74492bb..7f85538 100644 --- a/.github/workflows/azure-static-web-apps-thankful-mushroom-08ecc5d1e.yml +++ b/.github/workflows/azure-static-web-apps-thankful-mushroom-08ecc5d1e.yml @@ -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' + + - 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 @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d360296..ebeac49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/cleanup-staging.yml b/.github/workflows/cleanup-staging.yml new file mode 100644 index 0000000..62d5e0f --- /dev/null +++ b/.github/workflows/cleanup-staging.yml @@ -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 + 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' }}" + + 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" + + # 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-) + 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 + + 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 diff --git a/CICD_MAINTENANCE.md b/CICD_MAINTENANCE.md new file mode 100644 index 0000000..754b014 --- /dev/null +++ b/CICD_MAINTENANCE.md @@ -0,0 +1,407 @@ +# CI/CD Pipeline Maintenance Guide + +This document provides guidelines and best practices for maintaining the Bleedy CI/CD pipeline. + +## Table of Contents + +- [Overview](#overview) +- [Workflows](#workflows) +- [Lock File Management](#lock-file-management) +- [Build Optimization](#build-optimization) +- [Staging Environment Management](#staging-environment-management) +- [Troubleshooting](#troubleshooting) +- [Best Practices](#best-practices) + +## Overview + +The Bleedy project uses GitHub Actions for continuous integration and deployment. The pipeline includes: + +1. **CI Checks** - Validates code quality, formatting, types, and builds +2. **Azure Static Web Apps Deployment** - Deploys to production and creates PR previews +3. **Staging Cleanup** - Removes unused staging environments +4. **Post-Merge Cleanup** - Cleans up consolidated PRs and branches + +## Workflows + +### CI Checks (`ci.yml`) + +Runs on every push to master and on pull requests. Performs: + +- Lock file validation +- Dependency installation +- Code formatting checks +- Linting +- Type checking +- Production build + +**Location**: `.github/workflows/ci.yml` + +### Azure Static Web Apps Deployment (`azure-static-web-apps-*.yml`) + +Handles deployment to Azure Static Web Apps: + +- **On Push to Master**: Deploys to production +- **On PR Open/Update**: Creates staging environment with preview URL +- **On PR Close**: Removes staging environment + +**Features**: + +- Lock file validation before build +- Retry logic for npm ci (up to 3 attempts) +- Deployment summary in GitHub Actions +- Node.js caching for faster builds + +**Location**: `.github/workflows/azure-static-web-apps-thankful-mushroom-08ecc5d1e.yml` + +### Staging Environment Cleanup (`cleanup-staging.yml`) + +Automated cleanup of unused staging environments: + +- **Schedule**: Runs weekly on Sundays at 2 AM UTC +- **Manual Trigger**: Can be run manually with dry-run mode +- **Logic**: Removes staging environments for closed PRs + +**Configuration Required**: + +- `AZURE_CREDENTIALS` secret (service principal) +- `AZURE_RESOURCE_GROUP` secret (optional, defaults to 'bleedy-rg') + +**Location**: `.github/workflows/cleanup-staging.yml` + +### Post-Merge Cleanup (`post-merge-cleanup.yml`) + +Cleans up consolidated PRs and their branches after merge. + +**Location**: `.github/workflows/post-merge-cleanup.yml` + +## Lock File Management + +### Why Lock File Validation Matters + +The `package-lock.json` file ensures deterministic dependency installation. When it's out of sync with `package.json`, `npm ci` fails, breaking the build pipeline. + +### Validation Script + +A validation script runs before every build: + +**Location**: `scripts/validate-lockfile.sh` + +**What it checks**: + +- Lock file exists +- Lock file is in sync with package.json +- npm ci can run successfully + +### Common Issues and Fixes + +#### Issue: Lock file out of sync + +**Symptoms**: + +``` +npm ci fails with "package-lock.json" error +Validation script reports "Lock file is NOT in sync" +``` + +**Fix**: + +```bash +# Remove the lock file +rm package-lock.json + +# Regenerate it +npm install + +# Commit the new lock file +git add package-lock.json +git commit -m "chore: regenerate package-lock.json" +``` + +#### Issue: Different npm versions + +**Symptoms**: + +``` +Lock file format changes between developers +Frequent lock file conflicts +``` + +**Fix**: + +- Ensure all developers use npm 11.0.0+ (specified in package.json) +- Use `corepack enable` to enforce package manager version +- Never manually edit `package-lock.json` + +### Best Practices + +1. **Never manually edit package-lock.json** - Always use npm commands +2. **Use `npm install`** to add/update dependencies (updates lock file) +3. **Use `npm ci`** in CI environments (enforces lock file) +4. **Commit lock file changes** with dependency updates +5. **Use consistent npm versions** across the team + +## Build Optimization + +### Chunk Splitting Configuration + +The build is optimized using Vite's `manualChunks` feature: + +```typescript +build: { + rollupOptions: { + output: { + manualChunks: { + 'element-plus': ['element-plus', '@element-plus/icons-vue'], + 'vue-vendor': ['vue', 'vue-router', 'pinia'], + 'utilities': ['file-saver', 'jszip'] + } + } + }, + chunkSizeWarningLimit: 1500 +} +``` + +**Benefits**: + +- Better caching (vendor code changes less frequently) +- Faster initial loads +- Improved parallel loading +- Smaller individual chunks + +**Chunks**: + +- `element-plus.js` (~880KB) - UI framework +- `vue-vendor.js` (~190KB) - Vue core and state management +- `utilities.js` (~100KB) - File handling utilities +- `index.js` (~150KB) - Application code + +### Dynamic vs Static Imports + +**Use static imports** for components used in the template: + +```typescript +// โœ… Correct +import MyComponent from './MyComponent.vue' + +export default { + components: {MyComponent} +} +``` + +**Avoid dynamic imports** when component is used in template: + +```typescript +// โŒ Incorrect - causes build warnings +export default { + components: { + MyComponent: () => import('./MyComponent.vue') + } +} +``` + +### Monitoring Build Performance + +Watch for these warnings during build: + +1. **Dynamic/static import conflicts** - Indicates incorrect import usage +2. **Large chunk warnings** - May need additional chunk splitting +3. **Slow transformation** - Consider optimizing dependencies + +## Staging Environment Management + +### Automatic Cleanup + +The staging cleanup workflow runs weekly and: + +1. Lists all staging environments in Azure +2. Checks which PRs are still open +3. Removes staging environments for closed PRs +4. Keeps production and active PR environments + +### Manual Cleanup + +To manually trigger cleanup: + +1. Go to Actions โ†’ "Cleanup Staging Environments" +2. Click "Run workflow" +3. Select dry run mode to preview (optional) +4. Click "Run workflow" + +### Dry Run Mode + +Test the cleanup without deleting anything: + +```yaml +dry_run: 'true' +``` + +This shows what would be deleted in the workflow summary. + +### Required Secrets + +Set these in repository settings: + +``` +AZURE_CREDENTIALS # Service principal JSON +AZURE_RESOURCE_GROUP # Resource group name (optional) +``` + +To create service principal: + +```bash +az ad sp create-for-rbac \ + --name "bleedy-staging-cleanup" \ + --role contributor \ + --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \ + --sdk-auth +``` + +## Troubleshooting + +### npm ci Failures + +**Symptoms**: Build fails at dependency installation + +**Check**: + +1. Is lock file in sync? Run `scripts/validate-lockfile.sh` +2. Are there network issues? Check npm registry status +3. Is cache corrupted? Retry the workflow + +**The workflow includes**: + +- Automatic retry (up to 3 attempts) +- 5-second delay between retries +- Detailed error messages + +### Azure Deployment Failures + +**Symptoms**: Build succeeds but deployment fails + +**Common causes**: + +1. Invalid Azure token +2. Too many staging environments (cleanup needed) +3. Build output location mismatch + +**Solutions**: + +1. Verify `AZURE_STATIC_WEB_APPS_API_TOKEN` secret +2. Run staging cleanup workflow +3. Ensure `output_location: 'dist'` matches build output + +### Build Performance Issues + +**Symptoms**: Slow builds, timeouts + +**Solutions**: + +1. Check if dependencies need updates +2. Review chunk splitting configuration +3. Consider increasing workflow timeout +4. Use Node.js caching (already enabled) + +### Lock File Conflicts + +**Symptoms**: Merge conflicts in package-lock.json + +**Resolution**: + +```bash +# Accept either version +git checkout --theirs package-lock.json +# OR +git checkout --ours package-lock.json + +# Then regenerate +npm install + +# Commit the result +git add package-lock.json +git commit -m "chore: resolve lock file conflict" +``` + +## Best Practices + +### Dependency Management + +1. **Update dependencies regularly** - Use Dependabot +2. **Test updates locally first** - Don't merge untested updates +3. **Keep npm version consistent** - Use packageManager field +4. **Review security alerts** - Address vulnerabilities promptly +5. **Avoid manual package.json edits** - Use npm commands + +### Workflow Maintenance + +1. **Keep actions up to date** - Update action versions regularly +2. **Monitor workflow runs** - Check for degraded performance +3. **Review staging environments** - Ensure cleanup is working +4. **Test manual workflows** - Verify they work as expected +5. **Document changes** - Update this guide when making changes + +### Build Optimization + +1. **Monitor chunk sizes** - Keep under warning limit (1500KB) +2. **Profile build times** - Identify slow steps +3. **Use caching effectively** - Node modules, build outputs +4. **Minimize dependencies** - Only add what's necessary +5. **Review build output** - Check for unexpected files + +### Security + +1. **Rotate secrets regularly** - Azure tokens, service principals +2. **Use least privilege** - Grant minimal required permissions +3. **Review workflow permissions** - Limit GitHub token scope +4. **Audit dependencies** - Run `npm audit` regularly +5. **Keep dependencies updated** - Patch security vulnerabilities + +### Code Quality + +1. **Run checks locally** - Before pushing to CI +2. **Fix linting errors** - Don't disable rules without reason +3. **Address type errors** - Maintain type safety +4. **Format code consistently** - Use Prettier +5. **Write meaningful commits** - Help reviewers understand changes + +## Maintenance Checklist + +### Weekly + +- [ ] Review failed workflow runs +- [ ] Check staging environment count +- [ ] Review dependency security alerts + +### Monthly + +- [ ] Update GitHub Actions versions +- [ ] Review and update dependencies +- [ ] Check Azure resource usage +- [ ] Review build performance metrics + +### Quarterly + +- [ ] Audit Azure permissions +- [ ] Rotate service principal secrets +- [ ] Review and update documentation +- [ ] Evaluate new CI/CD features + +## Additional Resources + +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Azure Static Web Apps Docs](https://learn.microsoft.com/en-us/azure/static-web-apps/) +- [Vite Build Optimization](https://vitejs.dev/guide/build.html) +- [npm ci Documentation](https://docs.npmjs.com/cli/v10/commands/npm-ci) +- [Lock File Specification](https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json) + +## Getting Help + +If you encounter issues not covered in this guide: + +1. Check workflow logs in GitHub Actions +2. Review recent changes that might have caused the issue +3. Search existing GitHub issues +4. Create a new issue with: + - Description of the problem + - Steps to reproduce + - Relevant logs and error messages + - What you've tried to fix it diff --git a/package-lock.json b/package-lock.json index a30bbe1..54f8132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "eslint": "^9.17.0", "eslint-plugin-format": "^1.0.1", "eslint-plugin-vue": "^9.32.0", + "husky": "^9.1.7", "jsdom": "^25.0.1", "postcss": "^8.4.49", "prettier": "^3.4.2", @@ -5519,6 +5520,22 @@ "node": ">=18.18.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/scripts/validate-lockfile.sh b/scripts/validate-lockfile.sh new file mode 100755 index 0000000..b63b0d0 --- /dev/null +++ b/scripts/validate-lockfile.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Script to validate that package-lock.json is in sync with package.json +# This helps prevent npm ci failures due to lock file being out of sync + +set -e + +echo "๐Ÿ” Validating package-lock.json sync with package.json..." + +# Check if package-lock.json exists +if [ ! -f "package-lock.json" ]; then + echo "โŒ Error: package-lock.json not found!" + echo "Run 'npm install' to generate it." + exit 1 +fi + +# Check if package.json exists +if [ ! -f "package.json" ]; then + echo "โŒ Error: package.json not found!" + exit 1 +fi + +# Create a temporary directory for testing +TEMP_DIR=$(mktemp -d) +trap "rm -rf $TEMP_DIR" EXIT + +# Copy package files to temp directory +cp package.json package-lock.json "$TEMP_DIR/" +cd "$TEMP_DIR" + +echo "๐Ÿ“ฆ Testing npm ci in isolated environment..." + +# Try npm ci - if it fails, the lock file is out of sync +if npm ci --dry-run > /dev/null 2>&1; then + echo "โœ… Lock file is in sync with package.json" + exit 0 +else + echo "โŒ Lock file is NOT in sync with package.json!" + echo "" + echo "This usually happens when:" + echo " 1. package.json was manually edited without running 'npm install'" + echo " 2. Different npm versions were used to update dependencies" + echo " 3. Lock file was manually edited (should never be done)" + echo "" + echo "To fix this issue:" + echo " 1. Run: rm package-lock.json" + echo " 2. Run: npm install" + echo " 3. Commit the updated package-lock.json" + echo "" + echo "โš ๏ธ IMPORTANT: Never manually edit package-lock.json!" + exit 1 +fi diff --git a/src/components/ImageSelection.vue b/src/components/ImageSelection.vue index 3971c0f..e24ca27 100644 --- a/src/components/ImageSelection.vue +++ b/src/components/ImageSelection.vue @@ -58,11 +58,13 @@