From 684863ab83143dc9236a0d20756c2d77c1d34cdd Mon Sep 17 00:00:00 2001 From: Youssouf EL Azizi Date: Tue, 25 Nov 2025 13:17:10 +0100 Subject: [PATCH 1/8] wip: add schema validation and conditions foundation --- src/lib/conditions.ts | 249 +++++++++++++++++++++++ src/lib/validators/survey-schema.test.ts | 16 +- src/lib/validators/survey-schema.ts | 50 +++-- src/lib/validators/survey-validator.ts | 31 ++- 4 files changed, 320 insertions(+), 26 deletions(-) create mode 100644 src/lib/conditions.ts diff --git a/src/lib/conditions.ts b/src/lib/conditions.ts new file mode 100644 index 00000000..b939a2b6 --- /dev/null +++ b/src/lib/conditions.ts @@ -0,0 +1,249 @@ +import type { ShowIfCondition } from "./validators/survey-schema"; + +export type AnswerValue = number | number[] | null | string; +export type Answers = Record; +export type QuestionList = Array<{ showIf?: ShowIfCondition }>; +export type SectionList = Array<{ showIf?: ShowIfCondition }>; + +/** + * Evaluates a showIf condition against current answers + * @param condition - The condition to evaluate + * @param answers - Current survey answers + * @returns true if condition is met (show question/section), false otherwise + */ +export function evaluateCondition( + condition: ShowIfCondition | undefined, + answers: Answers +): boolean { + // No condition means always show + if (!condition) return true; + + const { + question, + equals, + notEquals, + in: inArray, + notIn: notInArray + } = condition; + + // Validate question ID format + if (!question || !/^[a-z0-9-]+-q-\d+$/.test(question)) { + console.warn( + `[Condition] Invalid question ID format: "${question}". Showing question by default.` + ); + return true; + } + + // Get answer value + const answerValue = answers[question]; + + // Handle unanswered questions - hide if condition depends on it + if (answerValue === null || answerValue === undefined) { + return false; + } + + // Evaluate operators + if (equals !== undefined) { + return evaluateEquals(answerValue, equals); + } + + if (notEquals !== undefined) { + return evaluateNotEquals(answerValue, notEquals); + } + + if (inArray !== undefined) { + return evaluateIn(answerValue, inArray); + } + + if (notInArray !== undefined) { + return evaluateNotIn(answerValue, notInArray); + } + + // No valid operator found + console.warn( + `[Condition] No valid operator found in condition for question "${question}". Showing by default.` + ); + return true; +} + +/** + * Checks if answer equals expected value + */ +function evaluateEquals(answer: AnswerValue, expected: number): boolean { + if (typeof answer !== "number") { + console.warn( + `[Condition] equals operator used on non-number answer. Expected number, got ${typeof answer}` + ); + return false; + } + return answer === expected; +} + +/** + * Checks if answer does not equal expected value + */ +function evaluateNotEquals(answer: AnswerValue, expected: number): boolean { + if (typeof answer !== "number") { + console.warn( + `[Condition] notEquals operator used on non-number answer. Expected number, got ${typeof answer}` + ); + return false; + } + return answer !== expected; +} + +/** + * Checks if answer is one of expected values + */ +function evaluateIn(answer: AnswerValue, expected: number[]): boolean { + if (typeof answer !== "number") { + console.warn( + `[Condition] in operator used on non-number answer. Expected number, got ${typeof answer}` + ); + return false; + } + return expected.includes(answer); +} + +/** + * Checks if answer is not one of expected values + */ +function evaluateNotIn(answer: AnswerValue, expected: number[]): boolean { + if (typeof answer !== "number") { + console.warn( + `[Condition] notIn operator used on non-number answer. Expected number, got ${typeof answer}` + ); + return false; + } + return !expected.includes(answer); +} + +/** + * Filters sections based on their showIf conditions and whether they have visible questions + * @param sections - All survey sections + * @param answers - Current survey answers + * @returns Array of section indices that should be visible + */ +export function getVisibleSectionIndices( + sections: SectionList, + answers: Answers +): number[] { + return sections + .map((section, index) => { + const sectionVisible = evaluateCondition(section.showIf, answers); + if (!sectionVisible) return { index, visible: false }; + + // Check if section has at least one visible question + const questions = (section as any).questions as QuestionList | undefined; + if (!questions || questions.length === 0) { + return { index, visible: false }; + } + + const hasVisibleQuestion = questions.some((q) => + evaluateCondition(q.showIf, answers) + ); + + return { + index, + visible: hasVisibleQuestion + }; + }) + .filter((item) => item.visible) + .map((item) => item.index); +} + +/** + * Finds the next visible question after the given index + * @param questions - All questions in a section + * @param fromIdx - Current question index + * @param answers - Current survey answers + * @returns Index of next visible question, or undefined if none + */ +export function getNextVisibleQuestionIndex( + questions: QuestionList, + fromIdx: number, + answers: Answers +): number | undefined { + for (let i = fromIdx + 1; i < questions.length; i++) { + if (evaluateCondition(questions[i].showIf, answers)) { + return i; + } + } + return undefined; +} + +/** + * Finds the previous visible question before the given index + * @param questions - All questions in a section + * @param fromIdx - Current question index + * @param answers - Current survey answers + * @returns Index of previous visible question, or undefined if none + */ +export function getPrevVisibleQuestionIndex( + questions: QuestionList, + fromIdx: number, + answers: Answers +): number | undefined { + for (let i = fromIdx - 1; i >= 0; i--) { + if (evaluateCondition(questions[i].showIf, answers)) { + return i; + } + } + return undefined; +} + +/** + * Finds the first visible question in a section + * @param questions - All questions in a section + * @param answers - Current survey answers + * @returns Index of first visible question, or undefined if none + */ +export function getFirstVisibleQuestionIndex( + questions: QuestionList, + answers: Answers +): number | undefined { + return getNextVisibleQuestionIndex(questions, -1, answers); +} + +/** + * Finds the last visible question in a section + * @param questions - All questions in a section + * @param answers - Current survey answers + * @returns Index of last visible question, or undefined if none + */ +export function getLastVisibleQuestionIndex( + questions: QuestionList, + answers: Answers +): number | undefined { + return getPrevVisibleQuestionIndex(questions, questions.length, answers); +} + +/** + * Checks if there's a visible question after the given index + * @param questions - All questions in a section + * @param fromIdx - Current question index + * @param answers - Current survey answers + * @returns true if there's a next visible question + */ +export function hasNextVisibleQuestion( + questions: QuestionList, + fromIdx: number, + answers: Answers +): boolean { + return getNextVisibleQuestionIndex(questions, fromIdx, answers) !== undefined; +} + +/** + * Checks if there's a visible question before the given index + * @param questions - All questions in a section + * @param fromIdx - Current question index + * @param answers - Current survey answers + * @returns true if there's a previous visible question + */ +export function hasPrevVisibleQuestion( + questions: QuestionList, + fromIdx: number, + answers: Answers +): boolean { + return getPrevVisibleQuestionIndex(questions, fromIdx, answers) !== undefined; +} diff --git a/src/lib/validators/survey-schema.test.ts b/src/lib/validators/survey-schema.test.ts index 9666774c..a4407469 100644 --- a/src/lib/validators/survey-schema.test.ts +++ b/src/lib/validators/survey-schema.test.ts @@ -155,14 +155,14 @@ describe("SurveyQuestionSchema", () => { expect(result.success).toBe(true); }); - test("warns about too many question marks", () => { - const invalidQuestion = { + test("allows multiple question marks (warning at validator level)", () => { + const question = { label: "Test?? Really??", choices: ["Yes", "No"] }; - const result = SurveyQuestionSchema.safeParse(invalidQuestion); - expect(result.success).toBe(false); + const result = SurveyQuestionSchema.safeParse(question); + expect(result.success).toBe(true); }); }); @@ -685,18 +685,14 @@ describe("Edge case tests", () => { }); describe("Question mark placement", () => { - test("rejects question mark not at the end", () => { + test("allows question mark not at the end (warning at validator level)", () => { const question = { label: "What? is your age", choices: ["18-24", "25-34", "35+"] }; const result = SurveyQuestionSchema.safeParse(question); - expect(result.success).toBe(false); - if (!result.success) { - const issues = result.error.issues; - expect(issues.some((i) => i.message.includes("at the end"))).toBe(true); - } + expect(result.success).toBe(true); }); test("accepts question mark at the end", () => { diff --git a/src/lib/validators/survey-schema.ts b/src/lib/validators/survey-schema.ts index 43f8b656..d5b2f356 100644 --- a/src/lib/validators/survey-schema.ts +++ b/src/lib/validators/survey-schema.ts @@ -8,6 +8,36 @@ const MIN_TITLE_LENGTH = VALIDATION_THRESHOLDS.MIN_TITLE_LENGTH; const MAX_LABEL_LENGTH = VALIDATION_THRESHOLDS.MAX_LABEL_LENGTH; const MAX_CHOICE_LENGTH = VALIDATION_THRESHOLDS.MAX_CHOICE_LENGTH; +// Schema for conditional visibility (showIf) +export const ShowIfConditionSchema = z + .object({ + question: z + .string() + .regex( + /^[a-z0-9-]+-q-\d+$/, + "Must be valid question ID format (e.g., 'profile-q-0')" + ), + equals: z.number().int().nonnegative().optional(), + notEquals: z.number().int().nonnegative().optional(), + in: z.array(z.number().int().nonnegative()).optional(), + notIn: z.array(z.number().int().nonnegative()).optional() + }) + .refine( + (data) => { + const operators = [ + data.equals, + data.notEquals, + data.in, + data.notIn + ].filter((op) => op !== undefined); + return operators.length === 1; + }, + { + message: + "Exactly one operator (equals, notEquals, in, notIn) must be specified" + } + ); + export const SurveyQuestionSchema = z.object({ label: z .string() @@ -19,20 +49,7 @@ export const SurveyQuestionSchema = z.object({ ) .refine((val) => val.trim().length > 0, { message: "Question label cannot be empty or whitespace only" - }) - .refine( - (val) => { - // Check for valid question mark usage: at most one, must be at the end - const questionMarks = (val.match(/\?/g) || []).length; - if (questionMarks === 0) return true; - if (questionMarks > 1) return false; - return val.trim().endsWith("?"); - }, - { - message: - "Question label should contain at most one question mark at the end" - } - ), + }), required: z.boolean().optional().default(true), @@ -63,8 +80,10 @@ export const SurveyQuestionSchema = z.object({ { message: "Duplicate choices detected (case-insensitive comparison)" } - ) + ), // Note: Multiple "Other" variations are handled as warnings in cross-file validation + + showIf: ShowIfConditionSchema.optional() }); export const SurveyFileSchema = z @@ -128,6 +147,7 @@ export const SurveyFileSchema = z * TypeScript types inferred from Zod schemas * These replace the types in custom-yaml.d.ts */ +export type ShowIfCondition = z.infer; export type SurveyQuestion = z.infer; export type SurveyQuestionsYamlFile = z.infer; diff --git a/src/lib/validators/survey-validator.ts b/src/lib/validators/survey-validator.ts index 8028c580..418aef46 100644 --- a/src/lib/validators/survey-validator.ts +++ b/src/lib/validators/survey-validator.ts @@ -95,7 +95,36 @@ export function validateAllSurveyFiles(surveyDir: string): ValidationReport { } }); - const allWarnings = [...filenameWarnings, ...otherWarnings]; + // Check for question mark style issues (warning only) + const questionMarkWarnings: ValidationError[] = []; + validatedData.questions.forEach((question, index) => { + const questionMarks = (question.label.match(/\?/g) || []).length; + + if (questionMarks > 1) { + questionMarkWarnings.push({ + severity: ValidationSeverity.WARNING, + message: `Question ${index + 1} has multiple question marks`, + path: `questions[${index}].label`, + value: question.label + }); + } else if ( + questionMarks === 1 && + !question.label.trim().endsWith("?") + ) { + questionMarkWarnings.push({ + severity: ValidationSeverity.WARNING, + message: `Question ${index + 1} has question mark not at end`, + path: `questions[${index}].label`, + value: question.label + }); + } + }); + + const allWarnings = [ + ...filenameWarnings, + ...otherWarnings, + ...questionMarkWarnings + ]; if (allWarnings.length > 0) { result.errors = allWarnings; // Don't mark as invalid for warnings From 5bd81a1b4f153f2157b67dfc8d8d74b9d794fe11 Mon Sep 17 00:00:00 2001 From: Youssouf EL Azizi Date: Tue, 25 Nov 2025 13:21:15 +0100 Subject: [PATCH 2/8] feat: implement conditional survey logic with showIf conditions - Add conditional visibility system for questions/sections - Refactor survey machine to handle visible question navigation - Extract SurveyActions component from survey-form - Refactor Steps to use machine context and visible sections - Add comprehensive tests for conditional logic - Update type definitions and disable inspector in test mode - Fix: restore auto-empty-array for unanswered multiple choice --- RFC-conditional-survey-logic.md | 340 +++++++++++++++++ custom-yaml.d.ts | 19 +- src/components/survey/question.tsx | 1 + src/components/survey/steps.tsx | 49 ++- src/components/survey/survey-app.tsx | 1 + src/components/survey/survey-context.tsx | 6 +- src/components/survey/survey-controls.tsx | 83 +++++ src/components/survey/survey-form.test.tsx | 1 + src/components/survey/survey-form.tsx | 82 +---- src/components/survey/survey-machine.test.ts | 364 ++++++++++++++++++- src/components/survey/survey-machine.ts | 151 ++++++-- src/lib/validators/survey-schema.ts | 22 +- survey/1-profile.yml | 3 + survey/2-learning-and-education.yml | 2 +- 14 files changed, 980 insertions(+), 144 deletions(-) create mode 100644 RFC-conditional-survey-logic.md diff --git a/RFC-conditional-survey-logic.md b/RFC-conditional-survey-logic.md new file mode 100644 index 00000000..3cf9eeb6 --- /dev/null +++ b/RFC-conditional-survey-logic.md @@ -0,0 +1,340 @@ +# RFC: Conditional Logic for Survey Questions + +## Problem Statement + +Our survey shows all questions to all respondents, leading to: + +- Longer survey completion times +- Lower response rates +- Irrelevant questions for many users (e.g., "plans to return to Morocco" shown to everyone, even those not working abroad) + +**Goal**: Add conditional logic to show/hide questions and sections based on previous answers. + +## Proposed YAML Schema + +### Current Format (No Changes for Existing Surveys) + +```yaml +title: Profile +label: profile +position: 1 +questions: + - label: Where are you currently working? + required: true + choices: + - Currently working in Morocco + - Currently working outside Morocco + - Not currently working +``` + +### New Format: Simple Conditions (Recommended) + +Add optional `showIf` field to questions: + +```yaml +questions: + - label: Where are you currently working? + required: true + choices: + - Currently working in Morocco + - Currently working outside Morocco + - Not currently working + - Student + + # Only show if user selected "Currently working outside Morocco" (index 1) + - label: Do you have any plans to come back to Morocco? + required: true + showIf: + question: profile-q-0 # References question by ID + equals: 1 # Choice index + choices: + - Yes, within 12 months + - Yes, within 24 months + - No plans to return +``` + +**Simple Condition Operators:** + +```yaml +# Single choice - exact match +showIf: + question: profile-q-2 + equals: 1 + +# Single choice - not equal +showIf: + question: work-q-0 + notEquals: 3 + +# Single choice - one of multiple values +showIf: + question: work-q-0 + in: [0, 1, 2] # Employed full-time, part-time, or freelancer + +# Multiple choice - user selected specific option +showIf: + question: tech-q-5 + includes: 3 # User checked choice index 3 + +# Check if question was answered (not skipped) +showIf: + question: work-q-2 + answered: true +``` + +### Advanced Format: Complex Conditions (When Needed) + +For complex scenarios requiring AND/OR logic: + +```yaml +questions: + - label: Are you satisfied with your work-life balance? + required: true + conditions: + and: # All must be true + - question: work-q-0 + operator: equals + value: 0 # Full-time employed + - question: profile-q-2 + operator: equals + value: 0 # Working in Morocco + choices: + - Very satisfied + - Satisfied + - Dissatisfied + + - label: What are your freelancing challenges? + required: true + conditions: + or: # At least one must be true + - question: work-q-0 + operator: equals + value: 2 # Freelancer + - question: work-q-0 + operator: equals + value: 1 # Part-time + choices: + - Finding clients + - Pricing services +``` + +**Advanced Operators:** + +```yaml +# All operators from simple format, plus: +operator: notIn +operator: includesAny # For multiple choice: has any of these +operator: includesAll # For multiple choice: has all of these +operator: notIncludes +operator: notAnswered +``` + +### Section-Level Conditions + +Hide entire sections based on profile answers: + +```yaml +title: Work Experience +label: work +position: 3 + +# Only show this section if user is not a student +showIf: + question: profile-q-3 # Occupation + notEquals: 3 # Student choice index + +questions: + - label: What is your job title? + # ... rest of questions +``` + +Or with advanced conditions: + +```yaml +title: Advanced Career Topics +label: career +position: 5 + +conditions: + and: + - question: profile-q-5 # Years of experience + operator: in + value: [3, 4, 5] # 3+ years + - question: work-q-0 + operator: in + value: [0, 1, 2] # Currently employed + +questions: + # ... questions only for experienced employed developers +``` + +## Cross-Section References + +Questions can reference answers from any previous section: + +```yaml +# In work.yml (section 3) +questions: + - label: Would you consider remote work abroad? + showIf: + question: profile-q-2 # From profile.yml (section 1) + equals: 0 # Currently in Morocco + choices: + - Yes + - No +``` + +## Question ID Format + +Question IDs follow the pattern: `{section-label}-q-{index}` + +Examples: + +- `profile-q-0` - First question in profile section +- `profile-q-1` - Second question in profile section +- `work-q-0` - First question in work section + +**Important**: Index is zero-based and represents the question's position in the YAML file. + +## Answer Data Format (No Changes) + +Conditional logic doesn't change how answers are stored: + +```javascript +{ + "profile-q-0": 1, // Single choice: index 1 + "tech-q-2": [0, 2, 4], // Multiple choice: indices array + "work-q-5": null, // Skipped question + "profile-q-8": 5, // "Other" selected + "profile-q-8-others": "Text input" // Other text +} +``` + +Hidden questions are simply not answered (null or not present). + +## Operator Reference Table + +| Operator | Question Type | Description | Example | +| ------------- | --------------- | ----------------------------------- | --------------------- | +| `equals` | Single choice | Answer equals specific value | `equals: 1` | +| `notEquals` | Single choice | Answer not equal to value | `notEquals: 2` | +| `in` | Single choice | Answer is one of these values | `in: [0, 1, 2]` | +| `notIn` | Single choice | Answer is not one of these values | `notIn: [3, 4]` | +| `includes` | Multiple choice | Answer array includes value | `includes: 3` | +| `notIncludes` | Multiple choice | Answer array doesn't include value | `notIncludes: 4` | +| `includesAny` | Multiple choice | Answer has any of these values | `includesAny: [0, 1]` | +| `includesAll` | Multiple choice | Answer has all of these values | `includesAll: [1, 3]` | +| `answered` | Any | Question was answered (not skipped) | `answered: true` | +| `notAnswered` | Any | Question was skipped | `notAnswered: true` | + +## Use Cases + +### Use Case 1: Work Status Follow-ups + +```yaml +- label: Current employment status? + choices: + - Employed full-time + - Employed part-time + - Freelancer + - Student + - Unemployed + +- label: Your job title? + showIf: + question: work-q-0 + in: [0, 1, 2] # Any employed status + choices: + - Frontend Developer + - Backend Developer + +- label: Are you looking for work? + showIf: + question: work-q-0 + in: [3, 4] # Student or unemployed + choices: + - Yes, actively + - No +``` + +### Use Case 2: Location-Based Questions + +```yaml +- label: Where do you work? + choices: + - Morocco + - Outside Morocco + +- label: Which Moroccan city? + showIf: + question: profile-q-5 + equals: 0 + choices: + - Casablanca + - Rabat + +- label: Do you plan to return to Morocco? + showIf: + question: profile-q-5 + equals: 1 + choices: + - Yes + - No +``` + +### Use Case 3: Experience-Based Sections + +```yaml +title: Senior Developer Topics +label: senior +position: 7 + +showIf: + question: profile-q-4 # Years of experience + in: [4, 5] # 5+ years + +questions: + - label: Do you mentor junior developers? + choices: + - Yes, regularly + - Occasionally + - No +``` + +## Backward Compatibility + +- ✅ Existing surveys without conditions work unchanged +- ✅ Questions without `showIf` or `conditions` are always visible +- ✅ Sections without conditions are always visible +- ✅ Answer data format remains the same + +## Implementation Notes + +1. **Hybrid approach**: Support both simple `showIf` and advanced `conditions` formats +2. **Auto-detection**: Parser determines format based on field structure +3. **Validation**: Question IDs in conditions should be validated against existing questions +4. **Order dependency**: Conditions can only reference previous questions (to avoid circular dependencies) +5. **Progressive enhancement**: Start with simple cases, add complexity as needed + +## Questions for Discussion + +1. Should we allow forward references (referencing questions that come later)? +2. Should we add a "preview mode" to test conditions without submitting? +3. Should section-level conditions be required, or is question-level enough initially? +4. Do we need a visual indicator in the survey that questions were skipped due to conditions? + +## Next Steps + +1. Gather feedback on YAML format proposal +2. Decide on operator set (minimal vs comprehensive) +3. Create TypeScript types for conditional logic +4. Implement condition evaluation engine +5. Update survey components to support filtering +6. Write tests and documentation +7. Add sample conditions to one section as a pilot + +--- + +**Status**: RFC / Seeking Feedback +**Created**: 2025-01-23 +**Author**: GeeksBlaBla Team diff --git a/custom-yaml.d.ts b/custom-yaml.d.ts index 1f3a6f81..7f5a1cf9 100644 --- a/custom-yaml.d.ts +++ b/custom-yaml.d.ts @@ -1,23 +1,10 @@ /** - * This file is used to define the types for the results.json and questions.json files - * Mainly to prevent code editor from loading typing from json files which is can be very heavy + * Module declaration for YAML imports + * Types are now imported from @/lib/validators/survey-schema (Zod-inferred) */ -type SurveyQuestion = { - label: string; - choices: string[]; - multiple: boolean | null; - required: boolean | null; -}; - -type SurveyQuestionsYamlFile = { - title: string; - label: string; - position: number; - questions: SurveyQuestion[]; -}; - declare module "@/survey/*.yml" { + import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; const value: SurveyQuestionsYamlFile; export default value; } diff --git a/src/components/survey/question.tsx b/src/components/survey/question.tsx index f71687cc..b09de57e 100644 --- a/src/components/survey/question.tsx +++ b/src/components/survey/question.tsx @@ -6,6 +6,7 @@ import { type ChangeEvent } from "react"; import { Choice } from "./choice"; +import type { SurveyQuestion } from "@/lib/validators/survey-schema"; const GRID_LAYOUT_THRESHOLD = 10; diff --git a/src/components/survey/steps.tsx b/src/components/survey/steps.tsx index 9fe51996..3e7ab2ad 100644 --- a/src/components/survey/steps.tsx +++ b/src/components/survey/steps.tsx @@ -1,3 +1,6 @@ +import { useMemo } from "react"; +import { SurveyMachineContext } from "./survey-context"; + type StepProps = { label: string; selectedIndex: number; @@ -90,31 +93,49 @@ const Step = ({ ); }; -type StepsProps = { - selectedIndex: number; - sections: string[]; - onStepClick?: (index: number) => void; -}; +export const Steps = () => { + const actorRef = SurveyMachineContext.useActorRef(); + const context = SurveyMachineContext.useSelector((state) => state.context); + const visibleSections = useMemo( + () => + context.visibleSectionIndices.map((idx) => ({ + label: context.sections[idx].label, + originalIdx: idx + })), + [context.sections, context.visibleSectionIndices] + ); + + const sectionsLabels = useMemo( + () => visibleSections.map((s) => s.label), + [visibleSections] + ); + + const currentVisibleSectionIdx = useMemo( + () => context.visibleSectionIndices.indexOf(context.currentSectionIdx), + [context.visibleSectionIndices, context.currentSectionIdx] + ); + + const handleStepClick = (visibleIdx: number) => { + const originalIdx = visibleSections[visibleIdx]?.originalIdx; + if (originalIdx !== undefined) { + actorRef.send({ type: "GO_TO_SECTION", sectionIdx: originalIdx }); + } + }; -export const Steps = ({ - selectedIndex = 0, - sections, - onStepClick -}: StepsProps) => { return (
- {sections.map((section, index) => { + {sectionsLabels.map((section, index) => { return ( ); })} diff --git a/src/components/survey/survey-app.tsx b/src/components/survey/survey-app.tsx index 2f9a8ffb..d896ad3e 100644 --- a/src/components/survey/survey-app.tsx +++ b/src/components/survey/survey-app.tsx @@ -1,5 +1,6 @@ import { SurveyProvider } from "./survey-context"; import { SurveyForm } from "./survey-form"; +import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; type Props = { questions: SurveyQuestionsYamlFile[]; diff --git a/src/components/survey/survey-context.tsx b/src/components/survey/survey-context.tsx index 4f190893..45bd3a48 100644 --- a/src/components/survey/survey-context.tsx +++ b/src/components/survey/survey-context.tsx @@ -2,6 +2,7 @@ import { createActorContext } from "@xstate/react"; import { useEffect, type ReactNode } from "react"; import { surveyMachine, type SurveyContext } from "./survey-machine"; import { createSurveyInspector } from "./survey-inspector"; +import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; const STORAGE_KEY = "survey-state"; const STORAGE_VERSION = 1; @@ -78,7 +79,10 @@ export const SurveyProvider = ({ sections, children }: SurveyProviderProps) => { sections, persisted: persisted || undefined }, - inspect: import.meta.env.DEV ? createSurveyInspector() : undefined + inspect: + import.meta.env.DEV && import.meta.env.MODE !== "test" + ? createSurveyInspector() + : undefined }} > diff --git a/src/components/survey/survey-controls.tsx b/src/components/survey/survey-controls.tsx index bf44dd84..9e690f27 100644 --- a/src/components/survey/survey-controls.tsx +++ b/src/components/survey/survey-controls.tsx @@ -1,3 +1,6 @@ +import { SurveyMachineContext } from "./survey-context"; +import { hasPrevVisibleQuestion } from "@/lib/conditions"; + type ErrorMessageProps = { error: string | null; onClose: () => void; @@ -79,3 +82,83 @@ export const BackButton = ({ onClick }: BackButtonProps) => (
); + +export const SurveyActions = () => { + const actorRef = SurveyMachineContext.useActorRef(); + + const context = SurveyMachineContext.useSelector((state) => state.context); + const isSubmitting = SurveyMachineContext.useSelector((state) => + state.matches("submitting") + ); + + const currentSection = context.sections[context.currentSectionIdx]; + const currentQuestion = currentSection?.questions[context.currentQuestionIdx]; + + const isRequired = !!currentQuestion?.required; + const canGoBack = + context.visibleSectionIndices.indexOf(context.currentSectionIdx) > 0 || + hasPrevVisibleQuestion( + currentSection.questions, + context.currentQuestionIdx, + context.answers + ); + + const questionId = `${currentSection.label}-q-${context.currentQuestionIdx}`; + + const handleNext = () => { + const currentAnswer = context.answers[questionId]; + if (currentAnswer === undefined && currentQuestion.multiple) { + actorRef.send({ + type: "ANSWER_CHANGE", + questionId, + value: [] + }); + } + actorRef.send({ type: "NEXT" }); + }; + + const handleSkip = () => { + const value = currentQuestion.multiple ? [] : null; + actorRef.send({ + type: "ANSWER_CHANGE", + questionId, + value + }); + actorRef.send({ type: "SKIP" }); + }; + + const handleBack = () => { + actorRef.send({ type: "BACK" }); + }; + + return ( +
+ actorRef.send({ type: "CLEAR_ERROR" })} + /> +
{canGoBack && }
+
+ {!isRequired && ( + + )} + +
+
+ ); +}; diff --git a/src/components/survey/survey-form.test.tsx b/src/components/survey/survey-form.test.tsx index fb4bee5a..8f3ef79c 100644 --- a/src/components/survey/survey-form.test.tsx +++ b/src/components/survey/survey-form.test.tsx @@ -5,6 +5,7 @@ import { SurveyForm } from "./survey-form"; import { SurveyProvider } from "./survey-context"; import * as utils from "./utils"; import { ERRORS } from "./survey-machine"; +import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; // Mock utils module to prevent jsdom navigation errors vi.mock("./utils", async () => { diff --git a/src/components/survey/survey-form.tsx b/src/components/survey/survey-form.tsx index d799ee32..889f2d92 100644 --- a/src/components/survey/survey-form.tsx +++ b/src/components/survey/survey-form.tsx @@ -1,8 +1,7 @@ -import { useMemo } from "react"; import { Steps } from "./steps"; import { Question } from "./question"; import { SurveyMachineContext } from "./survey-context"; -import { ErrorMessage, BackButton } from "./survey-controls"; +import { SurveyActions } from "./survey-controls"; const QUESTION_CONTAINER_MIN_HEIGHT = "300px"; @@ -11,23 +10,11 @@ export const SurveyForm = () => { // Select all needed state const context = SurveyMachineContext.useSelector((state) => state.context); - const isSubmitting = SurveyMachineContext.useSelector((state) => - state.matches("submitting") - ); // Compute derived state const currentSection = context.sections[context.currentSectionIdx]; const currentQuestion = currentSection?.questions[context.currentQuestionIdx]; - const sectionsLabels = useMemo( - () => context.sections.map((q) => q.label), - [context.sections] - ); - - const isRequired = !!currentQuestion?.required; - const canGoBack = - context.currentSectionIdx > 0 || context.currentQuestionIdx > 0; - const questionId = `${currentSection.label}-q-${context.currentQuestionIdx}`; const handleAnswerChange = (value: number | number[] | null | string) => { @@ -46,49 +33,13 @@ export const SurveyForm = () => { }); }; - const handleNext = () => { - // If no answer selected for multiple choice, set to empty array - const currentAnswer = context.answers[questionId]; - if (currentAnswer === undefined && currentQuestion.multiple) { - actorRef.send({ - type: "ANSWER_CHANGE", - questionId, - value: [] - }); - } - actorRef.send({ type: "NEXT" }); - }; - - const handleSkip = () => { - // Set answer to null for single choice or [] for multiple choice when skipping - const value = currentQuestion.multiple ? [] : null; - actorRef.send({ - type: "ANSWER_CHANGE", - questionId, - value - }); - actorRef.send({ type: "SKIP" }); - }; - - const handleBack = () => { - actorRef.send({ type: "BACK" }); - }; - - const handleSectionClick = (sectionIdx: number) => { - actorRef.send({ type: "GO_TO_SECTION", sectionIdx }); - }; - if (!currentSection || !currentQuestion) { return null; } return (
- +
{ />
-
- actorRef.send({ type: "CLEAR_ERROR" })} - /> -
{canGoBack && }
-
- {!isRequired && ( - - )} - -
-
+
diff --git a/src/components/survey/survey-machine.test.ts b/src/components/survey/survey-machine.test.ts index 6396b1e2..959b885a 100644 --- a/src/components/survey/survey-machine.test.ts +++ b/src/components/survey/survey-machine.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { createActor } from "xstate"; import { surveyMachine, ERRORS } from "./survey-machine"; import * as utils from "./utils"; +import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; // Mock submitAnswers vi.mock("./utils", () => ({ @@ -113,7 +114,8 @@ describe("Survey State Machine", () => { currentSectionIdx: 0, currentQuestionIdx: 0, answers: {}, - error: null + error: null, + visibleSectionIndices: [0, 1] }); }); @@ -790,5 +792,365 @@ describe("Survey State Machine", () => { const snapshot = actor.getSnapshot(); expect(snapshot.context.answers["profile-q-2"]).toBe(null); }); + + it("should handle empty array for required multiple choice", () => { + const actor = createActor(surveyMachine, { + input: { sections: mockSections } + }); + actor.start(); + + // Answer first question to move to second (multiple choice) + actor.send({ + type: "ANSWER_CHANGE", + questionId: "profile-q-0", + value: 0 + }); + actor.send({ type: "NEXT" }); + + // Set empty array and try to go next (q-1 is not required, so move to q-2) + actor.send({ + type: "ANSWER_CHANGE", + questionId: "profile-q-1", + value: [] + }); + actor.send({ type: "NEXT" }); + + // Should move forward since q-1 is not required + expect(actor.getSnapshot().context.currentQuestionIdx).toBe(2); + }); + + it("should normalize boolean answers to empty array", async () => { + const actor = createActor(surveyMachine, { + input: { sections: mockSections } + }); + actor.start(); + + const submitSpy = vi.spyOn(utils, "submitAnswers").mockResolvedValue({ + data: undefined, + error: undefined + }); + + // Answer with boolean (simulating skipped multiple choice) + actor.send({ + type: "ANSWER_CHANGE", + questionId: "profile-q-0", + value: 0 + }); + actor.send({ type: "NEXT" }); + + actor.send({ + type: "ANSWER_CHANGE", + questionId: "profile-q-1", + value: false as any + }); + actor.send({ type: "NEXT" }); + actor.send({ type: "NEXT" }); + + await vi.waitFor(() => { + return submitSpy.mock.calls.length > 0; + }); + + const call = submitSpy.mock.calls[0][0]; + expect(call.answers["profile-q-1"]).toEqual([]); + }); + }); + + describe("CLEAR_ERROR Event", () => { + it("should manually clear error with CLEAR_ERROR event", () => { + const actor = createActor(surveyMachine, { + input: { sections: mockSections } + }); + actor.start(); + + // Trigger error + actor.send({ type: "NEXT" }); + expect(actor.getSnapshot().context.error).toBe(ERRORS.required); + + // Manually clear + actor.send({ type: "CLEAR_ERROR" }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.error).toBe(null); + }); + }); + + describe("Conditional Visibility (showIf)", () => { + const conditionalSections: SurveyQuestionsYamlFile[] = [ + { + title: "Basic Info", + label: "basic", + position: 1, + questions: [ + { + label: "Are you employed?", + choices: ["Yes", "No"], + multiple: false, + required: true + }, + { + label: "What is your job title?", + choices: ["Developer", "Designer", "Manager", "Other"], + multiple: false, + required: false, + showIf: { question: "basic-q-0", equals: 0 } + }, + { + label: "Are you looking for work?", + choices: ["Yes", "No"], + multiple: false, + required: false, + showIf: { question: "basic-q-0", equals: 1 } + } + ] + }, + { + title: "Employment Details", + label: "employment", + position: 2, + showIf: { question: "basic-q-0", equals: 0 }, + questions: [ + { + label: "Years of experience?", + choices: ["0-2", "3-5", "6+"], + multiple: false, + required: true + } + ] + } + ]; + + it("should skip hidden questions when navigating forward", () => { + const actor = createActor(surveyMachine, { + input: { sections: conditionalSections } + }); + actor.start(); + + // Answer "No" to employment - should skip q-1 and show q-2 + actor.send({ + type: "ANSWER_CHANGE", + questionId: "basic-q-0", + value: 1 + }); + actor.send({ type: "NEXT" }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.currentQuestionIdx).toBe(2); // Skipped q-1 + }); + + it("should skip hidden questions when navigating backward", () => { + const actor = createActor(surveyMachine, { + input: { sections: conditionalSections } + }); + actor.start(); + + // Answer "No" to employment + actor.send({ + type: "ANSWER_CHANGE", + questionId: "basic-q-0", + value: 1 + }); + actor.send({ type: "NEXT" }); + + // Now on q-2, go back should skip q-1 + actor.send({ type: "BACK" }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.currentQuestionIdx).toBe(0); // Back to q-0 + }); + + it("should update visibleSectionIndices when answer changes", () => { + const actor = createActor(surveyMachine, { + input: { sections: conditionalSections } + }); + actor.start(); + + // Initially employment section should be hidden + expect(actor.getSnapshot().context.visibleSectionIndices).toEqual([0]); + + // Answer "Yes" to employment - employment section becomes visible + actor.send({ + type: "ANSWER_CHANGE", + questionId: "basic-q-0", + value: 0 + }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.visibleSectionIndices).toEqual([0, 1]); + }); + + it("should auto-submit when all remaining questions are hidden", async () => { + const sectionsWithHiddenEnd: SurveyQuestionsYamlFile[] = [ + { + title: "Survey", + label: "survey", + position: 1, + questions: [ + { + label: "Do you code?", + choices: ["Yes", "No"], + multiple: false, + required: true + }, + { + label: "What languages?", + choices: ["JS", "Python"], + multiple: true, + required: false, + showIf: { question: "survey-q-0", equals: 0 } + }, + { + label: "Why not?", + choices: ["No interest", "No time"], + multiple: false, + required: false, + showIf: { question: "survey-q-0", equals: 1 } + } + ] + } + ]; + + const actor = createActor(surveyMachine, { + input: { sections: sectionsWithHiddenEnd } + }); + actor.start(); + + vi.spyOn(utils, "submitAnswers").mockResolvedValue({ + data: undefined, + error: undefined + }); + + // Answer "Yes" - this hides q-2, leaving q-1 as only remaining question + actor.send({ + type: "ANSWER_CHANGE", + questionId: "survey-q-0", + value: 0 + }); + actor.send({ type: "NEXT" }); + + // Now on q-1. Answer it. + actor.send({ + type: "ANSWER_CHANGE", + questionId: "survey-q-1", + value: [0] + }); + + // NEXT should trigger submit since q-2 is hidden (no more visible questions) + actor.send({ type: "NEXT" }); + + await vi.waitFor(() => { + const snapshot = actor.getSnapshot(); + return snapshot.value === "submitting" || snapshot.value === "complete"; + }); + + expect(utils.submitAnswers).toHaveBeenCalled(); + }); + + it("should skip section if all questions are hidden", async () => { + const sectionsWithHiddenSection: SurveyQuestionsYamlFile[] = [ + { + title: "Profile", + label: "profile", + position: 1, + questions: [ + { + label: "Are you a developer?", + choices: ["Yes", "No"], + multiple: false, + required: true + } + ] + }, + { + title: "Developer Questions", + label: "dev", + position: 2, + questions: [ + { + label: "What's your role?", + choices: ["Frontend", "Backend"], + multiple: false, + required: false, + showIf: { question: "profile-q-0", equals: 0 } + }, + { + label: "Years of experience?", + choices: ["0-2", "3+"], + multiple: false, + required: false, + showIf: { question: "profile-q-0", equals: 0 } + } + ] + }, + { + title: "Final", + label: "final", + position: 3, + questions: [ + { + label: "Any feedback?", + choices: ["Yes", "No"], + multiple: false, + required: false + } + ] + } + ]; + + const actor = createActor(surveyMachine, { + input: { sections: sectionsWithHiddenSection } + }); + actor.start(); + + vi.spyOn(utils, "submitAnswers").mockResolvedValue({ + data: undefined, + error: undefined + }); + + // Answer "No" - this hides all questions in dev section + actor.send({ + type: "ANSWER_CHANGE", + questionId: "profile-q-0", + value: 1 + }); + actor.send({ type: "NEXT" }); + + // Should submit first section + await vi.waitFor(() => { + return actor.getSnapshot().value === "submitting"; + }); + + await vi.runAllTimersAsync(); + + // Should skip dev section and go to final section + await vi.waitFor(() => { + const snapshot = actor.getSnapshot(); + return ( + snapshot.value === "answering" && + snapshot.context.currentSectionIdx === 2 + ); + }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.currentSectionIdx).toBe(2); // Skipped section 1 + expect(snapshot.context.currentQuestionIdx).toBe(0); + }); + + it("should handle section showIf condition hiding entire section", () => { + const actor = createActor(surveyMachine, { + input: { sections: conditionalSections } + }); + actor.start(); + + // Answer "No" - employment section should be hidden + actor.send({ + type: "ANSWER_CHANGE", + questionId: "basic-q-0", + value: 1 + }); + + const snapshot = actor.getSnapshot(); + expect(snapshot.context.visibleSectionIndices).toEqual([0]); + expect(snapshot.context.visibleSectionIndices).not.toContain(1); + }); }); }); diff --git a/src/components/survey/survey-machine.ts b/src/components/survey/survey-machine.ts index ebd78aa6..b54c5650 100644 --- a/src/components/survey/survey-machine.ts +++ b/src/components/survey/survey-machine.ts @@ -1,5 +1,15 @@ import { setup, assign, fromPromise } from "xstate"; import { submitAnswers, goToThanksPage } from "./utils"; +import { + getVisibleSectionIndices, + getNextVisibleQuestionIndex, + getPrevVisibleQuestionIndex, + getFirstVisibleQuestionIndex, + getLastVisibleQuestionIndex, + hasNextVisibleQuestion, + hasPrevVisibleQuestion +} from "@/lib/conditions"; +import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; const ERROR_TIMEOUT_MS = 3000; @@ -9,6 +19,7 @@ export type SurveyContext = { currentQuestionIdx: number; answers: Record; error: string | null; + visibleSectionIndices: number[]; }; export type SurveyEvents = @@ -80,30 +91,66 @@ export const surveyMachine = setup({ const questionId = `${section.label}-q-${context.currentQuestionIdx}`; const value = context.answers[questionId]; - return value === null || value === undefined; + return ( + value === null || + value === undefined || + (Array.isArray(value) && value.length === 0) + ); }, isLastQuestion: ({ context }) => { const section = context.sections[context.currentSectionIdx]; - return context.currentQuestionIdx === section.questions.length - 1; + return !hasNextVisibleQuestion( + section.questions, + context.currentQuestionIdx, + context.answers + ); }, isNotLastQuestion: ({ context }) => { const section = context.sections[context.currentSectionIdx]; - return context.currentQuestionIdx < section.questions.length - 1; + return hasNextVisibleQuestion( + section.questions, + context.currentQuestionIdx, + context.answers + ); }, isLastSection: ({ context }) => { - return context.currentSectionIdx === context.sections.length - 1; + return ( + context.currentSectionIdx === + context.visibleSectionIndices[context.visibleSectionIndices.length - 1] + ); }, isNotLastSection: ({ context }) => { - return context.currentSectionIdx < context.sections.length - 1; + return ( + context.currentSectionIdx !== + context.visibleSectionIndices[context.visibleSectionIndices.length - 1] + ); }, canGoBackInSection: ({ context }) => { - return context.currentQuestionIdx > 0; + const section = context.sections[context.currentSectionIdx]; + return hasPrevVisibleQuestion( + section.questions, + context.currentQuestionIdx, + context.answers + ); }, canGoBackToSection: ({ context }) => { - return context.currentQuestionIdx === 0 && context.currentSectionIdx > 0; + const section = context.sections[context.currentSectionIdx]; + const hasPrevInSection = hasPrevVisibleQuestion( + section.questions, + context.currentQuestionIdx, + context.answers + ); + const currentVisibleSectionIdx = context.visibleSectionIndices.indexOf( + context.currentSectionIdx + ); + return !hasPrevInSection && currentVisibleSectionIdx > 0; } }, actions: { + computeVisibleIndices: assign({ + visibleSectionIndices: ({ context }) => + getVisibleSectionIndices(context.sections, context.answers) + }), updateAnswer: assign({ answers: ({ context, event }) => { if (event.type !== "ANSWER_CHANGE") return context.answers; @@ -114,20 +161,73 @@ export const surveyMachine = setup({ } }), incrementQuestion: assign({ - currentQuestionIdx: ({ context }) => context.currentQuestionIdx + 1 + currentQuestionIdx: ({ context }) => { + const section = context.sections[context.currentSectionIdx]; + const next = getNextVisibleQuestionIndex( + section.questions, + context.currentQuestionIdx, + context.answers + ); + return next ?? context.currentQuestionIdx; + } }), decrementQuestion: assign({ - currentQuestionIdx: ({ context }) => context.currentQuestionIdx - 1 + currentQuestionIdx: ({ context }) => { + const section = context.sections[context.currentSectionIdx]; + const prev = getPrevVisibleQuestionIndex( + section.questions, + context.currentQuestionIdx, + context.answers + ); + return prev ?? context.currentQuestionIdx; + } }), incrementSection: assign({ - currentSectionIdx: ({ context }) => context.currentSectionIdx + 1, - currentQuestionIdx: 0 + currentSectionIdx: ({ context }) => { + const currentIdx = context.visibleSectionIndices.indexOf( + context.currentSectionIdx + ); + const nextVisibleIdx = context.visibleSectionIndices[currentIdx + 1]; + return nextVisibleIdx ?? context.currentSectionIdx; + }, + currentQuestionIdx: ({ context }) => { + const currentIdx = context.visibleSectionIndices.indexOf( + context.currentSectionIdx + ); + const nextSectionIdx = context.visibleSectionIndices[currentIdx + 1]; + if (nextSectionIdx !== undefined) { + const nextSection = context.sections[nextSectionIdx]; + const firstVisible = getFirstVisibleQuestionIndex( + nextSection.questions, + context.answers + ); + return firstVisible ?? 0; + } + return 0; + } }), goToLastQuestionInPreviousSection: assign({ - currentSectionIdx: ({ context }) => context.currentSectionIdx - 1, + currentSectionIdx: ({ context }) => { + const currentIdx = context.visibleSectionIndices.indexOf( + context.currentSectionIdx + ); + const prevVisibleIdx = context.visibleSectionIndices[currentIdx - 1]; + return prevVisibleIdx ?? context.currentSectionIdx; + }, currentQuestionIdx: ({ context }) => { - const prevSection = context.sections[context.currentSectionIdx - 1]; - return prevSection.questions.length - 1; + const currentIdx = context.visibleSectionIndices.indexOf( + context.currentSectionIdx + ); + const prevSectionIdx = context.visibleSectionIndices[currentIdx - 1]; + if (prevSectionIdx !== undefined) { + const prevSection = context.sections[prevSectionIdx]; + const lastVisible = getLastVisibleQuestionIndex( + prevSection.questions, + context.answers + ); + return lastVisible ?? 0; + } + return 0; } }), setRequiredError: assign({ @@ -183,15 +283,22 @@ export const surveyMachine = setup({ }).createMachine({ id: "survey", initial: "answering", - context: ({ input }) => ({ - sections: input.sections, - currentSectionIdx: input.persisted?.currentSectionIdx ?? 0, - currentQuestionIdx: input.persisted?.currentQuestionIdx ?? 0, - answers: input.persisted?.answers ?? {}, - error: null - }), + context: ({ input }) => { + const sections = input.sections; + const answers = input.persisted?.answers ?? {}; + + return { + sections, + currentSectionIdx: input.persisted?.currentSectionIdx ?? 0, + currentQuestionIdx: input.persisted?.currentQuestionIdx ?? 0, + answers, + error: null, + visibleSectionIndices: getVisibleSectionIndices(sections, answers) + }; + }, states: { answering: { + entry: ["computeVisibleIndices"], after: { ERROR_TIMEOUT: { guard: ({ context }) => context.error !== null, @@ -203,7 +310,7 @@ export const surveyMachine = setup({ actions: ["clearError"] }, ANSWER_CHANGE: { - actions: ["updateAnswer", "clearError"] + actions: ["updateAnswer", "computeVisibleIndices", "clearError"] }, NEXT: [ { diff --git a/src/lib/validators/survey-schema.ts b/src/lib/validators/survey-schema.ts index d5b2f356..6b4c95a0 100644 --- a/src/lib/validators/survey-schema.ts +++ b/src/lib/validators/survey-schema.ts @@ -15,26 +15,26 @@ export const ShowIfConditionSchema = z .string() .regex( /^[a-z0-9-]+-q-\d+$/, - "Must be valid question ID format (e.g., 'profile-q-0')" + "Question ID must be in format: {section-label}-q-{index}" ), equals: z.number().int().nonnegative().optional(), notEquals: z.number().int().nonnegative().optional(), - in: z.array(z.number().int().nonnegative()).optional(), - notIn: z.array(z.number().int().nonnegative()).optional() + in: z.array(z.number().int().nonnegative()).min(1).optional(), + notIn: z.array(z.number().int().nonnegative()).min(1).optional() }) .refine( (data) => { const operators = [ - data.equals, - data.notEquals, - data.in, - data.notIn - ].filter((op) => op !== undefined); + data.equals !== undefined, + data.notEquals !== undefined, + data.in !== undefined, + data.notIn !== undefined + ].filter(Boolean); return operators.length === 1; }, { message: - "Exactly one operator (equals, notEquals, in, notIn) must be specified" + "Exactly one operator must be specified: equals, notEquals, in, or notIn" } ); @@ -127,7 +127,9 @@ export const SurveyFileSchema = z { message: "Duplicate question labels detected within the section" } - ) + ), + + showIf: ShowIfConditionSchema.optional() }) .refine( (data) => { diff --git a/survey/1-profile.yml b/survey/1-profile.yml index 8924edea..9f7c11cd 100644 --- a/survey/1-profile.yml +++ b/survey/1-profile.yml @@ -114,6 +114,9 @@ questions: - No - label: If you are working abroad, do you have any plans to come back to Morocco? + showIf: + question: profile-q-7 + equals: 3 choices: - Yes, within the next 12 months - Yes, within the next 24 months diff --git a/survey/2-learning-and-education.yml b/survey/2-learning-and-education.yml index 61e70afc..1c8d3e85 100644 --- a/survey/2-learning-and-education.yml +++ b/survey/2-learning-and-education.yml @@ -72,7 +72,7 @@ questions: - Coding challenges (LeetCode, HackerRank, etc) - Other platforms - - label: What are the biggest challenges you find in learning new technologies? (pick 3) + - label: What are the biggest challenges you find in learning new technologies (pick 3)? required: true choices: - Lack of time From 54d8a2ab900dfa2eca8525ee2a9c42a739eaf15d Mon Sep 17 00:00:00 2001 From: Youssouf EL Azizi Date: Tue, 25 Nov 2025 15:36:25 +0100 Subject: [PATCH 3/8] feat: add showIf validation, conditions tests, convert theme-toggle to Astro - Add 51 unit tests for conditions.ts evaluation functions - Add blocking cross-reference validation for showIf conditions - Fix SectionList type to include questions property (remove any cast) - Convert theme-toggle from React to Astro component with vanilla JS --- src/components/header.astro | 6 +- src/components/theme-toggle.astro | 67 ++++ src/components/theme-toggle.tsx | 82 ----- src/lib/conditions.test.ts | 445 +++++++++++++++++++++++++ src/lib/conditions.ts | 7 +- src/lib/validators/survey-validator.ts | 81 +++++ 6 files changed, 601 insertions(+), 87 deletions(-) create mode 100644 src/components/theme-toggle.astro delete mode 100644 src/components/theme-toggle.tsx create mode 100644 src/lib/conditions.test.ts diff --git a/src/components/header.astro b/src/components/header.astro index 4f38e4c3..95e14e6c 100644 --- a/src/components/header.astro +++ b/src/components/header.astro @@ -3,7 +3,7 @@ import Logo from "../assets/logo.svg?raw"; import Github from "../assets/github.svg?raw"; import Chart from "../assets/Chart.svg?raw"; import HeaderLink from "./header-link.astro"; -import { ThemeToggle } from "./theme-toggle"; +import ThemeToggle from "./theme-toggle.astro"; import { website } from "@/website"; const navItems = [ @@ -85,7 +85,7 @@ const currentPath = Astro.url.pathname; )) }
  • - +
  • @@ -109,7 +109,7 @@ const currentPath = Astro.url.pathname; )) } - + diff --git a/src/components/theme-toggle.astro b/src/components/theme-toggle.astro new file mode 100644 index 00000000..a86dea4d --- /dev/null +++ b/src/components/theme-toggle.astro @@ -0,0 +1,67 @@ +--- + +--- + + + + + + diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx deleted file mode 100644 index 3c08348f..00000000 --- a/src/components/theme-toggle.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect, useState } from "react"; - -export const ThemeToggle = () => { - const [theme, setTheme] = useState<"light" | "dark">("dark"); - const [mounted, setMounted] = useState(false); - - // Load theme from localStorage on mount - useEffect(() => { - setMounted(true); - const storedTheme = localStorage.getItem("theme") as - | "light" - | "dark" - | null; - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches - ? "dark" - : "light"; - const initialTheme = storedTheme || "dark"; - setTheme(initialTheme); - document.documentElement.classList.toggle("dark", initialTheme === "dark"); - }, []); - - const toggleTheme = () => { - const newTheme = theme === "light" ? "dark" : "light"; - setTheme(newTheme); - localStorage.setItem("theme", newTheme); - document.documentElement.classList.toggle("dark", newTheme === "dark"); - }; - - // Don't render until mounted to prevent hydration mismatch - if (!mounted) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/src/lib/conditions.test.ts b/src/lib/conditions.test.ts new file mode 100644 index 00000000..26534fc6 --- /dev/null +++ b/src/lib/conditions.test.ts @@ -0,0 +1,445 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { + evaluateCondition, + getVisibleSectionIndices, + getNextVisibleQuestionIndex, + getPrevVisibleQuestionIndex, + getFirstVisibleQuestionIndex, + getLastVisibleQuestionIndex, + hasNextVisibleQuestion, + hasPrevVisibleQuestion, + type Answers, + type QuestionList, + type SectionList +} from "./conditions"; + +describe("evaluateCondition", () => { + let consoleWarnSpy: ReturnType; + + beforeEach(() => { + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + consoleWarnSpy.mockRestore(); + }); + + describe("no condition", () => { + it("returns true when condition is undefined", () => { + expect(evaluateCondition(undefined, {})).toBe(true); + }); + }); + + describe("invalid question ID format", () => { + it("returns true and warns for empty question ID", () => { + const condition = { question: "", equals: 1 }; + expect(evaluateCondition(condition, {})).toBe(true); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("Invalid question ID format") + ); + }); + + it("returns true and warns for invalid format without -q-", () => { + const condition = { question: "profile-0", equals: 1 }; + expect(evaluateCondition(condition, {})).toBe(true); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + + it("returns true and warns for uppercase question ID", () => { + const condition = { question: "Profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, {})).toBe(true); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + + it("accepts valid question ID formats", () => { + const answers: Answers = { "profile-q-0": 1 }; + const condition = { question: "profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(true); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); + + it("accepts kebab-case section labels", () => { + const answers: Answers = { "learning-education-q-5": 2 }; + const condition = { question: "learning-education-q-5", equals: 2 }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + }); + + describe("unanswered questions", () => { + it("returns false when answer is null", () => { + const answers: Answers = { "profile-q-0": null }; + const condition = { question: "profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + + it("returns false when answer is undefined (not in answers)", () => { + const answers: Answers = {}; + const condition = { question: "profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + }); + + describe("equals operator", () => { + it("returns true when answer equals expected value", () => { + const answers: Answers = { "profile-q-0": 2 }; + const condition = { question: "profile-q-0", equals: 2 }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + + it("returns false when answer does not equal expected value", () => { + const answers: Answers = { "profile-q-0": 1 }; + const condition = { question: "profile-q-0", equals: 2 }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + + it("returns false and warns when answer is an array", () => { + const answers: Answers = { "profile-q-0": [1, 2] }; + const condition = { question: "profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(false); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("equals operator used on non-number answer") + ); + }); + + it("returns false and warns when answer is a string", () => { + const answers: Answers = { "profile-q-0": "text" }; + const condition = { question: "profile-q-0", equals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(false); + expect(consoleWarnSpy).toHaveBeenCalled(); + }); + + it("handles equals: 0 correctly", () => { + const answers: Answers = { "profile-q-0": 0 }; + const condition = { question: "profile-q-0", equals: 0 }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + }); + + describe("notEquals operator", () => { + it("returns true when answer does not equal expected value", () => { + const answers: Answers = { "profile-q-0": 1 }; + const condition = { question: "profile-q-0", notEquals: 2 }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + + it("returns false when answer equals expected value", () => { + const answers: Answers = { "profile-q-0": 2 }; + const condition = { question: "profile-q-0", notEquals: 2 }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + + it("returns false and warns when answer is an array", () => { + const answers: Answers = { "profile-q-0": [1, 2] }; + const condition = { question: "profile-q-0", notEquals: 1 }; + expect(evaluateCondition(condition, answers)).toBe(false); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("notEquals operator used on non-number answer") + ); + }); + }); + + describe("in operator", () => { + it("returns true when answer is in the expected array", () => { + const answers: Answers = { "profile-q-0": 2 }; + const condition = { question: "profile-q-0", in: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + + it("returns false when answer is not in the expected array", () => { + const answers: Answers = { "profile-q-0": 5 }; + const condition = { question: "profile-q-0", in: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + + it("returns true when answer is the only value in array", () => { + const answers: Answers = { "profile-q-0": 1 }; + const condition = { question: "profile-q-0", in: [1] }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + + it("returns false and warns when answer is an array", () => { + const answers: Answers = { "profile-q-0": [1, 2] }; + const condition = { question: "profile-q-0", in: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(false); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("in operator used on non-number answer") + ); + }); + }); + + describe("notIn operator", () => { + it("returns true when answer is not in the expected array", () => { + const answers: Answers = { "profile-q-0": 5 }; + const condition = { question: "profile-q-0", notIn: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(true); + }); + + it("returns false when answer is in the expected array", () => { + const answers: Answers = { "profile-q-0": 2 }; + const condition = { question: "profile-q-0", notIn: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(false); + }); + + it("returns false and warns when answer is an array", () => { + const answers: Answers = { "profile-q-0": [4, 5] }; + const condition = { question: "profile-q-0", notIn: [1, 2, 3] }; + expect(evaluateCondition(condition, answers)).toBe(false); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("notIn operator used on non-number answer") + ); + }); + }); + + describe("no valid operator", () => { + it("returns true and warns when no operator is provided", () => { + const answers: Answers = { "profile-q-0": 1 }; + const condition = { question: "profile-q-0" } as { + question: string; + equals?: number; + }; + expect(evaluateCondition(condition, answers)).toBe(true); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("No valid operator found") + ); + }); + }); +}); + +describe("getVisibleSectionIndices", () => { + it("returns all indices when no conditions exist", () => { + const sections = [ + { questions: [{}] }, + { questions: [{}] }, + { questions: [{}] } + ] as SectionList; + const answers: Answers = {}; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0, 1, 2]); + }); + + it("filters out sections with failing showIf conditions", () => { + const sections = [ + { showIf: undefined, questions: [{}] }, + { showIf: { question: "profile-q-0", equals: 1 }, questions: [{}] }, + { showIf: undefined, questions: [{}] } + ] as SectionList; + const answers: Answers = { "profile-q-0": 0 }; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0, 2]); + }); + + it("includes sections when showIf condition passes", () => { + const sections = [ + { showIf: undefined, questions: [{}] }, + { showIf: { question: "profile-q-0", equals: 1 }, questions: [{}] } + ] as SectionList; + const answers: Answers = { "profile-q-0": 1 }; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0, 1]); + }); + + it("filters out sections with no questions", () => { + const sections = [ + { questions: [{}] }, + { questions: [] }, + { questions: [{}] } + ] as SectionList; + const answers: Answers = {}; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0, 2]); + }); + + it("filters out sections where all questions are hidden", () => { + const sections = [ + { questions: [{}] }, + { + questions: [ + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } } + ] + } + ] as SectionList; + const answers: Answers = { "profile-q-0": 0 }; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0]); + }); + + it("includes sections with at least one visible question", () => { + const sections = [ + { + questions: [ + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: undefined } + ] + } + ] as SectionList; + const answers: Answers = { "profile-q-0": 0 }; + expect(getVisibleSectionIndices(sections, answers)).toEqual([0]); + }); +}); + +describe("getNextVisibleQuestionIndex", () => { + it("returns next index when no conditions exist", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getNextVisibleQuestionIndex(questions, 0, {})).toBe(1); + expect(getNextVisibleQuestionIndex(questions, 1, {})).toBe(2); + }); + + it("returns undefined when at last question", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getNextVisibleQuestionIndex(questions, 2, {})).toBeUndefined(); + }); + + it("skips hidden questions", () => { + const questions: QuestionList = [ + {}, + { showIf: { question: "profile-q-0", equals: 1 } }, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getNextVisibleQuestionIndex(questions, 0, answers)).toBe(2); + }); + + it("returns undefined when all remaining questions are hidden", () => { + const questions: QuestionList = [ + {}, + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } } + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getNextVisibleQuestionIndex(questions, 0, answers)).toBeUndefined(); + }); + + it("finds visible question after multiple hidden ones", () => { + const questions: QuestionList = [ + {}, + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } }, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getNextVisibleQuestionIndex(questions, 0, answers)).toBe(3); + }); +}); + +describe("getPrevVisibleQuestionIndex", () => { + it("returns previous index when no conditions exist", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getPrevVisibleQuestionIndex(questions, 2, {})).toBe(1); + expect(getPrevVisibleQuestionIndex(questions, 1, {})).toBe(0); + }); + + it("returns undefined when at first question", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getPrevVisibleQuestionIndex(questions, 0, {})).toBeUndefined(); + }); + + it("skips hidden questions", () => { + const questions: QuestionList = [ + {}, + { showIf: { question: "profile-q-0", equals: 1 } }, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getPrevVisibleQuestionIndex(questions, 2, answers)).toBe(0); + }); + + it("returns undefined when all previous questions are hidden", () => { + const questions: QuestionList = [ + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } }, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getPrevVisibleQuestionIndex(questions, 2, answers)).toBeUndefined(); + }); +}); + +describe("getFirstVisibleQuestionIndex", () => { + it("returns 0 when first question is visible", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getFirstVisibleQuestionIndex(questions, {})).toBe(0); + }); + + it("skips hidden first questions", () => { + const questions: QuestionList = [ + { showIf: { question: "profile-q-0", equals: 1 } }, + {}, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getFirstVisibleQuestionIndex(questions, answers)).toBe(1); + }); + + it("returns undefined when all questions are hidden", () => { + const questions: QuestionList = [ + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } } + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getFirstVisibleQuestionIndex(questions, answers)).toBeUndefined(); + }); +}); + +describe("getLastVisibleQuestionIndex", () => { + it("returns last index when last question is visible", () => { + const questions: QuestionList = [{}, {}, {}]; + expect(getLastVisibleQuestionIndex(questions, {})).toBe(2); + }); + + it("skips hidden last questions", () => { + const questions: QuestionList = [ + {}, + {}, + { showIf: { question: "profile-q-0", equals: 1 } } + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getLastVisibleQuestionIndex(questions, answers)).toBe(1); + }); + + it("returns undefined when all questions are hidden", () => { + const questions: QuestionList = [ + { showIf: { question: "profile-q-0", equals: 1 } }, + { showIf: { question: "profile-q-0", equals: 1 } } + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(getLastVisibleQuestionIndex(questions, answers)).toBeUndefined(); + }); +}); + +describe("hasNextVisibleQuestion", () => { + it("returns true when there is a next visible question", () => { + const questions: QuestionList = [{}, {}]; + expect(hasNextVisibleQuestion(questions, 0, {})).toBe(true); + }); + + it("returns false when at last question", () => { + const questions: QuestionList = [{}, {}]; + expect(hasNextVisibleQuestion(questions, 1, {})).toBe(false); + }); + + it("returns false when all remaining questions are hidden", () => { + const questions: QuestionList = [ + {}, + { showIf: { question: "profile-q-0", equals: 1 } } + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(hasNextVisibleQuestion(questions, 0, answers)).toBe(false); + }); +}); + +describe("hasPrevVisibleQuestion", () => { + it("returns true when there is a previous visible question", () => { + const questions: QuestionList = [{}, {}]; + expect(hasPrevVisibleQuestion(questions, 1, {})).toBe(true); + }); + + it("returns false when at first question", () => { + const questions: QuestionList = [{}, {}]; + expect(hasPrevVisibleQuestion(questions, 0, {})).toBe(false); + }); + + it("returns false when all previous questions are hidden", () => { + const questions: QuestionList = [ + { showIf: { question: "profile-q-0", equals: 1 } }, + {} + ]; + const answers: Answers = { "profile-q-0": 0 }; + expect(hasPrevVisibleQuestion(questions, 1, answers)).toBe(false); + }); +}); diff --git a/src/lib/conditions.ts b/src/lib/conditions.ts index b939a2b6..15cd0b1f 100644 --- a/src/lib/conditions.ts +++ b/src/lib/conditions.ts @@ -3,7 +3,10 @@ import type { ShowIfCondition } from "./validators/survey-schema"; export type AnswerValue = number | number[] | null | string; export type Answers = Record; export type QuestionList = Array<{ showIf?: ShowIfCondition }>; -export type SectionList = Array<{ showIf?: ShowIfCondition }>; +export type SectionList = Array<{ + showIf?: ShowIfCondition; + questions?: QuestionList; +}>; /** * Evaluates a showIf condition against current answers @@ -134,7 +137,7 @@ export function getVisibleSectionIndices( if (!sectionVisible) return { index, visible: false }; // Check if section has at least one visible question - const questions = (section as any).questions as QuestionList | undefined; + const questions = section.questions; if (!questions || questions.length === 0) { return { index, visible: false }; } diff --git a/src/lib/validators/survey-validator.ts b/src/lib/validators/survey-validator.ts index 418aef46..022399dd 100644 --- a/src/lib/validators/survey-validator.ts +++ b/src/lib/validators/survey-validator.ts @@ -364,6 +364,87 @@ function performCrossFileValidation( }); }); + // Check 7: Validate showIf cross-references + // Build position map: questionId -> { sectionPosition, questionIndex } + const questionPositionMap = new Map< + string, + { sectionPosition: number; questionIndex: number } + >(); + + files.forEach((file) => { + file.questions.forEach((_, qIndex) => { + const questionId = `${file.label}-q-${qIndex}`; + questionPositionMap.set(questionId, { + sectionPosition: file.position, + questionIndex: qIndex + }); + }); + }); + + // Validate section-level showIf references + files.forEach((file, fileIndex) => { + if (file.showIf?.question) { + const refId = file.showIf.question; + const refPosition = questionPositionMap.get(refId); + + if (!refPosition) { + errors.push({ + severity: ValidationSeverity.ERROR, + message: `Section showIf references non-existent question "${refId}"`, + path: `${filenames[fileIndex]}.showIf.question`, + value: refId + }); + } else if (refPosition.sectionPosition >= file.position) { + errors.push({ + severity: ValidationSeverity.ERROR, + message: `Section showIf references question "${refId}" which is not in a previous section (references must point backward)`, + path: `${filenames[fileIndex]}.showIf.question`, + value: { + referenced: refId, + refSection: refPosition.sectionPosition, + currentSection: file.position + } + }); + } + } + + // Validate question-level showIf references + file.questions.forEach((question, qIndex) => { + if (question.showIf?.question) { + const refId = question.showIf.question; + const refPosition = questionPositionMap.get(refId); + const currentQuestionId = `${file.label}-q-${qIndex}`; + + if (!refPosition) { + errors.push({ + severity: ValidationSeverity.ERROR, + message: `Question showIf references non-existent question "${refId}"`, + path: `${filenames[fileIndex]}.questions[${qIndex}].showIf.question`, + value: refId + }); + } else { + // Check if reference points backward (earlier section or earlier in same section) + const isEarlierSection = refPosition.sectionPosition < file.position; + const isSameSectionEarlierQuestion = + refPosition.sectionPosition === file.position && + refPosition.questionIndex < qIndex; + + if (!isEarlierSection && !isSameSectionEarlierQuestion) { + errors.push({ + severity: ValidationSeverity.ERROR, + message: `Question showIf references "${refId}" which does not come before "${currentQuestionId}" (references must point backward)`, + path: `${filenames[fileIndex]}.questions[${qIndex}].showIf.question`, + value: { + referenced: refId, + current: currentQuestionId + } + }); + } + } + } + }); + }); + return errors; } From 570e9b07eab4a84d8e97dd0a14964e19e1656964 Mon Sep 17 00:00:00 2001 From: Youssouf EL Azizi Date: Tue, 25 Nov 2025 23:21:09 +0100 Subject: [PATCH 4/8] revert strict boolean expr, fix type errors --- .eslintrc.cjs | 32 - .vscode/settings.json | 53 +- custom-json.d.ts | 12 +- custom-yaml.d.ts | 1 + eslint.config.js | 24 + package.json | 22 +- pnpm-lock.yaml | 2278 ++++++++++++++--- prettier.config.cjs | 13 - results/2024/sections/ai.mdx | 6 +- scripts/export-results.ts | 17 +- scripts/generate-questions.ts | 34 +- scripts/validate-survey.ts | 15 +- src/actions/init-session.ts | 18 +- src/actions/submit-answers.ts | 11 +- src/components/chart/bar-chart.tsx | 170 +- src/components/chart/chart-actions.tsx | 18 +- src/components/chart/chart.astro | 11 +- src/components/chart/chart.test.ts | 150 +- src/components/chart/chart.tsx | 17 +- src/components/chart/data.ts | 24 +- src/components/chart/pie-chart.tsx | 92 +- src/components/chart/share-buttons.tsx | 22 +- src/components/chart/utils.ts | 108 +- src/components/faq-item.astro | 3 +- src/components/faq-list.astro | 3 +- src/components/footer.astro | 6 +- src/components/google-analytics.astro | 12 +- src/components/header.astro | 16 +- src/components/home/hero.astro | 16 +- src/components/home/past-repports.tsx | 196 +- src/components/home/why.tsx | 216 +- src/components/layout.astro | 134 +- src/components/playground/chart-types.ts | 6 + src/components/playground/filters-options.tsx | 59 +- src/components/playground/index.tsx | 71 +- src/components/playground/playground-form.tsx | 79 +- src/components/report/hero.astro | 13 +- src/components/report/index.astro | 22 +- src/components/report/toc.astro | 296 +-- src/components/social-media-card.astro | 15 +- src/components/survey/choice.tsx | 18 +- src/components/survey/exit-popup.astro | 69 +- src/components/survey/index.astro | 4 +- src/components/survey/question.tsx | 70 +- src/components/survey/steps.tsx | 101 +- src/components/survey/survey-app.tsx | 10 +- src/components/survey/survey-context.tsx | 59 +- src/components/survey/survey-controls.tsx | 92 +- src/components/survey/survey-form.test.tsx | 42 +- src/components/survey/survey-form.tsx | 16 +- src/components/survey/survey-inspector.ts | 19 +- src/components/survey/survey-machine.test.ts | 50 +- src/components/survey/survey-machine.ts | 97 +- src/components/survey/utils.ts | 10 +- src/components/theme-toggle.astro | 18 +- src/lib/captcha.ts | 24 +- src/lib/conditions.test.ts | 16 +- src/lib/conditions.ts | 28 +- src/lib/firebase/client.ts | 15 +- src/lib/firebase/database.ts | 24 +- src/lib/firebase/server.ts | 13 +- src/lib/utils.ts | 3 +- src/lib/validators/survey-schema.test.ts | 153 +- src/lib/validators/survey-schema.ts | 27 +- src/lib/validators/survey-validator.ts | 95 +- src/pages/2022.astro | 1 + src/pages/api/init-session.ts | 7 +- src/pages/api/remove-session.ts | 1 + src/pages/before-start.astro | 108 +- src/pages/home.astro | 2 +- src/pages/playground.astro | 3 +- src/pages/survey.astro | 7 +- src/pages/thanks.astro | 63 +- vitest-setup.js | 2 +- vitest.config.ts | 8 +- 75 files changed, 3646 insertions(+), 1940 deletions(-) delete mode 100644 .eslintrc.cjs create mode 100644 eslint.config.js delete mode 100644 prettier.config.cjs create mode 100644 src/components/playground/chart-types.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index b90905fa..00000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,32 +0,0 @@ -/** @type {import("eslint").Linter.Config} */ -module.exports = { - extends: ['plugin:astro/recommended'], - plugins: ['unicorn'], - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: __dirname, - sourceType: 'module', - ecmaVersion: 'latest' - }, - overrides: [ - { - files: ['*.astro'], - parser: 'astro-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - extraFileExtensions: ['.astro'] - }, - rules: { - // Add any specific rules for .astro files here - } - } - ], - rules: { - 'unicorn/filename-case': [ - 'error', - { - case: 'kebabCase', - }, - ], - } -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5de7bc0d..4b356f5b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,50 @@ { + // Disable the default formatter, use eslint instead + "prettier.enable": false, + "editor.formatOnSave": false, + + // Auto fix + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + + // Silent the stylistic rules in your IDE, but still auto fix them + "eslint.rules.customizations": [ + { "rule": "style/*", "severity": "off", "fixable": true }, + { "rule": "format/*", "severity": "off", "fixable": true }, + { "rule": "*-indent", "severity": "off", "fixable": true }, + { "rule": "*-spacing", "severity": "off", "fixable": true }, + { "rule": "*-spaces", "severity": "off", "fixable": true }, + { "rule": "*-order", "severity": "off", "fixable": true }, + { "rule": "*-dangle", "severity": "off", "fixable": true }, + { "rule": "*-newline", "severity": "off", "fixable": true }, + { "rule": "*quotes", "severity": "off", "fixable": true }, + { "rule": "*semi", "severity": "off", "fixable": true } + ], + + // Enable eslint for all supported languages "eslint.validate": [ "javascript", "javascriptreact", - "astro", "typescript", - "typescriptreact" - ], - "prettier.documentSelectors": ["**/*.astro"], - "[astro]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - } -} \ No newline at end of file + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "yaml", + "toml", + "xml", + "gql", + "graphql", + "astro", + "svelte", + "css", + "less", + "scss", + "pcss", + "postcss" + ] +} diff --git a/custom-json.d.ts b/custom-json.d.ts index d35fa33e..35d5d421 100644 --- a/custom-json.d.ts +++ b/custom-json.d.ts @@ -3,22 +3,22 @@ * Mainly to prevent code editor from loading typing from json files which is can be very heavy */ -type Question = { +interface Question { label: string; choices: string[]; multiple: boolean | null; required: boolean | null; -}; +} -type QuestionMap = { +interface QuestionMap { [key: string]: Question; -}; +} -type Results = { +interface Results { results: { [key: string]: number | number[] | string | string[] | null | undefined; }[]; -}; +} // 2020 declare module "@/results/2020/data/results.json" { diff --git a/custom-yaml.d.ts b/custom-yaml.d.ts index 7f5a1cf9..126a17a8 100644 --- a/custom-yaml.d.ts +++ b/custom-yaml.d.ts @@ -5,6 +5,7 @@ declare module "@/survey/*.yml" { import type { SurveyQuestionsYamlFile } from "@/lib/validators/survey-schema"; + const value: SurveyQuestionsYamlFile; export default value; } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..0279a272 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,24 @@ +import antfu from "@antfu/eslint-config"; + +export default antfu({ + astro: true, + react: true, + typescript: { + tsconfigPath: "tsconfig.json" + }, + stylistic: { + quotes: "double", + semi: true + } +}, { + rules: { + "unicorn/filename-case": [ + "error", + { + case: "kebabCase" + } + ], + "ts/strict-boolean-expressions": "off", + "style/comma-dangle": ["error", "never"] + } +}); diff --git a/package.json b/package.json index a9be294f..c4922d0b 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "test": "vitest", "test:ui": "vitest --ui", "test:ci": "vitest --coverage.enabled true", - "lint": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\"", - "lint:ci": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"", + "lint": "eslint --fix \"**/*.{js,jsx,ts,tsx,astro}\"", + "lint:ci": "eslint \"**/*.{js,jsx,ts,tsx,astro}\"", "validate-survey": "tsx ./scripts/validate-survey.ts", "prepare": "husky", "export-results": " tsx --env-file=.env.local ./scripts/export-results.ts", @@ -57,6 +57,8 @@ "zod": "^4.1.13" }, "devDependencies": { + "@antfu/eslint-config": "^6.2.0", + "@eslint-react/eslint-plugin": "^2.3.7", "@rollup/plugin-yaml": "^4.1.2", "@tailwindcss/typography": "^0.5.15", "@testing-library/jest-dom": "^6.5.0", @@ -66,26 +68,22 @@ "@vitejs/plugin-react": "^5.0.4", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", - "eslint": "^8.57.0", - "eslint-plugin-astro": "^0.34.0", - "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint": "^9.18.0", + "eslint-plugin-astro": "^1.2.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^7.0.0", + "eslint-plugin-react-refresh": "^0.4.16", "eslint-plugin-unicorn": "^55.0.0", "husky": "^9.1.6", "jsdom": "^25.0.0", "lint-staged": "^15.2.10", - "prettier": "^3.3.3", - "prettier-plugin-astro": "^0.14.1", "rimraf": "^6.0.1", "tsx": "^4.19.1", "vitest": "^3.2.4" }, "lint-staged": { "*.{js,jsx,ts,tsx,astro}": [ - "eslint --fix", - "prettier --write" - ], - "*.{md,mdx}": [ - "prettier --write" + "eslint --fix" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 278d1743..355080fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,6 +114,12 @@ importers: specifier: ^4.1.13 version: 4.1.13 devDependencies: + '@antfu/eslint-config': + specifier: ^6.2.0 + version: 6.2.0(@eslint-react/eslint-plugin@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(@vue/compiler-sfc@3.5.25)(astro-eslint-parser@1.2.2)(eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier-plugin-astro@0.14.1)(typescript@5.5.4)(vitest@3.2.4) + '@eslint-react/eslint-plugin': + specifier: ^2.3.7 + version: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) '@rollup/plugin-yaml': specifier: ^4.1.2 version: 4.1.2(rollup@4.52.5) @@ -131,7 +137,7 @@ importers: version: 14.5.2(@testing-library/dom@10.4.0) '@typescript-eslint/parser': specifier: ^5.62.0 - version: 5.62.0(eslint@8.57.0)(typescript@5.5.4) + version: 5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@6.4.0(@types/node@22.5.0)(jiti@2.6.1)(tsx@4.19.1)(yaml@2.8.1)) @@ -142,17 +148,23 @@ importers: specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) eslint: - specifier: ^8.57.0 - version: 8.57.0 + specifier: ^9.18.0 + version: 9.39.1(jiti@2.6.1) eslint-plugin-astro: - specifier: ^0.34.0 - version: 0.34.0(eslint@8.57.0)(typescript@5.5.4) + specifier: ^1.2.0 + version: 1.5.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: - specifier: ^6.10.0 - version: 6.10.0(eslint@8.57.0) + specifier: ^6.10.2 + version: 6.10.2(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-react-hooks: + specifier: ^7.0.0 + version: 7.0.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.16 + version: 0.4.24(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-unicorn: specifier: ^55.0.0 - version: 55.0.0(eslint@8.57.0) + version: 55.0.0(eslint@9.39.1(jiti@2.6.1)) husky: specifier: ^9.1.6 version: 9.1.6 @@ -162,12 +174,6 @@ importers: lint-staged: specifier: ^15.2.10 version: 15.2.10 - prettier: - specifier: ^3.3.3 - version: 3.3.3 - prettier-plugin-astro: - specifier: ^0.14.1 - version: 0.14.1 rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -191,9 +197,67 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@antfu/eslint-config@6.2.0': + resolution: {integrity: sha512-ksasd+mJk441HHodwPh3Nhmwo9jSHUmgQyfTxMQM05U7SjDS0fy2KnXnPx0Vhc/CqKiJnx8wGpQCCJibyASX9Q==} + hasBin: true + peerDependencies: + '@eslint-react/eslint-plugin': ^2.0.1 + '@next/eslint-plugin-next': '>=15.0.0' + '@prettier/plugin-xml': ^3.4.1 + '@unocss/eslint-plugin': '>=0.50.0' + astro-eslint-parser: ^1.0.2 + eslint: ^9.10.0 + eslint-plugin-astro: ^1.2.0 + eslint-plugin-format: '>=0.1.0' + eslint-plugin-jsx-a11y: '>=6.10.2' + eslint-plugin-react-hooks: ^7.0.0 + eslint-plugin-react-refresh: ^0.4.19 + eslint-plugin-solid: ^0.14.3 + eslint-plugin-svelte: '>=2.35.1' + eslint-plugin-vuejs-accessibility: ^2.4.1 + prettier-plugin-astro: ^0.14.0 + prettier-plugin-slidev: ^1.0.5 + svelte-eslint-parser: '>=0.37.0' + peerDependenciesMeta: + '@eslint-react/eslint-plugin': + optional: true + '@next/eslint-plugin-next': + optional: true + '@prettier/plugin-xml': + optional: true + '@unocss/eslint-plugin': + optional: true + astro-eslint-parser: + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-jsx-a11y: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + eslint-plugin-vuejs-accessibility: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-slidev: + optional: true + svelte-eslint-parser: + optional: true + '@antfu/install-pkg@0.4.1': resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} @@ -352,6 +416,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -374,6 +442,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-transform-react-jsx-self@7.27.1': resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -422,6 +495,10 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -433,6 +510,12 @@ packages: resolution: {integrity: sha512-+ntATQe1AlL7nTOYjwjj6w3299CgRot48wL761TUGYpYgAou3AaONZazp0PKZyCyWhudWsjhq1nvRHOvbMzhTA==} engines: {node: '>=18'} + '@clack/core@0.5.0': + resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} + + '@clack/prompts@0.11.0': + resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@cloudflare/workers-types@4.20251011.0': resolution: {integrity: sha512-gQpih+pbq3sP4uXltUeCSbPgZxTNp2gQd8639SaIbQMwgA6oJNHLhIART1fWy6DQACngiRzDVULA2x0ohmkGTQ==} @@ -510,6 +593,18 @@ packages: resolution: {integrity: sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==} engines: {node: '>=18.0.0'} + '@es-joy/jsdoccomment@0.50.2': + resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} + engines: {node: '>=18'} + + '@es-joy/jsdoccomment@0.76.0': + resolution: {integrity: sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==} + engines: {node: '>=20.11.0'} + + '@es-joy/resolve.exports@1.2.0': + resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} + engines: {node: '>=10'} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -948,23 +1043,99 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-plugin-eslint-comments@4.5.0': + resolution: {integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.11.1': resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-react/ast@2.3.7': + resolution: {integrity: sha512-PxAoMuk3dpcimo0rtWx9UEd0/NqNyg7pYs18kqoCuLxfLUpoV6wwwz/hIWNa3JvWu/5ALeelo5SCbUc7jTXQlQ==} + engines: {node: '>=20.19.0'} + + '@eslint-react/core@2.3.7': + resolution: {integrity: sha512-wHve68VM/WDS6ttHkUYAlHgRW+SJe9xCZB2yb0lJRAZzUM2f2vrbv1i9wKwJSmKUm4LA0Hha0oSg+XY2eWknwA==} + engines: {node: '>=20.19.0'} + + '@eslint-react/eff@2.3.7': + resolution: {integrity: sha512-RdsS0smV9WEriBkbVqCEggRy2HjTXtlqPjl5eJQGq+e4Cy8SPLS1XlbkdNW5iQI1mQwEqhkpy6Ucwt5VB6aKzA==} + engines: {node: '>=20.19.0'} + + '@eslint-react/eslint-plugin@2.3.7': + resolution: {integrity: sha512-pJUNL2JOiQ3x3OWBIdEaQeClsTqevmSJfsxXozTb3aujBaniBaD5B4XTyDNWiq2ZyPqAgKBjLBTKGXyjsaZwBA==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + '@eslint-react/shared@2.3.7': + resolution: {integrity: sha512-5QefzMEPOY8CPdNyEDlb3reXcLBLjxN1oRAOxhML//wWktYggFrG3oswKZ4NIrWwjgLSVQz1/kKd4D+82CcIMA==} + engines: {node: '>=20.19.0'} + + '@eslint-react/var@2.3.7': + resolution: {integrity: sha512-vBBWIWrkUR+J7Q08oADwPJE/iYTr+ro+DWHlReF6uwQ0s8refbefaJrbHiYV85XjqSKrUp2hHgTEBPuOf6QfGQ==} + engines: {node: '>=20.19.0'} + + '@eslint/compat@1.4.1': + resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/markdown@7.5.1': + resolution: {integrity: sha512-R8uZemG9dKTbru/DQRPblbJyXpObwKzo8rv1KYGGuPUPtjM4LXBYM9q5CIZAComzZupws3tWbDwam5AFpPLyJQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} @@ -1207,18 +1378,21 @@ packages: engines: {node: '>=6'} hasBin: true - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@iconify/tools@4.0.5': resolution: {integrity: sha512-l8KoA1lxlN/FFjlMd3vjfD7BtcX/QnFWtlBapILMlJSBgM5zhDYak/ldw/LkKG3258q/0YmXa48sO/QpxX7ptg==} @@ -1537,8 +1711,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} '@polka/url@1.0.0-next.29': @@ -1881,6 +2055,16 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sindresorhus/base62@1.0.0': + resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} + engines: {node: '>=18'} + + '@stylistic/eslint-plugin@5.6.1': + resolution: {integrity: sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -2001,6 +2185,9 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.7': resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==} @@ -2079,6 +2266,14 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.48.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@5.62.0': resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2089,14 +2284,48 @@ packages: typescript: optional: true + '@typescript-eslint/parser@8.48.0': + resolution: {integrity: sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@5.62.0': resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2106,10 +2335,27 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -2139,6 +2385,19 @@ packages: '@vitest/browser': optional: true + '@vitest/eslint-plugin@1.5.0': + resolution: {integrity: sha512-j3uuIAPTYWYnSit9lspb08/EKsxEmGqjQf+Wpb1DQkxc+mMkhL58ZknDCgjYhY4Zu76oxZ0hVWTHlmRW0mJq5w==} + engines: {node: '>=18'} + peerDependencies: + eslint: '>=8.57.0' + typescript: '>=5.0.0' + vitest: '*' + peerDependenciesMeta: + typescript: + optional: true + vitest: + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -2199,6 +2458,21 @@ packages: '@vscode/l10n@0.0.18': resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + '@vue/compiler-core@3.5.25': + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} + + '@vue/compiler-dom@3.5.25': + resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} + + '@vue/compiler-sfc@3.5.25': + resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==} + + '@vue/compiler-ssr@3.5.25': + resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==} + + '@vue/shared@3.5.25': + resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} + '@whatwg-node/disposablestack@0.0.6': resolution: {integrity: sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==} engines: {node: '>=18.0.0'} @@ -2316,6 +2590,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2325,9 +2603,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -2380,9 +2655,9 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true - astro-eslint-parser@0.17.0: - resolution: {integrity: sha512-yTgzioUI9MKgBF4LxP7YI+uuZLrnXgHDeW4dpa3VqCNbDmPzL7ix93tc0JJIwWGcskoSAAHZZVaBSp8bHyZZZA==} - engines: {node: ^14.18.0 || >=16.0.0} + astro-eslint-parser@1.2.2: + resolution: {integrity: sha512-JepyLROIad6f44uyqMF6HKE2QbunNzp3mYKRcPoDGt0QkxXmH222FAFC64WTyQu2Kg8NNEXHTN/sWuUId9sSxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} astro-icon@1.1.1: resolution: {integrity: sha512-HKBesWk2Faw/0+klLX+epQVqdTfSzZz/9+5vxXUjTJaN/HnpDf608gRPgHh7ZtwBPNJMEFoU5GLegxoDcT56OQ==} @@ -2392,9 +2667,9 @@ packages: engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true - astrojs-compiler-sync@0.3.5: - resolution: {integrity: sha512-y420rhIIJ2HHDkYeqKArBHSdJNIIGMztLH90KGIX3zjcJyt/cr9Z2wYA8CP5J1w6KE7xqMh0DAkhfjhNDpQb2Q==} - engines: {node: ^14.18.0 || >=16.0.0} + astrojs-compiler-sync@1.1.1: + resolution: {integrity: sha512-0mKvB9sDQRIZPsEJadw6OaFbGJ92cJPPR++ICca9XEyiUAZqgVuk25jNmzHPT0KF80rI94trSZrUR5iHFXGGOQ==} + engines: {node: ^18.18.0 || >=20.9.0} peerDependencies: '@astrojs/compiler': '>=0.27.0' @@ -2456,6 +2731,10 @@ packages: resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} hasBin: true + baseline-browser-mapping@2.8.31: + resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + hasBin: true + bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} @@ -2466,6 +2745,9 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + birecord@0.1.1: + resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2496,6 +2778,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -2506,6 +2793,10 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + builtin-modules@5.0.0: + resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + engines: {node: '>=18.20'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2535,6 +2826,9 @@ packages: caniuse-lite@1.0.30001751: resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2558,6 +2852,9 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2678,15 +2975,28 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} confbox@0.1.7: resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -2707,6 +3017,9 @@ packages: core-js-compat@3.38.1: resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} + core-js-compat@3.47.0: + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -2718,6 +3031,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} @@ -2820,10 +3137,6 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-equal@2.2.3: - resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} - engines: {node: '>= 0.4'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2876,6 +3189,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-sequences@27.5.1: + resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -2891,10 +3208,6 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -3029,6 +3342,9 @@ packages: electron-to-chromium@1.5.237: resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} + electron-to-chromium@1.5.260: + resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + emmet@2.4.7: resolution: {integrity: sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==} @@ -3051,10 +3367,18 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + env-paths@3.0.0: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3078,13 +3402,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-get-iterator@1.1.3: - resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - - es-iterator-helpers@1.0.19: - resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} - engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -3150,36 +3467,237 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-plugin-astro@0.34.0: - resolution: {integrity: sha512-nzw2H4g7HPXPLsWVpGUxuJ/ViVPLI8lM/AaUCJ51qTLTWtaMhvlvoe2d7yIPMFc+7xeCzQdo1POK8eR7NFsdKQ==} + eslint-compat-utils@0.6.5: + resolution: {integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@2.1.0: + resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@2.1.4: + resolution: {integrity: sha512-bEnmU5gqzS+4O+id9vrbP43vByjF+8KOs+QuuV4OlqAuXmnRW2zfI/Rza1fQvdihQ5h4DUo0NqFAiViD4mSrzQ==} + + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + + eslint-merge-processors@2.0.0: + resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==} + peerDependencies: + eslint: '*' + + eslint-plugin-antfu@3.1.1: + resolution: {integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==} + peerDependencies: + eslint: '*' + + eslint-plugin-astro@1.5.0: + resolution: {integrity: sha512-IWy4kY3DKTJxd7g652zIWpBGFuxw7NIIt16kyqc8BlhnIKvI8yGJj+Maua0DiNYED3F/D8AmzoTTTA6A95WX9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.57.0' + + eslint-plugin-command@3.3.1: + resolution: {integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==} + peerDependencies: + eslint: '*' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - eslint: '>=7.0.0' + eslint: '>=8' + + eslint-plugin-import-lite@0.3.0: + resolution: {integrity: sha512-dkNBAL6jcoCsXZsQ/Tt2yXmMDoNt5NaBh/U7yvccjiK8cai6Ay+MK77bMykmqQA2bTF6lngaLCDij6MTO3KkvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=4.5' + peerDependenciesMeta: + typescript: + optional: true + + eslint-plugin-jsdoc@61.4.1: + resolution: {integrity: sha512-3c1QW/bV25sJ1MsIvsvW+EtLtN6yZMduw7LVQNVt72y2/5BbV5Pg5b//TE5T48LRUxoEQGaZJejCmcj3wCxBzw==} + engines: {node: '>=20.11.0'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.21.0: + resolution: {integrity: sha512-HttlxdNG5ly3YjP1cFMP62R4qKLxJURfBZo2gnMY+yQojZxkLyOpY1H1KRTKBmvQeSG9pIpSGEhDjE17vvYosg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' - eslint-plugin-jsx-a11y@6.10.0: - resolution: {integrity: sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==} + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-n@17.23.1: + resolution: {integrity: sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@4.15.1: + resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + eslint: '>=8.45.0' + + eslint-plugin-pnpm@1.3.0: + resolution: {integrity: sha512-Lkdnj3afoeUIkDUu8X74z60nrzjQ2U55EbOeI+qz7H1He4IO4gmUKT2KQIl0It52iMHJeuyLDWWNgjr6UIK8nw==} + peerDependencies: + eslint: ^9.0.0 + + eslint-plugin-react-dom@2.3.7: + resolution: {integrity: sha512-T8r1oKTEh42LUA3VB8J6EXGoX2m3TFCGDp7ehcyoNoJNf+o8BdWI4zg62euR6t/CwEUrvxF2wdADGxXH4+J5XA==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + eslint-plugin-react-hooks-extra@2.3.7: + resolution: {integrity: sha512-IqVbpbRcsZ16AXP3wuePBj3nuPnZF2MPapdlg2ygfNKNQehlSVAYHZHzzuSjhI+4UR/w+AhuhQfcanz14DN/+A==} + engines: {node: '>=20.0.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-naming-convention@2.3.7: + resolution: {integrity: sha512-pW0l0kY8Wf42g3R/D8DVVibRc7BvU49VFshKCYQ4DLA53FPJsdOd9b28aKyyvgDAh65HDFEJKLvp03C+OkY7Fg==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + eslint-plugin-react-refresh@0.4.24: + resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} + peerDependencies: + eslint: '>=8.40' + + eslint-plugin-react-web-api@2.3.7: + resolution: {integrity: sha512-DSosa2MGofBzaEdPaqXUwskk91NNa2Igw7bYNQ83eRd1zjLHictuSmboc8HFkufBF50G9Mhovn0QgGF3r//s6g==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + eslint-plugin-react-x@2.3.7: + resolution: {integrity: sha512-NDFRTSSDUp3uvmQA/Khm6xfxG4Fbc7/fDX2M5TgbqbKsN+kmu9T1J4jr/5AoWe4DU82ECl7RrWQUHZ49ahVlfQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^9.39.1 + typescript: ^5.9.3 + + eslint-plugin-regexp@2.10.0: + resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8.44.0' + + eslint-plugin-toml@0.12.0: + resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + eslint-plugin-unicorn@55.0.0: resolution: {integrity: sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==} engines: {node: '>=18.18'} peerDependencies: eslint: '>=8.56.0' - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-plugin-unicorn@62.0.0: + resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} + engines: {node: ^20.10.0 || >=21.0.0} + peerDependencies: + eslint: '>=9.38.0' + + eslint-plugin-unused-imports@4.3.0: + resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-plugin-vue@10.6.0: + resolution: {integrity: sha512-TsoFluWxOpsJlE/l2jJygLQLWBPJ3Qdkesv7tBIunICbTcG0dS1/NBw/Ol4tJw5kHWlAVds4lUmC29/vlPUcEQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + peerDependenciesMeta: + '@stylistic/eslint-plugin': + optional: true + '@typescript-eslint/parser': + optional: true + + eslint-plugin-yml@1.19.0: + resolution: {integrity: sha512-S+4GbcCWksFKAvFJtf0vpdiCkZZvDJCV4Zsi9ahmYkYOYcf+LRqqzvzkb/ST7vTYV6sFwXOvawzYyL/jFT2nQA==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-processor-vue-blocks@2.0.0: + resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: '>=9.0.0' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} @@ -3240,6 +3758,9 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3259,6 +3780,10 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3275,6 +3800,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + faye-websocket@0.11.4: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} engines: {node: '>=0.8.0'} @@ -3298,9 +3826,9 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -3316,6 +3844,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -3331,12 +3863,9 @@ packages: firebase@10.13.0: resolution: {integrity: sha512-a8gm8c9CYO98QuXJn7m5W5Gj7kHV8fme81/mQ9dBs+VMz9uI5HdavnMVPXCILputpZFMFpiKK+u7VVsn5lQg+w==} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -3375,6 +3904,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -3474,14 +4007,22 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} globals@15.9.0: resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} engines: {node: '>=18'} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -3490,6 +4031,9 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + google-auth-library@9.14.1: resolution: {integrity: sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==} engines: {node: '>=14'} @@ -3580,6 +4124,12 @@ packages: hastscript@8.0.0: resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -3593,6 +4143,9 @@ packages: html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3650,6 +4203,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-size@2.0.2: resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} @@ -3670,6 +4227,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inflight@1.0.6: resolution: {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. @@ -3696,10 +4257,6 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -3707,10 +4264,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} - is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -3726,6 +4279,10 @@ packages: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} + is-builtin-module@5.0.0: + resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} + engines: {node: '>=18.20'} + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -3754,9 +4311,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -3769,10 +4323,6 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -3780,15 +4330,17 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-immutable-type@5.0.1: + resolution: {integrity: sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg==} + peerDependencies: + eslint: '*' + typescript: '>=4.7.4' + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} hasBin: true - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -3801,10 +4353,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -3816,10 +4364,6 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} @@ -3844,17 +4388,9 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} - engines: {node: '>= 0.4'} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -3881,9 +4417,6 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -3921,6 +4454,18 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + jsdoc-type-pratt-parser@4.8.0: + resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} + engines: {node: '>=12.0.0'} + + jsdoc-type-pratt-parser@6.10.0: + resolution: {integrity: sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==} + engines: {node: '>=20.0.0'} + jsdom@25.0.0: resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==} engines: {node: '>=18'} @@ -3972,6 +4517,10 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-eslint-parser@2.4.1: + resolution: {integrity: sha512-uuPNLJkKN8NXAlZlQ6kmUF9qO+T6Kyd7oV4+/7yy8Jz6+MZNyhPq8EdLpdfnPVzUC8qSf1b4j1azKaGnFsjmsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + jsonc-parser@2.3.1: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} @@ -4070,6 +4619,10 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4160,6 +4713,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -4186,6 +4742,12 @@ packages: mdast-util-from-markdown@2.0.1: resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -4204,6 +4766,9 @@ packages: mdast-util-gfm@3.0.0: resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + mdast-util-mdx-expression@2.0.0: resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} @@ -4250,6 +4815,9 @@ packages: micromark-core-commonmark@2.0.1: resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -4334,6 +4902,9 @@ packages: micromark-util-normalize-identifier@2.0.0: resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + micromark-util-resolve-all@2.0.0: resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} @@ -4424,6 +4995,9 @@ packages: mlly@1.7.1: resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -4458,6 +5032,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + neotraverse@0.6.18: resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} engines: {node: '>= 10'} @@ -4503,6 +5081,9 @@ packages: node-releases@2.0.25: resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + nopt@8.1.0: resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4533,6 +5114,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-deep-merge@2.0.0: + resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} + object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} @@ -4541,10 +5125,6 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -4643,6 +5223,9 @@ packages: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -4650,6 +5233,9 @@ packages: parse-latin@7.0.0: resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} @@ -4740,10 +5326,19 @@ packages: pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + pnpm-workspace-yaml@1.3.0: + resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -4786,6 +5381,10 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -4860,6 +5459,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-string@9.1.0: resolution: {integrity: sha512-t6dqMECpCkqfyv2FfwVS1xcB6lgXW/0XZSaKdsCNGYkqMO76AFiJEg4vINzoDKcZa6MS7JX+OHIjwh06K5vczw==} engines: {node: '>=18'} @@ -4957,9 +5559,9 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - reflect.getprototypeof@1.0.6: - resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} - engines: {node: '>= 0.4'} + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -4973,6 +5575,10 @@ packages: regex@6.0.1: resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -4985,6 +5591,10 @@ packages: resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} hasBin: true + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + hasBin: true + rehype-parse@9.0.0: resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} @@ -5039,6 +5649,10 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -5088,11 +5702,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rimraf@6.0.1: resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} engines: {node: 20 || >=22} @@ -5136,6 +5745,10 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -5242,6 +5855,9 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + spdx-license-ids@3.0.20: resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} @@ -5255,10 +5871,6 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} - engines: {node: '>= 0.4'} - stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} @@ -5269,6 +5881,9 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-ts@2.2.1: + resolution: {integrity: sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -5281,8 +5896,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string.prototype.includes@2.0.0: - resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.trim@1.2.9: resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} @@ -5317,6 +5933,10 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -5367,8 +5987,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - synckit@0.9.1: - resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==} + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} tailwind-merge@2.5.2: @@ -5384,6 +6004,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -5400,9 +6024,6 @@ packages: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -5456,6 +6077,14 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + to-valid-identifier@1.0.0: + resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} + engines: {node: '>=20'} + + toml-eslint-parser@0.10.0: + resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + tosource@2.0.0-alpha.3: resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==} engines: {node: '>=10'} @@ -5481,6 +6110,17 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-declaration-location@1.0.7: + resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} + peerDependencies: + typescript: '>=4.0.0' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -5498,6 +6138,9 @@ packages: '@swc/wasm': optional: true + ts-pattern@5.9.0: + resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -5532,10 +6175,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -5721,6 +6360,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -6042,6 +6687,12 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + vue-eslint-parser@10.2.0: + resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -6086,14 +6737,6 @@ packages: which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - which-pm-runs@1.1.0: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} @@ -6151,6 +6794,10 @@ packages: utf-8-validate: optional: true + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -6178,6 +6825,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-eslint-parser@1.3.1: + resolution: {integrity: sha512-MdSgP9YA9QjtAO2+lt4O7V2bnH22LPnfeVLiQqjY3cOyn8dy/Ief8otjIe6SPPTK03nM7O3Yl0LTfWuF7l+9yw==} + engines: {node: ^14.17.0 || >=16.0.0} + yaml-language-server@1.15.0: resolution: {integrity: sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==} hasBin: true @@ -6242,6 +6893,12 @@ packages: typescript: ^4.9.4 || ^5.0.2 zod: ^3 + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -6262,11 +6919,70 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 + '@antfu/eslint-config@6.2.0(@eslint-react/eslint-plugin@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(@vue/compiler-sfc@3.5.25)(astro-eslint-parser@1.2.2)(eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier-plugin-astro@0.14.1)(typescript@5.5.4)(vitest@3.2.4)': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@clack/prompts': 0.11.0 + '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint/markdown': 7.5.1 + '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@vitest/eslint-plugin': 1.5.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)(vitest@3.2.4) + ansis: 4.2.0 + cac: 6.7.14 + eslint: 9.39.1(jiti@2.6.1) + eslint-config-flat-gitignore: 2.1.0(eslint@9.39.1(jiti@2.6.1)) + eslint-flat-config-utils: 2.1.4 + eslint-merge-processors: 2.0.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-antfu: 3.1.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-command: 3.3.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import-lite: 0.3.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-jsdoc: 61.4.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-jsonc: 2.21.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-n: 17.23.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 4.15.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-pnpm: 1.3.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-regexp: 2.10.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-toml: 0.12.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-unicorn: 62.0.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-vue: 10.6.0(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) + eslint-plugin-yml: 1.19.0(eslint@9.39.1(jiti@2.6.1)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1)) + globals: 16.5.0 + jsonc-eslint-parser: 2.4.1 + local-pkg: 1.1.2 + parse-gitignore: 2.0.0 + toml-eslint-parser: 0.10.0 + vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) + yaml-eslint-parser: 1.3.1 + optionalDependencies: + '@eslint-react/eslint-plugin': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + astro-eslint-parser: 1.2.2 + eslint-plugin-astro: 1.5.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-react-refresh: 0.4.24(eslint@9.39.1(jiti@2.6.1)) + prettier-plugin-astro: 0.14.1 + transitivePeerDependencies: + - '@eslint/json' + - '@vue/compiler-sfc' + - supports-color + - typescript + - vitest + '@antfu/install-pkg@0.4.1': dependencies: package-manager-detector: 0.2.0 tinyexec: 0.3.0 + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.4.1 + tinyexec: 1.0.1 + '@antfu/utils@0.7.10': {} '@astro-community/astro-embed-youtube@0.5.3(astro@5.14.5(@netlify/blobs@10.1.0)(@types/node@22.5.0)(jiti@2.6.1)(rollup@4.52.5)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.8.1))': @@ -6579,6 +7295,8 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.28.4': @@ -6601,6 +7319,10 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -6670,6 +7392,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} '@bundled-es-modules/message-format@6.2.4': {} @@ -6678,6 +7405,17 @@ snapshots: dependencies: fontkit: 2.0.4 + '@clack/core@0.5.0': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.11.0': + dependencies: + '@clack/core': 0.5.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@cloudflare/workers-types@4.20251011.0': optional: true @@ -6784,6 +7522,24 @@ snapshots: tslib: 2.8.1 optional: true + '@es-joy/jsdoccomment@0.50.2': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.48.0 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@es-joy/jsdoccomment@0.76.0': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.48.0 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 6.10.0 + + '@es-joy/resolve.exports@1.2.0': {} + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -7003,19 +7759,127 @@ snapshots: '@esbuild/win32-x64@0.25.11': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.1(jiti@2.6.1))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.39.1(jiti@2.6.1) + ignore: 5.3.2 + + '@eslint-community/eslint-utils@4.4.0(eslint@9.39.1(jiti@2.6.1))': dependencies: - eslint: 8.57.0 + eslint: 9.39.1(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': + dependencies: + eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.1': {} - '@eslint/eslintrc@2.1.4': + '@eslint-community/regexpp@4.12.2': {} + + '@eslint-react/ast@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-react/eff': 2.3.7 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + string-ts: 2.2.1 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + '@eslint-react/core@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + birecord: 0.1.1 + ts-pattern: 5.9.0 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + '@eslint-react/eff@2.3.7': {} + + '@eslint-react/eslint-plugin@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + eslint-plugin-react-dom: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-react-hooks-extra: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-react-naming-convention: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-react-web-api: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint-plugin-react-x: 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + ts-api-utils: 2.1.0(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@eslint-react/shared@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-react/eff': 2.3.7 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + ts-pattern: 5.9.0 + zod: 4.1.13 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + '@eslint-react/var@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + ts-pattern: 5.9.0 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + '@eslint/compat@1.4.1(eslint@9.39.1(jiti@2.6.1))': + dependencies: + '@eslint/core': 0.17.0 + optionalDependencies: + eslint: 9.39.1(jiti@2.6.1) + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.3.6 - espree: 9.6.1 - globals: 13.24.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -7024,7 +7888,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@9.39.1': {} + + '@eslint/markdown@7.5.1': + dependencies: + '@eslint/core': 0.17.0 + '@eslint/plugin-kit': 0.4.1 + github-slugger: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-frontmatter: 2.0.1 + mdast-util-gfm: 3.1.0 + micromark-extension-frontmatter: 2.0.0 + micromark-extension-gfm: 3.0.0 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 '@fastify/busboy@2.1.1': {} @@ -7421,17 +8306,16 @@ snapshots: protobufjs: 7.4.0 yargs: 17.7.2 - '@humanwhocodes/config-array@0.11.14': + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.4.3': {} '@iconify/tools@4.0.5': dependencies: @@ -7802,7 +8686,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.1.1': {} + '@pkgr/core@0.2.9': {} '@polka/url@1.0.0-next.29': {} @@ -8075,6 +8959,18 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sindresorhus/base62@1.0.0': {} + + '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1))': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/types': 8.48.0 + eslint: 9.39.1(jiti@2.6.1) + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.3 + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -8222,6 +9118,8 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.7': dependencies: '@types/node': 22.5.0 @@ -8312,25 +9210,86 @@ snapshots: '@types/node': 22.5.0 optional: true - '@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-community/regexpp': 4.11.1 + '@typescript-eslint/parser': 5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.48.0 + eslint: 9.39.1(jiti@2.6.1) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.4) debug: 4.3.6 - eslint: 8.57.0 + eslint: 9.39.1(jiti@2.6.1) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + eslint: 9.39.1(jiti@2.6.1) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.48.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + debug: 4.4.3 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 + '@typescript-eslint/scope-manager@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.5.4)': + dependencies: + typescript: 5.5.4 + + '@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + debug: 4.4.3 + eslint: 9.39.1(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@5.62.0': {} + '@typescript-eslint/types@8.48.0': {} + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.4)': dependencies: '@typescript-eslint/types': 5.62.0 @@ -8345,11 +9304,42 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/project-service': 8.48.0(typescript@5.5.4) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.48.0': + dependencies: + '@typescript-eslint/types': 8.48.0 + eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.2.0': {} '@vercel/nft@0.27.10(rollup@4.52.5)': @@ -8414,6 +9404,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/eslint-plugin@1.5.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4)(vitest@3.2.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + optionalDependencies: + typescript: 5.5.4 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.5.0)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@25.0.0)(tsx@4.19.1)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -8517,6 +9518,38 @@ snapshots: '@vscode/l10n@0.0.18': {} + '@vue/compiler-core@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.25 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.25': + dependencies: + '@vue/compiler-core': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/compiler-sfc@3.5.25': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.25 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-ssr': 3.5.25 + '@vue/shared': 3.5.25 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.25': + dependencies: + '@vue/compiler-dom': 3.5.25 + '@vue/shared': 3.5.25 + + '@vue/shared@3.5.25': {} + '@whatwg-node/disposablestack@0.0.6': dependencies: '@whatwg-node/promise-helpers': 1.3.2 @@ -8640,8 +9673,7 @@ snapshots: ansi-styles@6.2.1: {} - ansis@4.2.0: - optional: true + ansis@4.2.0: {} any-promise@1.3.0: {} @@ -8650,6 +9682,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + are-docs-informative@0.0.2: {} + arg@4.1.3: optional: true @@ -8657,10 +9691,6 @@ snapshots: argparse@2.0.1: {} - aria-query@5.1.3: - dependencies: - deep-equal: 2.2.3 - aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -8725,23 +9755,22 @@ snapshots: astring@1.9.0: {} - astro-eslint-parser@0.17.0(typescript@5.5.4): + astro-eslint-parser@1.2.2: dependencies: - '@astrojs/compiler': 2.10.3 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.4) - astrojs-compiler-sync: 0.3.5(@astrojs/compiler@2.10.3) - debug: 4.3.6 - entities: 4.5.0 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - globby: 11.1.0 + '@astrojs/compiler': 2.13.0 + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + astrojs-compiler-sync: 1.1.1(@astrojs/compiler@2.13.0) + debug: 4.4.3 + entities: 6.0.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + fast-glob: 3.3.3 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - - typescript astro-icon@1.1.1: dependencies: @@ -8853,10 +9882,10 @@ snapshots: - uploadthing - yaml - astrojs-compiler-sync@0.3.5(@astrojs/compiler@2.10.3): + astrojs-compiler-sync@1.1.1(@astrojs/compiler@2.13.0): dependencies: - '@astrojs/compiler': 2.10.3 - synckit: 0.9.1 + '@astrojs/compiler': 2.13.0 + synckit: 0.11.11 async-retry@1.3.3: dependencies: @@ -8913,6 +9942,8 @@ snapshots: baseline-browser-mapping@2.8.18: {} + baseline-browser-mapping@2.8.31: {} + bignumber.js@9.1.2: optional: true @@ -8922,6 +9953,8 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 + birecord@0.1.1: {} + boolbase@1.0.0: {} boxen@8.0.1: @@ -8967,12 +10000,22 @@ snapshots: node-releases: 2.0.25 update-browserslist-db: 1.1.3(browserslist@4.26.3) + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.31 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.260 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-crc32@0.2.13: {} buffer-equal-constant-time@1.0.1: {} builtin-modules@3.3.0: {} + builtin-modules@5.0.0: {} + cac@6.7.14: {} call-bind@1.0.7: @@ -8996,6 +10039,8 @@ snapshots: caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001757: {} + ccount@2.0.1: {} chai@5.3.3: @@ -9024,6 +10069,8 @@ snapshots: chalk@5.3.0: {} + change-case@5.4.4: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -9140,12 +10187,20 @@ snapshots: commander@7.2.0: {} + comment-parser@1.4.1: {} + common-ancestor-path@1.0.1: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} confbox@0.1.7: {} + confbox@0.1.8: {} + + confbox@0.2.2: {} + consola@3.4.2: {} convert-source-map@1.9.0: {} @@ -9160,6 +10215,10 @@ snapshots: dependencies: browserslist: 4.23.3 + core-js-compat@3.47.0: + dependencies: + browserslist: 4.28.0 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -9177,6 +10236,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + crossws@0.3.5: dependencies: uncrypto: 0.1.3 @@ -9272,27 +10337,6 @@ snapshots: deep-eql@5.0.2: {} - deep-equal@2.2.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.4 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - isarray: 2.0.5 - object-is: 1.1.6 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - deep-is@0.1.4: {} define-data-property@1.1.4: @@ -9336,6 +10380,8 @@ snapshots: didyoumean@1.2.2: {} + diff-sequences@27.5.1: {} + diff@4.0.2: optional: true @@ -9347,10 +10393,6 @@ snapshots: dlv@1.1.3: {} - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -9409,6 +10451,8 @@ snapshots: electron-to-chromium@1.5.237: {} + electron-to-chromium@1.5.260: {} + emmet@2.4.7: dependencies: '@emmetio/abbreviation': 2.3.3 @@ -9420,8 +10464,7 @@ snapshots: emoji-regex@9.2.2: {} - empathic@2.0.0: - optional: true + empathic@2.0.0: {} encoding-sniffer@0.2.0: dependencies: @@ -9432,8 +10475,15 @@ snapshots: dependencies: once: 1.4.0 + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@4.5.0: {} + entities@6.0.1: {} + env-paths@3.0.0: optional: true @@ -9498,35 +10548,6 @@ snapshots: es-errors@1.3.0: {} - es-get-iterator@1.1.3: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - is-arguments: 1.1.1 - is-map: 2.0.3 - is-set: 2.0.3 - is-string: 1.0.7 - isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 - - es-iterator-helpers@1.0.19: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - iterator.prototype: 1.1.2 - safe-array-concat: 1.1.2 - es-module-lexer@1.7.0: {} es-object-atoms@1.0.0: @@ -9655,29 +10676,111 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@8.57.0): + eslint-compat-utils@0.5.1(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 8.57.0 + eslint: 9.39.1(jiti@2.6.1) semver: 7.6.3 - eslint-plugin-astro@0.34.0(eslint@8.57.0)(typescript@5.5.4): + eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@jridgewell/sourcemap-codec': 1.5.0 - '@typescript-eslint/types': 5.62.0 - astro-eslint-parser: 0.17.0(typescript@5.5.4) - eslint: 8.57.0 - eslint-compat-utils: 0.5.1(eslint@8.57.0) - globals: 13.24.0 - postcss: 8.4.41 - postcss-selector-parser: 6.1.2 + eslint: 9.39.1(jiti@2.6.1) + semver: 7.7.3 + + eslint-config-flat-gitignore@2.1.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@eslint/compat': 1.4.1(eslint@9.39.1(jiti@2.6.1)) + eslint: 9.39.1(jiti@2.6.1) + + eslint-flat-config-utils@2.1.4: + dependencies: + pathe: 2.0.3 + + eslint-json-compat-utils@0.2.1(eslint@9.39.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.1): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.1 + + eslint-merge-processors@2.0.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + + eslint-plugin-antfu@3.1.1(eslint@9.39.1(jiti@2.6.1)): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + + eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@jridgewell/sourcemap-codec': 1.5.5 + '@typescript-eslint/types': 8.48.0 + astro-eslint-parser: 1.2.2 + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@2.6.1)) + globals: 16.5.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-command@3.3.1(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@es-joy/jsdoccomment': 0.50.2 + eslint: 9.39.1(jiti@2.6.1) + + eslint-plugin-es-x@7.8.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/regexpp': 4.11.1 + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.5.1(eslint@9.39.1(jiti@2.6.1)) + + eslint-plugin-import-lite@0.3.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/types': 8.48.0 + eslint: 9.39.1(jiti@2.6.1) + optionalDependencies: + typescript: 5.5.4 + + eslint-plugin-jsdoc@61.4.1(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@es-joy/jsdoccomment': 0.76.0 + '@es-joy/resolve.exports': 1.2.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint: 9.39.1(jiti@2.6.1) + espree: 10.4.0 + esquery: 1.6.0 + html-entities: 2.6.0 + object-deep-merge: 2.0.0 + parse-imports-exports: 0.2.4 + semver: 7.7.3 + spdx-expression-parse: 4.0.0 + to-valid-identifier: 1.0.0 transitivePeerDependencies: - supports-color - - typescript - eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.0): + eslint-plugin-jsonc@2.21.0(eslint@9.39.1(jiti@2.6.1)): dependencies: - aria-query: 5.1.3 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + diff-sequences: 27.5.1 + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@2.6.1)) + eslint-json-compat-utils: 0.2.1(eslint@9.39.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.1) + espree: 9.6.1 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.1 + natural-compare: 1.4.0 + synckit: 0.11.11 + transitivePeerDependencies: + - '@eslint/json' + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)): + dependencies: + aria-query: 5.3.2 array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 ast-types-flow: 0.0.8 @@ -9685,24 +10788,188 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - es-iterator-helpers: 1.0.19 - eslint: 8.57.0 + eslint: 9.39.1(jiti@2.6.1) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 minimatch: 3.1.2 object.fromentries: 2.0.8 safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-n@17.23.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + enhanced-resolve: 5.18.3 + eslint: 9.39.1(jiti@2.6.1) + eslint-plugin-es-x: 7.8.0(eslint@9.39.1(jiti@2.6.1)) + get-tsconfig: 4.8.1 + globals: 15.15.0 + globrex: 0.1.2 + ignore: 5.3.2 + semver: 7.7.3 + ts-declaration-location: 1.0.7(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + eslint-plugin-no-only-tests@3.3.0: {} + + eslint-plugin-perfectionist@4.15.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-pnpm@1.3.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + empathic: 2.0.0 + eslint: 9.39.1(jiti@2.6.1) + jsonc-eslint-parser: 2.4.1 + pathe: 2.0.3 + pnpm-workspace-yaml: 1.3.0 + tinyglobby: 0.2.15 + yaml-eslint-parser: 1.3.1 + + eslint-plugin-react-dom@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/core': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + compare-versions: 6.1.1 + eslint: 9.39.1(jiti@2.6.1) + string-ts: 2.2.1 + ts-pattern: 5.9.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-hooks-extra@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/core': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + string-ts: 2.2.1 + ts-pattern: 5.9.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@babel/core': 7.28.4 + '@babel/parser': 7.28.5 + eslint: 9.39.1(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.1.13 + zod-validation-error: 4.0.2(zod@4.1.13) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-naming-convention@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/core': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + string-ts: 2.2.1 + ts-pattern: 5.9.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + + eslint-plugin-react-web-api@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/core': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + string-ts: 2.2.1 + ts-pattern: 5.9.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-x@2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@eslint-react/ast': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/core': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/eff': 2.3.7 + '@eslint-react/shared': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@eslint-react/var': 2.3.7(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + compare-versions: 6.1.1 + eslint: 9.39.1(jiti@2.6.1) + is-immutable-type: 5.0.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + string-ts: 2.2.1 + ts-api-utils: 2.1.0(typescript@5.5.4) + ts-pattern: 5.9.0 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + eslint-plugin-regexp@2.10.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/regexpp': 4.11.1 + comment-parser: 1.4.1 + eslint: 9.39.1(jiti@2.6.1) + jsdoc-type-pratt-parser: 4.8.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-toml@0.12.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + debug: 4.4.3 + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@2.6.1)) + lodash: 4.17.21 + toml-eslint-parser: 0.10.0 + transitivePeerDependencies: + - supports-color - eslint-plugin-unicorn@55.0.0(eslint@8.57.0): + eslint-plugin-unicorn@55.0.0(eslint@9.39.1(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.24.7 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.39.1(jiti@2.6.1)) ci-info: 4.0.0 clean-regexp: 1.0.0 core-js-compat: 3.38.1 - eslint: 8.57.0 + eslint: 9.39.1(jiti@2.6.1) esquery: 1.6.0 globals: 15.9.0 indent-string: 4.0.0 @@ -9715,56 +10982,121 @@ snapshots: semver: 7.6.3 strip-indent: 3.0.0 - eslint-scope@7.2.2: + eslint-plugin-unicorn@62.0.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint/plugin-kit': 0.4.1 + change-case: 5.4.4 + ci-info: 4.3.1 + clean-regexp: 1.0.0 + core-js-compat: 3.47.0 + eslint: 9.39.1(jiti@2.6.1) + esquery: 1.6.0 + find-up-simple: 1.0.1 + globals: 16.5.0 + indent-string: 5.0.0 + is-builtin-module: 5.0.0 + jsesc: 3.1.0 + pluralize: 8.0.0 + regexp-tree: 0.1.27 + regjsparser: 0.13.0 + semver: 7.7.3 + strip-indent: 4.1.1 + + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1)): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@5.62.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + + eslint-plugin-vue@10.6.0(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.39.1(jiti@2.6.1)) + eslint: 9.39.1(jiti@2.6.1) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 7.1.0 + semver: 7.7.3 + vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) + xml-name-validator: 4.0.0 + optionalDependencies: + '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + + eslint-plugin-yml@1.19.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + debug: 4.4.3 + diff-sequences: 27.5.1 + escape-string-regexp: 4.0.0 + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@2.6.1)) + natural-compare: 1.4.0 + yaml-eslint-parser: 1.3.1 + transitivePeerDependencies: + - supports-color + + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@vue/compiler-sfc': 3.5.25 + eslint: 9.39.1(jiti@2.6.1) + + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.11.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.6 - doctrine: 3.0.0 + cross-spawn: 7.0.6 + debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + espree@9.6.1: dependencies: acorn: 8.12.1 @@ -9837,6 +11169,8 @@ snapshots: expect-type@1.2.2: {} + exsolve@1.0.8: {} + extend@3.0.2: {} extract-zip@2.0.1: @@ -9861,6 +11195,14 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -9876,6 +11218,10 @@ snapshots: dependencies: reusify: 1.0.4 + fault@2.0.1: + dependencies: + format: 0.2.2 + faye-websocket@0.11.4: dependencies: websocket-driver: 0.7.4 @@ -9899,9 +11245,9 @@ snapshots: fflate@0.8.2: {} - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 file-uri-to-path@1.0.0: {} @@ -9913,6 +11259,8 @@ snapshots: find-root@1.1.0: {} + find-up-simple@1.0.1: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -9973,13 +11321,10 @@ snapshots: transitivePeerDependencies: - '@react-native-async-storage/async-storage' - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.3.1: {} flatted@3.3.3: {} @@ -10026,6 +11371,8 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + format@0.2.2: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -10145,12 +11492,14 @@ snapshots: globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} + + globals@15.15.0: {} globals@15.9.0: {} + globals@16.5.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -10165,6 +11514,8 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + globrex@0.1.2: {} + google-auth-library@9.14.1: dependencies: base64-js: 1.5.1 @@ -10376,6 +11727,12 @@ snapshots: property-information: 6.5.0 space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -10389,6 +11746,8 @@ snapshots: html-entities@2.5.2: optional: true + html-entities@2.6.0: {} + html-escaper@2.0.2: {} html-escaper@3.0.3: {} @@ -10451,6 +11810,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + image-size@2.0.2: optional: true @@ -10465,6 +11826,8 @@ snapshots: indent-string@4.0.0: {} + indent-string@5.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -10491,11 +11854,6 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - is-arguments@1.1.1: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 @@ -10503,10 +11861,6 @@ snapshots: is-arrayish@0.2.1: {} - is-async-function@2.0.0: - dependencies: - has-tostringtag: 1.0.2 - is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 @@ -10524,6 +11878,10 @@ snapshots: dependencies: builtin-modules: 3.3.0 + is-builtin-module@5.0.0: + dependencies: + builtin-modules: 5.0.0 + is-callable@1.2.7: {} is-core-module@2.15.1: @@ -10544,10 +11902,6 @@ snapshots: is-extglob@2.1.1: {} - is-finalizationregistry@1.0.2: - dependencies: - call-bind: 1.0.7 - is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@4.0.0: {} @@ -10556,22 +11910,26 @@ snapshots: dependencies: get-east-asian-width: 1.2.0 - is-generator-function@1.0.10: - dependencies: - has-tostringtag: 1.0.2 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-hexadecimal@2.0.1: {} + is-immutable-type@5.0.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4): + dependencies: + '@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.5.4) + eslint: 9.39.1(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.5.4) + ts-declaration-location: 1.0.7(typescript@5.5.4) + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 - is-map@2.0.3: {} - is-negative-zero@2.0.3: {} is-number-object@1.0.7: @@ -10580,8 +11938,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-plain-obj@4.1.0: {} is-potential-custom-element-name@1.0.1: {} @@ -10591,8 +11947,6 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 - is-set@2.0.3: {} - is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 @@ -10614,17 +11968,10 @@ snapshots: dependencies: which-typed-array: 1.1.15 - is-weakmap@2.0.2: {} - is-weakref@1.0.2: dependencies: call-bind: 1.0.7 - is-weakset@2.0.3: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -10654,14 +12001,6 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - iterator.prototype@1.1.2: - dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.6 - set-function-name: 2.0.2 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -10699,6 +12038,12 @@ snapshots: dependencies: argparse: 2.0.1 + jsdoc-type-pratt-parser@4.1.0: {} + + jsdoc-type-pratt-parser@4.8.0: {} + + jsdoc-type-pratt-parser@6.10.0: {} + jsdom@25.0.0: dependencies: cssstyle: 4.1.0 @@ -10752,6 +12097,13 @@ snapshots: json5@2.2.3: {} + jsonc-eslint-parser@2.4.1: + dependencies: + acorn: 8.15.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.7.3 + jsonc-parser@2.3.1: {} jsonc-parser@3.3.1: {} @@ -10902,6 +12254,12 @@ snapshots: mlly: 1.7.1 pkg-types: 1.2.0 + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -10979,6 +12337,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: dependencies: '@babel/parser': 7.25.4 @@ -11026,6 +12388,34 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.0 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-gfm-autolink-literal@2.0.1: dependencies: '@types/mdast': 4.0.4 @@ -11083,6 +12473,18 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + mdast-util-mdx-expression@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -11195,6 +12597,13 @@ snapshots: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.0 @@ -11397,6 +12806,10 @@ snapshots: dependencies: micromark-util-symbol: 2.0.0 + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-resolve-all@2.0.0: dependencies: micromark-util-types: 2.0.0 @@ -11500,6 +12913,13 @@ snapshots: pkg-types: 1.2.0 ufo: 1.5.4 + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + mrmime@2.0.1: {} ms@2.1.2: {} @@ -11522,6 +12942,8 @@ snapshots: natural-compare@1.4.0: {} + natural-orderby@5.0.0: {} + neotraverse@0.6.18: {} nlcst-to-string@4.0.0: @@ -11552,6 +12974,8 @@ snapshots: node-releases@2.0.25: {} + node-releases@2.0.27: {} + nopt@8.1.0: dependencies: abbrev: 3.0.1 @@ -11579,15 +13003,12 @@ snapshots: object-assign@4.1.1: {} + object-deep-merge@2.0.0: {} + object-hash@3.0.0: {} object-inspect@1.13.2: {} - object-is@1.1.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - object-keys@1.1.1: {} object.assign@4.1.5: @@ -11699,8 +13120,11 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - parse-gitignore@2.0.0: - optional: true + parse-gitignore@2.0.0: {} + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 parse-json@5.2.0: dependencies: @@ -11718,6 +13142,8 @@ snapshots: unist-util-visit-children: 3.0.0 vfile: 6.0.3 + parse-statements@1.0.11: {} + parse5-htmlparser2-tree-adapter@7.0.0: dependencies: domhandler: 5.0.3 @@ -11785,8 +13211,24 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + pluralize@8.0.0: {} + pnpm-workspace-yaml@1.3.0: + dependencies: + yaml: 2.8.1 + possible-typed-array-names@1.0.0: {} postcss-import@15.1.0(postcss@8.4.41): @@ -11832,6 +13274,11 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-value-parser@4.2.0: {} postcss@8.4.41: @@ -11850,14 +13297,16 @@ snapshots: prettier-plugin-astro@0.14.1: dependencies: - '@astrojs/compiler': 2.10.3 + '@astrojs/compiler': 2.13.0 prettier: 3.3.3 sass-formatter: 0.7.9 + optional: true prettier@2.8.7: optional: true - prettier@3.3.3: {} + prettier@3.3.3: + optional: true pretty-format@27.5.1: dependencies: @@ -11915,6 +13364,8 @@ snapshots: punycode@2.3.1: {} + quansync@0.2.11: {} + query-string@9.1.0: dependencies: decode-uri-component: 0.4.1 @@ -12041,15 +13492,9 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - reflect.getprototypeof@1.0.6: + refa@0.12.1: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - which-builtin-type: 1.1.4 + '@eslint-community/regexpp': 4.11.1 regenerator-runtime@0.14.1: {} @@ -12063,6 +13508,11 @@ snapshots: dependencies: regex-utilities: 2.3.0 + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.11.1 + refa: 0.12.1 + regexp-tree@0.1.27: {} regexp.prototype.flags@1.5.2: @@ -12076,6 +13526,10 @@ snapshots: dependencies: jsesc: 0.5.0 + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + rehype-parse@9.0.0: dependencies: '@types/hast': 3.0.4 @@ -12175,6 +13629,8 @@ snapshots: requires-port@1.0.0: {} + reserved-identifiers@1.2.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -12236,10 +13692,6 @@ snapshots: rfdc@1.4.1: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rimraf@6.0.1: dependencies: glob: 11.0.0 @@ -12279,7 +13731,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 - s.color@0.0.15: {} + s.color@0.0.15: + optional: true safe-array-concat@1.1.2: dependencies: @@ -12301,6 +13754,7 @@ snapshots: sass-formatter@0.7.9: dependencies: suf-log: 2.5.3 + optional: true saxes@6.0.0: dependencies: @@ -12310,6 +13764,12 @@ snapshots: dependencies: loose-envify: 1.4.0 + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.11.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + semver@5.7.2: {} semver@6.3.1: {} @@ -12438,6 +13898,11 @@ snapshots: spdx-exceptions: 2.5.0 spdx-license-ids: 3.0.20 + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + spdx-license-ids@3.0.20: {} split-on-first@3.0.0: {} @@ -12446,10 +13911,6 @@ snapshots: std-env@3.10.0: {} - stop-iteration-iterator@1.0.0: - dependencies: - internal-slot: 1.0.7 - stream-events@1.0.5: dependencies: stubs: 3.0.0 @@ -12460,6 +13921,8 @@ snapshots: string-argv@0.3.2: {} + string-ts@2.2.1: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -12478,8 +13941,9 @@ snapshots: get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 - string.prototype.includes@2.0.0: + string.prototype.includes@2.0.1: dependencies: + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 @@ -12526,6 +13990,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + strip-json-comments@3.1.1: {} strip-literal@3.1.0: @@ -12561,6 +14027,7 @@ snapshots: suf-log@2.5.3: dependencies: s.color: 0.0.15 + optional: true supports-color@5.5.0: dependencies: @@ -12584,10 +14051,9 @@ snapshots: symbol-tree@3.2.4: {} - synckit@0.9.1: + synckit@0.11.11: dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.7.0 + '@pkgr/core': 0.2.9 tailwind-merge@2.5.2: {} @@ -12622,6 +14088,8 @@ snapshots: transitivePeerDependencies: - ts-node + tapable@2.3.0: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -12657,8 +14125,6 @@ snapshots: glob: 10.4.5 minimatch: 9.0.5 - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -12702,6 +14168,15 @@ snapshots: dependencies: is-number: 7.0.0 + to-valid-identifier@1.0.0: + dependencies: + '@sindresorhus/base62': 1.0.0 + reserved-identifiers: 1.2.0 + + toml-eslint-parser@0.10.0: + dependencies: + eslint-visitor-keys: 3.4.3 + tosource@2.0.0-alpha.3: {} totalist@3.0.1: {} @@ -12723,6 +14198,15 @@ snapshots: trough@2.2.0: {} + ts-api-utils@2.1.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-declaration-location@1.0.7(typescript@5.5.4): + dependencies: + picomatch: 4.0.3 + typescript: 5.5.4 + ts-interface-checker@0.1.13: {} ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4): @@ -12744,6 +14228,8 @@ snapshots: yn: 3.1.1 optional: true + ts-pattern@5.9.0: {} + tsconfck@3.1.6(typescript@5.5.4): optionalDependencies: typescript: 5.5.4 @@ -12770,8 +14256,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.20.2: {} - type-fest@0.6.0: {} type-fest@0.8.1: {} @@ -12940,6 +14424,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -13220,6 +14710,18 @@ snapshots: vscode-uri@3.1.0: {} + vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + debug: 4.4.3 + eslint: 9.39.1(jiti@2.6.1) + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -13264,28 +14766,6 @@ snapshots: is-string: 1.0.7 is-symbol: 1.0.4 - which-builtin-type@1.1.4: - dependencies: - function.prototype.name: 1.1.6 - has-tostringtag: 1.0.2 - is-async-function: 2.0.0 - is-date-object: 1.0.5 - is-finalizationregistry: 1.0.2 - is-generator-function: 1.0.10 - is-regex: 1.1.4 - is-weakref: 1.0.2 - isarray: 2.0.5 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.3 - which-pm-runs@1.1.0: {} which-typed-array@1.1.15: @@ -13339,6 +14819,8 @@ snapshots: ws@8.18.0: {} + xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} @@ -13355,6 +14837,11 @@ snapshots: yallist@5.0.0: {} + yaml-eslint-parser@1.3.1: + dependencies: + eslint-visitor-keys: 3.4.3 + yaml: 2.8.1 + yaml-language-server@1.15.0: dependencies: ajv: 8.17.1 @@ -13376,8 +14863,7 @@ snapshots: yaml@2.5.0: {} - yaml@2.8.1: - optional: true + yaml@2.8.1: {} yargs-parser@21.1.1: {} @@ -13418,6 +14904,10 @@ snapshots: typescript: 5.5.4 zod: 3.25.76 + zod-validation-error@4.0.2(zod@4.1.13): + dependencies: + zod: 4.1.13 + zod@3.25.76: {} zod@4.1.13: {} diff --git a/prettier.config.cjs b/prettier.config.cjs deleted file mode 100644 index 2378c736..00000000 --- a/prettier.config.cjs +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import("prettier").Config} */ -module.exports = { - plugins: [require.resolve('prettier-plugin-astro')], - trailingComma: 'none', - overrides: [ - { - files: '*.astro', - options: { - parser: 'astro' - } - } - ] -} \ No newline at end of file diff --git a/results/2024/sections/ai.mdx b/results/2024/sections/ai.mdx index 17a45d7e..06229f93 100644 --- a/results/2024/sections/ai.mdx +++ b/results/2024/sections/ai.mdx @@ -5,7 +5,7 @@ position: 6 # AI -AI continues to be a hot topic in all the tech communities around the world. Globally, some companies consider it as an enabler and decided to fully embrace it, whereas other companies are much more reserved against its usage by their employees. This year, we continue to explore its impact on Moroccan software developers and how they are adjusting to this evolving trend. +AI continues to be a hot topic in all the tech communities around the world. Globally, some companies consider it as an enabler and decided to fully embrace it, whereas other companies are much more reserved against its usage by their employees. This year, we continue to explore its impact on Moroccan software developers and how they are adjusting to this evolving trend. Moroccan software developers are embracing the AI age. They are learning AI and building AI projects. @@ -54,8 +54,8 @@ Amongst the respondents who use AI tools, most of them are using OpenAI's models -In the enterprise world, compagnies are slowly but increasingly investing in AI with more use cases in -production. +In the enterprise world, compagnies are slowly but increasingly investing in AI +with more use cases in production. diff --git a/scripts/export-results.ts b/scripts/export-results.ts index 322462ad..f024817b 100644 --- a/scripts/export-results.ts +++ b/scripts/export-results.ts @@ -1,4 +1,5 @@ -import fs from "fs"; +import fs from "node:fs"; +import process from "node:process"; import { exportResults } from "@/lib/firebase/database"; function getFileName() { @@ -12,16 +13,20 @@ function writeToFile(filename: string, data: any) { fs.writeFile(filename, JSON.stringify(data), (err: any) => { if (err) { console.log(err); - } else { - console.log(`[SUCCESS] ${new Date()} JSON saved to ${filename}`); + } + else { + console.log(`[SUCCESS] ${new Date().toISOString()} JSON saved to ${filename}`); } }); } -const run = async () => { +async function run() { console.log("exporting results"); const data = await exportResults(); writeToFile(getFileName(), data); -}; +} -run(); +run().catch((error) => { + console.error("Error exporting results:", error); + process.exit(1); +}); diff --git a/scripts/generate-questions.ts b/scripts/generate-questions.ts index 6e44be1b..224ab823 100644 --- a/scripts/generate-questions.ts +++ b/scripts/generate-questions.ts @@ -1,19 +1,20 @@ // this is a simple script to convert the questions from yaml to json so it can be used in the playground and chart component +/* eslint-disable node/prefer-global/process */ -import yaml from "js-yaml"; -import fs from "fs"; -import path from "path"; -import { - validateAllSurveyFiles, - formatValidationReport -} from "../src/lib/validators/survey-validator"; import type { SurveyQuestion, SurveyQuestionsYamlFile } from "../src/lib/validators/survey-schema"; +import fs from "node:fs"; +import path from "node:path"; +import yaml from "js-yaml"; import { SURVEY_DIR } from "../src/lib/validators/constants"; +import { + formatValidationReport, + validateAllSurveyFiles +} from "../src/lib/validators/survey-validator"; -const generate = async () => { +async function generate() { console.log("Starting survey questions generation...\n"); // Phase 1: Validate all survey files @@ -39,7 +40,7 @@ const generate = async () => { try { const files = fs .readdirSync(SURVEY_DIR) - .filter((file) => file.endsWith(".yml")) + .filter(file => file.endsWith(".yml")) .sort(); const data: SurveyQuestionsYamlFile[] = []; @@ -68,20 +69,25 @@ const generate = async () => { console.log( `\n✓ Successfully generated ${Object.keys(QS).length} questions` ); - } catch (e) { + } + catch (e) { console.error("\n❌ Error generating questions:", e); process.exit(1); } -}; +} function writeToFile(filename: string, data: Record) { fs.writeFile(filename, JSON.stringify(data), (err: any) => { if (err) { console.log(err); - } else { - console.log(`[SUCCESS] ${new Date()} JSON saved to ${filename}`); + } + else { + console.log(`[SUCCESS] ${new Date().toISOString()} JSON saved to ${filename}`); } }); } -generate(); +generate().catch((error) => { + console.error("Error generating questions:", error); + process.exit(1); +}); diff --git a/scripts/validate-survey.ts b/scripts/validate-survey.ts index 53b9aeb7..875c9a94 100644 --- a/scripts/validate-survey.ts +++ b/scripts/validate-survey.ts @@ -2,12 +2,13 @@ * Standalone script to validate all survey YAML files * This can be run independently or as part of the build process */ +/* eslint-disable node/prefer-global/process */ +import { SURVEY_DIR } from "../src/lib/validators/constants"; import { - validateAllSurveyFiles, - formatValidationReport + formatValidationReport, + validateAllSurveyFiles } from "../src/lib/validators/survey-validator"; -import { SURVEY_DIR } from "../src/lib/validators/constants"; async function main() { console.log("🔍 Validating survey YAML files...\n"); @@ -19,10 +20,14 @@ async function main() { if (validationReport.success) { console.log("\n✓ All validations passed!"); process.exit(0); - } else { + } + else { console.error("\n❌ Validation failed! Please fix the errors above."); process.exit(1); } } -main(); +main().catch((error) => { + console.error("Error validating survey:", error); + process.exit(1); +}); diff --git a/src/actions/init-session.ts b/src/actions/init-session.ts index 77490558..d27f55c7 100644 --- a/src/actions/init-session.ts +++ b/src/actions/init-session.ts @@ -1,9 +1,9 @@ -import { getActiveApp } from "@/lib/firebase/server"; -import { defineAction, ActionError } from "astro:actions"; -import { getAuth } from "firebase-admin/auth"; +import { ActionError, defineAction } from "astro:actions"; import { z } from "astro:schema"; -import { initUserSubmission } from "@/lib/firebase/database"; +import { getAuth } from "firebase-admin/auth"; import { isCaptchaValid } from "@/lib/captcha"; +import { initUserSubmission } from "@/lib/firebase/database"; +import { getActiveApp } from "@/lib/firebase/server"; // Add this import export const initSession = defineAction({ accept: "json", @@ -29,7 +29,7 @@ export const initSession = defineAction({ /* Validate captcha */ if (captchaToken && import.meta.env.CAPTCHA_ENABLED === "true") { - console.log("checking captcha "); + console.warn("checking captcha "); const isValid = await isCaptchaValid(captchaToken); if (!isValid) { @@ -51,8 +51,9 @@ export const initSession = defineAction({ }); } await initUserSubmission(user); - console.log("user session created"); - } catch (error) { + console.warn("user session created"); + } + catch (error) { console.error("Error verifying id token:", error); throw new ActionError({ code: "UNAUTHORIZED", @@ -74,7 +75,8 @@ export const initSession = defineAction({ return { success: true }; - } catch (error) { + } + catch (error) { console.error("Error signing in:", error); throw new ActionError({ code: "UNAUTHORIZED", diff --git a/src/actions/submit-answers.ts b/src/actions/submit-answers.ts index 00727ff0..b4e87760 100644 --- a/src/actions/submit-answers.ts +++ b/src/actions/submit-answers.ts @@ -1,8 +1,8 @@ -import { getActiveApp } from "@/lib/firebase/server"; -import { defineAction, ActionError } from "astro:actions"; -import { getAuth } from "firebase-admin/auth"; +import { ActionError, defineAction } from "astro:actions"; import { z } from "astro:schema"; +import { getAuth } from "firebase-admin/auth"; import { saveAnswers } from "@/lib/firebase/database"; +import { getActiveApp } from "@/lib/firebase/server"; export const submitAnswers = defineAction({ accept: "json", @@ -35,8 +35,9 @@ export const submitAnswers = defineAction({ } /* Save answers to database */ await saveAnswers(user.uid, answers); - console.log("answers saved"); - } catch (error) { + console.warn("answers saved"); + } + catch (error) { console.error("Error token or saving answers:", error); throw new ActionError({ code: "INTERNAL_SERVER_ERROR", diff --git a/src/components/chart/bar-chart.tsx b/src/components/chart/bar-chart.tsx index a4ec7e37..d7c5e128 100644 --- a/src/components/chart/bar-chart.tsx +++ b/src/components/chart/bar-chart.tsx @@ -1,4 +1,5 @@ -import { getPercent, type FinalResult } from "./utils"; +import type { FinalResult } from "./utils"; +import { getPercent } from "./utils"; // Chart colors from theme const colors = [ @@ -14,65 +15,78 @@ const colors = [ "bg-chart-10" ]; -type BarChartProps = { +interface BarChartProps { results: FinalResult | null; sortByTotal?: boolean; showEmptyOptions?: boolean; -}; +} -type BarProps = { +interface BarProps { result: FinalResult["results"][number]; index: number; total: number; -}; +} -const Tooltip = ({ result }: { result: FinalResult["results"][number] }) => { - if (!result.grouped) return null; +function Tooltip({ result }: { result: FinalResult["results"][number] }) { + if (!result.grouped) + return null; return (

    - {result.label} : {result.total}{" "} + {result.label} + {" "} + : + {result.total} + {" "}

    - {result.grouped.results.length > 6 ? ( -
    - {Array.from({ - length: Math.ceil(result.grouped.results.length / 6) - }).map((_, tableIndex) => ( - + {result.grouped.results.length > 6 + ? ( +
    + {Array.from({ + length: Math.ceil(result.grouped.results.length / 6) + }).map((_, tableIndex) => { + const tableGroups = result.grouped!.results.slice(tableIndex * 6, (tableIndex + 1) * 6); + return ( +
    + + {tableGroups.map(group => ( + + + + + ))} + +
    {group.label} + {group.total} + {" "} + ( + {((group.total / result.total) * 100).toFixed(1)} + %) +
    + ); + })} +
    + ) + : ( + - {result?.grouped?.results - .slice(tableIndex * 6, (tableIndex + 1) * 6) - .map((group, index) => ( - - - - - ))} + {result.grouped.results.map(group => ( + + + + + ))}
    {group.label} - {group.total} ( - {((group.total / result.total) * 100).toFixed(1)} - %) -
    {group.label} + {group.total} + {" "} + ( + {((group.total / result.total) * 100).toFixed(1)} + %) +
    - ))} -
    - ) : ( - - - {result.grouped.results.map((group, index) => ( - - - - - ))} - -
    {group.label} - {group.total} ( - {((group.total / result.total) * 100).toFixed(1)}%) -
    - )} + )} {
    ); -}; +} -const Bar = ({ result, index, total }: BarProps) => { +function Bar({ result, index, total }: BarProps) { const displayResults = result.grouped ? result.grouped.results : [result]; return ( @@ -102,50 +116,55 @@ const Bar = ({ result, index, total }: BarProps) => {
    - {getPercent(result.total, total)}% -{" "} + {getPercent(result.total, total)} + % - + {" "} {result.total}
    - {!result.grouped ? ( -
    - ) : ( -
    - {displayResults.map((group, groupIndex) => ( + {!result.grouped + ? (
    - ))} -
    - )} + ) + : ( +
    + {displayResults.map((group, groupIndex) => ( +
    + ))} +
    + )}
    ); -}; +} -export const BarChart = ({ +export function BarChart({ results, sortByTotal = true, showEmptyOptions = true -}: BarChartProps) => { - if (!results) return null; +}: BarChartProps) { + if (!results) + return null; const displayResults = sortByTotal ? [...results.results].sort((a, b) => b.total - a.total) @@ -153,13 +172,13 @@ export const BarChart = ({ const filteredResults = showEmptyOptions ? displayResults - : displayResults.filter((result) => result.total > 0); + : displayResults.filter(result => result.total > 0); // Create a set of unique labels for the legend const legendLabels = new Set(); filteredResults.forEach((result) => { if (result.grouped) { - result.grouped.results.forEach((group) => legendLabels.add(group.label)); + result.grouped.results.forEach(group => legendLabels.add(group.label)); } }); @@ -178,7 +197,9 @@ export const BarChart = ({ {results.isFiltered && "NOTE: Filters applied"} - Total: {results.total} + Total: + {" "} + {results.total}
    {/* legend for grouped questions */} @@ -190,7 +211,8 @@ export const BarChart = ({ className={`w-4 h-4 ${ colors[index % colors.length] } mr-2 opacity-60`} - > + > + {label} ))} @@ -198,4 +220,4 @@ export const BarChart = ({ )} ); -}; +} diff --git a/src/components/chart/chart-actions.tsx b/src/components/chart/chart-actions.tsx index 41c67ff9..643014ff 100644 --- a/src/components/chart/chart-actions.tsx +++ b/src/components/chart/chart-actions.tsx @@ -1,14 +1,14 @@ import type { Year } from "./data"; -import { ShareButtons } from "./share-buttons"; import type { FinalResult } from "./utils"; import queryString from "query-string"; +import { ShareButtons } from "./share-buttons"; -type ShareButtonsProps = { +interface ShareButtonsProps { results: FinalResult; year?: Year; -}; +} -export const ChartActions = ({ results, year }: ShareButtonsProps) => { +export function ChartActions({ results, year }: ShareButtonsProps) { const shareUrl = `/#${new URLSearchParams({ question_id: results.id }).toString()}`; @@ -19,21 +19,21 @@ export const ChartActions = ({ results, year }: ShareButtonsProps) => { ); -}; +} -const PlaygroundButton = ({ +function PlaygroundButton({ results, year }: { results: FinalResult; year?: Year; -}) => { +}) { return ( ); -}; +} diff --git a/src/components/chart/chart.astro b/src/components/chart/chart.astro index f317921d..d6d8e96f 100644 --- a/src/components/chart/chart.astro +++ b/src/components/chart/chart.astro @@ -1,9 +1,10 @@ --- -import { getQuestion, type QuestionCondition } from "./utils"; - import type { Year } from "./data"; + +import type { QuestionCondition } from "./utils"; import getContext from "@astro-utils/context"; import { Chart as RChart } from "./chart"; +import { getQuestion } from "./utils"; export interface Props { id: string; @@ -28,14 +29,16 @@ const results = getQuestion({ id, year, condition, groupBy }); --- { - results ? ( + results +? ( - ) : ( + ) +: (
    {" "} No results for {id} in {year} diff --git a/src/components/chart/chart.test.ts b/src/components/chart/chart.test.ts index 44d5ae51..71531d73 100644 --- a/src/components/chart/chart.test.ts +++ b/src/components/chart/chart.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { getQuestion } from "./utils"; const questions = { @@ -49,7 +49,7 @@ const questions = { const results = [ { - userId: "user-0", + "userId": "user-0", "profile-q-0": 0, "profile-q-1": [0], "profile-q-2": 1, @@ -58,7 +58,7 @@ const results = [ "profile-q-5": [0, 1, 2, 3] }, { - userId: "user-1", + "userId": "user-1", "profile-q-0": 1, "profile-q-1": [0, 1], "profile-q-2": 1, @@ -67,7 +67,7 @@ const results = [ "profile-q-5": [2, 3, 4] }, { - userId: "user-2", + "userId": "user-2", "profile-q-0": 0, "profile-q-1": [1, 2], "profile-q-2": 2, @@ -76,7 +76,7 @@ const results = [ "profile-q-5": [0, 1, 4, 5] }, { - userId: "user-3", + "userId": "user-3", "profile-q-0": 1, "profile-q-1": [0], "profile-q-2": 0, @@ -86,7 +86,7 @@ const results = [ }, { // no answer for q-2 - userId: "user-4", + "userId": "user-4", "profile-q-0": 1, "profile-q-1": [0], "profile-q-3": [2], @@ -101,12 +101,12 @@ const dataSource = { }; describe("getQuestion Simple calculations", () => { - test("throws error for non-existent question id", () => { + it("throws error for non-existent question id", () => { const result = getQuestion({ id: "non-existent-id", dataSource }); expect(result).toBeNull(); }); - test("handles empty results", () => { + it("handles empty results", () => { const emptyDataSource = { ...dataSource, results: [] }; const result = getQuestion({ id: "profile-q-0", @@ -115,10 +115,10 @@ describe("getQuestion Simple calculations", () => { expect(result).not.toBeNull(); expect(result?.total).toBe(0); expect(result?.results).toHaveLength(2); - expect(result?.results.every((r) => r.total === 0)).toBeTruthy(); + expect(result?.results.every(r => r.total === 0)).toBeTruthy(); }); - test("returns correct data for a simple question", () => { + it("returns correct data for a simple question", () => { const result = getQuestion({ id: "profile-q-0", dataSource }); expect(result?.label).toBe("question 0"); expect(result?.total).toBe(5); @@ -129,17 +129,17 @@ describe("getQuestion Simple calculations", () => { expect(result?.results[1].total).toBe(3); }); - test("handles missing answers correctly", () => { + it("handles missing answers correctly", () => { const result = getQuestion({ id: "profile-q-2", dataSource }); expect(result?.total).toBe(4); // user-4 didn't answer this question }); - test("returns all choices even if not present in results", () => { + it("returns all choices even if not present in results", () => { const result = getQuestion({ id: "profile-q-4", dataSource }); expect(result?.results).toHaveLength(5); - expect(result?.results.some((r) => r.total === 0)).toBeTruthy(); + expect(result?.results.some(r => r.total === 0)).toBeTruthy(); }); - test("handles multiple choice questions correctly", () => { + it("handles multiple choice questions correctly", () => { const result = getQuestion({ id: "profile-q-1", dataSource }); expect(result?.total).toBe(5); expect(result?.results).toHaveLength( @@ -150,9 +150,10 @@ describe("getQuestion Simple calculations", () => { expect(result?.results[2].total).toBe(1); }); - test("total should equal the total of all results if the question is not multiple", () => { + it("total should equal the total of all results if the question is not multiple", () => { const result = getQuestion({ id: "profile-q-0", dataSource }); - if (!result) return; + if (!result) + return; const totalAllChoices = result.results.reduce( (acc, curr) => acc + curr.total, 0 @@ -163,13 +164,17 @@ describe("getQuestion Simple calculations", () => { describe("getQuestion Filters", () => { // filters - test("applies simple condition filter correctly with non multiple choice question", () => { + it("applies simple condition filter correctly with non multiple choice question", () => { const result = getQuestion({ id: "profile-q-0", dataSource, - condition: (v) => v["profile-q-4"] === 3 + condition: (v) => { + const value = v["profile-q-4"]; + return typeof value === "number" && value === 3; + } }); - if (!result) return; + if (!result) + return; const totalAllChoices = result.results.reduce( (acc, curr) => acc + curr.total, 0 @@ -179,16 +184,19 @@ describe("getQuestion Filters", () => { expect(result.results[1].total).toBe(2); }); - test("applies simple condition filter correctly with multiple choice question", () => { + it("applies simple condition filter correctly with multiple choice question", () => { const result = getQuestion({ id: "profile-q-0", dataSource, - condition: (v) => v["profile-q-5"].includes(3) + condition: (v) => { + const value = v["profile-q-5"]; + return Array.isArray(value) && (value as number[]).includes(3); + } }); expect(result?.total).toBe(4); }); - test("handles array condition filter", () => { + it("handles array condition filter", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -196,7 +204,7 @@ describe("getQuestion Filters", () => { }); expect(result?.total).toBe(2); }); - test("handles multiple value array condition filter", () => { + it("handles multiple value array condition filter", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -205,7 +213,7 @@ describe("getQuestion Filters", () => { expect(result?.total).toBe(3); }); - test("handles multiple value array condition filter", () => { + it("handles multiple value array condition filter with multiple questions", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -219,7 +227,7 @@ describe("getQuestion Filters", () => { expect(result?.results[1].total).toBe(2); }); - test("handles complex array condition filter with multiple questions", () => { + it("handles complex array condition filter with multiple questions", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -233,17 +241,17 @@ describe("getQuestion Filters", () => { expect(result?.results[1].total).toBe(0); }); - test("handles array condition filter with no matching values", () => { + it("handles array condition filter with no matching values", () => { const result = getQuestion({ id: "profile-q-0", dataSource, condition: [{ question_id: "profile-q-4", values: ["999"] }] }); expect(result?.total).toBe(0); - expect(result?.results.every((r) => r.total === 0)).toBeTruthy(); + expect(result?.results.every(r => r.total === 0)).toBeTruthy(); }); - test("handles array condition filter with empty values array", () => { + it("handles array condition filter with empty values array", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -252,32 +260,36 @@ describe("getQuestion Filters", () => { expect(result?.total).toBe(5); }); - test("handles function condition with complex logic", () => { + it("handles function condition with complex logic", () => { const result = getQuestion({ id: "profile-q-0", dataSource, - condition: (v) => - v["profile-q-4"] % 2 === 0 && v["profile-q-5"].includes(1) + condition: (v) => { + const value4 = v["profile-q-4"]; + const value5 = v["profile-q-5"]; + return typeof value4 === "number" && value4 % 2 === 0 && Array.isArray(value5) && (value5 as number[]).includes(1); + } }); expect(result?.total).toBe(1); expect(result?.results[0].total).toBe(1); expect(result?.results[1].total).toBe(0); }); - test("applies filter correctly to multiple choice questions", () => { + it("applies filter correctly to multiple choice questions", () => { const result = getQuestion({ id: "profile-q-1", dataSource, condition: [{ question_id: "profile-q-0", values: ["1"] }] }); - if (!result) return; + if (!result) + return; expect(result.total).toBe(3); expect(result.results[0].total).toBe(3); expect(result.results[1].total).toBe(1); expect(result.results[2].total).toBe(0); }); - test("handles missing answers in filter conditions", () => { + it("handles missing answers in filter conditions", () => { const result = getQuestion({ id: "profile-q-0", dataSource, @@ -286,14 +298,17 @@ describe("getQuestion Filters", () => { expect(result?.total).toBe(4); // user-4 should be excluded due to missing answer }); - test("sets isFiltered correctly", () => { + it("sets isFiltered correctly", () => { const resultNoFilter = getQuestion({ id: "profile-q-0", dataSource }); expect(resultNoFilter?.isFiltered).toBe(false); const resultWithFunctionFilter = getQuestion({ id: "profile-q-0", dataSource, - condition: (v) => v["profile-q-4"] === 3 + condition: (v) => { + const value = v["profile-q-4"]; + return typeof value === "number" && value === 3; + } }); expect(resultWithFunctionFilter?.isFiltered).toBe(true); @@ -314,25 +329,27 @@ describe("getQuestion Filters", () => { }); describe("getQuestion Grouping", () => { - test("groups results correctly", () => { + it("groups results correctly", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-4" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.results[0].grouped).not.toBeNull(); expect(result.results[0].grouped?.results).toHaveLength(5); }); - test("groups results correctly for single-choice questions", () => { + it("groups results correctly for single-choice questions", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-4" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.results[0].grouped).not.toBeNull(); expect(result.results[0].grouped?.results).toHaveLength(5); @@ -341,13 +358,14 @@ describe("getQuestion Grouping", () => { expect(result.results[1].grouped?.total).toBe(3); }); - test("groups results correctly for multiple-choice questions", () => { + it("groups results correctly for multiple-choice questions", () => { const result = getQuestion({ id: "profile-q-1", dataSource, groupBy: "profile-q-0" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(3); expect(result.results[0].grouped).not.toBeNull(); expect(result.results[0].grouped?.results).toHaveLength(2); @@ -355,13 +373,14 @@ describe("getQuestion Grouping", () => { expect(result.results[2].grouped?.results).toHaveLength(2); }); - test("handles grouping with missing answers", () => { + it("handles grouping with missing answers", () => { const result = getQuestion({ id: "profile-q-2", dataSource, groupBy: "profile-q-0" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(3); expect(result.results[0].grouped?.results).toHaveLength(2); expect(result.results[1].grouped?.results).toHaveLength(2); @@ -371,14 +390,18 @@ describe("getQuestion Grouping", () => { expect(result.results[2].grouped?.total).toBe(1); }); - test("groups results correctly with a filter applied", () => { + it("groups results correctly with a filter applied", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-4", - condition: (v) => v["profile-q-5"].includes(3) + condition: (v) => { + const value = v["profile-q-5"]; + return Array.isArray(value) && (value as number[]).includes(3); + } }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength( questions["profile-q-0"].choices.length ); @@ -393,25 +416,27 @@ describe("getQuestion Grouping", () => { expect(result.results[1].grouped?.total).toBe(result.results[1].total); }); - test("handles grouping by a question with more choices than answers", () => { + it("handles grouping by a question with more choices than answers", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-5" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.results[0].grouped?.results).toHaveLength(6); expect(result.results[1].grouped?.results).toHaveLength(6); }); - test("nested grouping works correctly", () => { + it("nested grouping works correctly", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-1" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.results[0].grouped?.results).toHaveLength(3); expect(result.results[1].grouped?.results).toHaveLength(3); @@ -421,47 +446,50 @@ describe("getQuestion Grouping", () => { expect(nestedGroup).toBeNull(); }); - test("grouping preserves all choices even if not present in results", () => { + it("grouping preserves all choices even if not present in results", () => { const result = getQuestion({ id: "profile-q-4", dataSource, groupBy: "profile-q-0" }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(5); expect( - result.results.every((r) => r.grouped?.results.length === 2) + result.results.every(r => r.grouped?.results.length === 2) ).toBeTruthy(); - expect(result.results.some((r) => r.total === 0)).toBeTruthy(); + expect(result.results.some(r => r.total === 0)).toBeTruthy(); }); - test("grouping handles condition that filters out all results", () => { + it("grouping handles condition that filters out all results", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-4", condition: () => false }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.results[0].grouped?.total).toBe(0); expect(result.results[1].grouped?.total).toBe(0); expect( - result.results[0].grouped?.results.every((r) => r.total === 0) + result.results[0].grouped?.results.every(r => r.total === 0) ).toBeTruthy(); expect( - result.results[1].grouped?.results.every((r) => r.total === 0) + result.results[1].grouped?.results.every(r => r.total === 0) ).toBeTruthy(); }); - test("grouping with array condition filter", () => { + it("grouping with array condition filter", () => { const result = getQuestion({ id: "profile-q-0", dataSource, groupBy: "profile-q-4", condition: [{ question_id: "profile-q-5", values: ["3"] }] }); - if (!result) return; + if (!result) + return; expect(result.results).toHaveLength(2); expect(result.total).toBe(4); expect(result.results[0].grouped?.total).toBe(result.results[0].total); diff --git a/src/components/chart/chart.tsx b/src/components/chart/chart.tsx index f243f9c1..8f6e5071 100644 --- a/src/components/chart/chart.tsx +++ b/src/components/chart/chart.tsx @@ -1,11 +1,11 @@ +import type { Year } from "./data"; +import type { FinalResult } from "./utils"; import React from "react"; import { BarChart } from "./bar-chart"; -import { PieChart } from "./pie-chart"; -import { type FinalResult } from "./utils"; import { ChartActions } from "./chart-actions"; -import type { Year } from "./data"; +import { PieChart } from "./pie-chart"; -type ChartProps = { +interface ChartProps { results: FinalResult | null; sortByTotal?: boolean; title?: boolean; @@ -13,7 +13,7 @@ type ChartProps = { pie?: boolean; year?: Year; showEmptyOptions?: boolean; -}; +} export const Chart: React.FC = ({ results, @@ -24,7 +24,8 @@ export const Chart: React.FC = ({ year, showEmptyOptions = true }) => { - if (!results) return null; + if (!results) + return null; const ChartComponent = pie ? PieChart : BarChart; @@ -42,7 +43,9 @@ export const Chart: React.FC = ({ {results?.otherOptions.length > 0 && (
    - Others ({results?.otherOptions.length}) + Others ( + {results?.otherOptions.length} + ) submitted by participants diff --git a/src/components/chart/data.ts b/src/components/chart/data.ts index 7c83631b..e024108b 100644 --- a/src/components/chart/data.ts +++ b/src/components/chart/data.ts @@ -1,15 +1,15 @@ -import DATA_2020 from "@/results/2020/data/results.json"; import Questions_2020 from "@/results/2020/data/questions.json"; -import DATA_2021 from "@/results/2021/data/results.json"; +import DATA_2020 from "@/results/2020/data/results.json"; import Questions_2021 from "@/results/2021/data/questions.json"; +import DATA_2021 from "@/results/2021/data/results.json"; -import DATA_2022 from "@/results/2022/data/results.json"; import Questions_2022 from "@/results/2022/data/questions.json"; +import DATA_2022 from "@/results/2022/data/results.json"; -import DATA_2023 from "@/results/2023/data/results.json"; import Questions_2023 from "@/results/2023/data/questions.json"; -import DATA_2024 from "@/results/2024/data/results.json"; +import DATA_2023 from "@/results/2023/data/results.json"; import Questions_2024 from "@/results/2024/data/questions.json"; +import DATA_2024 from "@/results/2024/data/results.json"; export type Year = "2020" | "2021" | "2022" | "2023" | "2024"; export type Question = globalThis.Question; @@ -24,28 +24,28 @@ export type SurveyDataType = { }; const surveyData: SurveyDataType = { - "2020": { + 2020: { questions: Questions_2020 as unknown as QuestionMap, results: DATA_2020.results as unknown as Results["results"] }, - "2021": { + 2021: { questions: Questions_2021 as unknown as QuestionMap, results: DATA_2021.results as unknown as Results["results"] }, - "2022": { + 2022: { questions: Questions_2022 as unknown as QuestionMap, results: DATA_2022.results as unknown as Results["results"] }, - "2023": { + 2023: { questions: Questions_2023 as unknown as QuestionMap, results: DATA_2023.results as unknown as Results["results"] }, - "2024": { + 2024: { questions: Questions_2024 as unknown as QuestionMap, results: DATA_2024.results as unknown as Results["results"] } }; -export const getSurveyData = (year: Year) => { +export function getSurveyData(year: Year) { return surveyData[year]; -}; +} diff --git a/src/components/chart/pie-chart.tsx b/src/components/chart/pie-chart.tsx index 683fab2c..14e42843 100644 --- a/src/components/chart/pie-chart.tsx +++ b/src/components/chart/pie-chart.tsx @@ -1,4 +1,6 @@ -import { getPercent, type FinalResult } from "./utils"; +import type { FinalResult } from "./utils"; +import { useMemo } from "react"; +import { getPercent } from "./utils"; // Chart colors using CSS variables const colors = [ @@ -14,11 +16,11 @@ const colors = [ "var(--chart-10)" ]; -type PieChartProps = { +interface PieChartProps { results: FinalResult | null; sortByTotal?: boolean; -}; -const PieSlice = ({ +} +function PieSlice({ result, total, startAngle, @@ -30,7 +32,7 @@ const PieSlice = ({ startAngle: number; endAngle: number; color: string; -}) => { +}) { const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; const x1 = 50 + 50 * Math.cos((Math.PI * startAngle) / 180); const y1 = 50 + 50 * Math.sin((Math.PI * startAngle) / 180); @@ -41,7 +43,7 @@ const PieSlice = ({ const textX = 50 + 35 * Math.cos((Math.PI * midAngle) / 180); const textY = 50 + 35 * Math.sin((Math.PI * midAngle) / 180); - const percentage = parseFloat(getPercent(result.total, total)); + const percentage = Number.parseFloat(getPercent(result.total, total)); return ( @@ -60,42 +62,58 @@ const PieSlice = ({ fontSize="8px" className="font-medium" > - {percentage}% + {percentage} + % )} {`${result.label}: ${result.total} (${percentage}%)`} ); -}; +} -export const PieChart = ({ results, sortByTotal = true }: PieChartProps) => { - if (!results) return null; +export function PieChart({ results, sortByTotal = true }: PieChartProps) { + const displayResults = useMemo(() => { + if (!results) + return []; + return sortByTotal + ? [...results.results].sort((a, b) => b.total - a.total) + : results.results; + }, [results, sortByTotal]); - const displayResults = sortByTotal - ? [...results.results].sort((a, b) => b.total - a.total) - : results.results; + const slicesWithAngles = useMemo(() => { + if (!results) + return []; + return displayResults.reduce>((acc, result, index) => { + const sliceAngle = (result.total / results.total) * 360; + const previousEndAngle = acc.length > 0 ? acc[acc.length - 1].endAngle : 0; + const startAngle = previousEndAngle; + const endAngle = previousEndAngle + sliceAngle; + acc.push({ + result, + index, + startAngle, + endAngle + }); + return acc; + }, []); + }, [displayResults, results]); - let startAngle = 0; + if (!results) + return null; return (
    - {displayResults.map((result, index) => { - const sliceAngle = (result.total / results.total) * 360; - const endAngle = startAngle + sliceAngle; - const slice = ( - - ); - startAngle = endAngle; - return slice; - })} + {slicesWithAngles.map(({ result, index, startAngle, endAngle }) => ( + + ))}
    {displayResults.map((result, index) => ( @@ -103,14 +121,20 @@ export const PieChart = ({ results, sortByTotal = true }: PieChartProps) => {
    + > +
    - {result.label}: {result.total} ( - {getPercent(result.total, results.total)}%) + {result.label} + : + {result.total} + {" "} + ( + {getPercent(result.total, results.total)} + %)
    ))}
    ); -}; +} diff --git a/src/components/chart/share-buttons.tsx b/src/components/chart/share-buttons.tsx index b1aba3d1..a646189f 100644 --- a/src/components/chart/share-buttons.tsx +++ b/src/components/chart/share-buttons.tsx @@ -1,11 +1,11 @@ import { useState } from "react"; -type ShareButtonsProps = { +interface ShareButtonsProps { url: string; title: string; -}; +} -export const ShareButtons = ({ url, title }: ShareButtonsProps) => { +export function ShareButtons({ url, title }: ShareButtonsProps) { const [copied, setCopied] = useState(false); const shareLinks = { @@ -14,11 +14,10 @@ export const ShareButtons = ({ url, title }: ShareButtonsProps) => { facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}` }; - const copyToClipboard = () => { - navigator.clipboard.writeText(url).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); + const copyToClipboard = async () => { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); }; return ( @@ -86,7 +85,10 @@ export const ShareButtons = ({ url, title }: ShareButtonsProps) => {