diff --git a/.github/chatmodes/Azure_function_codegen_and_deployment.chatmode.md b/.github/chatmodes/Azure_function_codegen_and_deployment.chatmode.md deleted file mode 100644 index fc8ebb8..0000000 --- a/.github/chatmodes/Azure_function_codegen_and_deployment.chatmode.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -description: Generate and deploy Azure Functions with comprehensive planning, code generation, and deployment automation. -tools: ["changes","edit","extensions","fetch","findTestFiles","githubRepo","new","openSimpleBrowser","problems","runCommands","runNotebooks","runTasks","search","testFailure","todos","usages","vscodeAPI","Microsoft Docs","azureterraformbestpractices","bicepschema","deploy","quota","get_bestpractices","azure_query_azure_resource_graph","azure_generate_azure_cli_command","azure_get_auth_state","azure_get_current_tenant","azure_get_available_tenants","azure_set_current_tenant","azure_get_selected_subscriptions","azure_open_subscription_picker","azure_sign_out_azure_user","azure_diagnose_resource","azure_list_activity_logs"] -model: Claude Sonnet 4 ---- - -# Azure Functions Code Generation and Deployment - -Enterprise-grade Azure Functions development workflow with automated planning, code generation, testing, and deployment using Azure best practices and Infrastructure as Code (IaC). - -## Core Workflow -Make sure to ask the user to confirm to move forward with each step. - -### 1. Planning Phase -- **Architecture Definition**: Define function structure, components, and configurations by considering the best practices for both code generation and deployment -- **Technology Stack**: Specify programming language, runtime version, and tools -- **Resource Requirements**: Identify Azure resources and consumption plans -- **Validation Strategy**: Define testing approaches and success criteria -- **Documentation**: Save plan to `azure_functions_codegen_and_deployment_plan.md` - -### 2. Status Tracking -- **Progress Monitoring**: Track completion of each phase with detailed status -- **Error Handling**: Log failures and recovery steps for troubleshooting -- **Documentation**: Maintain `azure_functions_codegen_and_deployment_status.md` - -### 3. Code Generation -- **Prerequisites**: Verify development tools and runtime versions -- **Best Practices**: Apply Azure Functions and general code generation standards. Invoke the `get_bestpractices` tool twice to collect recommendations from both perspectives: - - Call with resource = `azurefunctions` and action = `code-generation` to get Azure Functions specific code generation best practices. - - Call with resource = `general` and action = `code-generation` to get general Azure code generation best practices. - Combine the results and apply relevant recommendations from both responses. -- **Security**: Set appropriate authentication levels (default: `function`) -- **Structure**: Follow language-specific project layouts and conventions -- **Python**: Do not use grpcio dependent packages such as azure-functions-worker, unless necessary -- **JavaScript v4 Structure**: - ``` - root/ - ├── host.json # Function host configuration - ├── local.settings.json # Development settings - ├── package.json # Dependencies - ├── src/ - │ ├── app.js # Main application entry - │ └── [modules].js # Business logic - └── tests/ # Test suite - ``` - -### 4. Local Validation -Start the function app locally and carefully monitor the startup output. Look for any errors, warnings, or unusual messages. -Don't proceed to testing until you've confirmed a clean startup. If you see any issues, investigate and fix them before continuing. -- **Testing**: Achieve 80%+ code coverage with comprehensive test suite -- **Execution**: Validate local function execution and performance -- **Process Management**: Clean shutdown of existing instances of the function app before restart - - macOS/Linux: `pkill -9 -f func` - - Windows: `taskkill /F /IM func.exe /T` -#### Post-Testing Cleanup Protocol -Upon finishing testing, ensure all processes are properly shut down to prevent resource conflicts and port binding issues: - -### 5. Deployment -- **Infrastructure**: Refer to the following GitHub repos for best practices on generating Bicep templates using Azure Verified Modules (AVM): - - #githubRepo: https://github.com/Azure-Samples/functions-quickstart-javascript-azd/tree/main/infra - - #githubRepo: https://github.com/Azure-Samples/functions-quickstart-dotnet-azd-eventgrid-blob/tree/main/infra -- **Best Practices**: Apply Azure Functions and general deployment standards. Invoke the `get_bestpractices` tool twice to collect recommendations from both perspectives: - - Call with resource = `azurefunctions` and action = `deployment` to get Azure Functions specific deployment best practices. - - Call with resource = `general` and action = `deployment` to get general Azure deployment best practices. - Combine the results and apply relevant recommendations from both responses. -- **Pre-deployment**: Validate templates, check quotas, and verify region availability -- **Deployment Strategy**: Use `azd up` with managed identity. - - ALWAYS Use Flex Consumption plan (FC1) for deployment, never Y1 dynamic. - - ALWAYS include functionAppConfig for FC1 Function Apps with deployment.storage configuration. Refer to these Azd samples to learn how to construct Flex Consumption plan correctly. - - #githubRepo: https://github.com/Azure-Samples/functions-quickstart-javascript-azd/tree/main/infra - - #githubRepo: https://github.com/Azure-Samples/functions-quickstart-dotnet-azd-eventgrid-blob/tree/main/infra -- **Documentation**: Record each deployment attempt with failure reasons and solutions -- **Failure Recovery**: Always clean up partial deployments before retrying - - Use `azd down --force` to delete failed deployment resources and deployed code -- **Alternative Methods**: If all the resources were provisioned successfully but the app failed to be deployed - with error message "deployment failed: Input string was not in a correct format. Failure to parse near offset 40. - Format item ends prematurely.", use Azure CLI deployment to upload the function app code. - - -### 6. Post-Deployment -- **Authentication**: Retrieve function names being deployed, then retrieve and configure function keys -- **Endpoint Testing**: Validate all function endpoints with proper authentication -- **Monitoring**: Verify Application Insights telemetry and establish performance baselines -- **Documentation**: Create a README with deployment and usage instructions - -## Enterprise Environment Considerations - -### Corporate Policy Compliance -- **Alternative Strategies**: Prepare Azure CLI fallback for blocked `azd` commands -- **Compliance Standards**: Use Azure Verified Modules (AVM) for enterprise requirements -- **Network Restrictions**: Consider VNet integration and private endpoints - -### Security & Authentication -- **Managed Identity**: Preferred authentication method for Azure-hosted resources -- **Function Keys**: Use function-level keys following principle of least privilege -- **Key Management**: Retrieve keys post-deployment for endpoint testing -- **RBAC Configuration**: Implement proper role assignments for dependencies - -## Quality Assurance - -### Testing Requirements -- **Unit Tests**: 100% passing rate -- **Integration Tests**: 80%+ coverage of main scenarios -- **Code Quality**: ESLint/linting checks passing -- **Performance**: Baseline performance validation - -### Deployment Validation -- **Infrastructure**: Bicep templates pass validation -- **Pre-deployment**: Use deploy tool and set parameter `command` to be `deploy_iac_rules_get` to get the best practices rules for iac generation. -- **Authentication**: Proper managed identity and RBAC configuration -- **Monitoring**: Application Insights receiving telemetry - -## Failure Recovery & Troubleshooting - -### Common Issues & Solutions -1. **Policy Violations**: Switch to Azure CLI deployment methods -2. **Missing Dependencies**: Systematic tool installation and validation -3. **Authentication Issues**: Comprehensive RBAC and managed identity setup -4. **Runtime Compatibility**: Use supported versions (Node.js 20+, Python 3.11+) -5. **Partial Deployments**: Clean resource group deletion before retry - -### Deployment Failure Recovery Protocol -```bash -# Delete failed deployment resources and deployed code -azd down --force - -# Or -# Clean failed deployment -az group delete --name rg- --yes --no-wait -az group wait --name rg- --deleted --timeout 300 - -# Retry deployment -azd up -``` - -## Reference Resources - -### Azure Functions Best Practices -- **Programming Models**: Use latest versions (v4 JavaScript, v2 Python) -- **Extension Bundles**: Prefer over SDKs for simplified dependency management -- **Event Sources**: Use EventGrid for blob triggers -- **Configuration**: Generate `local.settings.json` for local development - -### Infrastructure Templates -- [JavaScript Azure Functions AZD Sample](https://github.com/Azure-Samples/functions-quickstart-javascript-azd/tree/main/infra) -- [.NET Azure Functions with EventGrid Sample](https://github.com/Azure-Samples/functions-quickstart-dotnet-azd-eventgrid-blob/tree/main/infra) \ No newline at end of file diff --git a/.github/workflows/production-deployment.yml b/.github/workflows/production-deployment.yml deleted file mode 100644 index 06c135c..0000000 --- a/.github/workflows/production-deployment.yml +++ /dev/null @@ -1,249 +0,0 @@ -name: Production Deployment - -on: - push: - branches: [ main ] - workflow_dispatch: - inputs: - custom_domain: - description: 'Custom domain name' - required: false - default: 'miraclesinmotion.org' - force_deploy: - description: 'Force deployment even if tests fail' - required: false - default: 'false' - -env: - NODE_VERSION: '22' - AZURE_STATIC_WEB_APPS_API_TOKEN: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - -jobs: - build-and-test: - runs-on: ubuntu-latest - name: Build and Test - steps: - - uses: actions/checkout@v4 - with: - submodules: true - lfs: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install main dependencies - run: npm install --legacy-peer-deps - - - name: Install API dependencies - run: | - cd api - npm install - cd .. - - - name: Run linting - run: npm run lint - continue-on-error: true - - - name: Run tests - run: npx vitest run --reporter=verbose - continue-on-error: ${{ github.event.inputs.force_deploy == 'true' }} - - - name: Build application - run: npm run build - - - name: Build API - run: | - cd api - npm run build || npm run tsc - cd .. - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: build-files - path: | - dist/ - api/ - staticwebapp.config.json - - deploy-infrastructure: - runs-on: ubuntu-latest - needs: build-and-test - name: Deploy Infrastructure - outputs: - static-web-app-name: ${{ steps.deploy.outputs.staticWebAppName }} - function-app-name: ${{ steps.deploy.outputs.functionAppName }} - static-web-app-url: ${{ steps.deploy.outputs.staticWebAppUrl }} - steps: - - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v2 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Create Resource Group - run: | - az group create \ - --name rg-miraclesinmotion-prod \ - --location "East US" - - - name: Deploy Infrastructure - id: deploy - run: | - DEPLOYMENT_NAME="mim-prod-$(date +%Y%m%d-%H%M%S)" - - # Deploy infrastructure - DEPLOYMENT_OUTPUT=$(az deployment group create \ - --resource-group rg-miraclesinmotion-prod \ - --template-file infrastructure/main-production.bicep \ - --parameters infrastructure/main-production.parameters.json \ - --parameters stripePublicKey="${{ secrets.STRIPE_PUBLIC_KEY }}" \ - --parameters customDomainName="${{ github.event.inputs.custom_domain || 'miraclesinmotion.org' }}" \ - --parameters enableCustomDomain=true \ - --name $DEPLOYMENT_NAME \ - --output json) - - # Extract outputs - STATIC_WEB_APP_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.properties.outputs.staticWebAppName.value') - FUNCTION_APP_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.properties.outputs.functionAppName.value') - STATIC_WEB_APP_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.properties.outputs.staticWebAppUrl.value') - - # Set outputs - echo "staticWebAppName=$STATIC_WEB_APP_NAME" >> $GITHUB_OUTPUT - echo "functionAppName=$FUNCTION_APP_NAME" >> $GITHUB_OUTPUT - echo "staticWebAppUrl=$STATIC_WEB_APP_URL" >> $GITHUB_OUTPUT - - echo "✅ Infrastructure deployed successfully" - echo "📱 Static Web App: $STATIC_WEB_APP_NAME" - echo "⚡ Function App: $FUNCTION_APP_NAME" - echo "🌐 URL: $STATIC_WEB_APP_URL" - - deploy-application: - runs-on: ubuntu-latest - needs: deploy-infrastructure - name: Deploy Application - environment: - name: production - url: ${{ needs.deploy-infrastructure.outputs.static-web-app-url }} - steps: - - uses: actions/checkout@v4 - - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - name: build-files - - - name: Azure Login - uses: azure/login@v2 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Get Static Web App Deployment Token - id: swa-token - run: | - DEPLOYMENT_TOKEN=$(az staticwebapp secrets list \ - --name ${{ needs.deploy-infrastructure.outputs.static-web-app-name }} \ - --resource-group rg-miraclesinmotion-prod \ - --query "properties.apiKey" \ - --output tsv) - echo "::add-mask::$DEPLOYMENT_TOKEN" - echo "token=$DEPLOYMENT_TOKEN" >> $GITHUB_OUTPUT - - - name: Setup Node.js for SWA CLI - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Install SWA CLI - run: npm install -g @azure/static-web-apps-cli - - - name: Deploy to Static Web App - run: | - swa deploy ./dist \ - --api-location ./api \ - --env production \ - --deployment-token ${{ steps.swa-token.outputs.token }} - - - name: Deploy Azure Functions - run: | - # Create deployment package - cd api - zip -r ../api-deployment.zip . -x "node_modules/*" "*.test.*" "*.md" - cd .. - - # Deploy functions - az functionapp deployment source config-zip \ - --resource-group rg-miraclesinmotion-prod \ - --name ${{ needs.deploy-infrastructure.outputs.function-app-name }} \ - --src api-deployment.zip - - - name: Warm up application - run: | - echo "🔥 Warming up the deployed application..." - curl -s ${{ needs.deploy-infrastructure.outputs.static-web-app-url }} > /dev/null - curl -s ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/portals > /dev/null - echo "✅ Application warmed up successfully" - - post-deployment: - runs-on: ubuntu-latest - needs: [deploy-infrastructure, deploy-application] - name: Post-Deployment Tasks - steps: - - name: Run smoke tests - run: | - echo "🧪 Running smoke tests..." - - # Test main page - STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}) - if [ $STATUS -eq 200 ]; then - echo "✅ Main page is accessible" - else - echo "❌ Main page returned status: $STATUS" - exit 1 - fi - - # Test portals page - STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/portals) - if [ $STATUS -eq 200 ]; then - echo "✅ Portals page is accessible" - else - echo "❌ Portals page returned status: $STATUS" - exit 1 - fi - - echo "🎉 All smoke tests passed!" - - - name: Create deployment summary - run: | - echo "## 🚀 Production Deployment Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📊 Deployment Details" >> $GITHUB_STEP_SUMMARY - echo "- **Static Web App**: ${{ needs.deploy-infrastructure.outputs.static-web-app-name }}" >> $GITHUB_STEP_SUMMARY - echo "- **Primary URL**: ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}" >> $GITHUB_STEP_SUMMARY - echo "- **Portal Access**: ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/portals" >> $GITHUB_STEP_SUMMARY - echo "- **Custom Domain**: https://${{ github.event.inputs.custom_domain || 'miraclesinmotion.org' }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 🔗 Quick Links" >> $GITHUB_STEP_SUMMARY - echo "- [🏠 Main Site](${{ needs.deploy-infrastructure.outputs.static-web-app-url }})" >> $GITHUB_STEP_SUMMARY - echo "- [🚪 Portals](${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/portals)" >> $GITHUB_STEP_SUMMARY - echo "- [💰 Donate](${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/donate)" >> $GITHUB_STEP_SUMMARY - echo "- [🤝 Volunteer](${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/volunteers)" >> $GITHUB_STEP_SUMMARY - echo "- [📊 Analytics](${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/analytics)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📋 Next Steps" >> $GITHUB_STEP_SUMMARY - echo "1. Configure DNS records for custom domain" >> $GITHUB_STEP_SUMMARY - echo "2. Update Stripe webhook endpoints" >> $GITHUB_STEP_SUMMARY - echo "3. Test all portal functionality" >> $GITHUB_STEP_SUMMARY - echo "4. Monitor application performance" >> $GITHUB_STEP_SUMMARY - - - name: Notify team - if: success() - run: | - echo "🎉 Production deployment completed successfully!" - echo "🌐 Application is live at: ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}" - echo "🚪 Portals are accessible at: ${{ needs.deploy-infrastructure.outputs.static-web-app-url }}/#/portals" \ No newline at end of file diff --git a/.gitignore b/.gitignore index e775575..113147c 100644 --- a/.gitignore +++ b/.gitignore @@ -76,11 +76,6 @@ jspm_packages/ build/ dist/ -# Azure Functions zip layout: populate from `api/dist` via `npm run build` + copy (docs/deployment). Do not commit tsc emit here. -api/deploy-package/**/*.js -api/deploy-package/**/*.js.map -api/deploy-package/**/*.d.ts - # Temporary folders tmp/ temp/ diff --git a/README.md b/README.md index a1684c6..23651c8 100644 Binary files a/README.md and b/README.md differ diff --git a/api/deploy-package/host.json b/api/deploy-package/host.json deleted file mode 100644 index f5ba60f..0000000 --- a/api/deploy-package/host.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - }, - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[4.*, 5.0.0)" - }, - "functionTimeout": "00:05:00", - "languageWorkers": { - "node": { - "arguments": ["--max-old-space-size=2048"] - } - } -} \ No newline at end of file diff --git a/api/deploy-package/package.json b/api/deploy-package/package.json deleted file mode 100644 index 3bfd856..0000000 --- a/api/deploy-package/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "miracles-in-motion-api", - "version": "1.0.0", - "description": "Azure Functions API for Miracles in Motion nonprofit platform", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "prestart": "npm run build", - "start": "func start", - "test": "jest" - }, - "dependencies": { - "@azure/cosmos": "^4.1.1", - "@azure/keyvault-secrets": "^4.8.1", - "@azure/identity": "^4.5.0", - "@azure/functions": "^4.5.1", - "stripe": "^17.3.0", - "joi": "^17.13.3", - "uuid": "^11.0.3", - "cors": "^2.8.5" - }, - "devDependencies": { - "@types/node": "^22.10.1", - "@types/uuid": "^10.0.0", - "@types/cors": "^2.8.17", - "typescript": "^5.6.3", - "jest": "^29.7.0", - "@types/jest": "^29.5.14" - }, - "engines": { - "node": ">=22.0.0" - } -} \ No newline at end of file diff --git a/api/host.json b/api/host.json deleted file mode 100644 index f5ba60f..0000000 --- a/api/host.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - }, - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[4.*, 5.0.0)" - }, - "functionTimeout": "00:05:00", - "languageWorkers": { - "node": { - "arguments": ["--max-old-space-size=2048"] - } - } -} \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json deleted file mode 100644 index a9fa71c..0000000 --- a/api/package-lock.json +++ /dev/null @@ -1,4762 +0,0 @@ -{ - "name": "miracles-in-motion-api", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "miracles-in-motion-api", - "version": "1.0.0", - "dependencies": { - "@azure/cosmos": "^4.1.1", - "@azure/functions": "^4.5.1", - "@azure/identity": "^4.5.0", - "@azure/keyvault-secrets": "^4.8.1", - "cors": "^2.8.5", - "joi": "^17.13.3", - "stripe": "^17.3.0", - "uuid": "^11.0.3" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/jest": "^29.5.14", - "@types/node": "^22.10.1", - "@types/uuid": "^10.0.0", - "jest": "^29.7.0", - "typescript": "^5.6.3" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@azure-rest/core-client": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.5.1.tgz", - "integrity": "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0", - "@azure/core-tracing": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-auth": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", - "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-util": "^1.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-client": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", - "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-http-compat": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.1.tgz", - "integrity": "sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-client": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-lro": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", - "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-util": "^1.2.0", - "@azure/logger": "^1.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-paging": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", - "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz", - "integrity": "sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-tracing": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", - "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-util": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", - "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/cosmos": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.5.1.tgz", - "integrity": "sha512-fbuXnfsjkVKNKG/xtIM+rQSU9AiWB3Qah8L6cHFFUX7t0P6jXYWSwI3FO/NxadHtkISb/WiBCQ7PJsUveM0XMg==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-rest-pipeline": "^1.19.1", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/keyvault-keys": "^4.9.0", - "@azure/logger": "^1.1.4", - "fast-json-stable-stringify": "^2.1.0", - "priorityqueuejs": "^2.0.0", - "semaphore": "^1.1.0", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/functions": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.8.0.tgz", - "integrity": "sha512-LNtl3xZNE40vE7+SIST+GYQX5cnnI1M65fXPi26l9XCdPakuQrz54lHv+qQQt1GG5JbqLfQk75iM7A6Y9O+2dQ==", - "license": "MIT", - "dependencies": { - "cookie": "^0.7.0", - "long": "^4.0.0", - "undici": "^5.29.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@azure/identity": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.12.0.tgz", - "integrity": "sha512-6vuh2R3Cte6SD6azNalLCjIDoryGdcvDVEV7IDRPtm5lHX5ffkDlIalaoOp5YJU08e4ipjJENel20kSMDLAcug==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.2", - "@azure/core-rest-pipeline": "^1.17.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^4.2.0", - "@azure/msal-node": "^3.5.0", - "open": "^10.1.0", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/keyvault-common": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz", - "integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.5.0", - "@azure/core-rest-pipeline": "^1.8.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.10.0", - "@azure/logger": "^1.1.4", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/keyvault-keys": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.0.tgz", - "integrity": "sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==", - "license": "MIT", - "dependencies": { - "@azure-rest/core-client": "^2.3.3", - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-http-compat": "^2.2.0", - "@azure/core-lro": "^2.7.2", - "@azure/core-paging": "^1.6.2", - "@azure/core-rest-pipeline": "^1.19.0", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/keyvault-common": "^2.0.0", - "@azure/logger": "^1.1.4", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/keyvault-secrets": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.10.0.tgz", - "integrity": "sha512-WvXc3h2hqHL1pMzUU7ANE2RBKoxjK3JQc0YNn6GUFvOWQtf2ZR+sH4/5cZu8zAg62v9qLCduBN7065nHKl+AOA==", - "license": "MIT", - "dependencies": { - "@azure-rest/core-client": "^2.3.3", - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-http-compat": "^2.2.0", - "@azure/core-lro": "^2.7.2", - "@azure/core-paging": "^1.6.2", - "@azure/core-rest-pipeline": "^1.19.0", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/keyvault-common": "^2.0.0", - "@azure/logger": "^1.1.4", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", - "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", - "license": "MIT", - "dependencies": { - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/msal-browser": { - "version": "4.24.1", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.24.1.tgz", - "integrity": "sha512-e4sp8ihJIyZQvN0ZM1MMuKlEiiLWUS9V9+kxsVAc6K8MtpXHui8VINmKUxXH0OOksLhFDpdq4sGW1w6uYp431A==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "15.13.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-common": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.13.0.tgz", - "integrity": "sha512-8oF6nj02qX7eE/6+wFT5NluXRHc05AgdCC3fJnkjiJooq8u7BcLmxaYYSwc2AfEkWRMRi6Eyvvbeqk4U4412Ag==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.0.tgz", - "integrity": "sha512-23BXm82Mp5XnRhrcd4mrHa0xuUNRp96ivu3nRatrfdAqjoeWAGyD0eEAafxAOHAEWWmdlyFK4ELFcdziXyw2sA==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "15.13.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@azure/msal-node/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "22.18.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz", - "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typespec/ts-http-runtime": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz", - "integrity": "sha512-SnbaqayTVFEA6/tYumdF0UmybY0KHyKwGPBXnyckFlrrKdhWFrL3a2HIPXHjht5ZOElKGcXfD2D63P36btb+ww==", - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz", - "integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001748", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.230", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz", - "integrity": "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/priorityqueuejs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/priorityqueuejs/-/priorityqueuejs-2.0.0.tgz", - "integrity": "sha512-19BMarhgpq3x4ccvVi8k2QpJZcymo/iFUcrhPd4V96kYGovOdTsWwy7fxChYi4QY+m2EnGBWSX9Buakz+tWNQQ==", - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semaphore": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", - "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stripe": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.7.0.tgz", - "integrity": "sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==", - "license": "MIT", - "dependencies": { - "@types/node": ">=8.1.0", - "qs": "^6.11.0" - }, - "engines": { - "node": ">=12.*" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/api/package.json b/api/package.json deleted file mode 100644 index 3bfd856..0000000 --- a/api/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "miracles-in-motion-api", - "version": "1.0.0", - "description": "Azure Functions API for Miracles in Motion nonprofit platform", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "prestart": "npm run build", - "start": "func start", - "test": "jest" - }, - "dependencies": { - "@azure/cosmos": "^4.1.1", - "@azure/keyvault-secrets": "^4.8.1", - "@azure/identity": "^4.5.0", - "@azure/functions": "^4.5.1", - "stripe": "^17.3.0", - "joi": "^17.13.3", - "uuid": "^11.0.3", - "cors": "^2.8.5" - }, - "devDependencies": { - "@types/node": "^22.10.1", - "@types/uuid": "^10.0.0", - "@types/cors": "^2.8.17", - "typescript": "^5.6.3", - "jest": "^29.7.0", - "@types/jest": "^29.5.14" - }, - "engines": { - "node": ">=22.0.0" - } -} \ No newline at end of file diff --git a/api/src/DIContainer.ts b/api/src/DIContainer.ts deleted file mode 100644 index 21eabbf..0000000 --- a/api/src/DIContainer.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { promises as fs } from 'node:fs'; -import path from 'node:path'; -import { CosmosClient } from '@azure/cosmos'; -import { SecretClient } from '@azure/keyvault-secrets'; -import { DefaultAzureCredential } from '@azure/identity'; -import type { Donation } from './types'; - -export interface ServiceContainer { - donationsContainer: DonationContainerLike; - volunteersContainer: DonationContainerLike; - programsContainer: DonationContainerLike; - secretClient: SecretClientLike; -} - -type QueryParameter = { name: string; value: string }; - -type DonationQuerySpec = { - query: string; - parameters?: QueryParameter[]; -}; - -type DonationContainerLike = { - items: { - create(item: Donation): Promise; - query(spec: DonationQuerySpec): { - fetchAll(): Promise<{ resources: Donation[] }>; - }; - }; -}; - -type SecretClientLike = { - getSecret(name: string): Promise<{ value?: string }>; -}; - -class EnvSecretClient implements SecretClientLike { - public async getSecret(name: string): Promise<{ value?: string }> { - const envName = name.toUpperCase().replace(/-/g, '_'); - return { value: process.env[envName] }; - } -} - -class FileBackedContainer implements DonationContainerLike { - private readonly filePath: string; - - public constructor(collectionName: string) { - const dataDir = process.env.MIM_DATA_DIR || path.resolve(process.cwd(), 'data'); - this.filePath = path.join(dataDir, `${collectionName}.json`); - } - - public items = { - create: async (item: Donation): Promise => { - const items = await this.readAll(); - items.push(item); - await this.writeAll(items); - }, - query: (spec: DonationQuerySpec) => ({ - fetchAll: async (): Promise<{ resources: Donation[] }> => { - let items = await this.readAll(); - const status = spec.parameters?.find((parameter) => parameter.name === '@status')?.value; - const program = spec.parameters?.find((parameter) => parameter.name === '@program')?.value; - - if (status) { - items = items.filter((item) => item.status === status); - } - - if (program) { - items = items.filter((item) => item.program === program); - } - - items.sort((left, right) => right.createdAt.localeCompare(left.createdAt)); - return { resources: items }; - } - }) - }; - - private async ensureFile(): Promise { - await fs.mkdir(path.dirname(this.filePath), { recursive: true }); - - try { - await fs.access(this.filePath); - } catch { - await fs.writeFile(this.filePath, '[]\n', 'utf8'); - } - } - - private async readAll(): Promise { - await this.ensureFile(); - const raw = await fs.readFile(this.filePath, 'utf8'); - - if (!raw.trim()) { - return []; - } - - return JSON.parse(raw) as Donation[]; - } - - private async writeAll(items: Donation[]): Promise { - await this.ensureFile(); - const tempPath = `${this.filePath}.tmp`; - await fs.writeFile(tempPath, `${JSON.stringify(items, null, 2)}\n`, 'utf8'); - await fs.rename(tempPath, this.filePath); - } -} - -class DIContainer { - private static instance: DIContainer; - private services: ServiceContainer | null = null; - - private constructor() {} - - public static getInstance(): DIContainer { - if (!DIContainer.instance) { - DIContainer.instance = new DIContainer(); - } - return DIContainer.instance; - } - - public async initializeServices(): Promise { - if (this.services) { - return this.services; - } - - try { - const cosmosConnectionString = process.env.COSMOS_CONNECTION_STRING; - const keyVaultUrl = process.env.KEY_VAULT_URL; - - let donationsContainer: DonationContainerLike; - let volunteersContainer: DonationContainerLike; - let programsContainer: DonationContainerLike; - - if (cosmosConnectionString) { - const cosmosClient = new CosmosClient(cosmosConnectionString); - const databaseName = process.env.COSMOS_DATABASE_NAME || 'MiraclesInMotion'; - const database = cosmosClient.database(databaseName); - - donationsContainer = database.container('donations') as unknown as DonationContainerLike; - volunteersContainer = database.container('volunteers') as unknown as DonationContainerLike; - programsContainer = database.container('programs') as unknown as DonationContainerLike; - } else { - donationsContainer = new FileBackedContainer('donations'); - volunteersContainer = new FileBackedContainer('volunteers'); - programsContainer = new FileBackedContainer('programs'); - } - - const secretClient = keyVaultUrl - ? new SecretClient(keyVaultUrl, new DefaultAzureCredential()) - : new EnvSecretClient(); - - this.services = { - donationsContainer, - volunteersContainer, - programsContainer, - secretClient - }; - - console.log( - `Services initialized successfully (${cosmosConnectionString ? 'cosmos' : 'local-file'} storage, ${keyVaultUrl ? 'key-vault' : 'env'} secrets)` - ); - return this.services; - } catch (error) { - console.error('❌ Failed to initialize services:', error); - throw error; - } - } - - public getServices(): ServiceContainer { - if (!this.services) { - throw new Error('Services not initialized. Call initializeServices() first.'); - } - return this.services; - } -} - -export default DIContainer; diff --git a/api/src/ai/chat.ts b/api/src/ai/chat.ts deleted file mode 100644 index 796d773..0000000 --- a/api/src/ai/chat.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; - -const DEFAULT_MODEL = 'grok-3'; -const DEFAULT_BASE_URL = 'https://api.x.ai/v1'; -const MAX_MESSAGES = 10; -const MAX_MESSAGE_CHARS = 4000; - -type ChatRole = 'system' | 'assistant' | 'user'; - -interface ChatMessage { - role: ChatRole; - content: string; -} - -interface ChatRequestBody { - messages?: ChatMessage[]; - pageContext?: Record; -} - -interface xAIChatResponse { - choices?: Array<{ - message?: { - content?: string; - }; - }>; - output_text?: string; - output?: Array<{ - content?: Array<{ - text?: string; - }>; - }>; -} - -function jsonResponse(status: number, body: Record): HttpResponseInit { - return { - status, - jsonBody: body, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type' - } - }; -} - -function normalizeMessages(messages: ChatMessage[] | undefined): ChatMessage[] { - if (!Array.isArray(messages)) { - return []; - } - - return messages - .filter((message): message is ChatMessage => Boolean(message && typeof message === 'object')) - .map(message => ({ - role: message.role, - content: String(message.content || '').trim().slice(0, MAX_MESSAGE_CHARS) - })) - .filter(message => ['assistant', 'user', 'system'].includes(message.role) && message.content.length > 0) - .slice(-MAX_MESSAGES); -} - -function buildSystemPrompt(pagePath: string): string { - return [ - 'You are the Miracles in Motion website assistant for MIM4U.', - 'Answer as a warm, concise concierge for Miracles in Motion Foundation — a faith-centered nonprofit serving Los Angeles County with outreach, emergency assistance, resource navigation, wellness support, volunteer coordination, donations, and corporate partnerships.', - 'Use only the provided context and the user messages. If you are unsure, say so and direct the user to info@mim4u.org.', - 'Keep replies practical and short. Prefer bullets only when they improve clarity.', - 'When relevant, guide users to these routes: #/donate, #/volunteers, #/request-assistance, #/impact, #/stories.', - 'Do not invent phone numbers, addresses, office hours, eligibility rules, or application steps.', - 'If someone needs urgent support, tell them to use #/request-assistance and contact info@mim4u.org directly.', - `Current page route: ${pagePath || '/'}` - ].join('\n'); -} - -function extractReply(payload: xAIChatResponse): string { - const choiceReply = payload.choices?.[0]?.message?.content?.trim(); - if (choiceReply) { - return choiceReply; - } - - const outputText = payload.output_text?.trim(); - if (outputText) { - return outputText; - } - - const outputReply = payload.output - ?.flatMap(item => item.content ?? []) - .map(item => item.text?.trim() ?? '') - .filter(Boolean) - .join('\n') - .trim(); - - return outputReply || ''; -} - -export async function chatWithAssistant(request: HttpRequest, context: InvocationContext): Promise { - if (request.method === 'OPTIONS') { - return jsonResponse(200, { ok: true }); - } - - const apiKey = process.env.XAI_API_KEY?.trim(); - if (!apiKey) { - return jsonResponse(503, { - success: false, - error: 'Assistant is not configured on the server.', - timestamp: new Date().toISOString() - }); - } - - try { - const body = await request.json() as ChatRequestBody; - const messages = normalizeMessages(body.messages); - if (messages.length === 0) { - return jsonResponse(400, { - success: false, - error: 'At least one message is required.', - timestamp: new Date().toISOString() - }); - } - - const pagePath = body.pageContext?.path || '/'; - const model = process.env.XAI_MODEL?.trim() || process.env.EXPLORER_AI_MODEL?.trim() || DEFAULT_MODEL; - const baseUrl = (process.env.XAI_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, ''); - - const upstreamResponse = await fetch(`${baseUrl}/chat/completions`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model, - stream: false, - messages: [ - { role: 'system', content: buildSystemPrompt(pagePath) }, - ...messages - ] - }) - }); - - const payload = await upstreamResponse.json() as xAIChatResponse; - if (!upstreamResponse.ok) { - context.error('xAI upstream returned an error', upstreamResponse.status, payload); - return jsonResponse(502, { - success: false, - error: 'Assistant upstream request failed.', - timestamp: new Date().toISOString() - }); - } - - const reply = extractReply(payload); - if (!reply) { - return jsonResponse(502, { - success: false, - error: 'Assistant returned an empty response.', - timestamp: new Date().toISOString() - }); - } - - return jsonResponse(200, { - success: true, - reply, - model, - timestamp: new Date().toISOString() - }); - } catch (error) { - context.error('Error in ai chat handler:', error); - return jsonResponse(500, { - success: false, - error: 'Assistant request failed.', - timestamp: new Date().toISOString() - }); - } -} - -app.http('chatWithAssistant', { - methods: ['POST', 'OPTIONS'], - authLevel: 'anonymous', - route: 'ai/chat', - handler: chatWithAssistant -}); diff --git a/api/src/donations/createDonation.ts b/api/src/donations/createDonation.ts deleted file mode 100644 index 202bc5a..0000000 --- a/api/src/donations/createDonation.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; -import DIContainer from '../DIContainer'; -import { ApiResponse, CreateDonationRequest, Donation } from '../types'; -import { v4 as uuidv4 } from 'uuid'; -import Stripe from 'stripe'; - -export async function createDonation(request: HttpRequest, context: InvocationContext): Promise { - try { - await DIContainer.getInstance().initializeServices(); - const { donationsContainer, secretClient } = DIContainer.getInstance().getServices(); - - // Get request body - const donationRequest = await request.json() as CreateDonationRequest; - - // Validate required fields - if (!donationRequest.amount || !donationRequest.donorEmail || !donationRequest.donorName) { - const response: ApiResponse = { - success: false, - error: 'Missing required fields: amount, donorEmail, donorName', - timestamp: new Date().toISOString() - }; - - return { - status: 400, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } - - // Initialize Stripe if payment method is stripe - let stripePaymentIntentId: string | undefined; - if (donationRequest.paymentMethod === 'stripe') { - try { - const stripeSecretKey = await secretClient.getSecret('stripe-secret-key'); - if (!stripeSecretKey.value) { - throw new Error('stripe-secret-key is not configured'); - } - - const stripe = new Stripe(stripeSecretKey.value!, { - apiVersion: '2025-02-24.acacia' - }); - - const paymentIntent = await stripe.paymentIntents.create({ - amount: Math.round(donationRequest.amount * 100), // Convert to cents - currency: donationRequest.currency.toLowerCase(), - metadata: { - donorEmail: donationRequest.donorEmail, - donorName: donationRequest.donorName, - program: donationRequest.program || 'general' - } - }); - - stripePaymentIntentId = paymentIntent.id; - } catch (stripeError) { - context.error('Stripe payment intent creation failed:', stripeError); - const response: ApiResponse = { - success: false, - error: 'Payment processing failed', - timestamp: new Date().toISOString() - }; - - return { - status: 500, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } - } - - // Create donation record - const donation: Donation = { - id: uuidv4(), - amount: donationRequest.amount, - currency: donationRequest.currency, - donorName: donationRequest.donorName, - donorEmail: donationRequest.donorEmail, - donorPhone: donationRequest.donorPhone, - program: donationRequest.program, - isRecurring: donationRequest.isRecurring, - frequency: donationRequest.frequency, - paymentMethod: donationRequest.paymentMethod, - stripePaymentIntentId, - status: 'pending', - message: donationRequest.message, - isAnonymous: donationRequest.isAnonymous, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - }; - - // Persist the donation using the active backend. - await donationsContainer.items.create(donation); - - const response: ApiResponse = { - success: true, - data: donation, - message: 'Donation created successfully', - timestamp: new Date().toISOString() - }; - - return { - status: 201, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } catch (error) { - context.error('Error creating donation:', error); - - const response: ApiResponse = { - success: false, - error: 'Failed to create donation', - timestamp: new Date().toISOString() - }; - - return { - status: 500, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } -} - -app.http('createDonation', { - methods: ['POST'], - authLevel: 'anonymous', - route: 'donations', - handler: createDonation -}); diff --git a/api/src/donations/getDonations.ts b/api/src/donations/getDonations.ts deleted file mode 100644 index 8beb1c8..0000000 --- a/api/src/donations/getDonations.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; -import DIContainer from '../DIContainer'; -import { ApiResponse, PaginatedResponse, Donation } from '../types'; -import { v4 as uuidv4 } from 'uuid'; - -export async function getDonations(request: HttpRequest, context: InvocationContext): Promise { - try { - await DIContainer.getInstance().initializeServices(); - const { donationsContainer } = DIContainer.getInstance().getServices(); - - const page = parseInt(request.query.get('page') || '1'); - const limit = parseInt(request.query.get('limit') || '10'); - const status = request.query.get('status'); - const program = request.query.get('program'); - - let query = 'SELECT * FROM c WHERE 1=1'; - const parameters: any[] = []; - - if (status) { - query += ' AND c.status = @status'; - parameters.push({ name: '@status', value: status }); - } - - if (program) { - query += ' AND c.program = @program'; - parameters.push({ name: '@program', value: program }); - } - - query += ' ORDER BY c.createdAt DESC'; - - const { resources: donations } = await donationsContainer.items - .query({ - query, - parameters - }) - .fetchAll(); - - // Simple pagination - const total = donations.length; - const pages = Math.ceil(total / limit); - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - const paginatedDonations = donations.slice(startIndex, endIndex); - - const response: PaginatedResponse = { - success: true, - data: paginatedDonations, - pagination: { - page, - limit, - total, - pages - }, - timestamp: new Date().toISOString() - }; - - return { - status: 200, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } catch (error) { - context.error('Error fetching donations:', error); - - const response: ApiResponse = { - success: false, - error: 'Failed to fetch donations', - timestamp: new Date().toISOString() - }; - - return { - status: 500, - jsonBody: response, - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' - } - }; - } -} - -app.http('getDonations', { - methods: ['GET'], - authLevel: 'anonymous', - route: 'donations', - handler: getDonations -}); \ No newline at end of file diff --git a/api/src/index.ts b/api/src/index.ts deleted file mode 100644 index 81e45e9..0000000 --- a/api/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import './donations/createDonation'; -import './donations/getDonations'; -import './ai/chat'; diff --git a/api/src/types.ts b/api/src/types.ts deleted file mode 100644 index bf89261..0000000 --- a/api/src/types.ts +++ /dev/null @@ -1,180 +0,0 @@ -export interface Donation { - id: string; - amount: number; - currency: string; - donorName: string; - donorEmail: string; - donorPhone?: string; - program?: string; - isRecurring: boolean; - frequency?: 'monthly' | 'quarterly' | 'annually'; - paymentMethod: 'stripe' | 'paypal' | 'bank_transfer'; - stripePaymentIntentId?: string; - status: 'pending' | 'completed' | 'failed' | 'cancelled' | 'refunded'; - message?: string; - isAnonymous: boolean; - createdAt: string; - updatedAt: string; - metadata?: Record; -} - -export interface Volunteer { - id: string; - firstName: string; - lastName: string; - email: string; - phone: string; - dateOfBirth: string; - address: { - street: string; - city: string; - state: string; - zipCode: string; - country: string; - }; - emergencyContact: { - name: string; - phone: string; - relationship: string; - }; - skills: string[]; - interests: string[]; - availability: { - monday: boolean; - tuesday: boolean; - wednesday: boolean; - thursday: boolean; - friday: boolean; - saturday: boolean; - sunday: boolean; - timeSlots: string[]; - }; - experience: string; - motivation: string; - backgroundCheck: { - completed: boolean; - completedDate?: string; - status?: 'pending' | 'approved' | 'rejected'; - }; - status: 'pending' | 'approved' | 'inactive' | 'suspended'; - createdAt: string; - updatedAt: string; - lastActivityAt?: string; -} - -export interface Program { - id: string; - name: string; - description: string; - category: 'education' | 'healthcare' | 'community' | 'environment' | 'arts' | 'other'; - targetAudience: string; - goals: string[]; - location: { - type: 'physical' | 'virtual' | 'hybrid'; - address?: string; - city?: string; - state?: string; - country?: string; - virtualLink?: string; - }; - schedule: { - startDate: string; - endDate?: string; - frequency: 'one-time' | 'weekly' | 'monthly' | 'ongoing'; - daysOfWeek: string[]; - timeSlots: string[]; - }; - requirements: { - minimumAge?: number; - maximumAge?: number; - skills?: string[]; - experience?: string; - other?: string[]; - }; - capacity: { - minimum: number; - maximum: number; - current: number; - }; - budget: { - total: number; - raised: number; - currency: string; - }; - coordinator: { - name: string; - email: string; - phone: string; - }; - volunteers: string[]; // Array of volunteer IDs - status: 'planning' | 'active' | 'completed' | 'cancelled' | 'on-hold'; - createdAt: string; - updatedAt: string; - images?: string[]; - documents?: string[]; -} - -export interface ApiResponse { - success: boolean; - data?: T; - error?: string; - message?: string; - timestamp: string; -} - -export interface PaginatedResponse extends ApiResponse { - pagination: { - page: number; - limit: number; - total: number; - pages: number; - }; -} - -export interface CreateDonationRequest { - amount: number; - currency: string; - donorName: string; - donorEmail: string; - donorPhone?: string; - program?: string; - isRecurring: boolean; - frequency?: 'monthly' | 'quarterly' | 'annually'; - paymentMethod: 'stripe' | 'paypal' | 'bank_transfer'; - message?: string; - isAnonymous: boolean; -} - -export interface CreateVolunteerRequest { - firstName: string; - lastName: string; - email: string; - phone: string; - dateOfBirth: string; - address: { - street: string; - city: string; - state: string; - zipCode: string; - country: string; - }; - emergencyContact: { - name: string; - phone: string; - relationship: string; - }; - skills: string[]; - interests: string[]; - availability: { - monday: boolean; - tuesday: boolean; - wednesday: boolean; - thursday: boolean; - friday: boolean; - saturday: boolean; - sunday: boolean; - timeSlots: string[]; - }; - experience: string; - motivation: string; -} \ No newline at end of file diff --git a/api/tsconfig.json b/api/tsconfig.json deleted file mode 100644 index 71000b0..0000000 --- a/api/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "sourceMap": true, - "moduleResolution": "node", - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.test.ts", "dist"] -} \ No newline at end of file diff --git a/docs/DEPLOYMENT_PREREQUISITES.md b/docs/DEPLOYMENT_PREREQUISITES.md index 8e03b2f..e66fcd2 100644 --- a/docs/DEPLOYMENT_PREREQUISITES.md +++ b/docs/DEPLOYMENT_PREREQUISITES.md @@ -1,768 +1,57 @@ -# 🚀 Deployment Prerequisites Guide +# Deployment prerequisites (Proxmox production) -Complete guide for setting up MS Azure, MS Entra, Cloudflare, and all other services required for production deployment. +> **Azure removed (2026-06):** Static Web Apps, Bicep infra, Azure Functions (`api/`), and Azure CI workflows are no longer part of this repo. Production is **VMID 7810** via Proxmox — see [PROXMOX_DEPLOYMENT.md](./deployment/PROXMOX_DEPLOYMENT.md). -## 📋 Table of Contents +## 1. Operator / LAN access -1. [Azure Setup](#azure-setup) -2. [MS Entra (Azure AD) Configuration](#ms-entra-azure-ad-configuration) -3. [Cloudflare Configuration](#cloudflare-configuration) -4. [Stripe Configuration](#stripe-configuration) -5. [Environment Variables](#environment-variables) -6. [Pre-Deployment Checklist](#pre-deployment-checklist) -7. [Post-Deployment Verification](#post-deployment-verification) +| Requirement | Detail | +|-------------|--------| +| Proxmox SSH | `root@192.168.11.12` (r630-02) | +| Web CT | VMID **7810** @ `192.168.11.37` | +| API CT | VMID **7811** @ `192.168.11.36` (optional; `/api/` proxy) | +| Deploy script | `../proxmox/scripts/mim4u-deploy-to-7810.sh` | ---- - -## 1. Azure Setup - -### 1.1 Prerequisites - -- Azure subscription with Contributor or Owner role -- Azure CLI installed and configured -- Bicep CLI installed (optional, for local validation) -- PowerShell 7+ (for deployment scripts) - -### 1.2 Initial Azure Configuration - -#### Login to Azure +## 2. Build toolchain ```bash -# Login to Azure -az login - -# Verify subscription -az account show - -# Set default subscription (if multiple) -az account set --subscription "Your Subscription ID" +node -v # 22.x recommended +npm -v # 10+ +convert -version # ImageMagick (brand prebuild) ``` -#### Create Resource Group +Install deps: ```bash -# Create resource group for production -az group create \ - --name rg-miraclesinmotion-prod \ - --location eastus2 - -# Verify resource group -az group show --name rg-miraclesinmotion-prod +cd miracles_in_motion +npm ci # uses legacy-peer-deps via .npmrc ``` -### 1.3 Required Azure Services +## 3. Environment variables -The infrastructure deployment will create: +Copy `.env.example` → `.env.local` for local dev. Production frontend is static; secrets belong on **7811** (API) or NPMplus, not in the Vite bundle. -- **Azure Static Web Apps** (Standard SKU) - Frontend hosting -- **Azure Functions** (Premium EP1) - Backend API -- **Azure Cosmos DB** - Database -- **Azure Key Vault** - Secrets management -- **Azure Application Insights** - Monitoring -- **Log Analytics Workspace** - Logging -- **Azure SignalR** - Real-time communications -- **Storage Account** - Function app storage +| Variable | Purpose | +|----------|---------| +| `VITE_STRIPE_PUBLISHABLE_KEY` | Donate page (public key only) | +| `VITE_API_BASE_URL` | Override API base (default `/api` via nginx) | +| `VITE_GA_MEASUREMENT_ID` | Analytics (optional) | -### 1.4 Deploy Infrastructure +See `env.production.example` for a non-Azure production template. -```bash -# Navigate to infrastructure directory -cd infrastructure +## 4. Pre-deploy checklist -# Deploy production infrastructure -az deployment group create \ - --resource-group rg-miraclesinmotion-prod \ - --template-file main-production.bicep \ - --parameters main-production.parameters.json \ - --parameters stripePublicKey="pk_live_YOUR_KEY" \ - --parameters customDomainName="miraclesinmotion.org" \ - --parameters enableCustomDomain=true +- [ ] `npm run validate:ci` passes (type-check + tests + header WCAG audit) +- [ ] `npm run build` succeeds (brand export + Vite) +- [ ] NPMplus TLS valid for mim4u.org / www / secure / training +- [ ] `./scripts/mim4u-deploy-to-7810.sh` from proxmox repo (or `--dry-run` review) +- [ ] Post-deploy: `curl -H 'Host: mim4u.org' http://192.168.11.37/` → 200 +- [ ] Optional: `npm run a11y:header-audit:live` on production URL -# Note: Replace pk_live_YOUR_KEY with your actual Stripe public key -``` +## 5. Stripe & DNS (unchanged) -### 1.5 Get Deployment Outputs +- **Stripe:** live keys and webhooks configured on API (7811), not in this static repo. +- **Cloudflare / DNS:** A/AAAA or CNAME to public IP per NPMplus; use proxmox `scripts/update-all-dns-to-public-ip.sh --zone-only=mim4u.org --dry-run` before changes. -```bash -# Get deployment outputs -az deployment group show \ - --resource-group rg-miraclesinmotion-prod \ - --name deployment-name \ - --query properties.outputs -``` - -**Important Outputs:** -- `staticWebAppName` - Static Web App resource name -- `staticWebAppUrl` - Default URL for Static Web App -- `functionAppName` - Function App resource name -- `keyVaultName` - Key Vault resource name -- `appInsightsName` - Application Insights resource name - ---- - -## 2. MS Entra (Azure AD) Configuration - -### 2.1 Create App Registration - -#### Using Azure Portal - -1. Navigate to **Azure Portal** → **Microsoft Entra ID** → **App registrations** -2. Click **+ New registration** -3. Configure: - - **Name**: `Miracles In Motion Web App` - - **Supported account types**: `Accounts in any organizational directory and personal Microsoft accounts` - - **Redirect URI**: - - Type: `Single-page application (SPA)` - - URI: `https://miraclesinmotion.org` (production) - - URI: `https://YOUR_STATIC_WEB_APP.azurestaticapps.net` (staging) - -4. Click **Register** - -#### Using Azure CLI - -```bash -# Create app registration -az ad app create \ - --display-name "Miracles In Motion Web App" \ - --sign-in-audience "AzureADMultipleOrgs" \ - --web-redirect-uris "https://miraclesinmotion.org" "https://www.miraclesinmotion.org" - -# Get app registration ID -APP_ID=$(az ad app list --display-name "Miracles In Motion Web App" --query "[0].appId" -o tsv) -echo "App ID: $APP_ID" -``` - -### 2.2 Configure Authentication - -1. In the app registration, go to **Authentication** -2. Enable **ID tokens** (used for implicit and hybrid flows) -3. Add redirect URIs: - - `https://miraclesinmotion.org` - - `https://www.miraclesinmotion.org` - - `https://YOUR_STATIC_WEB_APP.azurestaticapps.net` -4. Under **Implicit grant and hybrid flows**, enable: - - ✅ ID tokens -5. Save changes - -### 2.3 Configure API Permissions - -1. Go to **API permissions** -2. Click **+ Add a permission** -3. Select **Microsoft Graph** -4. Add the following **Delegated permissions**: - - `User.Read` - Read user profile - - `User.ReadBasic.All` - Read all users' basic profiles - - `email` - View users' email address - - `openid` - Sign users in - - `profile` - View users' basic profile -5. Click **Add permissions** -6. Click **Grant admin consent** (if you have admin rights) - -### 2.4 Create Client Secret (Optional - for server-side flows) - -```bash -# Create client secret (valid for 24 months) -az ad app credential reset \ - --id $APP_ID \ - --display-name "Miracles In Motion Secret" \ - --years 2 - -# Save the secret value immediately - it won't be shown again! -``` - -### 2.5 Configure App Roles - -1. Go to **App roles** → **+ Create app role** -2. Create roles: - - **Display name**: `Admin` - - **Allowed member types**: `Users/Groups` - - **Value**: `Admin` - - **Description**: `Administrator access to all features` - - - **Display name**: `Volunteer` - - **Allowed member types**: `Users/Groups` - - **Value**: `Volunteer` - - **Description**: `Volunteer access to assigned tasks` - - - **Display name**: `Resource` - - **Allowed member types**: `Users/Groups` - - **Value**: `Resource` - - **Description**: `Resource provider access` - -3. Save each role - -### 2.6 Assign Users to Roles - -```bash -# Get user object ID -USER_ID=$(az ad user show --id "user@domain.com" --query "id" -o tsv) - -# Get app role ID (Admin role) -ROLE_ID=$(az ad app show --id $APP_ID --query "appRoles[?value=='Admin'].id" -o tsv) - -# Assign user to role -az ad app assignment create \ - --app-id $APP_ID \ - --principal-id $USER_ID \ - --role-id $ROLE_ID -``` - -### 2.7 Configure Static Web App Authentication - -1. Navigate to **Static Web App** → **Authentication** -2. Click **Add identity provider** -3. Select **Microsoft** -4. Configure: - - **App registration**: Select your app registration - - **App ID**: Your app registration ID - - **App secret setting name**: `MICROSOFT_CLIENT_SECRET` (optional) -5. Save - -#### Using Azure CLI - -```bash -# Get Static Web App resource ID -SWA_ID=$(az staticwebapp show \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod \ - --query "id" -o tsv) - -# Configure Microsoft identity provider -az staticwebapp identity assign \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod - -# Note: Static Web Apps authentication is configured via Azure Portal -# or through the staticwebapp.config.json file -``` - -### 2.8 Update staticwebapp.config.json - -The `staticwebapp.config.json` file should include authentication configuration: - -```json -{ - "routes": [ - { - "route": "/api/*", - "allowedRoles": ["anonymous", "authenticated"] - }, - { - "route": "/admin/*", - "allowedRoles": ["Admin"] - }, - { - "route": "/volunteer/*", - "allowedRoles": ["Volunteer", "Admin"] - }, - { - "route": "/*", - "rewrite": "/index.html" - } - ], - "auth": { - "identityProviders": { - "azureActiveDirectory": { - "registration": { - "openIdIssuer": "https://login.microsoftonline.com/{tenantId}/v2.0", - "clientIdSettingName": "AZURE_CLIENT_ID", - "clientSecretSettingName": "AZURE_CLIENT_SECRET" - } - } - } - }, - "navigationFallback": { - "rewrite": "/index.html", - "exclude": ["/api/*", "/admin/*"] - } -} -``` - -### 2.9 Store Configuration in Key Vault - -```bash -# Store Azure AD configuration in Key Vault -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "azure-client-id" \ - --value "$APP_ID" - -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "azure-client-secret" \ - --value "YOUR_CLIENT_SECRET" - -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "azure-tenant-id" \ - --value "$(az account show --query tenantId -o tsv)" -``` - ---- - -## 3. Cloudflare Configuration - -### 3.1 Prerequisites - -- Cloudflare account -- Domain registered and added to Cloudflare -- DNS management access - -### 3.2 Add Domain to Cloudflare - -1. Log in to **Cloudflare Dashboard** -2. Click **Add a site** -3. Enter your domain: `miraclesinmotion.org` -4. Select a plan (Free plan is sufficient for basic needs) -5. Cloudflare will scan your existing DNS records - -### 3.3 Update Nameservers - -1. Copy the nameservers provided by Cloudflare -2. Update your domain registrar with these nameservers -3. Wait for DNS propagation (24-48 hours) - -### 3.4 Configure DNS Records - -#### Add CNAME Records - -1. Go to **DNS** → **Records** -2. Add the following records: - -| Type | Name | Content | Proxy | TTL | -|------|------|---------|-------|-----| -| CNAME | www | YOUR_STATIC_WEB_APP.azurestaticapps.net | ✅ Proxied | Auto | -| CNAME | @ | YOUR_STATIC_WEB_APP.azurestaticapps.net | ✅ Proxied | Auto | - -**Note**: Replace `YOUR_STATIC_WEB_APP` with your actual Static Web App name. - -#### Verify DNS Configuration - -```bash -# Check DNS records -dig miraclesinmotion.org -dig www.miraclesinmotion.org - -# Check Cloudflare proxy status -curl -I https://miraclesinmotion.org -# Look for "CF-Cache-Status" header -``` - -### 3.5 Configure SSL/TLS - -1. Go to **SSL/TLS** → **Overview** -2. Select **Full (strict)** encryption mode -3. Enable **Always Use HTTPS** -4. Enable **Automatic HTTPS Rewrites** - -### 3.6 Configure Page Rules - -1. Go to **Rules** → **Page Rules** -2. Create rules: - -**Rule 1: Force HTTPS** -- URL: `*miraclesinmotion.org/*` -- Settings: - - Always Use HTTPS: ✅ On - - SSL: Full (strict) - -**Rule 2: Cache Static Assets** -- URL: `*miraclesinmotion.org/assets/*` -- Settings: - - Cache Level: Cache Everything - - Edge Cache TTL: 1 month - -### 3.7 Configure Security Settings - -1. Go to **Security** → **Settings** -2. Configure: - - **Security Level**: Medium - - **Challenge Passage**: 30 minutes - - **Browser Integrity Check**: On - - **Privacy Pass Support**: On - -### 3.8 Configure Firewall Rules - -1. Go to **Security** → **WAF** → **Custom rules** -2. Create rules to block malicious traffic: - -**Rule: Block Bad Bots** -- Expression: `(http.user_agent contains "bot" and not http.user_agent contains "Googlebot")` -- Action: Block - -**Rule: Rate Limiting** -- Expression: `(http.request.uri.path contains "/api/")` -- Action: Challenge -- Rate: 100 requests per minute - -### 3.9 Configure Speed Optimization - -1. Go to **Speed** → **Optimization** -2. Enable: - - ✅ Auto Minify (JavaScript, CSS, HTML) - - ✅ Brotli compression - - ✅ Rocket Loader (optional) - - ✅ Mirage (optional, for mobile) - -### 3.10 Configure Analytics - -1. Go to **Analytics** → **Web Analytics** -2. Enable **Web Analytics** for your domain -3. Add the tracking script to your application (optional) - -### 3.11 Configure Custom Domain in Azure - -After DNS is configured: - -```bash -# Add custom domain to Static Web App -az staticwebapp hostname set \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod \ - --hostname "miraclesinmotion.org" - -az staticwebapp hostname set \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod \ - --hostname "www.miraclesinmotion.org" -``` - -**Note**: Azure will automatically provision SSL certificates for custom domains. - -### 3.12 Verify Cloudflare Configuration - -```bash -# Test DNS resolution -nslookup miraclesinmotion.org -nslookup www.miraclesinmotion.org - -# Test HTTPS -curl -I https://miraclesinmotion.org - -# Test Cloudflare headers -curl -I https://miraclesinmotion.org | grep -i "cf-" - -# Expected headers: -# CF-Cache-Status: DYNAMIC -# CF-Ray: [unique-id] -# Server: cloudflare -``` - ---- - -## 4. Stripe Configuration - -### 4.1 Create Stripe Account - -1. Go to [Stripe Dashboard](https://dashboard.stripe.com) -2. Create account or log in -3. Complete account verification - -### 4.2 Get API Keys - -1. Go to **Developers** → **API keys** -2. Copy: - - **Publishable key** (starts with `pk_live_`) - - **Secret key** (starts with `sk_live_`) - Keep this secret! - -### 4.3 Configure Webhooks - -1. Go to **Developers** → **Webhooks** -2. Click **+ Add endpoint** -3. Configure: - - **Endpoint URL**: `https://miraclesinmotion.org/api/webhooks/stripe` - - **Events to send**: Select relevant events: - - `payment_intent.succeeded` - - `payment_intent.payment_failed` - - `charge.succeeded` - - `charge.failed` -4. Copy the **Webhook signing secret** (starts with `whsec_`) - -### 4.4 Store Stripe Secrets in Key Vault - -```bash -# Store Stripe keys in Key Vault -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "stripe-publishable-key" \ - --value "pk_live_YOUR_KEY" - -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "stripe-secret-key" \ - --value "sk_live_YOUR_KEY" - -az keyvault secret set \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "stripe-webhook-secret" \ - --value "whsec_YOUR_SECRET" -``` - -### 4.5 Update Function App Settings - -```bash -# Get secrets from Key Vault -STRIPE_SECRET=$(az keyvault secret show \ - --vault-name YOUR_KEY_VAULT_NAME \ - --name "stripe-secret-key" \ - --query "value" -o tsv) - -# Update Function App settings -az functionapp config appsettings set \ - --name YOUR_FUNCTION_APP_NAME \ - --resource-group rg-miraclesinmotion-prod \ - --settings "STRIPE_SECRET_KEY=@Microsoft.KeyVault(SecretUri=https://YOUR_KEY_VAULT_NAME.vault.azure.net/secrets/stripe-secret-key/)" -``` - ---- - -## 5. Environment Variables - -### 5.1 Create Environment File Template - -Create `.env.production` file: - -```bash -# Azure Configuration -AZURE_STATIC_WEB_APP_URL=https://miraclesinmotion.org -AZURE_FUNCTION_APP_URL=https://YOUR_FUNCTION_APP.azurewebsites.net -AZURE_CLIENT_ID=your-azure-client-id -AZURE_TENANT_ID=your-azure-tenant-id - -# Stripe Configuration -VITE_STRIPE_PUBLISHABLE_KEY=pk_live_YOUR_KEY -STRIPE_SECRET_KEY=sk_live_YOUR_KEY -STRIPE_WEBHOOK_SECRET=whsec_YOUR_SECRET - -# Cosmos DB Configuration -COSMOS_DATABASE_NAME=MiraclesInMotion -COSMOS_ENDPOINT=https://YOUR_COSMOS_ACCOUNT.documents.azure.com:443/ - -# Application Insights -APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=YOUR_KEY - -# Key Vault -KEY_VAULT_URL=https://YOUR_KEY_VAULT_NAME.vault.azure.net/ - -# SignalR -SIGNALR_CONNECTION_STRING=Endpoint=https://YOUR_SIGNALR.service.signalr.net;AccessKey=YOUR_KEY; - -# Custom Domain -CUSTOM_DOMAIN=miraclesinmotion.org -``` - -### 5.2 Update Static Web App Configuration - -```bash -# Set environment variables for Static Web App -az staticwebapp appsettings set \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod \ - --setting-names "VITE_STRIPE_PUBLISHABLE_KEY=pk_live_YOUR_KEY" \ - "AZURE_CLIENT_ID=your-azure-client-id" \ - "AZURE_TENANT_ID=your-azure-tenant-id" -``` - ---- - -## 6. Pre-Deployment Checklist - -### 6.1 Azure Checklist - -- [ ] Azure subscription created and active -- [ ] Resource group created -- [ ] Infrastructure deployed via Bicep -- [ ] All Azure resources created successfully -- [ ] Key Vault configured with secrets -- [ ] Application Insights configured -- [ ] Static Web App created -- [ ] Function App created and configured -- [ ] Cosmos DB database and containers created -- [ ] RBAC permissions configured - -### 6.2 MS Entra Checklist - -- [ ] App registration created -- [ ] Redirect URIs configured -- [ ] API permissions granted -- [ ] App roles created (Admin, Volunteer, Resource) -- [ ] Users assigned to roles -- [ ] Client ID and Tenant ID recorded -- [ ] Client secret created (if needed) -- [ ] Static Web App authentication configured - -### 6.3 Cloudflare Checklist - -- [ ] Domain added to Cloudflare -- [ ] Nameservers updated at registrar -- [ ] DNS records configured (CNAME for www and @) -- [ ] SSL/TLS set to Full (strict) -- [ ] Always Use HTTPS enabled -- [ ] Page rules configured -- [ ] Firewall rules configured -- [ ] Security settings configured -- [ ] Speed optimization enabled -- [ ] Custom domain added to Azure Static Web App - -### 6.4 Stripe Checklist - -- [ ] Stripe account created and verified -- [ ] API keys obtained (publishable and secret) -- [ ] Webhook endpoint configured -- [ ] Webhook signing secret obtained -- [ ] Secrets stored in Key Vault -- [ ] Function App configured with Stripe keys - -### 6.5 Application Checklist - -- [ ] Environment variables configured -- [ ] staticwebapp.config.json updated -- [ ] Authentication flow tested -- [ ] API endpoints tested -- [ ] Stripe integration tested -- [ ] Monitoring configured -- [ ] Logging configured - ---- - -## 7. Post-Deployment Verification - -### 7.1 Verify Azure Resources - -```bash -# Check Static Web App status -az staticwebapp show \ - --name YOUR_STATIC_WEB_APP_NAME \ - --resource-group rg-miraclesinmotion-prod - -# Check Function App status -az functionapp show \ - --name YOUR_FUNCTION_APP_NAME \ - --resource-group rg-miraclesinmotion-prod - -# Check Cosmos DB status -az cosmosdb show \ - --name YOUR_COSMOS_ACCOUNT \ - --resource-group rg-miraclesinmotion-prod -``` - -### 7.2 Verify Authentication - -1. Navigate to `https://miraclesinmotion.org` -2. Click "Sign In" -3. Verify Microsoft authentication flow -4. Verify user roles are assigned correctly -5. Test role-based access control - -### 7.3 Verify Cloudflare - -```bash -# Test DNS resolution -dig miraclesinmotion.org -dig www.miraclesinmotion.org - -# Test HTTPS -curl -I https://miraclesinmotion.org - -# Verify Cloudflare headers -curl -I https://miraclesinmotion.org | grep -i "cf-" -``` - -### 7.4 Verify Stripe Integration - -1. Test donation flow on the website -2. Verify webhook events are received -3. Check Stripe dashboard for transactions -4. Verify payment processing - -### 7.5 Verify Monitoring - -1. Check Application Insights for telemetry -2. Verify logs are being collected -3. Set up alerts for critical issues -4. Test error tracking - -### 7.6 Performance Testing - -```bash -# Test page load times -curl -w "@curl-format.txt" -o /dev/null -s https://miraclesinmotion.org - -# Test API response times -curl -w "@curl-format.txt" -o /dev/null -s https://miraclesinmotion.org/api/donations -``` - ---- - -## 8. Troubleshooting - -### 8.1 Common Issues - -#### Authentication Not Working - -- Verify app registration redirect URIs -- Check Static Web App authentication configuration -- Verify user roles are assigned -- Check browser console for errors - -#### DNS Not Resolving - -- Verify nameservers are updated -- Wait for DNS propagation (24-48 hours) -- Check Cloudflare DNS records -- Verify CNAME records point to correct Azure endpoint - -#### SSL Certificate Issues - -- Verify Cloudflare SSL mode is "Full (strict)" -- Check Azure Static Web App custom domain configuration -- Wait for SSL certificate provisioning (can take up to 24 hours) - -#### Stripe Webhook Not Working - -- Verify webhook endpoint URL is correct -- Check webhook signing secret -- Verify Function App is receiving webhook events -- Check Function App logs for errors - -### 8.2 Support Resources - -- **Azure Documentation**: https://docs.microsoft.com/azure -- **MS Entra Documentation**: https://docs.microsoft.com/azure/active-directory -- **Cloudflare Documentation**: https://developers.cloudflare.com -- **Stripe Documentation**: https://stripe.com/docs - ---- - -## 9. Next Steps - -After completing all prerequisites: - -1. Deploy the application using the deployment script -2. Verify all functionality -3. Set up monitoring and alerts -4. Configure backup and disaster recovery -5. Set up CI/CD pipeline -6. Schedule regular security audits -7. Set up performance monitoring - ---- - -## 10. Security Best Practices - -1. **Never commit secrets to source control** -2. **Use Key Vault for all secrets** -3. **Enable MFA for all Azure accounts** -4. **Regularly rotate API keys and secrets** -5. **Monitor for suspicious activity** -6. **Keep dependencies updated** -7. **Use HTTPS everywhere** -8. **Implement rate limiting** -9. **Regular security audits** -10. **Follow principle of least privilege** - ---- - -**Last Updated**: January 2025 -**Maintained by**: Miracles In Motion Development Team +## 6. Legacy Azure documentation +Historical docs under `docs/deployment/` and `docs/phases/` may still mention Azure. Treat them as **archived**; do not follow for new deploys. diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 16808f2..a73391c 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -1,139 +1,74 @@ # Quick Start Guide -Fast path to get the Miracles in Motion project running, tested, and deployed. +Fast path to run, test, and deploy the Miracles in Motion public site. ## 1. Prerequisites -| Tool | Recommended Version | Notes | -|------|---------------------|-------| -| Node.js | 20.x / 22.x | Functions runtime Standard supports node:20; local dev can use 22 | -| npm | 10+ | Bundled with recent Node | -| Azure CLI | >= 2.60 | For infra & Static Web Apps commands | -| SWA CLI (@azure/static-web-apps-cli) | latest | Local API + front-end emulation | -| Git | latest | Source control | -| WSL2 | Enabled | Shell environment (Ubuntu recommended) | + +| Tool | Version | Notes | +|------|---------|-------| +| Node.js | 22.x | CI and local dev | +| npm | 10+ | Use `npm ci` (`.npmrc` sets `legacy-peer-deps`) | +| ImageMagick | any recent | Required for `npm run brand:export` / `prebuild` | +| Git | latest | | + +Production deploy additionally requires LAN access to Proxmox — see [PROXMOX_DEPLOYMENT.md](./deployment/PROXMOX_DEPLOYMENT.md). + +## 2. Clone & install ```bash -# Verify versions -node -v -npm -v -az version +git clone https://github.com/Order-of-Hospitallers/miracles_in_motion.git +cd miracles_in_motion +npm ci ``` -## 2. Clone & Install -```bash -git clone https://github.com/Miracles-In-Motion/public-web.git -cd public-web -npm install --legacy-peer-deps -cd api && npm install --legacy-peer-deps && cd .. -``` +## 3. Environment -## 3. Environment Setup -Create a `.env.local` (frontend) and `api/local.settings.json` (Azure Functions) as needed. +Copy `.env.example` to `.env.local` (do not commit secrets): -Example `.env.local` (do NOT commit secrets): -``` +```env VITE_API_BASE=/api VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxx VITE_DEFAULT_LANGUAGE=en -VITE_SUPPORTED_LANGUAGES=en,es,fr,de,zh,ar,pt,ru ``` -Example `api/local.settings.json`: -```json -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "node" - } -} -``` +## 4. Run locally -## 4. Run Locally (Integrated) -Use SWA CLI to serve front-end + Functions together. ```bash -npm run build:api # Optional: compile API TypeScript -swa start http://localhost:5173 --api-location ./api --devserver-run-command "npm run dev" --api-language node +npm run dev +# http://localhost:5173 ``` -If you prefer two terminals: + +## 5. Test & validate + ```bash -npm run dev # Front-end (Vite) -cd api && npm start # Functions runtime +npm test # unit tests (Vitest) +npm run type-check # tsc --noEmit +npm run a11y:header-audit # WCAG header contrast tokens +npm run validate:ci # all three above ``` -## 5. Testing +## 6. Production build + ```bash -npm test # Front-end tests (Vitest / Testing Library) +npm run build +# output: dist/ +npm run preview # optional local preview ``` -Add more tests under `src/components/__tests__/` or `src/test`. -## 6. Build +## 7. Deploy to mim4u.org (Proxmox) + +From the **proxmox** repo on operator LAN: + ```bash -npm run build # Produces front-end dist/ -cd api && npm run build # Compiles Functions to dist (if configured) +./scripts/mim4u-deploy-to-7810.sh ``` -## 7. Azure Deployment (Static Web App Standard) +Verify: + ```bash -# Login -az login - -# Ensure resource group exists -az group create --name rg-mim-prod --location eastus2 - -# Create Static Web App (front-end + managed functions) -az staticwebapp create \ - --name mim-prod-web-standard \ - --resource-group rg-mim-prod \ - --location eastus2 \ - --source . \ - --branch main \ - --app-location / \ - --output-location dist +curl -I -H 'Host: mim4u.org' http://192.168.11.37/ ``` -To deploy updates without GitHub Actions (manual token): -```bash -TOKEN=$(az staticwebapp secrets list --name mim-prod-web-standard --resource-group rg-mim-prod --query properties.apiKey -o tsv) -swa deploy ./dist --env production --deployment-token $TOKEN -``` +## 8. CI -## 8. Custom Domain -1. Add CNAME `www` → ``. -2. Set hostname: -```bash -az staticwebapp hostname set \ - --name mim-prod-web-standard \ - --resource-group rg-mim-prod \ - --hostname miraclesinmotion.org -``` -Azure provisions SSL automatically. - -## 9. Configuration (staticwebapp.config.json) -Key elements: -- `navigationFallback` ensures SPA routing. -- `globalHeaders` for security (CSP, HSTS). Adjust `Content-Security-Policy` as integrations evolve. - -## 10. Useful Scripts -| Script | Purpose | -|--------|---------| -| `npm run dev` | Start Vite dev server | -| `npm test` | Run tests | -| `npm run build` | Build front-end | -| `npm run analyze` | (If defined) Bundle analysis | - -## 11. Troubleshooting -| Issue | Resolution | -|-------|------------| -| 404 on portal route | Ensure hash routing `/#/portals` or SPA fallback set | -| Functions 500 error | Check `api` logs, run locally with `func start` if using standalone Functions | -| CSP blocking script | Update CSP in `staticwebapp.config.json` to allow required domain | -| Node version mismatch | Use Node 20 for SWA managed functions, 22 locally if desired | - -## 12. Next Steps -- Configure GitHub Actions for CI/CD. -- Add monitoring (Application Insights) if using standalone Functions. -- Replace test Stripe keys with live keys in production. - ---- -Last updated: 2025-11-11 \ No newline at end of file +GitHub Actions runs on PR (`validate.yml`) and main push (`deploy.yml`). See [DEPLOYMENT_PREREQUISITES.md](./DEPLOYMENT_PREREQUISITES.md). diff --git a/docs/deployment/PROXMOX_DEPLOYMENT.md b/docs/deployment/PROXMOX_DEPLOYMENT.md new file mode 100644 index 0000000..0f47db8 --- /dev/null +++ b/docs/deployment/PROXMOX_DEPLOYMENT.md @@ -0,0 +1,56 @@ +# MIM4U production deployment (Proxmox) + +Production for **https://mim4u.org** is served from **VMID 7810** (mim-web-1 @ `192.168.11.37`) behind NPMplus. Azure Static Web Apps and Azure Functions are **not** used. + +## Prerequisites + +- Node.js 22.x, npm 10+ +- ImageMagick (`convert`) for `npm run brand:export` (runs on `prebuild`) +- LAN SSH to Proxmox host **192.168.11.12** (r630-02) +- Sibling repo layout: `../proxmox` beside `miracles_in_motion`, or set `MIM_ROOT` + +## Build and deploy + +From the **proxmox** repo (operator LAN): + +```bash +./scripts/mim4u-deploy-to-7810.sh +``` + +Or manually: + +```bash +cd ~/projects/miracles_in_motion +npm run build +# then tar dist/ into VMID 7810 /var/www/html (see deploy script) +``` + +The deploy script **wipes** `/var/www/html/*` before extract to avoid stale PWA/JS bundles. + +## Verify + +```bash +curl -I -H 'Host: mim4u.org' http://192.168.11.37/ +curl -I -H 'Host: mim4u.org' http://192.168.11.37/brand/logo-horizontal-nav.svg +``` + +Public HTTPS: NPMplus proxy hosts for `mim4u.org`, `www.mim4u.org`, `secure.mim4u.org`, `training.mim4u.org` → `192.168.11.37:80`. + +## CI (GitHub Actions) + +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `validate.yml` | Pull requests | type-check, tests, header WCAG audit, build | +| `deploy.yml` | Push to `main` | validate + GitHub Pages artifact (optional mirror) | +| `a11y-live.yml` | Weekly / manual | Lighthouse accessibility on live mim4u.org | + +Canonical production traffic is **Proxmox 7810**, not GitHub Pages. + +## API backend + +`/api/*` on mim4u.org is proxied by nginx on **7810** to **VMID 7811** (mim-api-1 @ `192.168.11.36:3001`). API deployment is separate from this frontend repo. + +## Related proxmox docs + +- `docs/04-configuration/ALL_VMIDS_ENDPOINTS.md` — VMIDs and FQDNs +- `docs/04-configuration/E2E_ENDPOINTS_LIST.md` — public routing verification diff --git a/docs/deployment/README.md b/docs/deployment/README.md index c074622..e4770c2 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -1,46 +1,20 @@ -# 📚 Deployment Documentation +# Deployment documentation -This directory contains all deployment-related documentation for the Miracles In Motion project. +## Canonical (use this) ---- +| Doc | Purpose | +|-----|---------| +| [PROXMOX_DEPLOYMENT.md](./PROXMOX_DEPLOYMENT.md) | Production deploy to VMID 7810 | +| [../DEPLOYMENT_PREREQUISITES.md](../DEPLOYMENT_PREREQUISITES.md) | Checklist and env vars | +| [../QuickStart.md](../QuickStart.md) | Local dev, test, build | -## 📋 Documentation Files +Proxmox operator script: `../../proxmox/scripts/mim4u-deploy-to-7810.sh` (sibling repo). -### Status & Reports -- **DEPLOYMENT_STATUS.md** - Current deployment status and checklist -- **DEPLOYMENT_STATUS_FINAL.md** - Final deployment status summary -- **DEPLOYMENT_VERIFICATION_REPORT.md** - Deployment verification results -- **DEPLOYMENT_COMPLETE.md** - Deployment completion summary +## Archived (Azure — removed 2026-06) -### Guides & Instructions -- **DEPLOYMENT_SETUP_README.md** - Deployment setup overview -- **ALL_NEXT_STEPS.md** - Complete next steps for deployment -- **COMPLETE_NEXT_STEPS.md** - Complete deployment steps guide -- **DEPLOYMENT_COMPLETE_GUIDE.md** - Quick deployment guide -- **FINAL_DEPLOYMENT_STEPS.md** - Final deployment steps +The following files describe **deprecated** Azure Static Web Apps / Functions / Bicep flows. Do not use for new deployments: -### Next Steps & Tasks -- **DEPLOYMENT_NEXT_STEPS.md** - Next steps for deployment -- **NEXT_STEPS_COMPLETE.md** - Next steps completion summary -- **REMAINING_TASKS_COMPLETE.md** - Remaining tasks summary - -### Cloudflare & Domain -- **CLOUDFLARE_SETUP.md** - Cloudflare setup instructions -- **CLOUDFLARE_AUTOMATION_COMPLETE.md** - Cloudflare automation guide -- **CUSTOM_DOMAIN_SETUP.md** - Custom domain configuration - ---- - -## 🚀 Quick Start - -1. **Check Current Status:** Start with `DEPLOYMENT_STATUS.md` -2. **Follow Next Steps:** See `ALL_NEXT_STEPS.md` for complete guide -3. **Cloudflare Setup:** See `CLOUDFLARE_SETUP.md` if using Cloudflare -4. **Custom Domain:** See `CUSTOM_DOMAIN_SETUP.md` for domain configuration - ---- - -## 📝 Note - -All deployment documentation has been organized here from the project root for better structure and easier access. +- `CLOUDFLARE_*.md`, `DEPLOYMENT_*.md`, `FINAL_*.md`, `ALL_NEXT_STEPS.md`, etc. in this folder +- `docs/phases/*DEPLOYMENT*`, `PRODUCTION_DEPLOYMENT_SUCCESS.md` +Production is **https://mim4u.org** on Proxmox **7810** only. diff --git a/env.production.example b/env.production.example index ec33f6e..6599df0 100644 --- a/env.production.example +++ b/env.production.example @@ -1,65 +1,29 @@ -# Azure Configuration -AZURE_STATIC_WEB_APP_URL=https://miraclesinmotion.org -AZURE_FUNCTION_APP_URL=https://YOUR_FUNCTION_APP.azurewebsites.net -AZURE_CLIENT_ID=your-azure-client-id -AZURE_TENANT_ID=your-azure-tenant-id -AZURE_CLIENT_SECRET=your-azure-client-secret +# Production environment template (Proxmox / mim4u.org) +# Copy relevant values to API host (VMID 7811) or operator .env — not all belong in Vite. -# Stripe Configuration +# Public site +CUSTOM_DOMAIN=mim4u.org +VITE_API_BASE_URL=/api + +# Stripe (public key in frontend; secret + webhook on API 7811) VITE_STRIPE_PUBLISHABLE_KEY=pk_live_YOUR_KEY STRIPE_SECRET_KEY=sk_live_YOUR_KEY STRIPE_WEBHOOK_SECRET=whsec_YOUR_SECRET -# Cosmos DB Configuration -COSMOS_DATABASE_NAME=MiraclesInMotion -COSMOS_ENDPOINT=https://YOUR_COSMOS_ACCOUNT.documents.azure.com:443/ -COSMOS_KEY=your-cosmos-key - -# Application Insights -APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=YOUR_KEY;IngestionEndpoint=https://YOUR_REGION.in.applicationinsights.azure.com/ - -# Key Vault -KEY_VAULT_URL=https://YOUR_KEY_VAULT_NAME.vault.azure.net/ - -# SignalR -SIGNALR_CONNECTION_STRING=Endpoint=https://YOUR_SIGNALR.service.signalr.net;AccessKey=YOUR_KEY;Version=1.0; - -# Custom Domain -CUSTOM_DOMAIN=miraclesinmotion.org - -# Environment -NODE_ENV=production -VITE_API_BASE_URL=https://miraclesinmotion.org/api - -# Feature Flags +# Analytics +VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX VITE_ENABLE_ANALYTICS=true + +# Feature flags VITE_ENABLE_PWA=true -VITE_ENABLE_AI=true +VITE_ENABLE_CHAT=false +NODE_ENV=production -# Cloudflare -CLOUDFLARE_ZONE_ID=your-cloudflare-zone-id -CLOUDFLARE_API_TOKEN=your-cloudflare-api-token - -# Salesforce (Optional) -SALESFORCE_CLIENT_ID=your-salesforce-client-id -SALESFORCE_CLIENT_SECRET=your-salesforce-client-secret -SALESFORCE_USERNAME=your-salesforce-username -SALESFORCE_PASSWORD=your-salesforce-password -SALESFORCE_SECURITY_TOKEN=your-salesforce-security-token - -# Email Configuration (Optional) -SMTP_HOST=smtp.office365.com -SMTP_PORT=587 -SMTP_USER=your-email@domain.com -SMTP_PASSWORD=your-email-password -SMTP_FROM=noreply@miraclesinmotion.org - -# Monitoring -SENTRY_DSN=your-sentry-dsn -LOG_LEVEL=info - -# Security -SESSION_SECRET=your-session-secret -JWT_SECRET=your-jwt-secret -ENCRYPTION_KEY=your-encryption-key +# Cloudflare (DNS only — optional; use proxmox scripts) +CLOUDFLARE_ZONE_ID=your-zone-id +CLOUDFLARE_API_TOKEN=your-scoped-token +# Operator deploy (proxmox repo, not committed) +# PROXMOX_HOST=192.168.11.12 +# VMID_MIM_WEB=7810 +# IP_MIM_WEB=192.168.11.37 diff --git a/infrastructure/main-production.bicep b/infrastructure/main-production.bicep deleted file mode 100644 index 078ed02..0000000 --- a/infrastructure/main-production.bicep +++ /dev/null @@ -1,472 +0,0 @@ -@description('Environment (dev, staging, prod)') -param environment string = 'prod' - -@description('Azure region for resources') -param location string = resourceGroup().location - -@description('Stripe public key for payments') -@secure() -param stripePublicKey string - -@description('Azure AD Client ID for authentication') -param azureClientId string = '' - -@description('Azure AD Tenant ID') -param azureTenantId string = subscription().tenantId - -@description('Azure AD Client Secret (optional, for server-side flows)') -@secure() -param azureClientSecret string = '' - -@description('Custom domain name for the application') -param customDomainName string = '' - -@description('Enable custom domain configuration') -param enableCustomDomain bool = false - -@description('Static Web App SKU') -@allowed(['Standard']) -param staticWebAppSku string = 'Standard' - -@description('Function App SKU (Y1 for Consumption, EP1/EP2/EP3 for Premium)') -@allowed(['Y1', 'EP1', 'EP2', 'EP3']) -param functionAppSku string = 'Y1' - -// Variables -var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 6) -var resourcePrefix = 'mim-${environment}-${uniqueSuffix}' - -// Log Analytics Workspace (needed first for Application Insights) -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { - name: '${resourcePrefix}-logs' - location: location - properties: { - sku: { - name: 'PerGB2018' - } - retentionInDays: 30 - features: { - searchVersion: 1 - legacy: 0 - enableLogAccessUsingOnlyResourcePermissions: true - } - } -} - -// Application Insights -resource appInsights 'Microsoft.Insights/components@2020-02-02' = { - name: '${resourcePrefix}-appinsights' - location: location - kind: 'web' - properties: { - Application_Type: 'web' - Flow_Type: 'Redfield' - Request_Source: 'IbizaAIExtension' - RetentionInDays: 90 - WorkspaceResourceId: logAnalyticsWorkspace.id - IngestionMode: 'LogAnalytics' - publicNetworkAccessForIngestion: 'Enabled' - publicNetworkAccessForQuery: 'Enabled' - } -} - -// Key Vault -resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { - name: '${resourcePrefix}-kv' - location: location - properties: { - sku: { - family: 'A' - name: 'standard' - } - tenantId: subscription().tenantId - enableRbacAuthorization: true - enableSoftDelete: true - softDeleteRetentionInDays: 90 - enablePurgeProtection: true - networkAcls: { - defaultAction: 'Allow' - bypass: 'AzureServices' - } - } -} - -// Cosmos DB Account - Production Ready -resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { - name: '${resourcePrefix}-cosmos' - location: location - kind: 'GlobalDocumentDB' - properties: { - databaseAccountOfferType: 'Standard' - consistencyPolicy: { - defaultConsistencyLevel: 'Session' - } - locations: [ - { - locationName: location - failoverPriority: 0 - isZoneRedundant: true - } - ] - enableAutomaticFailover: true - enableMultipleWriteLocations: false - backupPolicy: { - type: 'Periodic' - periodicModeProperties: { - backupIntervalInMinutes: 240 - backupRetentionIntervalInHours: 720 - backupStorageRedundancy: 'Geo' - } - } - networkAclBypass: 'AzureServices' - publicNetworkAccess: 'Enabled' - } -} - -// Cosmos DB Database -resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = { - parent: cosmosAccount - name: 'MiraclesInMotion' - properties: { - resource: { - id: 'MiraclesInMotion' - } - } -} - -// Cosmos DB Containers -resource donationsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'donations' - properties: { - resource: { - id: 'donations' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - indexingPolicy: { - indexingMode: 'consistent' - automatic: true - includedPaths: [ - { - path: '/*' - } - ] - } - } - } -} - -resource volunteersContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'volunteers' - properties: { - resource: { - id: 'volunteers' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - } - } -} - -resource programsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'programs' - properties: { - resource: { - id: 'programs' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - } - } -} - -resource studentsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'students' - properties: { - resource: { - id: 'students' - partitionKey: { - paths: ['/schoolId'] - kind: 'Hash' - } - } - } -} - -// Function App Service Plan - Consumption Plan (Y1) for Production -// Note: Changed from Premium to Consumption to avoid quota issues -// Premium can be enabled later by requesting quota increase -resource functionAppServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = { - name: '${resourcePrefix}-func-plan' - location: location - sku: { - name: functionAppSku - tier: functionAppSku == 'Y1' ? 'Dynamic' : 'ElasticPremium' - size: functionAppSku != 'Y1' ? functionAppSku : null - capacity: functionAppSku != 'Y1' ? 1 : null - } - kind: 'functionapp' - properties: { - reserved: functionAppSku != 'Y1' - maximumElasticWorkerCount: functionAppSku != 'Y1' ? 20 : null - } -} - -// Storage Account for Function App -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { - name: replace('${resourcePrefix}stor', '-', '') - location: location - sku: { - name: 'Standard_LRS' - } - kind: 'StorageV2' - properties: { - supportsHttpsTrafficOnly: true - encryption: { - services: { - file: { - keyType: 'Account' - enabled: true - } - blob: { - keyType: 'Account' - enabled: true - } - } - keySource: 'Microsoft.Storage' - } - accessTier: 'Hot' - } -} - -// Function App with Enhanced Configuration -resource functionApp 'Microsoft.Web/sites@2023-12-01' = { - name: '${resourcePrefix}-func' - location: location - kind: 'functionapp,linux' - identity: { - type: 'SystemAssigned' - } - properties: { - serverFarmId: functionAppServicePlan.id - siteConfig: { - linuxFxVersion: 'NODE|22' - appSettings: [ - { - name: 'AzureWebJobsStorage' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' - } - { - name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' - } - { - name: 'WEBSITE_CONTENTSHARE' - value: toLower('${resourcePrefix}-func') - } - { - name: 'FUNCTIONS_EXTENSION_VERSION' - value: '~4' - } - { - name: 'FUNCTIONS_WORKER_RUNTIME' - value: 'node' - } - { - name: 'WEBSITE_NODE_DEFAULT_VERSION' - value: '~22' - } - { - name: 'APPINSIGHTS_INSTRUMENTATIONKEY' - value: appInsights.properties.InstrumentationKey - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: appInsights.properties.ConnectionString - } - { - name: 'COSMOS_CONNECTION_STRING' - value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString - } - { - name: 'COSMOS_DATABASE_NAME' - value: 'MiraclesInMotion' - } - { - name: 'KEY_VAULT_URL' - value: keyVault.properties.vaultUri - } - { - name: 'STRIPE_PUBLIC_KEY' - value: stripePublicKey - } - ] - cors: { - allowedOrigins: ['*'] - supportCredentials: false - } - use32BitWorkerProcess: false - ftpsState: 'FtpsOnly' - minTlsVersion: '1.2' - } - httpsOnly: true - clientAffinityEnabled: false - } -} - -// SignalR Service - Standard for Production -resource signalR 'Microsoft.SignalRService/signalR@2023-02-01' = { - name: '${resourcePrefix}-signalr' - location: location - sku: { - name: 'Standard_S1' - capacity: 1 - } - kind: 'SignalR' - properties: { - features: [ - { - flag: 'ServiceMode' - value: 'Serverless' - } - ] - cors: { - allowedOrigins: ['*'] - } - networkACLs: { - defaultAction: 'Allow' - } - } -} - -// Static Web App - Production Ready with Custom Domain Support -resource staticWebApp 'Microsoft.Web/staticSites@2023-12-01' = { - name: '${resourcePrefix}-web' - location: 'Central US' - sku: { - name: staticWebAppSku - tier: staticWebAppSku - } - properties: { - buildProperties: { - appLocation: '/' - apiLocation: 'api' - outputLocation: 'dist' - } - stagingEnvironmentPolicy: 'Enabled' - allowConfigFileUpdates: true - enterpriseGradeCdnStatus: 'Enabled' - } -} - -// Note: Static Web App authentication is configured via staticwebapp.config.json -// and Azure Portal. App settings are configured separately through Azure Portal -// or during deployment. The azureClientId and azureTenantId parameters are -// stored in Key Vault for reference and can be used to configure authentication -// in the Azure Portal after deployment. - -// Custom Domain Configuration (if enabled) -// Note: Using TXT validation for Enterprise Grade Edge compatibility -resource customDomain 'Microsoft.Web/staticSites/customDomains@2023-12-01' = if (enableCustomDomain && !empty(customDomainName)) { - parent: staticWebApp - name: customDomainName - properties: { - validationMethod: 'txt-token' - } -} - -// Key Vault Secrets -resource cosmosConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'cosmos-connection-string' - properties: { - value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString - } -} - -resource signalRConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'signalr-connection-string' - properties: { - value: signalR.listKeys().primaryConnectionString - } -} - -resource stripeSecretKeySecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'stripe-secret-key' - properties: { - value: 'sk_live_placeholder' // Replace with actual secret key - } -} - -// Azure AD Configuration Secrets -resource azureClientIdSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = if (!empty(azureClientId)) { - parent: keyVault - name: 'azure-client-id' - properties: { - value: azureClientId - } -} - -resource azureTenantIdSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'azure-tenant-id' - properties: { - value: azureTenantId - } -} - -resource azureClientSecretSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = if (!empty(azureClientSecret)) { - parent: keyVault - name: 'azure-client-secret' - properties: { - value: azureClientSecret - } -} - -// RBAC Assignments for Function App -resource keyVaultSecretsUserRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(keyVault.id, functionApp.id, 'Key Vault Secrets User') - scope: keyVault - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User - principalId: functionApp.identity.principalId - principalType: 'ServicePrincipal' - } -} - -resource cosmosContributorRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(cosmosAccount.id, functionApp.id, 'Cosmos DB Built-in Data Contributor') - scope: cosmosAccount - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00000000-0000-0000-0000-000000000002') // Cosmos DB Built-in Data Contributor - principalId: functionApp.identity.principalId - principalType: 'ServicePrincipal' - } -} - -// Outputs -output resourceGroupName string = resourceGroup().name -output cosmosAccountName string = cosmosAccount.name -output functionAppName string = functionApp.name -output staticWebAppName string = staticWebApp.name -output keyVaultName string = keyVault.name -output appInsightsName string = appInsights.name -output signalRName string = signalR.name -output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name -output functionAppUrl string = 'https://${functionApp.properties.defaultHostName}' -output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' -output customDomainName string = enableCustomDomain ? customDomainName : '' -output applicationInsightsInstrumentationKey string = appInsights.properties.InstrumentationKey -output applicationInsightsConnectionString string = appInsights.properties.ConnectionString -output azureClientId string = azureClientId -output azureTenantId string = azureTenantId -output keyVaultUri string = keyVault.properties.vaultUri diff --git a/infrastructure/main-production.parameters.json b/infrastructure/main-production.parameters.json deleted file mode 100644 index 436e8e9..0000000 --- a/infrastructure/main-production.parameters.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "environment": { - "value": "prod" - }, - "location": { - "value": "East US" - }, - "stripePublicKey": { - "value": "pk_live_placeholder" - }, - "azureClientId": { - "value": "" - }, - "azureTenantId": { - "value": "" - }, - "azureClientSecret": { - "value": "" - }, - "customDomainName": { - "value": "mim4u.org" - }, - "enableCustomDomain": { - "value": true - }, - "staticWebAppSku": { - "value": "Standard" - }, - "functionAppSku": { - "value": "Y1" - } - } -} \ No newline at end of file diff --git a/infrastructure/main.bicep b/infrastructure/main.bicep deleted file mode 100644 index f4950e6..0000000 --- a/infrastructure/main.bicep +++ /dev/null @@ -1,323 +0,0 @@ -@description('Environment (dev, staging, prod)') -param environment string = 'prod' - -@description('Azure region for resources') -param location string = resourceGroup().location - -@description('Stripe public key for payments') -@secure() -param stripePublicKey string - -// Variables -var uniqueSuffix = substring(uniqueString(resourceGroup().id), 0, 6) - -// Cosmos DB Account -resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { - name: 'mim-${environment}-${uniqueSuffix}-cosmos' - location: location - kind: 'GlobalDocumentDB' - properties: { - databaseAccountOfferType: 'Standard' - consistencyPolicy: { - defaultConsistencyLevel: 'Session' - } - locations: [ - { - locationName: location - failoverPriority: 0 - isZoneRedundant: false - } - ] - capabilities: [ - { - name: 'EnableServerless' - } - ] - backupPolicy: { - type: 'Periodic' - periodicModeProperties: { - backupIntervalInMinutes: 240 - backupRetentionIntervalInHours: 720 - backupStorageRedundancy: 'Local' - } - } - } -} - -// Cosmos DB Database -resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = { - parent: cosmosAccount - name: 'MiraclesInMotion' - properties: { - resource: { - id: 'MiraclesInMotion' - } - } -} - -// Cosmos DB Containers -resource donationsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'donations' - properties: { - resource: { - id: 'donations' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - indexingPolicy: { - indexingMode: 'consistent' - automatic: true - includedPaths: [ - { - path: '/*' - } - ] - } - } - } -} - -resource volunteersContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'volunteers' - properties: { - resource: { - id: 'volunteers' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - } - } -} - -resource programsContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { - parent: cosmosDatabase - name: 'programs' - properties: { - resource: { - id: 'programs' - partitionKey: { - paths: ['/id'] - kind: 'Hash' - } - } - } -} - -// Key Vault -resource keyVault 'Microsoft.KeyVault/vaults@2024-04-01-preview' = { - name: 'mim${environment}${uniqueSuffix}kv' - location: location - properties: { - sku: { - family: 'A' - name: 'standard' - } - tenantId: tenant().tenantId - accessPolicies: [] - enabledForDeployment: false - enabledForDiskEncryption: false - enabledForTemplateDeployment: true - enableSoftDelete: true - softDeleteRetentionInDays: 90 - enableRbacAuthorization: true - } -} - -// Application Insights -resource appInsights 'Microsoft.Insights/components@2020-02-02' = { - name: 'mim-${environment}-${uniqueSuffix}-insights' - location: location - kind: 'web' - properties: { - Application_Type: 'web' - WorkspaceResourceId: logAnalyticsWorkspace.id - } -} - -// Log Analytics Workspace -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { - name: 'mim-${environment}-${uniqueSuffix}-logs' - location: location - properties: { - sku: { - name: 'PerGB2018' - } - retentionInDays: 30 - } -} - -// Storage Account for Functions -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { - name: 'mim${environment}${uniqueSuffix}st' - location: location - sku: { - name: 'Standard_LRS' - } - kind: 'StorageV2' - properties: { - accessTier: 'Hot' - supportsHttpsTrafficOnly: true - minimumTlsVersion: 'TLS1_2' - } -} - -// App Service Plan -resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = { - name: 'mim-${environment}-${uniqueSuffix}-plan' - location: location - sku: { - name: 'Y1' - tier: 'Dynamic' - } - properties: { - reserved: false - } -} - -// Function App -resource functionApp 'Microsoft.Web/sites@2023-01-01' = { - name: 'mim-${environment}-${uniqueSuffix}-func' - location: location - kind: 'functionapp' - identity: { - type: 'SystemAssigned' - } - properties: { - serverFarmId: appServicePlan.id - siteConfig: { - appSettings: [ - { - name: 'AzureWebJobsStorage' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net' - } - { - name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net' - } - { - name: 'WEBSITE_CONTENTSHARE' - value: toLower('mim-${environment}-func') - } - { - name: 'FUNCTIONS_EXTENSION_VERSION' - value: '~4' - } - { - name: 'FUNCTIONS_WORKER_RUNTIME' - value: 'node' - } - { - name: 'WEBSITE_NODE_DEFAULT_VERSION' - value: '~22' - } - { - name: 'APPINSIGHTS_INSTRUMENTATIONKEY' - value: appInsights.properties.InstrumentationKey - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: appInsights.properties.ConnectionString - } - { - name: 'COSMOS_CONNECTION_STRING' - value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString - } - { - name: 'COSMOS_DATABASE_NAME' - value: 'MiraclesInMotion' - } - { - name: 'KEY_VAULT_URL' - value: keyVault.properties.vaultUri - } - { - name: 'STRIPE_PUBLIC_KEY' - value: stripePublicKey - } - ] - } - httpsOnly: true - } -} - -// SignalR Service -resource signalR 'Microsoft.SignalRService/signalR@2023-02-01' = { - name: 'mim-${environment}-${uniqueSuffix}-signalr' - location: location - sku: { - name: 'Free_F1' - capacity: 1 - } - kind: 'SignalR' - properties: { - features: [ - { - flag: 'ServiceMode' - value: 'Serverless' - } - ] - cors: { - allowedOrigins: ['*'] - } - } -} - -// Static Web App -resource staticWebApp 'Microsoft.Web/staticSites@2023-01-01' = { - name: 'mim-${environment}-${uniqueSuffix}-web' - location: 'Central US' - sku: { - name: 'Free' - } - properties: { - buildProperties: { - outputLocation: 'dist' - apiLocation: '' - appLocation: '/' - } - stagingEnvironmentPolicy: 'Enabled' - } -} - -// Key Vault Secrets -resource cosmosConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'cosmos-connection-string' - properties: { - value: cosmosAccount.listConnectionStrings().connectionStrings[0].connectionString - } -} - -resource signalRConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { - parent: keyVault - name: 'signalr-connection-string' - properties: { - value: signalR.listKeys().primaryConnectionString - } -} - -// RBAC Assignments for Function App -resource keyVaultSecretsUserRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(keyVault.id, functionApp.id, 'Key Vault Secrets User') - scope: keyVault - properties: { - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User - principalId: functionApp.identity.principalId - principalType: 'ServicePrincipal' - } -} - -// Outputs -output resourceGroupName string = resourceGroup().name -output cosmosAccountName string = cosmosAccount.name -output functionAppName string = functionApp.name -output staticWebAppName string = staticWebApp.name -output keyVaultName string = keyVault.name -output appInsightsName string = appInsights.name -output signalRName string = signalR.name -output functionAppUrl string = 'https://${functionApp.properties.defaultHostName}' -output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' diff --git a/infrastructure/main.parameters.json b/infrastructure/main.parameters.json deleted file mode 100644 index 75ee81d..0000000 --- a/infrastructure/main.parameters.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appName": { - "value": "miraclesinmotion" - }, - "environment": { - "value": "prod" - }, - "location": { - "value": "eastus2" - }, - "stripePublicKey": { - "value": "pk_live_placeholder" - } - } -} \ No newline at end of file diff --git a/scripts/deploy-production-full.ps1 b/scripts/deploy-production-full.ps1 deleted file mode 100644 index 7371672..0000000 --- a/scripts/deploy-production-full.ps1 +++ /dev/null @@ -1,202 +0,0 @@ -# Production Deployment Script for Miracles in Motion -# This script deploys the application to Azure with production SKUs and custom domain support - -param( - [Parameter(Mandatory=$false)] - [string]$ResourceGroupName = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$Location = "East US", - - [Parameter(Mandatory=$false)] - [string]$CustomDomain = "mim4u.org", - - [Parameter(Mandatory=$false)] - [string]$StripePublicKey = "", - - [Parameter(Mandatory=$false)] - [switch]$SkipBuild = $false -) - -Write-Host "🚀 Starting Production Deployment for Miracles in Motion" -ForegroundColor Green -Write-Host "=================================================" -ForegroundColor Green - -# Check if Azure CLI is installed -if (!(Get-Command "az" -ErrorAction SilentlyContinue)) { - Write-Error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" - exit 1 -} - -# Check if Static Web Apps CLI is installed -if (!(Get-Command "swa" -ErrorAction SilentlyContinue)) { - Write-Host "📦 Installing Azure Static Web Apps CLI..." -ForegroundColor Yellow - npm install -g @azure/static-web-apps-cli -} - -# Login to Azure if not already logged in -$currentAccount = az account show --query "user.name" -o tsv 2>$null -if (!$currentAccount) { - Write-Host "🔐 Please log in to Azure..." -ForegroundColor Yellow - az login -} - -Write-Host "✅ Logged in as: $currentAccount" -ForegroundColor Green - -# Create resource group if it doesn't exist -Write-Host "📁 Creating resource group: $ResourceGroupName" -ForegroundColor Yellow -az group create --name $ResourceGroupName --location $Location - -# Validate Stripe key -if ([string]::IsNullOrEmpty($StripePublicKey)) { - $StripePublicKey = Read-Host "Enter your Stripe Public Key (pk_live_...)" -} - -if (!$StripePublicKey.StartsWith("pk_live_")) { - Write-Warning "Warning: Using non-production Stripe key. For production, use pk_live_..." -} - -# Build and test the application -if (!$SkipBuild) { - Write-Host "🔨 Building the application..." -ForegroundColor Yellow - - # Install dependencies - Write-Host "📦 Installing main project dependencies..." -ForegroundColor Cyan - npm install --legacy-peer-deps - - # Install API dependencies - Write-Host "📦 Installing API dependencies..." -ForegroundColor Cyan - Set-Location api - npm install - Set-Location .. - - # Run tests - Write-Host "🧪 Running tests..." -ForegroundColor Cyan - npx vitest run --reporter=verbose - - if ($LASTEXITCODE -ne 0) { - Write-Warning "Some tests failed, but continuing with deployment..." - } - - # Build the application - Write-Host "🏗️ Building production bundle..." -ForegroundColor Cyan - npm run build - - if ($LASTEXITCODE -ne 0) { - Write-Error "Build failed! Please fix the errors and try again." - exit 1 - } - - Write-Host "✅ Build completed successfully" -ForegroundColor Green -} - -# Deploy infrastructure -Write-Host "🏗️ Deploying Azure infrastructure..." -ForegroundColor Yellow - -$deploymentName = "mim-prod-deployment-$(Get-Date -Format 'yyyyMMdd-HHmmss')" - -$deploymentResult = az deployment group create ` - --resource-group $ResourceGroupName ` - --template-file "infrastructure/main-production.bicep" ` - --parameters "infrastructure/main-production.parameters.json" ` - --parameters stripePublicKey=$StripePublicKey ` - --parameters customDomainName=$CustomDomain ` - --parameters enableCustomDomain=$true ` - --name $deploymentName ` - --output json | ConvertFrom-Json - -if ($LASTEXITCODE -ne 0) { - Write-Error "Infrastructure deployment failed!" - exit 1 -} - -Write-Host "✅ Infrastructure deployed successfully" -ForegroundColor Green - -# Get deployment outputs -$staticWebAppName = $deploymentResult.properties.outputs.staticWebAppName.value -$functionAppName = $deploymentResult.properties.outputs.functionAppName.value -$staticWebAppUrl = $deploymentResult.properties.outputs.staticWebAppUrl.value - -Write-Host "📋 Deployment Details:" -ForegroundColor Cyan -Write-Host " Static Web App: $staticWebAppName" -ForegroundColor White -Write-Host " Function App: $functionAppName" -ForegroundColor White -Write-Host " Primary URL: $staticWebAppUrl" -ForegroundColor White -if ($CustomDomain) { - Write-Host " Custom Domain: https://$CustomDomain" -ForegroundColor White -} - -# Get deployment token for Static Web App -Write-Host "🔑 Getting deployment token..." -ForegroundColor Yellow -$deploymentToken = az staticwebapp secrets list --name $staticWebAppName --resource-group $ResourceGroupName --query "properties.apiKey" -o tsv - -if ([string]::IsNullOrEmpty($deploymentToken)) { - Write-Error "Failed to get deployment token!" - exit 1 -} - -# Deploy to Static Web App -Write-Host "🚀 Deploying to Static Web App..." -ForegroundColor Yellow - -$env:SWA_CLI_DEPLOYMENT_TOKEN = $deploymentToken - -# Deploy using SWA CLI -swa deploy ./dist --api-location ./api --env production --deployment-token $deploymentToken - -if ($LASTEXITCODE -ne 0) { - Write-Error "Static Web App deployment failed!" - exit 1 -} - -Write-Host "✅ Application deployed successfully!" -ForegroundColor Green - -# Deploy Function App -Write-Host "🔧 Deploying Azure Functions..." -ForegroundColor Yellow - -# Build API project -Set-Location api -npm run build 2>$null -if ($LASTEXITCODE -ne 0) { - Write-Host "Building API project..." -ForegroundColor Cyan - npm run tsc 2>$null -} -Set-Location .. - -# Deploy functions -az functionapp deployment source config-zip --resource-group $ResourceGroupName --name $functionAppName --src "./api.zip" 2>$null - -if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Azure Functions deployed successfully" -ForegroundColor Green -} else { - Write-Warning "Function deployment may have issues, but Static Web App is deployed" -} - -# Custom Domain Setup Instructions -if ($CustomDomain) { - Write-Host "🌐 Custom Domain Setup:" -ForegroundColor Magenta - Write-Host "================================" -ForegroundColor Magenta - Write-Host "1. Add a CNAME record in your DNS:" -ForegroundColor Yellow - Write-Host " Name: www (or @)" -ForegroundColor White - Write-Host " Value: $($staticWebAppUrl -replace 'https://', '')" -ForegroundColor White - Write-Host "" - Write-Host "2. Wait for DNS propagation (up to 48 hours)" -ForegroundColor Yellow - Write-Host "3. The SSL certificate will be automatically provisioned" -ForegroundColor Yellow - Write-Host "" -} - -# Final Summary -Write-Host "🎉 DEPLOYMENT COMPLETE!" -ForegroundColor Green -Write-Host "========================" -ForegroundColor Green -Write-Host "🌐 Primary URL: $staticWebAppUrl" -ForegroundColor Cyan -if ($CustomDomain) { - Write-Host "🌐 Custom Domain: https://$CustomDomain (after DNS setup)" -ForegroundColor Cyan -} -Write-Host "🔗 Portal Access: $staticWebAppUrl#/portals" -ForegroundColor Cyan -Write-Host "📊 Analytics: $staticWebAppUrl#/analytics" -ForegroundColor Cyan -Write-Host "🤖 AI Portal: $staticWebAppUrl#/ai-portal" -ForegroundColor Cyan -Write-Host "" -Write-Host "📚 Next Steps:" -ForegroundColor Yellow -Write-Host "1. Set up DNS records for custom domain" -ForegroundColor White -Write-Host "2. Configure authentication providers if needed" -ForegroundColor White -Write-Host "3. Set up monitoring and alerts" -ForegroundColor White -Write-Host "4. Update Stripe webhook endpoints" -ForegroundColor White -Write-Host "" -Write-Host "✨ Your Miracles in Motion application is now live in production!" -ForegroundColor Green \ No newline at end of file diff --git a/scripts/deploy-production.ps1 b/scripts/deploy-production.ps1 deleted file mode 100644 index 696e60d..0000000 --- a/scripts/deploy-production.ps1 +++ /dev/null @@ -1,62 +0,0 @@ -# Miracles in Motion - Production Deployment Script -param( - [string]$ResourceGroupName = "rg-miraclesinmotion-prod", - [string]$Location = "East US 2", - [string]$SubscriptionId = "6187c4d0-3c1a-4135-a8b5-c9782fcf0743" -) - -Write-Host "🚀 Starting Miracles in Motion Production Deployment" -ForegroundColor Green - -# Set subscription -Write-Host "Setting Azure subscription..." -ForegroundColor Yellow -az account set --subscription $SubscriptionId - -# Create resource group -Write-Host "Creating resource group: $ResourceGroupName" -ForegroundColor Yellow -az group create --name $ResourceGroupName --location $Location - -# Deploy infrastructure using Bicep -Write-Host "Deploying Azure infrastructure..." -ForegroundColor Yellow -$deploymentResult = az deployment group create ` - --resource-group $ResourceGroupName ` - --template-file infrastructure/main.bicep ` - --parameters @infrastructure/main.parameters.json ` - --query 'properties.outputs' ` - --output json - -if ($LASTEXITCODE -ne 0) { - Write-Error "Infrastructure deployment failed!" - exit 1 -} - -Write-Host "✅ Infrastructure deployed successfully!" -ForegroundColor Green - -# Parse deployment outputs -$outputs = $deploymentResult | ConvertFrom-Json - -# Build and deploy Functions -Write-Host "Building Azure Functions..." -ForegroundColor Yellow -Set-Location api -npm install -npm run build - -# Deploy Functions -Write-Host "Deploying Azure Functions..." -ForegroundColor Yellow -func azure functionapp publish $outputs.functionAppName.value - -# Build frontend -Write-Host "Building frontend application..." -ForegroundColor Yellow -Set-Location .. -npm install -npm run build - -# Deploy to Static Web Apps -Write-Host "Deploying to Azure Static Web Apps..." -ForegroundColor Yellow -az staticwebapp deploy ` - --name $outputs.staticWebAppName.value ` - --resource-group $ResourceGroupName ` - --source dist/ - -Write-Host "🎉 Deployment completed successfully!" -ForegroundColor Green -Write-Host "🌐 Frontend URL: https://$($outputs.staticWebAppName.value).azurestaticapps.net" -ForegroundColor Cyan -Write-Host "⚡ Functions URL: https://$($outputs.functionAppName.value).azurewebsites.net" -ForegroundColor Cyan \ No newline at end of file diff --git a/scripts/deploy-simple.ps1 b/scripts/deploy-simple.ps1 deleted file mode 100644 index d240271..0000000 --- a/scripts/deploy-simple.ps1 +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env pwsh -# Simple Azure deployment script - -Write-Host "🚀 Deploying Miracles in Motion to Azure..." -ForegroundColor Green - -# Deploy infrastructure -Write-Host "Deploying infrastructure..." -ForegroundColor Yellow -$deployment = az deployment group create ` - --resource-group rg-miraclesinmotion-prod ` - --template-file infrastructure/main.bicep ` - --parameters @infrastructure/main.parameters.json ` - --name "infra-deploy-$(Get-Date -Format 'yyyyMMdd-HHmmss')" ` - --output json | ConvertFrom-Json - -if ($LASTEXITCODE -ne 0) { - Write-Error "❌ Infrastructure deployment failed!" - exit 1 -} - -Write-Host "✅ Infrastructure deployed successfully!" -ForegroundColor Green - -# Get deployment outputs -$functionAppName = $deployment.properties.outputs.functionAppName.value -$staticWebAppName = $deployment.properties.outputs.staticWebAppName.value - -Write-Host "Function App: $functionAppName" -ForegroundColor Cyan -Write-Host "Static Web App: $staticWebAppName" -ForegroundColor Cyan - -# Install Azure Functions Core Tools if needed -Write-Host "Checking Azure Functions Core Tools..." -ForegroundColor Yellow -try { - func --version -} catch { - Write-Host "Installing Azure Functions Core Tools..." -ForegroundColor Yellow - npm install -g azure-functions-core-tools@4 --unsafe-perm true -} - -# Deploy Functions -Write-Host "Deploying Azure Functions..." -ForegroundColor Yellow -Set-Location api -func azure functionapp publish $functionAppName --typescript -Set-Location .. - -# Deploy Static Web App -Write-Host "Deploying Static Web App..." -ForegroundColor Yellow -az staticwebapp deploy ` - --name $staticWebAppName ` - --resource-group rg-miraclesinmotion-prod ` - --source dist/ - -Write-Host "🎉 Deployment completed successfully!" -ForegroundColor Green -Write-Host "🌐 Frontend URL: https://$staticWebAppName.azurestaticapps.net" -ForegroundColor Cyan -Write-Host "⚡ Functions URL: https://$functionAppName.azurewebsites.net" -ForegroundColor Cyan \ No newline at end of file diff --git a/scripts/deployment-checklist.ps1 b/scripts/deployment-checklist.ps1 deleted file mode 100644 index a647aa8..0000000 --- a/scripts/deployment-checklist.ps1 +++ /dev/null @@ -1,350 +0,0 @@ -# Deployment Checklist Script for Miracles In Motion -# This script verifies all prerequisites are met before deployment - -param( - [Parameter(Mandatory=$false)] - [string]$ResourceGroupName = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$StaticWebAppName = "", - - [Parameter(Mandatory=$false)] - [string]$FunctionAppName = "", - - [Parameter(Mandatory=$false)] - [switch]$SkipCloudflare = $false, - - [Parameter(Mandatory=$false)] - [switch]$SkipStripe = $false -) - -$ErrorActionPreference = "Stop" - -# Colors for output -function Write-ColorOutput($ForegroundColor) { - $fc = $host.UI.RawUI.ForegroundColor - $host.UI.RawUI.ForegroundColor = $ForegroundColor - if ($args) { - Write-Output $args - } - $host.UI.RawUI.ForegroundColor = $fc -} - -Write-ColorOutput Green "🚀 Deployment Prerequisites Checklist" -Write-Output "==========================================" -Write-Output "" - -$allChecksPassed = $true -$checks = @() - -# Function to add check result -function Add-Check { - param( - [string]$Name, - [bool]$Passed, - [string]$Message = "" - ) - - $checks += @{ - Name = $Name - Passed = $Passed - Message = $Message - } - - if (-not $Passed) { - $script:allChecksPassed = $false - } -} - -# 1. Azure CLI Check -Write-ColorOutput Cyan "1. Checking Azure CLI..." -try { - $azVersion = az version --output json | ConvertFrom-Json - Add-Check "Azure CLI" $true "Version: $($azVersion.'azure-cli')" - Write-ColorOutput Green " ✅ Azure CLI installed" -} catch { - Add-Check "Azure CLI" $false "Azure CLI not found. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" - Write-ColorOutput Red " ❌ Azure CLI not found" -} -Write-Output "" - -# 2. Azure Login Check -Write-ColorOutput Cyan "2. Checking Azure login status..." -try { - $account = az account show --output json 2>$null | ConvertFrom-Json - if ($account) { - Add-Check "Azure Login" $true "Logged in as: $($account.user.name)" - Write-ColorOutput Green " ✅ Logged in to Azure" - Write-Output " Subscription: $($account.name)" - Write-Output " Tenant ID: $($account.tenantId)" - } else { - throw "Not logged in" - } -} catch { - Add-Check "Azure Login" $false "Not logged in to Azure. Run: az login" - Write-ColorOutput Red " ❌ Not logged in to Azure" -} -Write-Output "" - -# 3. Resource Group Check -Write-ColorOutput Cyan "3. Checking resource group..." -try { - $rg = az group show --name $ResourceGroupName --output json 2>$null | ConvertFrom-Json - if ($rg) { - Add-Check "Resource Group" $true "Resource group exists: $($rg.name)" - Write-ColorOutput Green " ✅ Resource group exists" - Write-Output " Location: $($rg.location)" - } else { - throw "Resource group not found" - } -} catch { - Add-Check "Resource Group" $false "Resource group not found: $ResourceGroupName" - Write-ColorOutput Red " ❌ Resource group not found" -} -Write-Output "" - -# 4. Static Web App Check -Write-ColorOutput Cyan "4. Checking Static Web App..." -if ([string]::IsNullOrEmpty($StaticWebAppName)) { - # Try to find Static Web App - $swa = az staticwebapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - if ($swa) { - $StaticWebAppName = $swa.name - } -} - -if (-not [string]::IsNullOrEmpty($StaticWebAppName)) { - try { - $swa = az staticwebapp show --name $StaticWebAppName --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json - if ($swa) { - Add-Check "Static Web App" $true "Static Web App exists: $($swa.name)" - Write-ColorOutput Green " ✅ Static Web App exists" - Write-Output " URL: https://$($swa.defaultHostname)" - } else { - throw "Static Web App not found" - } - } catch { - Add-Check "Static Web App" $false "Static Web App not found: $StaticWebAppName" - Write-ColorOutput Red " ❌ Static Web App not found" - } -} else { - Add-Check "Static Web App" $false "Static Web App name not specified" - Write-ColorOutput Red " ❌ Static Web App name not specified" -} -Write-Output "" - -# 5. Function App Check -Write-ColorOutput Cyan "5. Checking Function App..." -if ([string]::IsNullOrEmpty($FunctionAppName)) { - # Try to find Function App - $fa = az functionapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - if ($fa) { - $FunctionAppName = $fa.name - } -} - -if (-not [string]::IsNullOrEmpty($FunctionAppName)) { - try { - $fa = az functionapp show --name $FunctionAppName --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json - if ($fa) { - Add-Check "Function App" $true "Function App exists: $($fa.name)" - Write-ColorOutput Green " ✅ Function App exists" - Write-Output " URL: https://$($fa.defaultHostName)" - } else { - throw "Function App not found" - } - } catch { - Add-Check "Function App" $false "Function App not found: $FunctionAppName" - Write-ColorOutput Red " ❌ Function App not found" - } -} else { - Add-Check "Function App" $false "Function App name not specified" - Write-ColorOutput Red " ❌ Function App name not specified" -} -Write-Output "" - -# 6. Key Vault Check -Write-ColorOutput Cyan "6. Checking Key Vault..." -try { - $kv = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - if ($kv) { - Add-Check "Key Vault" $true "Key Vault exists: $($kv.name)" - Write-ColorOutput Green " ✅ Key Vault exists" - - # Check for required secrets - $requiredSecrets = @("stripe-secret-key", "azure-client-id", "azure-tenant-id") - $missingSecrets = @() - - foreach ($secret in $requiredSecrets) { - try { - $secretValue = az keyvault secret show --vault-name $kv.name --name $secret --output json 2>$null | ConvertFrom-Json - if (-not $secretValue) { - $missingSecrets += $secret - } - } catch { - $missingSecrets += $secret - } - } - - if ($missingSecrets.Count -eq 0) { - Write-ColorOutput Green " ✅ Required secrets present" - } else { - Write-ColorOutput Yellow " ⚠️ Missing secrets: $($missingSecrets -join ', ')" - } - } else { - throw "Key Vault not found" - } -} catch { - Add-Check "Key Vault" $false "Key Vault not found" - Write-ColorOutput Red " ❌ Key Vault not found" -} -Write-Output "" - -# 7. Cosmos DB Check -Write-ColorOutput Cyan "7. Checking Cosmos DB..." -try { - $cosmos = az cosmosdb list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - if ($cosmos) { - Add-Check "Cosmos DB" $true "Cosmos DB exists: $($cosmos.name)" - Write-ColorOutput Green " ✅ Cosmos DB exists" - } else { - throw "Cosmos DB not found" - } -} catch { - Add-Check "Cosmos DB" $false "Cosmos DB not found" - Write-ColorOutput Red " ❌ Cosmos DB not found" -} -Write-Output "" - -# 8. Application Insights Check -Write-ColorOutput Cyan "8. Checking Application Insights..." -try { - $ai = az monitor app-insights component show --app $ResourceGroupName --output json 2>$null | ConvertFrom-Json - if (-not $ai) { - # Try alternative method - $ai = az resource list --resource-group $ResourceGroupName --resource-type "Microsoft.Insights/components" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - } - if ($ai) { - Add-Check "Application Insights" $true "Application Insights exists" - Write-ColorOutput Green " ✅ Application Insights exists" - } else { - throw "Application Insights not found" - } -} catch { - Add-Check "Application Insights" $false "Application Insights not found" - Write-ColorOutput Red " ❌ Application Insights not found" -} -Write-Output "" - -# 9. Azure AD App Registration Check -Write-ColorOutput Cyan "9. Checking Azure AD App Registration..." -try { - $appReg = az ad app list --display-name "Miracles In Motion Web App" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 - if ($appReg) { - Add-Check "Azure AD App Registration" $true "App Registration exists: $($appReg.appId)" - Write-ColorOutput Green " ✅ Azure AD App Registration exists" - Write-Output " App ID: $($appReg.appId)" - - # Check redirect URIs - if ($appReg.web.redirectUris) { - Write-Output " Redirect URIs: $($appReg.web.redirectUris.Count)" - } - } else { - throw "App Registration not found" - } -} catch { - Add-Check "Azure AD App Registration" $false "Azure AD App Registration not found" - Write-ColorOutput Red " ❌ Azure AD App Registration not found" -} -Write-Output "" - -# 10. Cloudflare Check -if (-not $SkipCloudflare) { - Write-ColorOutput Cyan "10. Checking Cloudflare configuration..." - try { - # Check DNS resolution - $dnsResult = Resolve-DnsName -Name "miraclesinmotion.org" -ErrorAction SilentlyContinue - if ($dnsResult) { - Add-Check "Cloudflare DNS" $true "DNS resolution working" - Write-ColorOutput Green " ✅ DNS resolution working" - } else { - Add-Check "Cloudflare DNS" $false "DNS resolution failed" - Write-ColorOutput Red " ❌ DNS resolution failed" - } - } catch { - Add-Check "Cloudflare DNS" $false "Could not verify DNS" - Write-ColorOutput Yellow " ⚠️ Could not verify DNS" - } - Write-Output "" -} - -# 11. Stripe Check -if (-not $SkipStripe) { - Write-ColorOutput Cyan "11. Checking Stripe configuration..." - try { - if ($kv) { - $stripeKey = az keyvault secret show --vault-name $kv.name --name "stripe-secret-key" --output json 2>$null | ConvertFrom-Json - if ($stripeKey -and $stripeKey.value -like "sk_live_*") { - Add-Check "Stripe Configuration" $true "Stripe keys configured" - Write-ColorOutput Green " ✅ Stripe keys configured" - } else { - Add-Check "Stripe Configuration" $false "Stripe keys not configured or not production keys" - Write-ColorOutput Yellow " ⚠️ Stripe keys not configured or not production keys" - } - } else { - Add-Check "Stripe Configuration" $false "Key Vault not available" - Write-ColorOutput Yellow " ⚠️ Key Vault not available" - } - } catch { - Add-Check "Stripe Configuration" $false "Could not verify Stripe configuration" - Write-ColorOutput Yellow " ⚠️ Could not verify Stripe configuration" - } - Write-Output "" -} - -# 12. Environment Variables Check -Write-ColorOutput Cyan "12. Checking environment variables..." -$envFile = ".env.production" -if (Test-Path $envFile) { - Add-Check "Environment File" $true "Environment file exists" - Write-ColorOutput Green " ✅ Environment file exists" -} else { - Add-Check "Environment File" $false "Environment file not found: $envFile" - Write-ColorOutput Yellow " ⚠️ Environment file not found" -} -Write-Output "" - -# Summary -Write-Output "" -Write-ColorOutput Cyan "==========================================" -Write-ColorOutput Cyan "Summary" -Write-ColorOutput Cyan "==========================================" -Write-Output "" - -$passedChecks = ($checks | Where-Object { $_.Passed -eq $true }).Count -$totalChecks = $checks.Count - -Write-Output "Passed: $passedChecks / $totalChecks" -Write-Output "" - -foreach ($check in $checks) { - if ($check.Passed) { - Write-ColorOutput Green "✅ $($check.Name)" - } else { - Write-ColorOutput Red "❌ $($check.Name)" - if ($check.Message) { - Write-Output " $($check.Message)" - } - } -} - -Write-Output "" - -if ($allChecksPassed) { - Write-ColorOutput Green "✅ All checks passed! Ready for deployment." - exit 0 -} else { - Write-ColorOutput Red "❌ Some checks failed. Please fix the issues before deploying." - exit 1 -} - diff --git a/scripts/populate-env.ps1 b/scripts/populate-env.ps1 deleted file mode 100644 index f7b1927..0000000 --- a/scripts/populate-env.ps1 +++ /dev/null @@ -1,273 +0,0 @@ -# Script to populate .env file with Azure configuration -# This script gathers Azure information and creates/updates the .env file - -param( - [Parameter(Mandatory=$false)] - [string]$ResourceGroupName = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$Location = "eastus2", - - [Parameter(Mandatory=$false)] - [string]$Domain = "mim4u.org", - - [Parameter(Mandatory=$false)] - [switch]$CreateResourceGroup = $false -) - -$ErrorActionPreference = "Stop" - -Write-Host "🔧 Populating .env file with Azure configuration" -ForegroundColor Green -Write-Host "=============================================" -ForegroundColor Green -Write-Host "" - -# Check if logged in to Azure -$account = az account show --output json 2>$null | ConvertFrom-Json -if (-not $account) { - Write-Host "❌ Not logged in to Azure. Please run: az login" -ForegroundColor Red - exit 1 -} - -Write-Host "✅ Logged in to Azure" -ForegroundColor Green -Write-Host " Subscription: $($account.name)" -ForegroundColor Gray -Write-Host " Tenant ID: $($account.tenantId)" -ForegroundColor Gray -Write-Host "" - -# Get subscription ID -$subscriptionId = $account.id -$tenantId = $account.tenantId - -# Check if resource group exists -$rgExists = az group exists --name $ResourceGroupName --output tsv -if ($rgExists -eq "false") { - if ($CreateResourceGroup) { - Write-Host "📁 Creating resource group: $ResourceGroupName" -ForegroundColor Cyan - az group create --name $ResourceGroupName --location $Location | Out-Null - Write-Host "✅ Resource group created" -ForegroundColor Green - } else { - Write-Host "⚠️ Resource group '$ResourceGroupName' does not exist." -ForegroundColor Yellow - Write-Host " Run with -CreateResourceGroup to create it, or deploy infrastructure first." -ForegroundColor Yellow - } -} else { - Write-Host "✅ Resource group exists: $ResourceGroupName" -ForegroundColor Green -} - -Write-Host "" - -# Check for existing resources -Write-Host "🔍 Checking for existing resources..." -ForegroundColor Cyan - -# Check for Static Web App -$staticWebApp = az staticwebapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$staticWebAppName = "" -$staticWebAppUrl = "" - -if ($staticWebApp) { - $staticWebAppName = $staticWebApp.name - $staticWebAppUrl = "https://$($staticWebApp.defaultHostname)" - Write-Host "✅ Found Static Web App: $staticWebAppName" -ForegroundColor Green -} else { - Write-Host "⚠️ Static Web App not found (will use placeholder)" -ForegroundColor Yellow - $staticWebAppUrl = "https://mim4u.org" -} - -# Check for Function App -$functionApp = az functionapp list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$functionAppName = "" -$functionAppUrl = "" - -if ($functionApp) { - $functionAppName = $functionApp.name - $functionAppUrl = "https://$($functionApp.defaultHostName)" - Write-Host "✅ Found Function App: $functionAppName" -ForegroundColor Green -} else { - Write-Host "⚠️ Function App not found (will use placeholder)" -ForegroundColor Yellow - $functionAppUrl = "https://YOUR_FUNCTION_APP.azurewebsites.net" -} - -# Check for Key Vault -$keyVault = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$keyVaultName = "" -$keyVaultUrl = "" - -if ($keyVault) { - $keyVaultName = $keyVault.name - $keyVaultUrl = "https://$keyVaultName.vault.azure.net/" - Write-Host "✅ Found Key Vault: $keyVaultName" -ForegroundColor Green -} else { - Write-Host "⚠️ Key Vault not found (will use placeholder)" -ForegroundColor Yellow - $keyVaultUrl = "https://YOUR_KEY_VAULT_NAME.vault.azure.net/" -} - -# Check for Cosmos DB -$cosmosAccount = az cosmosdb list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$cosmosEndpoint = "" - -if ($cosmosAccount) { - $cosmosEndpoint = "https://$($cosmosAccount.name).documents.azure.com:443/" - Write-Host "✅ Found Cosmos DB: $($cosmosAccount.name)" -ForegroundColor Green -} else { - Write-Host "⚠️ Cosmos DB not found (will use placeholder)" -ForegroundColor Yellow - $cosmosEndpoint = "https://YOUR_COSMOS_ACCOUNT.documents.azure.com:443/" -} - -# Check for Application Insights -$appInsights = az monitor app-insights component show --app $ResourceGroupName --output json 2>$null | ConvertFrom-Json -if (-not $appInsights) { - $appInsights = az resource list --resource-group $ResourceGroupName --resource-type "Microsoft.Insights/components" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -} -$appInsightsConnectionString = "" - -if ($appInsights) { - $appInsightsConnectionString = $appInsights.connectionString - Write-Host "✅ Found Application Insights: $($appInsights.name)" -ForegroundColor Green -} else { - Write-Host "⚠️ Application Insights not found (will use placeholder)" -ForegroundColor Yellow - $appInsightsConnectionString = "InstrumentationKey=YOUR_KEY;IngestionEndpoint=https://YOUR_REGION.in.applicationinsights.azure.com/" -} - -# Check for SignalR -$signalR = az signalr list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$signalRConnectionString = "" - -if ($signalR) { - $signalRKeys = az signalr key list --name $signalR.name --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json - if ($signalRKeys) { - $signalREndpoint = $signalR.hostName - $signalRKey = $signalRKeys.primaryKey - $signalRConnectionString = "Endpoint=https://$signalREndpoint;AccessKey=$signalRKey;Version=1.0;" - Write-Host "✅ Found SignalR: $($signalR.name)" -ForegroundColor Green - } -} else { - Write-Host "⚠️ SignalR not found (will use placeholder)" -ForegroundColor Yellow - $signalRConnectionString = "Endpoint=https://YOUR_SIGNALR.service.signalr.net;AccessKey=YOUR_KEY;Version=1.0;" -} - -# Check for Azure AD App Registration -$appReg = az ad app list --display-name "Miracles In Motion Web App" --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -$azureClientId = "" - -if ($appReg) { - $azureClientId = $appReg.appId - Write-Host "✅ Found Azure AD App Registration: $azureClientId" -ForegroundColor Green -} else { - Write-Host "⚠️ Azure AD App Registration not found (will use placeholder)" -ForegroundColor Yellow - Write-Host " Run: .\scripts\setup-azure-entra.ps1 to create it" -ForegroundColor Yellow - $azureClientId = "your-azure-client-id" -} - -Write-Host "" - -# Prompt for Stripe keys -Write-Host "💳 Stripe Configuration" -ForegroundColor Cyan -$stripePublishableKey = Read-Host "Enter Stripe Publishable Key (pk_live_...) [or press Enter to skip]" -if ([string]::IsNullOrWhiteSpace($stripePublishableKey)) { - $stripePublishableKey = "pk_live_YOUR_KEY" -} - -$stripeSecretKey = Read-Host "Enter Stripe Secret Key (sk_live_...) [or press Enter to skip]" -if ([string]::IsNullOrWhiteSpace($stripeSecretKey)) { - $stripeSecretKey = "sk_live_YOUR_KEY" -} - -$stripeWebhookSecret = Read-Host "Enter Stripe Webhook Secret (whsec_...) [or press Enter to skip]" -if ([string]::IsNullOrWhiteSpace($stripeWebhookSecret)) { - $stripeWebhookSecret = "whsec_YOUR_SECRET" -} - -Write-Host "" - -# Create .env file content -$envContent = @" -# Azure Configuration -AZURE_SUBSCRIPTION_ID=$subscriptionId -AZURE_TENANT_ID=$tenantId -AZURE_RESOURCE_GROUP=$ResourceGroupName -AZURE_LOCATION=$Location -AZURE_STATIC_WEB_APP_URL=$staticWebAppUrl -AZURE_STATIC_WEB_APP_NAME=$staticWebAppName -AZURE_FUNCTION_APP_URL=$functionAppUrl -AZURE_FUNCTION_APP_NAME=$functionAppName -AZURE_CLIENT_ID=$azureClientId -AZURE_TENANT_ID=$tenantId -AZURE_CLIENT_SECRET=your-azure-client-secret - -# Stripe Configuration -VITE_STRIPE_PUBLISHABLE_KEY=$stripePublishableKey -STRIPE_SECRET_KEY=$stripeSecretKey -STRIPE_WEBHOOK_SECRET=$stripeWebhookSecret - -# Cosmos DB Configuration -COSMOS_DATABASE_NAME=MiraclesInMotion -COSMOS_ENDPOINT=$cosmosEndpoint -COSMOS_KEY=your-cosmos-key - -# Application Insights -APPLICATIONINSIGHTS_CONNECTION_STRING=$appInsightsConnectionString - -# Key Vault -KEY_VAULT_URL=$keyVaultUrl -KEY_VAULT_NAME=$keyVaultName - -# SignalR -SIGNALR_CONNECTION_STRING=$signalRConnectionString - -# Custom Domain -CUSTOM_DOMAIN=$Domain - -# Environment -NODE_ENV=production -VITE_API_BASE_URL=$staticWebAppUrl/api - -# Feature Flags -VITE_ENABLE_ANALYTICS=true -VITE_ENABLE_PWA=true -VITE_ENABLE_AI=true - -# Cloudflare (Optional) -CLOUDFLARE_ZONE_ID=your-cloudflare-zone-id -CLOUDFLARE_API_TOKEN=your-cloudflare-api-token - -# Salesforce (Optional) -SALESFORCE_CLIENT_ID=your-salesforce-client-id -SALESFORCE_CLIENT_SECRET=your-salesforce-client-secret -SALESFORCE_USERNAME=your-salesforce-username -SALESFORCE_PASSWORD=your-salesforce-password -SALESFORCE_SECURITY_TOKEN=your-salesforce-security-token - -# Email Configuration (Optional) -SMTP_HOST=smtp.office365.com -SMTP_PORT=587 -SMTP_USER=your-email@domain.com -SMTP_PASSWORD=your-email-password -SMTP_FROM=noreply@mim4u.org - -# Monitoring (Optional) -SENTRY_DSN=your-sentry-dsn -LOG_LEVEL=info - -# Security -SESSION_SECRET=your-session-secret -JWT_SECRET=your-jwt-secret -ENCRYPTION_KEY=your-encryption-key -"@ - -# Write .env file -$envFile = ".env.production" -$envContent | Out-File -FilePath $envFile -Encoding UTF8 -NoNewline - -Write-Host "✅ Created .env file: $envFile" -ForegroundColor Green -Write-Host "" -Write-Host "📋 Summary:" -ForegroundColor Cyan -Write-Host " Subscription: $($account.name)" -ForegroundColor Gray -Write-Host " Tenant ID: $tenantId" -ForegroundColor Gray -Write-Host " Resource Group: $ResourceGroupName" -ForegroundColor Gray -Write-Host " Domain: $Domain" -ForegroundColor Gray -Write-Host "" -Write-Host "⚠️ Next Steps:" -ForegroundColor Yellow -Write-Host "1. Review and update placeholder values in $envFile" -ForegroundColor White -Write-Host "2. Run: .\scripts\setup-azure-entra.ps1 to create Azure AD app registration" -ForegroundColor White -Write-Host "3. Deploy infrastructure: az deployment group create ..." -ForegroundColor White -Write-Host "4. Store secrets in Key Vault using: .\scripts\store-secrets-in-keyvault.ps1" -ForegroundColor White -Write-Host "" - diff --git a/scripts/setup-azure-entra.ps1 b/scripts/setup-azure-entra.ps1 deleted file mode 100644 index 7a60dd5..0000000 --- a/scripts/setup-azure-entra.ps1 +++ /dev/null @@ -1,272 +0,0 @@ -# MS Entra (Azure AD) Setup Script for Miracles In Motion (PowerShell) -# This script helps configure Azure AD authentication for the application - -param( - [Parameter(Mandatory=$false)] - [string]$AppName = "Miracles In Motion Web App", - - [Parameter(Mandatory=$false)] - [string]$Domain = "mim4u.org", - - [Parameter(Mandatory=$false)] - [string]$StaticWebAppName = "", - - [Parameter(Mandatory=$false)] - [string]$AzureResourceGroup = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$KeyVaultName = "" -) - -$ErrorActionPreference = "Stop" - -Write-Host "🔐 MS Entra (Azure AD) Setup Script" -ForegroundColor Green -Write-Host "==========================================" -ForegroundColor Green -Write-Host "" - -# Check if Azure CLI is installed -if (-not (Get-Command "az" -ErrorAction SilentlyContinue)) { - Write-Host "❌ Azure CLI not found. Please install it first." -ForegroundColor Red - Write-Host "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" -ForegroundColor Yellow - exit 1 -} - -# Check if logged in to Azure -Write-Host "📋 Checking Azure login status..." -ForegroundColor Cyan -$account = az account show --output json 2>$null | ConvertFrom-Json -if (-not $account) { - Write-Host "⚠️ Not logged in to Azure. Please log in..." -ForegroundColor Yellow - az login - $account = az account show --output json | ConvertFrom-Json -} -Write-Host "✅ Logged in as: $($account.user.name)" -ForegroundColor Green -Write-Host "" - -# Get Azure Static Web App URL -Write-Host "📋 Getting Azure Static Web App information..." -ForegroundColor Cyan - -if ([string]::IsNullOrEmpty($StaticWebAppName)) { - # Try to find Static Web App - $swa = az staticwebapp list --resource-group $AzureResourceGroup --output json | ConvertFrom-Json | Select-Object -First 1 - if ($swa) { - $StaticWebAppName = $swa.name - } -} - -$azureStaticWebAppUrl = "" -if (-not [string]::IsNullOrEmpty($StaticWebAppName)) { - $azureStaticWebAppUrl = az staticwebapp show ` - --name $StaticWebAppName ` - --resource-group $AzureResourceGroup ` - --query "defaultHostname" -o tsv 2>$null - - if ($azureStaticWebAppUrl) { - $azureStaticWebAppUrl = "https://$azureStaticWebAppUrl" - Write-Host "✅ Static Web App URL: $azureStaticWebAppUrl" -ForegroundColor Green - } -} else { - Write-Host "⚠️ Static Web App not found. Using default URL format." -ForegroundColor Yellow - $azureStaticWebAppUrl = "https://${StaticWebAppName}.azurestaticapps.net" -} - -$productionUrl = "https://$Domain" -$wwwUrl = "https://www.$Domain" - -Write-Host "" - -# Get Tenant ID -$tenantId = $account.tenantId -Write-Host "✅ Tenant ID: $tenantId" -ForegroundColor Green -Write-Host "" - -# Check if app registration already exists -Write-Host "🔍 Checking for existing app registration..." -ForegroundColor Cyan -$existingApp = az ad app list --display-name $AppName --output json | ConvertFrom-Json | Select-Object -First 1 - -if ($existingApp) { - Write-Host "⚠️ App registration already exists: $($existingApp.appId)" -ForegroundColor Yellow - $updateApp = Read-Host "Do you want to update it? (y/n)" - if ($updateApp -ne "y") { - $appId = $existingApp.appId - Write-Host "✅ Using existing app registration" -ForegroundColor Green - } else { - $appId = $existingApp.appId - Write-Host "📝 Updating app registration..." -ForegroundColor Cyan - } -} else { - # Create app registration - Write-Host "📝 Creating app registration..." -ForegroundColor Cyan - $appId = az ad app create ` - --display-name $AppName ` - --sign-in-audience "AzureADMultipleOrgs" ` - --web-redirect-uris $productionUrl $wwwUrl $azureStaticWebAppUrl ` - --query "appId" -o tsv - - Write-Host "✅ App registration created: $appId" -ForegroundColor Green -} - -Write-Host "" - -# Update redirect URIs -Write-Host "📝 Updating redirect URIs..." -ForegroundColor Cyan -az ad app update --id $appId ` - --web-redirect-uris $productionUrl $wwwUrl $azureStaticWebAppUrl ` - --enable-id-token-issuance true ` - --enable-access-token-issuance false | Out-Null - -Write-Host "✅ Redirect URIs updated" -ForegroundColor Green -Write-Host " - $productionUrl" -Write-Host " - $wwwUrl" -Write-Host " - $azureStaticWebAppUrl" -Write-Host "" - -# Configure API permissions -Write-Host "📝 Configuring API permissions..." -ForegroundColor Cyan - -$graphPermissions = @( - "User.Read", - "User.ReadBasic.All", - "email", - "openid", - "profile" -) - -foreach ($permission in $graphPermissions) { - Write-Host " Adding permission: $permission" -ForegroundColor Gray - $permissionId = az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?value=='$permission'].id" -o tsv - if ($permissionId) { - az ad app permission add ` - --id $appId ` - --api "00000003-0000-0000-c000-000000000000" ` - --api-permissions "${permissionId}=Scope" 2>$null | Out-Null - } -} - -Write-Host "✅ API permissions configured" -ForegroundColor Green -Write-Host "" - -# Create service principal -Write-Host "📝 Creating service principal..." -ForegroundColor Cyan -$spId = az ad sp create --id $appId --query "id" -o tsv 2>$null -if (-not $spId) { - $spId = az ad sp show --id $appId --query "id" -o tsv -} - -Write-Host "✅ Service principal created: $spId" -ForegroundColor Green -Write-Host "" - -# Grant admin consent -Write-Host "📝 Granting admin consent for API permissions..." -ForegroundColor Cyan -$hasAdmin = Read-Host "Do you have admin privileges to grant consent? (y/n)" - -if ($hasAdmin -eq "y") { - az ad app permission admin-consent --id $appId 2>$null - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Admin consent granted" -ForegroundColor Green - } else { - Write-Host "⚠️ Could not grant admin consent. You may need to do this manually." -ForegroundColor Yellow - Write-Host " Go to: Azure Portal → Microsoft Entra ID → App registrations → $AppName → API permissions → Grant admin consent" -ForegroundColor Yellow - } -} else { - Write-Host "⚠️ Skipping admin consent. Please grant consent manually in Azure Portal." -ForegroundColor Yellow - Write-Host " Go to: Azure Portal → Microsoft Entra ID → App registrations → $AppName → API permissions → Grant admin consent" -ForegroundColor Yellow -} - -Write-Host "" - -# Create client secret -Write-Host "📝 Client Secret Configuration..." -ForegroundColor Cyan -$createSecret = Read-Host "Do you want to create a client secret? (y/n)" - -$clientSecret = "" -if ($createSecret -eq "y") { - $secretName = "Miracles In Motion Secret $(Get-Date -Format 'yyyyMMdd')" - $clientSecret = az ad app credential reset --id $appId --display-name $secretName --years 2 --query "password" -o tsv - Write-Host "✅ Client secret created" -ForegroundColor Green - Write-Host "⚠️ IMPORTANT: Save this secret now - it won't be shown again!" -ForegroundColor Red - Write-Host "Secret: $clientSecret" -ForegroundColor Yellow - Write-Host "" - Read-Host "Press Enter to continue after saving the secret..." -} else { - Write-Host "⚠️ Skipping client secret creation" -ForegroundColor Yellow -} - -Write-Host "" - -# Store configuration in Key Vault -Write-Host "📝 Storing configuration in Key Vault..." -ForegroundColor Cyan - -if ([string]::IsNullOrEmpty($KeyVaultName)) { - $KeyVaultName = az keyvault list --resource-group $AzureResourceGroup --query "[0].name" -o tsv 2>$null -} - -if ($KeyVaultName) { - Write-Host "Storing in Key Vault: $KeyVaultName" -ForegroundColor Gray - - az keyvault secret set --vault-name $KeyVaultName --name "azure-client-id" --value $appId 2>$null | Out-Null - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Client ID stored" -ForegroundColor Green - } else { - Write-Host "⚠️ Could not store Client ID" -ForegroundColor Yellow - } - - az keyvault secret set --vault-name $KeyVaultName --name "azure-tenant-id" --value $tenantId 2>$null | Out-Null - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Tenant ID stored" -ForegroundColor Green - } else { - Write-Host "⚠️ Could not store Tenant ID" -ForegroundColor Yellow - } - - if ($clientSecret) { - az keyvault secret set --vault-name $KeyVaultName --name "azure-client-secret" --value $clientSecret 2>$null | Out-Null - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Client Secret stored" -ForegroundColor Green - } else { - Write-Host "⚠️ Could not store Client Secret" -ForegroundColor Yellow - } - } -} else { - Write-Host "⚠️ Key Vault not found. Skipping secret storage." -ForegroundColor Yellow -} - -Write-Host "" - -# Summary -Write-Host "✅ MS Entra Setup Complete!" -ForegroundColor Green -Write-Host "==================================" -ForegroundColor Green -Write-Host "" -Write-Host "Configuration Summary:" -Write-Host " App Registration ID: $appId" -Write-Host " Tenant ID: $tenantId" -Write-Host " Service Principal ID: $spId" -Write-Host "" -Write-Host "Redirect URIs:" -Write-Host " - $productionUrl" -Write-Host " - $wwwUrl" -Write-Host " - $azureStaticWebAppUrl" -Write-Host "" -Write-Host "Next Steps:" -Write-Host "1. Assign users to app roles in Azure Portal" -Write-Host "2. Update staticwebapp.config.json with authentication configuration" -Write-Host "3. Update application code to use Azure AD authentication" -Write-Host "4. Test authentication flow" -Write-Host "" -Write-Host "Azure Portal Links:" -Write-Host " App Registration: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/$appId" -Write-Host " API Permissions: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$appId" -Write-Host "" - -# Export variables -$configContent = @" -# Azure AD Configuration -AZURE_CLIENT_ID=$appId -AZURE_TENANT_ID=$tenantId -AZURE_CLIENT_SECRET=$clientSecret -AZURE_STATIC_WEB_APP_URL=$azureStaticWebAppUrl -AZURE_PRODUCTION_URL=$productionUrl -"@ - -$configContent | Out-File -FilePath ".azure-entra-config.env" -Encoding UTF8 -Write-Host "✅ Configuration saved to .azure-entra-config.env" -ForegroundColor Green -Write-Host "" - diff --git a/scripts/setup-azure-entra.sh b/scripts/setup-azure-entra.sh deleted file mode 100644 index 3892c46..0000000 --- a/scripts/setup-azure-entra.sh +++ /dev/null @@ -1,295 +0,0 @@ -#!/bin/bash - -# MS Entra (Azure AD) Setup Script for Miracles In Motion -# This script helps configure Azure AD authentication for the application - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Configuration -APP_NAME="Miracles In Motion Web App" -DOMAIN="miraclesinmotion.org" -STATIC_WEB_APP_NAME="${STATIC_WEB_APP_NAME:-mim-prod-web}" -AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-rg-miraclesinmotion-prod}" - -echo -e "${GREEN}🔐 MS Entra (Azure AD) Setup Script${NC}" -echo "==========================================" -echo "" - -# Check if Azure CLI is installed -if ! command -v az &> /dev/null; then - echo -e "${RED}❌ Azure CLI not found. Please install it first.${NC}" - echo "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" - exit 1 -fi - -# Check if logged in to Azure -echo -e "${BLUE}📋 Checking Azure login status...${NC}" -CURRENT_USER=$(az account show --query "user.name" -o tsv 2>/dev/null || echo "") -if [ -z "$CURRENT_USER" ]; then - echo -e "${YELLOW}⚠️ Not logged in to Azure. Please log in...${NC}" - az login - CURRENT_USER=$(az account show --query "user.name" -o tsv) -fi -echo -e "${GREEN}✅ Logged in as: $CURRENT_USER${NC}" -echo "" - -# Get Azure Static Web App URL -echo -e "${BLUE}📋 Getting Azure Static Web App information...${NC}" -AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --query "defaultHostname" -o tsv 2>/dev/null || echo "") - -if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then - echo -e "${YELLOW}⚠️ Static Web App not found. Using default URL format.${NC}" - AZURE_STATIC_WEB_APP_URL="${STATIC_WEB_APP_NAME}.azurestaticapps.net" -fi - -FULL_STATIC_WEB_APP_URL="https://${AZURE_STATIC_WEB_APP_URL}" -PRODUCTION_URL="https://${DOMAIN}" -WWW_URL="https://www.${DOMAIN}" - -echo -e "${GREEN}✅ Static Web App URL: $FULL_STATIC_WEB_APP_URL${NC}" -echo "" - -# Get Tenant ID -TENANT_ID=$(az account show --query "tenantId" -o tsv) -echo -e "${GREEN}✅ Tenant ID: $TENANT_ID${NC}" -echo "" - -# Check if app registration already exists -echo -e "${BLUE}🔍 Checking for existing app registration...${NC}" -EXISTING_APP_ID=$(az ad app list --display-name "$APP_NAME" --query "[0].appId" -o tsv 2>/dev/null || echo "") - -if [ -n "$EXISTING_APP_ID" ] && [ "$EXISTING_APP_ID" != "null" ]; then - echo -e "${YELLOW}⚠️ App registration already exists: $EXISTING_APP_ID${NC}" - read -p "Do you want to update it? (y/n): " UPDATE_APP - if [ "$UPDATE_APP" != "y" ]; then - APP_ID=$EXISTING_APP_ID - echo -e "${GREEN}✅ Using existing app registration${NC}" - else - echo -e "${BLUE}📝 Updating app registration...${NC}" - APP_ID=$EXISTING_APP_ID - fi -else - # Create app registration - echo -e "${BLUE}📝 Creating app registration...${NC}" - APP_ID=$(az ad app create \ - --display-name "$APP_NAME" \ - --sign-in-audience "AzureADMultipleOrgs" \ - --web-redirect-uris "$PRODUCTION_URL" "$WWW_URL" "$FULL_STATIC_WEB_APP_URL" \ - --query "appId" -o tsv) - - echo -e "${GREEN}✅ App registration created: $APP_ID${NC}" -fi -echo "" - -# Update redirect URIs -echo -e "${BLUE}📝 Updating redirect URIs...${NC}" -az ad app update --id "$APP_ID" \ - --web-redirect-uris "$PRODUCTION_URL" "$WWW_URL" "$FULL_STATIC_WEB_APP_URL" \ - --enable-id-token-issuance true \ - --enable-access-token-issuance false \ - --query "appId" -o tsv > /dev/null - -echo -e "${GREEN}✅ Redirect URIs updated${NC}" -echo " - $PRODUCTION_URL" -echo " - $WWW_URL" -echo " - $FULL_STATIC_WEB_APP_URL" -echo "" - -# Configure API permissions -echo -e "${BLUE}📝 Configuring API permissions...${NC}" - -# Microsoft Graph permissions -GRAPH_PERMISSIONS=( - "User.Read" - "User.ReadBasic.All" - "email" - "openid" - "profile" -) - -GRAPH_RESOURCE_ID=$(az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "id" -o tsv) - -for PERMISSION in "${GRAPH_PERMISSIONS[@]}"; do - PERMISSION_ID=$(az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?value=='$PERMISSION'].id" -o tsv) - if [ -n "$PERMISSION_ID" ]; then - echo " Adding permission: $PERMISSION" - az ad app permission add \ - --id "$APP_ID" \ - --api "00000003-0000-0000-c000-000000000000" \ - --api-permissions "$PERMISSION_ID=Scope" \ - --query "appId" -o tsv > /dev/null 2>&1 || echo " (may already exist)" - fi -done - -echo -e "${GREEN}✅ API permissions configured${NC}" -echo "" - -# Create app roles -echo -e "${BLUE}📝 Creating app roles...${NC}" - -# Function to create app role -create_app_role() { - local role_name=$1 - local role_value=$2 - local role_description=$3 - - echo " Creating role: $role_name" - - local role_json=$(cat </dev/null || echo "") - - if [ -z "$role_exists" ]; then - # Add new role to existing roles - local updated_roles=$(echo "$existing_roles" | jq ". + [$role_json]") - az ad app update --id "$APP_ID" --app-roles "$updated_roles" > /dev/null 2>&1 - echo " ✅ Role created" - else - echo " ⚠️ Role already exists" - fi -} - -# Create app roles -create_app_role "Admin" "Admin" "Administrator access to all features" -create_app_role "Volunteer" "Volunteer" "Volunteer access to assigned tasks" -create_app_role "Resource" "Resource" "Resource provider access" - -echo -e "${GREEN}✅ App roles created${NC}" -echo "" - -# Create service principal -echo -e "${BLUE}📝 Creating service principal...${NC}" -SP_ID=$(az ad sp create --id "$APP_ID" --query "id" -o tsv 2>/dev/null || \ - az ad sp show --id "$APP_ID" --query "id" -o tsv) - -echo -e "${GREEN}✅ Service principal created: $SP_ID${NC}" -echo "" - -# Grant admin consent (requires admin privileges) -echo -e "${BLUE}📝 Granting admin consent for API permissions...${NC}" -read -p "Do you have admin privileges to grant consent? (y/n): " HAS_ADMIN - -if [ "$HAS_ADMIN" == "y" ]; then - az ad app permission admin-consent --id "$APP_ID" && \ - echo -e "${GREEN}✅ Admin consent granted${NC}" || \ - echo -e "${YELLOW}⚠️ Could not grant admin consent. You may need to do this manually.${NC}" -else - echo -e "${YELLOW}⚠️ Skipping admin consent. Please grant consent manually in Azure Portal.${NC}" - echo " Go to: Azure Portal → Microsoft Entra ID → App registrations → $APP_NAME → API permissions → Grant admin consent" -fi -echo "" - -# Create client secret (optional) -echo -e "${BLUE}📝 Client Secret Configuration...${NC}" -read -p "Do you want to create a client secret? (y/n): " CREATE_SECRET - -if [ "$CREATE_SECRET" == "y" ]; then - SECRET_NAME="Miracles In Motion Secret $(date +%Y%m%d)" - SECRET=$(az ad app credential reset --id "$APP_ID" --display-name "$SECRET_NAME" --years 2 --query "password" -o tsv) - echo -e "${GREEN}✅ Client secret created${NC}" - echo -e "${RED}⚠️ IMPORTANT: Save this secret now - it won't be shown again!${NC}" - echo "Secret: $SECRET" - echo "" - read -p "Press Enter to continue after saving the secret..." -else - echo -e "${YELLOW}⚠️ Skipping client secret creation${NC}" - SECRET="" -fi -echo "" - -# Store configuration in Key Vault (if available) -echo -e "${BLUE}📝 Storing configuration in Key Vault...${NC}" -KEY_VAULT_NAME=$(az keyvault list --resource-group "$AZURE_RESOURCE_GROUP" --query "[0].name" -o tsv 2>/dev/null || echo "") - -if [ -n "$KEY_VAULT_NAME" ]; then - echo "Storing in Key Vault: $KEY_VAULT_NAME" - - az keyvault secret set \ - --vault-name "$KEY_VAULT_NAME" \ - --name "azure-client-id" \ - --value "$APP_ID" > /dev/null 2>&1 && echo -e "${GREEN}✅ Client ID stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Client ID${NC}" - - az keyvault secret set \ - --vault-name "$KEY_VAULT_NAME" \ - --name "azure-tenant-id" \ - --value "$TENANT_ID" > /dev/null 2>&1 && echo -e "${GREEN}✅ Tenant ID stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Tenant ID${NC}" - - if [ -n "$SECRET" ]; then - az keyvault secret set \ - --vault-name "$KEY_VAULT_NAME" \ - --name "azure-client-secret" \ - --value "$SECRET" > /dev/null 2>&1 && echo -e "${GREEN}✅ Client Secret stored${NC}" || echo -e "${YELLOW}⚠️ Could not store Client Secret${NC}" - fi -else - echo -e "${YELLOW}⚠️ Key Vault not found. Skipping secret storage.${NC}" -fi -echo "" - -# Summary -echo -e "${GREEN}✅ MS Entra Setup Complete!${NC}" -echo "==================================" -echo "" -echo "Configuration Summary:" -echo " App Registration ID: $APP_ID" -echo " Tenant ID: $TENANT_ID" -echo " Service Principal ID: $SP_ID" -echo "" -echo "Redirect URIs:" -echo " - $PRODUCTION_URL" -echo " - $WWW_URL" -echo " - $FULL_STATIC_WEB_APP_URL" -echo "" -echo "App Roles:" -echo " - Admin" -echo " - Volunteer" -echo " - Resource" -echo "" -echo "Next Steps:" -echo "1. Assign users to app roles in Azure Portal" -echo "2. Update staticwebapp.config.json with authentication configuration" -echo "3. Update application code to use Azure AD authentication" -echo "4. Test authentication flow" -echo "" -echo "Azure Portal Links:" -echo " App Registration: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/$APP_ID" -echo " API Permissions: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/CallAnAPI/appId/$APP_ID" -echo " App Roles: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/AppRoles/appId/$APP_ID" -echo "" - -# Export variables for use in other scripts -cat > .azure-entra-config.env </dev/null || true - set +a - fi -done - -# Check if credentials are already set in environment -if [ -n "$CLOUDFLARE_API_TOKEN" ] && [ -n "$CLOUDFLARE_ZONE_ID" ]; then - CREDENTIALS_LOADED=true -fi - -# Check if credentials are set -if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ZONE_ID" ]; then - echo -e "${YELLOW}⚠️ Cloudflare credentials not found in env files${NC}" - echo "Checking environment variables..." - - # Final check - maybe they're already exported - if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ZONE_ID" ]; then - echo -e "${RED}❌ Cloudflare credentials not found${NC}" - echo "Please set: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID" - echo "Or add them to .env.production file" - exit 1 - fi -fi - -echo -e "${GREEN}✅ Credentials loaded${NC}" -echo "Zone ID: ${CLOUDFLARE_ZONE_ID:0:15}..." -echo "" - -# Get Azure Static Web App default hostname -echo -e "${GREEN}📋 Getting Azure Static Web App information...${NC}" -AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --query "defaultHostname" -o tsv 2>/dev/null || echo "") - -if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then - echo -e "${RED}❌ Could not find Static Web App${NC}" - exit 1 -fi - -echo -e "${GREEN}✅ Found Static Web App: ${AZURE_STATIC_WEB_APP_URL}${NC}" -echo "" - -# Verify Cloudflare API access -echo -e "${GREEN}🔐 Verifying Cloudflare API access...${NC}" -ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID" \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json") - -ZONE_SUCCESS=$(echo "$ZONE_RESPONSE" | grep -o '"success":true' || echo "") - -if [ -z "$ZONE_SUCCESS" ]; then - echo -e "${RED}❌ Failed to authenticate with Cloudflare API${NC}" - echo "Response: $ZONE_RESPONSE" - exit 1 -fi - -ZONE_NAME=$(echo "$ZONE_RESPONSE" | grep -o '"name":"[^"]*"' | cut -d'"' -f4) -echo -e "${GREEN}✅ Authenticated with Cloudflare${NC}" -echo "Zone: $ZONE_NAME" -echo "" - -# Function to create or update DNS record -create_dns_record() { - local record_type=$1 - local record_name=$2 - local record_content=$3 - local proxy=$4 - - echo -n "Configuring DNS: $record_name.$DOMAIN -> $record_content... " - - # Check if record exists - EXISTING_RECORD=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records?type=$record_type&name=$record_name.$DOMAIN" \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H "Content-Type: application/json") - - RECORD_ID=$(echo "$EXISTING_RECORD" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) - - if [ -n "$RECORD_ID" ] && [ "$RECORD_ID" != "null" ]; then - # Update existing record - DATA=$(cat </dev/null && \ - echo -e "${GREEN}✅ Custom domain $DOMAIN added${NC}" || \ - echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}" - -# For www subdomain -az staticwebapp hostname set \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --hostname "www.$DOMAIN" 2>/dev/null && \ - echo -e "${GREEN}✅ Custom domain www.$DOMAIN added${NC}" || \ - echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}" -echo "" - -# Summary -echo -e "${GREEN}✅ Cloudflare Setup Complete!${NC}" -echo "==================================" -echo "" -echo "Configuration Summary:" -echo " Domain: $DOMAIN" -echo " Static Web App: $AZURE_STATIC_WEB_APP_URL" -echo "" -echo "DNS Records:" -echo " ✅ www.$DOMAIN -> $AZURE_STATIC_WEB_APP_URL (Proxied)" -echo " ✅ $DOMAIN -> $AZURE_STATIC_WEB_APP_URL (Proxied)" -echo "" -echo "Cloudflare Settings:" -echo " ✅ SSL Mode: Full" -echo " ✅ Always Use HTTPS: Enabled" -echo " ✅ Security Level: Medium" -echo " ✅ Browser Integrity Check: Enabled" -echo " ✅ Minification: Enabled (JS, CSS, HTML)" -echo " ✅ Brotli Compression: Enabled" -echo "" -echo "Next Steps:" -echo " 1. Wait for DNS propagation (usually 5-30 minutes)" -echo " 2. Verify SSL certificates are provisioned (1-24 hours)" -echo " 3. Test the website at https://$DOMAIN" -echo " 4. Monitor Cloudflare analytics" -echo "" - diff --git a/scripts/setup-cloudflare.ps1 b/scripts/setup-cloudflare.ps1 deleted file mode 100644 index 2ec176d..0000000 --- a/scripts/setup-cloudflare.ps1 +++ /dev/null @@ -1,290 +0,0 @@ -# Cloudflare Setup Script for Miracles In Motion (PowerShell) -# This script helps configure Cloudflare for the production deployment - -param( - [Parameter(Mandatory=$false)] - [string]$Domain = "mim4u.org", - - [Parameter(Mandatory=$false)] - [string]$StaticWebAppName = "", - - [Parameter(Mandatory=$false)] - [string]$AzureResourceGroup = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$CloudflareApiToken = "", - - [Parameter(Mandatory=$false)] - [string]$CloudflareZoneId = "" -) - -$ErrorActionPreference = "Stop" - -Write-Host "🌐 Cloudflare Setup Script" -ForegroundColor Green -Write-Host "==================================" -ForegroundColor Green -Write-Host "" - -# Check if Azure CLI is installed -if (-not (Get-Command "az" -ErrorAction SilentlyContinue)) { - Write-Host "❌ Azure CLI not found. Please install it first." -ForegroundColor Red - Write-Host "Install from: https://docs.microsoft.com/cli/azure/install-azure-cli" -ForegroundColor Yellow - exit 1 -} - -# Get Azure Static Web App default hostname -Write-Host "📋 Getting Azure Static Web App information..." -ForegroundColor Cyan - -if ([string]::IsNullOrEmpty($StaticWebAppName)) { - # Try to find Static Web App - $swa = az staticwebapp list --resource-group $AzureResourceGroup --output json | ConvertFrom-Json | Select-Object -First 1 - if ($swa) { - $StaticWebAppName = $swa.name - } -} - -if ([string]::IsNullOrEmpty($StaticWebAppName)) { - Write-Host "❌ Static Web App name not specified and could not be found." -ForegroundColor Red - exit 1 -} - -$azureStaticWebAppUrl = az staticwebapp show ` - --name $StaticWebAppName ` - --resource-group $AzureResourceGroup ` - --query "defaultHostname" -o tsv - -if ([string]::IsNullOrEmpty($azureStaticWebAppUrl)) { - Write-Host "❌ Could not find Static Web App." -ForegroundColor Red - exit 1 -} - -Write-Host "✅ Found Static Web App: $azureStaticWebAppUrl" -ForegroundColor Green -Write-Host "" - -# Get Cloudflare API Token -if ([string]::IsNullOrEmpty($CloudflareApiToken)) { - $CloudflareApiToken = Read-Host "Enter your Cloudflare API Token" -} - -if ([string]::IsNullOrEmpty($CloudflareApiToken)) { - Write-Host "❌ Cloudflare API Token is required." -ForegroundColor Red - exit 1 -} - -# Get Cloudflare Zone ID -if ([string]::IsNullOrEmpty($CloudflareZoneId)) { - Write-Host "Looking up Zone ID for $Domain..." -ForegroundColor Cyan - - $headers = @{ - "Authorization" = "Bearer $CloudflareApiToken" - "Content-Type" = "application/json" - } - - $zoneResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones?name=$Domain" -Method Get -Headers $headers - - if ($zoneResponse.success -and $zoneResponse.result.Count -gt 0) { - $CloudflareZoneId = $zoneResponse.result[0].id - Write-Host "✅ Zone ID: $CloudflareZoneId" -ForegroundColor Green - } else { - Write-Host "❌ Could not find Zone ID for $Domain" -ForegroundColor Red - exit 1 - } -} - -Write-Host "" - -# Function to create DNS record -function New-CloudflareDnsRecord { - param( - [string]$RecordType, - [string]$RecordName, - [string]$RecordContent, - [bool]$Proxied = $true - ) - - Write-Host "Creating DNS record: $RecordName.$Domain -> $RecordContent" -ForegroundColor Cyan - - $headers = @{ - "Authorization" = "Bearer $CloudflareApiToken" - "Content-Type" = "application/json" - } - - $body = @{ - type = $RecordType - name = $RecordName - content = $RecordContent - proxied = $Proxied - ttl = 1 - } | ConvertTo-Json - - try { - $response = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/dns_records" -Method Post -Headers $headers -Body $body - - if ($response.success) { - Write-Host "✅ DNS record created successfully" -ForegroundColor Green - return $true - } else { - $errors = $response.errors | ForEach-Object { $_.message } -Join ", " - Write-Host "⚠️ DNS record may already exist or error: $errors" -ForegroundColor Yellow - return $false - } - } catch { - Write-Host "⚠️ Error creating DNS record: $($_.Exception.Message)" -ForegroundColor Yellow - return $false - } -} - -# Create CNAME records -Write-Host "📝 Creating DNS Records..." -ForegroundColor Green -New-CloudflareDnsRecord -RecordType "CNAME" -RecordName "www" -RecordContent $azureStaticWebAppUrl -Proxied $true -New-CloudflareDnsRecord -RecordType "CNAME" -RecordName "@" -RecordContent $azureStaticWebAppUrl -Proxied $true -Write-Host "" - -# Configure SSL/TLS settings -Write-Host "🔒 Configuring SSL/TLS..." -ForegroundColor Green - -$headers = @{ - "Authorization" = "Bearer $CloudflareApiToken" - "Content-Type" = "application/json" -} - -$sslBody = @{ - value = "full" -} | ConvertTo-Json - -try { - $sslResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/ssl" -Method Patch -Headers $headers -Body $sslBody - if ($sslResponse.success) { - Write-Host "✅ SSL mode set to Full" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not update SSL settings: $($_.Exception.Message)" -ForegroundColor Yellow -} - -$httpsBody = @{ - value = "on" -} | ConvertTo-Json - -try { - $httpsResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/always_use_https" -Method Patch -Headers $headers -Body $httpsBody - if ($httpsResponse.success) { - Write-Host "✅ Always Use HTTPS enabled" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not enable Always Use HTTPS: $($_.Exception.Message)" -ForegroundColor Yellow -} - -Write-Host "" - -# Configure Security Settings -Write-Host "🛡️ Configuring Security Settings..." -ForegroundColor Green - -$securityBody = @{ - value = "medium" -} | ConvertTo-Json - -try { - $securityResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/security_level" -Method Patch -Headers $headers -Body $securityBody - if ($securityResponse.success) { - Write-Host "✅ Security level set to Medium" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not update security level: $($_.Exception.Message)" -ForegroundColor Yellow -} - -$browserCheckBody = @{ - value = "on" -} | ConvertTo-Json - -try { - $browserCheckResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/browser_check" -Method Patch -Headers $headers -Body $browserCheckBody - if ($browserCheckResponse.success) { - Write-Host "✅ Browser Integrity Check enabled" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not enable browser check: $($_.Exception.Message)" -ForegroundColor Yellow -} - -Write-Host "" - -# Configure Speed Settings -Write-Host "⚡ Configuring Speed Settings..." -ForegroundColor Green - -$minifyBody = @{ - value = @{ - css = "on" - html = "on" - js = "on" - } -} | ConvertTo-Json -Depth 3 - -try { - $minifyResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/minify" -Method Patch -Headers $headers -Body $minifyBody - if ($minifyResponse.success) { - Write-Host "✅ Minification enabled" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not enable minification: $($_.Exception.Message)" -ForegroundColor Yellow -} - -$brotliBody = @{ - value = "on" -} | ConvertTo-Json - -try { - $brotliResponse = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$CloudflareZoneId/settings/brotli" -Method Patch -Headers $headers -Body $brotliBody - if ($brotliResponse.success) { - Write-Host "✅ Brotli compression enabled" -ForegroundColor Green - } -} catch { - Write-Host "⚠️ Could not enable Brotli: $($_.Exception.Message)" -ForegroundColor Yellow -} - -Write-Host "" - -# Add custom domain to Azure Static Web App -Write-Host "🔗 Adding Custom Domain to Azure Static Web App..." -ForegroundColor Green - -try { - az staticwebapp hostname set ` - --name $StaticWebAppName ` - --resource-group $AzureResourceGroup ` - --hostname $Domain 2>$null | Out-Null - Write-Host "✅ Custom domain $Domain added" -ForegroundColor Green -} catch { - Write-Host "⚠️ Domain may already be added or DNS not ready" -ForegroundColor Yellow -} - -try { - az staticwebapp hostname set ` - --name $StaticWebAppName ` - --resource-group $AzureResourceGroup ` - --hostname "www.$Domain" 2>$null | Out-Null - Write-Host "✅ Custom domain www.$Domain added" -ForegroundColor Green -} catch { - Write-Host "⚠️ Domain may already be added or DNS not ready" -ForegroundColor Yellow -} - -Write-Host "" - -# Summary -Write-Host "✅ Cloudflare Setup Complete!" -ForegroundColor Green -Write-Host "==================================" -ForegroundColor Green -Write-Host "" -Write-Host "Next Steps:" -Write-Host "1. Verify DNS propagation (may take 24-48 hours)" -Write-Host "2. Verify SSL certificates are provisioned" -Write-Host "3. Test the website at https://$Domain" -Write-Host "4. Monitor Cloudflare analytics" -Write-Host "" -Write-Host "DNS Records Created:" -Write-Host " - www.$Domain -> $azureStaticWebAppUrl" -Write-Host " - $Domain -> $azureStaticWebAppUrl" -Write-Host "" -Write-Host "Cloudflare Settings:" -Write-Host " - SSL Mode: Full (strict)" -Write-Host " - Always Use HTTPS: Enabled" -Write-Host " - Security Level: Medium" -Write-Host " - Minification: Enabled" -Write-Host " - Brotli Compression: Enabled" -Write-Host "" - diff --git a/scripts/setup-cloudflare.sh b/scripts/setup-cloudflare.sh deleted file mode 100644 index b5cba1c..0000000 --- a/scripts/setup-cloudflare.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/bin/bash - -# Cloudflare Setup Script for Miracles In Motion -# This script helps configure Cloudflare for the production deployment - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Configuration -DOMAIN="miraclesinmotion.org" -STATIC_WEB_APP_NAME="${STATIC_WEB_APP_NAME:-mim-prod-web}" -AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-rg-miraclesinmotion-prod}" - -echo -e "${GREEN}🌐 Cloudflare Setup Script${NC}" -echo "==================================" -echo "" - -# Check if Cloudflare CLI is installed -if ! command -v cloudflared &> /dev/null; then - echo -e "${YELLOW}⚠️ Cloudflare CLI (cloudflared) not found.${NC}" - echo "Install it from: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/" - echo "" -fi - -# Check if jq is installed -if ! command -v jq &> /dev/null; then - echo -e "${YELLOW}⚠️ jq not found. Installing...${NC}" - # Try to install jq (this may vary by OS) - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - sudo apt-get update && sudo apt-get install -y jq - elif [[ "$OSTYPE" == "darwin"* ]]; then - brew install jq - fi -fi - -# Get Azure Static Web App default hostname -echo -e "${GREEN}📋 Getting Azure Static Web App information...${NC}" -AZURE_STATIC_WEB_APP_URL=$(az staticwebapp show \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --query "defaultHostname" -o tsv 2>/dev/null || echo "") - -if [ -z "$AZURE_STATIC_WEB_APP_URL" ]; then - echo -e "${RED}❌ Could not find Static Web App. Please check the name and resource group.${NC}" - echo "Usage: STATIC_WEB_APP_NAME=your-app-name AZURE_RESOURCE_GROUP=your-rg ./setup-cloudflare.sh" - exit 1 -fi - -echo -e "${GREEN}✅ Found Static Web App: ${AZURE_STATIC_WEB_APP_URL}${NC}" -echo "" - -# Prompt for Cloudflare API credentials -echo -e "${YELLOW}📝 Cloudflare API Configuration${NC}" -echo "You need a Cloudflare API token with the following permissions:" -echo " - Zone:Read, DNS:Edit, SSL:Edit, Page Rules:Edit" -echo "" -read -p "Enter your Cloudflare API Token: " CF_API_TOKEN -read -p "Enter your Cloudflare Zone ID (or we'll look it up): " CF_ZONE_ID - -if [ -z "$CF_ZONE_ID" ]; then - echo "Looking up Zone ID for $DOMAIN..." - CF_ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" | jq -r '.result[0].id') - - if [ -z "$CF_ZONE_ID" ] || [ "$CF_ZONE_ID" == "null" ]; then - echo -e "${RED}❌ Could not find Zone ID for $DOMAIN${NC}" - exit 1 - fi -fi - -echo -e "${GREEN}✅ Zone ID: $CF_ZONE_ID${NC}" -echo "" - -# Function to create DNS record -create_dns_record() { - local record_type=$1 - local record_name=$2 - local record_content=$3 - local proxy=$4 - - echo "Creating DNS record: $record_name.$DOMAIN -> $record_content" - - local data=$(jq -n \ - --arg type "$record_type" \ - --arg name "$record_name" \ - --arg content "$record_content" \ - --argjson proxied "$proxy" \ - '{ - type: $type, - name: $name, - content: $content, - proxied: $proxied, - ttl: 1 - }') - - local response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "$data") - - local success=$(echo "$response" | jq -r '.success') - if [ "$success" == "true" ]; then - echo -e "${GREEN}✅ DNS record created successfully${NC}" - else - local errors=$(echo "$response" | jq -r '.errors[]?.message' | tr '\n' ' ') - echo -e "${YELLOW}⚠️ DNS record may already exist or error: $errors${NC}" - fi -} - -# Create CNAME records -echo -e "${GREEN}📝 Creating DNS Records...${NC}" -create_dns_record "CNAME" "www" "$AZURE_STATIC_WEB_APP_URL" "true" -create_dns_record "CNAME" "@" "$AZURE_STATIC_WEB_APP_URL" "true" -echo "" - -# Configure SSL/TLS settings -echo -e "${GREEN}🔒 Configuring SSL/TLS...${NC}" -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/ssl" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":"full"}' | jq -r '.success' && echo -e "${GREEN}✅ SSL mode set to Full${NC}" || echo -e "${YELLOW}⚠️ Could not update SSL settings${NC}" - -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/always_use_https" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Always Use HTTPS enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable Always Use HTTPS${NC}" -echo "" - -# Create Page Rules -echo -e "${GREEN}📋 Creating Page Rules...${NC}" - -# Rule 1: Force HTTPS -create_page_rule() { - local url=$1 - local settings=$2 - local rule_name=$3 - - local data=$(jq -n \ - --arg url "$url" \ - --argjson settings "$settings" \ - '{ - targets: [ - { - target: "url", - constraint: { - operator: "matches", - value: $url - } - } - ], - actions: $settings, - priority: 1, - status: "active" - }') - - local response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/pagerules" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data "$data") - - local success=$(echo "$response" | jq -r '.success') - if [ "$success" == "true" ]; then - echo -e "${GREEN}✅ Page rule '$rule_name' created${NC}" - else - echo -e "${YELLOW}⚠️ Could not create page rule '$rule_name'${NC}" - fi -} - -# Create page rules -HTTPS_SETTINGS='[{"id": "always_use_https"}]' -create_page_rule "*${DOMAIN}/*" "$HTTPS_SETTINGS" "Force HTTPS" -echo "" - -# Configure Security Settings -echo -e "${GREEN}🛡️ Configuring Security Settings...${NC}" -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/security_level" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":"medium"}' | jq -r '.success' && echo -e "${GREEN}✅ Security level set to Medium${NC}" || echo -e "${YELLOW}⚠️ Could not update security level${NC}" - -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/browser_check" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Browser Integrity Check enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable browser check${NC}" -echo "" - -# Configure Speed Settings -echo -e "${GREEN}⚡ Configuring Speed Settings...${NC}" -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/minify" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":{"css":"on","html":"on","js":"on"}}' | jq -r '.success' && echo -e "${GREEN}✅ Minification enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable minification${NC}" - -curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/settings/brotli" \ - -H "Authorization: Bearer $CF_API_TOKEN" \ - -H "Content-Type: application/json" \ - --data '{"value":"on"}' | jq -r '.success' && echo -e "${GREEN}✅ Brotli compression enabled${NC}" || echo -e "${YELLOW}⚠️ Could not enable Brotli${NC}" -echo "" - -# Add custom domain to Azure Static Web App -echo -e "${GREEN}🔗 Adding Custom Domain to Azure Static Web App...${NC}" -az staticwebapp hostname set \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --hostname "$DOMAIN" 2>/dev/null && echo -e "${GREEN}✅ Custom domain $DOMAIN added${NC}" || echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}" - -az staticwebapp hostname set \ - --name "$STATIC_WEB_APP_NAME" \ - --resource-group "$AZURE_RESOURCE_GROUP" \ - --hostname "www.$DOMAIN" 2>/dev/null && echo -e "${GREEN}✅ Custom domain www.$DOMAIN added${NC}" || echo -e "${YELLOW}⚠️ Domain may already be added or DNS not ready${NC}" -echo "" - -# Summary -echo -e "${GREEN}✅ Cloudflare Setup Complete!${NC}" -echo "==================================" -echo "" -echo "Next Steps:" -echo "1. Verify DNS propagation (may take 24-48 hours)" -echo "2. Verify SSL certificates are provisioned" -echo "3. Test the website at https://$DOMAIN" -echo "4. Monitor Cloudflare analytics" -echo "" -echo "DNS Records Created:" -echo " - www.$DOMAIN -> $AZURE_STATIC_WEB_APP_URL" -echo " - $DOMAIN -> $AZURE_STATIC_WEB_APP_URL" -echo "" -echo "Cloudflare Settings:" -echo " - SSL Mode: Full (strict)" -echo " - Always Use HTTPS: Enabled" -echo " - Security Level: Medium" -echo " - Minification: Enabled" -echo " - Brotli Compression: Enabled" -echo "" - diff --git a/scripts/store-secrets-in-keyvault.ps1 b/scripts/store-secrets-in-keyvault.ps1 deleted file mode 100644 index 8f0e6bd..0000000 --- a/scripts/store-secrets-in-keyvault.ps1 +++ /dev/null @@ -1,197 +0,0 @@ -# Script to store secrets in Azure Key Vault -# This script reads from .env.production and stores secrets in Key Vault - -param( - [Parameter(Mandatory=$false)] - [string]$ResourceGroupName = "rg-miraclesinmotion-prod", - - [Parameter(Mandatory=$false)] - [string]$KeyVaultName = "", - - [Parameter(Mandatory=$false)] - [switch]$CreateKeyVault = $false -) - -$ErrorActionPreference = "Stop" - -Write-Host "🔐 Storing Secrets in Azure Key Vault" -ForegroundColor Green -Write-Host "====================================" -ForegroundColor Green -Write-Host "" - -# Check if logged in to Azure -$account = az account show --output json 2>$null | ConvertFrom-Json -if (-not $account) { - Write-Host "❌ Not logged in to Azure. Please run: az login" -ForegroundColor Red - exit 1 -} - -Write-Host "✅ Logged in to Azure" -ForegroundColor Green -Write-Host " Subscription: $($account.name)" -ForegroundColor Gray -Write-Host "" - -# Get Key Vault name -if ([string]::IsNullOrEmpty($KeyVaultName)) { - $KeyVaultName = az keyvault list --resource-group $ResourceGroupName --output json 2>$null | ConvertFrom-Json | Select-Object -First 1 -ExpandProperty name -} - -if ([string]::IsNullOrEmpty($KeyVaultName)) { - if ($CreateKeyVault) { - $KeyVaultName = "mim-prod-$(Get-Random -Minimum 1000 -Maximum 9999)-kv" - Write-Host "📦 Creating Key Vault: $KeyVaultName" -ForegroundColor Cyan - - az keyvault create ` - --name $KeyVaultName ` - --resource-group $ResourceGroupName ` - --location $(az group show --name $ResourceGroupName --query location -o tsv) ` - --sku standard ` - --enable-rbac-authorization true ` - --enable-soft-delete true ` - --retention-days 90 | Out-Null - - Write-Host "✅ Key Vault created" -ForegroundColor Green - } else { - Write-Host "❌ Key Vault not found. Run with -CreateKeyVault to create one." -ForegroundColor Red - exit 1 - } -} else { - Write-Host "✅ Found Key Vault: $KeyVaultName" -ForegroundColor Green -} - -Write-Host "" - -# Check if .env.production exists -$envFile = ".env.production" -if (-not (Test-Path $envFile)) { - Write-Host "❌ .env.production file not found. Please create it first." -ForegroundColor Red - Write-Host " Run: .\scripts\populate-env.ps1" -ForegroundColor Yellow - exit 1 -} - -Write-Host "📄 Reading secrets from $envFile..." -ForegroundColor Cyan - -# Read .env file and parse key-value pairs -$envContent = Get-Content $envFile -Raw -$secrets = @{} - -# Parse environment variables (simple parser - handles KEY=VALUE format) -$lines = $envContent -split "`n" -foreach ($line in $lines) { - $line = $line.Trim() - if ($line -and -not $line.StartsWith("#") -and $line -match "^([^=]+)=(.*)$") { - $key = $matches[1].Trim() - $value = $matches[2].Trim() - - # Skip empty values and placeholders - if ($value -and $value -notmatch "^(your-|YOUR_|placeholder)" -and $value -ne "") { - $secrets[$key] = $value - } - } -} - -Write-Host "✅ Found $($secrets.Count) secrets to store" -ForegroundColor Green -Write-Host "" - -# Define which secrets to store in Key Vault -$secretsToStore = @( - @{Name="azure-tenant-id"; EnvKey="AZURE_TENANT_ID"; Required=$true}, - @{Name="azure-client-id"; EnvKey="AZURE_CLIENT_ID"; Required=$false}, - @{Name="azure-client-secret"; EnvKey="AZURE_CLIENT_SECRET"; Required=$false}, - @{Name="stripe-publishable-key"; EnvKey="VITE_STRIPE_PUBLISHABLE_KEY"; Required=$false}, - @{Name="stripe-secret-key"; EnvKey="STRIPE_SECRET_KEY"; Required=$false}, - @{Name="stripe-webhook-secret"; EnvKey="STRIPE_WEBHOOK_SECRET"; Required=$false}, - @{Name="cosmos-endpoint"; EnvKey="COSMOS_ENDPOINT"; Required=$false}, - @{Name="cosmos-key"; EnvKey="COSMOS_KEY"; Required=$false}, - @{Name="cosmos-database-name"; EnvKey="COSMOS_DATABASE_NAME"; Required=$false}, - @{Name="app-insights-connection-string"; EnvKey="APPLICATIONINSIGHTS_CONNECTION_STRING"; Required=$false}, - @{Name="signalr-connection-string"; EnvKey="SIGNALR_CONNECTION_STRING"; Required=$false}, - @{Name="cloudflare-zone-id"; EnvKey="CLOUDFLARE_ZONE_ID"; Required=$false}, - @{Name="cloudflare-api-token"; EnvKey="CLOUDFLARE_API_TOKEN"; Required=$false}, - @{Name="salesforce-client-id"; EnvKey="SALESFORCE_CLIENT_ID"; Required=$false}, - @{Name="salesforce-client-secret"; EnvKey="SALESFORCE_CLIENT_SECRET"; Required=$false}, - @{Name="smtp-password"; EnvKey="SMTP_PASSWORD"; Required=$false}, - @{Name="session-secret"; EnvKey="SESSION_SECRET"; Required=$false}, - @{Name="jwt-secret"; EnvKey="JWT_SECRET"; Required=$false}, - @{Name="encryption-key"; EnvKey="ENCRYPTION_KEY"; Required=$false} -) - -$storedCount = 0 -$skippedCount = 0 -$errorCount = 0 - -Write-Host "📦 Storing secrets in Key Vault..." -ForegroundColor Cyan -Write-Host "" - -foreach ($secretDef in $secretsToStore) { - $secretName = $secretDef.Name - $envKey = $secretDef.EnvKey - $required = $secretDef.Required - - if ($secrets.ContainsKey($envKey)) { - $secretValue = $secrets[$envKey] - - try { - Write-Host " Storing: $secretName" -ForegroundColor Gray - az keyvault secret set ` - --vault-name $KeyVaultName ` - --name $secretName ` - --value $secretValue 2>$null | Out-Null - - if ($LASTEXITCODE -eq 0) { - Write-Host " ✅ Stored successfully" -ForegroundColor Green - $storedCount++ - } else { - Write-Host " ⚠️ Failed to store (may need RBAC permissions)" -ForegroundColor Yellow - $errorCount++ - } - } catch { - Write-Host " ❌ Error: $($_.Exception.Message)" -ForegroundColor Red - $errorCount++ - } - } else { - if ($required) { - Write-Host " ⚠️ Missing required secret: $secretName ($envKey)" -ForegroundColor Yellow - $skippedCount++ - } else { - Write-Host " ⏭️ Skipping: $secretName (not in .env file)" -ForegroundColor Gray - $skippedCount++ - } - } -} - -Write-Host "" -Write-Host "📊 Summary:" -ForegroundColor Cyan -Write-Host " ✅ Stored: $storedCount" -ForegroundColor Green -Write-Host " ⏭️ Skipped: $skippedCount" -ForegroundColor Yellow -Write-Host " ❌ Errors: $errorCount" -ForegroundColor $(if ($errorCount -gt 0) { "Red" } else { "Green" }) -Write-Host "" - -# Prompt for additional secrets -Write-Host "💡 Additional Secrets" -ForegroundColor Cyan -Write-Host "You can manually add more secrets using:" -ForegroundColor Gray -Write-Host " az keyvault secret set --vault-name $KeyVaultName --name --value " -ForegroundColor White -Write-Host "" - -# Show how to retrieve secrets -Write-Host "📖 Retrieving Secrets" -ForegroundColor Cyan -Write-Host "To retrieve a secret:" -ForegroundColor Gray -Write-Host " az keyvault secret show --vault-name $KeyVaultName --name --query value -o tsv" -ForegroundColor White -Write-Host "" - -# Show Key Vault URL -$keyVaultUrl = "https://$KeyVaultName.vault.azure.net/" -Write-Host "🔗 Key Vault URL: $keyVaultUrl" -ForegroundColor Cyan -Write-Host "" - -if ($errorCount -gt 0) { - Write-Host "⚠️ Some secrets failed to store. You may need to:" -ForegroundColor Yellow - Write-Host "1. Grant yourself 'Key Vault Secrets Officer' role on the Key Vault" -ForegroundColor White - Write-Host "2. Or use Azure Portal to manually add secrets" -ForegroundColor White - Write-Host "" - Write-Host "Grant role command:" -ForegroundColor Cyan - $currentUser = az ad signed-in-user show --query id -o tsv - Write-Host " az role assignment create --role 'Key Vault Secrets Officer' --assignee $currentUser --scope /subscriptions/$($account.id)/resourceGroups/$ResourceGroupName/providers/Microsoft.KeyVault/vaults/$KeyVaultName" -ForegroundColor White - Write-Host "" -} - -Write-Host "✅ Done!" -ForegroundColor Green - diff --git a/scripts/test-deployment.sh b/scripts/test-deployment.sh deleted file mode 100644 index aa62933..0000000 --- a/scripts/test-deployment.sh +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/bash -# Deployment Testing Script for Miracles in Motion -# Tests all endpoints and verifies deployment status - -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Configuration -STATIC_WEB_APP_URL="https://lemon-water-015cb3010.3.azurestaticapps.net" -FUNCTION_APP_URL="https://mim-prod-igiay4-func.azurewebsites.net" -RESOURCE_GROUP="rg-miraclesinmotion-prod" - -echo -e "${GREEN}🧪 Starting Deployment Tests${NC}" -echo "==================================" -echo "" - -# Test counter -TESTS_PASSED=0 -TESTS_FAILED=0 - -# Function to test endpoint -test_endpoint() { - local url=$1 - local name=$2 - local expected_code=${3:-200} - - echo -n "Testing $name... " - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" || echo "000") - - if [ "$HTTP_CODE" = "$expected_code" ]; then - echo -e "${GREEN}✅ PASS${NC} (HTTP $HTTP_CODE)" - ((TESTS_PASSED++)) - return 0 - else - echo -e "${RED}❌ FAIL${NC} (HTTP $HTTP_CODE, expected $expected_code)" - ((TESTS_FAILED++)) - return 1 - fi -} - -# Test Static Web App -echo -e "${YELLOW}📱 Testing Static Web App${NC}" -test_endpoint "$STATIC_WEB_APP_URL" "Static Web App Homepage" -test_endpoint "$STATIC_WEB_APP_URL/index.html" "Static Web App Index" -test_endpoint "$STATIC_WEB_APP_URL/manifest.webmanifest" "PWA Manifest" 200 - -# Test Function App -echo "" -echo -e "${YELLOW}⚡ Testing Function App${NC}" -test_endpoint "$FUNCTION_APP_URL" "Function App Homepage" -test_endpoint "$FUNCTION_APP_URL/api/health" "Function App Health Check" 200 - -# Test Azure Resources -echo "" -echo -e "${YELLOW}☁️ Testing Azure Resources${NC}" - -# Check Static Web App status -echo -n "Checking Static Web App status... " -SWA_STATUS=$(az staticwebapp show --name mim-prod-igiay4-web --resource-group $RESOURCE_GROUP --query "properties.provisioningState" -o tsv 2>/dev/null || echo "Unknown") -if [ "$SWA_STATUS" = "Succeeded" ] || [ "$SWA_STATUS" = "Ready" ]; then - echo -e "${GREEN}✅ PASS${NC} (Status: $SWA_STATUS)" - ((TESTS_PASSED++)) -else - echo -e "${YELLOW}⚠️ WARNING${NC} (Status: $SWA_STATUS)" -fi - -# Check Function App status -echo -n "Checking Function App status... " -FA_STATUS=$(az functionapp show --name mim-prod-igiay4-func --resource-group $RESOURCE_GROUP --query "state" -o tsv 2>/dev/null || echo "Unknown") -if [ "$FA_STATUS" = "Running" ]; then - echo -e "${GREEN}✅ PASS${NC} (Status: $FA_STATUS)" - ((TESTS_PASSED++)) -else - echo -e "${RED}❌ FAIL${NC} (Status: $FA_STATUS)" - ((TESTS_FAILED++)) -fi - -# Check Key Vault -echo -n "Checking Key Vault... " -KV_EXISTS=$(az keyvault show --name mim-prod-igiay4-kv --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "") -if [ -n "$KV_EXISTS" ]; then - echo -e "${GREEN}✅ PASS${NC}" - ((TESTS_PASSED++)) -else - echo -e "${RED}❌ FAIL${NC}" - ((TESTS_FAILED++)) -fi - -# Check Application Insights -echo -n "Checking Application Insights... " -AI_EXISTS=$(az monitor app-insights component show --app mim-prod-igiay4-appinsights --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "") -if [ -n "$AI_EXISTS" ]; then - echo -e "${GREEN}✅ PASS${NC}" - ((TESTS_PASSED++)) -else - echo -e "${RED}❌ FAIL${NC}" - ((TESTS_FAILED++)) -fi - -# Test SSL/TLS -echo "" -echo -e "${YELLOW}🔒 Testing SSL/TLS${NC}" -echo -n "Testing HTTPS on Static Web App... " -if echo | openssl s_client -connect lemon-water-015cb3010.3.azurestaticapps.net:443 -servername lemon-water-015cb3010.3.azurestaticapps.net 2>/dev/null | grep -q "Verify return code: 0"; then - echo -e "${GREEN}✅ PASS${NC}" - ((TESTS_PASSED++)) -else - echo -e "${YELLOW}⚠️ WARNING${NC} (Could not verify SSL)" -fi - -# Test Performance -echo "" -echo -e "${YELLOW}⚡ Testing Performance${NC}" -echo -n "Testing Static Web App response time... " -RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" --max-time 10 "$STATIC_WEB_APP_URL" || echo "999") -if (( $(echo "$RESPONSE_TIME < 3.0" | bc -l) )); then - echo -e "${GREEN}✅ PASS${NC} (${RESPONSE_TIME}s)" - ((TESTS_PASSED++)) -else - echo -e "${YELLOW}⚠️ WARNING${NC} (${RESPONSE_TIME}s - may be slow)" -fi - -# Summary -echo "" -echo "==================================" -echo -e "${GREEN}Tests Passed: $TESTS_PASSED${NC}" -if [ $TESTS_FAILED -gt 0 ]; then - echo -e "${RED}Tests Failed: $TESTS_FAILED${NC}" - exit 1 -else - echo -e "${GREEN}Tests Failed: $TESTS_FAILED${NC}" - echo "" - echo -e "${GREEN}✅ All tests passed!${NC}" - exit 0 -fi - diff --git a/staticwebapp.config.json b/staticwebapp.config.json deleted file mode 100644 index 8b25e13..0000000 --- a/staticwebapp.config.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "routes": [ - { - "route": "/api/*", - "allowedRoles": ["anonymous", "authenticated"] - }, - { - "route": "/admin/*", - "allowedRoles": ["Admin"] - }, - { - "route": "/volunteer/*", - "allowedRoles": ["Volunteer", "Admin"] - }, - { - "route": "/resource/*", - "allowedRoles": ["Resource", "Admin"] - }, - { - "route": "/portals/*", - "allowedRoles": ["authenticated"] - }, - { - "route": "/analytics/*", - "allowedRoles": ["Admin"] - }, - { - "route": "/ai-portal/*", - "allowedRoles": ["Admin", "Volunteer"] - }, - { - "route": "/*", - "rewrite": "/index.html" - } - ], - "navigationFallback": { - "rewrite": "/index.html", - "exclude": ["/api/*", "/admin/*", "/volunteer/*", "/resource/*"] - }, - "responseOverrides": { - "401": { - "redirect": "/#/portals", - "statusCode": 302 - }, - "403": { - "redirect": "/#/portals", - "statusCode": 302 - } - }, - "auth": { - "identityProviders": { - "azureActiveDirectory": { - "registration": { - "openIdIssuer": "https://login.microsoftonline.com/{tenantId}/v2.0", - "clientIdSettingName": "AZURE_CLIENT_ID", - "clientSecretSettingName": "AZURE_CLIENT_SECRET" - }, - "userDetailsClaim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", - "userDetailsPrincipalName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" - } - } - }, - "globalHeaders": { - "X-Content-Type-Options": "nosniff", - "X-Frame-Options": "DENY", - "X-XSS-Protection": "1; mode=block", - "Referrer-Policy": "strict-origin-when-cross-origin", - "Permissions-Policy": "geolocation=(), microphone=(), camera=()", - "Strict-Transport-Security": "max-age=31536000; includeSubDomains", - "Content-Security-Policy": "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https: blob:; font-src 'self' https: data:; connect-src 'self' https: wss:; media-src 'self' https: data:; object-src 'none'; base-uri 'self'; form-action 'self' https:; frame-ancestors 'none'; upgrade-insecure-requests" - }, - "mimeTypes": { - ".json": "application/json", - ".js": "text/javascript", - ".css": "text/css", - ".svg": "image/svg+xml", - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".ico": "image/x-icon", - ".woff": "font/woff", - ".woff2": "font/woff2", - ".ttf": "font/ttf", - ".eot": "application/vnd.ms-fontobject" - }, - "platform": { - "apiRuntime": "node:22" - }, - "forwardingGateway": { - "allowedForwardedHosts": [ - "mim4u.org", - "www.mim4u.org" - ] - } -} \ No newline at end of file diff --git a/swa-cli.config.json b/swa-cli.config.json deleted file mode 100644 index 7137ca0..0000000 --- a/swa-cli.config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", - "configurations": { - "miracles-in-motion": { - "appLocation": ".", - "apiLocation": "api", - "outputLocation": "dist", - "apiLanguage": "node", - "apiVersion": "20", - "appBuildCommand": "npm run build", - "apiBuildCommand": "npm run build --if-present", - "run": "npm run dev", - "appDevserverUrl": "http://localhost:5173" - } - } -} \ No newline at end of file