commit 326a4aaa2995ec292826825640b2b20b03834296 Author: Interview Assistant Developer Date: Wed Sep 17 15:16:13 2025 +0000 Initial commit: Real-Time Interview Assistant with Authelia Authentication 🎯 Features implemented: - Multi-language speech recognition (EN, FR, ES, DE) - CV upload and parsing with regex-escaped skills extraction - Authelia authentication integration for n8n webhook - Complete n8n workflow for AI question generation - Real-time language switching with enhanced UI - Professional authentication modal with dual login options 🔧 Technical stack: - Angular 18 with standalone components and signals - TypeScript with strict typing and interfaces - Authelia session-based authentication - n8n workflow automation with OpenAI integration - PDF.js for CV text extraction - Web Speech API for voice recognition 🛠️ Infrastructure: - Secure authentication flow with proper error handling - Environment-based configuration for dev/prod - Comprehensive documentation and workflow templates - Clean project structure with proper git ignore rules 🔒 Security features: - Cookie-based session management with CORS - Protected n8n webhooks via Authelia - Graceful fallback to local processing - Secure redirect handling and session persistence 🚀 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f166060 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..0a5d68e --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "root": true, + "ignorePatterns": ["projects/**/*"], + "overrides": [ + { + "files": ["*.ts"], + "parserOptions": { + "project": ["tsconfig.json"], + "createDefaultProgram": true + }, + "extends": [ + "@angular-eslint/recommended", + "@angular-eslint/template/process-inline-templates", + "@typescript-eslint/recommended", + "prettier" + ], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ], + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/explicit-function-return-type": "warn", + "@typescript-eslint/no-explicit-any": "warn" + } + }, + { + "files": ["*.html"], + "extends": ["@angular-eslint/template/recommended"], + "rules": {} + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..102c688 --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db + +# Test files and temporary scripts (not for production) +test-*.js +test-*.html +test-*.ts + +# Environment-specific files +.env +.env.local +.env.production + +# Build artifacts +*.tsbuildinfo + +# Log files +*.log + +# Temporary files +temp/ +tmp/ + +# n8n workflow files in root (keep docs/ versions) +/n8n-*.json +!docs/n8n-*.json + +# Authentication test files +auth-test.* + +# Development test files +/tests diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c2ca384 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..925af83 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a298b5b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe71dfe --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# InterviewAssistant + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.1. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/angular.json b/angular.json new file mode 100644 index 0000000..4096969 --- /dev/null +++ b/angular.json @@ -0,0 +1,149 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "interview-assistant": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.scss" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "interview-assistant:build:production" + }, + "development": { + "buildTarget": "interview-assistant:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular/build:extract-i18n" + }, + "test": { + "builder": "@angular/build:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.scss" + ] + } + }, + "cypress-run": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "interview-assistant:serve" + }, + "configurations": { + "production": { + "devServerTarget": "interview-assistant:serve:production" + } + } + }, + "cypress-open": { + "builder": "@cypress/schematic:cypress", + "options": { + "watch": true, + "headless": false + } + }, + "ct": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "interview-assistant:serve", + "watch": true, + "headless": false, + "testingType": "component" + }, + "configurations": { + "development": { + "devServerTarget": "interview-assistant:serve:development" + } + } + }, + "e2e": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "interview-assistant:serve", + "watch": true, + "headless": false + }, + "configurations": { + "production": { + "devServerTarget": "interview-assistant:serve:production" + } + } + } + } + } + }, + "cli": { + "schematicCollections": [ + "@cypress/schematic", + "@schematics/angular" + ] + } +} \ No newline at end of file diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..4ff5d26 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + + e2e: { + 'baseUrl': 'http://localhost:4200' + }, + + + component: { + devServer: { + framework: 'angular', + bundler: 'webpack', + }, + specPattern: '**/*.cy.ts' + } + +}) \ No newline at end of file diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts new file mode 100644 index 0000000..3e07149 --- /dev/null +++ b/cypress/e2e/spec.cy.ts @@ -0,0 +1,6 @@ +describe('My First Test', () => { + it('Visits the initial project page', () => { + cy.visit('/') + cy.contains('app is running') + }) +}) diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..20b22a1 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} + \ No newline at end of file diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 0000000..af1f44a --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,43 @@ +// *********************************************** +// This example namespace declaration will help +// with Intellisense and code completion in your +// IDE or Text Editor. +// *********************************************** +// declare namespace Cypress { +// interface Chainable { +// customCommand(param: any): typeof customCommand; +// } +// } +// +// function customCommand(param: any): void { +// console.warn(param); +// } +// +// NOTE: You can use it like so: +// Cypress.Commands.add('customCommand', customCommand); +// +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/cypress/support/component-index.html b/cypress/support/component-index.html new file mode 100644 index 0000000..ac6e79f --- /dev/null +++ b/cypress/support/component-index.html @@ -0,0 +1,12 @@ + + + + + + + Components App + + +
+ + \ No newline at end of file diff --git a/cypress/support/component.ts b/cypress/support/component.ts new file mode 100644 index 0000000..96e1d27 --- /dev/null +++ b/cypress/support/component.ts @@ -0,0 +1,39 @@ +// *********************************************************** +// This example support/component.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +import { mount } from 'cypress/angular' + +// Augment the Cypress namespace to include type definitions for +// your custom command. +// Alternatively, can be defined in cypress/support/component.d.ts +// with a at the top of your spec. +declare global { + namespace Cypress { + interface Chainable { + mount: typeof mount + } + } +} + +Cypress.Commands.add('mount', mount) + +// Example use: +// cy.mount(MyComponent) diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 0000000..55540ff --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// When a command from ./commands is ready to use, import with `import './commands'` syntax +// import './commands'; diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 0000000..79d78d7 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.ts"], + "compilerOptions": { + "sourceMap": false, + "types": ["cypress"] + } +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..93e511e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,114 @@ +# Interview Assistant - Documentation + +## n8n Workflow Integration + +### Overview +This directory contains n8n workflow templates for integrating AI-powered question generation with the Interview Assistant application. + +### Files + +#### `n8n-cv-analysis-workflow.json` +Complete n8n workflow for CV analysis and interview question generation. + +**Features:** +- Receives CV data via webhook from Angular app +- Processes CV content for key information extraction +- Generates personalized interview questions using OpenAI/Claude +- Returns structured question bank with categories and difficulty levels +- Includes error handling and fallback questions + +**Setup Instructions:** +1. **Import to n8n:** + - Open your n8n instance + - Go to "Import from File" or use Ctrl+I + - Select `n8n-cv-analysis-workflow.json` + +2. **Configure OpenAI API:** + - Add your OpenAI API key to the "Generate Questions (OpenAI)" node + - Or replace with Claude/other AI provider if preferred + +3. **Test the Webhook:** + - Copy the webhook URL from the imported workflow + - Update Angular environment files if URL differs + +4. **Authentication:** + - Ensure webhook is protected by Authelia (as configured) + - Test authentication flow with Angular app + +### API Reference + +#### Webhook Endpoint +- **URL:** `https://n8n.gm-tech.org/webhook/cv-analysis` +- **Method:** POST +- **Authentication:** Authelia session-based +- **Content-Type:** application/json + +#### Request Format +```json +{ + "analysisId": "uuid-string", + "cvProfile": { + "personalInfo": { + "fullName": "string", + "email": "string" + }, + "skills": [ + { + "name": "string", + "category": "technical|soft|language", + "yearsOfExperience": number + } + ], + "experience": [...], + "education": [...], + "parsedText": "full CV content as string" + } +} +``` + +#### Response Format +```json +{ + "status": "completed|failed|processing", + "analysisId": "uuid-string", + "questionBankId": "generated-id", + "questionsGenerated": number, + "candidateName": "string", + "questions": [ + { + "id": number, + "question": "string", + "category": "technical|behavioral|scenario", + "difficulty": "easy|medium|hard", + "expectedSkills": ["skill1", "skill2"], + "reasoning": "why this question is relevant" + } + ], + "metadata": { + "skillsAnalyzed": number, + "experienceYears": number, + "processingTime": "ISO timestamp" + } +} +``` + +### Security Considerations + +- **Authelia Protection:** All webhooks require valid authentication +- **CORS Configuration:** Properly configured for Angular app origin +- **Data Sanitization:** CV data is processed securely +- **No Data Persistence:** Workflow processes data in memory only +- **Error Handling:** Graceful fallbacks prevent data exposure + +### Troubleshooting + +**Common Issues:** +1. **401 Authentication Error:** Ensure user is logged into Authelia +2. **Workflow Not Found:** Check if workflow is imported and activated +3. **OpenAI API Errors:** Verify API key configuration and rate limits +4. **Webhook Timeout:** Check n8n server performance and AI response times + +**Debug Mode:** +- Enable debug logging in n8n workflow nodes +- Check Angular console for detailed error messages +- Use test scripts provided in project root for debugging \ No newline at end of file diff --git a/docs/n8n-cv-analysis-workflow.json b/docs/n8n-cv-analysis-workflow.json new file mode 100644 index 0000000..b546171 --- /dev/null +++ b/docs/n8n-cv-analysis-workflow.json @@ -0,0 +1,189 @@ +{ + "name": "CV Interview Assistant", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "cv-analysis", + "responseMode": "responseNode", + "options": {} + }, + "id": "f8b4c5d6-7890-1234-5678-90abcdef1234", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [240, 300], + "webhookId": "cv-analysis-webhook" + }, + { + "parameters": { + "jsCode": "// Extract and structure CV data for AI processing\nconst input = $input.first().json;\nconst cvProfile = input.cvProfile || {};\nconst personalInfo = cvProfile.personalInfo || {};\nconst skills = cvProfile.skills || [];\nconst experience = cvProfile.experience || [];\nconst education = cvProfile.education || [];\n\n// Extract key information\nconst candidateName = personalInfo.fullName || 'Candidate';\nconst skillNames = skills.map(s => s.name).filter(Boolean);\nconst technicalSkills = skills.filter(s => s.category === 'technical').map(s => s.name);\nconst yearsExperience = Math.max(...skills.map(s => s.yearsOfExperience || 0), 0);\nconst totalPositions = experience.length;\nconst educationLevel = education.length > 0 ? education[0].degree : 'Not specified';\n\n// Create structured prompt data\nconst promptData = {\n analysisId: input.analysisId,\n candidateName: candidateName,\n email: personalInfo.email || '',\n skills: skillNames,\n technicalSkills: technicalSkills,\n yearsExperience: yearsExperience,\n totalPositions: totalPositions,\n educationLevel: educationLevel,\n fullCvText: cvProfile.parsedText || '',\n experienceSummary: experience.map(exp => `${exp.jobTitle} at ${exp.company} (${exp.duration})`).join('; '),\n skillsSummary: skills.map(s => `${s.name}${s.yearsOfExperience ? ` (${s.yearsOfExperience} years)` : ''}`).join(', ')\n};\n\nreturn [{ json: promptData }];" + }, + "id": "a1b2c3d4-5678-9012-3456-789abcdef012", + "name": "Extract CV Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [460, 300] + }, + { + "parameters": { + "resource": "chat", + "operation": "create", + "model": "gpt-4", + "messages": { + "values": [ + { + "role": "system", + "content": "You are an expert interview question generator. Create relevant, insightful interview questions based on the candidate's CV profile." + }, + { + "role": "user", + "content": "Generate 10 diverse interview questions for this candidate profile:\n\n**Candidate:** {{$json.candidateName}}\n**Email:** {{$json.email}}\n**Experience:** {{$json.yearsExperience}} years total, {{$json.totalPositions}} positions\n**Education:** {{$json.educationLevel}}\n**Technical Skills:** {{$json.technicalSkills.join(', ')}}\n**All Skills:** {{$json.skillsSummary}}\n**Experience Summary:** {{$json.experienceSummary}}\n\n**Full CV Content:**\n{{$json.fullCvText}}\n\n---\n\nGenerate exactly 10 questions covering:\n- 4 Technical questions (specific to their skills/experience)\n- 3 Behavioral questions (leadership, teamwork, problem-solving)\n- 2 Scenario-based questions (hypothetical situations)\n- 1 Career/Growth question\n\nReturn ONLY a valid JSON array with this exact format:\n```json\n[\n {\n \"id\": 1,\n \"question\": \"Detailed question text here\",\n \"category\": \"technical\",\n \"difficulty\": \"medium\",\n \"expectedSkills\": [\"JavaScript\", \"Problem Solving\"],\n \"reasoning\": \"Why this question is relevant to the candidate\"\n }\n]\n```\n\nEnsure questions are:\n- Specific to the candidate's actual experience\n- Appropriate difficulty level\n- Clear and actionable\n- Professional and respectful" + } + ] + }, + "options": { + "temperature": 0.7, + "maxTokens": 2000 + } + }, + "id": "b2c3d4e5-6789-0123-4567-89abcdef0123", + "name": "Generate Questions (OpenAI)", + "type": "n8n-nodes-base.openAi", + "typeVersion": 1.3, + "position": [680, 300] + }, + { + "parameters": { + "jsCode": "// Parse and validate AI response\nconst aiResponse = $input.first().json.choices[0].message.content;\nconst extractData = $('Extract CV Data').first().json;\n\ntry {\n // Extract JSON from AI response (handle markdown code blocks)\n let jsonStr = aiResponse;\n if (aiResponse.includes('```json')) {\n const start = aiResponse.indexOf('```json') + 7;\n const end = aiResponse.lastIndexOf('```');\n jsonStr = aiResponse.substring(start, end).trim();\n }\n \n const questions = JSON.parse(jsonStr);\n \n // Validate and enhance questions\n const validatedQuestions = questions.map((q, index) => ({\n id: q.id || (index + 1),\n question: q.question || 'Question not generated',\n category: q.category || 'general',\n difficulty: q.difficulty || 'medium',\n expectedSkills: Array.isArray(q.expectedSkills) ? q.expectedSkills : [],\n reasoning: q.reasoning || 'Standard interview question',\n generatedAt: new Date().toISOString()\n }));\n \n // Generate question bank ID\n const questionBankId = `qb_${extractData.analysisId}_${Date.now()}`;\n \n return [{\n json: {\n status: 'completed',\n analysisId: extractData.analysisId,\n questionBankId: questionBankId,\n questionsGenerated: validatedQuestions.length,\n candidateName: extractData.candidateName,\n questions: validatedQuestions,\n metadata: {\n skillsAnalyzed: extractData.skills.length,\n experienceYears: extractData.yearsExperience,\n processingTime: new Date().toISOString()\n }\n }\n }];\n \n} catch (error) {\n // Handle parsing errors gracefully\n return [{\n json: {\n status: 'failed',\n analysisId: extractData.analysisId,\n error: 'Failed to generate questions',\n errorDetails: error.message,\n fallbackQuestions: [\n {\n id: 1,\n question: `Tell me about your experience with ${extractData.technicalSkills[0] || 'your main technology stack'}.`,\n category: 'technical',\n difficulty: 'medium',\n expectedSkills: extractData.technicalSkills.slice(0, 2),\n reasoning: 'Fallback technical question'\n },\n {\n id: 2,\n question: 'Describe a challenging project you worked on and how you overcame obstacles.',\n category: 'behavioral',\n difficulty: 'medium',\n expectedSkills: ['Problem Solving', 'Communication'],\n reasoning: 'Fallback behavioral question'\n }\n ]\n }\n }];\n}" + }, + "id": "c3d4e5f6-7890-1234-5678-9abcdef01234", + "name": "Process AI Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [900, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{$json}}", + "options": { + "responseHeaders": { + "entries": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS" + }, + { + "name": "Access-Control-Allow-Headers", + "value": "Content-Type, Authorization" + } + ] + } + } + }, + "id": "d4e5f6g7-8901-2345-6789-abcdef012345", + "name": "Return Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.1, + "position": [1120, 300] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "error-condition", + "leftValue": "={{$json.status}}", + "rightValue": "failed", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "e5f6g7h8-9012-3456-7890-bcdef0123456", + "name": "Check for Errors", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1120, 480] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Extract CV Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract CV Data": { + "main": [ + [ + { + "node": "Generate Questions (OpenAI)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Generate Questions (OpenAI)": { + "main": [ + [ + { + "node": "Process AI Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Process AI Response": { + "main": [ + [ + { + "node": "Return Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [ + { + "id": "interview-assistant", + "name": "Interview Assistant" + } + ], + "triggerCount": 1, + "updatedAt": "2025-09-16T22:00:00.000Z", + "versionId": "1" +} \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..f5e1318 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,60 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution order + random: true + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/interview-assistant'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' }, + { type: 'lcov' } + ], + check: { + global: { + statements: 80, + branches: 80, + functions: 80, + lines: 80 + } + } + }, + reporters: ['progress', 'kjhtml', 'coverage'], + browsers: ['Chrome'], + restartOnFileChange: true, + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: [ + '--no-sandbox', + '--disable-web-security', + '--disable-gpu', + '--remote-debugging-port=9222', + '--headless' + ] + } + } + }); +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..a693e82 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13455 @@ +{ + "name": "interview-assistant", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "interview-assistant", + "version": "0.0.0", + "dependencies": { + "@angular/common": "^20.3.0", + "@angular/compiler": "^20.3.0", + "@angular/core": "^20.3.0", + "@angular/forms": "^20.3.0", + "@angular/platform-browser": "^20.3.0", + "@angular/router": "^20.3.0", + "dexie": "^4.2.0", + "pdfjs-dist": "^5.4.149", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-eslint/eslint-plugin": "^20.3.0", + "@angular-eslint/eslint-plugin-template": "^20.3.0", + "@angular-eslint/template-parser": "^20.3.0", + "@angular/build": "^20.3.1", + "@angular/cli": "^20.3.1", + "@angular/compiler-cli": "^20.3.0", + "@cypress/schematic": "^4.1.2", + "@types/dom-speech-recognition": "^0.0.6", + "@types/jasmine": "~5.1.0", + "@typescript-eslint/eslint-plugin": "^8.44.0", + "@typescript-eslint/parser": "^8.44.0", + "cypress": "^15.2.0", + "eslint": "^9.35.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "jasmine-core": "~5.9.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "prettier": "^3.6.2", + "typescript": "~5.9.2" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", + "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", + "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", + "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", + "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", + "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", + "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", + "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", + "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", + "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", + "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", + "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", + "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", + "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", + "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2003.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.1.tgz", + "integrity": "sha512-PE/yMVv8RZ7nQzGROi0juZo+yMZE2QwyBXc9yFrHIRozuTzTFaMW/9ifCZDVrpicjyHEk3s+7hUVNCcKO/xIIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.1", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.1.tgz", + "integrity": "sha512-TmS69GqBlbTfydn7C4tUKr0mshYSStuCkgruXbvedHFX8+7XBp8wPE+VUzdKnSmKZi6buI4oskDbJ1AdGtNm/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.1.tgz", + "integrity": "sha512-uzMqcgOfcCBiYb+cbMJmgJL2C2d3uYFp6hU2ClYS8kRPXiA9sNVnvLmv4JrYJVLGQDejJtjPGIQrcmq11OQNLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.1", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-20.3.0.tgz", + "integrity": "sha512-QwuNnmRNr/uNj89TxknPbGcs5snX1w7RoJJPNAsfb2QGcHzUTQovS8hqm9kaDZdpUJDPP7jt7B6F0+EjrPAXRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-20.3.0.tgz", + "integrity": "sha512-7ghzGTiExrgTetDQ6IPP5uXSa94Xhtzp2VHCIa58EcUb7oMv06HWZ1Uss3xgFmACsLpN+vayKJIdFiboqaGVRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.3.0", + "@angular-eslint/utils": "20.3.0", + "ts-api-utils": "^2.1.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-20.3.0.tgz", + "integrity": "sha512-WMJDJfybOLCiN4QrOyrLl+Zt5F+A/xoDYMWTdn+LgACheLs2tguVQiwf+oCgHnHGcsTsulPYlRHldKBGZMgs4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.3.0", + "@angular-eslint/utils": "20.3.0", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@angular-eslint/template-parser": "20.3.0", + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-20.3.0.tgz", + "integrity": "sha512-gB564h/kZ7siWvgHDETU++sk5e25qFfVaizLaa6KoBEYFP6dOCiedz15LTcA0TsXp0rGu6Z6zkl291iSM1qzDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.3.0", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-20.3.0.tgz", + "integrity": "sha512-7XOQeNXgyhznDwoP1TwPrCMq/uXKJHQgCVPFREkJGKbNf/jzNldB7iV1eqpBzUQIPEQFgfcDG67dexpMAq3N4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.3.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/build": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.1.tgz", + "integrity": "sha512-z5n8WnisyPrRvS1WctdDB3Svas0Wql1Eplnwh4O7waZHeJTOcd8zZeFxPbPGp12ybGf3HEEjTeWOigm1kRgW9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2003.1", + "@babel/core": "7.28.3", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.14", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", + "browserslist": "^4.23.0", + "esbuild": "0.25.9", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "magic-string": "0.30.17", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", + "piscina": "5.1.3", + "rolldown": "1.0.0-beta.32", + "sass": "1.90.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.14", + "vite": "7.1.5", + "watchpack": "2.4.4" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.4.2" + }, + "peerDependencies": { + "@angular/compiler": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.3.1", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^20.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.8 <6.0", + "vitest": "^3.1.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular/cli": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.1.tgz", + "integrity": "sha512-TqhuDecbfAQgRDYPfpRQG9ZuTqb1DOeU7oQAYxpz9m/a7A2xqeNFLuCwwz8rqEPZB79/9r5ja0Gs1J4i080U0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2003.1", + "@angular-devkit/core": "20.3.1", + "@angular-devkit/schematics": "20.3.1", + "@inquirer/prompts": "7.8.2", + "@listr2/prompt-adapter-inquirer": "3.0.1", + "@modelcontextprotocol/sdk": "1.17.3", + "@schematics/angular": "20.3.1", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.35.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.1", + "npm-package-arg": "13.0.0", + "pacote": "21.0.0", + "resolve": "1.22.10", + "semver": "7.7.2", + "yargs": "18.0.0", + "zod": "3.25.76" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.0.tgz", + "integrity": "sha512-Il0HqdRdrmI8ufLXd49EYaa/BPqfiSqe5uuKrDxhkAdbRXwCXWsxbO/n8AwilwWn3CKLOCrEXQYKwbcFW0nYQQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.0.tgz", + "integrity": "sha512-DvGDusjsDhxIX+nDzihSCGo81Fa8y94KB/bh24eyPwJWV6b0OkawFSvVwzxx8prV0UnNkCN1S/UoZXmtVZGJ4A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.0.tgz", + "integrity": "sha512-umnZzzKw9RqDVkotYIyupJiKXQpU8knehMUBT1G3QwdeHppC+d/opxISYTkQtY/4IUAsZFLMukWIr82as0DSmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.0", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.0.tgz", + "integrity": "sha512-4uH2TAMm1nXqQ9lcZyyNkjcdQ0Fjcf9Hh0HYrhMOEV6GAUHvM2I8Vr2dSQ40p/UKLEfe9+cpZ78EPocqPQCG6A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.0.tgz", + "integrity": "sha512-/KGCZUskk8imxz2e47CKe5Ykh3eqEDop0b9YUkZTvJ/dY/cdFK89RAK2xUvOlyUr2mkcByzdzyOhHaM9XEaELg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.0", + "@angular/core": "20.3.0", + "@angular/platform-browser": "20.3.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.0.tgz", + "integrity": "sha512-/KsgfxDwP7/KXGrLLSyg4+Xd8HxmHi5dVCu+xHfa3QjzVIvvZfWZLxQj7guRlDtg/mz+t0/OSKvSUZzOAfVzGQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "20.3.0", + "@angular/common": "20.3.0", + "@angular/core": "20.3.0" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/router": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.0.tgz", + "integrity": "sha512-JshumajvPCMztz1+7r/l5tRxFL3cn2jCpr5szdc5hESkpytY4050hedd09GogL1UoIyZAjhyYLhSlMnvrgjHBA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.0", + "@angular/core": "20.3.0", + "@angular/platform-browser": "20.3.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@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.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "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/core/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/@babel/core/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/@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-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "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-compilation-targets/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/@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-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "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/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/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@cypress/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@cypress/schematic": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-4.1.2.tgz", + "integrity": "sha512-sp7LaT4nNjfLnL/DB3+zJzrxDU0H9A57/DlwnZyZZLiyjwYODZi+eGaQZKXpmTx72VBORrhdm8471xF7xLD6qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.3.1", + "rxjs": "~7.8.2" + }, + "peerDependencies": { + "@angular/cli": ">=18.0.0", + "@angular/core": ">=18.0.0" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.0.tgz", + "integrity": "sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.4.tgz", + "integrity": "sha512-2n9Vgf4HSciFq8ttKXk+qy+GsyTXPV1An6QAwe/8bkbbqvG4VW1I/ZY1pNu2rf+h9bdzMLPbRSfcNxkHBy/Ydw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.0", + "@inquirer/core": "^10.2.2", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", + "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.0", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.20.tgz", + "integrity": "sha512-7omh5y5bK672Q+Brk4HBbnHNowOZwrb/78IFXdrEB9PfdxL3GudQyDk8O9vQ188wj3xrEebS2M9n18BjJoI83g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/external-editor": "^1.0.2", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.20.tgz", + "integrity": "sha512-Dt9S+6qUg94fEvgn54F2Syf0Z3U8xmnBI9ATq2f5h9xt09fs2IJXSCIXyyVHwvggKWFXEY/7jATRo2K6Dkn6Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", + "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.4.tgz", + "integrity": "sha512-cwSGpLBMwpwcZZsc6s1gThm0J+it/KIJ+1qFL2euLmSKUMGumJ5TcbMgxEjMjNHRGadouIYbiIgruKoDZk7klw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.20.tgz", + "integrity": "sha512-bbooay64VD1Z6uMfNehED2A2YOPHSJnQLs9/4WNiV/EK+vXczf/R988itL2XLDGTgmhMF2KkiWZo+iEZmc4jqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.20.tgz", + "integrity": "sha512-nxSaPV2cPvvoOmRygQR+h0B+Av73B01cqYLcr7NXcGXhbmsYfUb8fDdw2Us1bI2YsX+VvY7I7upgFYsyf8+Nug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.0", + "@inquirer/core": "^10.2.2", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", + "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.2.1", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.17", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.8.tgz", + "integrity": "sha512-CQ2VkIASbgI2PxdzlkeeieLRmniaUU1Aoi5ggEdm6BIyqopE9GuDXdDOj9XiwOqK5qm72oI2i6J+Gnjaa26ejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.3.tgz", + "integrity": "sha512-D5T6ioybJJH0IiSUK/JXcoRrrm8sXwzrVMjibuPs+AgxmogKslaafy1oxFiorNI4s3ElSkeQZbhYQgLqiL8h6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.2.2", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.4.tgz", + "integrity": "sha512-Qp20nySRmfbuJBBsgPU7E/cL62Hf250vMZRzYDcBHty2zdD1kKCnoDFWRr0WO2ZzaXp3R7a4esaVGJUx0E6zvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.0", + "@inquirer/core": "^10.2.2", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/@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/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/@listr2/prompt-adapter-inquirer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", + "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.1" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", + "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", + "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", + "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", + "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", + "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", + "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", + "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", + "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", + "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", + "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.81.0.tgz", + "integrity": "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.81.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.81.0.tgz", + "integrity": "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.32.tgz", + "integrity": "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.32.tgz", + "integrity": "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.32.tgz", + "integrity": "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.32.tgz", + "integrity": "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.32.tgz", + "integrity": "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.32.tgz", + "integrity": "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.32.tgz", + "integrity": "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", + "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", + "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", + "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", + "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", + "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", + "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", + "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", + "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", + "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", + "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", + "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", + "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", + "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", + "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", + "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", + "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", + "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", + "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", + "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", + "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", + "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", + "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.1.tgz", + "integrity": "sha512-v2SNPaEHuMZyL85tYEQeFJvf7cFxSzXHbotcCrXRBuK3RSAvYXxWlpuBU+jGfZq2FjFZ+G7nHJZLAA/a1UqAvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.1", + "@angular-devkit/schematics": "20.3.1", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", + "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", + "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", + "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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/dom-speech-recognition": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.6.tgz", + "integrity": "sha512-o7pAVq9UQPJL5RDjO1f/fcpfFHdgiMnR4PoIU2N/ZQrYOS3C5rzdOJMsrpqeBCbii2EE9mERXgqspQqPDdPahw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jasmine": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.9.tgz", + "integrity": "sha512-8t4HtkW4wxiPVedMpeZ63n3vlWxEIquo/zc1Tm8ElU+SqVV7+D3Na2PWaJUp179AzTragMWVwkMv7mvty0NfyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz", + "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", + "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/type-utils": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", + "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", + "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.0", + "@typescript-eslint/types": "^8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", + "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", + "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", + "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", + "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.0", + "@typescript-eslint/tsconfig-utils": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", + "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", + "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/algoliasearch": { + "version": "5.35.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", + "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.1.0", + "@algolia/client-abtesting": "5.35.0", + "@algolia/client-analytics": "5.35.0", + "@algolia/client-common": "5.35.0", + "@algolia/client-insights": "5.35.0", + "@algolia/client-personalization": "5.35.0", + "@algolia/client-query-suggestions": "5.35.0", + "@algolia/client-search": "5.35.0", + "@algolia/ingestion": "1.35.0", + "@algolia/monitoring": "1.35.0", + "@algolia/recommend": "5.35.0", + "@algolia/requester-browser-xhr": "5.35.0", + "@algolia/requester-fetch": "5.35.0", + "@algolia/requester-node-http": "5.35.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.0.tgz", + "integrity": "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "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/anymatch/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/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "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/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz", + "integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/beasties": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "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.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", + "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "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": { + "baseline-browser-mapping": "^2.8.2", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "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/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/caniuse-lite": { + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "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/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, + "node_modules/cli-table3/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/cli-table3/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/cli-table3/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/cli-table3/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/cli-table3/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/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cypress": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.2.0.tgz", + "integrity": "sha512-J4ehSzOSb58SkXyldCe9y/oZ8ep8Bl6+q9kDUjnkqNqc2ZKzDq5KSbhIc2lHFAFR5Jtj10oNqr9JRAZbr8DA8A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.9", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "ci-info": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-table3": "0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "hasha": "5.2.2", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.7.1", + "supports-color": "^8.1.1", + "systeminformation": "5.27.7", + "tmp": "~0.2.4", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/cypress/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/cypress/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/cypress/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/cypress/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/cypress/node_modules/chalk/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/cypress/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/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/cypress/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/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/cypress/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/cypress/node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/cypress/node_modules/listr2/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/cypress/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cypress/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/cypress/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/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/cypress/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/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/cypress/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/cypress/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/cypress/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/cypress/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dexie": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.0.tgz", + "integrity": "sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==", + "license": "Apache-2.0" + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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==", + "dev": true, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.218", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", + "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/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/enquirer/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/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "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-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/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/eslint/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/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/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/execa/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/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "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-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "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/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.0.tgz", + "integrity": "sha512-gEf705MZLrDPkbbhi8PnoO4ZwYgKoNL+ISZ3AjZMht2r3N5tuTwncyDi6Fv2/qDnMmZxgs0yI8WDOyR8q3G+SQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "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/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "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": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "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/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "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/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, + "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-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-lib-source-maps/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/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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz", + "integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==", + "dev": true, + "license": "MIT" + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "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/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/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/karma-coverage/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/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/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/karma/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/karma/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/karma/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/karma/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/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/karma/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/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/karma/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/karma/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/karma/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/karma/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/karma/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/karma/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/karma/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/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "> 0.8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/listr2": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", + "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", + "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.4.2", + "@lmdb/lmdb-darwin-x64": "3.4.2", + "@lmdb/lmdb-linux-arm": "3.4.2", + "@lmdb/lmdb-linux-arm64": "3.4.2", + "@lmdb/lmdb-linux-x64": "3.4.2", + "@lmdb/lmdb-win32-arm64": "3.4.2", + "@lmdb/lmdb-win32-x64": "3.4.2" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.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/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "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-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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/micromatch/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/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.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/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-gyp": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.4.2.tgz", + "integrity": "sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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-bundled": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-install-checks": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", + "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.1.tgz", + "integrity": "sha512-vaC03b2PqJA6QqmwHi1jNU8fAPXEnnyv4j/W4PVfgm24C4/zZGSVut3z0YUeN0WIFCo1oGOL02+6LbvFK7JL4Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.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/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordered-binary": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", + "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "license": "MIT" + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", + "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^10.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/pacote/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pacote/node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.149", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.149.tgz", + "integrity": "sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.77" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "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": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", + "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "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/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "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-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz", + "integrity": "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "=0.81.0", + "@oxc-project/types": "=0.81.0", + "@rolldown/pluginutils": "1.0.0-beta.32", + "ansis": "^4.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", + "@rolldown/binding-darwin-x64": "1.0.0-beta.32", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" + } + }, + "node_modules/rollup": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", + "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.2", + "@rollup/rollup-android-arm64": "4.50.2", + "@rollup/rollup-darwin-arm64": "4.50.2", + "@rollup/rollup-darwin-x64": "4.50.2", + "@rollup/rollup-freebsd-arm64": "4.50.2", + "@rollup/rollup-freebsd-x64": "4.50.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", + "@rollup/rollup-linux-arm-musleabihf": "4.50.2", + "@rollup/rollup-linux-arm64-gnu": "4.50.2", + "@rollup/rollup-linux-arm64-musl": "4.50.2", + "@rollup/rollup-linux-loong64-gnu": "4.50.2", + "@rollup/rollup-linux-ppc64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-musl": "4.50.2", + "@rollup/rollup-linux-s390x-gnu": "4.50.2", + "@rollup/rollup-linux-x64-gnu": "4.50.2", + "@rollup/rollup-linux-x64-musl": "4.50.2", + "@rollup/rollup-openharmony-arm64": "4.50.2", + "@rollup/rollup-win32-arm64-msvc": "4.50.2", + "@rollup/rollup-win32-ia32-msvc": "4.50.2", + "@rollup/rollup-win32-x64-msvc": "4.50.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "dev": true, + "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/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "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/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "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/string-width-cjs/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/string-width-cjs/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/string-width-cjs/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/string-width-cjs/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-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "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-ansi-cjs/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/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/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/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/systeminformation": { + "version": "5.27.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", + "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", + "dev": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "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/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/tuf-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", + "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vite": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/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/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "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": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "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" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7fd98c --- /dev/null +++ b/package.json @@ -0,0 +1,65 @@ +{ + "name": "interview-assistant", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "e2e": "ng e2e", + "cypress:open": "cypress open", + "cypress:run": "cypress run" + }, + "prettier": { + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] + }, + "private": true, + "dependencies": { + "@angular/common": "^20.3.0", + "@angular/compiler": "^20.3.0", + "@angular/core": "^20.3.0", + "@angular/forms": "^20.3.0", + "@angular/platform-browser": "^20.3.0", + "@angular/router": "^20.3.0", + "dexie": "^4.2.0", + "pdfjs-dist": "^5.4.149", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-eslint/eslint-plugin": "^20.3.0", + "@angular-eslint/eslint-plugin-template": "^20.3.0", + "@angular-eslint/template-parser": "^20.3.0", + "@angular/build": "^20.3.1", + "@angular/cli": "^20.3.1", + "@angular/compiler-cli": "^20.3.0", + "@cypress/schematic": "^4.1.2", + "@types/dom-speech-recognition": "^0.0.6", + "@types/jasmine": "~5.1.0", + "@typescript-eslint/eslint-plugin": "^8.44.0", + "@typescript-eslint/parser": "^8.44.0", + "cypress": "^15.2.0", + "eslint": "^9.35.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "jasmine-core": "~5.9.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "prettier": "^3.6.2", + "typescript": "~5.9.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..57614f9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000..997b610 --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,14 @@ +import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideHttpClient(withInterceptorsFromDi()) + ] +}; diff --git a/src/app/app.html b/src/app/app.html new file mode 100644 index 0000000..22e0768 --- /dev/null +++ b/src/app/app.html @@ -0,0 +1,45 @@ + +
+ +
+
+

🎤 {{ title() }}

+ + + +
+ +
+
+
+ + +
+ +
+ + +
+ +
+
\ No newline at end of file diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts new file mode 100644 index 0000000..918977a --- /dev/null +++ b/src/app/app.routes.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; +import { CVUploadComponent } from './components/cv-upload/cv-upload.component'; +import { InterviewSessionComponent } from './components/interview-session/interview-session.component'; + +export const routes: Routes = [ + { path: '', redirectTo: '/upload', pathMatch: 'full' }, + { path: 'upload', component: CVUploadComponent }, + { path: 'interview', component: InterviewSessionComponent }, + { path: '**', redirectTo: '/upload' } +]; diff --git a/src/app/app.scss b/src/app/app.scss new file mode 100644 index 0000000..b528ff9 --- /dev/null +++ b/src/app/app.scss @@ -0,0 +1,315 @@ +// Global styles for the Interview Assistant application +.app-container { + min-height: 100vh; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: #f8f9fa; +} + +// Header styles +.app-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + position: sticky; + top: 0; + z-index: 1000; + + .header-content { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; + + .app-title { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + white-space: nowrap; + } + + .app-nav { + display: flex; + gap: 1.5rem; + + .nav-link { + color: rgba(255, 255, 255, 0.9); + text-decoration: none; + padding: 0.5rem 1rem; + border-radius: 6px; + transition: all 0.3s ease; + font-weight: 500; + white-space: nowrap; + + &:hover { + background: rgba(255, 255, 255, 0.1); + color: white; + } + + &.active { + background: rgba(255, 255, 255, 0.2); + color: white; + font-weight: 600; + } + } + } + + .header-actions { + .btn { + padding: 0.5rem 1rem; + border: 1px solid rgba(255, 255, 255, 0.3); + background: transparent; + color: white; + border-radius: 6px; + cursor: pointer; + font-weight: 500; + transition: all 0.3s ease; + white-space: nowrap; + + &.btn-outline { + &:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.5); + } + } + } + } + } +} + +// Main content area +.app-main { + flex: 1; + padding: 2rem 0; + background-color: #f8f9fa; + min-height: calc(100vh - 120px); // Account for header and footer +} + +// Footer styles +.app-footer { + background: #343a40; + color: #dee2e6; + padding: 2rem 0; + margin-top: auto; + + .footer-content { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + display: flex; + justify-content: space-between; + align-items: center; + + p { + margin: 0; + font-size: 0.9rem; + } + + .footer-links { + display: flex; + gap: 1.5rem; + + .footer-link { + color: #adb5bd; + text-decoration: none; + font-size: 0.9rem; + transition: color 0.3s ease; + + &:hover { + color: #dee2e6; + } + } + } + } +} + +// Responsive design +@media (max-width: 768px) { + .app-header .header-content { + flex-direction: column; + gap: 1rem; + padding: 1rem; + + .app-nav { + order: 2; + width: 100%; + justify-content: center; + } + + .header-actions { + order: 3; + width: 100%; + text-align: center; + } + } + + .app-main { + padding: 1rem 0; + } + + .app-footer .footer-content { + flex-direction: column; + gap: 1rem; + text-align: center; + padding: 0 1rem; + } +} + +@media (max-width: 480px) { + .app-header .header-content { + .app-title { + font-size: 1.25rem; + } + + .app-nav { + flex-direction: column; + gap: 0.5rem; + + .nav-link { + text-align: center; + padding: 0.75rem; + } + } + } + + .app-footer .footer-content .footer-links { + flex-direction: column; + gap: 0.75rem; + } +} + +// Global utility classes +.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + text-align: center; + text-decoration: none; + transition: all 0.3s ease; + line-height: 1.5; + + &.btn-primary { + background-color: #007bff; + color: white; + + &:hover { + background-color: #0056b3; + transform: translateY(-1px); + } + } + + &.btn-success { + background-color: #28a745; + color: white; + + &:hover { + background-color: #1e7e34; + transform: translateY(-1px); + } + } + + &.btn-danger { + background-color: #dc3545; + color: white; + + &:hover { + background-color: #c82333; + transform: translateY(-1px); + } + } + + &.btn-secondary { + background-color: #6c757d; + color: white; + + &:hover { + background-color: #545b62; + transform: translateY(-1px); + } + } + + &.btn-outline { + background-color: transparent; + border: 1px solid #6c757d; + color: #6c757d; + + &:hover { + background-color: #6c757d; + color: white; + } + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none !important; + } +} + +// Container utility +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +// Text utilities +.text-center { + text-align: center; +} + +.text-muted { + color: #6c757d; +} + +// Spacing utilities +.mt-1 { margin-top: 0.25rem; } +.mt-2 { margin-top: 0.5rem; } +.mt-3 { margin-top: 1rem; } +.mt-4 { margin-top: 1.5rem; } +.mt-5 { margin-top: 3rem; } + +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 1rem; } +.mb-4 { margin-bottom: 1.5rem; } +.mb-5 { margin-bottom: 3rem; } + +.p-1 { padding: 0.25rem; } +.p-2 { padding: 0.5rem; } +.p-3 { padding: 1rem; } +.p-4 { padding: 1.5rem; } +.p-5 { padding: 3rem; } + +// Animation utilities +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in { + animation: fadeIn 0.3s ease-in; +} + +.slide-up { + animation: slideUp 0.3s ease-out; +} \ No newline at end of file diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts new file mode 100644 index 0000000..ab9e4aa --- /dev/null +++ b/src/app/app.spec.ts @@ -0,0 +1,23 @@ +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, interview-assistant'); + }); +}); diff --git a/src/app/app.ts b/src/app/app.ts new file mode 100644 index 0000000..7a1d059 --- /dev/null +++ b/src/app/app.ts @@ -0,0 +1,24 @@ +import { Component, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterOutlet, RouterLink, RouterLinkActive, Router } from '@angular/router'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive], + templateUrl: './app.html', + styleUrl: './app.scss' +}) +export class App { + protected readonly title = signal('Interview Assistant'); + + constructor(private router: Router) {} + + navigateToUpload() { + this.router.navigate(['/upload']); + } + + navigateToInterview() { + this.router.navigate(['/interview']); + } +} diff --git a/src/app/components/auth-login/auth-login.component.ts b/src/app/components/auth-login/auth-login.component.ts new file mode 100644 index 0000000..2a206af --- /dev/null +++ b/src/app/components/auth-login/auth-login.component.ts @@ -0,0 +1,350 @@ +import { Component, Output, EventEmitter, computed, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { AutheliaAuthService } from '../../services/authelia-auth.service'; +import { environment } from '../../../environments/environment'; + +@Component({ + selector: 'app-auth-login', + standalone: true, + imports: [CommonModule, FormsModule], + template: ` + + `, + styles: [` + .auth-login-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(4px); + } + + .auth-login-container.hidden { + display: none; + } + + .auth-login-modal { + background: var(--background-color, #1a1a1a); + border: 1px solid var(--border-color, #333); + border-radius: 12px; + padding: 24px; + min-width: 400px; + max-width: 500px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + } + + .auth-header { + text-align: center; + margin-bottom: 24px; + } + + .auth-header h3 { + color: var(--text-color, #ffffff); + margin: 0 0 8px 0; + font-size: 1.25rem; + } + + .auth-header p { + color: var(--text-secondary, #cccccc); + margin: 0; + font-size: 0.9rem; + } + + .auth-form { + margin-bottom: 20px; + } + + .auth-form.loading { + opacity: 0.7; + pointer-events: none; + } + + .form-group { + margin-bottom: 16px; + } + + .form-group label { + display: block; + color: var(--text-color, #ffffff); + margin-bottom: 6px; + font-weight: 500; + font-size: 0.9rem; + } + + .form-group input { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border-color, #333); + border-radius: 6px; + background: var(--input-background, #2a2a2a); + color: var(--text-color, #ffffff); + font-size: 14px; + box-sizing: border-box; + } + + .form-group input:focus { + outline: none; + border-color: var(--accent-color, #4CAF50); + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); + } + + .form-group input:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .error-message { + background: rgba(244, 67, 54, 0.1); + border: 1px solid #f44336; + color: #f44336; + padding: 10px 12px; + border-radius: 6px; + margin-bottom: 16px; + font-size: 0.9rem; + } + + .form-actions { + display: flex; + flex-direction: column; + gap: 10px; + } + + .form-actions button { + padding: 12px 20px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + } + + .login-btn { + background: var(--accent-color, #4CAF50); + color: white; + } + + .login-btn:hover:not(:disabled) { + background: var(--accent-hover, #45a049); + transform: translateY(-1px); + } + + .login-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + } + + .redirect-btn { + background: var(--secondary-color, #2196F3); + color: white; + } + + .redirect-btn:hover:not(:disabled) { + background: var(--secondary-hover, #1976D2); + transform: translateY(-1px); + } + + .cancel-btn { + background: var(--danger-color, #f44336); + color: white; + } + + .cancel-btn:hover:not(:disabled) { + background: var(--danger-hover, #d32f2f); + transform: translateY(-1px); + } + + .spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 1s ease-in-out infinite; + } + + @keyframes spin { + to { transform: rotate(360deg); } + } + + .auth-info { + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; + padding: 12px; + font-size: 0.85rem; + } + + .auth-info p { + margin: 4px 0; + color: var(--text-secondary, #cccccc); + } + + .auth-info strong { + color: var(--text-color, #ffffff); + } + `] +}) +export class AuthLoginComponent { + @Output() loginSuccess = new EventEmitter(); + @Output() loginCancelled = new EventEmitter(); + + public username = ''; + public password = ''; + public showLogin = signal(false); + public isLoading = signal(false); + public errorMessage = signal(''); + + public authBaseUrl = environment.authelia?.baseUrl || 'https://auth.gm-tech.org'; + + constructor(private autheliaAuth: AutheliaAuthService) {} + + public show(): void { + this.showLogin.set(true); + this.clearForm(); + } + + public hide(): void { + this.showLogin.set(false); + this.clearForm(); + } + + public onLogin(): void { + if (!this.username || !this.password) { + this.errorMessage.set('Please enter both username and password'); + return; + } + + this.isLoading.set(true); + this.errorMessage.set(''); + + console.log('🔐 Attempting login with Authelia...'); + + this.autheliaAuth.login(this.username, this.password, window.location.origin).subscribe({ + next: (response) => { + this.isLoading.set(false); + + if (response.status === 'OK') { + console.log('✅ Login successful'); + this.hide(); + this.loginSuccess.emit(); + } else { + console.log('❌ Login failed:', response.message); + this.errorMessage.set(response.message || 'Login failed'); + } + }, + error: (error) => { + this.isLoading.set(false); + console.error('❌ Login error:', error); + this.errorMessage.set('Login failed. Please check your credentials.'); + } + }); + } + + public redirectToAuthelia(): void { + console.log('🌐 Redirecting to Authelia portal...'); + // Redirect back to the Angular app, not to the webhook URL + this.autheliaAuth.redirectToLogin(window.location.origin); + } + + public onCancel(): void { + this.hide(); + this.loginCancelled.emit(); + } + + private clearForm(): void { + this.username = ''; + this.password = ''; + this.errorMessage.set(''); + this.isLoading.set(false); + } +} \ No newline at end of file diff --git a/src/app/components/comment-session/comment-session.component.scss b/src/app/components/comment-session/comment-session.component.scss new file mode 100644 index 0000000..3aef31a --- /dev/null +++ b/src/app/components/comment-session/comment-session.component.scss @@ -0,0 +1,469 @@ +.comment-session-container { + background: white; + border: 1px solid #e9ecef; + border-radius: 8px; + margin-bottom: 2rem; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.comment-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e9ecef; + background: #f8f9fa; + border-radius: 8px 8px 0 0; + + h3 { + margin: 0; + color: #495057; + font-weight: 600; + font-size: 1.1rem; + } + + .btn { + padding: 0.375rem 0.75rem; + border: 1px solid #6c757d; + background: transparent; + color: #6c757d; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; + transition: all 0.3s ease; + + &.btn-outline { + &:hover { + background: #6c757d; + color: white; + } + } + + &.btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.8rem; + } + } +} + +.comment-content { + padding: 1.5rem; +} + +.quick-comment-section { + margin-bottom: 2rem; + + .comment-input-group { + margin-bottom: 1rem; + + .comment-input { + width: 100%; + padding: 1rem; + border: 1px solid #ced4da; + border-radius: 6px; + font-family: inherit; + font-size: 0.95rem; + resize: vertical; + transition: border-color 0.3s ease; + + &:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); + } + } + + .comment-controls { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-top: 1rem; + + .comment-type-selector { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + + .type-option { + display: flex; + align-items: center; + gap: 0.25rem; + cursor: pointer; + padding: 0.375rem 0.75rem; + border: 1px solid #dee2e6; + border-radius: 20px; + font-size: 0.85rem; + transition: all 0.3s ease; + background: white; + + input[type="radio"] { + display: none; + } + + &.selected { + background: #007bff; + color: white; + border-color: #007bff; + } + + &:hover:not(.selected) { + border-color: #007bff; + background: #f0f8ff; + } + } + } + + .comment-actions { + display: flex; + gap: 0.5rem; + + .btn-primary { + background: #007bff; + color: white; + border: none; + + &:hover:not(:disabled) { + background: #0056b3; + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } + } + } + } + } + + .tag-input-section { + .tag-input { + width: 100%; + padding: 0.5rem 0.75rem; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 0.9rem; + transition: border-color 0.3s ease; + + &:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1); + } + } + } +} + +.comments-history { + margin-bottom: 2rem; + + .history-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + + h4 { + margin: 0; + color: #495057; + font-weight: 600; + } + + .history-filters { + display: flex; + gap: 0.5rem; + align-items: center; + + .filter-select { + padding: 0.375rem 0.75rem; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 0.85rem; + background: white; + + &:focus { + outline: none; + border-color: #007bff; + } + } + } + } + + .comments-list { + display: flex; + flex-direction: column; + gap: 1rem; + + .comment-item { + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 1rem; + background: white; + transition: all 0.3s ease; + + &.type-note { + border-left: 4px solid #6c757d; + } + + &.type-question { + border-left: 4px solid #ffc107; + } + + &.type-observation { + border-left: 4px solid #17a2b8; + } + + &.type-improvement { + border-left: 4px solid #28a745; + } + + .comment-header-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; + + .comment-meta { + display: flex; + gap: 0.75rem; + align-items: center; + + .comment-type-badge { + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + + &.badge-note { + background: #e9ecef; + color: #495057; + } + + &.badge-question { + background: #fff3cd; + color: #856404; + } + + &.badge-observation { + background: #d1ecf1; + color: #0c5460; + } + + &.badge-improvement { + background: #d4edda; + color: #155724; + } + } + + .comment-time { + color: #6c757d; + font-size: 0.8rem; + font-family: 'Courier New', monospace; + } + } + + .btn-icon { + background: none; + border: none; + cursor: pointer; + padding: 0.25rem; + color: #6c757d; + transition: color 0.3s ease; + + &:hover { + color: #dc3545; + } + } + } + + .comment-content-item { + color: #212529; + line-height: 1.5; + margin-bottom: 0.75rem; + } + + .comment-tags { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + + .tag { + background: #f8f9fa; + color: #495057; + padding: 0.2rem 0.5rem; + border-radius: 8px; + font-size: 0.75rem; + border: 1px solid #dee2e6; + } + } + } + } +} + +.quick-actions-section { + margin-bottom: 2rem; + + h4 { + margin: 0 0 1rem 0; + color: #495057; + font-weight: 600; + } + + .quick-actions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.75rem; + + .quick-action-btn { + padding: 0.75rem; + border: 1px solid #dee2e6; + background: #f8f9fa; + color: #495057; + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; + text-align: left; + transition: all 0.3s ease; + + &:hover { + border-color: #007bff; + background: #e7f3ff; + color: #007bff; + } + } + } +} + +.export-section { + padding-top: 1.5rem; + border-top: 1px solid #e9ecef; + + h4 { + margin: 0 0 1rem 0; + color: #495057; + font-weight: 600; + } + + .export-actions { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + + .btn { + padding: 0.5rem 1rem; + border: 1px solid #6c757d; + background: transparent; + color: #6c757d; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.3s ease; + + &.btn-outline { + &:hover { + background: #6c757d; + color: white; + } + } + } + } +} + +.minimized-summary { + padding: 1rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + + .summary-text { + color: #6c757d; + font-size: 0.9rem; + } + + .summary-types { + display: flex; + gap: 0.25rem; + + .type-mini-badge { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.7rem; + + &.badge-note { + background: #e9ecef; + } + + &.badge-question { + background: #fff3cd; + } + + &.badge-observation { + background: #d1ecf1; + } + + &.badge-improvement { + background: #d4edda; + } + } + } +} + +@media (max-width: 768px) { + .comment-header { + padding: 0.75rem 1rem; + flex-direction: column; + gap: 0.5rem; + text-align: center; + } + + .comment-content { + padding: 1rem; + } + + .comment-controls { + flex-direction: column !important; + align-items: stretch !important; + + .comment-type-selector { + justify-content: center; + } + + .comment-actions { + justify-content: center; + } + } + + .history-header { + flex-direction: column !important; + gap: 0.75rem; + align-items: stretch !important; + + .history-filters { + justify-content: center; + } + } + + .quick-actions-grid { + grid-template-columns: 1fr; + } + + .export-actions { + flex-direction: column; + + .btn { + width: 100%; + } + } + + .minimized-summary { + flex-direction: column; + gap: 0.5rem; + text-align: center; + } +} \ No newline at end of file diff --git a/src/app/components/comment-session/comment-session.component.ts b/src/app/components/comment-session/comment-session.component.ts new file mode 100644 index 0000000..3b68131 --- /dev/null +++ b/src/app/components/comment-session/comment-session.component.ts @@ -0,0 +1,349 @@ +import { Component, Output, EventEmitter, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +interface Comment { + id: string; + content: string; + timestamp: Date; + type: 'note' | 'question' | 'observation' | 'improvement'; + tags: string[]; +} + +@Component({ + selector: 'app-comment-session', + standalone: true, + imports: [CommonModule, FormsModule], + template: ` +
+
+

📝 Session Notes & Comments

+ +
+ + @if (isExpanded()) { +
+ +
+
+ + +
+
+ + + + +
+ +
+ + +
+
+
+ + +
+ +
+
+ + + @if (comments().length > 0) { +
+
+

💬 Session Comments ({{ comments().length }})

+
+ + +
+
+ +
+ @for (comment of filteredComments(); track comment.id) { +
+
+
+ + {{ getTypeIcon(comment.type) }} {{ comment.type }} + + + {{ formatTime(comment.timestamp) }} + +
+ +
+ +
+ {{ comment.content }} +
+ + @if (comment.tags.length > 0) { +
+ @for (tag of comment.tags; track tag) { + {{ tag }} + } +
+ } +
+ } +
+
+ } + + +
+

⚡ Quick Actions

+
+ + + + + + +
+
+ + +
+

📤 Export Comments

+
+ + + +
+
+
+ } + + + @if (!isExpanded() && comments().length > 0) { +
+ + {{ comments().length }} comments added this session + +
+ @for (type of getActiveTypes(); track type) { + + {{ getTypeIcon(type) }} + + } +
+
+ } +
+ `, + styleUrl: './comment-session.component.scss' +}) +export class CommentSessionComponent { + @Output() commentAdded = new EventEmitter(); + + // Component state + isExpanded = signal(false); + currentComment = ''; + currentTags = ''; + selectedType = signal<'note' | 'question' | 'observation' | 'improvement'>('note'); + comments = signal([]); + filteredComments = signal([]); + filterType = 'all'; + + toggleExpanded() { + this.isExpanded.update(expanded => !expanded); + } + + addComment() { + const content = this.currentComment.trim(); + if (!content) return; + + const tags = this.currentTags + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0); + + const newComment: Comment = { + id: `comment-${Date.now()}`, + content: content, + timestamp: new Date(), + type: this.selectedType(), + tags: tags + }; + + this.comments.update(comments => [...comments, newComment]); + this.applyFilter(); + + // Emit the comment content + this.commentAdded.emit(content); + + // Clear inputs + this.clearComment(); + } + + addPredefinedComment(content: string) { + this.currentComment = content; + this.selectedType.set('observation'); + this.addComment(); + } + + clearComment() { + this.currentComment = ''; + this.currentTags = ''; + } + + deleteComment(commentId: string) { + this.comments.update(comments => + comments.filter(comment => comment.id !== commentId) + ); + this.applyFilter(); + } + + clearAllComments() { + if (confirm('Are you sure you want to clear all comments?')) { + this.comments.set([]); + this.filteredComments.set([]); + } + } + + applyFilter() { + const allComments = this.comments(); + if (this.filterType === 'all') { + this.filteredComments.set(allComments); + } else { + this.filteredComments.set( + allComments.filter(comment => comment.type === this.filterType) + ); + } + } + + getTypeIcon(type: string): string { + switch (type) { + case 'note': return '📝'; + case 'question': return '❓'; + case 'observation': return '👁️'; + case 'improvement': return '💡'; + default: return '📝'; + } + } + + getActiveTypes(): string[] { + const types = new Set(this.comments().map(c => c.type)); + return Array.from(types); + } + + formatTime(timestamp: Date): string { + return timestamp.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit' + }); + } + + exportAsText() { + const text = this.comments() + .map(comment => { + const tags = comment.tags.length > 0 ? ` [${comment.tags.join(', ')}]` : ''; + return `[${comment.type.toUpperCase()}] ${this.formatTime(comment.timestamp)} - ${comment.content}${tags}`; + }) + .join('\n\n'); + + this.downloadText(text, 'interview-comments.txt'); + } + + exportAsJSON() { + const json = JSON.stringify(this.comments(), null, 2); + this.downloadText(json, 'interview-comments.json'); + } + + copyToClipboard() { + const text = this.comments() + .map(comment => `${comment.type}: ${comment.content}`) + .join('\n'); + + navigator.clipboard.writeText(text).then(() => { + // Could show a toast notification + console.log('Comments copied to clipboard'); + }); + } + + private downloadText(content: string, filename: string) { + const blob = new Blob([content], { type: 'text/plain' }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + window.URL.revokeObjectURL(url); + } +} \ No newline at end of file diff --git a/src/app/components/cv-upload/cv-upload.component.scss b/src/app/components/cv-upload/cv-upload.component.scss new file mode 100644 index 0000000..aa079eb --- /dev/null +++ b/src/app/components/cv-upload/cv-upload.component.scss @@ -0,0 +1,305 @@ +.cv-upload-container { + max-width: 800px; + margin: 2rem auto; + padding: 2rem; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + + h2 { + color: #1a1a1a; + margin-bottom: 2rem; + text-align: center; + font-weight: 600; + } + + h3 { + color: #333; + margin-bottom: 1rem; + font-weight: 500; + } +} + +.upload-section { + margin-bottom: 2rem; +} + +.drag-drop-area { + border: 2px dashed #ddd; + border-radius: 8px; + padding: 3rem 2rem; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + background-color: #fafafa; + + &:hover { + border-color: #007bff; + background-color: #f0f8ff; + } + + &.dragover { + border-color: #007bff; + background-color: #e6f3ff; + transform: scale(1.02); + } + + .upload-icon { + font-size: 3rem; + margin-bottom: 1rem; + opacity: 0.6; + } + + .upload-text { + font-size: 1.1rem; + color: #333; + margin-bottom: 0.5rem; + font-weight: 500; + } + + .upload-subtext { + color: #666; + font-size: 0.9rem; + margin-bottom: 0; + } + + .file-name { + font-size: 1.1rem; + color: #007bff; + font-weight: 600; + margin-bottom: 0.5rem; + } + + .file-size { + color: #666; + font-size: 0.9rem; + margin-bottom: 0; + } +} + +.error-message { + background-color: #fee; + color: #d63384; + padding: 0.75rem 1rem; + border-radius: 4px; + margin-top: 1rem; + border: 1px solid #f5c2c7; +} + +.manual-input-section { + margin-bottom: 2rem; + + .manual-input { + width: 100%; + min-height: 120px; + padding: 1rem; + border: 1px solid #ddd; + border-radius: 4px; + font-family: inherit; + font-size: 0.95rem; + resize: vertical; + transition: border-color 0.3s ease; + + &:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); + } + } +} + +.processing-section { + text-align: center; + padding: 2rem; + background-color: #f8f9fa; + border-radius: 8px; + margin-bottom: 2rem; + + .loading-spinner { + width: 40px; + height: 40px; + margin: 0 auto 1rem; + border: 4px solid #f3f3f3; + border-top: 4px solid #007bff; + border-radius: 50%; + animation: spin 1s linear infinite; + } + + p { + color: #666; + font-size: 1rem; + margin: 0; + } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.cv-summary { + background-color: #f8f9fa; + padding: 1.5rem; + border-radius: 8px; + margin-bottom: 2rem; + border: 1px solid #e9ecef; + + .profile-details { + p { + margin: 0.5rem 0; + color: #555; + + strong { + color: #333; + font-weight: 600; + } + } + } +} + +.action-buttons { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + + .btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; + + &.btn-primary { + background-color: #007bff; + color: white; + + &:hover { + background-color: #0056b3; + transform: translateY(-1px); + } + } + + &.btn-success { + background-color: #28a745; + color: white; + + &:hover { + background-color: #1e7e34; + transform: translateY(-1px); + } + } + + &.btn-secondary { + background-color: #6c757d; + color: white; + + &:hover { + background-color: #545b62; + transform: translateY(-1px); + } + } + + &:active { + transform: translateY(0); + } + } +} + +@media (max-width: 768px) { + .cv-upload-container { + margin: 1rem; + padding: 1rem; + } + + .drag-drop-area { + padding: 2rem 1rem; + } + + .action-buttons { + flex-direction: column; + align-items: center; + + .btn { + width: 100%; + max-width: 300px; + } + } +} + +// Extracted text preview styles +.extracted-text-section { + margin-top: 1.5rem; + border-top: 1px solid #e0e0e0; + padding-top: 1.5rem; + + h4 { + color: #333; + margin-bottom: 1rem; + font-size: 1.1rem; + font-weight: 600; + } + + .btn { + margin-bottom: 1rem; + } +} + +.extracted-text-preview { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 1rem; + + .text-stats { + margin-bottom: 0.75rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid #dee2e6; + color: #6c757d; + font-size: 0.85rem; + + strong { + color: #495057; + } + } + + .text-content { + background: white; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 1rem; + max-height: 400px; + overflow-y: auto; + + pre { + margin: 0; + font-family: 'Monaco', 'Consolas', 'Courier New', monospace; + font-size: 0.85rem; + line-height: 1.5; + white-space: pre-wrap; + word-wrap: break-word; + color: #333; + } + } + + .text-expansion { + margin-top: 0.75rem; + text-align: center; + + .btn-link { + background: none; + border: none; + color: #007bff; + text-decoration: underline; + cursor: pointer; + font-size: 0.85rem; + padding: 0.25rem 0.5rem; + + &:hover { + color: #0056b3; + } + } + } +} \ No newline at end of file diff --git a/src/app/components/cv-upload/cv-upload.component.ts b/src/app/components/cv-upload/cv-upload.component.ts new file mode 100644 index 0000000..5c7e0f1 --- /dev/null +++ b/src/app/components/cv-upload/cv-upload.component.ts @@ -0,0 +1,437 @@ +import { Component, OnInit, inject, signal, computed, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CVParserService } from '../../services/cv-parser.service'; +import { N8nSyncService } from '../../services/n8n-sync.service'; +import { LoggingService } from '../../services/logging.service'; +import { AutheliaAuthService } from '../../services/authelia-auth.service'; +import { AuthLoginComponent } from '../auth-login/auth-login.component'; +import { CVProfile } from '../../models/cv-profile.interface'; + +@Component({ + selector: 'app-cv-upload', + standalone: true, + imports: [CommonModule, AuthLoginComponent], + template: ` +
+

Upload Your CV

+ +
+
+ +
📄
+ + @if (!uploadedFile()) { +

+ Drop your CV here or click to browse +

+

+ Supports PDF, DOC, DOCX, and TXT files +

+ } @else { +

{{ uploadedFile()?.name }}

+

{{ formatFileSize(uploadedFile()?.size || 0) }}

+ } + + +
+ + @if (uploadError()) { +
+ {{ uploadError() }} +
+ } +
+ + @if (uploadedFile() && !isProcessing()) { +
+

Additional Information

+ +
+ } + + @if (isProcessing()) { +
+
+

Processing your CV and generating interview preparation...

+
+ } + + @if (cvProfile()) { +
+

CV Summary

+
+

Name: {{ cvProfile()?.personalInfo?.fullName }}

+

Email: {{ cvProfile()?.personalInfo?.email }}

+

Skills: {{ skillsDisplay() }}

+

Experience: {{ cvProfile()?.experience?.length || 0 }} positions

+

Education: {{ cvProfile()?.education?.length || 0 }} entries

+
+ + +
+

📄 Extracted Text Preview

+ + + @if (showExtractedText()) { +
+
+ + Characters: {{ getExtractedTextLength() }} | + Words: {{ getExtractedWordCount() }} | + File: {{ cvProfile()?.fileName }} + +
+
+
{{ getExtractedTextPreview() }}
+
+ @if (isExtractedTextLong()) { +
+ +
+ } +
+ } +
+
+ } + +
+ @if (uploadedFile() && !isProcessing()) { + + } + + @if (cvProfile()) { + + } + + @if (uploadedFile()) { + + } +
+
+ + + + + `, + styleUrl: './cv-upload.component.scss' +}) +export class CVUploadComponent implements OnInit { + @ViewChild('authLogin') authLogin!: AuthLoginComponent; + + private cvParserService = inject(CVParserService); + private n8nSyncService = inject(N8nSyncService); + private loggingService = inject(LoggingService); + private autheliaAuth = inject(AutheliaAuthService); + + uploadedFile = signal(null); + isDragOver = signal(false); + uploadError = signal(null); + isProcessing = signal(false); + cvProfile = signal(null); + manualNotes = signal(''); + + // Text preview state + showExtractedText = signal(false); + showFullText = signal(false); + + // Computed properties + skillsDisplay = computed(() => { + const skills = this.cvProfile()?.skills; + return skills ? skills.map(s => s.name).join(', ') : 'No skills listed'; + }); + + ngOnInit() { + console.log('CVUploadComponent initialized'); + } + + onDragOver(event: DragEvent) { + event.preventDefault(); + this.isDragOver.set(true); + } + + onDragLeave(event: DragEvent) { + event.preventDefault(); + this.isDragOver.set(false); + } + + onFileDrop(event: DragEvent) { + event.preventDefault(); + this.isDragOver.set(false); + + const files = event.dataTransfer?.files; + if (files && files.length > 0) { + this.handleFileUpload(files[0]); + } + } + + onFileSelected(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.handleFileUpload(input.files[0]); + } + } + + private handleFileUpload(file: File) { + this.uploadError.set(null); + + // Validate file type + const allowedTypes = ['application/pdf', 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'text/plain']; + + if (!allowedTypes.includes(file.type)) { + this.uploadError.set('Please upload a PDF, DOC, DOCX, or TXT file.'); + return; + } + + // Validate file size (10MB limit) + if (file.size > 10 * 1024 * 1024) { + this.uploadError.set('File size must be less than 10MB.'); + return; + } + + this.uploadedFile.set(file); + console.log(`File uploaded: ${file.name} (${file.size} bytes)`); + } + + updateManualNotes(event: Event) { + const textarea = event.target as HTMLTextAreaElement; + this.manualNotes.set(textarea.value); + } + + async processCV() { + const file = this.uploadedFile(); + if (!file) return; + + this.isProcessing.set(true); + this.uploadError.set(null); + + try { + // Parse CV content + const cvProfile = await this.cvParserService.parseCV(file); + this.cvProfile.set(cvProfile); + + // Send to n8n for question generation (with Authelia authentication) + this.sendToN8nWithAuth(cvProfile); + + console.log('CV processed successfully', { profileId: cvProfile.id }); + + } catch (error) { + this.uploadError.set('Failed to process CV. Please try again.'); + console.error('CV processing failed', error); + } finally { + this.isProcessing.set(false); + } + } + + startInterview() { + // Navigate to interview session + // This will be implemented when we set up routing + console.log('Starting interview session'); + } + + clearUpload() { + this.uploadedFile.set(null); + this.cvProfile.set(null); + this.manualNotes.set(''); + this.uploadError.set(null); + this.isProcessing.set(false); + } + + formatFileSize(bytes: number): string { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + + // Text preview methods + toggleTextPreview() { + this.showExtractedText.update(show => !show); + } + + toggleFullText() { + this.showFullText.update(show => !show); + } + + getExtractedTextLength(): number { + return this.cvProfile()?.parsedText?.length || 0; + } + + getExtractedWordCount(): number { + const text = this.cvProfile()?.parsedText || ''; + return text.trim() ? text.trim().split(/\s+/).length : 0; + } + + isExtractedTextLong(): boolean { + const text = this.cvProfile()?.parsedText || ''; + return text.length > 1000; // Show expand option if text is longer than 1000 chars + } + + getExtractedTextPreview(): string { + const text = this.cvProfile()?.parsedText || 'No text extracted'; + + if (!this.showFullText() && this.isExtractedTextLong()) { + return text.substring(0, 1000) + '\n\n... (truncated)'; + } + + return text; + } + + private async pollForAnalysisResults(analysisId: string) { + try { + this.n8nSyncService.pollAnalysisStatus(analysisId).subscribe({ + next: (response) => { + if (response.status === 'completed') { + console.log('N8n analysis completed:', response); + if (response.questionBankId) { + // Question bank generated, could fetch details if needed + console.log('Question bank ID:', response.questionBankId); + } + } else if (response.status === 'failed') { + console.error('N8n analysis failed'); + } + // Continue polling for processing states + }, + error: (error) => { + console.error('Error polling N8n analysis status:', error); + } + }); + } catch (error) { + console.error('Failed to start polling for analysis results:', error); + } + } + + /** + * Send CV to n8n with Authelia authentication handling + */ + private async sendToN8nWithAuth(cvProfile: CVProfile): Promise { + try { + // Check if already authenticated + if (this.autheliaAuth.isAuthenticated) { + console.log('✅ Already authenticated, submitting CV...'); + await this.submitToN8n(cvProfile); + return; + } + + // Check authentication status + console.log('🔍 Checking authentication status...'); + this.autheliaAuth.checkAuthenticationStatus().subscribe({ + next: async (isAuthenticated) => { + if (isAuthenticated) { + console.log('✅ Authentication verified, submitting CV...'); + await this.submitToN8n(cvProfile); + } else { + console.log('🔒 Authentication required, showing login...'); + this.showAuthLogin(cvProfile); + } + }, + error: (error) => { + console.log('❌ Authentication check failed:', error); + this.showAuthLogin(cvProfile); + } + }); + } catch (error) { + console.error('❌ Error in sendToN8nWithAuth:', error); + this.uploadError.set('Failed to connect to n8n services. Please try again.'); + } + } + + /** + * Actually submit CV to n8n (assumes authentication is valid) + */ + private async submitToN8n(cvProfile: CVProfile): Promise { + try { + console.log('🚀 Submitting CV to n8n...'); + const analysisResponse = await this.n8nSyncService.submitCVForAnalysis(cvProfile).toPromise(); + + if (analysisResponse) { + console.log('✅ CV analysis submitted to n8n:', analysisResponse.analysisId); + + // Poll for results if analysis is async + if (analysisResponse.status === 'processing') { + this.pollForAnalysisResults(analysisResponse.analysisId); + } else if (analysisResponse.status === 'completed') { + console.log('🎉 CV analysis completed immediately'); + } + } + } catch (error) { + console.error('❌ n8n submission failed:', error); + + if (error instanceof Error && error.message === 'Authelia authentication required') { + console.log('🔒 Authentication expired, showing login...'); + this.showAuthLogin(cvProfile); + } else { + console.warn('⚠️ n8n analysis failed, continuing with local processing'); + this.uploadError.set('n8n analysis unavailable. CV processed locally.'); + } + } + } + + /** + * Show authentication login modal + */ + private showAuthLogin(cvProfile?: CVProfile): void { + console.log('🔐 Showing authentication login...'); + + // Store CV profile for retry after login + if (cvProfile) { + (this as any).pendingCVProfile = cvProfile; + } + + this.authLogin.show(); + } + + /** + * Handle successful authentication + */ + public onAuthSuccess(): void { + console.log('✅ Authentication successful!'); + + // Retry CV submission if we had a pending one + const pendingCV = (this as any).pendingCVProfile; + if (pendingCV) { + console.log('🔄 Retrying CV submission after authentication...'); + (this as any).pendingCVProfile = null; + this.submitToN8n(pendingCV); + } + } + + /** + * Handle authentication cancellation + */ + public onAuthCancelled(): void { + console.log('❌ Authentication cancelled'); + (this as any).pendingCVProfile = null; + this.uploadError.set('Authentication required for n8n integration. CV processed locally only.'); + } +} \ No newline at end of file diff --git a/src/app/components/interview-session/interview-session.component.scss b/src/app/components/interview-session/interview-session.component.scss new file mode 100644 index 0000000..7be3578 --- /dev/null +++ b/src/app/components/interview-session/interview-session.component.scss @@ -0,0 +1,366 @@ +.interview-session-container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.session-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + padding: 1rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 8px; + + h2 { + margin: 0; + font-weight: 600; + } + + .session-controls { + display: flex; + align-items: center; + gap: 1rem; + + .btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + + &.btn-primary { + background-color: #28a745; + color: white; + + &:hover { + background-color: #1e7e34; + } + } + + &.btn-danger { + background-color: #dc3545; + color: white; + + &:hover { + background-color: #c82333; + } + } + } + + .session-status { + display: flex; + flex-direction: column; + align-items: center; + font-size: 0.9rem; + + .status-indicator { + margin-bottom: 0.25rem; + + &.active { + color: #90EE90; + } + + &.inactive { + color: #FFB6C1; + } + } + + .session-time { + font-family: 'Courier New', monospace; + font-weight: bold; + } + } + } +} + +.speech-recognition-panel { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 2rem; + + .speech-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + + h3 { + margin: 0; + color: #333; + font-weight: 500; + } + + .listening-indicator { + color: #28a745; + font-weight: 600; + animation: pulse 2s infinite; + } + + .not-listening { + color: #6c757d; + } + } + + .speech-content { + margin-bottom: 1rem; + + .current-speech, .detected-question { + background: white; + padding: 1rem; + border-radius: 4px; + margin-bottom: 1rem; + border: 1px solid #dee2e6; + + h4 { + margin: 0 0 0.5rem 0; + color: #495057; + font-size: 0.9rem; + font-weight: 600; + } + + .speech-text, .question-text { + margin: 0; + color: #212529; + font-size: 1rem; + line-height: 1.5; + min-height: 1.5rem; + } + + .repeat-indicator { + display: inline-block; + background: #ffc107; + color: #212529; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; + margin-top: 0.5rem; + } + } + + .detected-question { + border-color: #007bff; + background: #e7f3ff; + } + } + + .speech-controls { + display: flex; + gap: 0.5rem; + + .btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; + + &.btn-primary { + background-color: #007bff; + color: white; + } + + &.btn-secondary { + background-color: #6c757d; + color: white; + } + + &.btn-outline { + background-color: transparent; + border: 1px solid #6c757d; + color: #6c757d; + + &:hover { + background-color: #6c757d; + color: white; + } + } + + &.btn-sm { + padding: 0.375rem 0.75rem; + font-size: 0.8rem; + } + } + } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.session-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-bottom: 2rem; + + .stat-item { + background: white; + padding: 1rem; + border-radius: 8px; + border: 1px solid #e9ecef; + text-align: center; + + .stat-label { + display: block; + color: #6c757d; + font-size: 0.9rem; + margin-bottom: 0.5rem; + } + + .stat-value { + display: block; + font-size: 1.5rem; + font-weight: 600; + color: #007bff; + } + } +} + +.pre-session-setup { + background: #f8f9fa; + border-radius: 8px; + padding: 2rem; + margin-bottom: 2rem; + + .setup-instructions { + margin-bottom: 2rem; + + h3 { + color: #333; + margin-bottom: 1rem; + } + + ul { + color: #555; + line-height: 1.6; + + li { + margin-bottom: 0.5rem; + } + } + } + + .microphone-test { + h4 { + color: #333; + margin-bottom: 1rem; + } + + .btn { + padding: 0.75rem 1.5rem; + border: 1px solid #007bff; + background: transparent; + color: #007bff; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background: #007bff; + color: white; + } + } + + .mic-level-indicator { + width: 200px; + height: 8px; + background: #e9ecef; + border-radius: 4px; + margin-top: 1rem; + overflow: hidden; + + .mic-level-bar { + height: 100%; + background: linear-gradient(90deg, #28a745, #ffc107, #dc3545); + transition: width 0.1s ease; + } + } + } +} + +.session-history { + background: white; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 1.5rem; + + h3 { + margin: 0 0 1rem 0; + color: #333; + } + + .history-list { + .history-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + border: 1px solid #e9ecef; + border-radius: 4px; + margin-bottom: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background: #f8f9fa; + border-color: #007bff; + } + + .session-date { + font-weight: 500; + color: #333; + } + + .session-duration { + color: #6c757d; + font-family: 'Courier New', monospace; + } + + .session-questions { + color: #007bff; + font-weight: 500; + } + } + } +} + +@media (max-width: 768px) { + .interview-session-container { + padding: 0.5rem; + } + + .session-header { + flex-direction: column; + gap: 1rem; + text-align: center; + + .session-controls { + flex-direction: column; + width: 100%; + } + } + + .session-stats { + grid-template-columns: 1fr; + } + + .speech-controls { + flex-wrap: wrap; + } + + .history-item { + flex-direction: column !important; + align-items: flex-start !important; + gap: 0.5rem; + } +} \ No newline at end of file diff --git a/src/app/components/interview-session/interview-session.component.ts b/src/app/components/interview-session/interview-session.component.ts new file mode 100644 index 0000000..a6a07b2 --- /dev/null +++ b/src/app/components/interview-session/interview-session.component.ts @@ -0,0 +1,501 @@ +import { Component, OnInit, OnDestroy, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SpeechService } from '../../services/speech.service'; +import { QuestionBankService } from '../../services/question-bank.service'; +import { AnalyticsService } from '../../services/analytics.service'; +import { LoggingService } from '../../services/logging.service'; +import { QuestionDisplayComponent } from '../question-display/question-display.component'; +import { CommentSessionComponent } from '../comment-session/comment-session.component'; +import { LanguageSelectorComponent } from '../language-selector/language-selector.component'; +import { InterviewSession, QuestionAnswer, SessionStatus, CommentCategory } from '../../models/interview-session.interface'; +import { Question, QuestionCategory, QuestionDifficulty } from '../../models/question-bank.interface'; + +@Component({ + selector: 'app-interview-session', + standalone: true, + imports: [CommonModule, QuestionDisplayComponent, CommentSessionComponent, LanguageSelectorComponent], + template: ` +
+ +
+

Interview Assistant

+
+ @if (!isSessionActive()) { + + } @else { + + } + +
+ @if (isSessionActive()) { + 🟢 Active + } @else { + 🔴 Inactive + } + {{ formatSessionTime(sessionDuration()) }} +
+
+
+ + @if (isSessionActive()) { + +
+
+

🎙️ Live Audio Analysis

+ @if (isListening()) { + Listening... + } @else { + Not Listening + } +
+ +
+
+

Current Speech:

+

{{ currentSpeech() || 'Waiting for speech...' }}

+
+ + @if (detectedQuestion()) { +
+

🔍 Detected Question:

+

{{ detectedQuestion() }}

+ @if (isUserRepeat()) { + 🔄 User requesting help + } +
+ } +
+ +
+ + +
+
+ + + + + + +
+
+ Questions Detected: + {{ questionsDetected() }} +
+
+ Answers Provided: + {{ answersProvided() }} +
+
+ Response Time (avg): + {{ averageResponseTime() }}ms +
+
+ + + + + + } @else { + +
+
+

📋 Interview Preparation

+
    +
  • Ensure your microphone is working and positioned properly
  • +
  • Test your audio levels - speak clearly and at normal volume
  • +
  • Position the screen where you can see answers discreetly
  • +
  • Have your CV and notes ready for reference
  • +
  • Start the session when you're ready to begin the interview
  • +
+
+ +
+

🎤 Microphone Test

+ + @if (microphoneLevel()) { +
+
+
+ } +
+ + + +
+ } + + + @if (sessionHistory().length > 0) { +
+

📈 Previous Sessions

+
+ @for (session of sessionHistory(); track session.id) { +
+ {{ formatDate(session.startTime) }} + {{ formatDuration(session.duration) }} + {{ session.questionsAnswered.length }} Q&A +
+ } +
+
+ } +
+ `, + styleUrl: './interview-session.component.scss' +}) +export class InterviewSessionComponent implements OnInit, OnDestroy { + private speechService = inject(SpeechService); + private questionBankService = inject(QuestionBankService); + private analyticsService = inject(AnalyticsService); + private loggingService = inject(LoggingService); + + // Session state + isSessionActive = signal(false); + sessionDuration = signal(0); + currentSession = signal(null); + sessionHistory = signal([]); + + // Speech recognition state + isListening = signal(false); + isTesting = signal(false); + currentSpeech = signal(''); + detectedQuestion = signal(''); + isUserRepeat = signal(false); + microphoneLevel = signal(0); + + // Question/Answer state + currentQuestion = signal(null); + suggestedAnswer = signal(''); + isProcessingAnswer = signal(false); + + // Statistics + questionsDetected = signal(0); + answersProvided = signal(0); + averageResponseTime = signal(0); + + private sessionTimer?: number; + private responseStartTime = 0; + + ngOnInit() { + console.log('InterviewSessionComponent initialized'); + this.loadSessionHistory(); + this.setupSpeechRecognition(); + } + + ngOnDestroy() { + this.stopSession(); + this.speechService.stopListening(); + if (this.sessionTimer) { + clearInterval(this.sessionTimer); + } + } + + private setupSpeechRecognition() { + // Listen for speech events + this.speechService.speechResults$.subscribe((result: any) => { + this.currentSpeech.set(result.transcript); + this.analyzeForQuestions(result.transcript); + }); + + // Mock other observables for now + // this.speechService.listeningState$.subscribe((isListening: boolean) => { + // this.isListening.set(isListening); + // }); + + // this.speechService.microphoneLevel$.subscribe((level: number) => { + // this.microphoneLevel.set(level); + // }); + } + + async startSession() { + try { + const sessionId = `session-${Date.now()}`; + const newSession: InterviewSession = { + id: sessionId, + startTime: new Date(), + endTime: undefined, + duration: 0, + questionsAnswered: [], + cvProfileId: '', // Should be loaded from previous CV upload + questionBankId: '', + detectedQuestions: [], + providedAnswers: [], + manualComments: [], + speechHints: [], + analytics: { + totalQuestions: 0, + answersFromBank: 0, + answersGenerated: 0, + averageResponseTime: 0, + accuracyRate: 0, + topicsCovered: [] + }, + status: SessionStatus.ACTIVE + }; + + this.currentSession.set(newSession); + this.isSessionActive.set(true); + this.sessionDuration.set(0); + + // Start session timer + this.sessionTimer = setInterval(() => { + this.sessionDuration.update(duration => duration + 1); + }, 1000); + + // Start speech recognition + await this.speechService.startListening(); + + // Track session start + // this.analyticsService.trackSessionStart(sessionId); + + console.log('Interview session started', { sessionId }); + + } catch (error) { + console.error('Failed to start interview session', error); + } + } + + stopSession() { + if (!this.isSessionActive()) return; + + const session = this.currentSession(); + if (session) { + session.endTime = new Date(); + session.duration = this.sessionDuration(); + + // Save session to history + this.saveSession(session); + + // Track session end + // this.analyticsService.trackSessionEnd(session.id, { + // duration: session.duration, + // questionsAnswered: session.questionsAnswered.length, + // averageResponseTime: this.averageResponseTime() + // }); + } + + this.isSessionActive.set(false); + this.speechService.stopListening(); + + if (this.sessionTimer) { + clearInterval(this.sessionTimer); + this.sessionTimer = undefined; + } + + console.log('Interview session stopped'); + } + + toggleListening() { + if (this.isListening()) { + this.speechService.stopListening(); + } else { + this.speechService.startListening(); + } + } + + clearSpeech() { + this.currentSpeech.set(''); + this.detectedQuestion.set(''); + this.isUserRepeat.set(false); + } + + async testMicrophone() { + this.isTesting.set(true); + try { + // await this.speechService.testMicrophone(); + // Mock microphone test + console.log('Testing microphone...'); + setTimeout(() => this.isTesting.set(false), 3000); + } catch (error) { + this.isTesting.set(false); + console.error('Microphone test failed', error); + } + } + + private async analyzeForQuestions(transcript: string) { + if (!transcript.trim()) return; + + // Detect if user is repeating a question (simplified logic) + const isRepeat = this.detectUserRepeat(transcript); + this.isUserRepeat.set(isRepeat); + + // Extract potential questions + const question = this.extractQuestionFromSpeech(transcript); + if (question) { + this.detectedQuestion.set(question); + this.questionsDetected.update(count => count + 1); + + // Find answer from question bank + await this.findAnswer(question); + } + } + + private detectUserRepeat(transcript: string): boolean { + // Simple heuristics to detect if user is repeating a question for help + const repeatPatterns = [ + /what (is|are|was|were)/i, + /how (do|does|can|would)/i, + /tell me about/i, + /explain/i, + /(can you|could you) (explain|tell)/i + ]; + + return repeatPatterns.some(pattern => pattern.test(transcript)); + } + + private extractQuestionFromSpeech(transcript: string): string { + // Extract question-like phrases from transcript + const sentences = transcript.split(/[.!?]+/).filter(s => s.trim()); + + for (const sentence of sentences) { + if (sentence.includes('?') || + /^(what|how|why|when|where|who|which)/i.test(sentence.trim())) { + return sentence.trim(); + } + } + + return ''; + } + + private async findAnswer(question: string) { + if (!question) return; + + this.isProcessingAnswer.set(true); + this.responseStartTime = Date.now(); + + try { + // Search question bank for matching answer + // const answer = await this.questionBankService.findAnswer(question); + // Mock answer for now + const mockQuestion: Question = { + id: '1', + text: question, + category: QuestionCategory.GENERAL, + difficulty: QuestionDifficulty.MEDIUM, + tags: [], + answer: { + id: '1', + content: 'This is a mock answer for: ' + question, + keyPoints: [], + followUpQuestions: [], + estimatedDuration: 30, + personalizedContext: '' + }, + relatedSkills: [], + confidence: 0.8, + timesUsed: 0, + source: 'mock' + }; + + const answer = { + question: mockQuestion, + answer: 'This is a mock answer for: ' + question + }; + + if (answer) { + this.currentQuestion.set(answer.question); + this.suggestedAnswer.set(answer.answer); + + const responseTime = Date.now() - this.responseStartTime; + this.updateResponseTime(responseTime); + + console.log('Answer found', { + question: question, + responseTime: responseTime + }); + } + + } catch (error) { + console.error('Failed to find answer', error); + } finally { + this.isProcessingAnswer.set(false); + } + } + + onAnswerSelected(answer: string) { + this.answersProvided.update(count => count + 1); + + // Add to session history + const session = this.currentSession(); + if (session) { + const qa: QuestionAnswer = { + question: this.detectedQuestion(), + answer: answer, + timestamp: new Date(), + responseTime: Date.now() - this.responseStartTime, + source: 'question-bank' + }; + + session.questionsAnswered.push(qa); + } + + // this.analyticsService.trackAnswerProvided(answer); + } + + onCommentAdded(comment: string) { + const session = this.currentSession(); + if (session) { + session.manualComments.push({ + id: `comment-${Date.now()}`, + content: comment, + timestamp: new Date(), + category: CommentCategory.OBSERVATION, + tags: [] + }); + } + } + + private updateResponseTime(responseTime: number) { + const currentAvg = this.averageResponseTime(); + const count = this.answersProvided(); + const newAvg = count === 0 ? responseTime : (currentAvg * count + responseTime) / (count + 1); + this.averageResponseTime.set(Math.round(newAvg)); + } + + private loadSessionHistory() { + // Load previous sessions from storage + // This would typically come from a service + this.sessionHistory.set([]); + } + + private saveSession(session: InterviewSession) { + // Save session to storage and update history + const history = this.sessionHistory(); + history.unshift(session); + this.sessionHistory.set([...history.slice(0, 10)]); // Keep last 10 sessions + } + + viewSession(session: InterviewSession) { + // Navigate to session details view + console.log('Viewing session details', { sessionId: session.id }); + } + + formatSessionTime(seconds: number): string { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + formatDate(date: Date): string { + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); + } + + formatDuration(seconds: number): string { + const mins = Math.floor(seconds / 60); + return `${mins}m ${seconds % 60}s`; + } +} \ No newline at end of file diff --git a/src/app/components/language-selector/language-selector.component.scss b/src/app/components/language-selector/language-selector.component.scss new file mode 100644 index 0000000..4a8fc74 --- /dev/null +++ b/src/app/components/language-selector/language-selector.component.scss @@ -0,0 +1,436 @@ +.language-selector { + background: var(--surface-color, #ffffff); + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 8px; + padding: 16px; + margin: 16px 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: var(--text-primary, #333333); + + .selector-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color, #e0e0e0); + + h4 { + margin: 0; + color: var(--text-primary, #333); + font-size: 1.1rem; + font-weight: 600; + } + + .current-language { + .language-indicator { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--accent-color, #007bff); + color: white; + padding: 4px 12px; + border-radius: 16px; + font-size: 0.85rem; + font-weight: 500; + } + } + } + + .language-dropdown { + margin-bottom: 20px; + + label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--text-primary, #333); + } + + .language-select { + width: 100%; + padding: 12px 16px; + padding-right: 40px; /* Space for dropdown arrow */ + border: 2px solid var(--border-color, #d0d0d0); + border-radius: 8px; + background: var(--input-background, #ffffff); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m6 9 6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 16px; + font-size: 1rem; + font-weight: 500; + color: var(--text-primary, #333333) !important; + cursor: pointer; + transition: all 0.2s ease; + + /* Ensure text is always visible */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + &:hover { + border-color: var(--accent-color, #007bff); + background-color: var(--background-light, #f8f9fa); + } + + &:focus { + outline: none; + border-color: var(--accent-color, #007bff); + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15); + background-color: var(--input-background, #ffffff); + } + + option { + padding: 12px 16px; + background: var(--input-background, #ffffff) !important; + color: var(--text-primary, #333333) !important; + font-size: 0.95rem; + font-weight: 500; + + &:checked { + background: var(--accent-color, #007bff) !important; + color: white !important; + } + + &:hover { + background: var(--background-light, #f8f9fa) !important; + } + } + } + } + + .language-info { + background: var(--background-light, #f8f9fa); + padding: 16px; + border-radius: 6px; + margin-bottom: 16px; + + .info-section { + margin-bottom: 16px; + + h5 { + margin: 0 0 12px 0; + color: var(--text-primary, #333); + font-size: 0.95rem; + font-weight: 600; + } + + .feature-list { + display: flex; + flex-direction: column; + gap: 8px; + + .feature-item { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.9rem; + + .feature-label { + color: var(--text-secondary, #666); + } + + .feature-value { + color: var(--text-primary, #333); + font-weight: 500; + } + } + } + } + + .example-patterns { + h5 { + margin: 0 0 12px 0; + color: var(--text-primary, #333); + font-size: 0.95rem; + font-weight: 600; + } + + .pattern-examples { + display: flex; + flex-wrap: wrap; + gap: 8px; + + .pattern-tag { + background: var(--accent-color, #007bff); + color: white; + padding: 4px 8px; + border-radius: 12px; + font-size: 0.8rem; + font-weight: 500; + } + } + } + } + + .auto-detect-section { + background: var(--background-light, #f8f9fa); + padding: 12px; + border-radius: 6px; + margin-bottom: 16px; + + .checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 0.9rem; + color: var(--text-primary, #333); + + input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent-color, #007bff); + } + } + + .auto-detect-info { + margin-top: 8px; + padding-left: 24px; + + small { + color: var(--text-secondary, #666); + font-size: 0.8rem; + + .mismatch { + color: var(--warning-color, #ff9800); + font-weight: 500; + } + } + } + } + + .language-actions { + display: flex; + gap: 12px; + margin-bottom: 16px; + + .btn { + padding: 8px 16px; + border: 1px solid var(--border-color, #d0d0d0); + border-radius: 6px; + background: white; + color: var(--text-primary, #333); + cursor: pointer; + font-size: 0.85rem; + font-weight: 500; + transition: all 0.2s ease; + + &:hover { + background: var(--background-light, #f8f9fa); + } + + &.btn-outline { + background: transparent; + + &:hover { + background: var(--accent-color, #007bff); + color: white; + border-color: var(--accent-color, #007bff); + } + } + + &.btn-sm { + padding: 6px 12px; + font-size: 0.8rem; + } + } + } + + .language-test { + background: var(--info-background, #e3f2fd); + border: 1px solid var(--info-border, #90caf9); + border-radius: 6px; + padding: 16px; + margin-bottom: 16px; + + .test-instructions { + h5 { + margin: 0 0 12px 0; + color: var(--info-text, #0d47a1); + font-size: 0.95rem; + font-weight: 600; + } + + p { + margin: 0 0 12px 0; + color: var(--text-primary, #333); + font-size: 0.9rem; + } + + .test-examples { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 12px; + + .example { + background: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 0.85rem; + font-style: italic; + color: var(--text-secondary, #666); + border-left: 3px solid var(--accent-color, #007bff); + } + } + + .test-controls { + .btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + background: var(--accent-color, #007bff); + color: white; + cursor: pointer; + font-size: 0.85rem; + font-weight: 500; + transition: background-color 0.2s ease; + + &:hover { + background: var(--accent-color-dark, #0056b3); + } + + &.btn-primary { + background: var(--accent-color, #007bff); + + &:hover { + background: var(--accent-color-dark, #0056b3); + } + } + + &.btn-sm { + padding: 6px 12px; + font-size: 0.8rem; + } + } + } + } + } + + .test-result { + border-radius: 6px; + padding: 12px; + margin-bottom: 16px; + + &.success { + background: var(--success-background, #e8f5e8); + border: 1px solid var(--success-border, #4caf50); + + .result-header { + color: var(--success-text, #2e7d32); + } + } + + &:not(.success) { + background: var(--error-background, #ffebee); + border: 1px solid var(--error-border, #f44336); + + .result-header { + color: var(--error-text, #c62828); + } + } + + .result-header { + font-weight: 600; + font-size: 0.9rem; + margin-bottom: 8px; + } + + .result-details { + font-size: 0.85rem; + + .transcript { + margin-bottom: 4px; + + strong { + color: var(--text-primary, #333); + } + } + + .confidence { + margin-bottom: 4px; + + strong { + color: var(--text-primary, #333); + } + } + + .error { + color: var(--error-text, #c62828); + + strong { + color: var(--error-text, #c62828); + } + } + } + } + + // Responsive design + @media (max-width: 768px) { + padding: 12px; + + .selector-header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .language-actions { + flex-direction: column; + } + + .test-examples { + .example { + font-size: 0.8rem; + } + } + } +} + +// Default light mode variables +.language-selector { + --surface-color: #ffffff; + --border-color: #e0e0e0; + --text-primary: #333333; + --text-secondary: #666666; + --background-light: #f8f9fa; + --accent-color: #007bff; + --accent-color-dark: #0056b3; + --input-background: #ffffff; + --info-background: #e3f2fd; + --info-border: #90caf9; + --info-text: #0d47a1; + --success-background: #e8f5e8; + --success-border: #4caf50; + --success-text: #2e7d32; + --error-background: #ffebee; + --error-border: #f44336; + --error-text: #c62828; + --warning-color: #ff9800; +} + +// Dark mode support +@media (prefers-color-scheme: dark) { + .language-selector { + --surface-color: #2d2d2d; + --border-color: #404040; + --text-primary: #ffffff; + --text-secondary: #cccccc; + --background-light: #3a3a3a; + --accent-color: #4a9eff; + --accent-color-dark: #357abd; + --input-background: #404040; + --info-background: #1a2937; + --info-border: #2196f3; + --info-text: #64b5f6; + --success-background: #1b2e1b; + --success-border: #4caf50; + --success-text: #81c784; + --error-background: #2e1b1b; + --error-border: #f44336; + --error-text: #e57373; + --warning-color: #ffb74d; + } +} \ No newline at end of file diff --git a/src/app/components/language-selector/language-selector.component.ts b/src/app/components/language-selector/language-selector.component.ts new file mode 100644 index 0000000..bcbd2af --- /dev/null +++ b/src/app/components/language-selector/language-selector.component.ts @@ -0,0 +1,348 @@ +import { Component, OnInit, OnDestroy, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Subject, takeUntil } from 'rxjs'; +import { LanguageConfigService, SpeechLanguage } from '../../services/language-config.service'; + +@Component({ + selector: 'app-language-selector', + standalone: true, + imports: [CommonModule, FormsModule], + template: ` +
+
+

🌐 Speech Recognition Language

+
+ @if (currentLanguage()) { + + {{ currentLanguage()?.flag }} {{ currentLanguage()?.nativeName }} + + } +
+
+ +
+ + +
+ +
+ @if (currentLanguage()) { +
+
Language Features:
+
+
+ Question Patterns: + {{ currentLanguage()!.questionPatterns.length }} patterns +
+
+ Hint Patterns: + {{ currentLanguage()!.hintPatterns.length }} patterns +
+
+
+ +
+
Example Question Words:
+
+ @switch (currentLanguage()?.code) { + @case ('en-US') { + what + how + why + when + } + @case ('fr-FR') { + qu'est-ce que + comment + pourquoi + quand + } + @case ('es-ES') { + qué + cómo + por qué + cuándo + } + @case ('de-DE') { + was + wie + warum + wann + } + } +
+
+ } +
+ +
+ + + @if (autoDetectEnabled()) { +
+ + 🔍 Detected: {{ browserLanguage() }} + @if (browserLanguage() !== currentLanguage()?.code) { + (Override active) + } + +
+ } +
+ +
+ + +
+ + @if (testingLanguage()) { +
+
+
🎙️ Language Test
+

Say a question in {{ currentLanguage()?.nativeName }} to test recognition:

+ @switch (currentLanguage()?.code) { + @case ('en-US') { +
+ "What is your experience?" + "How do you handle challenges?" +
+ } + @case ('fr-FR') { +
+ "Qu'est-ce que votre expérience?" + "Comment gérez-vous les défis?" +
+ } + @case ('es-ES') { +
+ "¿Cuál es tu experiencia?" + "¿Cómo manejas los desafíos?" +
+ } + @case ('de-DE') { +
+ "Was ist deine Erfahrung?" + "Wie gehst du mit Herausforderungen um?" +
+ } + } +
+ +
+
+
+ } + + @if (lastTestResult()) { +
+
+ {{ lastTestResult()?.success ? '✅' : '❌' }} + Test {{ lastTestResult()?.success ? 'Successful' : 'Failed' }} +
+
+ @if (lastTestResult()?.transcript) { +
+ Recognized: "{{ lastTestResult()?.transcript }}" +
+
+ Confidence: {{ (lastTestResult()?.confidence || 0) * 100 | number:'1.0-0' }}% +
+ } + @if (lastTestResult()?.error) { +
+ Error: {{ lastTestResult()?.error }} +
+ } +
+
+ } +
+ `, + styleUrl: './language-selector.component.scss' +}) +export class LanguageSelectorComponent implements OnInit, OnDestroy { + private languageConfigService = inject(LanguageConfigService); + private destroy$ = new Subject(); + + // Component state + selectedLanguageCode = ''; + supportedLanguages = signal([]); + currentLanguage = signal(null); + autoDetectEnabled = signal(false); + browserLanguage = signal(''); + testingLanguage = signal(false); + lastTestResult = signal<{ + success: boolean; + transcript?: string; + confidence?: number; + error?: string; + } | null>(null); + + ngOnInit() { + this.loadSupportedLanguages(); + this.setupLanguageSubscription(); + this.loadConfiguration(); + this.detectBrowserLanguage(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private loadSupportedLanguages() { + const languages = this.languageConfigService.getSupportedLanguages(); + this.supportedLanguages.set(languages); + } + + private setupLanguageSubscription() { + this.languageConfigService.currentLanguage$ + .pipe(takeUntil(this.destroy$)) + .subscribe(languageCode => { + this.selectedLanguageCode = languageCode; + const language = this.languageConfigService.getLanguageByCode(languageCode); + this.currentLanguage.set(language || null); + }); + + this.languageConfigService.config$ + .pipe(takeUntil(this.destroy$)) + .subscribe(config => { + this.autoDetectEnabled.set(config.autoDetect); + }); + } + + private loadConfiguration() { + const currentLang = this.languageConfigService.getCurrentLanguage(); + this.selectedLanguageCode = currentLang; + + const language = this.languageConfigService.getLanguageByCode(currentLang); + this.currentLanguage.set(language || null); + + const config = this.languageConfigService.getConfig(); + this.autoDetectEnabled.set(config.autoDetect); + } + + private detectBrowserLanguage() { + const browserLang = navigator.language || navigator.languages?.[0] || 'en-US'; + this.browserLanguage.set(browserLang); + } + + onLanguageChange(languageCode: string) { + this.languageConfigService.setLanguage(languageCode); + + // Clear previous test results when language changes + this.lastTestResult.set(null); + + console.log(`Language changed to: ${languageCode}`); + } + + onAutoDetectChange(event: Event) { + const checkbox = event.target as HTMLInputElement; + const autoDetect = checkbox.checked; + + this.languageConfigService.updateConfig({ autoDetect }); + + if (autoDetect) { + // Trigger auto-detection + const detectedLanguage = this.languageConfigService.autoDetectLanguage(); + console.log(`Auto-detected language: ${detectedLanguage}`); + } + } + + resetToDefaults() { + this.languageConfigService.resetToDefaults(); + this.lastTestResult.set(null); + console.log('Language settings reset to defaults'); + } + + testLanguage() { + if (this.testingLanguage()) { + this.stopLanguageTest(); + return; + } + + this.testingLanguage.set(true); + this.lastTestResult.set(null); + + // Simple test using Web Speech API + if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { + const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; + const recognition = new SpeechRecognition(); + + recognition.lang = this.currentLanguage()?.code || 'en-US'; + recognition.continuous = false; + recognition.interimResults = false; + + recognition.onresult = (event: any) => { + const result = event.results[0]; + const transcript = result[0].transcript; + const confidence = result[0].confidence || 0.9; + + this.lastTestResult.set({ + success: true, + transcript, + confidence + }); + + this.stopLanguageTest(); + }; + + recognition.onerror = (event: any) => { + this.lastTestResult.set({ + success: false, + error: event.error || 'Recognition failed' + }); + + this.stopLanguageTest(); + }; + + recognition.onend = () => { + this.testingLanguage.set(false); + }; + + try { + recognition.start(); + } catch (error) { + this.lastTestResult.set({ + success: false, + error: 'Failed to start speech recognition' + }); + this.stopLanguageTest(); + } + } else { + this.lastTestResult.set({ + success: false, + error: 'Speech recognition not supported in this browser' + }); + this.stopLanguageTest(); + } + } + + stopLanguageTest() { + this.testingLanguage.set(false); + } +} \ No newline at end of file diff --git a/src/app/components/question-display/question-display.component.scss b/src/app/components/question-display/question-display.component.scss new file mode 100644 index 0000000..33f143c --- /dev/null +++ b/src/app/components/question-display/question-display.component.scss @@ -0,0 +1,421 @@ +.question-display-container { + position: relative; + margin-bottom: 2rem; +} + +.question-panel { + background: white; + border: 2px solid #007bff; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 4px 12px rgba(0, 123, 255, 0.15); + animation: slideIn 0.3s ease-out; + + .panel-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid #e9ecef; + + h3 { + margin: 0; + color: #007bff; + font-weight: 600; + } + + .processing-indicator { + display: flex; + align-items: center; + gap: 0.5rem; + color: #6c757d; + font-size: 0.9rem; + + .spinner { + width: 16px; + height: 16px; + border: 2px solid #f3f3f3; + border-top: 2px solid #007bff; + border-radius: 50%; + animation: spin 1s linear infinite; + } + } + + .response-time { + color: #28a745; + font-weight: 600; + font-size: 0.9rem; + background: #d4edda; + padding: 0.25rem 0.5rem; + border-radius: 4px; + } + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.question-section { + margin-bottom: 1.5rem; + + h4 { + margin: 0 0 0.75rem 0; + color: #495057; + font-size: 1rem; + font-weight: 600; + } + + .question-text { + background: #f8f9fa; + padding: 1rem; + border-radius: 6px; + border-left: 4px solid #007bff; + font-size: 1.1rem; + line-height: 1.5; + color: #212529; + margin-bottom: 0.5rem; + } + + .question-category { + .category-tag { + background: #007bff; + color: white; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + } + } +} + +.answer-section { + margin-bottom: 1.5rem; + + .answer-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + + h4 { + margin: 0; + color: #495057; + font-size: 1rem; + font-weight: 600; + } + + .answer-actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + + .btn { + padding: 0.375rem 0.75rem; + border: none; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + + &.btn-primary { + background: #28a745; + color: white; + + &:hover { + background: #1e7e34; + } + } + + &.btn-outline { + background: transparent; + border: 1px solid #6c757d; + color: #6c757d; + + &:hover { + background: #6c757d; + color: white; + } + } + + &.btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + } + } + } + } + + .answer-text { + background: #e8f5e8; + padding: 1.25rem; + border-radius: 6px; + border-left: 4px solid #28a745; + line-height: 1.6; + color: #212529; + white-space: pre-line; + transition: max-height 0.3s ease; + + &.collapsed { + max-height: 100px; + overflow: hidden; + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 30px; + background: linear-gradient(transparent, #e8f5e8); + } + } + } + + .answer-truncated-indicator { + margin-top: 0.5rem; + + .btn-link { + background: none; + border: none; + color: #007bff; + cursor: pointer; + font-size: 0.9rem; + text-decoration: underline; + + &:hover { + color: #0056b3; + } + } + } + + .answer-source { + margin-top: 0.5rem; + text-align: right; + + small { + color: #6c757d; + font-style: italic; + } + } +} + +.no-answer-section { + margin-bottom: 1.5rem; + + .no-answer-message { + background: #fff3cd; + border: 1px solid #ffeaa7; + border-radius: 6px; + padding: 1.25rem; + + h4 { + margin: 0 0 0.75rem 0; + color: #856404; + font-size: 1rem; + } + + p { + margin: 0 0 1rem 0; + color: #856404; + } + + .fallback-actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + + .btn { + padding: 0.5rem 1rem; + border: 1px solid #856404; + background: transparent; + color: #856404; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.3s ease; + + &:hover { + background: #856404; + color: white; + } + } + } + } +} + +.alternatives-section { + margin-bottom: 1.5rem; + + h4 { + margin: 0 0 1rem 0; + color: #495057; + font-size: 1rem; + font-weight: 600; + } + + .alternatives-list { + display: flex; + flex-direction: column; + gap: 0.75rem; + + .alternative-item { + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 6px; + padding: 1rem; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + border-color: #007bff; + background: #e7f3ff; + } + + .alt-preview { + color: #212529; + margin-bottom: 0.5rem; + line-height: 1.4; + } + + .alt-meta { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.8rem; + + .alt-confidence { + color: #28a745; + font-weight: 600; + } + + .alt-category { + background: #6c757d; + color: white; + padding: 0.2rem 0.4rem; + border-radius: 8px; + text-transform: uppercase; + letter-spacing: 0.3px; + } + } + } + } +} + +.quick-actions { + display: flex; + gap: 0.5rem; + justify-content: center; + flex-wrap: wrap; + padding-top: 1rem; + border-top: 1px solid #e9ecef; + + .btn { + padding: 0.5rem 1rem; + border: none; + background: transparent; + color: #6c757d; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; + transition: all 0.3s ease; + + &.btn-ghost { + &:hover { + background: #f8f9fa; + color: #495057; + } + } + + &.btn-sm { + padding: 0.375rem 0.75rem; + font-size: 0.8rem; + } + } +} + +.feedback-toast { + position: fixed; + top: 20px; + right: 20px; + background: #dc3545; + color: white; + padding: 1rem 1.5rem; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + animation: slideInRight 0.3s ease-out; + z-index: 1000; + + &.success { + background: #28a745; + } +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@media (max-width: 768px) { + .question-panel { + padding: 1rem; + margin: 0.5rem; + } + + .answer-header { + flex-direction: column; + align-items: flex-start !important; + gap: 0.75rem; + + .answer-actions { + width: 100%; + justify-content: space-between; + } + } + + .alternatives-list .alternative-item .alt-meta { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } + + .quick-actions { + flex-direction: column; + + .btn { + width: 100%; + } + } + + .feedback-toast { + position: fixed; + top: auto; + bottom: 20px; + left: 20px; + right: 20px; + text-align: center; + } +} \ No newline at end of file diff --git a/src/app/components/question-display/question-display.component.ts b/src/app/components/question-display/question-display.component.ts new file mode 100644 index 0000000..bd6537c --- /dev/null +++ b/src/app/components/question-display/question-display.component.ts @@ -0,0 +1,294 @@ +import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Question } from '../../models/question-bank.interface'; + +@Component({ + selector: 'app-question-display', + standalone: true, + imports: [CommonModule], + template: ` +
+ @if (currentQuestion() || suggestedAnswer() || isProcessing()) { +
+
+

💡 Interview Assistant

+ @if (isProcessing()) { +
+
+ Finding answer... +
+ } @else if (responseTime()) { +
+ ⚡ {{ responseTime() }}ms +
+ } +
+ + @if (currentQuestion()) { +
+

🎯 Detected Question:

+
+ {{ currentQuestion()!.text }} +
+ @if (currentQuestion()!.category) { +
+ {{ currentQuestion()!.category }} +
+ } +
+ } + + @if (suggestedAnswer() && !isProcessing()) { +
+
+

💬 Suggested Answer:

+
+ + + +
+
+ +
+ {{ formatAnswer(suggestedAnswer()) }} +
+ + @if (isAnswerLong() && !isAnswerExpanded()) { +
+ +
+ } + + @if (currentQuestion()?.source) { +
+ Source: {{ getSourceDisplay(currentQuestion()?.source || '') }} +
+ } +
+ } + + @if (!suggestedAnswer() && !isProcessing() && currentQuestion()) { +
+
+

❓ No Pre-generated Answer Found

+

This question wasn't in your preparation bank. Consider these options:

+
+ + + +
+
+
+ } + + + @if (alternativeAnswers().length > 0) { +
+

🔄 Alternative Answers:

+
+ @for (alt of alternativeAnswers(); track alt.id) { +
+
{{ getAnswerPreview(alt.answer) }}
+
+ {{ alt.confidence }}% match + @if (alt.category) { + {{ alt.category }} + } +
+
+ } +
+
+ } + + +
+ + + + +
+
+ } + + + @if (showFeedbackToast()) { + + } +
+ `, + styleUrl: './question-display.component.scss' +}) +export class QuestionDisplayComponent { + @Input() currentQuestion = signal(null); + @Input() suggestedAnswer = signal(''); + @Input() isProcessing = signal(false); + @Input() responseTime = signal(null); + + @Output() answerSelected = new EventEmitter(); + @Output() questionSkipped = new EventEmitter(); + @Output() answerRequested = new EventEmitter(); + @Output() feedbackGiven = new EventEmitter<{question: string, answer: string, helpful: boolean}>(); + + // Component state + isAnswerExpanded = signal(false); + alternativeAnswers = signal<{id: string, answer: string, confidence: number, category?: string}[]>([]); + showFeedbackToast = signal(false); + lastFeedbackPositive = signal(true); + + // Computed properties + isAnswerLong = computed(() => { + const answer = this.suggestedAnswer(); + return answer.length > 200 || answer.split(' ').length > 50; + }); + + selectAnswer(answer: string) { + this.answerSelected.emit(answer); + this.trackAnswerUsage(answer); + } + + copyToClipboard(text: string) { + navigator.clipboard.writeText(text).then(() => { + // Could show a toast notification + }); + } + + toggleAnswerExpanded() { + this.isAnswerExpanded.update(expanded => !expanded); + } + + formatAnswer(answer: string): string { + // Basic formatting for better readability + return answer + .replace(/\. /g, '.\n\n') + .replace(/: /g, ':\n') + .trim(); + } + + getAnswerWordCount(): number { + return this.suggestedAnswer().split(' ').length; + } + + getAnswerPreview(answer: string): string { + const words = answer.split(' '); + return words.length > 15 ? words.slice(0, 15).join(' ') + '...' : answer; + } + + getSourceDisplay(source: string): string { + switch (source) { + case 'cv-analysis': return 'CV Analysis'; + case 'question-bank': return 'Question Bank'; + case 'manual-input': return 'Manual Notes'; + case 'ai-generated': return 'AI Generated'; + default: return source; + } + } + + requestNewAnswer() { + const question = this.currentQuestion(); + if (question) { + this.answerRequested.emit(question.text); + } + } + + addToQuestionBank() { + const question = this.currentQuestion(); + if (question) { + // Open modal or form to add question to bank + console.log('Adding to question bank:', question.text); + } + } + + skipQuestion() { + this.questionSkipped.emit(); + } + + markAsHelpful(helpful: boolean) { + const question = this.currentQuestion(); + const answer = this.suggestedAnswer(); + + if (question && answer) { + this.feedbackGiven.emit({ + question: question.text, + answer: answer, + helpful: helpful + }); + + this.lastFeedbackPositive.set(helpful); + this.showFeedbackToast.set(true); + + // Hide toast after 3 seconds + setTimeout(() => { + this.showFeedbackToast.set(false); + }, 3000); + } + } + + requestImprovement() { + // Open improvement request modal + console.log('Improvement requested for:', this.currentQuestion()?.text); + } + + closePanel() { + // Reset all signals to hide the panel + this.currentQuestion.set(null); + this.suggestedAnswer.set(''); + this.isProcessing.set(false); + this.responseTime.set(null); + this.isAnswerExpanded.set(false); + this.alternativeAnswers.set([]); + } + + private trackAnswerUsage(answer: string) { + // Track which answers are being used for analytics + console.log('Answer used:', answer.substring(0, 50) + '...'); + } + + // Mock alternative answers (would come from service in real implementation) + private loadAlternativeAnswers(questionText: string) { + // This would be called when a question is set + // For now, just mock data + this.alternativeAnswers.set([ + { + id: '1', + answer: 'Alternative explanation focusing on practical examples...', + confidence: 85, + category: 'practical' + }, + { + id: '2', + answer: 'More technical explanation with code examples...', + confidence: 78, + category: 'technical' + } + ]); + } +} \ No newline at end of file diff --git a/src/app/models/cv-profile.interface.ts b/src/app/models/cv-profile.interface.ts new file mode 100644 index 0000000..a66d5d3 --- /dev/null +++ b/src/app/models/cv-profile.interface.ts @@ -0,0 +1,121 @@ +// CV Profile Data Model Interfaces + +export interface CVProfile { + id: string; // UUID + fileName: string; // Original CV filename + uploadDate: Date; // When uploaded + personalInfo: PersonalInfo; // Extracted personal details + experience: WorkExperience[]; // Job history + education: Education[]; // Educational background + skills: Skill[]; // Technical and soft skills + certifications: Certification[]; // Professional certifications + languages: Language[]; // Spoken languages + parsedText: string; // Full extracted text + extractedText?: string; // Alternative property name for compatibility + lastModified: Date; // Last update timestamp + sensitiveDataWarnings?: string[]; // Warnings about sensitive information +} + +export interface PersonalInfo { + fullName: string; + email: string; + phone: string; + location: string; + linkedIn?: string; + github?: string; + website?: string; +} + +export interface WorkExperience { + id: string; + company: string; + position: string; + startDate: Date; + endDate?: Date; // null for current position + description: string; + technologies: string[]; // Tech stack used + achievements: string[]; // Key accomplishments +} + +export interface Education { + id: string; + institution: string; + degree: string; + field: string; + startDate: Date; + endDate?: Date; + gpa?: number; + honors?: string[]; +} + +export interface Skill { + id: string; + name: string; + category: SkillCategory; // Technical, Soft, Language, etc. + proficiency: SkillLevel; // Beginner, Intermediate, Advanced, Expert + yearsOfExperience?: number; + lastUsed?: Date; +} + +export interface Certification { + id: string; + name: string; + issuer: string; + issueDate: Date; + expiryDate?: Date; + credentialId?: string; +} + +export interface Language { + id: string; + name: string; + proficiency: LanguageProficiency; // Native, Fluent, Conversational, Basic +} + +// Enumerations for CV Profile +export enum SkillCategory { + TECHNICAL = 'technical', + SOFT = 'soft', + LANGUAGE = 'language', + CERTIFICATION = 'certification' +} + +export enum SkillLevel { + BEGINNER = 'beginner', + INTERMEDIATE = 'intermediate', + ADVANCED = 'advanced', + EXPERT = 'expert' +} + +export enum LanguageProficiency { + NATIVE = 'native', + FLUENT = 'fluent', + CONVERSATIONAL = 'conversational', + BASIC = 'basic' +} + +// Utility types for CV processing +export interface CVParsingResult { + profile: CVProfile; + confidence: number; + warnings: string[]; + suggestions: string[]; +} + +export interface CVCorrections { + personalInfo?: Partial; + experience?: WorkExperience[]; + education?: Education[]; + skills?: Skill[]; + certifications?: Certification[]; + languages?: Language[]; +} + +export interface ParsingAccuracy { + overall: number; + personalInfo: number; + experience: number; + education: number; + skills: number; + certifications: number; +} \ No newline at end of file diff --git a/src/app/models/interview-session.interface.ts b/src/app/models/interview-session.interface.ts new file mode 100644 index 0000000..1549abd --- /dev/null +++ b/src/app/models/interview-session.interface.ts @@ -0,0 +1,166 @@ +// Interview Session Data Model Interfaces + +export interface InterviewSession { + id: string; // UUID + cvProfileId: string; // Associated CV + questionBankId: string; // Used question bank + startTime: Date; // Session start + endTime?: Date; // Session end (null if ongoing) + duration: number; // Session duration in seconds + detectedQuestions: DetectedQuestion[]; // Questions heard + providedAnswers: ProvidedAnswer[]; // Responses given + questionsAnswered: QuestionAnswer[]; // Legacy compatibility + manualComments: ManualComment[]; // User notes + speechHints: SpeechHint[]; // Conversation hints + analytics: SessionAnalytics; // Performance data + status: SessionStatus; // Active, Paused, Completed +} + +export interface DetectedQuestion { + id: string; + timestamp: Date; // When detected + originalText: string; // Exact speech heard + normalizedText: string; // Cleaned/standardized version + confidence: number; // Detection confidence (0-1) + questionBankMatch?: string; // Matched question ID + responseTime: number; // ms to provide answer + wasHelpRequested: boolean; // User asked for help + isQuestion: boolean; // Determined to be a question vs statement +} + +export interface ProvidedAnswer { + id: string; + questionId: string; // Reference to detected question + answerId?: string; // Question bank answer used + content: string; // Actual response provided + timestamp: Date; // When provided + source: AnswerSource; // QuestionBank, Generated, Manual + userRating?: number; // User feedback (1-5) +} + +export interface ManualComment { + id: string; + timestamp: Date; + content: string; // User's note + category: CommentCategory; // Correction, Addition, Observation + relatedQuestionId?: string; // Associated question + tags: string[]; // Categorization +} + +export interface SpeechHint { + id: string; + timestamp: Date; + text: string; // Detected speech pattern + hintType: HintType; // QuestionIntro, TopicShift, Clarification + confidence: number; // Detection confidence + preparedAnswers: string[]; // Question bank IDs prepared + type: string; // Alternative naming for compatibility + detectedText: string; // Alternative naming for compatibility +} + +export interface SessionAnalytics { + totalQuestions: number; + answersFromBank: number; + answersGenerated: number; + averageResponseTime: number; // milliseconds + accuracyRate: number; // 0-1 + topicsCovered: string[]; // Main subjects discussed + difficultyDistribution?: { [key in QuestionDifficulty]: number }; +} + +// Enumerations for Interview Session +export enum SessionStatus { + ACTIVE = 'active', + PAUSED = 'paused', + COMPLETED = 'completed' +} + +export enum AnswerSource { + QUESTION_BANK = 'question_bank', + GENERATED = 'generated', + MANUAL = 'manual' +} + +export enum CommentCategory { + CORRECTION = 'correction', + ADDITION = 'addition', + OBSERVATION = 'observation', + IMPROVEMENT = 'improvement' +} + +export enum HintType { + QUESTION_INTRO = 'question_intro', + TOPIC_SHIFT = 'topic_shift', + CLARIFICATION = 'clarification', + FOLLOW_UP = 'follow_up' +} + +export enum QuestionDifficulty { + EASY = 'easy', + MEDIUM = 'medium', + HARD = 'hard' +} + +// Speech recognition interfaces +export interface SpeechResult { + transcript: string; + confidence: number; + isFinal: boolean; + timestamp: Date; +} + +export interface SpeechAnalysis { + originalText: string; + isQuestion: boolean; + confidence: number; + questionType?: string; + detectedPatterns: string[]; +} + +export interface SpeechError { + type: string; + message: string; + timestamp: Date; + canRetry: boolean; +} + +// Session management interfaces +export interface SessionSummary { + sessionId: string; + duration: number; // minutes + questionsAnswered: number; + successRate: number; + improvements: string[]; // Suggested enhancements + newQuestions: Question[]; // Questions to add to bank +} + +export interface SessionConfiguration { + enableSpeechRecognition: boolean; + enableQuestionHints: boolean; + responseTimeTarget: number; // milliseconds + confidenceThreshold: number; // minimum confidence for auto-response + maxSessionDuration: number; // minutes +} + +// Legacy compatibility interface +export interface QuestionAnswer { + question: string; + answer: string; + timestamp: Date; + responseTime: number; + source: string; +} + +// Import from question bank for dependencies +interface Question { + id: string; + text: string; + category: any; + difficulty: any; + tags: string[]; + answer: any; + relatedSkills: string[]; + confidence: number; + timesUsed: number; + lastUsed?: Date; +} \ No newline at end of file diff --git a/src/app/models/n8n-sync.interface.ts b/src/app/models/n8n-sync.interface.ts new file mode 100644 index 0000000..2f199b3 --- /dev/null +++ b/src/app/models/n8n-sync.interface.ts @@ -0,0 +1,241 @@ +// N8n Synchronization Data Model Interfaces + +import { CVProfile } from './cv-profile.interface'; +import { QuestionBank } from './question-bank.interface'; +import { SessionAnalytics, SessionSummary } from './interview-session.interface'; + +export interface N8nSyncData { + id: string; + sessionId: string; // Associated session + lastSyncTime: Date; // When last synced + syncStatus: SyncStatus; // Pending, InProgress, Success, Failed + dataSnapshot: { + cvProfile: CVProfile; + questionBank: QuestionBank; + sessionSummary: SessionSummary; + analytics: SessionAnalytics; + }; + errors?: SyncError[]; // Any sync failures +} + +export interface SyncError { + timestamp: Date; + errorCode: string; + message: string; + retryCount: number; +} + +export enum SyncStatus { + PENDING = 'pending', + IN_PROGRESS = 'in_progress', + SUCCESS = 'success', + FAILED = 'failed' +} + +// N8n API Request/Response Interfaces +export interface CVAnalysisRequest { + cvProfileId: string; + personalInfo: PersonalInfo; + experience: WorkExperience[]; + education: Education[]; + skills: Skill[]; + certifications: Certification[]; + parsedText: string; +} + +export interface CVAnalysisResponse { + analysisId: string; + status: 'processing' | 'completed' | 'failed'; + estimatedCompletionTime: number; + questionBankId: string; +} + +export interface QuestionBankResponse { + questionBankId: string; + cvProfileId: string; + generatedDate: string; + questions: Question[]; + metadata: QuestionBankMetadata; +} + +export interface SessionSyncRequest { + sessionId: string; + cvProfileId: string; + startTime?: string; + endTime?: string; + detectedQuestions: DetectedQuestion[]; + providedAnswers: ProvidedAnswer[]; + manualComments: ManualComment[]; + analytics: SessionAnalytics; +} + +export interface SessionSyncResponse { + syncId: string; + status: 'success' | 'partial' | 'failed'; + updatedQuestions: Question[]; + recommendations: string[]; + errors: SyncError[]; +} + +export interface AnalyticsRequest { + sessionId: string; + analytics: SessionAnalytics; + improvements: string[]; +} + +export interface AnalyticsResponse { + processed: boolean; + insights: string[]; + recommendedActions: string[]; +} + +// N8n Authentication interfaces +export interface AutheliaAuthToken { + token: string; + expiresAt: Date; + refreshToken?: string; +} + +export interface N8nApiConfig { + baseUrl: string; + authToken: string; + timeout: number; + retryAttempts: number; +} + +// Data transformation interfaces +export interface DataTransformer { + transform(input: TInput): TOutput; + validate(data: TOutput): boolean; +} + +export interface N8nDataMapper { + mapCVProfileToRequest(profile: CVProfile): CVAnalysisRequest; + mapResponseToQuestionBank(response: QuestionBankResponse): QuestionBank; + mapSessionToSyncRequest(session: InterviewSession): SessionSyncRequest; + mapSyncResponseToSession(response: SessionSyncResponse, session: InterviewSession): InterviewSession; +} + +// Import types from other interfaces (for compatibility) +interface PersonalInfo { + fullName: string; + email: string; + phone: string; + location: string; + linkedIn?: string; + github?: string; + website?: string; +} + +interface WorkExperience { + id: string; + company: string; + position: string; + startDate: Date; + endDate?: Date; + description: string; + technologies: string[]; + achievements: string[]; +} + +interface Education { + id: string; + institution: string; + degree: string; + field: string; + startDate: Date; + endDate?: Date; + gpa?: number; + honors?: string[]; +} + +interface Skill { + id: string; + name: string; + category: string; + proficiency: string; + yearsOfExperience?: number; + lastUsed?: Date; +} + +interface Certification { + id: string; + name: string; + issuer: string; + issueDate: Date; + expiryDate?: Date; + credentialId?: string; +} + +interface Question { + id: string; + text: string; + category: string; + difficulty: string; + tags: string[]; + answer: Answer; + relatedSkills: string[]; + confidence: number; + timesUsed: number; + lastUsed?: Date; +} + +interface Answer { + id: string; + content: string; + keyPoints: string[]; + followUpQuestions: string[]; + estimatedDuration: number; + personalizedContext: string; +} + +interface QuestionBankMetadata { + totalQuestions: number; + categoriesDistribution: { [key: string]: number }; + averageConfidence: number; + lastUpdated: string; +} + +interface DetectedQuestion { + id: string; + timestamp: Date; + originalText: string; + normalizedText: string; + confidence: number; + questionBankMatch?: string; + responseTime: number; + wasHelpRequested: boolean; +} + +interface ProvidedAnswer { + id: string; + questionId: string; + answerId?: string; + content: string; + timestamp: Date; + source: string; + userRating?: number; +} + +interface ManualComment { + id: string; + timestamp: Date; + content: string; + category: string; + relatedQuestionId?: string; + tags: string[]; +} + +interface InterviewSession { + id: string; + cvProfileId: string; + questionBankId: string; + startTime: Date; + endTime?: Date; + detectedQuestions: DetectedQuestion[]; + providedAnswers: ProvidedAnswer[]; + manualComments: ManualComment[]; + speechHints: any[]; + analytics: SessionAnalytics; + status: string; +} \ No newline at end of file diff --git a/src/app/models/question-bank.interface.ts b/src/app/models/question-bank.interface.ts new file mode 100644 index 0000000..867f66f --- /dev/null +++ b/src/app/models/question-bank.interface.ts @@ -0,0 +1,141 @@ +// Question Bank Data Model Interfaces + +export interface QuestionBank { + id: string; // UUID + cvProfileId: string; // Associated CV profile + questions: Question[]; // Generated questions + generatedDate: Date; // When created + lastUsed: Date; // Last accessed + accuracy: number; // Success rate (0-1) + metadata: QuestionBankMetadata; +} + +export interface Question { + id: string; + text: string; // The actual question + category: QuestionCategory; // Technical, Behavioral, Experience + difficulty: QuestionDifficulty; // Easy, Medium, Hard + tags: string[]; // Keywords for matching + answer: Answer; // Prepared response + relatedSkills: string[]; // Associated skills from CV + confidence: number; // AI confidence in answer (0-1) + timesUsed: number; // Usage frequency + lastUsed?: Date; // Last accessed + source?: string; // Source of the question (cv-analysis, question-bank, etc.) +} + +export interface Answer { + id: string; + questionId?: string; // Reference to parent question + content: string; // Main response text + keyPoints: string[]; // Bullet points for quick reference + followUpQuestions: string[]; // Potential follow-ups + estimatedDuration: number; // Seconds to deliver + personalizedContext: string; // CV-specific details to include +} + +export interface QuestionBankMetadata { + totalQuestions: number; + categoriesDistribution: { [key: string]: number }; + averageConfidence: number; + lastUpdated: string; +} + +// Enumerations for Question Bank +export enum QuestionCategory { + TECHNICAL = 'technical', + BEHAVIORAL = 'behavioral', + EXPERIENCE = 'experience', + SITUATIONAL = 'situational', + COMPANY_SPECIFIC = 'company_specific', + EDUCATION = 'education', + GENERAL = 'general' +} + +export enum QuestionDifficulty { + EASY = 'easy', + MEDIUM = 'medium', + HARD = 'hard' +} + +// Question matching and retrieval interfaces +export interface QuestionMatch { + question: Question; + similarity: number; + matchType: 'exact' | 'high' | 'medium' | 'low'; +} + +export interface QuestionSearchOptions { + maxResults?: number; + minConfidence?: number; + categories?: QuestionCategory[]; + difficulties?: QuestionDifficulty[]; + fuzzyThreshold?: number; +} + +export interface QuestionBankOptimization { + pruneThreshold: number; // Remove questions below this confidence + maxQuestions: number; // Maximum questions to keep + categoryBalance: boolean; // Maintain category distribution +} + +// Question generation interfaces +export interface QuestionGenerationRequest { + cvProfileId: string; + focusAreas: string[]; + difficulty: QuestionDifficulty[]; + maxQuestions: number; + personalizeAnswers: boolean; +} + +export interface QuestionGenerationResponse { + questionBankId: string; + questionsGenerated: number; + confidence: number; + estimatedAccuracy: number; + processingTime: number; +} + +// Additional interfaces for question-bank.service.ts +export interface QuestionSearchCriteria { + query?: string; + category?: QuestionCategory; + difficulty?: QuestionDifficulty; + minConfidence?: number; + tags?: string[]; + sortBy?: 'confidence' | 'lastUsed' | 'timesUsed' | 'text'; + sortOrder?: 'asc' | 'desc'; + page?: number; + limit?: number; +} + +export interface QuestionMatchResult { + hasMatch: boolean; + confidence: number; + matches: QuestionMatch[]; +} + +export interface QuestionSearchResult { + results: Question[]; + totalResults: number; + searchTime: number; +} + +export interface QuestionBankGeneration { + includeGeneral: boolean; + includeTechnical: boolean; + includeExperience: boolean; + includeEducation: boolean; + maxQuestionsPerCategory: number; + personalizeAnswers: boolean; +} + +export interface QuestionOptimization { + removeUnused?: boolean; + unusedThresholdDays?: number; + removeLowConfidence?: boolean; + confidenceThreshold?: number; + mergeSimilar?: boolean; + similarityThreshold?: number; + rebalanceCategories?: boolean; +} \ No newline at end of file diff --git a/src/app/models/validation.ts b/src/app/models/validation.ts new file mode 100644 index 0000000..dce2af3 --- /dev/null +++ b/src/app/models/validation.ts @@ -0,0 +1,460 @@ +// Validation Schemas and Utility Functions + +import { + CVProfile, + PersonalInfo, + WorkExperience, + Education, + Skill, + Certification, + Language, + SkillCategory, + SkillLevel, + LanguageProficiency +} from './cv-profile.interface'; + +import { + QuestionBank, + Question, + Answer, + QuestionCategory, + QuestionDifficulty +} from './question-bank.interface'; + +import { + InterviewSession, + DetectedQuestion, + ProvidedAnswer, + ManualComment, + SessionStatus, + AnswerSource, + CommentCategory +} from './interview-session.interface'; + +// Validation result interface +export interface ValidationResult { + isValid: boolean; + errors: ValidationError[]; + warnings: ValidationWarning[]; +} + +export interface ValidationError { + field: string; + message: string; + code: string; + value?: any; +} + +export interface ValidationWarning { + field: string; + message: string; + suggestion?: string; +} + +// CV Profile Validation +export class CVProfileValidator { + static validate(profile: CVProfile): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate required fields + if (!profile.id || !this.isValidUUID(profile.id)) { + errors.push({ + field: 'id', + message: 'Invalid or missing UUID', + code: 'INVALID_UUID' + }); + } + + if (!profile.fileName || profile.fileName.trim().length === 0) { + errors.push({ + field: 'fileName', + message: 'File name is required', + code: 'REQUIRED_FIELD' + }); + } + + // Validate personal info + const personalInfoResult = this.validatePersonalInfo(profile.personalInfo); + errors.push(...personalInfoResult.errors); + warnings.push(...personalInfoResult.warnings); + + // Validate experience + profile.experience.forEach((exp, index) => { + const expResult = this.validateWorkExperience(exp); + expResult.errors.forEach(error => { + errors.push({ + ...error, + field: `experience[${index}].${error.field}` + }); + }); + }); + + // Validate education + profile.education.forEach((edu, index) => { + const eduResult = this.validateEducation(edu); + eduResult.errors.forEach(error => { + errors.push({ + ...error, + field: `education[${index}].${error.field}` + }); + }); + }); + + // Validate skills + profile.skills.forEach((skill, index) => { + const skillResult = this.validateSkill(skill); + skillResult.errors.forEach(error => { + errors.push({ + ...error, + field: `skills[${index}].${error.field}` + }); + }); + }); + + return { + isValid: errors.length === 0, + errors, + warnings + }; + } + + private static validatePersonalInfo(info: PersonalInfo): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate email + if (!info.email || !this.isValidEmail(info.email)) { + errors.push({ + field: 'email', + message: 'Valid email address is required', + code: 'INVALID_EMAIL', + value: info.email + }); + } + + // Validate phone + if (info.phone && !this.isValidPhone(info.phone)) { + warnings.push({ + field: 'phone', + message: 'Phone number format may be invalid', + suggestion: 'Use international format: +1234567890' + }); + } + + // Validate full name + if (!info.fullName || info.fullName.trim().length < 2) { + errors.push({ + field: 'fullName', + message: 'Full name must be at least 2 characters', + code: 'INVALID_NAME' + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } + + private static validateWorkExperience(exp: WorkExperience): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate dates + if (exp.endDate && exp.startDate >= exp.endDate) { + errors.push({ + field: 'endDate', + message: 'End date must be after start date', + code: 'INVALID_DATE_RANGE' + }); + } + + // Validate company and position + if (!exp.company || exp.company.trim().length === 0) { + errors.push({ + field: 'company', + message: 'Company name is required', + code: 'REQUIRED_FIELD' + }); + } + + if (!exp.position || exp.position.trim().length === 0) { + errors.push({ + field: 'position', + message: 'Position title is required', + code: 'REQUIRED_FIELD' + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } + + private static validateEducation(edu: Education): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate GPA + if (edu.gpa !== undefined && (edu.gpa < 0 || edu.gpa > 4.0)) { + errors.push({ + field: 'gpa', + message: 'GPA must be between 0.0 and 4.0', + code: 'INVALID_GPA', + value: edu.gpa + }); + } + + // Validate required fields + if (!edu.institution || edu.institution.trim().length === 0) { + errors.push({ + field: 'institution', + message: 'Institution name is required', + code: 'REQUIRED_FIELD' + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } + + private static validateSkill(skill: Skill): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate skill category + if (!Object.values(SkillCategory).includes(skill.category)) { + errors.push({ + field: 'category', + message: 'Invalid skill category', + code: 'INVALID_ENUM_VALUE', + value: skill.category + }); + } + + // Validate proficiency + if (!Object.values(SkillLevel).includes(skill.proficiency)) { + errors.push({ + field: 'proficiency', + message: 'Invalid skill proficiency level', + code: 'INVALID_ENUM_VALUE', + value: skill.proficiency + }); + } + + // Validate years of experience + if (skill.yearsOfExperience !== undefined) { + if (skill.yearsOfExperience < 0 || skill.yearsOfExperience > 50) { + warnings.push({ + field: 'yearsOfExperience', + message: 'Years of experience seems unusual', + suggestion: 'Verify the number is correct' + }); + } + } + + return { isValid: errors.length === 0, errors, warnings }; + } + + // Utility validation methods + private static isValidUUID(uuid: string): boolean { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(uuid); + } + + private static isValidEmail(email: string): boolean { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + private static isValidPhone(phone: string): boolean { + const phoneRegex = /^\+?[\d\s\-\(\)]{10,20}$/; + return phoneRegex.test(phone); + } +} + +// Question Bank Validation +export class QuestionBankValidator { + static validate(questionBank: QuestionBank): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate confidence range + if (questionBank.accuracy < 0 || questionBank.accuracy > 1) { + errors.push({ + field: 'accuracy', + message: 'Accuracy must be between 0 and 1', + code: 'INVALID_RANGE', + value: questionBank.accuracy + }); + } + + // Validate questions + questionBank.questions.forEach((question, index) => { + const questionResult = this.validateQuestion(question); + questionResult.errors.forEach(error => { + errors.push({ + ...error, + field: `questions[${index}].${error.field}` + }); + }); + }); + + // Check for duplicate questions + const questionTexts = questionBank.questions.map(q => q.text.toLowerCase().trim()); + const duplicates = questionTexts.filter((text, index) => questionTexts.indexOf(text) !== index); + if (duplicates.length > 0) { + warnings.push({ + field: 'questions', + message: 'Duplicate questions detected', + suggestion: 'Remove or merge similar questions' + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } + + private static validateQuestion(question: Question): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate category + if (!Object.values(QuestionCategory).includes(question.category)) { + errors.push({ + field: 'category', + message: 'Invalid question category', + code: 'INVALID_ENUM_VALUE', + value: question.category + }); + } + + // Validate difficulty + if (!Object.values(QuestionDifficulty).includes(question.difficulty)) { + errors.push({ + field: 'difficulty', + message: 'Invalid question difficulty', + code: 'INVALID_ENUM_VALUE', + value: question.difficulty + }); + } + + // Validate confidence + if (question.confidence < 0 || question.confidence > 1) { + errors.push({ + field: 'confidence', + message: 'Confidence must be between 0 and 1', + code: 'INVALID_RANGE', + value: question.confidence + }); + } + + // Validate answer + const answerResult = this.validateAnswer(question.answer); + answerResult.errors.forEach(error => { + errors.push({ + ...error, + field: `answer.${error.field}` + }); + }); + + return { isValid: errors.length === 0, errors, warnings }; + } + + private static validateAnswer(answer: Answer): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate estimated duration + if (answer.estimatedDuration < 5 || answer.estimatedDuration > 300) { + warnings.push({ + field: 'estimatedDuration', + message: 'Estimated duration seems unusual (5-300 seconds expected)', + suggestion: 'Verify the duration is realistic' + }); + } + + // Validate content length + if (!answer.content || answer.content.trim().length < 10) { + errors.push({ + field: 'content', + message: 'Answer content must be at least 10 characters', + code: 'INSUFFICIENT_CONTENT' + }); + } + + if (answer.content && answer.content.length > 2000) { + warnings.push({ + field: 'content', + message: 'Answer content is very long', + suggestion: 'Consider breaking into key points' + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } +} + +// Session Validation +export class InterviewSessionValidator { + static validate(session: InterviewSession): ValidationResult { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // Validate session timing + if (session.endTime && session.startTime >= session.endTime) { + errors.push({ + field: 'endTime', + message: 'End time must be after start time', + code: 'INVALID_TIME_RANGE' + }); + } + + // Validate status + if (!Object.values(SessionStatus).includes(session.status)) { + errors.push({ + field: 'status', + message: 'Invalid session status', + code: 'INVALID_ENUM_VALUE', + value: session.status + }); + } + + // Validate analytics + if (session.analytics.accuracyRate < 0 || session.analytics.accuracyRate > 1) { + errors.push({ + field: 'analytics.accuracyRate', + message: 'Accuracy rate must be between 0 and 1', + code: 'INVALID_RANGE', + value: session.analytics.accuracyRate + }); + } + + return { isValid: errors.length === 0, errors, warnings }; + } +} + +// Utility functions for data sanitization +export class DataSanitizer { + static sanitizeText(text: string): string { + return text + .trim() + .replace(/\s+/g, ' ') // Normalize whitespace + .replace(/[^\w\s\-\.\@]/g, ''); // Remove special characters except basic ones + } + + static normalizePhoneNumber(phone: string): string { + return phone.replace(/[\s\-\(\)]/g, ''); + } + + static normalizeEmail(email: string): string { + return email.toLowerCase().trim(); + } + + static generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } +} + +// Export all validation functions +export const validateCVProfile = CVProfileValidator.validate.bind(CVProfileValidator); +export const validateQuestionBank = QuestionBankValidator.validate.bind(QuestionBankValidator); +export const validateInterviewSession = InterviewSessionValidator.validate.bind(InterviewSessionValidator); \ No newline at end of file diff --git a/src/app/services/analytics.service.ts b/src/app/services/analytics.service.ts new file mode 100644 index 0000000..a8f5a58 --- /dev/null +++ b/src/app/services/analytics.service.ts @@ -0,0 +1,630 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, combineLatest } from 'rxjs'; +import { map, tap, filter } from 'rxjs/operators'; +import { + InterviewSession, + SessionAnalytics, + DetectedQuestion, + ProvidedAnswer, + QuestionDifficulty, + AnswerSource +} from '../models/interview-session.interface'; +import { Question, QuestionCategory } from '../models/question-bank.interface'; + +export interface PerformanceMetrics { + overallScore: number; + responseTimeScore: number; + accuracyScore: number; + completenessScore: number; + improvementAreas: string[]; + strengths: string[]; +} + +export interface SessionComparison { + currentSession: SessionAnalytics; + previousSession?: SessionAnalytics; + improvement: { + responseTime: number; + accuracy: number; + questionsAnswered: number; + }; + trends: { + improving: string[]; + declining: string[]; + stable: string[]; + }; +} + +export interface CategoryPerformance { + category: QuestionCategory; + questionsAnswered: number; + averageResponseTime: number; + accuracyRate: number; + difficulty: { + [QuestionDifficulty.EASY]: number; + [QuestionDifficulty.MEDIUM]: number; + [QuestionDifficulty.HARD]: number; + }; +} + +@Injectable({ + providedIn: 'root' +}) +export class AnalyticsService { + private currentSessionSubject = new BehaviorSubject(null); + private sessionsHistorySubject = new BehaviorSubject([]); + private performanceMetricsSubject = new BehaviorSubject(null); + + public currentSession$ = this.currentSessionSubject.asObservable(); + public sessionsHistory$ = this.sessionsHistorySubject.asObservable(); + public performanceMetrics$ = this.performanceMetricsSubject.asObservable(); + + // Real-time analytics streams + public realTimeAnalytics$ = combineLatest([ + this.currentSession$, + this.performanceMetrics$ + ]).pipe( + filter(([session, metrics]) => !!session), + map(([session, metrics]) => ({ + session: session!, + metrics, + liveStats: this.calculateLiveStats(session!) + })) + ); + + constructor() { + this.loadSessionsHistory(); + } + + // Session Management + public startSessionAnalytics(session: InterviewSession): void { + this.currentSessionSubject.next(session); + this.updatePerformanceMetrics(session); + } + + public updateSessionAnalytics(session: InterviewSession): void { + this.currentSessionSubject.next(session); + this.updatePerformanceMetrics(session); + } + + public completeSessionAnalytics(session: InterviewSession): Observable { + const analytics = this.calculateFinalAnalytics(session); + + // Update session with final analytics + session.analytics = analytics; + + // Add to history + this.addToHistory(session); + + // Clear current session + this.currentSessionSubject.next(null); + this.performanceMetricsSubject.next(null); + + return new Observable(observer => { + observer.next(analytics); + observer.complete(); + }); + } + + // Performance Analysis + public calculatePerformanceMetrics(session: InterviewSession): PerformanceMetrics { + const analytics = session.analytics; + const totalQuestions = analytics.totalQuestions; + + if (totalQuestions === 0) { + return { + overallScore: 0, + responseTimeScore: 0, + accuracyScore: 0, + completenessScore: 0, + improvementAreas: ['No questions answered yet'], + strengths: [] + }; + } + + // Calculate individual scores (0-100) + const responseTimeScore = this.calculateResponseTimeScore(analytics.averageResponseTime); + const accuracyScore = analytics.accuracyRate * 100; + const completenessScore = this.calculateCompletenessScore(session); + + // Calculate overall score (weighted average) + const overallScore = ( + responseTimeScore * 0.3 + + accuracyScore * 0.4 + + completenessScore * 0.3 + ); + + const improvementAreas = this.identifyImprovementAreas(session); + const strengths = this.identifyStrengths(session); + + return { + overallScore: Math.round(overallScore), + responseTimeScore: Math.round(responseTimeScore), + accuracyScore: Math.round(accuracyScore), + completenessScore: Math.round(completenessScore), + improvementAreas, + strengths + }; + } + + public compareWithPreviousSession(currentSession: InterviewSession): SessionComparison { + const history = this.sessionsHistorySubject.value; + const previousSession = history.length > 0 ? history[history.length - 1] : undefined; + + const currentAnalytics = currentSession.analytics; + const previousAnalytics = previousSession?.analytics; + + const improvement = { + responseTime: previousAnalytics ? + ((previousAnalytics.averageResponseTime - currentAnalytics.averageResponseTime) / previousAnalytics.averageResponseTime) * 100 : 0, + accuracy: previousAnalytics ? + (currentAnalytics.accuracyRate - previousAnalytics.accuracyRate) * 100 : 0, + questionsAnswered: previousAnalytics ? + currentAnalytics.totalQuestions - previousAnalytics.totalQuestions : currentAnalytics.totalQuestions + }; + + const trends = this.analyzeTrends(currentAnalytics, previousAnalytics); + + return { + currentSession: currentAnalytics, + previousSession: previousAnalytics, + improvement, + trends + }; + } + + public analyzeCategoryPerformance(session: InterviewSession): CategoryPerformance[] { + const categoryMap = new Map(); + + // Group questions by category + session.detectedQuestions.forEach(question => { + // Note: We'd need to get category from question bank + // For now, assuming we can determine category from text patterns + const category = this.inferQuestionCategory(question.originalText); + + if (!categoryMap.has(category)) { + categoryMap.set(category, { + questions: [], + answers: [], + responseTimes: [] + }); + } + + const categoryData = categoryMap.get(category)!; + categoryData.questions.push(question); + categoryData.responseTimes.push(question.responseTime); + + // Find corresponding answer + const answer = session.providedAnswers.find(a => a.questionId === question.id); + if (answer) { + categoryData.answers.push(answer); + } + }); + + // Calculate performance metrics for each category + const performances: CategoryPerformance[] = []; + + categoryMap.forEach((data, category) => { + const questionsAnswered = data.answers.length; + const averageResponseTime = data.responseTimes.reduce((sum, time) => sum + time, 0) / data.responseTimes.length; + const accuracyRate = this.calculateCategoryAccuracy(data.questions, data.answers); + + // Analyze difficulty distribution (simplified) + const difficulty = { + [QuestionDifficulty.EASY]: data.questions.filter(q => this.inferDifficulty(q) === QuestionDifficulty.EASY).length, + [QuestionDifficulty.MEDIUM]: data.questions.filter(q => this.inferDifficulty(q) === QuestionDifficulty.MEDIUM).length, + [QuestionDifficulty.HARD]: data.questions.filter(q => this.inferDifficulty(q) === QuestionDifficulty.HARD).length + }; + + performances.push({ + category, + questionsAnswered, + averageResponseTime, + accuracyRate, + difficulty + }); + }); + + return performances; + } + + // Real-time Statistics + private calculateLiveStats(session: InterviewSession) { + const now = new Date(); + const sessionDuration = session.startTime ? (now.getTime() - session.startTime.getTime()) / 1000 / 60 : 0; // minutes + + return { + sessionDuration: Math.round(sessionDuration), + questionsPerMinute: sessionDuration > 0 ? session.detectedQuestions.length / sessionDuration : 0, + currentStreak: this.calculateCurrentStreak(session), + timeToNextQuestion: this.estimateTimeToNextQuestion(session) + }; + } + + private calculateCurrentStreak(session: InterviewSession): number { + const recentAnswers = session.providedAnswers + .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) + .slice(0, 10); + + let streak = 0; + for (const answer of recentAnswers) { + if (answer.userRating && answer.userRating >= 4) { + streak++; + } else { + break; + } + } + + return streak; + } + + private estimateTimeToNextQuestion(session: InterviewSession): number { + const recentQuestions = session.detectedQuestions + .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) + .slice(0, 5); + + if (recentQuestions.length < 2) return 0; + + const intervals = []; + for (let i = 0; i < recentQuestions.length - 1; i++) { + const interval = recentQuestions[i].timestamp.getTime() - recentQuestions[i + 1].timestamp.getTime(); + intervals.push(interval / 1000); // Convert to seconds + } + + return Math.round(intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length); + } + + // Analytics Calculations + private calculateFinalAnalytics(session: InterviewSession): SessionAnalytics { + const detectedQuestions = session.detectedQuestions; + const providedAnswers = session.providedAnswers; + + const totalQuestions = detectedQuestions.length; + const answersFromBank = providedAnswers.filter(a => a.source === AnswerSource.QUESTION_BANK).length; + const answersGenerated = providedAnswers.filter(a => a.source === AnswerSource.GENERATED).length; + + const responseTimes = detectedQuestions.map(q => q.responseTime).filter(time => time > 0); + const averageResponseTime = responseTimes.length > 0 ? + responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length : 0; + + const accuracyRate = this.calculateOverallAccuracy(detectedQuestions, providedAnswers); + const topicsCovered = this.extractTopicsCovered(session); + const difficultyDistribution = this.calculateDifficultyDistribution(detectedQuestions); + + return { + totalQuestions, + answersFromBank, + answersGenerated, + averageResponseTime, + accuracyRate, + topicsCovered, + difficultyDistribution + }; + } + + private calculateResponseTimeScore(averageResponseTime: number): number { + // Ideal response time is 3-5 seconds + // Score decreases as time increases beyond 5 seconds or below 2 seconds + if (averageResponseTime <= 0) return 0; + + const idealTime = 4000; // 4 seconds in milliseconds + const tolerance = 2000; // 2 seconds tolerance + + if (averageResponseTime >= idealTime - tolerance && averageResponseTime <= idealTime + tolerance) { + return 100; + } + + const deviation = Math.abs(averageResponseTime - idealTime); + const score = Math.max(0, 100 - (deviation / 100)); // Lose 1 point per 100ms deviation + + return Math.min(100, score); + } + + private calculateCompletenessScore(session: InterviewSession): number { + const questionsAnswered = session.providedAnswers.length; + const questionsDetected = session.detectedQuestions.length; + + if (questionsDetected === 0) return 100; + + const completionRate = questionsAnswered / questionsDetected; + return completionRate * 100; + } + + private calculateOverallAccuracy(questions: DetectedQuestion[], answers: ProvidedAnswer[]): number { + if (answers.length === 0) return 0; + + const ratedAnswers = answers.filter(a => a.userRating !== undefined); + if (ratedAnswers.length === 0) return 0.8; // Default accuracy if no ratings + + const totalRating = ratedAnswers.reduce((sum, answer) => sum + (answer.userRating || 0), 0); + const maxPossibleRating = ratedAnswers.length * 5; + + return totalRating / maxPossibleRating; + } + + private calculateCategoryAccuracy(questions: DetectedQuestion[], answers: ProvidedAnswer[]): number { + if (answers.length === 0) return 0; + + const ratedAnswers = answers.filter(a => a.userRating !== undefined); + if (ratedAnswers.length === 0) return 0.8; + + const totalRating = ratedAnswers.reduce((sum, answer) => sum + (answer.userRating || 0), 0); + const maxPossibleRating = ratedAnswers.length * 5; + + return totalRating / maxPossibleRating; + } + + private extractTopicsCovered(session: InterviewSession): string[] { + const topics = new Set(); + + session.detectedQuestions.forEach(question => { + // Extract topics from question text using simple keyword matching + const text = question.originalText.toLowerCase(); + + // Technical topics + if (text.includes('javascript') || text.includes('js')) topics.add('JavaScript'); + if (text.includes('react') || text.includes('angular') || text.includes('vue')) topics.add('Frontend Frameworks'); + if (text.includes('node') || text.includes('express') || text.includes('api')) topics.add('Backend Development'); + if (text.includes('database') || text.includes('sql') || text.includes('mongodb')) topics.add('Databases'); + if (text.includes('test') || text.includes('testing')) topics.add('Testing'); + + // Behavioral topics + if (text.includes('team') || text.includes('collaboration')) topics.add('Teamwork'); + if (text.includes('challenge') || text.includes('problem')) topics.add('Problem Solving'); + if (text.includes('lead') || text.includes('manage')) topics.add('Leadership'); + if (text.includes('conflict') || text.includes('difficult')) topics.add('Conflict Resolution'); + }); + + return Array.from(topics); + } + + private calculateDifficultyDistribution(questions: DetectedQuestion[]): { [key in QuestionDifficulty]: number } { + const distribution = { + [QuestionDifficulty.EASY]: 0, + [QuestionDifficulty.MEDIUM]: 0, + [QuestionDifficulty.HARD]: 0 + }; + + questions.forEach(question => { + const difficulty = this.inferDifficulty(question); + distribution[difficulty]++; + }); + + return distribution; + } + + private identifyImprovementAreas(session: InterviewSession): string[] { + const areas: string[] = []; + const analytics = session.analytics; + + if (analytics.averageResponseTime > 8000) { + areas.push('Response time could be faster'); + } + + if (analytics.accuracyRate < 0.7) { + areas.push('Answer quality needs improvement'); + } + + if (analytics.answersFromBank / analytics.totalQuestions < 0.5) { + areas.push('Better preparation with question bank needed'); + } + + const categoryPerformance = this.analyzeCategoryPerformance(session); + const weakCategories = categoryPerformance.filter(cp => cp.accuracyRate < 0.6); + + weakCategories.forEach(category => { + areas.push(`Improve ${category.category} questions`); + }); + + return areas; + } + + private identifyStrengths(session: InterviewSession): string[] { + const strengths: string[] = []; + const analytics = session.analytics; + + if (analytics.averageResponseTime < 5000) { + strengths.push('Quick response times'); + } + + if (analytics.accuracyRate > 0.8) { + strengths.push('High answer quality'); + } + + if (analytics.answersFromBank / analytics.totalQuestions > 0.7) { + strengths.push('Well-prepared with question bank'); + } + + const categoryPerformance = this.analyzeCategoryPerformance(session); + const strongCategories = categoryPerformance.filter(cp => cp.accuracyRate > 0.8); + + strongCategories.forEach(category => { + strengths.push(`Strong in ${category.category} questions`); + }); + + return strengths; + } + + private analyzeTrends(current: SessionAnalytics, previous?: SessionAnalytics): { improving: string[], declining: string[], stable: string[] } { + const trends = { + improving: [] as string[], + declining: [] as string[], + stable: [] as string[] + }; + + if (!previous) { + return trends; + } + + // Response time trend + const responseTimeDiff = (previous.averageResponseTime - current.averageResponseTime) / previous.averageResponseTime; + if (responseTimeDiff > 0.1) trends.improving.push('Response time'); + else if (responseTimeDiff < -0.1) trends.declining.push('Response time'); + else trends.stable.push('Response time'); + + // Accuracy trend + const accuracyDiff = current.accuracyRate - previous.accuracyRate; + if (accuracyDiff > 0.05) trends.improving.push('Answer accuracy'); + else if (accuracyDiff < -0.05) trends.declining.push('Answer accuracy'); + else trends.stable.push('Answer accuracy'); + + // Questions answered trend + const questionsDiff = current.totalQuestions - previous.totalQuestions; + if (questionsDiff > 2) trends.improving.push('Questions answered'); + else if (questionsDiff < -2) trends.declining.push('Questions answered'); + else trends.stable.push('Questions answered'); + + return trends; + } + + // Helper Methods + private inferQuestionCategory(questionText: string): QuestionCategory { + const text = questionText.toLowerCase(); + + if (text.includes('experience') || text.includes('project') || text.includes('work')) { + return QuestionCategory.EXPERIENCE; + } + if (text.includes('technical') || text.includes('code') || text.includes('programming')) { + return QuestionCategory.TECHNICAL; + } + if (text.includes('team') || text.includes('challenge') || text.includes('difficult')) { + return QuestionCategory.BEHAVIORAL; + } + if (text.includes('education') || text.includes('degree') || text.includes('school')) { + return QuestionCategory.EDUCATION; + } + + return QuestionCategory.GENERAL; + } + + private inferDifficulty(question: DetectedQuestion): QuestionDifficulty { + const text = question.originalText.toLowerCase(); + const complexWords = ['complex', 'challenging', 'difficult', 'advanced', 'sophisticated']; + const easyWords = ['simple', 'basic', 'straightforward', 'easy']; + + if (complexWords.some(word => text.includes(word))) { + return QuestionDifficulty.HARD; + } + if (easyWords.some(word => text.includes(word))) { + return QuestionDifficulty.EASY; + } + + // Default to medium + return QuestionDifficulty.MEDIUM; + } + + private updatePerformanceMetrics(session: InterviewSession): void { + const metrics = this.calculatePerformanceMetrics(session); + this.performanceMetricsSubject.next(metrics); + } + + private addToHistory(session: InterviewSession): void { + const history = this.sessionsHistorySubject.value; + const updatedHistory = [...history, session]; + + // Keep only last 10 sessions + if (updatedHistory.length > 10) { + updatedHistory.shift(); + } + + this.sessionsHistorySubject.next(updatedHistory); + this.saveSessionsHistory(updatedHistory); + } + + private loadSessionsHistory(): void { + try { + const stored = localStorage.getItem('interview_sessions_history'); + if (stored) { + const history = JSON.parse(stored); + // Convert date strings back to Date objects + const convertedHistory = history.map((session: any) => ({ + ...session, + startTime: new Date(session.startTime), + endTime: session.endTime ? new Date(session.endTime) : undefined, + detectedQuestions: session.detectedQuestions.map((q: any) => ({ + ...q, + timestamp: new Date(q.timestamp) + })), + providedAnswers: session.providedAnswers.map((a: any) => ({ + ...a, + timestamp: new Date(a.timestamp) + })), + manualComments: session.manualComments.map((c: any) => ({ + ...c, + timestamp: new Date(c.timestamp) + })) + })); + this.sessionsHistorySubject.next(convertedHistory); + } + } catch (error) { + console.error('Failed to load sessions history:', error); + } + } + + private saveSessionsHistory(history: InterviewSession[]): void { + try { + localStorage.setItem('interview_sessions_history', JSON.stringify(history)); + } catch (error) { + console.error('Failed to save sessions history:', error); + } + } + + // Export Methods + public exportSessionAnalytics(session: InterviewSession): string { + const analytics = { + session: { + id: session.id, + startTime: session.startTime, + endTime: session.endTime, + duration: session.endTime ? + (session.endTime.getTime() - session.startTime.getTime()) / 1000 / 60 : 0 + }, + analytics: session.analytics, + performance: this.calculatePerformanceMetrics(session), + categoryPerformance: this.analyzeCategoryPerformance(session), + comparison: this.compareWithPreviousSession(session) + }; + + return JSON.stringify(analytics, null, 2); + } + + public generateAnalyticsReport(session: InterviewSession): string { + const metrics = this.calculatePerformanceMetrics(session); + const comparison = this.compareWithPreviousSession(session); + + let report = `Interview Session Analytics Report\n`; + report += `=====================================\n\n`; + report += `Session ID: ${session.id}\n`; + report += `Date: ${session.startTime.toLocaleDateString()}\n`; + report += `Duration: ${session.endTime ? Math.round((session.endTime.getTime() - session.startTime.getTime()) / 1000 / 60) : 'Ongoing'} minutes\n\n`; + + report += `Performance Metrics:\n`; + report += `- Overall Score: ${metrics.overallScore}/100\n`; + report += `- Response Time Score: ${metrics.responseTimeScore}/100\n`; + report += `- Accuracy Score: ${metrics.accuracyScore}/100\n`; + report += `- Completeness Score: ${metrics.completenessScore}/100\n\n`; + + report += `Strengths:\n`; + metrics.strengths.forEach(strength => { + report += `- ${strength}\n`; + }); + + report += `\nImprovement Areas:\n`; + metrics.improvementAreas.forEach(area => { + report += `- ${area}\n`; + }); + + if (comparison.previousSession) { + report += `\nComparison with Previous Session:\n`; + report += `- Response Time: ${comparison.improvement.responseTime > 0 ? 'Improved' : 'Declined'} by ${Math.abs(comparison.improvement.responseTime).toFixed(1)}%\n`; + report += `- Accuracy: ${comparison.improvement.accuracy > 0 ? 'Improved' : 'Declined'} by ${Math.abs(comparison.improvement.accuracy).toFixed(1)}%\n`; + report += `- Questions Answered: ${comparison.improvement.questionsAnswered > 0 ? 'Increased' : 'Decreased'} by ${Math.abs(comparison.improvement.questionsAnswered)}\n`; + } + + return report; + } +} \ No newline at end of file diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..28157d6 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,585 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { BehaviorSubject, Observable, of, throwError, timer } from 'rxjs'; +import { map, catchError, tap, switchMap, filter } from 'rxjs/operators'; + +export interface AuthCredentials { + username: string; + password: string; +} + +export interface AuthToken { + access_token: string; + refresh_token?: string; + expires_in: number; + token_type: string; + scope?: string; +} + +export interface User { + id: string; + username: string; + email?: string; + displayName?: string; + roles: string[]; + permissions: string[]; + lastLogin?: Date; + profileData?: any; +} + +export interface AuthState { + isAuthenticated: boolean; + user: User | null; + token: AuthToken | null; + expiresAt: Date | null; + lastActivity: Date; +} + +export interface SessionInfo { + sessionId: string; + startTime: Date; + lastActivity: Date; + ipAddress?: string; + userAgent?: string; + isActive: boolean; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private readonly STORAGE_KEY = 'interview_assistant_auth'; + private readonly REFRESH_THRESHOLD = 5 * 60 * 1000; // 5 minutes before expiry + private readonly SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes of inactivity + + private authStateSubject = new BehaviorSubject({ + isAuthenticated: false, + user: null, + token: null, + expiresAt: null, + lastActivity: new Date() + }); + + private sessionInfoSubject = new BehaviorSubject(null); + private autoRefreshTimer: any; + private sessionTimeoutTimer: any; + + public authState$ = this.authStateSubject.asObservable(); + public user$ = this.authState$.pipe(map(state => state.user)); + public isAuthenticated$ = this.authState$.pipe(map(state => state.isAuthenticated)); + public sessionInfo$ = this.sessionInfoSubject.asObservable(); + + // N8n/Authelia configuration + private readonly authConfig = { + autheliaBaseUrl: 'https://n8n.gm-tech.org', // Authelia endpoint + n8nBaseUrl: 'https://n8n.gm-tech.org', + loginEndpoint: '/api/verify', + refreshEndpoint: '/api/refresh', + logoutEndpoint: '/api/logout', + userInfoEndpoint: '/api/user', + verifyEndpoint: '/api/verify' + }; + + constructor(private http: HttpClient) { + this.initializeAuthService(); + this.setupActivityMonitoring(); + this.setupAutoRefresh(); + } + + // Core Authentication Methods + public login(credentials: AuthCredentials): Observable { + const loginUrl = `${this.authConfig.autheliaBaseUrl}${this.authConfig.loginEndpoint}`; + + return this.http.post(loginUrl, { + username: credentials.username, + password: credentials.password + }, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + withCredentials: true // Important for Authelia session cookies + }).pipe( + switchMap(response => { + if (response.status === 'OK') { + // Authelia login successful, now get user info + return this.getUserInfo(); + } else { + throw new Error('Authentication failed'); + } + }), + tap(user => { + const authState = this.createAuthState(user, credentials.username); + this.updateAuthState(authState); + this.startSession(); + }), + map(() => this.authStateSubject.value), + catchError(error => { + console.error('Login failed:', error); + return throwError(this.handleAuthError(error)); + }) + ); + } + + public logout(): Observable { + const logoutUrl = `${this.authConfig.autheliaBaseUrl}${this.authConfig.logoutEndpoint}`; + + return this.http.post(logoutUrl, {}, { + withCredentials: true + }).pipe( + tap(() => { + this.clearAuthState(); + this.endSession(); + }), + map(() => void 0), + catchError(error => { + // Even if logout fails on server, clear local state + console.warn('Logout request failed, clearing local state:', error); + this.clearAuthState(); + this.endSession(); + return of(void 0); + }) + ); + } + + public refreshToken(): Observable { + const refreshUrl = `${this.authConfig.autheliaBaseUrl}${this.authConfig.refreshEndpoint}`; + + return this.http.post(refreshUrl, {}, { + withCredentials: true + }).pipe( + switchMap(response => { + if (response.status === 'OK') { + return this.getUserInfo(); + } else { + throw new Error('Token refresh failed'); + } + }), + tap(user => { + const currentState = this.authStateSubject.value; + const newState = { + ...currentState, + user, + lastActivity: new Date() + }; + this.updateAuthState(newState); + }), + map(() => this.authStateSubject.value), + catchError(error => { + console.error('Token refresh failed:', error); + this.clearAuthState(); + return throwError('Session expired'); + }) + ); + } + + public verifySession(): Observable { + const verifyUrl = `${this.authConfig.autheliaBaseUrl}${this.authConfig.verifyEndpoint}`; + + return this.http.get(verifyUrl, { + withCredentials: true + }).pipe( + map(response => response.status === 'OK'), + tap(isValid => { + if (!isValid && this.authStateSubject.value.isAuthenticated) { + this.clearAuthState(); + } + }), + catchError(error => { + console.warn('Session verification failed:', error); + this.clearAuthState(); + return of(false); + }) + ); + } + + // User Information + private getUserInfo(): Observable { + const userInfoUrl = `${this.authConfig.autheliaBaseUrl}${this.authConfig.userInfoEndpoint}`; + + return this.http.get(userInfoUrl, { + withCredentials: true + }).pipe( + map(response => this.mapUserResponse(response)), + catchError(error => { + console.error('Failed to get user info:', error); + return throwError('Failed to retrieve user information'); + }) + ); + } + + private mapUserResponse(response: any): User { + return { + id: response.user || response.username || 'unknown', + username: response.username || response.user || 'unknown', + email: response.email, + displayName: response.name || response.display_name || response.username, + roles: response.groups || response.roles || [], + permissions: this.extractPermissions(response.groups || response.roles || []), + lastLogin: new Date(), + profileData: { + avatar: response.avatar, + preferences: response.preferences || {} + } + }; + } + + private extractPermissions(groups: string[]): string[] { + const permissions: string[] = []; + + // Map Authelia groups to application permissions + if (groups.includes('interview-admin')) { + permissions.push('admin', 'manage-users', 'view-analytics', 'export-data'); + } + + if (groups.includes('interview-user') || groups.includes('users')) { + permissions.push('use-app', 'upload-cv', 'view-sessions'); + } + + if (groups.includes('interview-guest')) { + permissions.push('use-app'); + } + + // Default permissions for any authenticated user + if (permissions.length === 0) { + permissions.push('use-app'); + } + + return permissions; + } + + // Permission Checking + public hasPermission(permission: string): boolean { + const user = this.authStateSubject.value.user; + return user ? user.permissions.includes(permission) : false; + } + + public hasRole(role: string): boolean { + const user = this.authStateSubject.value.user; + return user ? user.roles.includes(role) : false; + } + + public hasAnyRole(roles: string[]): boolean { + const user = this.authStateSubject.value.user; + return user ? roles.some(role => user.roles.includes(role)) : false; + } + + public requiresPermission(permission: string): Observable { + return this.isAuthenticated$.pipe( + map(isAuth => isAuth && this.hasPermission(permission)) + ); + } + + // Session Management + private startSession(): void { + const sessionInfo: SessionInfo = { + sessionId: this.generateSessionId(), + startTime: new Date(), + lastActivity: new Date(), + ipAddress: 'unknown', // Would need additional service to get IP + userAgent: navigator.userAgent, + isActive: true + }; + + this.sessionInfoSubject.next(sessionInfo); + this.setupSessionTimeout(); + } + + private endSession(): void { + const currentSession = this.sessionInfoSubject.value; + if (currentSession) { + this.sessionInfoSubject.next({ + ...currentSession, + isActive: false + }); + } + + this.clearSessionTimeout(); + } + + private updateActivity(): void { + const currentState = this.authStateSubject.value; + const currentSession = this.sessionInfoSubject.value; + + if (currentState.isAuthenticated) { + // Update auth state activity + this.authStateSubject.next({ + ...currentState, + lastActivity: new Date() + }); + + // Update session activity + if (currentSession) { + this.sessionInfoSubject.next({ + ...currentSession, + lastActivity: new Date() + }); + } + + // Reset session timeout + this.setupSessionTimeout(); + } + } + + private setupSessionTimeout(): void { + this.clearSessionTimeout(); + + this.sessionTimeoutTimer = setTimeout(() => { + console.log('Session timeout - logging out user'); + this.logout().subscribe(); + }, this.SESSION_TIMEOUT); + } + + private clearSessionTimeout(): void { + if (this.sessionTimeoutTimer) { + clearTimeout(this.sessionTimeoutTimer); + this.sessionTimeoutTimer = null; + } + } + + // Activity Monitoring + private setupActivityMonitoring(): void { + // Monitor user activity events + const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click']; + + activityEvents.forEach(event => { + document.addEventListener(event, () => { + this.updateActivity(); + }, { passive: true }); + }); + + // Monitor page visibility + document.addEventListener('visibilitychange', () => { + if (!document.hidden) { + this.updateActivity(); + } + }); + } + + // Auto-refresh Setup + private setupAutoRefresh(): void { + this.authState$.pipe( + filter(state => state.isAuthenticated && !!state.expiresAt) + ).subscribe(state => { + this.scheduleTokenRefresh(state.expiresAt!); + }); + } + + private scheduleTokenRefresh(expiresAt: Date): void { + if (this.autoRefreshTimer) { + clearTimeout(this.autoRefreshTimer); + } + + const refreshTime = expiresAt.getTime() - Date.now() - this.REFRESH_THRESHOLD; + + if (refreshTime > 0) { + this.autoRefreshTimer = setTimeout(() => { + console.log('Auto-refreshing token'); + this.refreshToken().subscribe({ + next: () => console.log('Token refreshed successfully'), + error: (error) => console.error('Auto-refresh failed:', error) + }); + }, refreshTime); + } + } + + // State Management + private createAuthState(user: User, username?: string): AuthState { + // For Authelia, we rely on cookies rather than JWT tokens + // So we create a minimal token representation + const token: AuthToken = { + access_token: 'authelia_session', // Placeholder + token_type: 'session', + expires_in: 3600 // 1 hour default + }; + + return { + isAuthenticated: true, + user, + token, + expiresAt: new Date(Date.now() + token.expires_in * 1000), + lastActivity: new Date() + }; + } + + private updateAuthState(state: AuthState): void { + this.authStateSubject.next(state); + this.saveAuthState(state); + } + + private clearAuthState(): void { + const clearedState: AuthState = { + isAuthenticated: false, + user: null, + token: null, + expiresAt: null, + lastActivity: new Date() + }; + + this.authStateSubject.next(clearedState); + this.clearStoredAuthState(); + + if (this.autoRefreshTimer) { + clearTimeout(this.autoRefreshTimer); + this.autoRefreshTimer = null; + } + } + + // Storage Management + private initializeAuthService(): void { + this.loadStoredAuthState(); + + // Verify stored session on startup + const currentState = this.authStateSubject.value; + if (currentState.isAuthenticated) { + this.verifySession().subscribe(isValid => { + if (!isValid) { + console.log('Stored session is invalid, clearing auth state'); + this.clearAuthState(); + } else { + console.log('Stored session is valid'); + this.startSession(); + } + }); + } + } + + private saveAuthState(state: AuthState): void { + try { + const stateToStore = { + ...state, + expiresAt: state.expiresAt?.toISOString(), + lastActivity: state.lastActivity.toISOString() + }; + + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(stateToStore)); + } catch (error) { + console.error('Failed to save auth state:', error); + } + } + + private loadStoredAuthState(): void { + try { + const stored = localStorage.getItem(this.STORAGE_KEY); + if (stored) { + const parsedState = JSON.parse(stored); + const state: AuthState = { + ...parsedState, + expiresAt: parsedState.expiresAt ? new Date(parsedState.expiresAt) : null, + lastActivity: new Date(parsedState.lastActivity) + }; + + // Check if stored state is expired + if (state.expiresAt && state.expiresAt <= new Date()) { + console.log('Stored auth state is expired'); + this.clearStoredAuthState(); + return; + } + + this.authStateSubject.next(state); + } + } catch (error) { + console.error('Failed to load stored auth state:', error); + this.clearStoredAuthState(); + } + } + + private clearStoredAuthState(): void { + try { + localStorage.removeItem(this.STORAGE_KEY); + } catch (error) { + console.error('Failed to clear stored auth state:', error); + } + } + + // HTTP Interceptor Support + public getAuthHeaders(): HttpHeaders { + // For Authelia, authentication is handled via cookies + // But we can provide headers if needed for API calls + return new HttpHeaders({ + 'Content-Type': 'application/json' + }); + } + + public isTokenExpired(): boolean { + const state = this.authStateSubject.value; + return !state.expiresAt || state.expiresAt <= new Date(); + } + + // Utility Methods + private generateSessionId(): string { + return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private handleAuthError(error: any): string { + if (error.status === 401) { + return 'Invalid username or password'; + } else if (error.status === 403) { + return 'Access forbidden'; + } else if (error.status === 429) { + return 'Too many login attempts. Please try again later.'; + } else if (error.status === 0) { + return 'Unable to connect to authentication server'; + } else { + return 'Authentication failed. Please try again.'; + } + } + + // Public API Methods + public getCurrentUser(): User | null { + return this.authStateSubject.value.user; + } + + public getCurrentSession(): SessionInfo | null { + return this.sessionInfoSubject.value; + } + + public getAuthState(): AuthState { + return this.authStateSubject.value; + } + + public forceLogout(): void { + console.log('Force logout initiated'); + this.clearAuthState(); + this.endSession(); + } + + // Test/Development Methods + public simulateLogin(username: string): Observable { + // For development/testing purposes + if (!environment.production) { + const mockUser: User = { + id: `dev_${username}`, + username, + email: `${username}@example.com`, + displayName: username, + roles: ['interview-user'], + permissions: ['use-app', 'upload-cv', 'view-sessions'], + lastLogin: new Date() + }; + + const authState = this.createAuthState(mockUser); + this.updateAuthState(authState); + this.startSession(); + + return of(authState); + } + + return throwError('Simulate login only available in development mode'); + } + + // Cleanup + public destroy(): void { + if (this.autoRefreshTimer) { + clearTimeout(this.autoRefreshTimer); + } + if (this.sessionTimeoutTimer) { + clearTimeout(this.sessionTimeoutTimer); + } + this.endSession(); + } +} + +// Environment check (would normally be imported) +const environment = { + production: !(window as any).location?.hostname?.includes('localhost') +}; \ No newline at end of file diff --git a/src/app/services/authelia-auth.service.ts b/src/app/services/authelia-auth.service.ts new file mode 100644 index 0000000..8f02859 --- /dev/null +++ b/src/app/services/authelia-auth.service.ts @@ -0,0 +1,238 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; +import { BehaviorSubject, Observable, throwError, of } from 'rxjs'; +import { map, catchError, tap, retry } from 'rxjs/operators'; +import { environment } from '../../environments/environment'; + +export interface AutheliaUser { + username: string; + displayName: string; + email: string; + groups: string[]; +} + +export interface AutheliaSession { + username: string; + displayName: string; + email: string; + groups: string[]; + authenticated: boolean; + expires: string; +} + +export interface AutheliaLoginRequest { + username: string; + password: string; + keepMeLoggedIn?: boolean; + targetURL?: string; +} + +export interface AutheliaLoginResponse { + status: 'OK' | 'FAILED'; + message?: string; + data?: { + redirect?: string; + }; +} + +@Injectable({ + providedIn: 'root' +}) +export class AutheliaAuthService { + private readonly authBaseUrl = environment.authelia?.baseUrl || 'https://auth.gm-tech.org'; + + private isAuthenticatedSubject = new BehaviorSubject(false); + private userSubject = new BehaviorSubject(null); + private sessionSubject = new BehaviorSubject(null); + + public isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); + public user$ = this.userSubject.asObservable(); + public session$ = this.sessionSubject.asObservable(); + + constructor(private http: HttpClient) { + this.checkAuthenticationStatus(); + } + + /** + * Check if user is currently authenticated with Authelia + */ + public checkAuthenticationStatus(): Observable { + const verifyUrl = `${this.authBaseUrl}/api/verify`; + + return this.http.get(verifyUrl, { + withCredentials: true, + headers: this.getHeaders() + }).pipe( + map(session => { + if (session.authenticated) { + this.sessionSubject.next(session); + this.userSubject.next({ + username: session.username, + displayName: session.displayName, + email: session.email, + groups: session.groups + }); + this.isAuthenticatedSubject.next(true); + console.log('✅ User authenticated with Authelia:', session.username); + return true; + } else { + this.clearSession(); + return false; + } + }), + catchError((error: HttpErrorResponse) => { + console.log('❌ Authentication check failed:', error.status); + this.clearSession(); + return of(false); + }) + ); + } + + /** + * Login with Authelia + */ + public login(username: string, password: string, targetURL?: string): Observable { + const loginUrl = `${this.authBaseUrl}/api/firstfactor`; + + const loginData: AutheliaLoginRequest = { + username, + password, + keepMeLoggedIn: true, + targetURL: targetURL || environment.n8nWebhookUrl + }; + + return this.http.post(loginUrl, loginData, { + withCredentials: true, + headers: this.getHeaders() + }).pipe( + tap(response => { + if (response.status === 'OK') { + console.log('✅ Login successful'); + // Refresh authentication status + this.checkAuthenticationStatus().subscribe(); + } else { + console.log('❌ Login failed:', response.message); + } + }), + catchError(this.handleError) + ); + } + + /** + * Logout from Authelia + */ + public logout(): Observable { + const logoutUrl = `${this.authBaseUrl}/api/logout`; + + return this.http.post(logoutUrl, {}, { + withCredentials: true, + headers: this.getHeaders() + }).pipe( + tap(() => { + console.log('✅ Logout successful'); + this.clearSession(); + }), + catchError(this.handleError) + ); + } + + /** + * Get authentication headers for API requests + */ + public getAuthHeaders(): HttpHeaders { + return this.getHeaders(); + } + + /** + * Make authenticated request to protected resource + */ + public makeAuthenticatedRequest(url: string, data?: any, method: 'GET' | 'POST' = 'POST'): Observable { + const options = { + withCredentials: true, + headers: this.getHeaders() + }; + + if (method === 'GET') { + return this.http.get(url, options).pipe( + retry(1), + catchError(this.handleAuthenticatedRequestError.bind(this)) + ); + } else { + return this.http.post(url, data, options).pipe( + retry(1), + catchError(this.handleAuthenticatedRequestError.bind(this)) + ); + } + } + + /** + * Redirect to Authelia login page + */ + public redirectToLogin(targetURL?: string): void { + const returnURL = targetURL || window.location.href; + const loginURL = `${this.authBaseUrl}/?rd=${encodeURIComponent(returnURL)}`; + window.location.href = loginURL; + } + + /** + * Get current authentication status + */ + public get isAuthenticated(): boolean { + return this.isAuthenticatedSubject.value; + } + + /** + * Get current user + */ + public get currentUser(): AutheliaUser | null { + return this.userSubject.value; + } + + /** + * Get current session + */ + public get currentSession(): AutheliaSession | null { + return this.sessionSubject.value; + } + + private getHeaders(): HttpHeaders { + return new HttpHeaders({ + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }); + } + + private clearSession(): void { + this.isAuthenticatedSubject.next(false); + this.userSubject.next(null); + this.sessionSubject.next(null); + } + + private handleAuthenticatedRequestError(error: HttpErrorResponse): Observable { + if (error.status === 401 || error.status === 403) { + console.log('🔒 Authentication required for protected resource'); + this.clearSession(); + + // Don't automatically redirect for API calls, let the calling component handle it + return throwError(() => new Error('Authentication required')); + } + + return this.handleError(error); + } + + private handleError(error: HttpErrorResponse): Observable { + let errorMessage = 'An error occurred'; + + if (error.error instanceof ErrorEvent) { + // Client-side error + errorMessage = `Error: ${error.error.message}`; + } else { + // Server-side error + errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; + } + + console.error('❌ Authelia service error:', errorMessage); + return throwError(() => new Error(errorMessage)); + } +} \ No newline at end of file diff --git a/src/app/services/cv-parser.service.spec.ts b/src/app/services/cv-parser.service.spec.ts new file mode 100644 index 0000000..5416b3b --- /dev/null +++ b/src/app/services/cv-parser.service.spec.ts @@ -0,0 +1,455 @@ +import { TestBed } from '@angular/core/testing'; +import { CVParserService } from './cv-parser.service'; + +// Mock PDF.js +const mockPDFJS = { + getDocument: jasmine.createSpy('getDocument').and.returnValue({ + promise: Promise.resolve({ + numPages: 2, + getPage: jasmine.createSpy('getPage').and.returnValue(Promise.resolve({ + getTextContent: jasmine.createSpy('getTextContent').and.returnValue(Promise.resolve({ + items: [ + { str: 'John Doe' }, + { str: 'Senior Software Developer' }, + { str: 'john.doe@example.com' }, + { str: '+1 (555) 123-4567' }, + { str: 'New York, NY' }, + { str: 'Experience:' }, + { str: 'Tech Corp - Senior Developer (2020-2023)' }, + { str: 'Led development of Angular applications' }, + { str: 'Education:' }, + { str: 'University of Technology - Computer Science (2016-2020)' }, + { str: 'Skills: Angular, TypeScript, Node.js, RxJS' } + ] + })) + })) + }) + }) +}; + +describe('CVParserService - Integration Tests', () => { + let service: CVParserService; + + beforeEach(() => { + // Mock PDF.js globally + (window as any).pdfjsLib = mockPDFJS; + + TestBed.configureTestingModule({ + providers: [CVParserService] + }); + service = TestBed.inject(CVParserService); + }); + + afterEach(() => { + delete (window as any).pdfjsLib; + }); + + describe('T013: Integration test CV parsing functionality', () => { + it('should parse PDF CV and extract personal information', async () => { + // Arrange + const mockPDFFile = new File(['mock pdf content'], 'john-doe-cv.pdf', { + type: 'application/pdf' + }); + + // Act + const result = await service.parseCV(mockPDFFile); + + // Assert + expect(result.personalInfo.fullName).toBe('John Doe'); + expect(result.personalInfo.email).toBe('john.doe@example.com'); + expect(result.personalInfo.phone).toBe('+1 (555) 123-4567'); + expect(result.personalInfo.location).toBe('New York, NY'); + expect(result.fileName).toBe('john-doe-cv.pdf'); + expect(result.uploadDate).toBeInstanceOf(Date); + }); + + it('should extract work experience with proper parsing', async () => { + // Arrange + const mockPDFFile = new File(['mock pdf content'], 'cv-with-experience.pdf', { + type: 'application/pdf' + }); + + // Mock more detailed experience text + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'John Doe' }, + { str: 'Work Experience:' }, + { str: 'Tech Corp' }, + { str: 'Senior Software Developer' }, + { str: 'January 2020 - December 2023' }, + { str: 'Led team of 5 developers in building Angular applications' }, + { str: 'Reduced application load time by 40%' }, + { str: 'Technologies: Angular, TypeScript, Node.js, MongoDB' }, + { str: 'Previous Company' }, + { str: 'Junior Developer' }, + { str: 'June 2018 - December 2019' }, + { str: 'Developed React components and REST APIs' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(mockPDFFile); + + // Assert + expect(result.experience.length).toBeGreaterThan(0); + + const seniorRole = result.experience.find(exp => exp.position === 'Senior Software Developer'); + expect(seniorRole).toBeDefined(); + expect(seniorRole!.company).toBe('Tech Corp'); + expect(seniorRole!.startDate.getFullYear()).toBe(2020); + expect(seniorRole!.endDate?.getFullYear()).toBe(2023); + expect(seniorRole!.technologies).toContain('Angular'); + expect(seniorRole!.technologies).toContain('TypeScript'); + expect(seniorRole!.achievements).toContain('Reduced application load time by 40%'); + }); + + it('should extract education information correctly', async () => { + // Arrange + const mockPDFFile = new File(['mock pdf content'], 'cv-with-education.pdf', { + type: 'application/pdf' + }); + + // Mock education-focused content + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'Education:' }, + { str: 'University of Technology' }, + { str: 'Bachelor of Science in Computer Science' }, + { str: 'September 2016 - May 2020' }, + { str: 'GPA: 3.8/4.0' }, + { str: 'Magna Cum Laude' }, + { str: 'Community College' }, + { str: 'Associate Degree in Information Technology' }, + { str: 'September 2014 - May 2016' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(mockPDFFile); + + // Assert + expect(result.education.length).toBeGreaterThan(0); + + const bachelorDegree = result.education.find(edu => edu.degree.includes('Bachelor')); + expect(bachelorDegree).toBeDefined(); + expect(bachelorDegree!.institution).toBe('University of Technology'); + expect(bachelorDegree!.field).toBe('Computer Science'); + expect(bachelorDegree!.startDate.getFullYear()).toBe(2016); + expect(bachelorDegree!.endDate?.getFullYear()).toBe(2020); + expect(bachelorDegree!.gpa).toBe(3.8); + expect(bachelorDegree!.honors).toContain('Magna Cum Laude'); + }); + + it('should extract and categorize skills properly', async () => { + // Arrange + const mockPDFFile = new File(['mock pdf content'], 'cv-with-skills.pdf', { + type: 'application/pdf' + }); + + // Mock skills-focused content + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'Technical Skills:' }, + { str: 'Angular (5 years)' }, + { str: 'TypeScript (4 years)' }, + { str: 'Node.js (3 years)' }, + { str: 'RxJS (2 years)' }, + { str: 'Soft Skills:' }, + { str: 'Leadership' }, + { str: 'Communication' }, + { str: 'Problem Solving' }, + { str: 'Languages:' }, + { str: 'English (Native)' }, + { str: 'Spanish (Conversational)' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(mockPDFFile); + + // Assert + expect(result.skills.length).toBeGreaterThan(0); + + const technicalSkills = result.skills.filter(skill => skill.category === 'technical'); + expect(technicalSkills.length).toBeGreaterThan(0); + + const angularSkill = technicalSkills.find(skill => skill.name === 'Angular'); + expect(angularSkill).toBeDefined(); + expect(angularSkill!.yearsOfExperience).toBe(5); + expect(angularSkill!.proficiency).toBe('advanced'); + + const softSkills = result.skills.filter(skill => skill.category === 'soft'); + expect(softSkills.length).toBeGreaterThan(0); + expect(softSkills.map(s => s.name)).toContain('Leadership'); + + expect(result.languages.length).toBeGreaterThan(0); + const english = result.languages.find(lang => lang.name === 'English'); + expect(english?.proficiency).toBe('native'); + }); + + it('should handle different CV formats and layouts', async () => { + // Test multiple format variations + const formats = [ + { name: 'traditional-format.pdf', layout: 'traditional' }, + { name: 'modern-format.pdf', layout: 'modern' }, + { name: 'creative-format.pdf', layout: 'creative' } + ]; + + for (const format of formats) { + // Arrange + const mockFile = new File(['mock content'], format.name, { type: 'application/pdf' }); + + // Act + const result = await service.parseCV(mockFile); + + // Assert + expect(result.fileName).toBe(format.name); + expect(result.personalInfo.fullName).toBeTruthy(); + expect(result.parsedText).toBeTruthy(); + expect(result.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + } + }); + + it('should extract certifications and professional achievements', async () => { + // Arrange + const mockPDFFile = new File(['mock pdf content'], 'cv-with-certifications.pdf', { + type: 'application/pdf' + }); + + // Mock certification content + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'Certifications:' }, + { str: 'AWS Certified Solutions Architect' }, + { str: 'Amazon Web Services' }, + { str: 'Issued: January 2023' }, + { str: 'Expires: January 2026' }, + { str: 'Credential ID: AWS-123456' }, + { str: 'Angular Certified Developer' }, + { str: 'Google' }, + { str: 'Issued: March 2022' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(mockPDFFile); + + // Assert + expect(result.certifications.length).toBeGreaterThan(0); + + const awsCert = result.certifications.find(cert => cert.name.includes('AWS')); + expect(awsCert).toBeDefined(); + expect(awsCert!.issuer).toBe('Amazon Web Services'); + expect(awsCert!.issueDate.getFullYear()).toBe(2023); + expect(awsCert!.expiryDate?.getFullYear()).toBe(2026); + expect(awsCert!.credentialId).toBe('AWS-123456'); + }); + + it('should handle malformed or corrupted PDF files', async () => { + // Arrange + const corruptedFile = new File(['corrupted content'], 'corrupted.pdf', { + type: 'application/pdf' + }); + + mockPDFJS.getDocument.and.returnValue({ + promise: Promise.reject(new Error('Invalid PDF format')) + }); + + // Act & Assert + await expectAsync(service.parseCV(corruptedFile)) + .toBeRejectedWithError('Failed to parse PDF: Invalid PDF format'); + }); + + it('should handle very large PDF files efficiently', async () => { + // Arrange + const largePDFFile = new File( + [new ArrayBuffer(10 * 1024 * 1024)], // 10MB file + 'large-cv.pdf', + { type: 'application/pdf' } + ); + + // Act + const startTime = performance.now(); + const result = await service.parseCV(largePDFFile); + const endTime = performance.now(); + + // Assert + expect(endTime - startTime).toBeLessThan(5000); // Should complete within 5 seconds + expect(result).toBeDefined(); + }); + + it('should extract contact information with various formats', async () => { + // Test different contact format variations + const contactVariations = [ + { email: 'john.doe@example.com', phone: '+1-555-123-4567' }, + { email: 'jane_smith@company.org', phone: '(555) 987-6543' }, + { email: 'developer123@gmail.com', phone: '555.456.7890' } + ]; + + for (const contact of contactVariations) { + // Arrange + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'Contact Information:' }, + { str: contact.email }, + { str: contact.phone }, + { str: 'LinkedIn: https://linkedin.com/in/profile' }, + { str: 'GitHub: https://github.com/username' } + ] + }) + })); + }); + + const mockFile = new File(['mock content'], 'test-cv.pdf', { type: 'application/pdf' }); + + // Act + const result = await service.parseCV(mockFile); + + // Assert + expect(result.personalInfo.email).toBe(contact.email); + expect(result.personalInfo.phone).toBe(contact.phone); + expect(result.personalInfo.linkedIn).toContain('linkedin.com'); + expect(result.personalInfo.github).toContain('github.com'); + } + }); + + it('should provide manual correction interface for parsing errors', async () => { + // Arrange + const mockFile = new File(['mock content'], 'test-cv.pdf', { type: 'application/pdf' }); + const parseResult = await service.parseCV(mockFile); + + const corrections = { + personalInfo: { + fullName: 'Corrected Name', + email: 'corrected@example.com' + }, + skills: [ + { + id: 'new-skill-id', + name: 'Vue.js', + category: 'technical' as const, + proficiency: 'intermediate' as const, + yearsOfExperience: 2 + } + ] + }; + + // Act + const correctedResult = await service.applyCorrections(parseResult, corrections); + + // Assert + expect(correctedResult.personalInfo.fullName).toBe('Corrected Name'); + expect(correctedResult.personalInfo.email).toBe('corrected@example.com'); + expect(correctedResult.skills.some(skill => skill.name === 'Vue.js')).toBe(true); + expect(correctedResult.lastModified).toBeInstanceOf(Date); + }); + + it('should maintain parsing accuracy above 90%', async () => { + // Test with known good CV samples + const testCVs = [ + 'standard-format-cv.pdf', + 'academic-cv.pdf', + 'technical-resume.pdf', + 'executive-cv.pdf', + 'fresh-graduate-cv.pdf' + ]; + + let totalAccuracy = 0; + + for (const cvName of testCVs) { + // Arrange + const mockFile = new File(['mock content'], cvName, { type: 'application/pdf' }); + + // Act + const result = await service.parseCV(mockFile); + const accuracy = await service.calculateParsingAccuracy(result); + + // Assert + expect(accuracy).toBeGreaterThan(0.9); // 90% accuracy requirement + totalAccuracy += accuracy; + } + + const averageAccuracy = totalAccuracy / testCVs.length; + expect(averageAccuracy).toBeGreaterThan(0.9); + }); + + it('should handle non-English CVs with proper encoding', async () => { + // Arrange + const nonEnglishFile = new File(['mock content'], 'cv-spanish.pdf', { + type: 'application/pdf' + }); + + // Mock non-English content + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'José García' }, + { str: 'Desarrollador Senior' }, + { str: 'Experiencia:' }, + { str: 'Tecnologías: Angular, TypeScript' }, + { str: 'Educación:' }, + { str: 'Universidad Politécnica' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(nonEnglishFile); + + // Assert + expect(result.personalInfo.fullName).toBe('José García'); + expect(result.parsedText).toContain('Desarrollador Senior'); + expect(result.experience.length).toBeGreaterThan(0); + }); + + it('should detect and warn about sensitive information', async () => { + // Arrange + const mockFile = new File(['mock content'], 'cv-with-sensitive.pdf', { + type: 'application/pdf' + }); + + // Mock content with sensitive information + mockPDFJS.getDocument().promise.then((pdf: any) => { + pdf.getPage.and.returnValue(Promise.resolve({ + getTextContent: () => Promise.resolve({ + items: [ + { str: 'John Doe' }, + { str: 'SSN: 123-45-6789' }, + { str: 'Date of Birth: 01/01/1990' }, + { str: 'Driver License: D1234567' } + ] + }) + })); + }); + + // Act + const result = await service.parseCV(mockFile); + + // Assert + expect(result.sensitiveDataWarnings).toBeDefined(); + expect(result.sensitiveDataWarnings!.length).toBeGreaterThan(0); + expect(result.sensitiveDataWarnings).toContain('SSN detected'); + expect(result.sensitiveDataWarnings).toContain('Date of birth detected'); + }); + }); +}); \ No newline at end of file diff --git a/src/app/services/cv-parser.service.ts b/src/app/services/cv-parser.service.ts new file mode 100644 index 0000000..be4d68d --- /dev/null +++ b/src/app/services/cv-parser.service.ts @@ -0,0 +1,540 @@ +import { Injectable } from '@angular/core'; +import * as pdfjsLib from 'pdfjs-dist'; +import { + CVProfile, + PersonalInfo, + WorkExperience, + Education, + Skill, + Certification, + Language, + SkillCategory, + SkillLevel, + LanguageProficiency, + CVParsingResult, + CVCorrections, + ParsingAccuracy +} from '../models/cv-profile.interface'; +import { DataSanitizer, validateCVProfile } from '../models/validation'; + +// Configure PDF.js worker - use local worker from node_modules +pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url +).toString(); + +@Injectable({ + providedIn: 'root' +}) +export class CVParserService { + private readonly EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; + private readonly PHONE_REGEX = /(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})/g; + private readonly DATE_REGEX = /\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{4}|\b\d{1,2}\/\d{1,2}\/\d{4}|\b\d{4}\b/gi; + private readonly URL_REGEX = /https?:\/\/[^\s]+|(?:linkedin\.com|github\.com)\/[^\s]+/gi; + + private readonly SKILL_KEYWORDS = { + technical: ['javascript', 'typescript', 'angular', 'react', 'vue', 'node.js', 'python', 'java', 'c#', 'c++', 'sql', 'mongodb', 'docker', 'kubernetes', 'aws', 'azure', 'git'], + soft: ['leadership', 'communication', 'teamwork', 'problem solving', 'analytical', 'creative', 'adaptable', 'organized'], + language: ['english', 'spanish', 'french', 'german', 'mandarin', 'japanese', 'portuguese', 'italian', 'russian', 'arabic'] + }; + + private readonly SECTION_HEADERS = { + experience: ['experience', 'work experience', 'employment', 'professional experience', 'career history'], + education: ['education', 'academic background', 'qualifications', 'degrees'], + skills: ['skills', 'technical skills', 'core competencies', 'expertise'], + certifications: ['certifications', 'certificates', 'credentials', 'licenses'] + }; + + constructor() {} + + public async parseCV(file: File): Promise { + console.log(`🔧 Starting CV parsing for file: ${file.name} (${file.size} bytes, type: ${file.type})`); + + try { + console.log(`📄 Extracting text from PDF...`); + const extractedText = await this.extractTextFromPDF(file); + console.log(`✅ Text extraction completed. Length: ${extractedText.length} characters`); + console.log(`📝 First 200 characters: "${extractedText.substring(0, 200)}..."`); + + const profile = this.parseTextToProfile(extractedText, file.name); + console.log(`🔍 Profile parsing completed`); + + // Add file metadata + profile.uploadDate = new Date(); + profile.lastModified = new Date(); + profile.id = DataSanitizer.generateUUID(); + profile.fileName = file.name; + profile.parsedText = extractedText; + + // Detect sensitive information + profile.sensitiveDataWarnings = this.detectSensitiveInformation(extractedText); + + console.log(`🎉 CV parsing successful - Profile ID: ${profile.id}`); + return profile; + } catch (error) { + console.error(`❌ CV parsing failed:`, error); + throw new Error(`Failed to parse PDF: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } + + private async extractTextFromPDF(file: File): Promise { + try { + console.log(`🔧 Converting file to array buffer...`); + const arrayBuffer = await file.arrayBuffer(); + console.log(`✅ Array buffer created: ${arrayBuffer.byteLength} bytes`); + + console.log(`🔧 Loading PDF document...`); + const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; + console.log(`✅ PDF loaded successfully: ${pdf.numPages} pages`); + + let fullText = ''; + + for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { + console.log(`🔧 Processing page ${pageNum}/${pdf.numPages}...`); + const page = await pdf.getPage(pageNum); + const textContent = await page.getTextContent(); + + console.log(`📄 Page ${pageNum} has ${textContent.items.length} text items`); + + const pageText = textContent.items + .map((item: any) => item.str) + .join(' '); + + console.log(`✅ Page ${pageNum} text (${pageText.length} chars): "${pageText.substring(0, 100)}..."`); + fullText += pageText + '\n'; + } + + const finalText = fullText.trim(); + console.log(`🎉 PDF text extraction complete: ${finalText.length} total characters`); + return finalText; + } catch (error) { + console.error(`❌ PDF extraction failed:`, error); + throw new Error(`Failed to extract text from PDF: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } + + private parseTextToProfile(text: string, fileName: string): CVProfile { + const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0); + + return { + id: '', // Will be set by caller + fileName, + uploadDate: new Date(), + personalInfo: this.extractPersonalInfo(lines), + experience: this.extractWorkExperience(lines), + education: this.extractEducation(lines), + skills: this.extractSkills(lines), + certifications: this.extractCertifications(lines), + languages: this.extractLanguages(lines), + parsedText: text, + lastModified: new Date() + }; + } + + private extractPersonalInfo(lines: string[]): PersonalInfo { + const fullText = lines.join(' '); + + // Extract email + const emailMatch = fullText.match(this.EMAIL_REGEX); + const email = emailMatch ? emailMatch[0] : ''; + + // Extract phone + const phoneMatch = fullText.match(this.PHONE_REGEX); + const phone = phoneMatch ? phoneMatch[0] : ''; + + // Extract URLs + const urlMatches = fullText.match(this.URL_REGEX) || []; + let linkedIn = ''; + let github = ''; + let website = ''; + + urlMatches.forEach(url => { + if (url.includes('linkedin.com')) linkedIn = url; + else if (url.includes('github.com')) github = url; + else website = url; + }); + + // Extract name (assume first non-empty line is the name) + const fullName = lines.find(line => + line.length > 2 && + !line.includes('@') && + !line.match(/\d/) && + line.split(' ').length >= 2 + ) || ''; + + // Extract location (look for city, state patterns) + const locationPattern = /([A-Za-z\s]+),\s*([A-Z]{2}|[A-Za-z\s]+)/; + const locationMatch = fullText.match(locationPattern); + const location = locationMatch ? locationMatch[0] : ''; + + return { + fullName: DataSanitizer.sanitizeText(fullName), + email: DataSanitizer.normalizeEmail(email), + phone: DataSanitizer.normalizePhoneNumber(phone), + location: DataSanitizer.sanitizeText(location), + linkedIn, + github, + website + }; + } + + private extractWorkExperience(lines: string[]): WorkExperience[] { + const experiences: WorkExperience[] = []; + const experienceSection = this.findSection(lines, this.SECTION_HEADERS.experience); + + if (!experienceSection.length) return experiences; + + let currentExperience: Partial | null = null; + + for (const line of experienceSection) { + // Check if line looks like a company/position header + if (this.looksLikeJobTitle(line)) { + if (currentExperience && currentExperience.company && currentExperience.position) { + experiences.push(this.completeWorkExperience(currentExperience)); + } + + currentExperience = this.parseJobHeader(line); + } else if (currentExperience) { + // Add to description or parse dates + const dates = this.extractDatesFromLine(line); + if (dates.length >= 1) { + currentExperience.startDate = dates[0]; + if (dates.length >= 2) { + currentExperience.endDate = dates[1]; + } + } else if (line.length > 10) { + currentExperience.description = (currentExperience.description || '') + ' ' + line; + } + } + } + + // Add final experience + if (currentExperience && currentExperience.company && currentExperience.position) { + experiences.push(this.completeWorkExperience(currentExperience)); + } + + return experiences; + } + + private extractEducation(lines: string[]): Education[] { + const education: Education[] = []; + const educationSection = this.findSection(lines, this.SECTION_HEADERS.education); + + if (!educationSection.length) return education; + + let currentEducation: Partial | null = null; + + for (const line of educationSection) { + if (this.looksLikeEducationEntry(line)) { + if (currentEducation && currentEducation.institution) { + education.push(this.completeEducation(currentEducation)); + } + + currentEducation = this.parseEducationHeader(line); + } else if (currentEducation) { + const dates = this.extractDatesFromLine(line); + if (dates.length >= 1) { + currentEducation.startDate = dates[0]; + if (dates.length >= 2) { + currentEducation.endDate = dates[1]; + } + } + + // Extract GPA + const gpaMatch = line.match(/GPA:?\s*(\d+\.?\d*)/i); + if (gpaMatch) { + currentEducation.gpa = parseFloat(gpaMatch[1]); + } + + // Extract honors + const honorsKeywords = ['magna cum laude', 'summa cum laude', 'cum laude', 'dean\'s list', 'honors']; + const honor = honorsKeywords.find(keyword => line.toLowerCase().includes(keyword)); + if (honor) { + currentEducation.honors = [honor]; + } + } + } + + if (currentEducation && currentEducation.institution) { + education.push(this.completeEducation(currentEducation)); + } + + return education; + } + + private extractSkills(lines: string[]): Skill[] { + const skills: Skill[] = []; + const skillsSection = this.findSection(lines, this.SECTION_HEADERS.skills); + const fullText = lines.join(' ').toLowerCase(); + + // Extract from skills section and full text + const allText = [...skillsSection, fullText].join(' '); + + // Technical skills + this.SKILL_KEYWORDS.technical.forEach(skillName => { + if (allText.toLowerCase().includes(skillName.toLowerCase())) { + const escapedSkillName = skillName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const yearsMatch = allText.match(new RegExp(`${escapedSkillName}.*?(\\d+)\\s*years?`, 'i')); + const years = yearsMatch ? parseInt(yearsMatch[1]) : undefined; + + skills.push({ + id: DataSanitizer.generateUUID(), + name: skillName, + category: SkillCategory.TECHNICAL, + proficiency: this.determineProficiency(years), + yearsOfExperience: years + }); + } + }); + + // Soft skills + this.SKILL_KEYWORDS.soft.forEach(skillName => { + if (allText.toLowerCase().includes(skillName.toLowerCase())) { + skills.push({ + id: DataSanitizer.generateUUID(), + name: skillName, + category: SkillCategory.SOFT, + proficiency: SkillLevel.INTERMEDIATE // Default for soft skills + }); + } + }); + + return skills; + } + + private extractCertifications(lines: string[]): Certification[] { + const certifications: Certification[] = []; + const certSection = this.findSection(lines, this.SECTION_HEADERS.certifications); + + for (const line of certSection) { + if (line.length > 5) { + const cert = this.parseCertificationLine(line); + if (cert) { + certifications.push(cert); + } + } + } + + return certifications; + } + + private extractLanguages(lines: string[]): Language[] { + const languages: Language[] = []; + const fullText = lines.join(' ').toLowerCase(); + + this.SKILL_KEYWORDS.language.forEach(langName => { + const pattern = new RegExp(`${langName}\\s*\\(?([^\\)]*?)\\)?`, 'i'); + const match = fullText.match(pattern); + + if (match) { + const proficiencyText = match[1] || ''; + let proficiency = LanguageProficiency.CONVERSATIONAL; + + if (proficiencyText.includes('native') || proficiencyText.includes('mother tongue')) { + proficiency = LanguageProficiency.NATIVE; + } else if (proficiencyText.includes('fluent')) { + proficiency = LanguageProficiency.FLUENT; + } else if (proficiencyText.includes('basic') || proficiencyText.includes('beginner')) { + proficiency = LanguageProficiency.BASIC; + } + + languages.push({ + id: DataSanitizer.generateUUID(), + name: langName, + proficiency + }); + } + }); + + return languages; + } + + // Helper methods + private findSection(lines: string[], headers: string[]): string[] { + let sectionStart = -1; + let sectionEnd = lines.length; + + // Find section start + for (let i = 0; i < lines.length; i++) { + const line = lines[i].toLowerCase(); + if (headers.some(header => line.includes(header))) { + sectionStart = i + 1; + break; + } + } + + if (sectionStart === -1) return []; + + // Find section end (next section header) + for (let i = sectionStart; i < lines.length; i++) { + const line = lines[i].toLowerCase(); + const isNextSection = Object.values(this.SECTION_HEADERS) + .flat() + .some(header => line.includes(header) && !headers.includes(header)); + + if (isNextSection) { + sectionEnd = i; + break; + } + } + + return lines.slice(sectionStart, sectionEnd); + } + + private looksLikeJobTitle(line: string): boolean { + const commonTitles = ['developer', 'engineer', 'manager', 'analyst', 'coordinator', 'specialist', 'director', 'lead']; + return commonTitles.some(title => line.toLowerCase().includes(title)) && + line.split(' ').length <= 8; + } + + private looksLikeEducationEntry(line: string): boolean { + const educationKeywords = ['university', 'college', 'institute', 'school', 'bachelor', 'master', 'phd', 'degree']; + return educationKeywords.some(keyword => line.toLowerCase().includes(keyword)); + } + + private parseJobHeader(line: string): Partial { + const parts = line.split(/[-–—]/); + + if (parts.length >= 2) { + return { + id: DataSanitizer.generateUUID(), + company: DataSanitizer.sanitizeText(parts[0].trim()), + position: DataSanitizer.sanitizeText(parts[1].trim()), + description: '', + technologies: [], + achievements: [] + }; + } + + return { + id: DataSanitizer.generateUUID(), + company: 'Unknown Company', + position: DataSanitizer.sanitizeText(line), + description: '', + technologies: [], + achievements: [] + }; + } + + private parseEducationHeader(line: string): Partial { + const parts = line.split(/[-–—]/); + + return { + id: DataSanitizer.generateUUID(), + institution: DataSanitizer.sanitizeText(parts[0] || 'Unknown Institution'), + degree: DataSanitizer.sanitizeText(parts[1] || 'Unknown Degree'), + field: DataSanitizer.sanitizeText(parts[2] || 'Unknown Field') + }; + } + + private parseCertificationLine(line: string): Certification | null { + const parts = line.split(/[-–—]/); + + if (parts.length >= 2) { + const dates = this.extractDatesFromLine(line); + + return { + id: DataSanitizer.generateUUID(), + name: DataSanitizer.sanitizeText(parts[0]), + issuer: DataSanitizer.sanitizeText(parts[1]), + issueDate: dates[0] || new Date(), + expiryDate: dates[1] + }; + } + + return null; + } + + private extractDatesFromLine(line: string): Date[] { + const dateMatches = line.match(this.DATE_REGEX) || []; + return dateMatches.map(dateStr => new Date(dateStr)).filter(date => !isNaN(date.getTime())); + } + + private determineProficiency(years?: number): SkillLevel { + if (!years) return SkillLevel.INTERMEDIATE; + if (years >= 7) return SkillLevel.EXPERT; + if (years >= 4) return SkillLevel.ADVANCED; + if (years >= 2) return SkillLevel.INTERMEDIATE; + return SkillLevel.BEGINNER; + } + + private completeWorkExperience(partial: Partial): WorkExperience { + return { + id: partial.id || DataSanitizer.generateUUID(), + company: partial.company || 'Unknown Company', + position: partial.position || 'Unknown Position', + startDate: partial.startDate || new Date(), + endDate: partial.endDate, + description: partial.description || '', + technologies: partial.technologies || [], + achievements: partial.achievements || [] + }; + } + + private completeEducation(partial: Partial): Education { + return { + id: partial.id || DataSanitizer.generateUUID(), + institution: partial.institution || 'Unknown Institution', + degree: partial.degree || 'Unknown Degree', + field: partial.field || 'Unknown Field', + startDate: partial.startDate || new Date(), + endDate: partial.endDate, + gpa: partial.gpa, + honors: partial.honors + }; + } + + private detectSensitiveInformation(text: string): string[] { + const warnings: string[] = []; + + // SSN detection + if (text.match(/\b\d{3}-\d{2}-\d{4}\b/)) { + warnings.push('SSN detected'); + } + + // Date of birth detection + if (text.match(/\b(?:born|dob|date of birth)\b/i)) { + warnings.push('Date of birth detected'); + } + + // Driver license detection + if (text.match(/\b(?:driver|license|dl)\s*#?\s*\w+/i)) { + warnings.push('Driver license detected'); + } + + return warnings; + } + + // Public methods for test compatibility + public async applyCorrections(profile: CVProfile, corrections: CVCorrections): Promise { + const correctedProfile = { ...profile }; + + if (corrections.personalInfo) { + correctedProfile.personalInfo = { ...profile.personalInfo, ...corrections.personalInfo }; + } + + if (corrections.skills) { + correctedProfile.skills = [...profile.skills, ...corrections.skills]; + } + + if (corrections.experience) { + correctedProfile.experience = corrections.experience; + } + + if (corrections.education) { + correctedProfile.education = corrections.education; + } + + correctedProfile.lastModified = new Date(); + + return correctedProfile; + } + + public async calculateParsingAccuracy(profile: CVProfile): Promise { + const validation = validateCVProfile(profile); + const totalFields = 10; // Adjust based on key fields + const validFields = totalFields - validation.errors.length; + + return Math.max(0, validFields / totalFields); + } +} \ No newline at end of file diff --git a/src/app/services/language-config.service.ts b/src/app/services/language-config.service.ts new file mode 100644 index 0000000..2f73ec6 --- /dev/null +++ b/src/app/services/language-config.service.ts @@ -0,0 +1,276 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export interface SpeechLanguage { + code: string; + name: string; + nativeName: string; + flag: string; + questionPatterns: RegExp[]; + hintPatterns: { pattern: RegExp; description: string }[]; +} + +export interface LanguageConfig { + selectedLanguage: string; + autoDetect: boolean; + fallbackLanguage: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class LanguageConfigService { + private readonly STORAGE_KEY = 'interview-assistant-language-config'; + private readonly DEFAULT_LANGUAGE = 'en-US'; + + // Supported languages for speech recognition + private readonly supportedLanguages: SpeechLanguage[] = [ + { + code: 'en-US', + name: 'English (US)', + nativeName: 'English', + flag: '🇺🇸', + questionPatterns: [ + /^(what|how|when|where|why|who|which|can|could|would|should|do|does|did|is|are|was|were|will|have|has|had)/i, + /(tell me about|explain|describe|walk me through)/i, + /(\?|question)/i + ], + hintPatterns: [ + { pattern: /(let me ask you about|let's talk about|tell me about)/i, description: 'Question introduction' }, + { pattern: /(now|next|moving on|let's move to)/i, description: 'Topic shift' }, + { pattern: /(can you clarify|what do you mean|could you explain)/i, description: 'Clarification request' }, + { pattern: /(follow up|following up|one more thing)/i, description: 'Follow-up question' } + ] + }, + { + code: 'fr-FR', + name: 'French (France)', + nativeName: 'Français', + flag: '🇫🇷', + questionPatterns: [ + /^(qu'est-ce que|qu'est-ce qui|que|quoi|comment|quand|où|pourquoi|qui|quel|quelle|peux-tu|pouvez-vous|peux tu|pouvez vous)/i, + /(parlez-moi de|expliquez|décrivez|dites-moi|expliquez-moi)/i, + /(question|\?)/i, + /^(est-ce que|est ce que)/i, + /(pouvez-vous me dire|peux-tu me dire)/i + ], + hintPatterns: [ + { pattern: /(permettez-moi de vous demander|parlons de|dites-moi)/i, description: 'Introduction de question' }, + { pattern: /(maintenant|ensuite|passons à|allons à)/i, description: 'Changement de sujet' }, + { pattern: /(pouvez-vous clarifier|que voulez-vous dire|pouvez-vous expliquer)/i, description: 'Demande de clarification' }, + { pattern: /(pour continuer|en continuant|une autre chose)/i, description: 'Question de suivi' } + ] + }, + { + code: 'es-ES', + name: 'Spanish (Spain)', + nativeName: 'Español', + flag: '🇪🇸', + questionPatterns: [ + /^(qué|cómo|cuándo|dónde|por qué|quién|cuál|puedes|puede|puedes tú|puede usted)/i, + /(háblame de|explica|describe|dime|explícame)/i, + /(pregunta|\?)/i, + /^(es que|es esto que)/i, + /(puedes decirme|puede decirme)/i + ], + hintPatterns: [ + { pattern: /(déjame preguntarte|hablemos de|dime)/i, description: 'Introducción de pregunta' }, + { pattern: /(ahora|siguiente|pasemos a|vamos a)/i, description: 'Cambio de tema' }, + { pattern: /(puedes aclarar|qué quieres decir|puedes explicar)/i, description: 'Solicitud de aclaración' }, + { pattern: /(para continuar|continuando|otra cosa)/i, description: 'Pregunta de seguimiento' } + ] + }, + { + code: 'de-DE', + name: 'German (Germany)', + nativeName: 'Deutsch', + flag: '🇩🇪', + questionPatterns: [ + /^(was|wie|wann|wo|warum|wer|welche|welcher|können sie|kannst du|könnten sie)/i, + /(erzählen sie mir|erklären sie|beschreiben sie|sagen sie mir)/i, + /(frage|\?)/i, + /(können sie mir sagen|kannst du mir sagen)/i + ], + hintPatterns: [ + { pattern: /(lassen sie mich fragen|sprechen wir über|sagen sie mir)/i, description: 'Frage Einleitung' }, + { pattern: /(jetzt|nächste|gehen wir zu|kommen wir zu)/i, description: 'Themenwechsel' }, + { pattern: /(können sie klären|was meinen sie|können sie erklären)/i, description: 'Klärung anfordern' }, + { pattern: /(um fortzufahren|fortsetzend|noch etwas)/i, description: 'Nachfrage' } + ] + } + ]; + + private currentLanguageSubject = new BehaviorSubject(this.DEFAULT_LANGUAGE); + private configSubject = new BehaviorSubject(this.getDefaultConfig()); + + public currentLanguage$ = this.currentLanguageSubject.asObservable(); + public config$ = this.configSubject.asObservable(); + + constructor() { + this.loadConfiguration(); + } + + // Get all supported languages + getSupportedLanguages(): SpeechLanguage[] { + return [...this.supportedLanguages]; + } + + // Get current language configuration + getCurrentLanguage(): string { + return this.currentLanguageSubject.value; + } + + // Get language details by code + getLanguageByCode(code: string): SpeechLanguage | undefined { + return this.supportedLanguages.find(lang => lang.code === code); + } + + // Set current language + setLanguage(languageCode: string): void { + const language = this.getLanguageByCode(languageCode); + if (language) { + this.currentLanguageSubject.next(languageCode); + this.updateConfig({ selectedLanguage: languageCode }); + console.log(`Language changed to: ${language.name} (${languageCode})`); + } else { + console.warn(`Language code ${languageCode} not supported. Using default.`); + } + } + + // Get question patterns for current language + getCurrentQuestionPatterns(): RegExp[] { + const language = this.getLanguageByCode(this.getCurrentLanguage()); + return language?.questionPatterns || this.supportedLanguages[0].questionPatterns; + } + + // Get hint patterns for current language + getCurrentHintPatterns(): { pattern: RegExp; description: string }[] { + const language = this.getLanguageByCode(this.getCurrentLanguage()); + return language?.hintPatterns || this.supportedLanguages[0].hintPatterns; + } + + // Update language configuration + updateConfig(partialConfig: Partial): void { + const currentConfig = this.configSubject.value; + const newConfig = { ...currentConfig, ...partialConfig }; + this.configSubject.next(newConfig); + this.saveConfiguration(newConfig); + } + + // Get current configuration + getConfig(): LanguageConfig { + return this.configSubject.value; + } + + // Auto-detect language from browser + autoDetectLanguage(): string { + const browserLang = navigator.language || navigator.languages?.[0] || this.DEFAULT_LANGUAGE; + + // Map browser language to supported language + const supportedCode = this.mapBrowserLanguageToSupported(browserLang); + + if (this.getConfig().autoDetect) { + this.setLanguage(supportedCode); + } + + return supportedCode; + } + + // Check if a language is supported + isLanguageSupported(languageCode: string): boolean { + return this.supportedLanguages.some(lang => lang.code === languageCode); + } + + // Get localized error messages + getLocalizedErrorMessage(errorType: string): string { + const language = this.getCurrentLanguage(); + + const errorMessages: { [key: string]: { [key: string]: string } } = { + 'not-allowed': { + 'en-US': 'Microphone access denied. Please allow microphone access in your browser settings.', + 'fr-FR': 'Accès au microphone refusé. Veuillez autoriser l\'accès au microphone dans les paramètres de votre navigateur.', + 'es-ES': 'Acceso al micrófono denegado. Permita el acceso al micrófono en la configuración de su navegador.', + 'de-DE': 'Mikrofonzugriff verweigert. Bitte erlauben Sie den Mikrofonzugriff in Ihren Browsereinstellungen.' + }, + 'no-speech': { + 'en-US': 'No speech detected. Please speak clearly into your microphone.', + 'fr-FR': 'Aucune parole détectée. Veuillez parler clairement dans votre microphone.', + 'es-ES': 'No se detectó habla. Hable claramente en su micrófono.', + 'de-DE': 'Keine Sprache erkannt. Bitte sprechen Sie deutlich in Ihr Mikrofon.' + }, + 'network': { + 'en-US': 'Network error occurred during speech recognition.', + 'fr-FR': 'Erreur réseau lors de la reconnaissance vocale.', + 'es-ES': 'Error de red durante el reconocimiento de voz.', + 'de-DE': 'Netzwerkfehler bei der Spracherkennung.' + } + }; + + return errorMessages[errorType]?.[language] || errorMessages[errorType]?.['en-US'] || 'An error occurred'; + } + + // Reset to default configuration + resetToDefaults(): void { + const defaultConfig = this.getDefaultConfig(); + this.configSubject.next(defaultConfig); + this.currentLanguageSubject.next(this.DEFAULT_LANGUAGE); + this.saveConfiguration(defaultConfig); + } + + private getDefaultConfig(): LanguageConfig { + return { + selectedLanguage: this.DEFAULT_LANGUAGE, + autoDetect: true, + fallbackLanguage: this.DEFAULT_LANGUAGE + }; + } + + private mapBrowserLanguageToSupported(browserLang: string): string { + // Extract language code (e.g., 'fr-FR' from 'fr-FR' or 'fr' from 'fr') + const langCode = browserLang.toLowerCase(); + + // Direct match + if (this.isLanguageSupported(langCode)) { + return langCode; + } + + // Try with region (fr -> fr-FR) + const langMap: { [key: string]: string } = { + 'fr': 'fr-FR', + 'en': 'en-US', + 'es': 'es-ES', + 'de': 'de-DE' + }; + + const baseLanguage = langCode.split('-')[0]; + if (langMap[baseLanguage] && this.isLanguageSupported(langMap[baseLanguage])) { + return langMap[baseLanguage]; + } + + return this.DEFAULT_LANGUAGE; + } + + private loadConfiguration(): void { + try { + const savedConfig = localStorage.getItem(this.STORAGE_KEY); + if (savedConfig) { + const config: LanguageConfig = JSON.parse(savedConfig); + this.configSubject.next(config); + this.currentLanguageSubject.next(config.selectedLanguage); + } else if (this.getConfig().autoDetect) { + this.autoDetectLanguage(); + } + } catch (error) { + console.warn('Failed to load language configuration from localStorage:', error); + this.resetToDefaults(); + } + } + + private saveConfiguration(config: LanguageConfig): void { + try { + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config)); + } catch (error) { + console.warn('Failed to save language configuration to localStorage:', error); + } + } +} \ No newline at end of file diff --git a/src/app/services/logging.service.ts b/src/app/services/logging.service.ts new file mode 100644 index 0000000..b3dc130 --- /dev/null +++ b/src/app/services/logging.service.ts @@ -0,0 +1,587 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + FATAL = 4 +} + +export interface LogEntry { + id: string; + timestamp: Date; + level: LogLevel; + category: string; + message: string; + data?: any; + source?: string; + userId?: string; + sessionId?: string; + stackTrace?: string; +} + +export interface LogFilter { + level?: LogLevel; + category?: string; + source?: string; + startDate?: Date; + endDate?: Date; + searchTerm?: string; +} + +export interface LogExport { + format: 'json' | 'csv' | 'txt'; + filter?: LogFilter; + includeData?: boolean; +} + +@Injectable({ + providedIn: 'root' +}) +export class LoggingService { + private readonly MAX_LOGS = 1000; + private readonly STORAGE_KEY = 'interview_assistant_logs'; + + private currentLogLevel = LogLevel.INFO; + private logs: LogEntry[] = []; + private logsSubject = new BehaviorSubject([]); + private currentSessionId?: string; + private currentUserId?: string; + + public logs$ = this.logsSubject.asObservable(); + public logCount$ = new BehaviorSubject(0); + + // Log categories + private readonly categories = { + SPEECH: 'speech', + CV_PARSER: 'cv-parser', + QUESTION_BANK: 'question-bank', + N8N_SYNC: 'n8n-sync', + ANALYTICS: 'analytics', + UI: 'ui', + AUTH: 'auth', + STORAGE: 'storage', + NETWORK: 'network', + ERROR: 'error', + PERFORMANCE: 'performance', + USER_ACTION: 'user-action', + SYSTEM: 'system' + }; + + constructor() { + this.loadLogsFromStorage(); + this.setupErrorHandling(); + this.setupPerformanceMonitoring(); + + // Set log level based on environment + if (this.isProduction()) { + this.currentLogLevel = LogLevel.WARN; + } else { + this.currentLogLevel = LogLevel.DEBUG; + } + + this.info(this.categories.SYSTEM, 'Logging service initialized', { + logLevel: LogLevel[this.currentLogLevel], + maxLogs: this.MAX_LOGS + }); + } + + // Configuration Methods + public setLogLevel(level: LogLevel): void { + this.currentLogLevel = level; + this.info(this.categories.SYSTEM, `Log level changed to ${LogLevel[level]}`); + } + + public setSessionId(sessionId: string): void { + this.currentSessionId = sessionId; + this.info(this.categories.SYSTEM, `Session ID set: ${sessionId}`); + } + + public setUserId(userId: string): void { + this.currentUserId = userId; + this.info(this.categories.SYSTEM, `User ID set: ${userId}`); + } + + // Core Logging Methods + public debug(category: string, message: string, data?: any, source?: string): void { + this.log(LogLevel.DEBUG, category, message, data, source); + } + + public info(category: string, message: string, data?: any, source?: string): void { + this.log(LogLevel.INFO, category, message, data, source); + } + + public warn(category: string, message: string, data?: any, source?: string): void { + this.log(LogLevel.WARN, category, message, data, source); + } + + public error(category: string, message: string, error?: any, source?: string): void { + let data = error; + let stackTrace: string | undefined; + + if (error instanceof Error) { + data = { + name: error.name, + message: error.message, + stack: error.stack + }; + stackTrace = error.stack; + } + + this.log(LogLevel.ERROR, category, message, data, source, stackTrace); + } + + public fatal(category: string, message: string, error?: any, source?: string): void { + let data = error; + let stackTrace: string | undefined; + + if (error instanceof Error) { + data = { + name: error.name, + message: error.message, + stack: error.stack + }; + stackTrace = error.stack; + } + + this.log(LogLevel.FATAL, category, message, data, source, stackTrace); + } + + // Specialized Logging Methods + public logSpeechEvent(event: string, data?: any): void { + this.info(this.categories.SPEECH, `Speech event: ${event}`, data, 'SpeechService'); + } + + public logCVParserEvent(event: string, data?: any): void { + this.info(this.categories.CV_PARSER, `CV parser event: ${event}`, data, 'CVParserService'); + } + + public logQuestionBankEvent(event: string, data?: any): void { + this.info(this.categories.QUESTION_BANK, `Question bank event: ${event}`, data, 'QuestionBankService'); + } + + public logN8nSyncEvent(event: string, data?: any): void { + this.info(this.categories.N8N_SYNC, `N8n sync event: ${event}`, data, 'N8nSyncService'); + } + + public logUserAction(action: string, data?: any): void { + this.info(this.categories.USER_ACTION, `User action: ${action}`, data, 'UI'); + } + + public logPerformance(operation: string, duration: number, data?: any): void { + this.info(this.categories.PERFORMANCE, `Performance: ${operation} took ${duration}ms`, { + operation, + duration, + ...data + }, 'PerformanceMonitor'); + } + + public logNetworkRequest(method: string, url: string, status: number, duration: number): void { + const level = status >= 400 ? LogLevel.ERROR : LogLevel.INFO; + this.log(level, this.categories.NETWORK, `${method} ${url} - ${status}`, { + method, + url, + status, + duration + }, 'HttpClient'); + } + + // Search and Filter Methods + public searchLogs(filter: LogFilter): LogEntry[] { + return this.logs.filter(log => { + if (filter.level !== undefined && log.level < filter.level) { + return false; + } + + if (filter.category && log.category !== filter.category) { + return false; + } + + if (filter.source && log.source !== filter.source) { + return false; + } + + if (filter.startDate && log.timestamp < filter.startDate) { + return false; + } + + if (filter.endDate && log.timestamp > filter.endDate) { + return false; + } + + if (filter.searchTerm) { + const searchTerm = filter.searchTerm.toLowerCase(); + return log.message.toLowerCase().includes(searchTerm) || + log.category.toLowerCase().includes(searchTerm) || + (log.source && log.source.toLowerCase().includes(searchTerm)); + } + + return true; + }); + } + + public getLogsByLevel(level: LogLevel): LogEntry[] { + return this.logs.filter(log => log.level === level); + } + + public getLogsByCategory(category: string): LogEntry[] { + return this.logs.filter(log => log.category === category); + } + + public getLogsByTimeRange(startDate: Date, endDate: Date): LogEntry[] { + return this.logs.filter(log => + log.timestamp >= startDate && log.timestamp <= endDate + ); + } + + // Export Methods + public exportLogs(exportConfig: LogExport): string { + const filteredLogs = exportConfig.filter ? this.searchLogs(exportConfig.filter) : this.logs; + + switch (exportConfig.format) { + case 'json': + return this.exportAsJson(filteredLogs, exportConfig.includeData); + case 'csv': + return this.exportAsCsv(filteredLogs, exportConfig.includeData); + case 'txt': + return this.exportAsText(filteredLogs, exportConfig.includeData); + default: + throw new Error(`Unsupported export format: ${exportConfig.format}`); + } + } + + public clearLogs(): void { + this.logs = []; + this.logsSubject.next([]); + this.logCount$.next(0); + this.clearStoredLogs(); + this.info(this.categories.SYSTEM, 'Logs cleared'); + } + + public getLogStatistics(): any { + const stats = { + total: this.logs.length, + byLevel: {} as any, + byCategory: {} as any, + bySource: {} as any, + timeRange: { + oldest: this.logs.length > 0 ? this.logs[0].timestamp : null, + newest: this.logs.length > 0 ? this.logs[this.logs.length - 1].timestamp : null + } + }; + + // Count by level + Object.values(LogLevel).forEach(level => { + if (typeof level === 'number') { + stats.byLevel[LogLevel[level]] = this.logs.filter(log => log.level === level).length; + } + }); + + // Count by category + this.logs.forEach(log => { + stats.byCategory[log.category] = (stats.byCategory[log.category] || 0) + 1; + }); + + // Count by source + this.logs.forEach(log => { + if (log.source) { + stats.bySource[log.source] = (stats.bySource[log.source] || 0) + 1; + } + }); + + return stats; + } + + // Core Logging Implementation + private log(level: LogLevel, category: string, message: string, data?: any, source?: string, stackTrace?: string): void { + if (level < this.currentLogLevel) { + return; // Skip logs below current level + } + + const logEntry: LogEntry = { + id: this.generateLogId(), + timestamp: new Date(), + level, + category, + message, + data: this.sanitizeData(data), + source, + userId: this.currentUserId, + sessionId: this.currentSessionId, + stackTrace + }; + + this.addLogEntry(logEntry); + + // Also log to browser console for development + if (!this.isProduction()) { + this.logToConsole(logEntry); + } + + // Store critical errors immediately + if (level >= LogLevel.ERROR) { + this.storeLogsToStorage(); + } + } + + private addLogEntry(entry: LogEntry): void { + this.logs.push(entry); + + // Maintain maximum log count + if (this.logs.length > this.MAX_LOGS) { + this.logs.shift(); // Remove oldest log + } + + this.logsSubject.next([...this.logs]); + this.logCount$.next(this.logs.length); + } + + private logToConsole(entry: LogEntry): void { + const prefix = `[${LogLevel[entry.level]}] [${entry.category}]`; + const message = `${prefix} ${entry.message}`; + + switch (entry.level) { + case LogLevel.DEBUG: + console.debug(message, entry.data); + break; + case LogLevel.INFO: + console.info(message, entry.data); + break; + case LogLevel.WARN: + console.warn(message, entry.data); + break; + case LogLevel.ERROR: + case LogLevel.FATAL: + console.error(message, entry.data); + if (entry.stackTrace) { + console.error(entry.stackTrace); + } + break; + } + } + + // Storage Methods + private loadLogsFromStorage(): void { + try { + const stored = localStorage.getItem(this.STORAGE_KEY); + if (stored) { + const parsedLogs = JSON.parse(stored); + this.logs = parsedLogs.map((log: any) => ({ + ...log, + timestamp: new Date(log.timestamp) + })); + this.logsSubject.next([...this.logs]); + this.logCount$.next(this.logs.length); + } + } catch (error) { + console.error('Failed to load logs from storage:', error); + } + } + + private storeLogsToStorage(): void { + try { + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.logs)); + } catch (error) { + console.error('Failed to store logs to storage:', error); + // If storage is full, try clearing old logs + if (error instanceof DOMException && error.code === 22) { + this.clearOldLogs(); + try { + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.logs)); + } catch (retryError) { + console.error('Failed to store logs after clearing old ones:', retryError); + } + } + } + } + + private clearStoredLogs(): void { + try { + localStorage.removeItem(this.STORAGE_KEY); + } catch (error) { + console.error('Failed to clear stored logs:', error); + } + } + + private clearOldLogs(): void { + // Keep only the most recent 500 logs + const keepCount = 500; + if (this.logs.length > keepCount) { + this.logs = this.logs.slice(-keepCount); + this.logsSubject.next([...this.logs]); + this.logCount$.next(this.logs.length); + } + } + + // Export Implementation + private exportAsJson(logs: LogEntry[], includeData = true): string { + const exportLogs = logs.map(log => { + const exportLog: any = { + id: log.id, + timestamp: log.timestamp.toISOString(), + level: LogLevel[log.level], + category: log.category, + message: log.message, + source: log.source, + userId: log.userId, + sessionId: log.sessionId + }; + + if (includeData && log.data) { + exportLog.data = log.data; + } + + if (log.stackTrace) { + exportLog.stackTrace = log.stackTrace; + } + + return exportLog; + }); + + return JSON.stringify(exportLogs, null, 2); + } + + private exportAsCsv(logs: LogEntry[], includeData = true): string { + const headers = ['ID', 'Timestamp', 'Level', 'Category', 'Message', 'Source', 'UserID', 'SessionID']; + if (includeData) { + headers.push('Data'); + } + + let csv = headers.join(',') + '\n'; + + logs.forEach(log => { + const row = [ + log.id, + log.timestamp.toISOString(), + LogLevel[log.level], + log.category, + `"${log.message.replace(/"/g, '""')}"`, // Escape quotes + log.source || '', + log.userId || '', + log.sessionId || '' + ]; + + if (includeData) { + const dataStr = log.data ? JSON.stringify(log.data).replace(/"/g, '""') : ''; + row.push(`"${dataStr}"`); + } + + csv += row.join(',') + '\n'; + }); + + return csv; + } + + private exportAsText(logs: LogEntry[], includeData = true): string { + let text = 'Interview Assistant - Log Export\n'; + text += '='.repeat(50) + '\n\n'; + + logs.forEach(log => { + text += `[${log.timestamp.toISOString()}] `; + text += `[${LogLevel[log.level]}] `; + text += `[${log.category}] `; + if (log.source) { + text += `[${log.source}] `; + } + text += `${log.message}\n`; + + if (includeData && log.data) { + text += ` Data: ${JSON.stringify(log.data)}\n`; + } + + if (log.stackTrace) { + text += ` Stack: ${log.stackTrace}\n`; + } + + text += '\n'; + }); + + return text; + } + + // Utility Methods + private generateLogId(): string { + return `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private sanitizeData(data: any): any { + if (!data) return data; + + try { + // Deep clone to avoid circular references + return JSON.parse(JSON.stringify(data, (key, value) => { + // Remove sensitive information + if (typeof key === 'string' && key.toLowerCase().includes('password')) { + return '[REDACTED]'; + } + if (typeof key === 'string' && key.toLowerCase().includes('token')) { + return '[REDACTED]'; + } + if (typeof key === 'string' && key.toLowerCase().includes('secret')) { + return '[REDACTED]'; + } + return value; + })); + } catch (error) { + return '[CIRCULAR_REFERENCE]'; + } + } + + private isProduction(): boolean { + // Check if we're in production environment + return (window as any).location?.hostname !== 'localhost' && + !(window as any).location?.hostname?.includes('127.0.0.1'); + } + + // Error Handling Setup + private setupErrorHandling(): void { + // Global error handler + window.addEventListener('error', (event) => { + this.error(this.categories.ERROR, 'Global error caught', { + message: event.message, + filename: event.filename, + lineno: event.lineno, + colno: event.colno, + error: event.error + }, 'GlobalErrorHandler'); + }); + + // Unhandled promise rejection handler + window.addEventListener('unhandledrejection', (event) => { + this.error(this.categories.ERROR, 'Unhandled promise rejection', { + reason: event.reason, + promise: event.promise + }, 'PromiseRejectionHandler'); + }); + } + + // Performance Monitoring Setup + private setupPerformanceMonitoring(): void { + // Monitor navigation timing + if (window.performance && window.performance.timing) { + window.addEventListener('load', () => { + const timing = window.performance.timing; + const navigationTime = timing.loadEventEnd - timing.navigationStart; + + this.logPerformance('page-load', navigationTime, { + domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart, + domInteractive: timing.domInteractive - timing.navigationStart, + domComplete: timing.domComplete - timing.navigationStart + }); + }); + } + } + + // Public API for categories + public getCategories(): typeof this.categories { + return this.categories; + } + + // Cleanup + public destroy(): void { + this.storeLogsToStorage(); + this.info(this.categories.SYSTEM, 'Logging service destroyed'); + } +} \ No newline at end of file diff --git a/src/app/services/n8n-sync.service.spec.ts b/src/app/services/n8n-sync.service.spec.ts new file mode 100644 index 0000000..1061754 --- /dev/null +++ b/src/app/services/n8n-sync.service.spec.ts @@ -0,0 +1,371 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { N8nSyncService } from './n8n-sync.service'; + +describe('N8nSyncService - Contract Tests', () => { + let service: N8nSyncService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [N8nSyncService] + }); + service = TestBed.inject(N8nSyncService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('T007: Contract test POST /interview/cv-analysis', () => { + it('should submit CV for analysis with correct request format', () => { + // Arrange + const mockCVAnalysisRequest = { + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + personalInfo: { + fullName: 'John Doe', + email: 'john.doe@example.com', + phone: '+1234567890', + location: 'New York, NY', + linkedIn: 'https://linkedin.com/in/johndoe', + github: 'https://github.com/johndoe' + }, + experience: [ + { + id: '123e4567-e89b-12d3-a456-426614174001', + company: 'Tech Corp', + position: 'Senior Developer', + startDate: new Date('2020-01-01'), + endDate: new Date('2023-12-31'), + description: 'Full-stack development', + technologies: ['Angular', 'Node.js', 'TypeScript'], + achievements: ['Led team of 5 developers', 'Reduced load time by 40%'] + } + ], + education: [ + { + id: '123e4567-e89b-12d3-a456-426614174002', + institution: 'University of Technology', + degree: 'Bachelor of Science', + field: 'Computer Science', + startDate: new Date('2016-09-01'), + endDate: new Date('2020-05-31') + } + ], + skills: [ + { + id: '123e4567-e89b-12d3-a456-426614174003', + name: 'Angular', + category: 'technical' as const, + proficiency: 'advanced' as const, + yearsOfExperience: 5 + } + ], + certifications: [], + parsedText: 'Full CV text content here...' + }; + + const expectedResponse = { + analysisId: '123e4567-e89b-12d3-a456-426614174004', + status: 'processing' as const, + estimatedCompletionTime: 300, + questionBankId: '123e4567-e89b-12d3-a456-426614174005' + }; + + // Act + service.submitCVAnalysis(mockCVAnalysisRequest).subscribe(response => { + // Assert + expect(response).toEqual(expectedResponse); + expect(response.analysisId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + expect(response.status).toBe('processing'); + expect(response.estimatedCompletionTime).toBeGreaterThan(0); + expect(response.questionBankId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + }); + + // Verify HTTP request + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/cv-analysis'); + expect(req.request.method).toBe('POST'); + expect(req.request.headers.get('Content-Type')).toBe('application/json'); + expect(req.request.headers.get('Authorization')).toContain('Bearer'); + expect(req.request.body).toEqual(mockCVAnalysisRequest); + + // Respond with mock data + req.flush(expectedResponse); + }); + + it('should handle 401 Unauthorized response', () => { + const mockRequest = { + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + personalInfo: { fullName: 'John Doe', email: 'john@example.com' }, + experience: [], + education: [], + skills: [], + certifications: [], + parsedText: 'CV text' + }; + + service.submitCVAnalysis(mockRequest).subscribe({ + next: () => fail('Should have failed with 401'), + error: (error) => { + expect(error.status).toBe(401); + expect(error.error.error).toBe('Invalid or missing authentication token'); + } + }); + + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/cv-analysis'); + req.flush( + { error: 'Invalid or missing authentication token' }, + { status: 401, statusText: 'Unauthorized' } + ); + }); + + it('should handle 429 Rate Limited response', () => { + const mockRequest = { + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + personalInfo: { fullName: 'John Doe', email: 'john@example.com' }, + experience: [], + education: [], + skills: [], + certifications: [], + parsedText: 'CV text' + }; + + service.submitCVAnalysis(mockRequest).subscribe({ + next: () => fail('Should have failed with 429'), + error: (error) => { + expect(error.status).toBe(429); + expect(error.error.error).toBe('Rate limit exceeded. Please try again later.'); + expect(error.error.retryAfter).toBeGreaterThan(0); + } + }); + + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/cv-analysis'); + req.flush( + { + error: 'Rate limit exceeded. Please try again later.', + retryAfter: 60 + }, + { status: 429, statusText: 'Too Many Requests' } + ); + }); + }); + + describe('T008: Contract test GET /interview/question-bank', () => { + it('should retrieve question bank with correct parameters', () => { + const cvProfileId = '123e4567-e89b-12d3-a456-426614174000'; + const expectedResponse = { + questionBankId: '123e4567-e89b-12d3-a456-426614174005', + cvProfileId: cvProfileId, + generatedDate: '2023-12-15T10:30:00Z', + questions: [ + { + id: '123e4567-e89b-12d3-a456-426614174006', + text: 'What is Angular dependency injection?', + category: 'technical' as const, + difficulty: 'medium' as const, + tags: ['angular', 'dependency-injection', 'typescript'], + answer: { + id: '123e4567-e89b-12d3-a456-426614174007', + content: 'Angular dependency injection is a design pattern...', + keyPoints: ['Singleton services', 'Hierarchical injectors', 'Provider configuration'], + followUpQuestions: ['How do you create a singleton service?'], + estimatedDuration: 45, + personalizedContext: 'Based on your 5 years of Angular experience...' + }, + relatedSkills: ['Angular'], + confidence: 0.95, + timesUsed: 0 + } + ], + metadata: { + totalQuestions: 25, + categoriesDistribution: { + technical: 15, + behavioral: 5, + experience: 5 + }, + averageConfidence: 0.89, + lastUpdated: '2023-12-15T10:30:00Z' + } + }; + + service.getQuestionBank(cvProfileId).subscribe(response => { + expect(response).toEqual(expectedResponse); + expect(response.questionBankId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + expect(response.cvProfileId).toBe(cvProfileId); + expect(response.questions.length).toBeGreaterThan(0); + expect(response.metadata.totalQuestions).toBeGreaterThan(0); + }); + + const req = httpMock.expectOne(`https://n8n.gm-tech.org/webhook/interview/question-bank?cvProfileId=${cvProfileId}`); + expect(req.request.method).toBe('GET'); + expect(req.request.headers.get('Authorization')).toContain('Bearer'); + + req.flush(expectedResponse); + }); + + it('should handle 404 Not Found for invalid CV profile', () => { + const invalidCvProfileId = 'invalid-uuid'; + + service.getQuestionBank(invalidCvProfileId).subscribe({ + next: () => fail('Should have failed with 404'), + error: (error) => { + expect(error.status).toBe(404); + expect(error.error.error).toBe('Question bank not found for the specified CV profile'); + } + }); + + const req = httpMock.expectOne(`https://n8n.gm-tech.org/webhook/interview/question-bank?cvProfileId=${invalidCvProfileId}`); + req.flush( + { error: 'Question bank not found for the specified CV profile' }, + { status: 404, statusText: 'Not Found' } + ); + }); + }); + + describe('T009: Contract test POST /interview/session-sync', () => { + it('should synchronize session data with correct format', () => { + const mockSessionSyncRequest = { + sessionId: '123e4567-e89b-12d3-a456-426614174008', + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + startTime: '2023-12-15T14:00:00Z', + endTime: '2023-12-15T15:30:00Z', + detectedQuestions: [ + { + id: '123e4567-e89b-12d3-a456-426614174009', + timestamp: '2023-12-15T14:15:00Z', + originalText: 'What is Angular dependency injection?', + normalizedText: 'what is angular dependency injection', + confidence: 0.95, + questionBankMatch: '123e4567-e89b-12d3-a456-426614174006', + responseTime: 250, + wasHelpRequested: true + } + ], + providedAnswers: [ + { + id: '123e4567-e89b-12d3-a456-426614174010', + questionId: '123e4567-e89b-12d3-a456-426614174009', + answerId: '123e4567-e89b-12d3-a456-426614174007', + content: 'Angular dependency injection is a design pattern that allows...', + timestamp: '2023-12-15T14:15:00Z', + source: 'question_bank' as const, + userRating: 5 + } + ], + manualComments: [ + { + id: '123e4567-e89b-12d3-a456-426614174011', + timestamp: '2023-12-15T14:20:00Z', + content: 'Need to emphasize SOLID principles in future answers', + category: 'improvement' as const, + relatedQuestionId: '123e4567-e89b-12d3-a456-426614174009', + tags: ['solid-principles', 'improvement'] + } + ], + analytics: { + totalQuestions: 1, + answersFromBank: 1, + answersGenerated: 0, + averageResponseTime: 250, + accuracyRate: 1.0, + topicsCovered: ['angular', 'dependency-injection'] + } + }; + + const expectedResponse = { + syncId: '123e4567-e89b-12d3-a456-426614174012', + status: 'success' as const, + updatedQuestions: [], + recommendations: ['Consider adding more examples to dependency injection answers'], + errors: [] + }; + + service.syncSessionData(mockSessionSyncRequest).subscribe(response => { + expect(response).toEqual(expectedResponse); + expect(response.syncId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + expect(response.status).toBe('success'); + }); + + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/session-sync'); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual(mockSessionSyncRequest); + + req.flush(expectedResponse); + }); + + it('should handle 400 Bad Request for invalid data', () => { + const invalidRequest = { + sessionId: 'invalid-uuid', + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + detectedQuestions: [], + providedAnswers: [] + }; + + service.syncSessionData(invalidRequest).subscribe({ + next: () => fail('Should have failed with 400'), + error: (error) => { + expect(error.status).toBe(400); + expect(error.error.error).toBeDefined(); + expect(error.error.validationErrors).toBeInstanceOf(Array); + } + }); + + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/session-sync'); + req.flush( + { + error: 'Invalid request data', + validationErrors: ['sessionId must be a valid UUID'] + }, + { status: 400, statusText: 'Bad Request' } + ); + }); + }); + + describe('T010: Contract test POST /interview/analytics', () => { + it('should submit analytics with correct format', () => { + const mockAnalyticsRequest = { + sessionId: '123e4567-e89b-12d3-a456-426614174008', + analytics: { + totalQuestions: 15, + answersFromBank: 12, + answersGenerated: 3, + averageResponseTime: 350, + accuracyRate: 0.87, + topicsCovered: ['angular', 'typescript', 'rxjs', 'testing'] + }, + improvements: [ + 'Add more behavioral question examples', + 'Improve response time for complex technical questions' + ] + }; + + const expectedResponse = { + processed: true, + insights: [ + 'Strong performance on Angular questions', + 'Consider practicing more complex scenarios' + ], + recommendedActions: [ + 'Focus on behavioral interview preparation', + 'Practice explaining complex technical concepts simply' + ] + }; + + service.submitAnalytics(mockAnalyticsRequest).subscribe(response => { + expect(response).toEqual(expectedResponse); + expect(response.processed).toBe(true); + expect(response.insights.length).toBeGreaterThan(0); + expect(response.recommendedActions.length).toBeGreaterThan(0); + }); + + const req = httpMock.expectOne('https://n8n.gm-tech.org/webhook/interview/analytics'); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual(mockAnalyticsRequest); + + req.flush(expectedResponse); + }); + }); +}); \ No newline at end of file diff --git a/src/app/services/n8n-sync.service.ts b/src/app/services/n8n-sync.service.ts new file mode 100644 index 0000000..9e38759 --- /dev/null +++ b/src/app/services/n8n-sync.service.ts @@ -0,0 +1,604 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; +import { environment } from '../../environments/environment'; +import { BehaviorSubject, Observable, of, throwError, timer, EMPTY } from 'rxjs'; +import { AutheliaAuthService } from './authelia-auth.service'; +import { map, catchError, retry, switchMap, tap, delay, timeout } from 'rxjs/operators'; +import { + N8nSyncData, + SyncStatus, + SyncError, + CVAnalysisRequest, + CVAnalysisResponse, + QuestionBankResponse, + SessionSyncRequest, + SessionSyncResponse, + AnalyticsRequest, + AnalyticsResponse, + AutheliaAuthToken, + N8nApiConfig, + N8nDataMapper +} from '../models/n8n-sync.interface'; +import { CVProfile } from '../models/cv-profile.interface'; +import { QuestionBank } from '../models/question-bank.interface'; +import { InterviewSession, SessionSummary, SessionAnalytics } from '../models/interview-session.interface'; +import { DataSanitizer } from '../models/validation'; + +@Injectable({ + providedIn: 'root' +}) +export class N8nSyncService implements N8nDataMapper { + private readonly apiConfig: N8nApiConfig = { + baseUrl: environment.n8nWebhookUrl.replace('/webhook/cv-analysis', ''), + authToken: '', + timeout: 30000, + retryAttempts: 3 + }; + + private authTokenSubject = new BehaviorSubject(null); + private syncStatusSubject = new BehaviorSubject(SyncStatus.PENDING); + private lastSyncSubject = new BehaviorSubject(null); + private isOnlineSubject = new BehaviorSubject(navigator.onLine); + + public authToken$ = this.authTokenSubject.asObservable(); + public syncStatus$ = this.syncStatusSubject.asObservable(); + public lastSync$ = this.lastSyncSubject.asObservable(); + public isOnline$ = this.isOnlineSubject.asObservable(); + + private pendingSyncs = new Map(); + private syncQueue: string[] = []; + + constructor( + private http: HttpClient, + private autheliaAuth: AutheliaAuthService + ) { + this.setupNetworkStatusMonitoring(); + this.initializeAuthToken(); + this.startSyncQueueProcessor(); + } + + // Authentication Methods + public async authenticateWithAuthelia(username: string, password: string): Promise { + try { + const authEndpoint = `${this.apiConfig.baseUrl}/api/verify`; + + const response = await this.http.post(authEndpoint, { + username, + password + }, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + withCredentials: true + }).toPromise(); + + if (response && response.token) { + const authToken: AutheliaAuthToken = { + token: response.token, + expiresAt: new Date(Date.now() + (response.expires_in || 3600) * 1000), + refreshToken: response.refresh_token + }; + + this.authTokenSubject.next(authToken); + this.apiConfig.authToken = response.token; + this.storeAuthToken(authToken); + + return true; + } + + return false; + } catch (error) { + console.error('Authelia authentication failed:', error); + return false; + } + } + + public async refreshAuthToken(): Promise { + const currentToken = this.authTokenSubject.value; + if (!currentToken?.refreshToken) { + return false; + } + + try { + const refreshEndpoint = `${this.apiConfig.baseUrl}/api/refresh`; + + const response = await this.http.post(refreshEndpoint, { + refresh_token: currentToken.refreshToken + }, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${currentToken.token}` + }) + }).toPromise(); + + if (response && response.token) { + const newAuthToken: AutheliaAuthToken = { + token: response.token, + expiresAt: new Date(Date.now() + (response.expires_in || 3600) * 1000), + refreshToken: response.refresh_token || currentToken.refreshToken + }; + + this.authTokenSubject.next(newAuthToken); + this.apiConfig.authToken = response.token; + this.storeAuthToken(newAuthToken); + + return true; + } + + return false; + } catch (error) { + console.error('Token refresh failed:', error); + this.clearAuthToken(); + return false; + } + } + + // CV Analysis Methods + public submitCVForAnalysis(cvProfile: CVProfile): Observable { + // Check if user is authenticated with Authelia + if (!this.autheliaAuth.isAuthenticated) { + console.log('🔒 Authelia authentication required for CV analysis'); + return throwError(() => new Error('Authelia authentication required')); + } + + this.syncStatusSubject.next(SyncStatus.IN_PROGRESS); + + const request = this.mapCVProfileToRequest(cvProfile); + const endpoint = environment.apiEndpoints.cvAnalysis; + + console.log('🚀 Submitting CV for analysis with Authelia auth'); + + // Use AutheliaAuthService for authenticated requests + return this.autheliaAuth.makeAuthenticatedRequest(endpoint, request, 'POST').pipe( + retry(this.apiConfig.retryAttempts), + tap(response => { + console.log('✅ CV analysis submitted:', response.analysisId); + this.syncStatusSubject.next(SyncStatus.SUCCESS); + }), + catchError(error => { + console.error('❌ CV analysis failed:', error); + this.syncStatusSubject.next(SyncStatus.FAILED); + + if (error.message === 'Authentication required') { + console.log('🔐 Redirecting to Authelia login...'); + // Let the component handle the redirect to login + } + + return throwError(() => error); + }) + ); + } + + public pollAnalysisStatus(analysisId: string): Observable { + const statusEndpoint = `${this.apiConfig.baseUrl}/webhook/cv-analysis/${analysisId}/status`; + + return timer(0, 5000).pipe( // Poll every 5 seconds + switchMap(() => + this.http.get(statusEndpoint, { + headers: this.getAuthHeaders() + }).pipe( + catchError(error => { + console.warn('Status polling error:', error); + return of({ status: 'failed' } as CVAnalysisResponse); + }) + ) + ), + tap(response => { + if (response.status === 'completed' || response.status === 'failed') { + this.syncStatusSubject.next( + response.status === 'completed' ? SyncStatus.SUCCESS : SyncStatus.FAILED + ); + } + }) + ); + } + + public getGeneratedQuestionBank(questionBankId: string): Observable { + if (!this.isAuthenticated()) { + return throwError('Authentication required'); + } + + const endpoint = `${this.apiConfig.baseUrl}/webhook/question-bank/${questionBankId}`; + + return this.http.get(endpoint, { + headers: this.getAuthHeaders() + }).pipe( + map(response => this.mapResponseToQuestionBank(response)), + retry(this.apiConfig.retryAttempts), + catchError(this.handleHttpError.bind(this)) + ); + } + + // Session Synchronization Methods + public syncInterviewSession(session: InterviewSession): Observable { + if (!this.isAuthenticated()) { + return this.queueSyncForLater(session); + } + + this.syncStatusSubject.next(SyncStatus.IN_PROGRESS); + + const request = this.mapSessionToSyncRequest(session); + const endpoint = `${this.apiConfig.baseUrl}/webhook/session-sync`; + + return this.http.post(endpoint, request, { + headers: this.getAuthHeaders(), + timeout: this.apiConfig.timeout + }).pipe( + retry(this.apiConfig.retryAttempts), + tap(response => { + this.syncStatusSubject.next( + response.status === 'success' ? SyncStatus.SUCCESS : SyncStatus.FAILED + ); + this.lastSyncSubject.next(new Date()); + + if (response.status === 'success') { + this.removePendingSync(session.id); + } + }), + catchError(error => { + this.syncStatusSubject.next(SyncStatus.FAILED); + this.queueSyncForLater(session); + return this.handleHttpError(error); + }) + ); + } + + public syncSessionAnalytics(sessionId: string, analytics: SessionAnalytics, improvements: string[]): Observable { + if (!this.isAuthenticated()) { + return throwError('Authentication required'); + } + + const request: AnalyticsRequest = { + sessionId, + analytics, + improvements + }; + + const endpoint = `${this.apiConfig.baseUrl}/webhook/analytics`; + + return this.http.post(endpoint, request, { + headers: this.getAuthHeaders() + }).pipe( + retry(this.apiConfig.retryAttempts), + catchError(this.handleHttpError.bind(this)) + ); + } + + // Bulk Synchronization Methods + public syncAllPendingData(): Observable { + if (!this.isAuthenticated() || this.pendingSyncs.size === 0) { + return of(void 0); + } + + this.syncStatusSubject.next(SyncStatus.IN_PROGRESS); + + const syncObservables: Observable[] = []; + + this.pendingSyncs.forEach(syncData => { + if (syncData.dataSnapshot.sessionSummary) { + // Reconstruct session from snapshot + const session: InterviewSession = { + id: syncData.sessionId, + cvProfileId: syncData.dataSnapshot.cvProfile.id, + questionBankId: '', // Will be filled from session summary + startTime: new Date(), + endTime: undefined, + duration: 0, + detectedQuestions: [], + providedAnswers: [], + questionsAnswered: [], + manualComments: [], + speechHints: [], + analytics: syncData.dataSnapshot.analytics, + status: 'completed' as any + }; + + syncObservables.push(this.syncInterviewSession(session)); + } + }); + + if (syncObservables.length === 0) { + this.syncStatusSubject.next(SyncStatus.SUCCESS); + return of(void 0); + } + + return new Observable(observer => { + Promise.all(syncObservables.map(obs => obs.toPromise())) + .then(() => { + this.syncStatusSubject.next(SyncStatus.SUCCESS); + observer.next(); + observer.complete(); + }) + .catch(error => { + this.syncStatusSubject.next(SyncStatus.FAILED); + observer.error(error); + }); + }); + } + + // Data Mapping Methods (N8nDataMapper implementation) + public mapCVProfileToRequest(profile: CVProfile): CVAnalysisRequest { + return { + cvProfileId: profile.id, + personalInfo: profile.personalInfo, + experience: profile.experience, + education: profile.education, + skills: profile.skills, + certifications: profile.certifications, + parsedText: profile.extractedText || '' + }; + } + + public mapResponseToQuestionBank(response: QuestionBankResponse): QuestionBank { + // Convert the response questions to our internal format + const mappedQuestions = response.questions.map(q => ({ + ...q, + category: q.category as any, // Type assertion for enum conversion + difficulty: q.difficulty as any, // Type assertion for enum conversion + })); + + return { + id: response.questionBankId, + cvProfileId: response.cvProfileId, + generatedDate: new Date(response.generatedDate), + lastUsed: new Date(), + questions: mappedQuestions, + accuracy: 0.9, // Default accuracy + metadata: { + ...response.metadata, + categoriesDistribution: response.metadata.categoriesDistribution || {} + } + }; + } + + public mapSessionToSyncRequest(session: InterviewSession): SessionSyncRequest { + return { + sessionId: session.id, + cvProfileId: session.cvProfileId, + startTime: session.startTime.toISOString(), + endTime: session.endTime?.toISOString(), + detectedQuestions: session.detectedQuestions, + providedAnswers: session.providedAnswers, + manualComments: session.manualComments, + analytics: session.analytics + }; + } + + public mapSyncResponseToSession(response: SessionSyncResponse, session: InterviewSession): InterviewSession { + return { + ...session, + // Update questions based on server response + detectedQuestions: session.detectedQuestions.map(dq => { + const updatedQuestion = response.updatedQuestions.find(uq => uq.id === dq.questionBankMatch); + if (updatedQuestion) { + return { + ...dq, + confidence: Math.max(dq.confidence, updatedQuestion.confidence) + }; + } + return dq; + }) + }; + } + + // Queue Management Methods + private queueSyncForLater(session: InterviewSession): Observable { + const syncData: N8nSyncData = { + id: DataSanitizer.generateUUID(), + sessionId: session.id, + lastSyncTime: new Date(), + syncStatus: SyncStatus.PENDING, + dataSnapshot: { + cvProfile: {} as CVProfile, // Would need to get from service + questionBank: {} as QuestionBank, // Would need to get from service + sessionSummary: this.createSessionSummary(session), + analytics: session.analytics + } + }; + + this.pendingSyncs.set(session.id, syncData); + this.syncQueue.push(session.id); + + // Return a pending response + return of({ + syncId: syncData.id, + status: 'partial', + updatedQuestions: [], + recommendations: ['Sync queued for when connection is available'], + errors: [] + }); + } + + private createSessionSummary(session: InterviewSession): SessionSummary { + const duration = session.endTime && session.startTime ? + (session.endTime.getTime() - session.startTime.getTime()) / (1000 * 60) : 0; + + return { + sessionId: session.id, + duration, + questionsAnswered: session.providedAnswers.length, + successRate: session.analytics.accuracyRate, + improvements: [], + newQuestions: [] + }; + } + + private removePendingSync(sessionId: string): void { + this.pendingSyncs.delete(sessionId); + const index = this.syncQueue.indexOf(sessionId); + if (index > -1) { + this.syncQueue.splice(index, 1); + } + } + + private startSyncQueueProcessor(): void { + // Process sync queue when online + this.isOnline$.subscribe(isOnline => { + if (isOnline && this.syncQueue.length > 0 && this.isAuthenticated()) { + this.processSyncQueue(); + } + }); + } + + private processSyncQueue(): void { + if (this.syncQueue.length === 0) return; + + const sessionId = this.syncQueue.shift(); + if (!sessionId) return; + + const syncData = this.pendingSyncs.get(sessionId); + if (!syncData) return; + + // Process this sync item + setTimeout(() => { + if (this.isAuthenticated() && this.isOnlineSubject.value) { + // Try to sync this item + // This would need the actual session data + console.log('Processing queued sync for session:', sessionId); + } + }, 1000); + } + + // Utility Methods + private isAuthenticated(): boolean { + const token = this.authTokenSubject.value; + return !!(token && token.expiresAt > new Date()); + } + + private getAuthHeaders(): HttpHeaders { + const token = this.authTokenSubject.value; + return new HttpHeaders({ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token?.token || ''}` + }); + } + + private handleHttpError(error: HttpErrorResponse): Observable { + let errorMessage = 'An error occurred'; + + if (error.error instanceof ErrorEvent) { + // Client-side error + errorMessage = `Error: ${error.error.message}`; + } else { + // Server-side error + switch (error.status) { + case 401: + errorMessage = 'Authentication failed'; + this.clearAuthToken(); + break; + case 403: + errorMessage = 'Access forbidden'; + break; + case 404: + errorMessage = 'Service not found'; + break; + case 500: + errorMessage = 'Server error'; + break; + case 503: + errorMessage = 'Service unavailable'; + break; + default: + errorMessage = `Error ${error.status}: ${error.message}`; + } + } + + console.error('N8n API Error:', errorMessage, error); + return throwError(errorMessage); + } + + private setupNetworkStatusMonitoring(): void { + window.addEventListener('online', () => { + this.isOnlineSubject.next(true); + console.log('Network connection restored'); + }); + + window.addEventListener('offline', () => { + this.isOnlineSubject.next(false); + console.log('Network connection lost'); + }); + } + + private initializeAuthToken(): void { + const storedToken = this.getStoredAuthToken(); + if (storedToken && storedToken.expiresAt > new Date()) { + this.authTokenSubject.next(storedToken); + this.apiConfig.authToken = storedToken.token; + } + } + + private storeAuthToken(token: AutheliaAuthToken): void { + try { + localStorage.setItem('n8n_auth_token', JSON.stringify({ + ...token, + expiresAt: token.expiresAt.toISOString() + })); + } catch (error) { + console.error('Failed to store auth token:', error); + } + } + + private getStoredAuthToken(): AutheliaAuthToken | null { + try { + const stored = localStorage.getItem('n8n_auth_token'); + if (stored) { + const parsed = JSON.parse(stored); + return { + ...parsed, + expiresAt: new Date(parsed.expiresAt) + }; + } + } catch (error) { + console.error('Failed to retrieve stored auth token:', error); + } + return null; + } + + private clearAuthToken(): void { + this.authTokenSubject.next(null); + this.apiConfig.authToken = ''; + localStorage.removeItem('n8n_auth_token'); + } + + // Health Check Methods + public checkN8nHealth(): Observable { + const healthEndpoint = `${this.apiConfig.baseUrl}/healthz`; + + return this.http.get(healthEndpoint, { + timeout: 5000 + }).pipe( + map(() => true), + catchError(() => of(false)) + ); + } + + public getApiStatus(): Observable { + if (!this.isAuthenticated()) { + return of({ authenticated: false, online: this.isOnlineSubject.value }); + } + + const statusEndpoint = `${this.apiConfig.baseUrl}/api/status`; + + return this.http.get(statusEndpoint, { + headers: this.getAuthHeaders() + }).pipe( + map(response => ({ + authenticated: true, + online: this.isOnlineSubject.value, + api: response + })), + catchError(() => of({ + authenticated: false, + online: this.isOnlineSubject.value, + error: 'Failed to get API status' + })) + ); + } + + // Cleanup + public destroy(): void { + // Clear any ongoing timers or subscriptions + this.pendingSyncs.clear(); + this.syncQueue.length = 0; + } +} \ No newline at end of file diff --git a/src/app/services/question-bank.service.spec.ts b/src/app/services/question-bank.service.spec.ts new file mode 100644 index 0000000..c06fb9e --- /dev/null +++ b/src/app/services/question-bank.service.spec.ts @@ -0,0 +1,425 @@ +import { TestBed } from '@angular/core/testing'; +import { QuestionBankService } from './question-bank.service'; + +// Mock Dexie and IndexedDB +class MockDexieDB { + cvProfiles = { + add: jasmine.createSpy('add').and.returnValue(Promise.resolve('test-id')), + get: jasmine.createSpy('get').and.returnValue(Promise.resolve(null)), + put: jasmine.createSpy('put').and.returnValue(Promise.resolve('test-id')), + delete: jasmine.createSpy('delete').and.returnValue(Promise.resolve()), + toArray: jasmine.createSpy('toArray').and.returnValue(Promise.resolve([])), + where: jasmine.createSpy('where').and.returnValue({ + equals: jasmine.createSpy('equals').and.returnValue({ + toArray: jasmine.createSpy('toArray').and.returnValue(Promise.resolve([])) + }) + }) + }; + + questionBanks = { + add: jasmine.createSpy('add').and.returnValue(Promise.resolve('test-id')), + get: jasmine.createSpy('get').and.returnValue(Promise.resolve(null)), + put: jasmine.createSpy('put').and.returnValue(Promise.resolve('test-id')), + delete: jasmine.createSpy('delete').and.returnValue(Promise.resolve()), + toArray: jasmine.createSpy('toArray').and.returnValue(Promise.resolve([])), + where: jasmine.createSpy('where').and.returnValue({ + equals: jasmine.createSpy('equals').and.returnValue({ + first: jasmine.createSpy('first').and.returnValue(Promise.resolve(null)), + toArray: jasmine.createSpy('toArray').and.returnValue(Promise.resolve([])) + }) + }) + }; + + interviewSessions = { + add: jasmine.createSpy('add').and.returnValue(Promise.resolve('test-id')), + get: jasmine.createSpy('get').and.returnValue(Promise.resolve(null)), + put: jasmine.createSpy('put').and.returnValue(Promise.resolve('test-id')), + delete: jasmine.createSpy('delete').and.returnValue(Promise.resolve()), + toArray: jasmine.createSpy('toArray').and.returnValue(Promise.resolve([])) + }; + + open = jasmine.createSpy('open').and.returnValue(Promise.resolve()); + close = jasmine.createSpy('close'); +} + +describe('QuestionBankService - Integration Tests', () => { + let service: QuestionBankService; + let mockDb: MockDexieDB; + + beforeEach(() => { + mockDb = new MockDexieDB(); + + TestBed.configureTestingModule({ + providers: [QuestionBankService] + }); + service = TestBed.inject(QuestionBankService); + + // Inject mock database + (service as any).db = mockDb; + }); + + describe('T012: Integration test IndexedDB operations', () => { + it('should initialize database connection', async () => { + // Act + await service.initializeDatabase(); + + // Assert + expect(mockDb.open).toHaveBeenCalled(); + }); + + it('should store CV profile in IndexedDB', async () => { + // Arrange + const cvProfile = { + id: '123e4567-e89b-12d3-a456-426614174000', + fileName: 'john-doe-cv.pdf', + uploadDate: new Date(), + personalInfo: { + fullName: 'John Doe', + email: 'john.doe@example.com', + phone: '+1234567890', + location: 'New York, NY' + }, + experience: [ + { + id: '123e4567-e89b-12d3-a456-426614174001', + company: 'Tech Corp', + position: 'Senior Developer', + startDate: new Date('2020-01-01'), + endDate: new Date('2023-12-31'), + description: 'Full-stack development', + technologies: ['Angular', 'Node.js'], + achievements: ['Led team of 5'] + } + ], + education: [], + skills: [ + { + id: '123e4567-e89b-12d3-a456-426614174003', + name: 'Angular', + category: 'technical' as const, + proficiency: 'advanced' as const, + yearsOfExperience: 5 + } + ], + certifications: [], + languages: [], + parsedText: 'Full CV text content', + lastModified: new Date() + }; + + // Act + const result = await service.saveCVProfile(cvProfile); + + // Assert + expect(mockDb.cvProfiles.put).toHaveBeenCalledWith(cvProfile); + expect(result).toBe('test-id'); + }); + + it('should retrieve CV profile from IndexedDB', async () => { + // Arrange + const expectedProfile = { + id: '123e4567-e89b-12d3-a456-426614174000', + fileName: 'john-doe-cv.pdf', + personalInfo: { fullName: 'John Doe', email: 'john@example.com' } + }; + + mockDb.cvProfiles.get.and.returnValue(Promise.resolve(expectedProfile)); + + // Act + const result = await service.getCVProfile('123e4567-e89b-12d3-a456-426614174000'); + + // Assert + expect(mockDb.cvProfiles.get).toHaveBeenCalledWith('123e4567-e89b-12d3-a456-426614174000'); + expect(result).toEqual(expectedProfile); + }); + + it('should store question bank in IndexedDB', async () => { + // Arrange + const questionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + questions: [ + { + id: '123e4567-e89b-12d3-a456-426614174006', + text: 'What is Angular dependency injection?', + category: 'technical' as const, + difficulty: 'medium' as const, + tags: ['angular', 'dependency-injection'], + answer: { + id: '123e4567-e89b-12d3-a456-426614174007', + content: 'Angular dependency injection is...', + keyPoints: ['Singleton services'], + followUpQuestions: [], + estimatedDuration: 45, + personalizedContext: 'Based on your experience...' + }, + relatedSkills: ['Angular'], + confidence: 0.95, + timesUsed: 0 + } + ], + generatedDate: new Date(), + lastUpdated: new Date(), + accuracy: 0.89, + totalUsage: 0 + }; + + // Act + const result = await service.saveQuestionBank(questionBank); + + // Assert + expect(mockDb.questionBanks.put).toHaveBeenCalledWith(questionBank); + expect(result).toBe('test-id'); + }); + + it('should retrieve question bank by CV profile ID', async () => { + // Arrange + const expectedQuestionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + questions: [] + }; + + mockDb.questionBanks.where().equals().first.and.returnValue(Promise.resolve(expectedQuestionBank)); + + // Act + const result = await service.getQuestionBankByCVProfile('123e4567-e89b-12d3-a456-426614174000'); + + // Assert + expect(mockDb.questionBanks.where).toHaveBeenCalledWith('cvProfileId'); + expect(result).toEqual(expectedQuestionBank); + }); + + it('should find answers by question text with fuzzy matching', async () => { + // Arrange + const questionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + questions: [ + { + id: '123e4567-e89b-12d3-a456-426614174006', + text: 'What is Angular dependency injection?', + tags: ['angular', 'dependency-injection', 'typescript'], + answer: { + content: 'Angular dependency injection is a design pattern...', + keyPoints: ['Singleton services', 'Hierarchical injectors'] + }, + confidence: 0.95 + }, + { + id: '123e4567-e89b-12d3-a456-426614174008', + text: 'How do you create components in Angular?', + tags: ['angular', 'components', 'typescript'], + answer: { + content: 'Angular components are created using...', + keyPoints: ['Component decorator', 'Template and styles'] + }, + confidence: 0.92 + } + ] + }; + + mockDb.questionBanks.toArray.and.returnValue(Promise.resolve([questionBank])); + + // Act - Test exact match + const exactMatch = await service.findAnswer('What is Angular dependency injection?'); + + // Assert + expect(exactMatch).toBeDefined(); + expect(exactMatch!.content).toContain('Angular dependency injection is a design pattern'); + expect(exactMatch!.confidence).toBe(0.95); + + // Act - Test fuzzy match + const fuzzyMatch = await service.findAnswer('angular dependency injection'); + + // Assert + expect(fuzzyMatch).toBeDefined(); + expect(fuzzyMatch!.content).toContain('Angular dependency injection is a design pattern'); + }); + + it('should perform fast question bank queries under 100ms', async () => { + // Arrange + const largeQuestionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + questions: Array.from({ length: 500 }, (_, i) => ({ + id: `question-${i}`, + text: `Question ${i} about Angular`, + tags: ['angular', 'typescript'], + answer: { content: `Answer ${i}` }, + confidence: 0.9 + })) + }; + + mockDb.questionBanks.toArray.and.returnValue(Promise.resolve([largeQuestionBank])); + + // Act + const startTime = performance.now(); + const result = await service.findAnswer('Question 250 about Angular'); + const endTime = performance.now(); + + // Assert + expect(endTime - startTime).toBeLessThan(100); // Sub-100ms requirement + expect(result).toBeDefined(); + }); + + it('should handle IndexedDB storage quota exceeded', async () => { + // Arrange + const quotaError = new DOMException('QuotaExceededError', 'QuotaExceededError'); + mockDb.questionBanks.put.and.returnValue(Promise.reject(quotaError)); + + const questionBank = { id: 'test', questions: [] }; + + // Act & Assert + await expectAsync(service.saveQuestionBank(questionBank)) + .toBeRejectedWithError('Storage quota exceeded'); + }); + + it('should manage session data with proper indexing', async () => { + // Arrange + const sessionData = { + id: '123e4567-e89b-12d3-a456-426614174008', + cvProfileId: '123e4567-e89b-12d3-a456-426614174000', + questionBankId: '123e4567-e89b-12d3-a456-426614174005', + startTime: new Date(), + endTime: new Date(), + detectedQuestions: [ + { + id: '123e4567-e89b-12d3-a456-426614174009', + timestamp: new Date(), + originalText: 'What is Angular?', + normalizedText: 'what is angular', + confidence: 0.95, + questionBankMatch: '123e4567-e89b-12d3-a456-426614174006', + responseTime: 250, + wasHelpRequested: true + } + ], + providedAnswers: [], + manualComments: [], + speechHints: [], + analytics: { + totalQuestions: 1, + answersFromBank: 1, + answersGenerated: 0, + averageResponseTime: 250, + accuracyRate: 1.0, + topicsCovered: ['angular'] + }, + status: 'completed' as const + }; + + // Act + const result = await service.saveInterviewSession(sessionData); + + // Assert + expect(mockDb.interviewSessions.put).toHaveBeenCalledWith(sessionData); + expect(result).toBe('test-id'); + }); + + it('should update question usage statistics', async () => { + // Arrange + const questionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + questions: [ + { + id: '123e4567-e89b-12d3-a456-426614174006', + text: 'What is Angular?', + timesUsed: 5, + confidence: 0.95 + } + ], + totalUsage: 10 + }; + + mockDb.questionBanks.get.and.returnValue(Promise.resolve(questionBank)); + + // Act + await service.updateQuestionUsage('123e4567-e89b-12d3-a456-426614174006'); + + // Assert + expect(mockDb.questionBanks.put).toHaveBeenCalledWith({ + ...questionBank, + questions: [ + { + ...questionBank.questions[0], + timesUsed: 6, + lastUsed: jasmine.any(Date) + } + ], + totalUsage: 11, + lastUpdated: jasmine.any(Date) + }); + }); + + it('should handle database corruption and recovery', async () => { + // Arrange + const corruptionError = new DOMException('Database corruption detected', 'CorruptionError'); + mockDb.open.and.returnValue(Promise.reject(corruptionError)); + + // Act & Assert + await expectAsync(service.initializeDatabase()) + .toBeRejectedWithError('Database corruption detected'); + + // Verify recovery attempt + expect(service.isDatabaseCorrupted()).toBe(true); + }); + + it('should optimize question bank for fast retrieval', async () => { + // Arrange + const questionBank = { + id: '123e4567-e89b-12d3-a456-426614174005', + questions: Array.from({ length: 100 }, (_, i) => ({ + id: `question-${i}`, + text: `Question ${i}`, + tags: i % 2 === 0 ? ['angular'] : ['typescript'], + confidence: 0.9 - (i * 0.001) // Varying confidence + })) + }; + + // Act + const optimizedBank = await service.optimizeQuestionBank(questionBank); + + // Assert + expect(optimizedBank.questions.length).toBeLessThanOrEqual(50); // Pruned for performance + expect(optimizedBank.questions[0].confidence).toBeGreaterThan(0.8); // High confidence first + }); + + it('should handle concurrent database operations', async () => { + // Arrange + const operations = Array.from({ length: 10 }, (_, i) => + service.saveCVProfile({ + id: `profile-${i}`, + fileName: `cv-${i}.pdf`, + personalInfo: { fullName: `User ${i}`, email: `user${i}@example.com` } + }) + ); + + // Act + const results = await Promise.all(operations); + + // Assert + expect(results.length).toBe(10); + results.forEach(result => expect(result).toBe('test-id')); + expect(mockDb.cvProfiles.put).toHaveBeenCalledTimes(10); + }); + + it('should clean up old session data automatically', async () => { + // Arrange + const oldDate = new Date(); + oldDate.setDate(oldDate.getDate() - 30); // 30 days ago + + const sessions = [ + { id: 'old-session', startTime: oldDate }, + { id: 'recent-session', startTime: new Date() } + ]; + + mockDb.interviewSessions.toArray.and.returnValue(Promise.resolve(sessions)); + + // Act + await service.cleanupOldSessions(7); // Keep 7 days + + // Assert + expect(mockDb.interviewSessions.delete).toHaveBeenCalledWith('old-session'); + expect(mockDb.interviewSessions.delete).not.toHaveBeenCalledWith('recent-session'); + }); + }); +}); \ No newline at end of file diff --git a/src/app/services/question-bank.service.ts b/src/app/services/question-bank.service.ts new file mode 100644 index 0000000..7b0bca0 --- /dev/null +++ b/src/app/services/question-bank.service.ts @@ -0,0 +1,705 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, from, of, EMPTY } from 'rxjs'; +import { map, catchError, switchMap, tap } from 'rxjs/operators'; +import Dexie, { Table } from 'dexie'; +import { + QuestionBank, + Question, + Answer, + QuestionSearchCriteria, + QuestionMatch, + QuestionMatchResult, + QuestionSearchResult, + QuestionCategory, + QuestionDifficulty, + QuestionBankGeneration, + QuestionOptimization +} from '../models/question-bank.interface'; +import { CVProfile } from '../models/cv-profile.interface'; +import { DataSanitizer } from '../models/validation'; + +class QuestionBankDatabase extends Dexie { + questionBanks!: Table; + questions!: Table; + answers!: Table; + + constructor() { + super('InterviewAssistantDB'); + + this.version(1).stores({ + questionBanks: 'id, cvProfileId, generatedDate, lastUsed, accuracy', + questions: 'id, bankId, category, difficulty, text, confidence, timesUsed, lastUsed', + answers: 'id, questionId, content, estimatedDuration' + }); + } +} + +@Injectable({ + providedIn: 'root' +}) +export class QuestionBankService { + private db = new QuestionBankDatabase(); + private currentQuestionBankSubject = new BehaviorSubject(null); + private availableQuestionsSubject = new BehaviorSubject([]); + private isLoadingSubject = new BehaviorSubject(false); + + public currentQuestionBank$ = this.currentQuestionBankSubject.asObservable(); + public availableQuestions$ = this.availableQuestionsSubject.asObservable(); + public isLoading$ = this.isLoadingSubject.asObservable(); + + constructor() { + this.initializeDatabase(); + } + + private initializeDatabase(): void { + this.db.open().catch(err => { + console.error('Failed to open question bank database:', err); + }); + } + + public createQuestionBank(cvProfile: CVProfile, generation: QuestionBankGeneration): Observable { + this.isLoadingSubject.next(true); + + const questionBank: QuestionBank = { + id: DataSanitizer.generateUUID(), + cvProfileId: cvProfile.id, + generatedDate: new Date(), + lastUsed: new Date(), + questions: [], + accuracy: 0.9, + metadata: { + totalQuestions: 0, + categoriesDistribution: {}, + averageConfidence: 0, + lastUpdated: new Date().toISOString() + } + }; + + return from(this.db.questionBanks.add(questionBank)).pipe( + switchMap(() => this.generateQuestionsFromCV(cvProfile, questionBank.id, generation)), + switchMap(questions => this.saveQuestions(questions, questionBank.id)), + switchMap(() => this.updateQuestionBankMetadata(questionBank.id)), + switchMap(() => this.getQuestionBank(questionBank.id)), + tap(bank => { + if (bank) { + this.currentQuestionBankSubject.next(bank); + } + this.isLoadingSubject.next(false); + }), + map(bank => bank!), + catchError(error => { + console.error('Error creating question bank:', error); + this.isLoadingSubject.next(false); + return EMPTY; + }) + ); + } + + public loadQuestionBank(bankId: string): Observable { + this.isLoadingSubject.next(true); + + return from(this.db.questionBanks.get(bankId)).pipe( + switchMap(bank => { + if (!bank) return of(null); + + return this.loadQuestionsForBank(bankId).pipe( + map(questions => { + bank.questions = questions; + return bank; + }) + ); + }), + tap(bank => { + this.currentQuestionBankSubject.next(bank); + if (bank) { + this.availableQuestionsSubject.next(bank.questions); + this.updateLastUsed(bankId); + } + this.isLoadingSubject.next(false); + }), + catchError(error => { + console.error('Error loading question bank:', error); + this.isLoadingSubject.next(false); + return of(null); + }) + ); + } + + public getQuestionBank(bankId: string): Observable { + return from(this.db.questionBanks.get(bankId)).pipe( + switchMap(bank => { + if (!bank) return of(null); + + return this.loadQuestionsForBank(bankId).pipe( + map(questions => { + bank.questions = questions; + return bank; + }) + ); + }), + catchError(error => { + console.error('Error getting question bank:', error); + return of(null); + }) + ); + } + + public searchQuestions(criteria: QuestionSearchCriteria): Observable { + const currentBank = this.currentQuestionBankSubject.value; + if (!currentBank) { + return of({ + results: [], + totalResults: 0, + searchTime: 0 + }); + } + + const startTime = performance.now(); + + return this.loadQuestionsForBank(currentBank.id).pipe( + map(questions => { + let filteredQuestions = questions; + + if (criteria.query) { + const queryLower = criteria.query.toLowerCase(); + filteredQuestions = filteredQuestions.filter(q => + q.text.toLowerCase().includes(queryLower) || + q.tags.some(tag => tag.toLowerCase().includes(queryLower)) || + q.answer.content.toLowerCase().includes(queryLower) + ); + } + + if (criteria.category) { + filteredQuestions = filteredQuestions.filter(q => q.category === criteria.category); + } + + if (criteria.difficulty) { + filteredQuestions = filteredQuestions.filter(q => q.difficulty === criteria.difficulty); + } + + if (criteria.minConfidence !== undefined) { + filteredQuestions = filteredQuestions.filter(q => q.confidence >= criteria.minConfidence!); + } + + if (criteria.tags && criteria.tags.length > 0) { + filteredQuestions = filteredQuestions.filter(q => + criteria.tags!.some((tag: string) => q.tags.includes(tag)) + ); + } + + // Apply sorting + if (criteria.sortBy) { + filteredQuestions.sort((a, b) => { + let comparison = 0; + switch (criteria.sortBy) { + case 'confidence': + comparison = b.confidence - a.confidence; + break; + case 'lastUsed': + comparison = (b.lastUsed?.getTime() || 0) - (a.lastUsed?.getTime() || 0); + break; + case 'timesUsed': + comparison = b.timesUsed - a.timesUsed; + break; + case 'text': + comparison = a.text.localeCompare(b.text); + break; + } + return criteria.sortOrder === 'desc' ? comparison : -comparison; + }); + } + + // Apply pagination + const startIndex = (criteria.page || 0) * (criteria.limit || 50); + const endIndex = startIndex + (criteria.limit || 50); + const paginatedResults = filteredQuestions.slice(startIndex, endIndex); + + const searchTime = performance.now() - startTime; + + return { + results: paginatedResults, + totalResults: filteredQuestions.length, + searchTime + }; + }), + catchError(error => { + console.error('Error searching questions:', error); + return of({ + results: [], + totalResults: 0, + searchTime: performance.now() - startTime + }); + }) + ); + } + + public findBestMatch(detectedText: string, threshold: number = 0.7): Observable { + const currentBank = this.currentQuestionBankSubject.value; + if (!currentBank) { + return of({ + hasMatch: false, + confidence: 0, + matches: [] + }); + } + + return this.loadQuestionsForBank(currentBank.id).pipe( + map(questions => { + const normalizedInput = this.normalizeText(detectedText); + const matches: QuestionMatch[] = []; + + questions.forEach(question => { + const normalizedQuestion = this.normalizeText(question.text); + const similarity = this.calculateTextSimilarity(normalizedInput, normalizedQuestion); + + if (similarity >= threshold) { + matches.push({ + question, + similarity, + matchType: this.determineMatchType(similarity) + }); + } + }); + + // Sort by similarity (highest first) + matches.sort((a, b) => b.similarity - a.similarity); + + return { + hasMatch: matches.length > 0, + confidence: matches.length > 0 ? matches[0].similarity : 0, + matches: matches.slice(0, 5) // Return top 5 matches + }; + }), + catchError(error => { + console.error('Error finding question match:', error); + return of({ + hasMatch: false, + confidence: 0, + matches: [] + }); + }) + ); + } + + public updateQuestionUsage(questionId: string): Observable { + return from(this.db.questions.where('id').equals(questionId).modify(question => { + question.timesUsed = question.timesUsed + 1; + question.lastUsed = new Date(); + })).pipe( + tap(() => { + // Update local state + const currentQuestions = this.availableQuestionsSubject.value; + const updatedQuestions = currentQuestions.map(q => { + if (q.id === questionId) { + return { ...q, timesUsed: q.timesUsed + 1, lastUsed: new Date() }; + } + return q; + }); + this.availableQuestionsSubject.next(updatedQuestions); + }), + map(() => void 0), + catchError(error => { + console.error('Error updating question usage:', error); + return EMPTY; + }) + ); + } + + public optimizeQuestionBank(optimization: QuestionOptimization): Observable { + const currentBank = this.currentQuestionBankSubject.value; + if (!currentBank) { + return EMPTY; + } + + return this.loadQuestionsForBank(currentBank.id).pipe( + switchMap(questions => { + let optimizedQuestions = [...questions]; + + if (optimization.removeUnused && optimization.unusedThresholdDays) { + const thresholdDate = new Date(); + thresholdDate.setDate(thresholdDate.getDate() - optimization.unusedThresholdDays); + + optimizedQuestions = optimizedQuestions.filter(q => + q.timesUsed > 0 || !q.lastUsed || q.lastUsed > thresholdDate + ); + } + + if (optimization.removeLowConfidence && optimization.confidenceThreshold) { + optimizedQuestions = optimizedQuestions.filter(q => + q.confidence >= optimization.confidenceThreshold! + ); + } + + if (optimization.mergeSimilar && optimization.similarityThreshold) { + optimizedQuestions = this.mergeSimilarQuestions(optimizedQuestions, optimization.similarityThreshold); + } + + if (optimization.rebalanceCategories) { + optimizedQuestions = this.rebalanceQuestionCategories(optimizedQuestions); + } + + // Save optimized questions + return this.replaceQuestionsInBank(currentBank.id, optimizedQuestions); + }), + switchMap(() => this.updateQuestionBankMetadata(currentBank.id)), + map(() => void 0), + catchError(error => { + console.error('Error optimizing question bank:', error); + return EMPTY; + }) + ); + } + + public getQuestionBanksByProfile(cvProfileId: string): Observable { + return from(this.db.questionBanks.where('cvProfileId').equals(cvProfileId).toArray()).pipe( + catchError(error => { + console.error('Error getting question banks for profile:', error); + return of([]); + }) + ); + } + + public deleteQuestionBank(bankId: string): Observable { + return from(this.db.transaction('rw', this.db.questionBanks, this.db.questions, this.db.answers, async () => { + // Delete all questions and answers for this bank + const questions = await this.db.questions.where('bankId').equals(bankId).toArray(); + const questionIds = questions.map(q => q.id); + + await this.db.answers.where('questionId').anyOf(questionIds).delete(); + await this.db.questions.where('bankId').equals(bankId).delete(); + await this.db.questionBanks.delete(bankId); + })).pipe( + tap(() => { + if (this.currentQuestionBankSubject.value?.id === bankId) { + this.currentQuestionBankSubject.next(null); + this.availableQuestionsSubject.next([]); + } + }), + map(() => void 0), + catchError(error => { + console.error('Error deleting question bank:', error); + return EMPTY; + }) + ); + } + + private generateQuestionsFromCV(cvProfile: CVProfile, bankId: string, generation: QuestionBankGeneration): Observable { + const questions: Question[] = []; + + // Generate behavioral questions + if (generation.includeGeneral) { + questions.push(...this.generateBehavioralQuestions(bankId)); + } + + // Generate technical questions based on skills + if (generation.includeTechnical) { + questions.push(...this.generateTechnicalQuestions(cvProfile, bankId)); + } + + // Generate experience-based questions + if (generation.includeExperience) { + questions.push(...this.generateExperienceQuestions(cvProfile, bankId)); + } + + // Generate education questions + if (generation.includeEducation) { + questions.push(...this.generateEducationQuestions(cvProfile, bankId)); + } + + return of(questions); + } + + private generateBehavioralQuestions(bankId: string): Question[] { + const behavioralTemplates = [ + 'Tell me about a time when you had to overcome a significant challenge.', + 'Describe a situation where you had to work with a difficult team member.', + 'Give me an example of when you had to learn something new quickly.', + 'Tell me about a time when you had to make a difficult decision.', + 'Describe a situation where you had to meet a tight deadline.', + 'Give me an example of when you had to resolve a conflict.', + 'Tell me about a time when you had to adapt to a major change.', + 'Describe a situation where you had to lead a team or project.', + 'Give me an example of when you had to deal with failure.', + 'Tell me about a time when you had to give difficult feedback.' + ]; + + return behavioralTemplates.map(template => this.createQuestion( + template, + QuestionCategory.BEHAVIORAL, + QuestionDifficulty.MEDIUM, + ['behavioral', 'soft-skills'], + bankId + )); + } + + private generateTechnicalQuestions(cvProfile: CVProfile, bankId: string): Question[] { + const questions: Question[] = []; + + cvProfile.skills.forEach(skill => { + const skillQuestions = this.generateSkillQuestions(skill.name, skill.category, bankId); + questions.push(...skillQuestions); + }); + + return questions; + } + + private generateSkillQuestions(skillName: string, category: string, bankId: string): Question[] { + const questions: Question[] = []; + + // Generate basic skill questions + questions.push(this.createQuestion( + `How would you rate your proficiency in ${skillName}?`, + QuestionCategory.TECHNICAL, + QuestionDifficulty.EASY, + ['skills', skillName.toLowerCase(), category.toLowerCase()], + bankId + )); + + questions.push(this.createQuestion( + `Can you describe a project where you used ${skillName}?`, + QuestionCategory.TECHNICAL, + QuestionDifficulty.MEDIUM, + ['skills', 'project-experience', skillName.toLowerCase()], + bankId + )); + + return questions; + } + + private generateExperienceQuestions(cvProfile: CVProfile, bankId: string): Question[] { + const questions: Question[] = []; + + cvProfile.experience.forEach(exp => { + questions.push(this.createQuestion( + `Tell me about your role as ${exp.position} at ${exp.company}.`, + QuestionCategory.EXPERIENCE, + QuestionDifficulty.MEDIUM, + ['experience', exp.company.toLowerCase(), exp.position.toLowerCase()], + bankId + )); + + if (exp.achievements.length > 0) { + questions.push(this.createQuestion( + `What was your biggest achievement at ${exp.company}?`, + QuestionCategory.EXPERIENCE, + QuestionDifficulty.MEDIUM, + ['achievements', exp.company.toLowerCase()], + bankId + )); + } + }); + + return questions; + } + + private generateEducationQuestions(cvProfile: CVProfile, bankId: string): Question[] { + const questions: Question[] = []; + + cvProfile.education.forEach(edu => { + questions.push(this.createQuestion( + `How did your ${edu.degree} in ${edu.field} prepare you for this role?`, + QuestionCategory.EDUCATION, + QuestionDifficulty.EASY, + ['education', edu.degree.toLowerCase(), edu.field.toLowerCase()], + bankId + )); + }); + + return questions; + } + + private createQuestion(text: string, category: QuestionCategory, difficulty: QuestionDifficulty, tags: string[], bankId: string): Question { + const questionId = DataSanitizer.generateUUID(); + const answerId = DataSanitizer.generateUUID(); + + const answer: Answer = { + id: answerId, + content: `[Generated answer for: ${text}]`, + keyPoints: [], + followUpQuestions: [], + estimatedDuration: 60, + personalizedContext: '' + }; + + return { + id: questionId, + text, + category, + difficulty, + tags, + answer, + relatedSkills: [], + confidence: 0.8, + timesUsed: 0, + lastUsed: undefined + }; + } + + private saveQuestions(questions: Question[], bankId: string): Observable { + return from(this.db.transaction('rw', this.db.questions, this.db.answers, async () => { + for (const question of questions) { + // Add bankId to question for indexing + (question as any).bankId = bankId; + await this.db.questions.add(question); + await this.db.answers.add(question.answer); + } + })).pipe( + map(() => void 0) + ); + } + + private replaceQuestionsInBank(bankId: string, questions: Question[]): Observable { + return from(this.db.transaction('rw', this.db.questions, this.db.answers, async () => { + // Delete existing questions and answers + const oldQuestions = await this.db.questions.where('bankId').equals(bankId).toArray(); + const oldQuestionIds = oldQuestions.map(q => q.id); + + await this.db.answers.where('questionId').anyOf(oldQuestionIds).delete(); + await this.db.questions.where('bankId').equals(bankId).delete(); + + // Add new questions + for (const question of questions) { + (question as any).bankId = bankId; + await this.db.questions.add(question); + await this.db.answers.add(question.answer); + } + })).pipe( + map(() => void 0) + ); + } + + private loadQuestionsForBank(bankId: string): Observable { + return from(this.db.questions.where('bankId').equals(bankId).toArray()).pipe( + switchMap(questions => { + if (questions.length === 0) return of([]); + + const questionIds = questions.map(q => q.id); + return from(this.db.answers.where('questionId').anyOf(questionIds).toArray()).pipe( + map(answers => { + const answerMap = new Map(answers.map(a => [a.questionId || a.id, a])); + return questions.map(q => ({ + ...q, + answer: answerMap.get(q.id) || q.answer + })); + }) + ); + }) + ); + } + + private updateQuestionBankMetadata(bankId: string): Observable { + return this.loadQuestionsForBank(bankId).pipe( + switchMap(questions => { + const categoriesDistribution: { [key: string]: number } = {}; + let totalConfidence = 0; + + questions.forEach(q => { + categoriesDistribution[q.category] = (categoriesDistribution[q.category] || 0) + 1; + totalConfidence += q.confidence; + }); + + const metadata = { + totalQuestions: questions.length, + categoriesDistribution, + averageConfidence: questions.length > 0 ? totalConfidence / questions.length : 0, + lastUpdated: new Date().toISOString() + }; + + return from(this.db.questionBanks.update(bankId, { metadata })); + }), + map(() => void 0) + ); + } + + private updateLastUsed(bankId: string): void { + this.db.questionBanks.where('id').equals(bankId).modify({ lastUsed: new Date() }).catch(error => { + console.error('Error updating last used:', error); + }); + } + + private normalizeText(text: string): string { + return text.toLowerCase() + .replace(/[^\w\s]/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + } + + private calculateTextSimilarity(text1: string, text2: string): number { + const words1 = text1.split(' '); + const words2 = text2.split(' '); + + const set1 = new Set(words1); + const set2 = new Set(words2); + + const intersection = new Set([...set1].filter(x => set2.has(x))); + const union = new Set([...set1, ...set2]); + + return intersection.size / union.size; + } + + private determineMatchType(similarity: number): 'exact' | 'high' | 'medium' | 'low' { + if (similarity >= 0.95) return 'exact'; + if (similarity >= 0.8) return 'high'; + if (similarity >= 0.6) return 'medium'; + return 'low'; + } + + private mergeSimilarQuestions(questions: Question[], threshold: number): Question[] { + const merged: Question[] = []; + const processed = new Set(); + + questions.forEach(question => { + if (processed.has(question.id)) return; + + const similar = questions.filter(q => + q.id !== question.id && + !processed.has(q.id) && + this.calculateTextSimilarity(question.text, q.text) >= threshold + ); + + if (similar.length > 0) { + // Merge similar questions + const mergedQuestion = { + ...question, + confidence: Math.max(question.confidence, ...similar.map(q => q.confidence)), + timesUsed: question.timesUsed + similar.reduce((sum, q) => sum + q.timesUsed, 0), + tags: [...new Set([...question.tags, ...similar.flatMap(q => q.tags)])] + }; + merged.push(mergedQuestion); + + similar.forEach(q => processed.add(q.id)); + } else { + merged.push(question); + } + + processed.add(question.id); + }); + + return merged; + } + + private rebalanceQuestionCategories(questions: Question[]): Question[] { + const categoryGroups = questions.reduce((groups, question) => { + if (!groups[question.category]) { + groups[question.category] = []; + } + groups[question.category].push(question); + return groups; + }, {} as { [key: string]: Question[] }); + + // Keep top questions from each category to maintain balance + const maxPerCategory = Math.max(10, Math.floor(questions.length / Object.keys(categoryGroups).length)); + + const balanced: Question[] = []; + Object.values(categoryGroups).forEach(categoryQuestions => { + const sorted = categoryQuestions.sort((a, b) => b.confidence - a.confidence); + balanced.push(...sorted.slice(0, maxPerCategory)); + }); + + return balanced; + } + + public destroy(): void { + this.db.close(); + } +} \ No newline at end of file diff --git a/src/app/services/speech.service.spec.ts b/src/app/services/speech.service.spec.ts new file mode 100644 index 0000000..97a1c83 --- /dev/null +++ b/src/app/services/speech.service.spec.ts @@ -0,0 +1,364 @@ +import { TestBed } from '@angular/core/testing'; +import { SpeechService } from './speech.service'; + +// Mock Web Speech API +interface MockSpeechRecognition extends EventTarget { + continuous: boolean; + interimResults: boolean; + lang: string; + start(): void; + stop(): void; + abort(): void; +} + +interface MockSpeechRecognitionEvent extends Event { + results: SpeechRecognitionResultList; + resultIndex: number; +} + +describe('SpeechService - Integration Tests', () => { + let service: SpeechService; + let mockSpeechRecognition: MockSpeechRecognition; + let mockResults: any; + + beforeEach(() => { + // Mock SpeechRecognition + mockSpeechRecognition = { + continuous: false, + interimResults: false, + lang: 'en-US', + start: jasmine.createSpy('start'), + stop: jasmine.createSpy('stop'), + abort: jasmine.createSpy('abort'), + addEventListener: jasmine.createSpy('addEventListener'), + removeEventListener: jasmine.createSpy('removeEventListener'), + dispatchEvent: jasmine.createSpy('dispatchEvent') + } as any; + + mockResults = { + length: 1, + item: (index: number) => ({ + length: 1, + item: (index: number) => ({ + transcript: 'What is Angular dependency injection?', + confidence: 0.95 + }), + isFinal: true + }) + }; + + // Mock global SpeechRecognition + (window as any).SpeechRecognition = jasmine.createSpy('SpeechRecognition').and.returnValue(mockSpeechRecognition); + (window as any).webkitSpeechRecognition = (window as any).SpeechRecognition; + + TestBed.configureTestingModule({ + providers: [SpeechService] + }); + service = TestBed.inject(SpeechService); + }); + + afterEach(() => { + delete (window as any).SpeechRecognition; + delete (window as any).webkitSpeechRecognition; + }); + + describe('T011: Integration test Speech Recognition API', () => { + it('should initialize speech recognition with correct configuration', () => { + // Act + service.initializeSpeechRecognition(); + + // Assert + expect((window as any).SpeechRecognition).toHaveBeenCalled(); + expect(mockSpeechRecognition.continuous).toBe(true); + expect(mockSpeechRecognition.interimResults).toBe(true); + expect(mockSpeechRecognition.lang).toBe('en-US'); + }); + + it('should start listening and configure event handlers', () => { + // Arrange + service.initializeSpeechRecognition(); + + // Act + service.startListening(); + + // Assert + expect(mockSpeechRecognition.start).toHaveBeenCalled(); + expect(mockSpeechRecognition.addEventListener).toHaveBeenCalledWith('result', jasmine.any(Function)); + expect(mockSpeechRecognition.addEventListener).toHaveBeenCalledWith('error', jasmine.any(Function)); + expect(mockSpeechRecognition.addEventListener).toHaveBeenCalledWith('end', jasmine.any(Function)); + }); + + it('should stop listening when requested', () => { + // Arrange + service.initializeSpeechRecognition(); + service.startListening(); + + // Act + service.stopListening(); + + // Assert + expect(mockSpeechRecognition.stop).toHaveBeenCalled(); + }); + + it('should process speech results and emit transcripts', (done) => { + // Arrange + service.initializeSpeechRecognition(); + + service.speechResults$.subscribe(result => { + // Assert + expect(result.transcript).toBe('What is Angular dependency injection?'); + expect(result.confidence).toBe(0.95); + expect(result.isFinal).toBe(true); + expect(result.timestamp).toBeInstanceOf(Date); + done(); + }); + + service.startListening(); + + // Act - Simulate speech recognition result + const resultEvent = { + type: 'result', + results: mockResults, + resultIndex: 0 + } as MockSpeechRecognitionEvent; + + service.handleSpeechResult(resultEvent); + }); + + it('should detect question patterns in speech', (done) => { + // Arrange + const questionTexts = [ + 'What is Angular dependency injection?', + 'How do you implement routing in Angular?', + 'Tell me about TypeScript interfaces', + 'Can you explain RxJS observables?' + ]; + + let detectedCount = 0; + service.detectedQuestions$.subscribe(question => { + // Assert + expect(question.originalText).toContain(questionTexts[detectedCount]); + expect(question.confidence).toBeGreaterThan(0.8); + expect(question.isQuestion).toBe(true); + expect(question.timestamp).toBeInstanceOf(Date); + + detectedCount++; + if (detectedCount === questionTexts.length) { + done(); + } + }); + + service.initializeSpeechRecognition(); + service.startListening(); + + // Act - Simulate multiple question results + questionTexts.forEach((text, index) => { + const mockResult = { + length: 1, + item: () => ({ + length: 1, + item: () => ({ + transcript: text, + confidence: 0.9 + }), + isFinal: true + }) + }; + + const resultEvent = { + type: 'result', + results: mockResult, + resultIndex: 0 + } as MockSpeechRecognitionEvent; + + setTimeout(() => service.handleSpeechResult(resultEvent), index * 100); + }); + }); + + it('should distinguish between questions and user reading responses', (done) => { + // Arrange + const testInputs = [ + { text: 'What is Angular?', isQuestion: true }, + { text: 'Angular is a platform for building mobile and desktop web applications', isQuestion: false }, + { text: 'How do you create components?', isQuestion: true }, + { text: 'Components are the building blocks of Angular applications', isQuestion: false } + ]; + + let processedCount = 0; + service.speechAnalysis$.subscribe(analysis => { + const expected = testInputs[processedCount]; + expect(analysis.isQuestion).toBe(expected.isQuestion); + expect(analysis.originalText).toBe(expected.text); + + processedCount++; + if (processedCount === testInputs.length) { + done(); + } + }); + + service.initializeSpeechRecognition(); + service.startListening(); + + // Act + testInputs.forEach((input, index) => { + const mockResult = { + length: 1, + item: () => ({ + length: 1, + item: () => ({ + transcript: input.text, + confidence: 0.9 + }), + isFinal: true + }) + }; + + const resultEvent = { + type: 'result', + results: mockResult, + resultIndex: 0 + } as MockSpeechRecognitionEvent; + + setTimeout(() => service.handleSpeechResult(resultEvent), index * 150); + }); + }); + + it('should handle speech recognition errors gracefully', (done) => { + // Arrange + service.speechErrors$.subscribe(error => { + // Assert + expect(error.type).toBe('network'); + expect(error.message).toContain('Network error'); + expect(error.timestamp).toBeInstanceOf(Date); + expect(error.canRetry).toBe(true); + done(); + }); + + service.initializeSpeechRecognition(); + service.startListening(); + + // Act - Simulate speech recognition error + const errorEvent = { + type: 'error', + error: 'network' + } as any; + + service.handleSpeechError(errorEvent); + }); + + it('should detect speech hints for upcoming questions', (done) => { + // Arrange + const hintTexts = [ + 'Let me ask you about Angular', + 'Tell me about your experience with', + 'Now I want to know about', + 'The next question is about' + ]; + + let hintCount = 0; + service.speechHints$.subscribe(hint => { + // Assert + expect(hint.type).toBe('question_intro'); + expect(hint.confidence).toBeGreaterThan(0.7); + expect(hint.detectedText).toContain(hintTexts[hintCount]); + + hintCount++; + if (hintCount === hintTexts.length) { + done(); + } + }); + + service.initializeSpeechRecognition(); + service.startListening(); + + // Act + hintTexts.forEach((text, index) => { + const mockResult = { + length: 1, + item: () => ({ + length: 1, + item: () => ({ + transcript: text, + confidence: 0.85 + }), + isFinal: true + }) + }; + + const resultEvent = { + type: 'result', + results: mockResult, + resultIndex: 0 + } as MockSpeechRecognitionEvent; + + setTimeout(() => service.handleSpeechResult(resultEvent), index * 200); + }); + }); + + it('should handle continuous listening with automatic restart', (done) => { + // Arrange + let restartCount = 0; + service.initializeSpeechRecognition(); + + // Monitor speech recognition state + service.isListening$.subscribe(isListening => { + if (!isListening && restartCount < 2) { + restartCount++; + // Should automatically restart + setTimeout(() => { + expect(mockSpeechRecognition.start).toHaveBeenCalledTimes(restartCount + 1); + if (restartCount === 2) { + done(); + } + }, 100); + } + }); + + service.startListening(); + + // Act - Simulate speech recognition ending + setTimeout(() => { + const endEvent = { type: 'end' } as Event; + service.handleSpeechEnd(endEvent); + }, 100); + + setTimeout(() => { + const endEvent = { type: 'end' } as Event; + service.handleSpeechEnd(endEvent); + }, 300); + }); + + it('should handle browser compatibility issues', () => { + // Arrange - Remove speech recognition support + delete (window as any).SpeechRecognition; + delete (window as any).webkitSpeechRecognition; + + // Act & Assert + expect(() => service.initializeSpeechRecognition()).toThrow(); + + service.speechErrors$.subscribe(error => { + expect(error.type).toBe('not_supported'); + expect(error.message).toContain('Speech recognition not supported'); + expect(error.canRetry).toBe(false); + }); + }); + + it('should manage microphone permissions correctly', (done) => { + // Arrange + service.permissionStatus$.subscribe(status => { + if (status === 'granted') { + expect(service.canStartListening()).toBe(true); + done(); + } + }); + + // Act + service.requestMicrophonePermission(); + + // Simulate permission granted + setTimeout(() => { + service.handlePermissionGranted(); + }, 100); + }); + }); +}); \ No newline at end of file diff --git a/src/app/services/speech.service.ts b/src/app/services/speech.service.ts new file mode 100644 index 0000000..46da488 --- /dev/null +++ b/src/app/services/speech.service.ts @@ -0,0 +1,382 @@ +import { Injectable, inject } from '@angular/core'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { filter, map, debounceTime, takeUntil } from 'rxjs/operators'; +import { + SpeechResult, + SpeechAnalysis, + SpeechError, + SpeechHint, + DetectedQuestion, + HintType +} from '../models/interview-session.interface'; +import { DataSanitizer } from '../models/validation'; +import { LanguageConfigService } from './language-config.service'; + +declare global { + interface Window { + SpeechRecognition: any; + webkitSpeechRecognition: any; + } +} + +interface MockSpeechRecognitionEvent extends Event { + results: SpeechRecognitionResultList; + resultIndex: number; +} + +@Injectable({ + providedIn: 'root' +}) +export class SpeechService { + private languageConfigService = inject(LanguageConfigService); + + private recognition: any; + private isInitialized = false; + private isCurrentlyListening = false; + private currentLanguage = 'en-US'; + private destroy$ = new Subject(); + + // Observables for speech data streams + private speechResultsSubject = new Subject(); + private speechAnalysisSubject = new Subject(); + private speechErrorsSubject = new Subject(); + private speechHintsSubject = new Subject(); + private detectedQuestionsSubject = new Subject(); + private isListeningSubject = new BehaviorSubject(false); + private permissionStatusSubject = new BehaviorSubject('prompt'); + private languageSubject = new BehaviorSubject('en-US'); + + // Public observables + public speechResults$ = this.speechResultsSubject.asObservable(); + public speechAnalysis$ = this.speechAnalysisSubject.asObservable(); + public speechErrors$ = this.speechErrorsSubject.asObservable(); + public speechHints$ = this.speechHintsSubject.asObservable(); + public detectedQuestions$ = this.detectedQuestionsSubject.asObservable(); + public isListening$ = this.isListeningSubject.asObservable(); + public permissionStatus$ = this.permissionStatusSubject.asObservable(); + public currentLanguage$ = this.languageSubject.asObservable(); + + constructor() { + this.setupLanguageSubscription(); + this.setupSpeechAnalysisDebouncing(); + } + + public initializeSpeechRecognition(): void { + if (this.isInitialized) { + return; + } + + // Check browser support + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognition) { + const error: SpeechError = { + type: 'not_supported', + message: 'Speech recognition not supported in this browser', + timestamp: new Date(), + canRetry: false + }; + this.speechErrorsSubject.next(error); + throw new Error('Speech recognition not supported'); + } + + this.recognition = new SpeechRecognition(); + this.configureRecognition(); + this.isInitialized = true; + } + + private configureRecognition(): void { + this.recognition.continuous = true; + this.recognition.interimResults = true; + this.recognition.lang = this.currentLanguage; + + this.recognition.addEventListener('result', this.handleSpeechResult.bind(this)); + this.recognition.addEventListener('error', this.handleSpeechError.bind(this)); + this.recognition.addEventListener('end', this.handleSpeechEnd.bind(this)); + this.recognition.addEventListener('start', () => { + this.isCurrentlyListening = true; + this.isListeningSubject.next(true); + console.log(`Speech recognition started with language: ${this.currentLanguage}`); + }); + } + + public startListening(): void { + if (!this.isInitialized) { + this.initializeSpeechRecognition(); + } + + if (!this.isCurrentlyListening) { + try { + this.recognition.start(); + } catch (error) { + this.handleSpeechError({ + type: 'error', + error: 'start_failed' + } as any); + } + } + } + + public stopListening(): void { + if (this.recognition && this.isCurrentlyListening) { + this.recognition.stop(); + this.isCurrentlyListening = false; + this.isListeningSubject.next(false); + } + } + + public handleSpeechResult(event: MockSpeechRecognitionEvent): void { + if (!event.results) return; + + const result = event.results[event.resultIndex]; + if (!result || !result.item(0)) return; + + const transcript = result.item(0).transcript; + const confidence = result.item(0).confidence || 0.9; + const isFinal = result.isFinal; + + // Emit speech result + const speechResult: SpeechResult = { + transcript, + confidence, + isFinal, + timestamp: new Date() + }; + this.speechResultsSubject.next(speechResult); + + // Only process final results for analysis + if (isFinal) { + this.analyzeSpeech(transcript, confidence); + this.detectSpeechHints(transcript, confidence); + } + } + + public handleSpeechError(event: any): void { + const errorType = event.error || 'unknown'; + let canRetry = true; + let message = this.languageConfigService.getLocalizedErrorMessage(errorType); + + switch (errorType) { + case 'network': + canRetry = true; + break; + case 'not-allowed': + canRetry = false; + this.permissionStatusSubject.next('denied'); + break; + case 'no-speech': + canRetry = true; + break; + case 'audio-capture': + canRetry = true; + break; + default: + message = `Speech recognition error: ${errorType}`; + } + + const error: SpeechError = { + type: errorType, + message, + timestamp: new Date(), + canRetry + }; + + this.speechErrorsSubject.next(error); + this.isCurrentlyListening = false; + this.isListeningSubject.next(false); + } + + public handleSpeechEnd(event: Event): void { + this.isCurrentlyListening = false; + this.isListeningSubject.next(false); + + // Auto-restart for continuous listening + setTimeout(() => { + if (!this.isCurrentlyListening) { + this.startListening(); + } + }, 100); + } + + private analyzeSpeech(transcript: string, confidence: number): void { + const normalizedText = transcript.toLowerCase().trim(); + const isQuestion = this.isQuestionPattern(normalizedText); + + const analysis: SpeechAnalysis = { + originalText: transcript, + isQuestion, + confidence, + detectedPatterns: this.getMatchedPatterns(normalizedText) + }; + + this.speechAnalysisSubject.next(analysis); + + // If it's a question, emit as detected question + if (isQuestion) { + const detectedQuestion: DetectedQuestion = { + id: DataSanitizer.generateUUID(), + timestamp: new Date(), + originalText: transcript, + normalizedText, + confidence, + responseTime: 0, // Will be updated when response is provided + wasHelpRequested: true, // Assume help is requested for detected questions + isQuestion: true + }; + + this.detectedQuestionsSubject.next(detectedQuestion); + } + } + + private detectSpeechHints(transcript: string, confidence: number): void { + const normalizedText = transcript.toLowerCase().trim(); + const hintPatterns = this.languageConfigService.getCurrentHintPatterns(); + + for (const hintPattern of hintPatterns) { + if (hintPattern.pattern.test(normalizedText)) { + const hint: SpeechHint = { + id: DataSanitizer.generateUUID(), + timestamp: new Date(), + text: transcript, + hintType: HintType.QUESTION_INTRO, // Default type, could be mapped better + confidence: confidence * 0.8, // Slightly lower confidence for hints + preparedAnswers: [], // Will be populated by question bank service + type: 'question_intro', // Alternative naming for compatibility + detectedText: transcript // Alternative naming for compatibility + }; + + this.speechHintsSubject.next(hint); + break; // Only emit one hint per transcript + } + } + } + + private isQuestionPattern(text: string): boolean { + const patterns = this.languageConfigService.getCurrentQuestionPatterns(); + return patterns.some(pattern => pattern.test(text)); + } + + private getMatchedPatterns(text: string): string[] { + const patterns: string[] = []; + const questionPatterns = this.languageConfigService.getCurrentQuestionPatterns(); + const hintPatterns = this.languageConfigService.getCurrentHintPatterns(); + + questionPatterns.forEach((pattern, index) => { + if (pattern.test(text)) { + patterns.push(`question_pattern_${index}`); + } + }); + + hintPatterns.forEach((hintPattern, index) => { + if (hintPattern.pattern.test(text)) { + patterns.push(`hint_pattern_${index}_${hintPattern.description}`); + } + }); + + return patterns; + } + + private setupSpeechAnalysisDebouncing(): void { + // Debounce speech analysis to avoid processing too frequently + this.speechResults$ + .pipe( + filter(result => result.isFinal), + debounceTime(500), // Wait 500ms after last result + map(result => result.transcript) + ) + .subscribe(transcript => { + // Additional processing can be added here + }); + } + + // Permission management + public async requestMicrophonePermission(): Promise { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + stream.getTracks().forEach(track => track.stop()); // Stop immediately, we just needed permission + this.permissionStatusSubject.next('granted'); + } catch (error) { + this.permissionStatusSubject.next('denied'); + throw error; + } + } + + public handlePermissionGranted(): void { + this.permissionStatusSubject.next('granted'); + } + + public canStartListening(): boolean { + return this.permissionStatusSubject.value === 'granted' && this.isInitialized; + } + + // Language management methods + public setLanguage(languageCode: string): void { + const wasListening = this.isCurrentlyListening; + + // Stop current recognition if running + if (wasListening) { + this.stopListening(); + } + + // Update language + this.currentLanguage = languageCode; + this.languageSubject.next(languageCode); + + // Update language config service + this.languageConfigService.setLanguage(languageCode); + + // Reinitialize recognition with new language + if (this.isInitialized) { + this.recognition.lang = languageCode; + console.log(`Speech recognition language changed to: ${languageCode}`); + } + + // Restart listening if it was active + if (wasListening) { + setTimeout(() => { + this.startListening(); + }, 100); + } + } + + public getCurrentLanguage(): string { + return this.currentLanguage; + } + + public getSupportedLanguages() { + return this.languageConfigService.getSupportedLanguages(); + } + + public autoDetectLanguage(): string { + const detectedLanguage = this.languageConfigService.autoDetectLanguage(); + this.setLanguage(detectedLanguage); + return detectedLanguage; + } + + private setupLanguageSubscription(): void { + // Subscribe to language changes from the config service + this.languageConfigService.currentLanguage$ + .pipe(takeUntil(this.destroy$)) + .subscribe(language => { + if (language !== this.currentLanguage) { + this.setLanguage(language); + } + }); + + // Initialize with current language + const currentLang = this.languageConfigService.getCurrentLanguage(); + this.currentLanguage = currentLang; + this.languageSubject.next(currentLang); + } + + // Cleanup + public destroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + this.stopListening(); + if (this.recognition) { + this.recognition = null; + } + this.isInitialized = false; + } +} \ No newline at end of file diff --git a/src/app/services/storage.service.ts b/src/app/services/storage.service.ts new file mode 100644 index 0000000..914cebf --- /dev/null +++ b/src/app/services/storage.service.ts @@ -0,0 +1,730 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, from, of, throwError } from 'rxjs'; +import { map, catchError, switchMap } from 'rxjs/operators'; +import Dexie, { Table } from 'dexie'; +import { CVProfile } from '../models/cv-profile.interface'; +import { QuestionBank } from '../models/question-bank.interface'; +import { InterviewSession } from '../models/interview-session.interface'; +import { N8nSyncData } from '../models/n8n-sync.interface'; + +export interface StorageStats { + totalSize: number; + usedSpace: number; + remainingSpace: number; + itemCounts: { + cvProfiles: number; + questionBanks: number; + sessions: number; + syncData: number; + }; + lastBackup?: Date; + lastCleanup?: Date; +} + +export interface BackupData { + version: string; + timestamp: Date; + cvProfiles: CVProfile[]; + questionBanks: QuestionBank[]; + sessions: InterviewSession[]; + syncData: N8nSyncData[]; + metadata: { + appVersion: string; + exportDate: string; + totalItems: number; + }; +} + +export interface StorageConfig { + maxCVProfiles: number; + maxQuestionBanks: number; + maxSessions: number; + maxSyncData: number; + autoCleanupDays: number; + compressionEnabled: boolean; +} + +class InterviewAssistantDatabase extends Dexie { + cvProfiles!: Table; + questionBanks!: Table; + sessions!: Table; + syncData!: Table; + metadata!: Table; + + constructor() { + super('InterviewAssistantMainDB'); + + this.version(1).stores({ + cvProfiles: 'id, fileName, uploadDate, lastModified', + questionBanks: 'id, cvProfileId, generatedDate, lastUsed, accuracy', + sessions: 'id, cvProfileId, questionBankId, startTime, endTime, status', + syncData: 'id, sessionId, lastSyncTime, syncStatus', + metadata: 'key, value, timestamp' + }); + + this.version(2).stores({ + cvProfiles: 'id, fileName, uploadDate, lastModified, fileSize', + questionBanks: 'id, cvProfileId, generatedDate, lastUsed, accuracy, version', + sessions: 'id, cvProfileId, questionBankId, startTime, endTime, status, duration', + syncData: 'id, sessionId, lastSyncTime, syncStatus, retryCount', + metadata: 'key, value, timestamp, category' + }).upgrade(async tx => { + // Migration logic for version 2 + await tx.table('cvProfiles').toCollection().modify((profile: any) => { + if (!profile.fileSize) { + profile.fileSize = 0; + } + }); + }); + } +} + +@Injectable({ + providedIn: 'root' +}) +export class StorageService { + private db = new InterviewAssistantDatabase(); + private storageStatsSubject = new BehaviorSubject(null); + private isInitializedSubject = new BehaviorSubject(false); + + private readonly defaultConfig: StorageConfig = { + maxCVProfiles: 50, + maxQuestionBanks: 100, + maxSessions: 200, + maxSyncData: 500, + autoCleanupDays: 30, + compressionEnabled: true + }; + + private config: StorageConfig = { ...this.defaultConfig }; + + public storageStats$ = this.storageStatsSubject.asObservable(); + public isInitialized$ = this.isInitializedSubject.asObservable(); + + constructor() { + this.initializeDatabase(); + } + + // Initialization + private async initializeDatabase(): Promise { + try { + await this.db.open(); + await this.loadConfiguration(); + await this.updateStorageStats(); + await this.performMaintenanceCheck(); + + this.isInitializedSubject.next(true); + console.log('Storage service initialized successfully'); + } catch (error) { + console.error('Failed to initialize storage service:', error); + this.isInitializedSubject.next(false); + } + } + + // Configuration Management + public async updateConfiguration(newConfig: Partial): Promise { + this.config = { ...this.config, ...newConfig }; + await this.storeMetadata('storage_config', this.config); + } + + public getConfiguration(): StorageConfig { + return { ...this.config }; + } + + private async loadConfiguration(): Promise { + try { + const stored = await this.getMetadata('storage_config'); + if (stored) { + this.config = { ...this.defaultConfig, ...stored }; + } + } catch (error) { + console.warn('Failed to load storage configuration, using defaults:', error); + } + } + + // CV Profile Storage + public storeCVProfile(profile: CVProfile): Observable { + return from(this.db.transaction('rw', this.db.cvProfiles, async () => { + // Check if we need to clean up old profiles + const count = await this.db.cvProfiles.count(); + if (count >= this.config.maxCVProfiles) { + await this.cleanupOldCVProfiles(); + } + + // Store the profile + await this.db.cvProfiles.put(profile); + await this.updateStorageStats(); + + return profile.id; + })).pipe( + catchError(error => { + console.error('Error storing CV profile:', error); + return throwError(error); + }) + ); + } + + public getCVProfile(id: string): Observable { + return from(this.db.cvProfiles.get(id)).pipe( + map(profile => profile || null), + catchError(error => { + console.error('Error getting CV profile:', error); + return of(null); + }) + ); + } + + public getAllCVProfiles(): Observable { + return from(this.db.cvProfiles.orderBy('uploadDate').reverse().toArray()).pipe( + catchError(error => { + console.error('Error getting all CV profiles:', error); + return of([]); + }) + ); + } + + public deleteCVProfile(id: string): Observable { + return from(this.db.transaction('rw', this.db.cvProfiles, this.db.questionBanks, this.db.sessions, async () => { + // Delete related question banks and sessions + await this.db.questionBanks.where('cvProfileId').equals(id).delete(); + await this.db.sessions.where('cvProfileId').equals(id).delete(); + + // Delete the profile + await this.db.cvProfiles.delete(id); + await this.updateStorageStats(); + })).pipe( + map(() => void 0), + catchError(error => { + console.error('Error deleting CV profile:', error); + return throwError(error); + }) + ); + } + + // Question Bank Storage + public storeQuestionBank(questionBank: QuestionBank): Observable { + return from(this.db.transaction('rw', this.db.questionBanks, async () => { + // Check if we need to clean up old question banks + const count = await this.db.questionBanks.count(); + if (count >= this.config.maxQuestionBanks) { + await this.cleanupOldQuestionBanks(); + } + + await this.db.questionBanks.put(questionBank); + await this.updateStorageStats(); + + return questionBank.id; + })).pipe( + catchError(error => { + console.error('Error storing question bank:', error); + return throwError(error); + }) + ); + } + + public getQuestionBank(id: string): Observable { + return from(this.db.questionBanks.get(id)).pipe( + map(bank => bank || null), + catchError(error => { + console.error('Error getting question bank:', error); + return of(null); + }) + ); + } + + public getQuestionBanksByProfile(cvProfileId: string): Observable { + return from(this.db.questionBanks.where('cvProfileId').equals(cvProfileId).toArray()).pipe( + catchError(error => { + console.error('Error getting question banks by profile:', error); + return of([]); + }) + ); + } + + public deleteQuestionBank(id: string): Observable { + return from(this.db.transaction('rw', this.db.questionBanks, this.db.sessions, async () => { + // Update sessions that reference this question bank + await this.db.sessions.where('questionBankId').equals(id).modify({ questionBankId: '' }); + + // Delete the question bank + await this.db.questionBanks.delete(id); + await this.updateStorageStats(); + })).pipe( + map(() => void 0), + catchError(error => { + console.error('Error deleting question bank:', error); + return throwError(error); + }) + ); + } + + // Session Storage + public storeSession(session: InterviewSession): Observable { + return from(this.db.transaction('rw', this.db.sessions, async () => { + // Check if we need to clean up old sessions + const count = await this.db.sessions.count(); + if (count >= this.config.maxSessions) { + await this.cleanupOldSessions(); + } + + await this.db.sessions.put(session); + await this.updateStorageStats(); + + return session.id; + })).pipe( + catchError(error => { + console.error('Error storing session:', error); + return throwError(error); + }) + ); + } + + public getSession(id: string): Observable { + return from(this.db.sessions.get(id)).pipe( + map(session => session || null), + catchError(error => { + console.error('Error getting session:', error); + return of(null); + }) + ); + } + + public getAllSessions(): Observable { + return from(this.db.sessions.orderBy('startTime').reverse().toArray()).pipe( + catchError(error => { + console.error('Error getting all sessions:', error); + return of([]); + }) + ); + } + + public getSessionsByProfile(cvProfileId: string): Observable { + return from(this.db.sessions.where('cvProfileId').equals(cvProfileId).toArray()).pipe( + catchError(error => { + console.error('Error getting sessions by profile:', error); + return of([]); + }) + ); + } + + public deleteSession(id: string): Observable { + return from(this.db.transaction('rw', this.db.sessions, this.db.syncData, async () => { + // Delete related sync data + await this.db.syncData.where('sessionId').equals(id).delete(); + + // Delete the session + await this.db.sessions.delete(id); + await this.updateStorageStats(); + })).pipe( + map(() => void 0), + catchError(error => { + console.error('Error deleting session:', error); + return throwError(error); + }) + ); + } + + // Sync Data Storage + public storeSyncData(syncData: N8nSyncData): Observable { + return from(this.db.transaction('rw', this.db.syncData, async () => { + // Check if we need to clean up old sync data + const count = await this.db.syncData.count(); + if (count >= this.config.maxSyncData) { + await this.cleanupOldSyncData(); + } + + await this.db.syncData.put(syncData); + await this.updateStorageStats(); + + return syncData.id; + })).pipe( + catchError(error => { + console.error('Error storing sync data:', error); + return throwError(error); + }) + ); + } + + public getSyncData(id: string): Observable { + return from(this.db.syncData.get(id)).pipe( + map(data => data || null), + catchError(error => { + console.error('Error getting sync data:', error); + return of(null); + }) + ); + } + + public getPendingSyncData(): Observable { + return from(this.db.syncData.where('syncStatus').equals('pending').toArray()).pipe( + catchError(error => { + console.error('Error getting pending sync data:', error); + return of([]); + }) + ); + } + + public deleteSyncData(id: string): Observable { + return from(this.db.syncData.delete(id)).pipe( + switchMap(() => from(this.updateStorageStats())), + map(() => void 0), + catchError(error => { + console.error('Error deleting sync data:', error); + return throwError(error); + }) + ); + } + + // Metadata Management + public storeMetadata(key: string, value: any, category = 'general'): Observable { + const metadata = { + key, + value, + timestamp: new Date(), + category + }; + + return from(this.db.metadata.put(metadata)).pipe( + map(() => void 0), + catchError(error => { + console.error('Error storing metadata:', error); + return throwError(error); + }) + ); + } + + public getMetadata(key: string): Observable { + return from(this.db.metadata.get(key)).pipe( + map(metadata => metadata ? metadata.value : null), + catchError(error => { + console.error('Error getting metadata:', error); + return of(null); + }) + ); + } + + // Storage Statistics + private async updateStorageStats(): Promise { + try { + const stats: StorageStats = { + totalSize: await this.calculateTotalSize(), + usedSpace: await this.calculateUsedSpace(), + remainingSpace: 0, // Will be calculated + itemCounts: { + cvProfiles: await this.db.cvProfiles.count(), + questionBanks: await this.db.questionBanks.count(), + sessions: await this.db.sessions.count(), + syncData: await this.db.syncData.count() + }, + lastBackup: await this.getMetadata('last_backup_date').toPromise(), + lastCleanup: await this.getMetadata('last_cleanup_date').toPromise() + }; + + stats.remainingSpace = stats.totalSize - stats.usedSpace; + this.storageStatsSubject.next(stats); + } catch (error) { + console.error('Error updating storage stats:', error); + } + } + + private async calculateTotalSize(): Promise { + // Estimate based on IndexedDB quota (simplified) + if ('storage' in navigator && 'estimate' in navigator.storage) { + try { + const estimate = await navigator.storage.estimate(); + return estimate.quota || 50 * 1024 * 1024; // Default to 50MB + } catch (error) { + console.warn('Could not get storage estimate:', error); + } + } + return 50 * 1024 * 1024; // 50MB default + } + + private async calculateUsedSpace(): Promise { + // Simplified calculation - in reality, you'd need to traverse all data + const counts = { + cvProfiles: await this.db.cvProfiles.count(), + questionBanks: await this.db.questionBanks.count(), + sessions: await this.db.sessions.count(), + syncData: await this.db.syncData.count() + }; + + // Rough estimates in bytes + const estimatedSize = + counts.cvProfiles * 100 * 1024 + // ~100KB per CV profile + counts.questionBanks * 50 * 1024 + // ~50KB per question bank + counts.sessions * 20 * 1024 + // ~20KB per session + counts.syncData * 10 * 1024; // ~10KB per sync data + + return estimatedSize; + } + + // Cleanup Operations + public performCleanup(): Observable { + return from(this.db.transaction('rw', + this.db.cvProfiles, + this.db.questionBanks, + this.db.sessions, + this.db.syncData, + async () => { + await this.cleanupOldCVProfiles(); + await this.cleanupOldQuestionBanks(); + await this.cleanupOldSessions(); + await this.cleanupOldSyncData(); + await this.storeMetadata('last_cleanup_date', new Date()); + await this.updateStorageStats(); + } + )).pipe( + map(() => void 0), + catchError(error => { + console.error('Error performing cleanup:', error); + return throwError(error); + }) + ); + } + + private async cleanupOldCVProfiles(): Promise { + const count = await this.db.cvProfiles.count(); + if (count > this.config.maxCVProfiles) { + const excess = count - this.config.maxCVProfiles; + const oldProfiles = await this.db.cvProfiles + .orderBy('uploadDate') + .limit(excess) + .toArray(); + + for (const profile of oldProfiles) { + await this.deleteCVProfile(profile.id).toPromise(); + } + } + } + + private async cleanupOldQuestionBanks(): Promise { + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - this.config.autoCleanupDays); + + await this.db.questionBanks + .where('lastUsed') + .below(cutoffDate) + .delete(); + } + + private async cleanupOldSessions(): Promise { + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - this.config.autoCleanupDays); + + await this.db.sessions + .where('startTime') + .below(cutoffDate) + .delete(); + } + + private async cleanupOldSyncData(): Promise { + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - this.config.autoCleanupDays); + + await this.db.syncData + .where('lastSyncTime') + .below(cutoffDate) + .delete(); + } + + // Backup and Restore + public createBackup(): Observable { + return from(this.db.transaction('r', + this.db.cvProfiles, + this.db.questionBanks, + this.db.sessions, + this.db.syncData, + async () => { + const backup: BackupData = { + version: '1.0', + timestamp: new Date(), + cvProfiles: await this.db.cvProfiles.toArray(), + questionBanks: await this.db.questionBanks.toArray(), + sessions: await this.db.sessions.toArray(), + syncData: await this.db.syncData.toArray(), + metadata: { + appVersion: '1.0.0', // Should come from app config + exportDate: new Date().toISOString(), + totalItems: 0 + } + }; + + backup.metadata.totalItems = + backup.cvProfiles.length + + backup.questionBanks.length + + backup.sessions.length + + backup.syncData.length; + + await this.storeMetadata('last_backup_date', new Date()); + + return backup; + } + )).pipe( + catchError(error => { + console.error('Error creating backup:', error); + return throwError(error); + }) + ); + } + + public restoreFromBackup(backup: BackupData): Observable { + return from(this.db.transaction('rw', + this.db.cvProfiles, + this.db.questionBanks, + this.db.sessions, + this.db.syncData, + async () => { + // Clear existing data + await this.db.cvProfiles.clear(); + await this.db.questionBanks.clear(); + await this.db.sessions.clear(); + await this.db.syncData.clear(); + + // Restore data + await this.db.cvProfiles.bulkAdd(backup.cvProfiles); + await this.db.questionBanks.bulkAdd(backup.questionBanks); + await this.db.sessions.bulkAdd(backup.sessions); + await this.db.syncData.bulkAdd(backup.syncData); + + await this.updateStorageStats(); + } + )).pipe( + map(() => void 0), + catchError(error => { + console.error('Error restoring from backup:', error); + return throwError(error); + }) + ); + } + + // Maintenance + private async performMaintenanceCheck(): Promise { + const lastCleanup = await this.getMetadata('last_cleanup_date').toPromise(); + const daysSinceCleanup = lastCleanup ? + (Date.now() - new Date(lastCleanup).getTime()) / (1000 * 60 * 60 * 24) : + Infinity; + + if (daysSinceCleanup > 7) { // Run cleanup weekly + this.performCleanup().subscribe({ + next: () => console.log('Automatic cleanup completed'), + error: (error) => console.error('Automatic cleanup failed:', error) + }); + } + } + + // Utility Methods + public exportToJson(): Observable { + return this.createBackup().pipe( + map(backup => JSON.stringify(backup, null, 2)) + ); + } + + public importFromJson(jsonData: string): Observable { + return new Observable(observer => { + try { + const backup: BackupData = JSON.parse(jsonData); + this.restoreFromBackup(backup).subscribe({ + next: () => { + observer.next(); + observer.complete(); + }, + error: (error) => observer.error(error) + }); + } catch (error) { + observer.error(new Error('Invalid backup format')); + } + }); + } + + public clearAllData(): Observable { + return from(this.db.transaction('rw', [ + this.db.cvProfiles, + this.db.questionBanks, + this.db.sessions, + this.db.syncData, + this.db.metadata + ], async () => { + await this.db.cvProfiles.clear(); + await this.db.questionBanks.clear(); + await this.db.sessions.clear(); + await this.db.syncData.clear(); + await this.db.metadata.clear(); + await this.updateStorageStats(); + })).pipe( + map(() => void 0), + catchError(error => { + console.error('Error clearing all data:', error); + return throwError(error); + }) + ); + } + + // Health Check + public checkStorageHealth(): Observable { + return from(this.db.transaction('r', + this.db.cvProfiles, + this.db.questionBanks, + this.db.sessions, + this.db.syncData, + async () => { + const health = { + isHealthy: true, + issues: [] as string[], + counts: { + cvProfiles: await this.db.cvProfiles.count(), + questionBanks: await this.db.questionBanks.count(), + sessions: await this.db.sessions.count(), + syncData: await this.db.syncData.count() + }, + orphanedData: { + questionBanks: 0, + sessions: 0 + } + }; + + // Check for orphaned question banks + const allProfiles = await this.db.cvProfiles.toArray(); + const profileIds = new Set(allProfiles.map(p => p.id)); + const orphanedBanks = await this.db.questionBanks + .filter(bank => !profileIds.has(bank.cvProfileId)) + .count(); + + health.orphanedData.questionBanks = orphanedBanks; + + if (orphanedBanks > 0) { + health.issues.push(`${orphanedBanks} orphaned question banks found`); + health.isHealthy = false; + } + + // Check for orphaned sessions + const orphanedSessions = await this.db.sessions + .filter(session => !profileIds.has(session.cvProfileId)) + .count(); + + health.orphanedData.sessions = orphanedSessions; + + if (orphanedSessions > 0) { + health.issues.push(`${orphanedSessions} orphaned sessions found`); + health.isHealthy = false; + } + + return health; + } + )).pipe( + catchError(error => { + console.error('Error checking storage health:', error); + return of({ + isHealthy: false, + issues: ['Failed to check storage health'], + error: error.message + }); + }) + ); + } + + // Cleanup + public destroy(): void { + this.db.close(); + } +} \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts new file mode 100644 index 0000000..3698e7d --- /dev/null +++ b/src/environments/environment.prod.ts @@ -0,0 +1,27 @@ +export const environment = { + production: true, + n8nWebhookUrl: 'https://n8n.gm-tech.org/webhook/cv-analysis', + authelia: { + baseUrl: 'https://auth.gm-tech.org', + loginPath: '/api/firstfactor', + verifyPath: '/api/verify', + logoutPath: '/api/logout' + }, + apiEndpoints: { + cvAnalysis: 'https://n8n.gm-tech.org/webhook/cv-analysis', + questionGeneration: 'https://n8n.gm-tech.org/webhook/cv-analysis', + interviewSession: 'https://n8n.gm-tech.org/webhook/interview-session' + }, + features: { + speechRecognition: true, + multiLanguageSupport: true, + n8nIntegration: true, + autheliaAuth: true, + offlineMode: false + }, + app: { + name: 'Interview Assistant', + version: '1.0.0', + debug: false + } +}; \ No newline at end of file diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000..bbb0190 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,27 @@ +export const environment = { + production: false, + n8nWebhookUrl: 'https://n8n.gm-tech.org/webhook/cv-analysis', + authelia: { + baseUrl: 'https://auth.gm-tech.org', + loginPath: '/api/firstfactor', + verifyPath: '/api/verify', + logoutPath: '/api/logout' + }, + apiEndpoints: { + cvAnalysis: 'https://n8n.gm-tech.org/webhook/cv-analysis', + questionGeneration: 'https://n8n.gm-tech.org/webhook/cv-analysis', + interviewSession: 'https://n8n.gm-tech.org/webhook/interview-session' + }, + features: { + speechRecognition: true, + multiLanguageSupport: true, + n8nIntegration: true, + autheliaAuth: true, + offlineMode: false + }, + app: { + name: 'Interview Assistant', + version: '1.0.0', + debug: true + } +}; \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..9f97af5 --- /dev/null +++ b/src/index.html @@ -0,0 +1,13 @@ + + + + + InterviewAssistant + + + + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5df75f9 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/src/styles.scss b/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..264f459 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0d12629 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,36 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["dom-speech-recognition"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "typeCheckHostBindings": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..04df34c --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.ts" + ] +}