@seo-console/package 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/lib/supabase/server.ts","../src/lib/database/seo-records.ts","../src/lib/validation/seo-schema.ts","../src/lib/validation/image-validator.ts","../src/lib/validation/html-validator.ts","../src/lib/storage/file-storage.ts","../src/lib/storage/supabase-storage.ts","../src/lib/storage/storage-factory.ts","../src/hooks/useGenerateMetadata.ts","../src/lib/sitemap-generator.ts","../src/lib/robots-generator.ts","../src/lib/metadata-extractor.ts","../src/lib/route-discovery.ts","../src/lib/validation/crawlability-validator.ts"],"sourcesContent":["/**\r\n * Server-side only exports\r\n * These can be safely imported in API routes and server components\r\n */\r\n\r\n// Export database functions\r\nexport {\r\n getSEORecords,\r\n getSEORecords as getAllSEORecords, // Alias for backward compatibility\r\n getSEORecordById,\r\n getSEORecordByRoute,\r\n createSEORecord,\r\n updateSEORecord,\r\n deleteSEORecord,\r\n} from \"./lib/database/seo-records\";\r\n\r\n// Export validation schemas\r\nexport { createSEORecordSchema, updateSEORecordSchema } from \"./lib/validation/seo-schema\";\r\n\r\n// Export validation utilities\r\nexport { validateOGImage } from \"./lib/validation/image-validator\";\r\nexport { validateHTML, validateURL } from \"./lib/validation/html-validator\";\r\n\r\n// Export types\r\nexport type { SEORecord, CreateSEORecord, UpdateSEORecord } from \"./lib/validation/seo-schema\";\r\nexport type { ValidationResult, ValidationIssue } from \"./lib/validation/html-validator\";\r\nexport type { ImageValidationResult } from \"./lib/validation/image-validator\";\r\n\r\n// Export Result type for error handling\r\nexport type Result<T> =\r\n | { success: true; data: T }\r\n | { success: false; error: Error };\r\n\r\n// Export hooks (can be used in server components)\r\nexport * from \"./hooks\";\r\n\r\n// Export sitemap and robots generators (server-side)\r\nexport { generateSitemapXML, generateSitemapFromRecords, seoRecordsToSitemapEntries } from \"./lib/sitemap-generator\";\r\nexport { generateRobotsTxt, updateRobotsTxtWithSitemap } from \"./lib/robots-generator\";\r\n\r\n// Export metadata extractor (server-side)\r\nexport { extractMetadataFromURL, crawlSiteForSEO, metadataToSEORecord } from \"./lib/metadata-extractor\";\r\n\r\n// Export route discovery (server-side)\r\nexport { discoverNextJSRoutes } from \"./lib/route-discovery\";\r\n\r\n// Export crawlability validator (server-side)\r\nexport { validateCrawlability, validateRobotsTxt, validatePublicAccess } from \"./lib/validation/crawlability-validator\";","import { createServerClient } from \"@supabase/ssr\";\nimport { cookies } from \"next/headers\";\n\nexport async function createClient() {\n const cookieStore = await cookies();\n\n return createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: Array<{ name: string; value: string; options?: unknown }>) {\n try {\n cookiesToSet.forEach(({ name, value, options }: { name: string; value: string; options?: unknown }) =>\n cookieStore.set(name, value, options as { [key: string]: unknown })\n );\n } catch {\n // Called from Server Component - ignore\n }\n },\n },\n }\n );\n}\n","import { createClient } from \"../supabase/server\";\r\nimport type { Database } from \"../../types/database.types\";\r\nimport type {\r\n CreateSEORecord,\r\n UpdateSEORecord,\r\n SEORecord,\r\n} from \"../validation/seo-schema\";\r\n\r\ntype SEORecordRow = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"];\r\ntype SEORecordInsert = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Insert\"];\r\ntype SEORecordUpdate = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Update\"];\r\n\r\n// Transform database row to SEORecord type\r\nfunction transformRowToSEORecord(row: SEORecordRow): SEORecord {\r\n return {\r\n id: row.id,\r\n userId: row.user_id,\r\n routePath: row.route_path,\r\n title: row.title ?? undefined,\r\n description: row.description ?? undefined,\r\n keywords: row.keywords ?? undefined,\r\n ogTitle: row.og_title ?? undefined,\r\n ogDescription: row.og_description ?? undefined,\r\n ogImageUrl: row.og_image_url ?? undefined,\r\n ogImageWidth: row.og_image_width ?? undefined,\r\n ogImageHeight: row.og_image_height ?? undefined,\r\n ogType: (row.og_type as SEORecord[\"ogType\"]) ?? undefined,\r\n ogUrl: row.og_url ?? undefined,\r\n ogSiteName: row.og_site_name ?? undefined,\r\n twitterCard: (row.twitter_card as SEORecord[\"twitterCard\"]) ?? undefined,\r\n twitterTitle: row.twitter_title ?? undefined,\r\n twitterDescription: row.twitter_description ?? undefined,\r\n twitterImageUrl: row.twitter_image_url ?? undefined,\r\n twitterSite: row.twitter_site ?? undefined,\r\n twitterCreator: row.twitter_creator ?? undefined,\r\n canonicalUrl: row.canonical_url ?? undefined,\r\n robots: row.robots ?? undefined,\r\n author: row.author ?? undefined,\r\n publishedTime: row.published_time\r\n ? new Date(row.published_time)\r\n : undefined,\r\n modifiedTime: row.modified_time\r\n ? new Date(row.modified_time)\r\n : undefined,\r\n structuredData: row.structured_data\r\n ? (row.structured_data as unknown as Record<string, unknown>)\r\n : undefined,\r\n validationStatus: (row.validation_status as SEORecord[\"validationStatus\"]) ?? undefined,\r\n lastValidatedAt: row.last_validated_at\r\n ? new Date(row.last_validated_at)\r\n : undefined,\r\n validationErrors: row.validation_errors\r\n ? (row.validation_errors as unknown as Record<string, unknown>)\r\n : undefined,\r\n createdAt: new Date(row.created_at),\r\n updatedAt: new Date(row.updated_at),\r\n };\r\n}\r\n\r\n// Transform SEORecord to database insert format\r\nfunction transformToInsert(\r\n record: CreateSEORecord\r\n): Omit<SEORecordInsert, \"id\" | \"created_at\" | \"updated_at\"> {\r\n return {\r\n user_id: record.userId,\r\n route_path: record.routePath,\r\n title: record.title ?? null,\r\n description: record.description ?? null,\r\n keywords: record.keywords ?? null,\r\n og_title: record.ogTitle ?? null,\r\n og_description: record.ogDescription ?? null,\r\n og_image_url: record.ogImageUrl ?? null,\r\n og_image_width: record.ogImageWidth ?? null,\r\n og_image_height: record.ogImageHeight ?? null,\r\n og_type: record.ogType ?? null,\r\n og_url: record.ogUrl ?? null,\r\n og_site_name: record.ogSiteName ?? null,\r\n twitter_card: record.twitterCard ?? null,\r\n twitter_title: record.twitterTitle ?? null,\r\n twitter_description: record.twitterDescription ?? null,\r\n twitter_image_url: record.twitterImageUrl ?? null,\r\n twitter_site: record.twitterSite ?? null,\r\n twitter_creator: record.twitterCreator ?? null,\r\n canonical_url: record.canonicalUrl ?? null,\r\n robots: record.robots ?? null,\r\n author: record.author ?? null,\r\n published_time: record.publishedTime?.toISOString() ?? null,\r\n modified_time: record.modifiedTime?.toISOString() ?? null,\r\n structured_data: (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null,\r\n };\r\n}\r\n\r\n// Transform SEORecord to database update format\r\nfunction transformToUpdate(\r\n record: Omit<UpdateSEORecord, \"id\">\r\n): Omit<SEORecordUpdate, \"updated_at\"> {\r\n const update: Partial<SEORecordUpdate> = {};\r\n\r\n if (record.routePath !== undefined) update.route_path = record.routePath;\r\n if (record.title !== undefined) update.title = record.title ?? null;\r\n if (record.description !== undefined)\r\n update.description = record.description ?? null;\r\n if (record.keywords !== undefined) update.keywords = record.keywords ?? null;\r\n if (record.ogTitle !== undefined) update.og_title = record.ogTitle ?? null;\r\n if (record.ogDescription !== undefined)\r\n update.og_description = record.ogDescription ?? null;\r\n if (record.ogImageUrl !== undefined)\r\n update.og_image_url = record.ogImageUrl ?? null;\r\n if (record.ogImageWidth !== undefined)\r\n update.og_image_width = record.ogImageWidth ?? null;\r\n if (record.ogImageHeight !== undefined)\r\n update.og_image_height = record.ogImageHeight ?? null;\r\n if (record.ogType !== undefined) update.og_type = record.ogType ?? null;\r\n if (record.ogUrl !== undefined) update.og_url = record.ogUrl ?? null;\r\n if (record.ogSiteName !== undefined)\r\n update.og_site_name = record.ogSiteName ?? null;\r\n if (record.twitterCard !== undefined)\r\n update.twitter_card = record.twitterCard ?? null;\r\n if (record.twitterTitle !== undefined)\r\n update.twitter_title = record.twitterTitle ?? null;\r\n if (record.twitterDescription !== undefined)\r\n update.twitter_description = record.twitterDescription ?? null;\r\n if (record.twitterImageUrl !== undefined)\r\n update.twitter_image_url = record.twitterImageUrl ?? null;\r\n if (record.twitterSite !== undefined)\r\n update.twitter_site = record.twitterSite ?? null;\r\n if (record.twitterCreator !== undefined)\r\n update.twitter_creator = record.twitterCreator ?? null;\r\n if (record.canonicalUrl !== undefined)\r\n update.canonical_url = record.canonicalUrl ?? null;\r\n if (record.robots !== undefined) update.robots = record.robots ?? null;\r\n if (record.author !== undefined) update.author = record.author ?? null;\r\n if (record.publishedTime !== undefined)\r\n update.published_time = record.publishedTime?.toISOString() ?? null;\r\n if (record.modifiedTime !== undefined)\r\n update.modified_time = record.modifiedTime?.toISOString() ?? null;\r\n if (record.structuredData !== undefined)\r\n update.structured_data = (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null;\r\n if (record.validationStatus !== undefined)\r\n update.validation_status = record.validationStatus ?? null;\r\n if (record.lastValidatedAt !== undefined)\r\n update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;\r\n if (record.validationErrors !== undefined)\r\n update.validation_errors = (record.validationErrors as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"validation_errors\"]) ?? null;\r\n\r\n return update;\r\n}\r\n\r\n// Result type for operations\r\nexport type Result<T, E = Error> =\r\n | { success: true; data: T }\r\n | { success: false; error: E };\r\n\r\n/**\r\n * Get all SEO records for the current user\r\n */\r\nexport async function getSEORecords(): Promise<Result<SEORecord[]>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"user_id\", user.id)\r\n .order(\"created_at\", { ascending: false });\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n const records = (data || []).map(transformRowToSEORecord);\r\n return { success: true, data: records };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get a single SEO record by ID\r\n */\r\nexport async function getSEORecordById(\r\n id: string\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get SEO record by route path\r\n */\r\nexport async function getSEORecordByRoute(\r\n routePath: string\r\n): Promise<Result<SEORecord | null>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"route_path\", routePath)\r\n .eq(\"user_id\", user.id)\r\n .maybeSingle();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return { success: true, data: null };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Create a new SEO record\r\n */\r\nexport async function createSEORecord(\r\n record: CreateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const insertData = transformToInsert({ ...record, userId: user.id });\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .insert(insertData)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Update an existing SEO record\r\n */\r\nexport async function updateSEORecord(\r\n record: UpdateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { id, ...updateData } = record;\r\n const transformedUpdate = transformToUpdate(updateData);\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .update(transformedUpdate)\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Delete an SEO record\r\n */\r\nexport async function deleteSEORecord(id: string): Promise<Result<void>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { error } = await supabase\r\n .from(\"seo_records\")\r\n .delete()\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id);\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: undefined };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n","import { z } from \"zod\";\r\n\r\n// Open Graph types\r\nexport const ogTypeSchema = z.enum([\r\n \"website\",\r\n \"article\",\r\n \"product\",\r\n \"book\",\r\n \"profile\",\r\n \"music\",\r\n \"video\",\r\n]);\r\n\r\n// Twitter Card types\r\nexport const twitterCardSchema = z.enum([\r\n \"summary\",\r\n \"summary_large_image\",\r\n \"app\",\r\n \"player\",\r\n]);\r\n\r\n// Validation status\r\nexport const validationStatusSchema = z.enum([\r\n \"pending\",\r\n \"valid\",\r\n \"invalid\",\r\n \"warning\",\r\n]);\r\n\r\n// Base SEO metadata schema\r\nexport const seoMetadataSchema = z.object({\r\n // Basic metadata\r\n title: z.string().max(60, \"Title must be 60 characters or less\").optional(),\r\n description: z\r\n .string()\r\n .max(160, \"Description must be 160 characters or less\")\r\n .optional(),\r\n keywords: z.array(z.string()).optional(),\r\n\r\n // Open Graph\r\n ogTitle: z.string().max(60).optional(),\r\n ogDescription: z.string().max(200).optional(),\r\n ogImageUrl: z.string().url(\"Must be a valid URL\").optional(),\r\n ogImageWidth: z.number().int().positive().max(1200).optional(),\r\n ogImageHeight: z.number().int().positive().max(1200).optional(),\r\n ogType: ogTypeSchema.optional(),\r\n ogUrl: z.string().url(\"Must be a valid URL\").optional(),\r\n ogSiteName: z.string().optional(),\r\n\r\n // Twitter Card\r\n twitterCard: twitterCardSchema.optional(),\r\n twitterTitle: z.string().max(70).optional(),\r\n twitterDescription: z.string().max(200).optional(),\r\n twitterImageUrl: z.string().url(\"Must be a valid URL\").optional(),\r\n twitterSite: z.string().optional(),\r\n twitterCreator: z.string().optional(),\r\n\r\n // Additional metadata\r\n canonicalUrl: z.string().url(\"Must be a valid URL\").optional(),\r\n robots: z.string().optional(),\r\n author: z.string().optional(),\r\n publishedTime: z.coerce.date().optional(),\r\n modifiedTime: z.coerce.date().optional(),\r\n\r\n // Structured data\r\n structuredData: z.record(z.unknown()).optional(),\r\n});\r\n\r\n// Full SEO record schema (for database operations)\r\nexport const seoRecordSchema = seoMetadataSchema.extend({\r\n id: z.string().uuid().optional(),\r\n userId: z.string().uuid(),\r\n routePath: z\r\n .string()\r\n .min(1, \"Route path is required\")\r\n .regex(/^\\/.*/, \"Route path must start with /\"),\r\n validationStatus: validationStatusSchema.optional(),\r\n lastValidatedAt: z.coerce.date().optional(),\r\n validationErrors: z.record(z.unknown()).optional(),\r\n createdAt: z.coerce.date().optional(),\r\n updatedAt: z.coerce.date().optional(),\r\n});\r\n\r\n// Schema for creating a new SEO record\r\nexport const createSEORecordSchema = seoRecordSchema.omit({\r\n id: true,\r\n validationStatus: true,\r\n lastValidatedAt: true,\r\n validationErrors: true,\r\n createdAt: true,\r\n updatedAt: true,\r\n});\r\n\r\n// Schema for updating an SEO record\r\nexport const updateSEORecordSchema = seoRecordSchema\r\n .partial()\r\n .required({ id: true })\r\n .omit({\r\n userId: true,\r\n createdAt: true,\r\n });\r\n\r\n// Type exports\r\nexport type OGType = z.infer<typeof ogTypeSchema>;\r\nexport type TwitterCard = z.infer<typeof twitterCardSchema>;\r\nexport type ValidationStatus = z.infer<typeof validationStatusSchema>;\r\nexport type SEOMetadata = z.infer<typeof seoMetadataSchema>;\r\nexport type SEORecord = z.infer<typeof seoRecordSchema>;\r\nexport type CreateSEORecord = z.infer<typeof createSEORecordSchema>;\r\nexport type UpdateSEORecord = z.infer<typeof updateSEORecordSchema>;\r\n","import sharp from \"sharp\";\r\n\r\nexport interface ImageValidationResult {\r\n isValid: boolean;\r\n issues: Array<{\r\n field: string;\r\n severity: \"critical\" | \"warning\" | \"info\";\r\n message: string;\r\n expected?: string;\r\n actual?: string;\r\n }>;\r\n metadata?: {\r\n width: number;\r\n height: number;\r\n format: string;\r\n size: number;\r\n };\r\n}\r\n\r\n/**\r\n * Validate OG image URL\r\n * Checks dimensions, format, file size, and accessibility\r\n */\r\nexport async function validateOGImage(\r\n imageUrl: string,\r\n expectedWidth?: number,\r\n expectedHeight?: number\r\n): Promise<ImageValidationResult> {\r\n const issues: ImageValidationResult[\"issues\"] = [];\r\n\r\n try {\r\n // Fetch image\r\n const response = await fetch(imageUrl, {\r\n headers: {\r\n \"User-Agent\":\r\n \"Mozilla/5.0 (compatible; SEO-Console/1.0; +https://example.com/bot)\",\r\n },\r\n signal: AbortSignal.timeout(15000), // 15 second timeout for images\r\n });\r\n\r\n if (!response.ok) {\r\n return {\r\n isValid: false,\r\n issues: [\r\n {\r\n field: \"image\",\r\n severity: \"critical\",\r\n message: `Failed to fetch image: ${response.status} ${response.statusText}`,\r\n actual: imageUrl,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const imageBuffer = await response.arrayBuffer();\r\n const buffer = Buffer.from(imageBuffer);\r\n\r\n // Check file size (recommended: < 1MB for OG images)\r\n const sizeInMB = buffer.length / (1024 * 1024);\r\n if (sizeInMB > 1) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"warning\",\r\n message: \"Image file size exceeds 1MB recommendation\",\r\n actual: `${sizeInMB.toFixed(2)}MB`,\r\n });\r\n }\r\n\r\n // Get image metadata using sharp\r\n const metadata = await sharp(buffer).metadata();\r\n const { width, height, format } = metadata;\r\n\r\n if (!width || !height) {\r\n return {\r\n isValid: false,\r\n issues: [\r\n {\r\n field: \"image\",\r\n severity: \"critical\",\r\n message: \"Could not determine image dimensions\",\r\n actual: imageUrl,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n // Validate dimensions\r\n // Recommended: 1200x630 for OG images\r\n const recommendedWidth = 1200;\r\n const recommendedHeight = 630;\r\n const aspectRatio = width / height;\r\n const recommendedAspectRatio = recommendedWidth / recommendedHeight;\r\n\r\n if (width < recommendedWidth || height < recommendedHeight) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"warning\",\r\n message: `Image dimensions below recommended size (${recommendedWidth}x${recommendedHeight})`,\r\n expected: `${recommendedWidth}x${recommendedHeight}`,\r\n actual: `${width}x${height}`,\r\n });\r\n }\r\n\r\n // Check aspect ratio (should be close to 1.91:1 for OG images)\r\n if (Math.abs(aspectRatio - recommendedAspectRatio) > 0.1) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"info\",\r\n message: \"Image aspect ratio differs from recommended 1.91:1\",\r\n expected: \"1.91:1\",\r\n actual: `${aspectRatio.toFixed(2)}:1`,\r\n });\r\n }\r\n\r\n // Validate format (prefer JPEG, PNG, WebP, or AVIF)\r\n const supportedFormats = [\"jpeg\", \"jpg\", \"png\", \"webp\", \"avif\", \"gif\"];\r\n if (!format || !supportedFormats.includes(format.toLowerCase())) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"warning\",\r\n message: \"Image format may not be optimal for social sharing\",\r\n expected: \"JPEG, PNG, WebP, or AVIF\",\r\n actual: format || \"unknown\",\r\n });\r\n }\r\n\r\n // If expected dimensions provided, validate against them\r\n if (expectedWidth && width !== expectedWidth) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"warning\",\r\n message: \"Image width does not match expected value\",\r\n expected: `${expectedWidth}px`,\r\n actual: `${width}px`,\r\n });\r\n }\r\n\r\n if (expectedHeight && height !== expectedHeight) {\r\n issues.push({\r\n field: \"image\",\r\n severity: \"warning\",\r\n message: \"Image height does not match expected value\",\r\n expected: `${expectedHeight}px`,\r\n actual: `${height}px`,\r\n });\r\n }\r\n\r\n return {\r\n isValid: issues.filter((i) => i.severity === \"critical\").length === 0,\r\n issues,\r\n metadata: {\r\n width,\r\n height,\r\n format: format || \"unknown\",\r\n size: buffer.length,\r\n },\r\n };\r\n } catch (error) {\r\n return {\r\n isValid: false,\r\n issues: [\r\n {\r\n field: \"image\",\r\n severity: \"critical\",\r\n message:\r\n error instanceof Error\r\n ? error.message\r\n : \"Failed to validate image\",\r\n actual: imageUrl,\r\n },\r\n ],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Validate image is accessible (returns 200 status)\r\n */\r\nexport async function validateImageAccessibility(\r\n imageUrl: string\r\n): Promise<boolean> {\r\n try {\r\n const response = await fetch(imageUrl, {\r\n method: \"HEAD\",\r\n headers: {\r\n \"User-Agent\":\r\n \"Mozilla/5.0 (compatible; SEO-Console/1.0; +https://example.com/bot)\",\r\n },\r\n signal: AbortSignal.timeout(10000),\r\n });\r\n\r\n return !!(response.ok && response.headers.get(\"content-type\")?.startsWith(\"image/\"));\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","import * as cheerio from \"cheerio\";\r\nimport type { SEORecord } from \"./seo-schema\";\r\n\r\nexport type ValidationSeverity = \"critical\" | \"warning\" | \"info\";\r\n\r\nexport interface ValidationIssue {\r\n field: string;\r\n severity: ValidationSeverity;\r\n message: string;\r\n expected?: string;\r\n actual?: string;\r\n}\r\n\r\nexport interface ValidationResult {\r\n isValid: boolean;\r\n issues: ValidationIssue[];\r\n validatedAt: Date;\r\n}\r\n\r\n/**\r\n * Validate HTML content against SEO record requirements\r\n */\r\nexport async function validateHTML(\r\n html: string,\r\n record: SEORecord,\r\n _baseUrl?: string\r\n): Promise<ValidationResult> {\r\n const issues: ValidationIssue[] = [];\r\n const $ = cheerio.load(html);\r\n\r\n // Validate title\r\n const title = $(\"title\").text().trim();\r\n if (record.title) {\r\n if (!title) {\r\n issues.push({\r\n field: \"title\",\r\n severity: \"critical\",\r\n message: \"Title tag is missing\",\r\n expected: record.title,\r\n });\r\n } else if (title !== record.title) {\r\n issues.push({\r\n field: \"title\",\r\n severity: \"warning\",\r\n message: \"Title tag does not match SEO record\",\r\n expected: record.title,\r\n actual: title,\r\n });\r\n }\r\n if (title.length > 60) {\r\n issues.push({\r\n field: \"title\",\r\n severity: \"warning\",\r\n message: \"Title exceeds recommended 60 characters\",\r\n actual: `${title.length} characters`,\r\n });\r\n }\r\n }\r\n\r\n // Validate meta description\r\n const metaDescription = $('meta[name=\"description\"]').attr(\"content\")?.trim();\r\n if (record.description) {\r\n if (!metaDescription) {\r\n issues.push({\r\n field: \"description\",\r\n severity: \"critical\",\r\n message: \"Meta description is missing\",\r\n expected: record.description,\r\n });\r\n } else if (metaDescription !== record.description) {\r\n issues.push({\r\n field: \"description\",\r\n severity: \"warning\",\r\n message: \"Meta description does not match SEO record\",\r\n expected: record.description,\r\n actual: metaDescription,\r\n });\r\n }\r\n if (metaDescription && metaDescription.length > 160) {\r\n issues.push({\r\n field: \"description\",\r\n severity: \"warning\",\r\n message: \"Description exceeds recommended 160 characters\",\r\n actual: `${metaDescription.length} characters`,\r\n });\r\n }\r\n }\r\n\r\n // Validate Open Graph tags\r\n if (record.ogTitle || record.ogDescription || record.ogImageUrl) {\r\n const ogTitle = $('meta[property=\"og:title\"]').attr(\"content\");\r\n const ogDescription = $('meta[property=\"og:description\"]').attr(\"content\");\r\n const ogImage = $('meta[property=\"og:image\"]').attr(\"content\");\r\n const ogType = $('meta[property=\"og:type\"]').attr(\"content\");\r\n const ogUrl = $('meta[property=\"og:url\"]').attr(\"content\");\r\n\r\n if (record.ogTitle && !ogTitle) {\r\n issues.push({\r\n field: \"og:title\",\r\n severity: \"critical\",\r\n message: \"Open Graph title is missing\",\r\n expected: record.ogTitle,\r\n });\r\n }\r\n\r\n if (record.ogDescription && !ogDescription) {\r\n issues.push({\r\n field: \"og:description\",\r\n severity: \"warning\",\r\n message: \"Open Graph description is missing\",\r\n expected: record.ogDescription,\r\n });\r\n }\r\n\r\n if (record.ogImageUrl && !ogImage) {\r\n issues.push({\r\n field: \"og:image\",\r\n severity: \"critical\",\r\n message: \"Open Graph image is missing\",\r\n expected: record.ogImageUrl,\r\n });\r\n }\r\n\r\n if (record.ogType && ogType !== record.ogType) {\r\n issues.push({\r\n field: \"og:type\",\r\n severity: \"warning\",\r\n message: \"Open Graph type does not match\",\r\n expected: record.ogType,\r\n actual: ogType,\r\n });\r\n }\r\n\r\n if (record.ogUrl && ogUrl !== record.ogUrl) {\r\n issues.push({\r\n field: \"og:url\",\r\n severity: \"warning\",\r\n message: \"Open Graph URL does not match\",\r\n expected: record.ogUrl,\r\n actual: ogUrl,\r\n });\r\n }\r\n }\r\n\r\n // Validate Twitter Card tags\r\n if (record.twitterCard || record.twitterTitle || record.twitterImageUrl) {\r\n const twitterCard = $('meta[name=\"twitter:card\"]').attr(\"content\");\r\n const twitterTitle = $('meta[name=\"twitter:title\"]').attr(\"content\");\r\n const _twitterDescription = $('meta[name=\"twitter:description\"]').attr(\"content\");\r\n const twitterImage = $('meta[name=\"twitter:image\"]').attr(\"content\");\r\n\r\n if (record.twitterCard && twitterCard !== record.twitterCard) {\r\n issues.push({\r\n field: \"twitter:card\",\r\n severity: \"warning\",\r\n message: \"Twitter card type does not match\",\r\n expected: record.twitterCard,\r\n actual: twitterCard,\r\n });\r\n }\r\n\r\n if (record.twitterTitle && !twitterTitle) {\r\n issues.push({\r\n field: \"twitter:title\",\r\n severity: \"warning\",\r\n message: \"Twitter title is missing\",\r\n expected: record.twitterTitle,\r\n });\r\n }\r\n\r\n if (record.twitterImageUrl && !twitterImage) {\r\n issues.push({\r\n field: \"twitter:image\",\r\n severity: \"warning\",\r\n message: \"Twitter image is missing\",\r\n expected: record.twitterImageUrl,\r\n });\r\n }\r\n }\r\n\r\n // Validate canonical URL\r\n if (record.canonicalUrl) {\r\n const canonical = $('link[rel=\"canonical\"]').attr(\"href\");\r\n if (!canonical) {\r\n issues.push({\r\n field: \"canonical\",\r\n severity: \"critical\",\r\n message: \"Canonical URL is missing\",\r\n expected: record.canonicalUrl,\r\n });\r\n } else if (canonical !== record.canonicalUrl) {\r\n issues.push({\r\n field: \"canonical\",\r\n severity: \"warning\",\r\n message: \"Canonical URL does not match\",\r\n expected: record.canonicalUrl,\r\n actual: canonical,\r\n });\r\n }\r\n\r\n // Validate canonical is absolute\r\n if (canonical && !canonical.startsWith(\"http://\") && !canonical.startsWith(\"https://\")) {\r\n issues.push({\r\n field: \"canonical\",\r\n severity: \"warning\",\r\n message: \"Canonical URL should be absolute\",\r\n actual: canonical,\r\n });\r\n }\r\n }\r\n\r\n // Check for noindex/nofollow robots meta tag\r\n const robotsMeta = $('meta[name=\"robots\"]').attr(\"content\");\r\n if (robotsMeta) {\r\n const robots = robotsMeta.toLowerCase();\r\n if (robots.includes(\"noindex\")) {\r\n issues.push({\r\n field: \"robots\",\r\n severity: \"critical\",\r\n message: \"Page has noindex meta tag - will NOT be indexed by search engines\",\r\n actual: robotsMeta,\r\n });\r\n }\r\n if (robots.includes(\"nofollow\")) {\r\n issues.push({\r\n field: \"robots\",\r\n severity: \"warning\",\r\n message: \"Page has nofollow meta tag - links won't be followed\",\r\n actual: robotsMeta,\r\n });\r\n }\r\n }\r\n\r\n // Check for duplicate titles (across the site - would need multiple HTML files)\r\n // This is a placeholder for future enhancement\r\n\r\n return {\r\n isValid: issues.filter((i) => i.severity === \"critical\").length === 0,\r\n issues,\r\n validatedAt: new Date(),\r\n };\r\n}\r\n\r\n/**\r\n * Fetch HTML from a URL and validate it\r\n */\r\nexport async function validateURL(\r\n url: string,\r\n record: SEORecord\r\n): Promise<ValidationResult> {\r\n try {\r\n const response = await fetch(url, {\r\n headers: {\r\n \"User-Agent\":\r\n \"Mozilla/5.0 (compatible; SEO-Console/1.0; +https://example.com/bot)\",\r\n },\r\n // Timeout after 10 seconds\r\n signal: AbortSignal.timeout(10000),\r\n });\r\n\r\n if (!response.ok) {\r\n return {\r\n isValid: false,\r\n issues: [\r\n {\r\n field: \"fetch\",\r\n severity: \"critical\",\r\n message: `Failed to fetch URL: ${response.status} ${response.statusText}`,\r\n actual: url,\r\n },\r\n ],\r\n validatedAt: new Date(),\r\n };\r\n }\r\n\r\n const html = await response.text();\r\n return validateHTML(html, record, url);\r\n } catch (error) {\r\n return {\r\n isValid: false,\r\n issues: [\r\n {\r\n field: \"fetch\",\r\n severity: \"critical\",\r\n message: error instanceof Error ? error.message : \"Failed to fetch URL\",\r\n actual: url,\r\n },\r\n ],\r\n validatedAt: new Date(),\r\n };\r\n }\r\n}\r\n","/**\r\n * File-based storage adapter\r\n * Stores SEO records in a JSON file\r\n * No database required!\r\n */\r\n\r\nimport { promises as fs } from \"fs\";\r\nimport { join } from \"path\";\r\nimport type { StorageAdapter } from \"./storage-adapter\";\r\nimport type { CreateSEORecord, UpdateSEORecord, SEORecord } from \"../../lib/validation/seo-schema\";\r\n\r\nexport class FileStorage implements StorageAdapter {\r\n private filePath: string;\r\n private records: SEORecord[] = [];\r\n private initialized = false;\r\n\r\n constructor(filePath: string = \"seo-records.json\") {\r\n this.filePath = filePath;\r\n }\r\n\r\n private async ensureInitialized() {\r\n if (this.initialized) return;\r\n\r\n try {\r\n const data = await fs.readFile(this.filePath, \"utf-8\");\r\n this.records = JSON.parse(data);\r\n } catch (error: any) {\r\n if (error.code === \"ENOENT\") {\r\n // File doesn't exist, start with empty array\r\n this.records = [];\r\n await this.save();\r\n } else {\r\n throw error;\r\n }\r\n }\r\n this.initialized = true;\r\n }\r\n\r\n private async save() {\r\n await fs.writeFile(this.filePath, JSON.stringify(this.records, null, 2), \"utf-8\");\r\n }\r\n\r\n async isAvailable(): Promise<boolean> {\r\n try {\r\n // Check if we can write to the directory\r\n const dir = this.filePath.includes(\"/\") ? this.filePath.substring(0, this.filePath.lastIndexOf(\"/\")) : \".\";\r\n await fs.access(dir);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n async getRecords(): Promise<SEORecord[]> {\r\n await this.ensureInitialized();\r\n return [...this.records];\r\n }\r\n\r\n async getRecordById(id: string): Promise<SEORecord | null> {\r\n await this.ensureInitialized();\r\n return this.records.find((r) => r.id === id) || null;\r\n }\r\n\r\n async getRecordByRoute(routePath: string): Promise<SEORecord | null> {\r\n await this.ensureInitialized();\r\n return this.records.find((r) => r.routePath === routePath) || null;\r\n }\r\n\r\n async createRecord(record: CreateSEORecord): Promise<SEORecord> {\r\n await this.ensureInitialized();\r\n \r\n const newRecord: SEORecord = {\r\n id: typeof crypto !== \"undefined\" && crypto.randomUUID \r\n ? crypto.randomUUID() \r\n : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,\r\n userId: \"file-user\", // File storage doesn't need user IDs\r\n routePath: record.routePath,\r\n title: record.title,\r\n description: record.description,\r\n keywords: record.keywords,\r\n ogTitle: record.ogTitle,\r\n ogDescription: record.ogDescription,\r\n ogImageUrl: record.ogImageUrl,\r\n ogImageWidth: record.ogImageWidth,\r\n ogImageHeight: record.ogImageHeight,\r\n ogType: record.ogType,\r\n ogUrl: record.ogUrl,\r\n ogSiteName: record.ogSiteName,\r\n twitterCard: record.twitterCard,\r\n twitterTitle: record.twitterTitle,\r\n twitterDescription: record.twitterDescription,\r\n twitterImageUrl: record.twitterImageUrl,\r\n twitterSite: record.twitterSite,\r\n twitterCreator: record.twitterCreator,\r\n canonicalUrl: record.canonicalUrl,\r\n robots: record.robots,\r\n author: record.author,\r\n publishedTime: record.publishedTime,\r\n modifiedTime: record.modifiedTime,\r\n structuredData: record.structuredData,\r\n validationStatus: \"pending\",\r\n lastValidatedAt: undefined,\r\n validationErrors: undefined,\r\n };\r\n\r\n this.records.push(newRecord);\r\n await this.save();\r\n return newRecord;\r\n }\r\n\r\n async updateRecord(record: UpdateSEORecord): Promise<SEORecord> {\r\n await this.ensureInitialized();\r\n \r\n const index = this.records.findIndex((r) => r.id === record.id);\r\n if (index === -1) {\r\n throw new Error(`SEO record with id ${record.id} not found`);\r\n }\r\n\r\n const updated: SEORecord = {\r\n ...this.records[index],\r\n ...record,\r\n };\r\n\r\n this.records[index] = updated;\r\n await this.save();\r\n return updated;\r\n }\r\n\r\n async deleteRecord(id: string): Promise<void> {\r\n await this.ensureInitialized();\r\n \r\n const index = this.records.findIndex((r) => r.id === id);\r\n if (index === -1) {\r\n throw new Error(`SEO record with id ${id} not found`);\r\n }\r\n\r\n this.records.splice(index, 1);\r\n await this.save();\r\n }\r\n}\r\n","/**\r\n * Supabase Storage Adapter\r\n * Wraps the existing Supabase database functions\r\n */\r\n\r\nimport type { StorageAdapter } from \"./storage-adapter\";\r\nimport type { CreateSEORecord, UpdateSEORecord, SEORecord } from \"../../lib/validation/seo-schema\";\r\nimport {\r\n getSEORecords,\r\n getSEORecordById,\r\n getSEORecordByRoute,\r\n createSEORecord,\r\n updateSEORecord,\r\n deleteSEORecord,\r\n} from \"../database/seo-records\";\r\n\r\nexport class SupabaseStorage implements StorageAdapter {\r\n constructor(\r\n private supabaseUrl: string,\r\n private supabaseKey: string\r\n ) {\r\n // Set environment variables for database functions\r\n if (typeof process !== \"undefined\") {\r\n process.env.NEXT_PUBLIC_SUPABASE_URL = supabaseUrl;\r\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = supabaseKey;\r\n }\r\n }\r\n\r\n async isAvailable(): Promise<boolean> {\r\n try {\r\n const result = await getSEORecords();\r\n return result.success;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n async getRecords(): Promise<SEORecord[]> {\r\n const result = await getSEORecords();\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to get records\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async getRecordById(id: string): Promise<SEORecord | null> {\r\n const result = await getSEORecordById(id);\r\n if (!result.success) {\r\n if (result.error?.message?.includes(\"not found\")) {\r\n return null;\r\n }\r\n throw new Error(result.error?.message || \"Failed to get record\");\r\n }\r\n return result.data || null;\r\n }\r\n\r\n async getRecordByRoute(routePath: string): Promise<SEORecord | null> {\r\n const result = await getSEORecordByRoute(routePath);\r\n if (!result.success) {\r\n if (result.error?.message?.includes(\"not found\")) {\r\n return null;\r\n }\r\n throw new Error(result.error?.message || \"Failed to get record\");\r\n }\r\n return result.data || null;\r\n }\r\n\r\n async createRecord(record: CreateSEORecord): Promise<SEORecord> {\r\n const result = await createSEORecord(record);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to create record\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async updateRecord(record: UpdateSEORecord): Promise<SEORecord> {\r\n const result = await updateSEORecord(record);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to update record\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async deleteRecord(id: string): Promise<void> {\r\n const result = await deleteSEORecord(id);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to delete record\");\r\n }\r\n }\r\n}\r\n","/**\r\n * Storage Factory\r\n * Creates the appropriate storage adapter based on configuration\r\n */\r\n\r\nimport type { StorageAdapter, StorageConfig } from \"./storage-adapter\";\r\nimport { FileStorage } from \"./file-storage\";\r\nimport { SupabaseStorage } from \"./supabase-storage\";\r\n\r\nexport function createStorageAdapter(config: StorageConfig): StorageAdapter {\r\n switch (config.type) {\r\n case \"file\":\r\n return new FileStorage(config.filePath || \"seo-records.json\");\r\n \r\n case \"supabase\":\r\n if (!config.supabaseUrl || !config.supabaseKey) {\r\n throw new Error(\"Supabase URL and key are required for Supabase storage\");\r\n }\r\n return new SupabaseStorage(config.supabaseUrl, config.supabaseKey);\r\n \r\n case \"memory\":\r\n // For testing - stores in memory only\r\n return new FileStorage(\":memory:\");\r\n \r\n default:\r\n throw new Error(`Unsupported storage type: ${config.type}`);\r\n }\r\n}\r\n\r\n/**\r\n * Auto-detect storage type from environment\r\n */\r\nexport function detectStorageConfig(): StorageConfig {\r\n // Check for Supabase config\r\n if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {\r\n return {\r\n type: \"supabase\",\r\n supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,\r\n supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,\r\n };\r\n }\r\n\r\n // Check for file storage config\r\n if (process.env.SEO_CONSOLE_STORAGE_PATH) {\r\n return {\r\n type: \"file\",\r\n filePath: process.env.SEO_CONSOLE_STORAGE_PATH,\r\n };\r\n }\r\n\r\n // Default to file storage\r\n return {\r\n type: \"file\",\r\n filePath: \"seo-records.json\",\r\n };\r\n}\r\n","import type { Metadata } from \"next\";\r\nimport { getSEORecordByRoute } from \"../lib/database/seo-records\";\r\nimport { detectStorageConfig, createStorageAdapter } from \"../lib/storage/storage-factory\";\r\nimport type { SEORecord } from \"../lib/validation/seo-schema\";\r\n\r\nexport interface GenerateMetadataOptions {\r\n routePath?: string;\r\n fallback?: Partial<Metadata>;\r\n}\r\n\r\n/**\r\n * Generate Next.js metadata from SEO records\r\n * \r\n * @param options - Configuration options\r\n * @param options.routePath - The route path to look up (defaults to current route)\r\n * @param options.fallback - Fallback metadata if no SEO record is found\r\n * @returns Next.js Metadata object\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata(): Promise<Metadata> {\r\n * return useGenerateMetadata({\r\n * routePath: \"/about\",\r\n * fallback: {\r\n * title: \"About Us\",\r\n * description: \"Learn more about our company\"\r\n * }\r\n * });\r\n * }\r\n * ```\r\n */\r\nexport async function useGenerateMetadata(\r\n options: GenerateMetadataOptions = {} as GenerateMetadataOptions\r\n): Promise<Metadata> {\r\n const { routePath, fallback = {} as Partial<Metadata> } = options;\r\n\r\n // If no route path provided, return fallback only\r\n if (!routePath) {\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n\r\n // Try to use storage adapter first (for file storage support)\r\n // Fall back to direct Supabase if storage adapter not available\r\n let record: SEORecord | null = null;\r\n\r\n try {\r\n const storageConfig = detectStorageConfig();\r\n if (storageConfig.type === \"file\" || storageConfig.type === \"memory\") {\r\n const storage = createStorageAdapter(storageConfig);\r\n record = await storage.getRecordByRoute(routePath);\r\n } else {\r\n // Use existing Supabase function\r\n const result = await getSEORecordByRoute(routePath);\r\n record = result.success ? result.data || null : null;\r\n }\r\n } catch (error) {\r\n // Fallback to Supabase if storage adapter fails\r\n const result = await getSEORecordByRoute(routePath);\r\n record = result.success ? result.data || null : null;\r\n }\r\n\r\n if (!record) {\r\n // Return fallback if record not found or error occurred\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n const metadata: Partial<Metadata> = {};\r\n\r\n // Basic metadata\r\n if (record.title) {\r\n metadata.title = record.title;\r\n }\r\n if (record.description) {\r\n metadata.description = record.description;\r\n }\r\n if (record.keywords && record.keywords.length > 0) {\r\n metadata.keywords = record.keywords;\r\n }\r\n if (record.author) {\r\n metadata.authors = [{ name: record.author }];\r\n }\r\n\r\n // Open Graph metadata\r\n if (\r\n record.ogTitle ||\r\n record.ogDescription ||\r\n record.ogImageUrl ||\r\n record.ogType\r\n ) {\r\n // Next.js only supports specific OG types\r\n const supportedOGTypes = [\"website\", \"article\", \"book\", \"profile\"] as const;\r\n const ogType = record.ogType && supportedOGTypes.includes(record.ogType as typeof supportedOGTypes[number])\r\n ? (record.ogType as typeof supportedOGTypes[number])\r\n : \"website\";\r\n\r\n const openGraph: NonNullable<Metadata[\"openGraph\"]> = {\r\n type: ogType,\r\n title: record.ogTitle || record.title || undefined,\r\n description: record.ogDescription || record.description || undefined,\r\n url: record.ogUrl || undefined,\r\n siteName: record.ogSiteName || undefined,\r\n };\r\n\r\n if (record.ogImageUrl) {\r\n openGraph.images = [\r\n {\r\n url: record.ogImageUrl,\r\n width: record.ogImageWidth || undefined,\r\n height: record.ogImageHeight || undefined,\r\n alt: record.ogTitle || record.title || undefined,\r\n },\r\n ];\r\n }\r\n\r\n // For article type, add published/modified times\r\n if (ogType === \"article\") {\r\n const articleOpenGraph = {\r\n ...openGraph,\r\n ...(record.publishedTime && {\r\n publishedTime: record.publishedTime.toISOString(),\r\n }),\r\n ...(record.modifiedTime && {\r\n modifiedTime: record.modifiedTime.toISOString(),\r\n }),\r\n } as Metadata[\"openGraph\"];\r\n metadata.openGraph = articleOpenGraph;\r\n } else {\r\n metadata.openGraph = openGraph;\r\n }\r\n }\r\n\r\n // Twitter Card metadata\r\n if (\r\n record.twitterCard ||\r\n record.twitterTitle ||\r\n record.twitterDescription ||\r\n record.twitterImageUrl\r\n ) {\r\n metadata.twitter = {\r\n card: record.twitterCard || \"summary\",\r\n title: record.twitterTitle || record.ogTitle || record.title || undefined,\r\n description:\r\n record.twitterDescription ||\r\n record.ogDescription ||\r\n record.description ||\r\n undefined,\r\n images: record.twitterImageUrl\r\n ? [record.twitterImageUrl]\r\n : undefined,\r\n site: record.twitterSite || undefined,\r\n creator: record.twitterCreator || undefined,\r\n };\r\n }\r\n\r\n // Canonical URL\r\n if (record.canonicalUrl) {\r\n metadata.alternates = {\r\n canonical: record.canonicalUrl,\r\n };\r\n }\r\n\r\n // Robots\r\n if (record.robots) {\r\n metadata.robots = record.robots as Metadata[\"robots\"];\r\n }\r\n\r\n // Merge with fallback (fallback takes precedence for missing values)\r\n return {\r\n ...fallback,\r\n ...metadata,\r\n // Ensure title and description from record override fallback if present\r\n title: record.title || fallback.title,\r\n description: record.description || fallback.description,\r\n // Merge openGraph if both exist\r\n openGraph: fallback.openGraph\r\n ? { ...metadata.openGraph, ...fallback.openGraph }\r\n : metadata.openGraph,\r\n // Merge twitter if both exist\r\n twitter: fallback.twitter\r\n ? { ...metadata.twitter, ...fallback.twitter }\r\n : metadata.twitter,\r\n };\r\n}\r\n\r\n/**\r\n * Helper to get route path from Next.js params\r\n * Useful for dynamic routes\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {\r\n * const routePath = getRoutePathFromParams(params, \"/blog/[slug]\");\r\n * return useGenerateMetadata({ routePath });\r\n * }\r\n * ```\r\n */\r\nexport function getRoutePathFromParams(\r\n params: Record<string, string | string[]>,\r\n pattern: string\r\n): string {\r\n let routePath = pattern;\r\n\r\n // Replace [param] and [...param] patterns with actual values\r\n for (const [key, value] of Object.entries(params)) {\r\n const paramValue = Array.isArray(value) ? value.join(\"/\") : value;\r\n routePath = routePath.replace(`[${key}]`, paramValue);\r\n routePath = routePath.replace(`[...${key}]`, paramValue);\r\n }\r\n\r\n return routePath;\r\n}\r\n","/**\r\n * Sitemap Generator\r\n * Generates sitemap.xml from SEO records\r\n * Only includes routes with canonical URLs\r\n */\r\n\r\nimport type { SEORecord } from \"./validation/seo-schema\";\r\n\r\nexport interface SitemapEntry {\r\n loc: string; // Canonical URL\r\n lastmod?: string; // ISO date string\r\n changefreq?: \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\r\n priority?: number; // 0.0 to 1.0\r\n}\r\n\r\nexport interface SitemapOptions {\r\n baseUrl: string;\r\n entries: SitemapEntry[];\r\n}\r\n\r\n/**\r\n * Generate sitemap.xml content\r\n */\r\nexport function generateSitemapXML(options: SitemapOptions): string {\r\n const { baseUrl, entries } = options;\r\n\r\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\r\n${entries\r\n .map((entry) => {\r\n const loc = entry.loc.startsWith(\"http\") ? entry.loc : new URL(entry.loc, baseUrl).toString();\r\n return ` <url>\r\n <loc>${escapeXML(loc)}</loc>${entry.lastmod ? `\\n <lastmod>${entry.lastmod}</lastmod>` : \"\"}${entry.changefreq ? `\\n <changefreq>${entry.changefreq}</changefreq>` : \"\"}${entry.priority !== undefined ? `\\n <priority>${entry.priority}</priority>` : \"\"}\r\n </url>`;\r\n })\r\n .join(\"\\n\")}\r\n</urlset>`;\r\n\r\n return xml;\r\n}\r\n\r\n/**\r\n * Convert SEO records to sitemap entries\r\n * Only includes records with canonical URLs\r\n */\r\nexport function seoRecordsToSitemapEntries(\r\n records: SEORecord[],\r\n baseUrl: string\r\n): SitemapEntry[] {\r\n return records\r\n .filter((record) => {\r\n // Only include records with canonical URLs\r\n return record.canonicalUrl && record.canonicalUrl.trim() !== \"\";\r\n })\r\n .map((record) => {\r\n const entry: SitemapEntry = {\r\n loc: record.canonicalUrl!,\r\n };\r\n\r\n // Add lastmod if available\r\n if (record.modifiedTime) {\r\n entry.lastmod = record.modifiedTime.toISOString().split(\"T\")[0];\r\n } else if (record.lastValidatedAt) {\r\n entry.lastmod = record.lastValidatedAt.toISOString().split(\"T\")[0];\r\n }\r\n\r\n // Set default changefreq and priority based on route\r\n if (record.routePath === \"/\") {\r\n entry.changefreq = \"daily\";\r\n entry.priority = 1.0;\r\n } else if (record.routePath.includes(\"/blog/\") || record.routePath.includes(\"/posts/\")) {\r\n entry.changefreq = \"weekly\";\r\n entry.priority = 0.8;\r\n } else {\r\n entry.changefreq = \"monthly\";\r\n entry.priority = 0.6;\r\n }\r\n\r\n return entry;\r\n })\r\n .sort((a, b) => {\r\n // Sort by priority (highest first), then by URL\r\n if (a.priority !== b.priority) {\r\n return (b.priority || 0) - (a.priority || 0);\r\n }\r\n return a.loc.localeCompare(b.loc);\r\n });\r\n}\r\n\r\n/**\r\n * Generate sitemap from SEO records\r\n */\r\nexport function generateSitemapFromRecords(\r\n records: SEORecord[],\r\n baseUrl: string\r\n): string {\r\n const entries = seoRecordsToSitemapEntries(records, baseUrl);\r\n return generateSitemapXML({ baseUrl, entries });\r\n}\r\n\r\n/**\r\n * Escape XML special characters\r\n */\r\nfunction escapeXML(str: string): string {\r\n return str\r\n .replace(/&/g, \"&amp;\")\r\n .replace(/</g, \"&lt;\")\r\n .replace(/>/g, \"&gt;\")\r\n .replace(/\"/g, \"&quot;\")\r\n .replace(/'/g, \"&apos;\");\r\n}\r\n\r\n/**\r\n * Validate sitemap entry\r\n */\r\nexport function validateSitemapEntry(entry: SitemapEntry): { valid: boolean; errors: string[] } {\r\n const errors: string[] = [];\r\n\r\n if (!entry.loc) {\r\n errors.push(\"Location (loc) is required\");\r\n } else {\r\n try {\r\n new URL(entry.loc);\r\n } catch {\r\n errors.push(\"Location must be a valid URL\");\r\n }\r\n }\r\n\r\n if (entry.priority !== undefined) {\r\n if (entry.priority < 0 || entry.priority > 1) {\r\n errors.push(\"Priority must be between 0.0 and 1.0\");\r\n }\r\n }\r\n\r\n if (entry.lastmod) {\r\n const date = new Date(entry.lastmod);\r\n if (isNaN(date.getTime())) {\r\n errors.push(\"Lastmod must be a valid date (YYYY-MM-DD)\");\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n };\r\n}\r\n","/**\r\n * Robots.txt Generator\r\n * Generates or updates robots.txt with sitemap reference\r\n */\r\n\r\nexport interface RobotsTxtOptions {\r\n userAgents?: Array<{\r\n agent: string;\r\n allow?: string[];\r\n disallow?: string[];\r\n }>;\r\n sitemapUrl?: string;\r\n crawlDelay?: number;\r\n}\r\n\r\n/**\r\n * Generate robots.txt content\r\n */\r\nexport function generateRobotsTxt(options: RobotsTxtOptions = {}): string {\r\n const { userAgents = [], sitemapUrl, crawlDelay } = options;\r\n\r\n let content = \"\";\r\n\r\n // Default user agent rules\r\n if (userAgents.length === 0) {\r\n content += \"User-agent: *\\n\";\r\n if (crawlDelay) {\r\n content += `Crawl-delay: ${crawlDelay}\\n`;\r\n }\r\n content += \"Allow: /\\n\";\r\n content += \"\\n\";\r\n } else {\r\n // Custom user agent rules\r\n for (const ua of userAgents) {\r\n content += `User-agent: ${ua.agent}\\n`;\r\n if (crawlDelay) {\r\n content += `Crawl-delay: ${crawlDelay}\\n`;\r\n }\r\n if (ua.allow) {\r\n for (const path of ua.allow) {\r\n content += `Allow: ${path}\\n`;\r\n }\r\n }\r\n if (ua.disallow) {\r\n for (const path of ua.disallow) {\r\n content += `Disallow: ${path}\\n`;\r\n }\r\n }\r\n content += \"\\n\";\r\n }\r\n }\r\n\r\n // Add sitemap reference\r\n if (sitemapUrl) {\r\n content += `Sitemap: ${sitemapUrl}\\n`;\r\n }\r\n\r\n return content.trim();\r\n}\r\n\r\n/**\r\n * Update existing robots.txt to include sitemap\r\n * Preserves existing content\r\n */\r\nexport function updateRobotsTxtWithSitemap(\r\n existingContent: string,\r\n sitemapUrl: string\r\n): string {\r\n // Check if sitemap already exists\r\n const sitemapRegex = /^Sitemap:\\s*.+$/m;\r\n if (sitemapRegex.test(existingContent)) {\r\n // Replace existing sitemap line\r\n return existingContent.replace(sitemapRegex, `Sitemap: ${sitemapUrl}`);\r\n }\r\n\r\n // Add sitemap at the end\r\n const trimmed = existingContent.trim();\r\n return trimmed ? `${trimmed}\\n\\nSitemap: ${sitemapUrl}` : `Sitemap: ${sitemapUrl}`;\r\n}\r\n\r\n/**\r\n * Extract sitemap URL from robots.txt\r\n */\r\nexport function extractSitemapFromRobotsTxt(content: string): string | null {\r\n const match = content.match(/^Sitemap:\\s*(.+)$/m);\r\n return match ? match[1].trim() : null;\r\n}\r\n","/**\r\n * Metadata Extractor\r\n * Extracts SEO metadata from HTML pages or Next.js metadata exports\r\n */\r\n\r\nimport * as cheerio from \"cheerio\";\r\nimport type { SEORecord } from \"../lib/validation/seo-schema\";\r\n\r\nexport interface ExtractedMetadata {\r\n title?: string;\r\n description?: string;\r\n ogTitle?: string;\r\n ogDescription?: string;\r\n ogImageUrl?: string;\r\n ogType?: string;\r\n ogUrl?: string;\r\n canonicalUrl?: string;\r\n robots?: string;\r\n keywords?: string[];\r\n}\r\n\r\n/**\r\n * Extract metadata from HTML string\r\n */\r\nexport function extractMetadataFromHTML(html: string, baseUrl?: string): ExtractedMetadata {\r\n const $ = cheerio.load(html);\r\n const metadata: ExtractedMetadata = {};\r\n\r\n // Basic metadata\r\n metadata.title = $(\"title\").text() || undefined;\r\n metadata.description = $('meta[name=\"description\"]').attr(\"content\") || undefined;\r\n metadata.robots = $('meta[name=\"robots\"]').attr(\"content\") || undefined;\r\n \r\n const keywords = $('meta[name=\"keywords\"]').attr(\"content\");\r\n if (keywords) {\r\n metadata.keywords = keywords.split(\",\").map((k) => k.trim());\r\n }\r\n\r\n // Open Graph\r\n metadata.ogTitle = $('meta[property=\"og:title\"]').attr(\"content\") || undefined;\r\n metadata.ogDescription = $('meta[property=\"og:description\"]').attr(\"content\") || undefined;\r\n metadata.ogImageUrl = $('meta[property=\"og:image\"]').attr(\"content\") || undefined;\r\n metadata.ogType = $('meta[property=\"og:type\"]').attr(\"content\") || undefined;\r\n metadata.ogUrl = $('meta[property=\"og:url\"]').attr(\"content\") || undefined;\r\n\r\n // Canonical URL\r\n metadata.canonicalUrl = $('link[rel=\"canonical\"]').attr(\"href\") || undefined;\r\n\r\n // Make URLs absolute if baseUrl provided\r\n if (baseUrl) {\r\n if (metadata.ogImageUrl && !metadata.ogImageUrl.startsWith(\"http\")) {\r\n metadata.ogImageUrl = new URL(metadata.ogImageUrl, baseUrl).toString();\r\n }\r\n if (metadata.canonicalUrl && !metadata.canonicalUrl.startsWith(\"http\")) {\r\n metadata.canonicalUrl = new URL(metadata.canonicalUrl, baseUrl).toString();\r\n }\r\n }\r\n\r\n return metadata;\r\n}\r\n\r\n/**\r\n * Extract metadata from a live URL\r\n */\r\nexport async function extractMetadataFromURL(url: string): Promise<ExtractedMetadata> {\r\n try {\r\n const response = await fetch(url, {\r\n headers: {\r\n \"User-Agent\": \"SEO-Console/1.0\",\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch ${url}: ${response.statusText}`);\r\n }\r\n\r\n const html = await response.text();\r\n return extractMetadataFromHTML(html, url);\r\n } catch (error) {\r\n console.error(`Error extracting metadata from ${url}:`, error);\r\n return {};\r\n }\r\n}\r\n\r\n/**\r\n * Convert extracted metadata to SEO record format\r\n */\r\nexport function metadataToSEORecord(\r\n metadata: ExtractedMetadata,\r\n routePath: string,\r\n userId: string = \"extracted\"\r\n): Partial<SEORecord> {\r\n return {\r\n userId,\r\n routePath,\r\n title: metadata.title,\r\n description: metadata.description,\r\n keywords: metadata.keywords,\r\n ogTitle: metadata.ogTitle,\r\n ogDescription: metadata.ogDescription,\r\n ogImageUrl: metadata.ogImageUrl,\r\n ogType: metadata.ogType as SEORecord[\"ogType\"],\r\n ogUrl: metadata.ogUrl,\r\n canonicalUrl: metadata.canonicalUrl,\r\n robots: metadata.robots as SEORecord[\"robots\"],\r\n validationStatus: \"pending\",\r\n };\r\n}\r\n\r\n/**\r\n * Crawl a site and extract metadata from all pages\r\n */\r\nexport async function crawlSiteForSEO(\r\n baseUrl: string,\r\n routes: string[]\r\n): Promise<Map<string, ExtractedMetadata>> {\r\n const results = new Map<string, ExtractedMetadata>();\r\n\r\n for (const route of routes) {\r\n const url = new URL(route, baseUrl).toString();\r\n try {\r\n const metadata = await extractMetadataFromURL(url);\r\n results.set(route, metadata);\r\n \r\n // Rate limiting - wait 100ms between requests\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n } catch (error) {\r\n console.error(`Failed to crawl ${url}:`, error);\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n","/**\r\n * Route Discovery Utility\r\n * Automatically discovers Next.js routes from the file system\r\n */\r\n\r\nimport { promises as fs } from \"fs\";\r\nimport { join, relative, dirname } from \"path\";\r\nimport { glob } from \"glob\";\r\n\r\nexport interface DiscoveredRoute {\r\n routePath: string;\r\n filePath: string;\r\n isDynamic: boolean;\r\n isCatchAll: boolean;\r\n params: string[];\r\n}\r\n\r\n/**\r\n * Discover all Next.js routes from the app directory\r\n */\r\nexport async function discoverNextJSRoutes(\r\n appDir: string = \"app\",\r\n rootDir: string = process.cwd()\r\n): Promise<DiscoveredRoute[]> {\r\n const routes: DiscoveredRoute[] = [];\r\n const appPath = join(rootDir, appDir);\r\n\r\n try {\r\n // Find all page.tsx files\r\n const pageFiles = await glob(\"**/page.tsx\", {\r\n cwd: appPath,\r\n absolute: false,\r\n ignore: [\"**/node_modules/**\", \"**/.next/**\"],\r\n });\r\n\r\n for (const file of pageFiles) {\r\n const route = fileToRoute(file, appDir);\r\n if (route) {\r\n routes.push(route);\r\n }\r\n }\r\n } catch (error) {\r\n console.error(\"Error discovering routes:\", error);\r\n }\r\n\r\n return routes;\r\n}\r\n\r\n/**\r\n * Convert file path to route path\r\n */\r\nfunction fileToRoute(filePath: string, appDir: string): DiscoveredRoute | null {\r\n // Remove app/ prefix and /page.tsx suffix\r\n let routePath = filePath\r\n .replace(/^app\\//, \"\")\r\n .replace(/\\/page\\.tsx$/, \"\")\r\n .replace(/\\/page$/, \"\");\r\n\r\n // Handle root route\r\n if (routePath === \"page\" || routePath === \"\") {\r\n routePath = \"/\";\r\n } else {\r\n routePath = \"/\" + routePath;\r\n }\r\n\r\n // Detect dynamic segments\r\n const segments = routePath.split(\"/\").filter(Boolean);\r\n const params: string[] = [];\r\n let isDynamic = false;\r\n let isCatchAll = false;\r\n\r\n for (const segment of segments) {\r\n if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\r\n // Catch-all route: [...slug]\r\n const param = segment.slice(4, -1);\r\n params.push(param);\r\n isDynamic = true;\r\n isCatchAll = true;\r\n } else if (segment.startsWith(\"[\") && segment.endsWith(\"]\")) {\r\n // Dynamic route: [slug]\r\n const param = segment.slice(1, -1);\r\n params.push(param);\r\n isDynamic = true;\r\n }\r\n }\r\n\r\n return {\r\n routePath,\r\n filePath: join(appDir, filePath),\r\n isDynamic,\r\n isCatchAll,\r\n params,\r\n };\r\n}\r\n\r\n/**\r\n * Generate example route paths for dynamic routes\r\n * Useful for creating sample SEO records\r\n */\r\nexport function generateExamplePaths(route: DiscoveredRoute, count: number = 3): string[] {\r\n if (!route.isDynamic) {\r\n return [route.routePath];\r\n }\r\n\r\n const examples: string[] = [];\r\n const segments = route.routePath.split(\"/\").filter(Boolean);\r\n\r\n for (let i = 0; i < count; i++) {\r\n let examplePath = \"\";\r\n for (const segment of segments) {\r\n if (segment.startsWith(\"[...\")) {\r\n // Catch-all: use multiple segments\r\n examplePath += `/example-${i}-part-1/example-${i}-part-2`;\r\n } else if (segment.startsWith(\"[\")) {\r\n // Dynamic: use example value\r\n examplePath += `/example-${i}`;\r\n } else {\r\n examplePath += `/${segment}`;\r\n }\r\n }\r\n examples.push(examplePath || \"/\");\r\n }\r\n\r\n return examples;\r\n}\r\n","/**\r\n * Crawlability Validator\r\n * Validates that search engines can crawl and index pages\r\n */\r\n\r\nimport { extractMetadataFromHTML } from \"../metadata-extractor\";\r\n\r\nexport interface CrawlabilityResult {\r\n crawlable: boolean;\r\n indexable: boolean;\r\n issues: CrawlabilityIssue[];\r\n warnings: CrawlabilityIssue[];\r\n}\r\n\r\nexport interface CrawlabilityIssue {\r\n type: \"noindex\" | \"nofollow\" | \"robots_blocked\" | \"auth_wall\" | \"redirect_loop\" | \"404\" | \"canonical_missing\";\r\n severity: \"error\" | \"warning\";\r\n message: string;\r\n page: string;\r\n}\r\n\r\n/**\r\n * Validate that a page is crawlable and indexable\r\n */\r\nexport async function validateCrawlability(\r\n url: string,\r\n html?: string\r\n): Promise<CrawlabilityResult> {\r\n const issues: CrawlabilityIssue[] = [];\r\n const warnings: CrawlabilityIssue[] = [];\r\n\r\n try {\r\n // Fetch page if HTML not provided\r\n if (!html) {\r\n const response = await fetch(url, {\r\n headers: {\r\n \"User-Agent\": \"SEO-Console-Bot/1.0\",\r\n },\r\n redirect: \"follow\",\r\n });\r\n\r\n // Check HTTP status\r\n if (response.status === 404) {\r\n issues.push({\r\n type: \"404\",\r\n severity: \"error\",\r\n message: \"Page returns 404 Not Found\",\r\n page: url,\r\n });\r\n return {\r\n crawlable: false,\r\n indexable: false,\r\n issues,\r\n warnings,\r\n };\r\n }\r\n\r\n if (response.status !== 200) {\r\n issues.push({\r\n type: \"404\",\r\n severity: \"error\",\r\n message: `Page returns HTTP ${response.status}`,\r\n page: url,\r\n });\r\n }\r\n\r\n // Check for authentication wall\r\n if (response.status === 401 || response.status === 403) {\r\n issues.push({\r\n type: \"auth_wall\",\r\n severity: \"error\",\r\n message: \"Page requires authentication (401/403)\",\r\n page: url,\r\n });\r\n }\r\n\r\n html = await response.text();\r\n }\r\n\r\n // Parse HTML\r\n const metadata = extractMetadataFromHTML(html, url);\r\n\r\n // Check robots meta tag\r\n if (metadata.robots) {\r\n const robots = metadata.robots.toLowerCase();\r\n \r\n if (robots.includes(\"noindex\")) {\r\n issues.push({\r\n type: \"noindex\",\r\n severity: \"error\",\r\n message: \"Page has noindex meta tag - will not be indexed\",\r\n page: url,\r\n });\r\n }\r\n\r\n if (robots.includes(\"nofollow\")) {\r\n warnings.push({\r\n type: \"nofollow\",\r\n severity: \"warning\",\r\n message: \"Page has nofollow meta tag - links won't be followed\",\r\n page: url,\r\n });\r\n }\r\n }\r\n\r\n // Check for canonical URL\r\n if (!metadata.canonicalUrl) {\r\n warnings.push({\r\n type: \"canonical_missing\",\r\n severity: \"warning\",\r\n message: \"Page missing canonical URL\",\r\n page: url,\r\n });\r\n }\r\n\r\n // Check for redirect loops (would need to track redirects)\r\n // This is handled by fetch with redirect: \"follow\"\r\n\r\n return {\r\n crawlable: issues.filter((i) => i.type !== \"noindex\").length === 0,\r\n indexable: !issues.some((i) => i.type === \"noindex\"),\r\n issues,\r\n warnings,\r\n };\r\n } catch (error) {\r\n issues.push({\r\n type: \"404\",\r\n severity: \"error\",\r\n message: error instanceof Error ? error.message : \"Failed to fetch page\",\r\n page: url,\r\n });\r\n\r\n return {\r\n crawlable: false,\r\n indexable: false,\r\n issues,\r\n warnings,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Validate robots.txt allows crawling\r\n */\r\nexport async function validateRobotsTxt(\r\n baseUrl: string,\r\n routePath: string\r\n): Promise<{ allowed: boolean; reason?: string }> {\r\n try {\r\n const robotsUrl = new URL(\"/robots.txt\", baseUrl).toString();\r\n const response = await fetch(robotsUrl);\r\n\r\n if (!response.ok) {\r\n return { allowed: true, reason: \"robots.txt not found (default: allow all)\" };\r\n }\r\n\r\n const robotsTxt = await response.text();\r\n const lines = robotsTxt.split(\"\\n\").map((l) => l.trim());\r\n\r\n let currentUserAgent = \"*\";\r\n let isAllowed = true;\r\n\r\n for (const line of lines) {\r\n if (line.startsWith(\"#\") || !line) continue;\r\n\r\n const [directive, ...valueParts] = line.split(\":\").map((s) => s.trim());\r\n const value = valueParts.join(\":\").trim();\r\n\r\n if (directive.toLowerCase() === \"user-agent\") {\r\n currentUserAgent = value;\r\n } else if (directive.toLowerCase() === \"disallow\") {\r\n if (currentUserAgent === \"*\" || currentUserAgent.toLowerCase() === \"googlebot\") {\r\n if (value === \"/\") {\r\n isAllowed = false;\r\n return { allowed: false, reason: \"robots.txt disallows all pages\" };\r\n } else if (routePath.startsWith(value)) {\r\n isAllowed = false;\r\n return { allowed: false, reason: `robots.txt disallows ${routePath}` };\r\n }\r\n }\r\n } else if (directive.toLowerCase() === \"allow\") {\r\n if (value === \"/\" || routePath.startsWith(value)) {\r\n isAllowed = true;\r\n }\r\n }\r\n }\r\n\r\n return { allowed: isAllowed };\r\n } catch (error) {\r\n // If robots.txt can't be fetched, assume allowed\r\n return { allowed: true, reason: \"Could not fetch robots.txt\" };\r\n }\r\n}\r\n\r\n/**\r\n * Check if site is publicly accessible (no auth wall)\r\n */\r\nexport async function validatePublicAccess(url: string): Promise<{ accessible: boolean; requiresAuth: boolean }> {\r\n try {\r\n const response = await fetch(url, {\r\n headers: {\r\n \"User-Agent\": \"SEO-Console-Bot/1.0\",\r\n },\r\n });\r\n\r\n const requiresAuth = response.status === 401 || response.status === 403;\r\n const accessible = response.ok && !requiresAuth;\r\n\r\n return { accessible, requiresAuth };\r\n } catch {\r\n return { accessible: false, requiresAuth: false };\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAAmC;AACnC,qBAAwB;AAExB,eAAsB,eAAe;AACnC,QAAM,cAAc,UAAM,wBAAQ;AAElC,aAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ;AAAA,MACE,SAAS;AAAA,QACP,SAAS;AACP,iBAAO,YAAY,OAAO;AAAA,QAC5B;AAAA,QACA,OAAO,cAAyE;AAC9E,cAAI;AACF,yBAAa;AAAA,cAAQ,CAAC,EAAE,MAAM,OAAO,QAAQ,MAC3C,YAAY,IAAI,MAAM,OAAO,OAAqC;AAAA,YACpE;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACbA,SAAS,wBAAwB,KAA8B;AAC7D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,OAAO,IAAI,SAAS;AAAA,IACpB,aAAa,IAAI,eAAe;AAAA,IAChC,UAAU,IAAI,YAAY;AAAA,IAC1B,SAAS,IAAI,YAAY;AAAA,IACzB,eAAe,IAAI,kBAAkB;AAAA,IACrC,YAAY,IAAI,gBAAgB;AAAA,IAChC,cAAc,IAAI,kBAAkB;AAAA,IACpC,eAAe,IAAI,mBAAmB;AAAA,IACtC,QAAS,IAAI,WAAmC;AAAA,IAChD,OAAO,IAAI,UAAU;AAAA,IACrB,YAAY,IAAI,gBAAgB;AAAA,IAChC,aAAc,IAAI,gBAA6C;AAAA,IAC/D,cAAc,IAAI,iBAAiB;AAAA,IACnC,oBAAoB,IAAI,uBAAuB;AAAA,IAC/C,iBAAiB,IAAI,qBAAqB;AAAA,IAC1C,aAAa,IAAI,gBAAgB;AAAA,IACjC,gBAAgB,IAAI,mBAAmB;AAAA,IACvC,cAAc,IAAI,iBAAiB;AAAA,IACnC,QAAQ,IAAI,UAAU;AAAA,IACtB,QAAQ,IAAI,UAAU;AAAA,IACtB,eAAe,IAAI,iBACf,IAAI,KAAK,IAAI,cAAc,IAC3B;AAAA,IACJ,cAAc,IAAI,gBACd,IAAI,KAAK,IAAI,aAAa,IAC1B;AAAA,IACJ,gBAAgB,IAAI,kBACf,IAAI,kBACL;AAAA,IACJ,kBAAmB,IAAI,qBAAuD;AAAA,IAC9E,iBAAiB,IAAI,oBACjB,IAAI,KAAK,IAAI,iBAAiB,IAC9B;AAAA,IACJ,kBAAkB,IAAI,oBACjB,IAAI,oBACL;AAAA,IACJ,WAAW,IAAI,KAAK,IAAI,UAAU;AAAA,IAClC,WAAW,IAAI,KAAK,IAAI,UAAU;AAAA,EACpC;AACF;AAGA,SAAS,kBACP,QAC2D;AAC3D,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,YAAY,OAAO;AAAA,IACnB,OAAO,OAAO,SAAS;AAAA,IACvB,aAAa,OAAO,eAAe;AAAA,IACnC,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,WAAW;AAAA,IAC5B,gBAAgB,OAAO,iBAAiB;AAAA,IACxC,cAAc,OAAO,cAAc;AAAA,IACnC,gBAAgB,OAAO,gBAAgB;AAAA,IACvC,iBAAiB,OAAO,iBAAiB;AAAA,IACzC,SAAS,OAAO,UAAU;AAAA,IAC1B,QAAQ,OAAO,SAAS;AAAA,IACxB,cAAc,OAAO,cAAc;AAAA,IACnC,cAAc,OAAO,eAAe;AAAA,IACpC,eAAe,OAAO,gBAAgB;AAAA,IACtC,qBAAqB,OAAO,sBAAsB;AAAA,IAClD,mBAAmB,OAAO,mBAAmB;AAAA,IAC7C,cAAc,OAAO,eAAe;AAAA,IACpC,iBAAiB,OAAO,kBAAkB;AAAA,IAC1C,eAAe,OAAO,gBAAgB;AAAA,IACtC,QAAQ,OAAO,UAAU;AAAA,IACzB,QAAQ,OAAO,UAAU;AAAA,IACzB,gBAAgB,OAAO,eAAe,YAAY,KAAK;AAAA,IACvD,eAAe,OAAO,cAAc,YAAY,KAAK;AAAA,IACrD,iBAAkB,OAAO,kBAAuG;AAAA,EAClI;AACF;AAGA,SAAS,kBACP,QACqC;AACrC,QAAM,SAAmC,CAAC;AAE1C,MAAI,OAAO,cAAc,OAAW,QAAO,aAAa,OAAO;AAC/D,MAAI,OAAO,UAAU,OAAW,QAAO,QAAQ,OAAO,SAAS;AAC/D,MAAI,OAAO,gBAAgB;AACzB,WAAO,cAAc,OAAO,eAAe;AAC7C,MAAI,OAAO,aAAa,OAAW,QAAO,WAAW,OAAO,YAAY;AACxE,MAAI,OAAO,YAAY,OAAW,QAAO,WAAW,OAAO,WAAW;AACtE,MAAI,OAAO,kBAAkB;AAC3B,WAAO,iBAAiB,OAAO,iBAAiB;AAClD,MAAI,OAAO,eAAe;AACxB,WAAO,eAAe,OAAO,cAAc;AAC7C,MAAI,OAAO,iBAAiB;AAC1B,WAAO,iBAAiB,OAAO,gBAAgB;AACjD,MAAI,OAAO,kBAAkB;AAC3B,WAAO,kBAAkB,OAAO,iBAAiB;AACnD,MAAI,OAAO,WAAW,OAAW,QAAO,UAAU,OAAO,UAAU;AACnE,MAAI,OAAO,UAAU,OAAW,QAAO,SAAS,OAAO,SAAS;AAChE,MAAI,OAAO,eAAe;AACxB,WAAO,eAAe,OAAO,cAAc;AAC7C,MAAI,OAAO,gBAAgB;AACzB,WAAO,eAAe,OAAO,eAAe;AAC9C,MAAI,OAAO,iBAAiB;AAC1B,WAAO,gBAAgB,OAAO,gBAAgB;AAChD,MAAI,OAAO,uBAAuB;AAChC,WAAO,sBAAsB,OAAO,sBAAsB;AAC5D,MAAI,OAAO,oBAAoB;AAC7B,WAAO,oBAAoB,OAAO,mBAAmB;AACvD,MAAI,OAAO,gBAAgB;AACzB,WAAO,eAAe,OAAO,eAAe;AAC9C,MAAI,OAAO,mBAAmB;AAC5B,WAAO,kBAAkB,OAAO,kBAAkB;AACpD,MAAI,OAAO,iBAAiB;AAC1B,WAAO,gBAAgB,OAAO,gBAAgB;AAChD,MAAI,OAAO,WAAW,OAAW,QAAO,SAAS,OAAO,UAAU;AAClE,MAAI,OAAO,WAAW,OAAW,QAAO,SAAS,OAAO,UAAU;AAClE,MAAI,OAAO,kBAAkB;AAC3B,WAAO,iBAAiB,OAAO,eAAe,YAAY,KAAK;AACjE,MAAI,OAAO,iBAAiB;AAC1B,WAAO,gBAAgB,OAAO,cAAc,YAAY,KAAK;AAC/D,MAAI,OAAO,mBAAmB;AAC5B,WAAO,kBAAmB,OAAO,kBAAuG;AAC1I,MAAI,OAAO,qBAAqB;AAC9B,WAAO,oBAAoB,OAAO,oBAAoB;AACxD,MAAI,OAAO,oBAAoB;AAC7B,WAAO,oBAAoB,OAAO,iBAAiB,YAAY,KAAK;AACtE,MAAI,OAAO,qBAAqB;AAC9B,WAAO,oBAAqB,OAAO,oBAA2G;AAEhJ,SAAO;AACT;AAUA,eAAsB,gBAA8C;AAClE,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,aAAa,EAClB,OAAO,GAAG,EACV,GAAG,WAAW,KAAK,EAAE,EACrB,MAAM,cAAc,EAAE,WAAW,MAAM,CAAC;AAE3C,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,UAAM,WAAW,QAAQ,CAAC,GAAG,IAAI,uBAAuB;AACxD,WAAO,EAAE,SAAS,MAAM,MAAM,QAAQ;AAAA,EACxC,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;AAKA,eAAsB,iBACpB,IAC4B;AAC5B,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,aAAa,EAClB,OAAO,GAAG,EACV,GAAG,MAAM,EAAE,EACX,GAAG,WAAW,KAAK,EAAE,EACrB,OAAO;AAEV,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,sBAAsB;AAAA,MACzC;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,wBAAwB,IAAI,EAAE;AAAA,EAC9D,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;AAKA,eAAsB,oBACpB,WACmC;AACnC,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,aAAa,EAClB,OAAO,GAAG,EACV,GAAG,cAAc,SAAS,EAC1B,GAAG,WAAW,KAAK,EAAE,EACrB,YAAY;AAEf,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,QAAI,CAAC,MAAM;AACT,aAAO,EAAE,SAAS,MAAM,MAAM,KAAK;AAAA,IACrC;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,wBAAwB,IAAI,EAAE;AAAA,EAC9D,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;AAKA,eAAsB,gBACpB,QAC4B;AAC5B,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,aAAa,kBAAkB,EAAE,GAAG,QAAQ,QAAQ,KAAK,GAAG,CAAC;AAEnE,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,aAAa,EAClB,OAAO,UAAU,EACjB,OAAO,EACP,OAAO;AAEV,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,wBAAwB,IAAI,EAAE;AAAA,EAC9D,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;AAKA,eAAsB,gBACpB,QAC4B;AAC5B,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,EAAE,IAAI,GAAG,WAAW,IAAI;AAC9B,UAAM,oBAAoB,kBAAkB,UAAU;AAEtD,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,aAAa,EAClB,OAAO,iBAAiB,EACxB,GAAG,MAAM,EAAE,EACX,GAAG,WAAW,KAAK,EAAE,EACrB,OAAO,EACP,OAAO;AAEV,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,sBAAsB;AAAA,MACzC;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,wBAAwB,IAAI,EAAE;AAAA,EAC9D,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;AAKA,eAAsB,gBAAgB,IAAmC;AACvE,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM;AAAA,MACJ,MAAM,EAAE,KAAK;AAAA,IACf,IAAI,MAAM,SAAS,KAAK,QAAQ;AAEhC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,IAAI,MAAM,wBAAwB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,EAAE,MAAM,IAAI,MAAM,SACrB,KAAK,aAAa,EAClB,OAAO,EACP,GAAG,MAAM,EAAE,EACX,GAAG,WAAW,KAAK,EAAE;AAExB,QAAI,OAAO;AACT,aAAO,EAAE,SAAS,OAAO,MAAM;AAAA,IACjC;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,OAAU;AAAA,EAC1C,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,eAAe;AAAA,IACnE;AAAA,EACF;AACF;;;ACnZA,iBAAkB;AAGX,IAAM,eAAe,aAAE,KAAK;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,oBAAoB,aAAE,KAAK;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,yBAAyB,aAAE,KAAK;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,oBAAoB,aAAE,OAAO;AAAA;AAAA,EAExC,OAAO,aAAE,OAAO,EAAE,IAAI,IAAI,qCAAqC,EAAE,SAAS;AAAA,EAC1E,aAAa,aACV,OAAO,EACP,IAAI,KAAK,4CAA4C,EACrD,SAAS;AAAA,EACZ,UAAU,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,SAAS;AAAA;AAAA,EAGvC,SAAS,aAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACrC,eAAe,aAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC5C,YAAY,aAAE,OAAO,EAAE,IAAI,qBAAqB,EAAE,SAAS;AAAA,EAC3D,cAAc,aAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,SAAS;AAAA,EAC7D,eAAe,aAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,SAAS;AAAA,EAC9D,QAAQ,aAAa,SAAS;AAAA,EAC9B,OAAO,aAAE,OAAO,EAAE,IAAI,qBAAqB,EAAE,SAAS;AAAA,EACtD,YAAY,aAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAGhC,aAAa,kBAAkB,SAAS;AAAA,EACxC,cAAc,aAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC1C,oBAAoB,aAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACjD,iBAAiB,aAAE,OAAO,EAAE,IAAI,qBAAqB,EAAE,SAAS;AAAA,EAChE,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,EACjC,gBAAgB,aAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAGpC,cAAc,aAAE,OAAO,EAAE,IAAI,qBAAqB,EAAE,SAAS;AAAA,EAC7D,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,QAAQ,aAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,eAAe,aAAE,OAAO,KAAK,EAAE,SAAS;AAAA,EACxC,cAAc,aAAE,OAAO,KAAK,EAAE,SAAS;AAAA;AAAA,EAGvC,gBAAgB,aAAE,OAAO,aAAE,QAAQ,CAAC,EAAE,SAAS;AACjD,CAAC;AAGM,IAAM,kBAAkB,kBAAkB,OAAO;AAAA,EACtD,IAAI,aAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,QAAQ,aAAE,OAAO,EAAE,KAAK;AAAA,EACxB,WAAW,aACR,OAAO,EACP,IAAI,GAAG,wBAAwB,EAC/B,MAAM,SAAS,8BAA8B;AAAA,EAChD,kBAAkB,uBAAuB,SAAS;AAAA,EAClD,iBAAiB,aAAE,OAAO,KAAK,EAAE,SAAS;AAAA,EAC1C,kBAAkB,aAAE,OAAO,aAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EACjD,WAAW,aAAE,OAAO,KAAK,EAAE,SAAS;AAAA,EACpC,WAAW,aAAE,OAAO,KAAK,EAAE,SAAS;AACtC,CAAC;AAGM,IAAM,wBAAwB,gBAAgB,KAAK;AAAA,EACxD,IAAI;AAAA,EACJ,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,WAAW;AACb,CAAC;AAGM,IAAM,wBAAwB,gBAClC,QAAQ,EACR,SAAS,EAAE,IAAI,KAAK,CAAC,EACrB,KAAK;AAAA,EACJ,QAAQ;AAAA,EACR,WAAW;AACb,CAAC;;;ACpGH,mBAAkB;AAuBlB,eAAsB,gBACpB,UACA,eACA,gBACgC;AAChC,QAAM,SAA0C,CAAC;AAEjD,MAAI;AAEF,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,SAAS;AAAA,QACP,cACE;AAAA,MACJ;AAAA,MACA,QAAQ,YAAY,QAAQ,IAAK;AAAA;AAAA,IACnC,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,OAAO;AAAA,YACP,UAAU;AAAA,YACV,SAAS,0BAA0B,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,YACzE,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,MAAM,SAAS,YAAY;AAC/C,UAAM,SAAS,OAAO,KAAK,WAAW;AAGtC,UAAM,WAAW,OAAO,UAAU,OAAO;AACzC,QAAI,WAAW,GAAG;AAChB,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ,GAAG,SAAS,QAAQ,CAAC,CAAC;AAAA,MAChC,CAAC;AAAA,IACH;AAGA,UAAM,WAAW,UAAM,aAAAA,SAAM,MAAM,EAAE,SAAS;AAC9C,UAAM,EAAE,OAAO,QAAQ,OAAO,IAAI;AAElC,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,OAAO;AAAA,YACP,UAAU;AAAA,YACV,SAAS;AAAA,YACT,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAIA,UAAM,mBAAmB;AACzB,UAAM,oBAAoB;AAC1B,UAAM,cAAc,QAAQ;AAC5B,UAAM,yBAAyB,mBAAmB;AAElD,QAAI,QAAQ,oBAAoB,SAAS,mBAAmB;AAC1D,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,4CAA4C,gBAAgB,IAAI,iBAAiB;AAAA,QAC1F,UAAU,GAAG,gBAAgB,IAAI,iBAAiB;AAAA,QAClD,QAAQ,GAAG,KAAK,IAAI,MAAM;AAAA,MAC5B,CAAC;AAAA,IACH;AAGA,QAAI,KAAK,IAAI,cAAc,sBAAsB,IAAI,KAAK;AACxD,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,GAAG,YAAY,QAAQ,CAAC,CAAC;AAAA,MACnC,CAAC;AAAA,IACH;AAGA,UAAM,mBAAmB,CAAC,QAAQ,OAAO,OAAO,QAAQ,QAAQ,KAAK;AACrE,QAAI,CAAC,UAAU,CAAC,iBAAiB,SAAS,OAAO,YAAY,CAAC,GAAG;AAC/D,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,UAAU;AAAA,MACpB,CAAC;AAAA,IACH;AAGA,QAAI,iBAAiB,UAAU,eAAe;AAC5C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,GAAG,aAAa;AAAA,QAC1B,QAAQ,GAAG,KAAK;AAAA,MAClB,CAAC;AAAA,IACH;AAEA,QAAI,kBAAkB,WAAW,gBAAgB;AAC/C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,GAAG,cAAc;AAAA,QAC3B,QAAQ,GAAG,MAAM;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,UAAU,EAAE,WAAW;AAAA,MACpE;AAAA,MACA,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,MAAM,OAAO;AAAA,MACf;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,QACN;AAAA,UACE,OAAO;AAAA,UACP,UAAU;AAAA,UACV,SACE,iBAAiB,QACb,MAAM,UACN;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC7KA,cAAyB;AAsBzB,eAAsB,aACpB,MACA,QACA,UAC2B;AAC3B,QAAM,SAA4B,CAAC;AACnC,QAAM,IAAY,aAAK,IAAI;AAG3B,QAAM,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK;AACrC,MAAI,OAAO,OAAO;AAChB,QAAI,CAAC,OAAO;AACV,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH,WAAW,UAAU,OAAO,OAAO;AACjC,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,QAAI,MAAM,SAAS,IAAI;AACrB,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ,GAAG,MAAM,MAAM;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,kBAAkB,EAAE,0BAA0B,EAAE,KAAK,SAAS,GAAG,KAAK;AAC5E,MAAI,OAAO,aAAa;AACtB,QAAI,CAAC,iBAAiB;AACpB,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH,WAAW,oBAAoB,OAAO,aAAa;AACjD,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,QAAI,mBAAmB,gBAAgB,SAAS,KAAK;AACnD,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ,GAAG,gBAAgB,MAAM;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,OAAO,WAAW,OAAO,iBAAiB,OAAO,YAAY;AAC/D,UAAM,UAAU,EAAE,2BAA2B,EAAE,KAAK,SAAS;AAC7D,UAAM,gBAAgB,EAAE,iCAAiC,EAAE,KAAK,SAAS;AACzE,UAAM,UAAU,EAAE,2BAA2B,EAAE,KAAK,SAAS;AAC7D,UAAM,SAAS,EAAE,0BAA0B,EAAE,KAAK,SAAS;AAC3D,UAAM,QAAQ,EAAE,yBAAyB,EAAE,KAAK,SAAS;AAEzD,QAAI,OAAO,WAAW,CAAC,SAAS;AAC9B,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,iBAAiB,CAAC,eAAe;AAC1C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,cAAc,CAAC,SAAS;AACjC,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,UAAU,WAAW,OAAO,QAAQ;AAC7C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,SAAS,UAAU,OAAO,OAAO;AAC1C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,OAAO,eAAe,OAAO,gBAAgB,OAAO,iBAAiB;AACvE,UAAM,cAAc,EAAE,2BAA2B,EAAE,KAAK,SAAS;AACjE,UAAM,eAAe,EAAE,4BAA4B,EAAE,KAAK,SAAS;AACnE,UAAM,sBAAsB,EAAE,kCAAkC,EAAE,KAAK,SAAS;AAChF,UAAM,eAAe,EAAE,4BAA4B,EAAE,KAAK,SAAS;AAEnE,QAAI,OAAO,eAAe,gBAAgB,OAAO,aAAa;AAC5D,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,gBAAgB,CAAC,cAAc;AACxC,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,mBAAmB,CAAC,cAAc;AAC3C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,OAAO,cAAc;AACvB,UAAM,YAAY,EAAE,uBAAuB,EAAE,KAAK,MAAM;AACxD,QAAI,CAAC,WAAW;AACd,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH,WAAW,cAAc,OAAO,cAAc;AAC5C,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,OAAO;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAGA,QAAI,aAAa,CAAC,UAAU,WAAW,SAAS,KAAK,CAAC,UAAU,WAAW,UAAU,GAAG;AACtF,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,aAAa,EAAE,qBAAqB,EAAE,KAAK,SAAS;AAC1D,MAAI,YAAY;AACd,UAAM,SAAS,WAAW,YAAY;AACtC,QAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,QAAI,OAAO,SAAS,UAAU,GAAG;AAC/B,aAAO,KAAK;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAKA,SAAO;AAAA,IACL,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,UAAU,EAAE,WAAW;AAAA,IACpE;AAAA,IACA,aAAa,oBAAI,KAAK;AAAA,EACxB;AACF;AAKA,eAAsB,YACpB,KACA,QAC2B;AAC3B,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS;AAAA,QACP,cACE;AAAA,MACJ;AAAA;AAAA,MAEA,QAAQ,YAAY,QAAQ,GAAK;AAAA,IACnC,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,OAAO;AAAA,YACP,UAAU;AAAA,YACV,SAAS,wBAAwB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,YACvE,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,QACA,aAAa,oBAAI,KAAK;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,aAAa,MAAM,QAAQ,GAAG;AAAA,EACvC,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,QACN;AAAA,UACE,OAAO;AAAA,UACP,UAAU;AAAA,UACV,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAClD,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,aAAa,oBAAI,KAAK;AAAA,IACxB;AAAA,EACF;AACF;;;AC7RA,gBAA+B;AAKxB,IAAM,cAAN,MAA4C;AAAA,EAKjD,YAAY,WAAmB,oBAAoB;AAHnD,SAAQ,UAAuB,CAAC;AAChC,SAAQ,cAAc;AAGpB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAc,oBAAoB;AAChC,QAAI,KAAK,YAAa;AAEtB,QAAI;AACF,YAAM,OAAO,MAAM,UAAAC,SAAG,SAAS,KAAK,UAAU,OAAO;AACrD,WAAK,UAAU,KAAK,MAAM,IAAI;AAAA,IAChC,SAAS,OAAY;AACnB,UAAI,MAAM,SAAS,UAAU;AAE3B,aAAK,UAAU,CAAC;AAChB,cAAM,KAAK,KAAK;AAAA,MAClB,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AACA,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAc,OAAO;AACnB,UAAM,UAAAA,SAAG,UAAU,KAAK,UAAU,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,GAAG,OAAO;AAAA,EAClF;AAAA,EAEA,MAAM,cAAgC;AACpC,QAAI;AAEF,YAAM,MAAM,KAAK,SAAS,SAAS,GAAG,IAAI,KAAK,SAAS,UAAU,GAAG,KAAK,SAAS,YAAY,GAAG,CAAC,IAAI;AACvG,YAAM,UAAAA,SAAG,OAAO,GAAG;AACnB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,aAAmC;AACvC,UAAM,KAAK,kBAAkB;AAC7B,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,MAAM,cAAc,IAAuC;AACzD,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAAA,EAClD;AAAA,EAEA,MAAM,iBAAiB,WAA8C;AACnE,UAAM,KAAK,kBAAkB;AAC7B,WAAO,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS,KAAK;AAAA,EAChE;AAAA,EAEA,MAAM,aAAa,QAA6C;AAC9D,UAAM,KAAK,kBAAkB;AAE7B,UAAM,YAAuB;AAAA,MAC3B,IAAI,OAAO,WAAW,eAAe,OAAO,aACxC,OAAO,WAAW,IAClB,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC,CAAC;AAAA,MAC/D,QAAQ;AAAA;AAAA,MACR,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,eAAe,OAAO;AAAA,MACtB,YAAY,OAAO;AAAA,MACnB,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,MACtB,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,MACrB,oBAAoB,OAAO;AAAA,MAC3B,iBAAiB,OAAO;AAAA,MACxB,aAAa,OAAO;AAAA,MACpB,gBAAgB,OAAO;AAAA,MACvB,cAAc,OAAO;AAAA,MACrB,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,MACf,eAAe,OAAO;AAAA,MACtB,cAAc,OAAO;AAAA,MACrB,gBAAgB,OAAO;AAAA,MACvB,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACpB;AAEA,SAAK,QAAQ,KAAK,SAAS;AAC3B,UAAM,KAAK,KAAK;AAChB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,QAA6C;AAC9D,UAAM,KAAK,kBAAkB;AAE7B,UAAM,QAAQ,KAAK,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE;AAC9D,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI,MAAM,sBAAsB,OAAO,EAAE,YAAY;AAAA,IAC7D;AAEA,UAAM,UAAqB;AAAA,MACzB,GAAG,KAAK,QAAQ,KAAK;AAAA,MACrB,GAAG;AAAA,IACL;AAEA,SAAK,QAAQ,KAAK,IAAI;AACtB,UAAM,KAAK,KAAK;AAChB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,IAA2B;AAC5C,UAAM,KAAK,kBAAkB;AAE7B,UAAM,QAAQ,KAAK,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;AACvD,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI,MAAM,sBAAsB,EAAE,YAAY;AAAA,IACtD;AAEA,SAAK,QAAQ,OAAO,OAAO,CAAC;AAC5B,UAAM,KAAK,KAAK;AAAA,EAClB;AACF;;;AC3HO,IAAM,kBAAN,MAAgD;AAAA,EACrD,YACU,aACA,aACR;AAFQ;AACA;AAGR,QAAI,OAAO,YAAY,aAAa;AAClC,cAAQ,IAAI,2BAA2B;AACvC,cAAQ,IAAI,gCAAgC;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,MAAM,cAAgC;AACpC,QAAI;AACF,YAAM,SAAS,MAAM,cAAc;AACnC,aAAO,OAAO;AAAA,IAChB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,aAAmC;AACvC,UAAM,SAAS,MAAM,cAAc;AACnC,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,uBAAuB;AAAA,IAClE;AACA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,cAAc,IAAuC;AACzD,UAAM,SAAS,MAAM,iBAAiB,EAAE;AACxC,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,OAAO,OAAO,SAAS,SAAS,WAAW,GAAG;AAChD,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,sBAAsB;AAAA,IACjE;AACA,WAAO,OAAO,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,iBAAiB,WAA8C;AACnE,UAAM,SAAS,MAAM,oBAAoB,SAAS;AAClD,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,OAAO,OAAO,SAAS,SAAS,WAAW,GAAG;AAChD,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,sBAAsB;AAAA,IACjE;AACA,WAAO,OAAO,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,aAAa,QAA6C;AAC9D,UAAM,SAAS,MAAM,gBAAgB,MAAM;AAC3C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,yBAAyB;AAAA,IACpE;AACA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,aAAa,QAA6C;AAC9D,UAAM,SAAS,MAAM,gBAAgB,MAAM;AAC3C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,yBAAyB;AAAA,IACpE;AACA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,aAAa,IAA2B;AAC5C,UAAM,SAAS,MAAM,gBAAgB,EAAE;AACvC,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,OAAO,OAAO,WAAW,yBAAyB;AAAA,IACpE;AAAA,EACF;AACF;;;AChFO,SAAS,qBAAqB,QAAuC;AAC1E,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,IAAI,YAAY,OAAO,YAAY,kBAAkB;AAAA,IAE9D,KAAK;AACH,UAAI,CAAC,OAAO,eAAe,CAAC,OAAO,aAAa;AAC9C,cAAM,IAAI,MAAM,wDAAwD;AAAA,MAC1E;AACA,aAAO,IAAI,gBAAgB,OAAO,aAAa,OAAO,WAAW;AAAA,IAEnE,KAAK;AAEH,aAAO,IAAI,YAAY,UAAU;AAAA,IAEnC;AACE,YAAM,IAAI,MAAM,6BAA6B,OAAO,IAAI,EAAE;AAAA,EAC9D;AACF;AAKO,SAAS,sBAAqC;AAEnD,MAAI,QAAQ,IAAI,4BAA4B,QAAQ,IAAI,+BAA+B;AACrF,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa,QAAQ,IAAI;AAAA,MACzB,aAAa,QAAQ,IAAI;AAAA,IAC3B;AAAA,EACF;AAGA,MAAI,QAAQ,IAAI,0BAA0B;AACxC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,QAAQ,IAAI;AAAA,IACxB;AAAA,EACF;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AACF;;;ACxBA,eAAsB,oBACpB,UAAmC,CAAC,GACjB;AACnB,QAAM,EAAE,WAAW,WAAW,CAAC,EAAuB,IAAI;AAG1D,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,aAAa,SAAS;AAAA,MACtB,GAAG;AAAA,IACL;AAAA,EACF;AAIA,MAAI,SAA2B;AAE/B,MAAI;AACF,UAAM,gBAAgB,oBAAoB;AAC1C,QAAI,cAAc,SAAS,UAAU,cAAc,SAAS,UAAU;AACpE,YAAM,UAAU,qBAAqB,aAAa;AAClD,eAAS,MAAM,QAAQ,iBAAiB,SAAS;AAAA,IACnD,OAAO;AAEL,YAAM,SAAS,MAAM,oBAAoB,SAAS;AAClD,eAAS,OAAO,UAAU,OAAO,QAAQ,OAAO;AAAA,IAClD;AAAA,EACF,SAAS,OAAO;AAEd,UAAM,SAAS,MAAM,oBAAoB,SAAS;AAClD,aAAS,OAAO,UAAU,OAAO,QAAQ,OAAO;AAAA,EAClD;AAEA,MAAI,CAAC,QAAQ;AAEX,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,aAAa,SAAS;AAAA,MACtB,GAAG;AAAA,IACL;AAAA,EACF;AACA,QAAM,WAA8B,CAAC;AAGrC,MAAI,OAAO,OAAO;AAChB,aAAS,QAAQ,OAAO;AAAA,EAC1B;AACA,MAAI,OAAO,aAAa;AACtB,aAAS,cAAc,OAAO;AAAA,EAChC;AACA,MAAI,OAAO,YAAY,OAAO,SAAS,SAAS,GAAG;AACjD,aAAS,WAAW,OAAO;AAAA,EAC7B;AACA,MAAI,OAAO,QAAQ;AACjB,aAAS,UAAU,CAAC,EAAE,MAAM,OAAO,OAAO,CAAC;AAAA,EAC7C;AAGA,MACE,OAAO,WACP,OAAO,iBACP,OAAO,cACP,OAAO,QACP;AAEA,UAAM,mBAAmB,CAAC,WAAW,WAAW,QAAQ,SAAS;AACjE,UAAM,SAAS,OAAO,UAAU,iBAAiB,SAAS,OAAO,MAAyC,IACrG,OAAO,SACR;AAEJ,UAAM,YAAgD;AAAA,MACpD,MAAM;AAAA,MACN,OAAO,OAAO,WAAW,OAAO,SAAS;AAAA,MACzC,aAAa,OAAO,iBAAiB,OAAO,eAAe;AAAA,MAC3D,KAAK,OAAO,SAAS;AAAA,MACrB,UAAU,OAAO,cAAc;AAAA,IACjC;AAEA,QAAI,OAAO,YAAY;AACrB,gBAAU,SAAS;AAAA,QACjB;AAAA,UACE,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO,gBAAgB;AAAA,UAC9B,QAAQ,OAAO,iBAAiB;AAAA,UAChC,KAAK,OAAO,WAAW,OAAO,SAAS;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW,WAAW;AACxB,YAAM,mBAAmB;AAAA,QACvB,GAAG;AAAA,QACH,GAAI,OAAO,iBAAiB;AAAA,UAC1B,eAAe,OAAO,cAAc,YAAY;AAAA,QAClD;AAAA,QACA,GAAI,OAAO,gBAAgB;AAAA,UACzB,cAAc,OAAO,aAAa,YAAY;AAAA,QAChD;AAAA,MACF;AACA,eAAS,YAAY;AAAA,IACvB,OAAO;AACL,eAAS,YAAY;AAAA,IACvB;AAAA,EACF;AAGA,MACE,OAAO,eACP,OAAO,gBACP,OAAO,sBACP,OAAO,iBACP;AACA,aAAS,UAAU;AAAA,MACjB,MAAM,OAAO,eAAe;AAAA,MAC5B,OAAO,OAAO,gBAAgB,OAAO,WAAW,OAAO,SAAS;AAAA,MAChE,aACE,OAAO,sBACP,OAAO,iBACP,OAAO,eACP;AAAA,MACF,QAAQ,OAAO,kBACX,CAAC,OAAO,eAAe,IACvB;AAAA,MACJ,MAAM,OAAO,eAAe;AAAA,MAC5B,SAAS,OAAO,kBAAkB;AAAA,IACpC;AAAA,EACF;AAGA,MAAI,OAAO,cAAc;AACvB,aAAS,aAAa;AAAA,MACpB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AAGA,MAAI,OAAO,QAAQ;AACjB,aAAS,SAAS,OAAO;AAAA,EAC3B;AAGA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA;AAAA,IAEH,OAAO,OAAO,SAAS,SAAS;AAAA,IAChC,aAAa,OAAO,eAAe,SAAS;AAAA;AAAA,IAE5C,WAAW,SAAS,YAChB,EAAE,GAAG,SAAS,WAAW,GAAG,SAAS,UAAU,IAC/C,SAAS;AAAA;AAAA,IAEb,SAAS,SAAS,UACd,EAAE,GAAG,SAAS,SAAS,GAAG,SAAS,QAAQ,IAC3C,SAAS;AAAA,EACf;AACF;AAcO,SAAS,uBACd,QACA,SACQ;AACR,MAAI,YAAY;AAGhB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,aAAa,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI;AAC5D,gBAAY,UAAU,QAAQ,IAAI,GAAG,KAAK,UAAU;AACpD,gBAAY,UAAU,QAAQ,OAAO,GAAG,KAAK,UAAU;AAAA,EACzD;AAEA,SAAO;AACT;;;AClMO,SAAS,mBAAmB,SAAiC;AAClE,QAAM,EAAE,SAAS,QAAQ,IAAI;AAE7B,QAAM,MAAM;AAAA;AAAA,EAEZ,QACC,IAAI,CAAC,UAAU;AACd,UAAM,MAAM,MAAM,IAAI,WAAW,MAAM,IAAI,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,OAAO,EAAE,SAAS;AAC5F,WAAO;AAAA,WACA,UAAU,GAAG,CAAC,SAAS,MAAM,UAAU;AAAA,eAAkB,MAAM,OAAO,eAAe,EAAE,GAAG,MAAM,aAAa;AAAA,kBAAqB,MAAM,UAAU,kBAAkB,EAAE,GAAG,MAAM,aAAa,SAAY;AAAA,gBAAmB,MAAM,QAAQ,gBAAgB,EAAE;AAAA;AAAA,EAEpQ,CAAC,EACA,KAAK,IAAI,CAAC;AAAA;AAGX,SAAO;AACT;AAMO,SAAS,2BACd,SACA,SACgB;AAChB,SAAO,QACJ,OAAO,CAAC,WAAW;AAElB,WAAO,OAAO,gBAAgB,OAAO,aAAa,KAAK,MAAM;AAAA,EAC/D,CAAC,EACA,IAAI,CAAC,WAAW;AACf,UAAM,QAAsB;AAAA,MAC1B,KAAK,OAAO;AAAA,IACd;AAGA,QAAI,OAAO,cAAc;AACvB,YAAM,UAAU,OAAO,aAAa,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IAChE,WAAW,OAAO,iBAAiB;AACjC,YAAM,UAAU,OAAO,gBAAgB,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IACnE;AAGA,QAAI,OAAO,cAAc,KAAK;AAC5B,YAAM,aAAa;AACnB,YAAM,WAAW;AAAA,IACnB,WAAW,OAAO,UAAU,SAAS,QAAQ,KAAK,OAAO,UAAU,SAAS,SAAS,GAAG;AACtF,YAAM,aAAa;AACnB,YAAM,WAAW;AAAA,IACnB,OAAO;AACL,YAAM,aAAa;AACnB,YAAM,WAAW;AAAA,IACnB;AAEA,WAAO;AAAA,EACT,CAAC,EACA,KAAK,CAAC,GAAG,MAAM;AAEd,QAAI,EAAE,aAAa,EAAE,UAAU;AAC7B,cAAQ,EAAE,YAAY,MAAM,EAAE,YAAY;AAAA,IAC5C;AACA,WAAO,EAAE,IAAI,cAAc,EAAE,GAAG;AAAA,EAClC,CAAC;AACL;AAKO,SAAS,2BACd,SACA,SACQ;AACR,QAAM,UAAU,2BAA2B,SAAS,OAAO;AAC3D,SAAO,mBAAmB,EAAE,SAAS,QAAQ,CAAC;AAChD;AAKA,SAAS,UAAU,KAAqB;AACtC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;;;AC5FO,SAAS,kBAAkB,UAA4B,CAAC,GAAW;AACxE,QAAM,EAAE,aAAa,CAAC,GAAG,YAAY,WAAW,IAAI;AAEpD,MAAI,UAAU;AAGd,MAAI,WAAW,WAAW,GAAG;AAC3B,eAAW;AACX,QAAI,YAAY;AACd,iBAAW,gBAAgB,UAAU;AAAA;AAAA,IACvC;AACA,eAAW;AACX,eAAW;AAAA,EACb,OAAO;AAEL,eAAW,MAAM,YAAY;AAC3B,iBAAW,eAAe,GAAG,KAAK;AAAA;AAClC,UAAI,YAAY;AACd,mBAAW,gBAAgB,UAAU;AAAA;AAAA,MACvC;AACA,UAAI,GAAG,OAAO;AACZ,mBAAW,QAAQ,GAAG,OAAO;AAC3B,qBAAW,UAAU,IAAI;AAAA;AAAA,QAC3B;AAAA,MACF;AACA,UAAI,GAAG,UAAU;AACf,mBAAW,QAAQ,GAAG,UAAU;AAC9B,qBAAW,aAAa,IAAI;AAAA;AAAA,QAC9B;AAAA,MACF;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AAGA,MAAI,YAAY;AACd,eAAW,YAAY,UAAU;AAAA;AAAA,EACnC;AAEA,SAAO,QAAQ,KAAK;AACtB;AAMO,SAAS,2BACd,iBACA,YACQ;AAER,QAAM,eAAe;AACrB,MAAI,aAAa,KAAK,eAAe,GAAG;AAEtC,WAAO,gBAAgB,QAAQ,cAAc,YAAY,UAAU,EAAE;AAAA,EACvE;AAGA,QAAM,UAAU,gBAAgB,KAAK;AACrC,SAAO,UAAU,GAAG,OAAO;AAAA;AAAA,WAAgB,UAAU,KAAK,YAAY,UAAU;AAClF;;;ACzEA,IAAAC,WAAyB;AAmBlB,SAAS,wBAAwB,MAAc,SAAqC;AACzF,QAAM,IAAY,cAAK,IAAI;AAC3B,QAAM,WAA8B,CAAC;AAGrC,WAAS,QAAQ,EAAE,OAAO,EAAE,KAAK,KAAK;AACtC,WAAS,cAAc,EAAE,0BAA0B,EAAE,KAAK,SAAS,KAAK;AACxE,WAAS,SAAS,EAAE,qBAAqB,EAAE,KAAK,SAAS,KAAK;AAE9D,QAAM,WAAW,EAAE,uBAAuB,EAAE,KAAK,SAAS;AAC1D,MAAI,UAAU;AACZ,aAAS,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,EAC7D;AAGA,WAAS,UAAU,EAAE,2BAA2B,EAAE,KAAK,SAAS,KAAK;AACrE,WAAS,gBAAgB,EAAE,iCAAiC,EAAE,KAAK,SAAS,KAAK;AACjF,WAAS,aAAa,EAAE,2BAA2B,EAAE,KAAK,SAAS,KAAK;AACxE,WAAS,SAAS,EAAE,0BAA0B,EAAE,KAAK,SAAS,KAAK;AACnE,WAAS,QAAQ,EAAE,yBAAyB,EAAE,KAAK,SAAS,KAAK;AAGjE,WAAS,eAAe,EAAE,uBAAuB,EAAE,KAAK,MAAM,KAAK;AAGnE,MAAI,SAAS;AACX,QAAI,SAAS,cAAc,CAAC,SAAS,WAAW,WAAW,MAAM,GAAG;AAClE,eAAS,aAAa,IAAI,IAAI,SAAS,YAAY,OAAO,EAAE,SAAS;AAAA,IACvE;AACA,QAAI,SAAS,gBAAgB,CAAC,SAAS,aAAa,WAAW,MAAM,GAAG;AACtE,eAAS,eAAe,IAAI,IAAI,SAAS,cAAc,OAAO,EAAE,SAAS;AAAA,IAC3E;AAAA,EACF;AAEA,SAAO;AACT;AAKA,eAAsB,uBAAuB,KAAyC;AACpF,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS;AAAA,QACP,cAAc;AAAA,MAChB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,mBAAmB,GAAG,KAAK,SAAS,UAAU,EAAE;AAAA,IAClE;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,wBAAwB,MAAM,GAAG;AAAA,EAC1C,SAAS,OAAO;AACd,YAAQ,MAAM,kCAAkC,GAAG,KAAK,KAAK;AAC7D,WAAO,CAAC;AAAA,EACV;AACF;AAKO,SAAS,oBACd,UACA,WACA,SAAiB,aACG;AACpB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,aAAa,SAAS;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,SAAS,SAAS;AAAA,IAClB,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS;AAAA,IACjB,OAAO,SAAS;AAAA,IAChB,cAAc,SAAS;AAAA,IACvB,QAAQ,SAAS;AAAA,IACjB,kBAAkB;AAAA,EACpB;AACF;AAKA,eAAsB,gBACpB,SACA,QACyC;AACzC,QAAM,UAAU,oBAAI,IAA+B;AAEnD,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,IAAI,IAAI,OAAO,OAAO,EAAE,SAAS;AAC7C,QAAI;AACF,YAAM,WAAW,MAAM,uBAAuB,GAAG;AACjD,cAAQ,IAAI,OAAO,QAAQ;AAG3B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,IACzD,SAAS,OAAO;AACd,cAAQ,MAAM,mBAAmB,GAAG,KAAK,KAAK;AAAA,IAChD;AAAA,EACF;AAEA,SAAO;AACT;;;AC9HA,kBAAwC;AACxC,kBAAqB;AAarB,eAAsB,qBACpB,SAAiB,OACjB,UAAkB,QAAQ,IAAI,GACF;AAC5B,QAAM,SAA4B,CAAC;AACnC,QAAM,cAAU,kBAAK,SAAS,MAAM;AAEpC,MAAI;AAEF,UAAM,YAAY,UAAM,kBAAK,eAAe;AAAA,MAC1C,KAAK;AAAA,MACL,UAAU;AAAA,MACV,QAAQ,CAAC,sBAAsB,aAAa;AAAA,IAC9C,CAAC;AAED,eAAW,QAAQ,WAAW;AAC5B,YAAM,QAAQ,YAAY,MAAM,MAAM;AACtC,UAAI,OAAO;AACT,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAAA,EAClD;AAEA,SAAO;AACT;AAKA,SAAS,YAAY,UAAkB,QAAwC;AAE7E,MAAI,YAAY,SACb,QAAQ,UAAU,EAAE,EACpB,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,WAAW,EAAE;AAGxB,MAAI,cAAc,UAAU,cAAc,IAAI;AAC5C,gBAAY;AAAA,EACd,OAAO;AACL,gBAAY,MAAM;AAAA,EACpB;AAGA,QAAM,WAAW,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO;AACpD,QAAM,SAAmB,CAAC;AAC1B,MAAI,YAAY;AAChB,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,WAAW,MAAM,KAAK,QAAQ,SAAS,GAAG,GAAG;AAEvD,YAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE;AACjC,aAAO,KAAK,KAAK;AACjB,kBAAY;AACZ,mBAAa;AAAA,IACf,WAAW,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAG;AAE3D,YAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE;AACjC,aAAO,KAAK,KAAK;AACjB,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,cAAU,kBAAK,QAAQ,QAAQ;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACrEA,eAAsB,qBACpB,KACA,MAC6B;AAC7B,QAAM,SAA8B,CAAC;AACrC,QAAM,WAAgC,CAAC;AAEvC,MAAI;AAEF,QAAI,CAAC,MAAM;AACT,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,SAAS;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,QACA,UAAU;AAAA,MACZ,CAAC;AAGD,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,QACR,CAAC;AACD,eAAO;AAAA,UACL,WAAW;AAAA,UACX,WAAW;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS,qBAAqB,SAAS,MAAM;AAAA,UAC7C,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAGA,UAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B;AAGA,UAAM,WAAW,wBAAwB,MAAM,GAAG;AAGlD,QAAI,SAAS,QAAQ;AACnB,YAAM,SAAS,SAAS,OAAO,YAAY;AAE3C,UAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,UAAI,OAAO,SAAS,UAAU,GAAG;AAC/B,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,UAAU;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,CAAC,SAAS,cAAc;AAC1B,eAAS,KAAK;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAKA,WAAO;AAAA,MACL,WAAW,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,WAAW;AAAA,MACjE,WAAW,CAAC,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS;AAAA,MACnD;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAClD,MAAM;AAAA,IACR,CAAC;AAED,WAAO;AAAA,MACL,WAAW;AAAA,MACX,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAKA,eAAsB,kBACpB,SACA,WACgD;AAChD,MAAI;AACF,UAAM,YAAY,IAAI,IAAI,eAAe,OAAO,EAAE,SAAS;AAC3D,UAAM,WAAW,MAAM,MAAM,SAAS;AAEtC,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,EAAE,SAAS,MAAM,QAAQ,4CAA4C;AAAA,IAC9E;AAEA,UAAM,YAAY,MAAM,SAAS,KAAK;AACtC,UAAM,QAAQ,UAAU,MAAM,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAEvD,QAAI,mBAAmB;AACvB,QAAI,YAAY;AAEhB,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,WAAW,GAAG,KAAK,CAAC,KAAM;AAEnC,YAAM,CAAC,WAAW,GAAG,UAAU,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACtE,YAAM,QAAQ,WAAW,KAAK,GAAG,EAAE,KAAK;AAExC,UAAI,UAAU,YAAY,MAAM,cAAc;AAC5C,2BAAmB;AAAA,MACrB,WAAW,UAAU,YAAY,MAAM,YAAY;AACjD,YAAI,qBAAqB,OAAO,iBAAiB,YAAY,MAAM,aAAa;AAC9E,cAAI,UAAU,KAAK;AACjB,wBAAY;AACZ,mBAAO,EAAE,SAAS,OAAO,QAAQ,iCAAiC;AAAA,UACpE,WAAW,UAAU,WAAW,KAAK,GAAG;AACtC,wBAAY;AACZ,mBAAO,EAAE,SAAS,OAAO,QAAQ,wBAAwB,SAAS,GAAG;AAAA,UACvE;AAAA,QACF;AAAA,MACF,WAAW,UAAU,YAAY,MAAM,SAAS;AAC9C,YAAI,UAAU,OAAO,UAAU,WAAW,KAAK,GAAG;AAChD,sBAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B,SAAS,OAAO;AAEd,WAAO,EAAE,SAAS,MAAM,QAAQ,6BAA6B;AAAA,EAC/D;AACF;AAKA,eAAsB,qBAAqB,KAAsE;AAC/G,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS;AAAA,QACP,cAAc;AAAA,MAChB;AAAA,IACF,CAAC;AAED,UAAM,eAAe,SAAS,WAAW,OAAO,SAAS,WAAW;AACpE,UAAM,aAAa,SAAS,MAAM,CAAC;AAEnC,WAAO,EAAE,YAAY,aAAa;AAAA,EACpC,QAAQ;AACN,WAAO,EAAE,YAAY,OAAO,cAAc,MAAM;AAAA,EAClD;AACF;","names":["sharp","fs","cheerio"]}