@rankcli/agent-runtime 0.0.6 → 0.0.8

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.
package/dist/index.mjs CHANGED
@@ -25650,6 +25650,1783 @@ async function runDirectAnalysis(url) {
25650
25650
  // src/fixer.ts
25651
25651
  import { readFileSync as readFileSync6, existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
25652
25652
  import { join as join6 } from "path";
25653
+
25654
+ // src/fixer/framework-fixes.ts
25655
+ function generateReactSEOHead(options) {
25656
+ const { siteName, siteUrl, title, description, image, twitterHandle, locale } = options;
25657
+ return {
25658
+ file: "src/components/SEOHead.tsx",
25659
+ code: `import { Helmet } from 'react-helmet-async';
25660
+
25661
+ /**
25662
+ * SEO Head Component
25663
+ *
25664
+ * Comprehensive SEO meta tags following best practices:
25665
+ * - Primary meta tags (title, description)
25666
+ * - Open Graph for Facebook/LinkedIn
25667
+ * - Twitter Card for X/Twitter
25668
+ * - JSON-LD structured data
25669
+ * - Canonical URLs
25670
+ *
25671
+ * @example
25672
+ * <SEOHead
25673
+ * title="Product Name"
25674
+ * description="Product description"
25675
+ * type="product"
25676
+ * schema={{
25677
+ * "@type": "Product",
25678
+ * name: "Product Name",
25679
+ * price: "99.00"
25680
+ * }}
25681
+ * />
25682
+ */
25683
+
25684
+ interface SEOHeadProps {
25685
+ // Required
25686
+ title?: string;
25687
+ description?: string;
25688
+
25689
+ // URLs
25690
+ url?: string;
25691
+ canonical?: string;
25692
+ image?: string;
25693
+
25694
+ // Page type
25695
+ type?: 'website' | 'article' | 'product' | 'profile';
25696
+
25697
+ // Article-specific
25698
+ publishedTime?: string;
25699
+ modifiedTime?: string;
25700
+ author?: string;
25701
+ section?: string;
25702
+ tags?: string[];
25703
+
25704
+ // Twitter
25705
+ twitterCard?: 'summary' | 'summary_large_image' | 'player';
25706
+
25707
+ // Structured data
25708
+ schema?: Record<string, unknown> | Record<string, unknown>[];
25709
+
25710
+ // Robots
25711
+ noindex?: boolean;
25712
+ nofollow?: boolean;
25713
+
25714
+ // Alternate languages
25715
+ alternates?: { hrefLang: string; href: string }[];
25716
+ }
25717
+
25718
+ const SITE_NAME = '${siteName}';
25719
+ const SITE_URL = '${siteUrl}';
25720
+ const DEFAULT_IMAGE = '${image || `${siteUrl}/og-image.png`}';
25721
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
25722
+ const DEFAULT_LOCALE = '${locale || "en_US"}';
25723
+
25724
+ export function SEOHead({
25725
+ title,
25726
+ description = '${description || `${siteName} - A compelling description of your product or service.`}',
25727
+ url,
25728
+ canonical,
25729
+ image = DEFAULT_IMAGE,
25730
+ type = 'website',
25731
+ publishedTime,
25732
+ modifiedTime,
25733
+ author,
25734
+ section,
25735
+ tags,
25736
+ twitterCard = 'summary_large_image',
25737
+ schema,
25738
+ noindex = false,
25739
+ nofollow = false,
25740
+ alternates,
25741
+ }: SEOHeadProps) {
25742
+ const pageUrl = url || (typeof window !== 'undefined' ? window.location.href : SITE_URL);
25743
+ const canonicalUrl = canonical || pageUrl;
25744
+ const fullTitle = title
25745
+ ? (title.includes(SITE_NAME) ? title : \`\${title} | \${SITE_NAME}\`)
25746
+ : SITE_NAME;
25747
+
25748
+ // Ensure image is absolute URL
25749
+ const imageUrl = image.startsWith('http') ? image : \`\${SITE_URL}\${image}\`;
25750
+
25751
+ // Build robots directive
25752
+ const robotsContent = [
25753
+ noindex ? 'noindex' : 'index',
25754
+ nofollow ? 'nofollow' : 'follow',
25755
+ ].join(', ');
25756
+
25757
+ // Default Organization schema
25758
+ const defaultSchema = {
25759
+ '@context': 'https://schema.org',
25760
+ '@type': 'WebSite',
25761
+ name: SITE_NAME,
25762
+ url: SITE_URL,
25763
+ };
25764
+
25765
+ // Merge with provided schema
25766
+ const jsonLd = schema
25767
+ ? Array.isArray(schema)
25768
+ ? [defaultSchema, ...schema]
25769
+ : [defaultSchema, schema]
25770
+ : [defaultSchema];
25771
+
25772
+ return (
25773
+ <Helmet>
25774
+ {/* Primary Meta Tags */}
25775
+ <title>{fullTitle}</title>
25776
+ <meta name="title" content={fullTitle} />
25777
+ <meta name="description" content={description} />
25778
+ <meta name="robots" content={robotsContent} />
25779
+ <link rel="canonical" href={canonicalUrl} />
25780
+
25781
+ {/* Open Graph / Facebook */}
25782
+ <meta property="og:type" content={type} />
25783
+ <meta property="og:url" content={pageUrl} />
25784
+ <meta property="og:title" content={fullTitle} />
25785
+ <meta property="og:description" content={description} />
25786
+ <meta property="og:image" content={imageUrl} />
25787
+ <meta property="og:image:width" content="1200" />
25788
+ <meta property="og:image:height" content="630" />
25789
+ <meta property="og:image:alt" content={fullTitle} />
25790
+ <meta property="og:site_name" content={SITE_NAME} />
25791
+ <meta property="og:locale" content={DEFAULT_LOCALE} />
25792
+
25793
+ {/* Article-specific Open Graph */}
25794
+ {type === 'article' && publishedTime && (
25795
+ <meta property="article:published_time" content={publishedTime} />
25796
+ )}
25797
+ {type === 'article' && modifiedTime && (
25798
+ <meta property="article:modified_time" content={modifiedTime} />
25799
+ )}
25800
+ {type === 'article' && author && (
25801
+ <meta property="article:author" content={author} />
25802
+ )}
25803
+ {type === 'article' && section && (
25804
+ <meta property="article:section" content={section} />
25805
+ )}
25806
+ {type === 'article' && tags?.map((tag, i) => (
25807
+ <meta key={i} property="article:tag" content={tag} />
25808
+ ))}
25809
+
25810
+ {/* Twitter */}
25811
+ <meta name="twitter:card" content={twitterCard} />
25812
+ <meta name="twitter:url" content={pageUrl} />
25813
+ <meta name="twitter:title" content={fullTitle} />
25814
+ <meta name="twitter:description" content={description} />
25815
+ <meta name="twitter:image" content={imageUrl} />
25816
+ <meta name="twitter:image:alt" content={fullTitle} />
25817
+ {TWITTER_HANDLE && <meta name="twitter:site" content={TWITTER_HANDLE} />}
25818
+ {TWITTER_HANDLE && <meta name="twitter:creator" content={TWITTER_HANDLE} />}
25819
+
25820
+ {/* Alternate Languages */}
25821
+ {alternates?.map((alt, i) => (
25822
+ <link key={i} rel="alternate" hrefLang={alt.hrefLang} href={alt.href} />
25823
+ ))}
25824
+
25825
+ {/* JSON-LD Structured Data */}
25826
+ <script type="application/ld+json">
25827
+ {JSON.stringify(jsonLd)}
25828
+ </script>
25829
+ </Helmet>
25830
+ );
25831
+ }
25832
+
25833
+ /**
25834
+ * Pre-built schema generators for common page types
25835
+ */
25836
+ export const SchemaGenerators = {
25837
+ organization: (data: {
25838
+ name: string;
25839
+ url: string;
25840
+ logo?: string;
25841
+ sameAs?: string[];
25842
+ }) => ({
25843
+ '@context': 'https://schema.org',
25844
+ '@type': 'Organization',
25845
+ name: data.name,
25846
+ url: data.url,
25847
+ logo: data.logo,
25848
+ sameAs: data.sameAs,
25849
+ }),
25850
+
25851
+ article: (data: {
25852
+ headline: string;
25853
+ description: string;
25854
+ image: string;
25855
+ datePublished: string;
25856
+ dateModified?: string;
25857
+ author: { name: string; url?: string };
25858
+ }) => ({
25859
+ '@context': 'https://schema.org',
25860
+ '@type': 'Article',
25861
+ headline: data.headline,
25862
+ description: data.description,
25863
+ image: data.image,
25864
+ datePublished: data.datePublished,
25865
+ dateModified: data.dateModified || data.datePublished,
25866
+ author: {
25867
+ '@type': 'Person',
25868
+ name: data.author.name,
25869
+ url: data.author.url,
25870
+ },
25871
+ }),
25872
+
25873
+ product: (data: {
25874
+ name: string;
25875
+ description: string;
25876
+ image: string;
25877
+ price: string;
25878
+ currency?: string;
25879
+ availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
25880
+ brand?: string;
25881
+ sku?: string;
25882
+ rating?: { value: number; count: number };
25883
+ }) => ({
25884
+ '@context': 'https://schema.org',
25885
+ '@type': 'Product',
25886
+ name: data.name,
25887
+ description: data.description,
25888
+ image: data.image,
25889
+ brand: data.brand ? { '@type': 'Brand', name: data.brand } : undefined,
25890
+ sku: data.sku,
25891
+ offers: {
25892
+ '@type': 'Offer',
25893
+ price: data.price,
25894
+ priceCurrency: data.currency || 'USD',
25895
+ availability: \`https://schema.org/\${data.availability || 'InStock'}\`,
25896
+ },
25897
+ aggregateRating: data.rating ? {
25898
+ '@type': 'AggregateRating',
25899
+ ratingValue: data.rating.value,
25900
+ reviewCount: data.rating.count,
25901
+ } : undefined,
25902
+ }),
25903
+
25904
+ faq: (items: { question: string; answer: string }[]) => ({
25905
+ '@context': 'https://schema.org',
25906
+ '@type': 'FAQPage',
25907
+ mainEntity: items.map(item => ({
25908
+ '@type': 'Question',
25909
+ name: item.question,
25910
+ acceptedAnswer: {
25911
+ '@type': 'Answer',
25912
+ text: item.answer,
25913
+ },
25914
+ })),
25915
+ }),
25916
+
25917
+ breadcrumb: (items: { name: string; url: string }[]) => ({
25918
+ '@context': 'https://schema.org',
25919
+ '@type': 'BreadcrumbList',
25920
+ itemListElement: items.map((item, index) => ({
25921
+ '@type': 'ListItem',
25922
+ position: index + 1,
25923
+ name: item.name,
25924
+ item: item.url,
25925
+ })),
25926
+ }),
25927
+
25928
+ localBusiness: (data: {
25929
+ name: string;
25930
+ description: string;
25931
+ url: string;
25932
+ phone: string;
25933
+ address: {
25934
+ street: string;
25935
+ city: string;
25936
+ state: string;
25937
+ zip: string;
25938
+ country: string;
25939
+ };
25940
+ geo?: { lat: number; lng: number };
25941
+ hours?: string[];
25942
+ priceRange?: string;
25943
+ }) => ({
25944
+ '@context': 'https://schema.org',
25945
+ '@type': 'LocalBusiness',
25946
+ name: data.name,
25947
+ description: data.description,
25948
+ url: data.url,
25949
+ telephone: data.phone,
25950
+ address: {
25951
+ '@type': 'PostalAddress',
25952
+ streetAddress: data.address.street,
25953
+ addressLocality: data.address.city,
25954
+ addressRegion: data.address.state,
25955
+ postalCode: data.address.zip,
25956
+ addressCountry: data.address.country,
25957
+ },
25958
+ geo: data.geo ? {
25959
+ '@type': 'GeoCoordinates',
25960
+ latitude: data.geo.lat,
25961
+ longitude: data.geo.lng,
25962
+ } : undefined,
25963
+ openingHours: data.hours,
25964
+ priceRange: data.priceRange,
25965
+ }),
25966
+ };`,
25967
+ explanation: `Comprehensive React SEO component with:
25968
+ \u2022 Full Open Graph support (including article metadata)
25969
+ \u2022 Twitter Cards with all variants
25970
+ \u2022 JSON-LD structured data with pre-built schema generators
25971
+ \u2022 Robots directives (noindex/nofollow)
25972
+ \u2022 Hreflang for internationalization
25973
+ \u2022 Canonical URL handling
25974
+
25975
+ Install: npm install react-helmet-async
25976
+ Wrap app: <HelmetProvider><App /></HelmetProvider>`,
25977
+ installCommands: ["npm install react-helmet-async"],
25978
+ additionalFiles: [
25979
+ {
25980
+ file: "src/main.tsx",
25981
+ code: `import React from 'react';
25982
+ import ReactDOM from 'react-dom/client';
25983
+ import { HelmetProvider } from 'react-helmet-async';
25984
+ import App from './App';
25985
+ import './index.css';
25986
+
25987
+ ReactDOM.createRoot(document.getElementById('root')!).render(
25988
+ <React.StrictMode>
25989
+ <HelmetProvider>
25990
+ <App />
25991
+ </HelmetProvider>
25992
+ </React.StrictMode>,
25993
+ );`,
25994
+ explanation: "Updated main.tsx with HelmetProvider wrapper."
25995
+ }
25996
+ ]
25997
+ };
25998
+ }
25999
+ function generateNextJsAppRouterMetadata(options) {
26000
+ const { siteName, siteUrl, title, description, image, twitterHandle, locale } = options;
26001
+ return {
26002
+ file: "app/layout.tsx",
26003
+ code: `import type { Metadata, Viewport } from 'next';
26004
+ import { Inter } from 'next/font/google';
26005
+ import './globals.css';
26006
+
26007
+ const inter = Inter({ subsets: ['latin'], display: 'swap' });
26008
+
26009
+ /**
26010
+ * Default metadata for all pages
26011
+ * Individual pages can override with their own metadata export
26012
+ */
26013
+ export const metadata: Metadata = {
26014
+ metadataBase: new URL('${siteUrl}'),
26015
+
26016
+ // Default title with template
26017
+ title: {
26018
+ default: '${title || siteName}',
26019
+ template: \`%s | ${siteName}\`,
26020
+ },
26021
+
26022
+ description: '${description || `${siteName} - A compelling description of your product or service.`}',
26023
+
26024
+ // Indexing
26025
+ robots: {
26026
+ index: true,
26027
+ follow: true,
26028
+ googleBot: {
26029
+ index: true,
26030
+ follow: true,
26031
+ 'max-video-preview': -1,
26032
+ 'max-image-preview': 'large',
26033
+ 'max-snippet': -1,
26034
+ },
26035
+ },
26036
+
26037
+ // Icons
26038
+ icons: {
26039
+ icon: '/favicon.ico',
26040
+ shortcut: '/favicon-16x16.png',
26041
+ apple: '/apple-touch-icon.png',
26042
+ },
26043
+
26044
+ // Manifest
26045
+ manifest: '/site.webmanifest',
26046
+
26047
+ // Open Graph
26048
+ openGraph: {
26049
+ type: 'website',
26050
+ locale: '${locale || "en_US"}',
26051
+ url: '${siteUrl}',
26052
+ siteName: '${siteName}',
26053
+ title: '${title || siteName}',
26054
+ description: '${description || `${siteName} - A compelling description.`}',
26055
+ images: [
26056
+ {
26057
+ url: '${image || "/og-image.png"}',
26058
+ width: 1200,
26059
+ height: 630,
26060
+ alt: '${siteName}',
26061
+ },
26062
+ ],
26063
+ },
26064
+
26065
+ // Twitter
26066
+ twitter: {
26067
+ card: 'summary_large_image',
26068
+ title: '${title || siteName}',
26069
+ description: '${description || `${siteName} - A compelling description.`}',
26070
+ images: ['${image || "/og-image.png"}'],
26071
+ ${twitterHandle ? `site: '${twitterHandle}',
26072
+ creator: '${twitterHandle}',` : ""}
26073
+ },
26074
+
26075
+ // Verification (add your IDs)
26076
+ verification: {
26077
+ // google: 'your-google-verification-code',
26078
+ // yandex: 'your-yandex-verification-code',
26079
+ // bing: 'your-bing-verification-code',
26080
+ },
26081
+
26082
+ // Alternate languages (uncomment and customize)
26083
+ // alternates: {
26084
+ // canonical: '${siteUrl}',
26085
+ // languages: {
26086
+ // 'en-US': '${siteUrl}/en',
26087
+ // 'es-ES': '${siteUrl}/es',
26088
+ // },
26089
+ // },
26090
+
26091
+ // Category
26092
+ category: 'technology',
26093
+ };
26094
+
26095
+ /**
26096
+ * Viewport configuration
26097
+ * Separated from metadata in Next.js 14+
26098
+ */
26099
+ export const viewport: Viewport = {
26100
+ themeColor: [
26101
+ { media: '(prefers-color-scheme: light)', color: '#ffffff' },
26102
+ { media: '(prefers-color-scheme: dark)', color: '#000000' },
26103
+ ],
26104
+ width: 'device-width',
26105
+ initialScale: 1,
26106
+ maximumScale: 5,
26107
+ };
26108
+
26109
+ export default function RootLayout({
26110
+ children,
26111
+ }: {
26112
+ children: React.ReactNode;
26113
+ }) {
26114
+ return (
26115
+ <html lang="${(locale || "en_US").split("_")[0]}" className={inter.className}>
26116
+ <body>
26117
+ {children}
26118
+
26119
+ {/* JSON-LD Organization Schema */}
26120
+ <script
26121
+ type="application/ld+json"
26122
+ dangerouslySetInnerHTML={{
26123
+ __html: JSON.stringify({
26124
+ '@context': 'https://schema.org',
26125
+ '@type': 'Organization',
26126
+ name: '${siteName}',
26127
+ url: '${siteUrl}',
26128
+ logo: '${siteUrl}/logo.png',
26129
+ sameAs: [
26130
+ // Add your social profiles
26131
+ // 'https://twitter.com/yourhandle',
26132
+ // 'https://linkedin.com/company/yourcompany',
26133
+ ],
26134
+ }),
26135
+ }}
26136
+ />
26137
+ </body>
26138
+ </html>
26139
+ );
26140
+ }`,
26141
+ explanation: `Next.js App Router layout with comprehensive SEO:
26142
+ \u2022 Metadata API with title templates
26143
+ \u2022 Full Open Graph and Twitter Card support
26144
+ \u2022 Viewport configuration (Next.js 14+)
26145
+ \u2022 JSON-LD Organization schema
26146
+ \u2022 Verification tags for search consoles
26147
+ \u2022 Internationalization ready
26148
+ \u2022 Web font optimization with next/font`,
26149
+ additionalFiles: [
26150
+ {
26151
+ file: "app/robots.ts",
26152
+ code: `import type { MetadataRoute } from 'next';
26153
+
26154
+ export default function robots(): MetadataRoute.Robots {
26155
+ const baseUrl = '${siteUrl}';
26156
+
26157
+ return {
26158
+ rules: [
26159
+ {
26160
+ userAgent: '*',
26161
+ allow: '/',
26162
+ disallow: ['/api/', '/admin/', '/_next/', '/private/'],
26163
+ },
26164
+ {
26165
+ userAgent: 'GPTBot',
26166
+ allow: '/',
26167
+ },
26168
+ ],
26169
+ sitemap: \`\${baseUrl}/sitemap.xml\`,
26170
+ };
26171
+ }`,
26172
+ explanation: "Robots.txt with AI crawler support."
26173
+ },
26174
+ {
26175
+ file: "app/sitemap.ts",
26176
+ code: `import type { MetadataRoute } from 'next';
26177
+
26178
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
26179
+ const baseUrl = '${siteUrl}';
26180
+
26181
+ // Static pages
26182
+ const staticPages: MetadataRoute.Sitemap = [
26183
+ {
26184
+ url: baseUrl,
26185
+ lastModified: new Date(),
26186
+ changeFrequency: 'daily',
26187
+ priority: 1,
26188
+ },
26189
+ {
26190
+ url: \`\${baseUrl}/about\`,
26191
+ lastModified: new Date(),
26192
+ changeFrequency: 'monthly',
26193
+ priority: 0.8,
26194
+ },
26195
+ {
26196
+ url: \`\${baseUrl}/pricing\`,
26197
+ lastModified: new Date(),
26198
+ changeFrequency: 'weekly',
26199
+ priority: 0.9,
26200
+ },
26201
+ {
26202
+ url: \`\${baseUrl}/blog\`,
26203
+ lastModified: new Date(),
26204
+ changeFrequency: 'daily',
26205
+ priority: 0.8,
26206
+ },
26207
+ ];
26208
+
26209
+ // Dynamic pages - fetch from your database/CMS
26210
+ // const posts = await db.post.findMany({ select: { slug: true, updatedAt: true } });
26211
+ // const dynamicPages = posts.map((post) => ({
26212
+ // url: \`\${baseUrl}/blog/\${post.slug}\`,
26213
+ // lastModified: post.updatedAt,
26214
+ // changeFrequency: 'weekly' as const,
26215
+ // priority: 0.7,
26216
+ // }));
26217
+
26218
+ return [...staticPages];
26219
+ }`,
26220
+ explanation: "Dynamic sitemap generator."
26221
+ },
26222
+ {
26223
+ file: "lib/seo.ts",
26224
+ code: `import type { Metadata } from 'next';
26225
+
26226
+ const baseUrl = '${siteUrl}';
26227
+ const siteName = '${siteName}';
26228
+
26229
+ interface PageSEOProps {
26230
+ title: string;
26231
+ description: string;
26232
+ path?: string;
26233
+ image?: string;
26234
+ type?: 'website' | 'article';
26235
+ publishedTime?: string;
26236
+ modifiedTime?: string;
26237
+ authors?: string[];
26238
+ tags?: string[];
26239
+ noIndex?: boolean;
26240
+ }
26241
+
26242
+ /**
26243
+ * Generate metadata for a page
26244
+ * Use in page.tsx: export const metadata = generateMetadata({ ... })
26245
+ */
26246
+ export function generatePageMetadata({
26247
+ title,
26248
+ description,
26249
+ path = '',
26250
+ image,
26251
+ type = 'website',
26252
+ publishedTime,
26253
+ modifiedTime,
26254
+ authors,
26255
+ tags,
26256
+ noIndex = false,
26257
+ }: PageSEOProps): Metadata {
26258
+ const url = \`\${baseUrl}\${path}\`;
26259
+ const ogImage = image || '/og-image.png';
26260
+
26261
+ return {
26262
+ title,
26263
+ description,
26264
+
26265
+ robots: noIndex ? { index: false, follow: false } : undefined,
26266
+
26267
+ alternates: {
26268
+ canonical: url,
26269
+ },
26270
+
26271
+ openGraph: {
26272
+ title,
26273
+ description,
26274
+ url,
26275
+ siteName,
26276
+ type,
26277
+ images: [{ url: ogImage, width: 1200, height: 630 }],
26278
+ ...(type === 'article' && {
26279
+ publishedTime,
26280
+ modifiedTime,
26281
+ authors,
26282
+ tags,
26283
+ }),
26284
+ },
26285
+
26286
+ twitter: {
26287
+ card: 'summary_large_image',
26288
+ title,
26289
+ description,
26290
+ images: [ogImage],
26291
+ },
26292
+ };
26293
+ }
26294
+
26295
+ /**
26296
+ * Generate JSON-LD for articles
26297
+ */
26298
+ export function generateArticleJsonLd(article: {
26299
+ title: string;
26300
+ description: string;
26301
+ url: string;
26302
+ image: string;
26303
+ datePublished: string;
26304
+ dateModified?: string;
26305
+ author: { name: string; url?: string };
26306
+ }) {
26307
+ return {
26308
+ '@context': 'https://schema.org',
26309
+ '@type': 'Article',
26310
+ headline: article.title,
26311
+ description: article.description,
26312
+ url: article.url,
26313
+ image: article.image,
26314
+ datePublished: article.datePublished,
26315
+ dateModified: article.dateModified || article.datePublished,
26316
+ author: {
26317
+ '@type': 'Person',
26318
+ name: article.author.name,
26319
+ url: article.author.url,
26320
+ },
26321
+ publisher: {
26322
+ '@type': 'Organization',
26323
+ name: siteName,
26324
+ logo: {
26325
+ '@type': 'ImageObject',
26326
+ url: \`\${baseUrl}/logo.png\`,
26327
+ },
26328
+ },
26329
+ };
26330
+ }
26331
+
26332
+ /**
26333
+ * Generate JSON-LD for products
26334
+ */
26335
+ export function generateProductJsonLd(product: {
26336
+ name: string;
26337
+ description: string;
26338
+ image: string;
26339
+ price: number;
26340
+ currency?: string;
26341
+ availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
26342
+ rating?: { value: number; count: number };
26343
+ brand?: string;
26344
+ sku?: string;
26345
+ }) {
26346
+ return {
26347
+ '@context': 'https://schema.org',
26348
+ '@type': 'Product',
26349
+ name: product.name,
26350
+ description: product.description,
26351
+ image: product.image,
26352
+ brand: product.brand ? { '@type': 'Brand', name: product.brand } : undefined,
26353
+ sku: product.sku,
26354
+ offers: {
26355
+ '@type': 'Offer',
26356
+ price: product.price,
26357
+ priceCurrency: product.currency || 'USD',
26358
+ availability: \`https://schema.org/\${product.availability || 'InStock'}\`,
26359
+ },
26360
+ ...(product.rating && {
26361
+ aggregateRating: {
26362
+ '@type': 'AggregateRating',
26363
+ ratingValue: product.rating.value,
26364
+ reviewCount: product.rating.count,
26365
+ },
26366
+ }),
26367
+ };
26368
+ }
26369
+
26370
+ /**
26371
+ * Generate JSON-LD for FAQ pages
26372
+ */
26373
+ export function generateFAQJsonLd(items: { question: string; answer: string }[]) {
26374
+ return {
26375
+ '@context': 'https://schema.org',
26376
+ '@type': 'FAQPage',
26377
+ mainEntity: items.map(item => ({
26378
+ '@type': 'Question',
26379
+ name: item.question,
26380
+ acceptedAnswer: {
26381
+ '@type': 'Answer',
26382
+ text: item.answer,
26383
+ },
26384
+ })),
26385
+ };
26386
+ }`,
26387
+ explanation: "SEO utility functions for generating metadata and JSON-LD."
26388
+ }
26389
+ ]
26390
+ };
26391
+ }
26392
+ function generateNuxtSEOHead(options) {
26393
+ const { siteName, siteUrl, title, description, image, twitterHandle, locale } = options;
26394
+ return {
26395
+ file: "composables/useSEO.ts",
26396
+ code: `/**
26397
+ * Comprehensive SEO composable for Nuxt 3
26398
+ *
26399
+ * Features:
26400
+ * - Full Open Graph support
26401
+ * - Twitter Cards
26402
+ * - JSON-LD structured data
26403
+ * - Canonical URLs
26404
+ * - Robots directives
26405
+ * - Internationalization
26406
+ */
26407
+
26408
+ interface SEOOptions {
26409
+ title?: string;
26410
+ description?: string;
26411
+ image?: string;
26412
+ url?: string;
26413
+ type?: 'website' | 'article' | 'product';
26414
+
26415
+ // Article-specific
26416
+ publishedTime?: string;
26417
+ modifiedTime?: string;
26418
+ author?: string;
26419
+ tags?: string[];
26420
+
26421
+ // Robots
26422
+ noIndex?: boolean;
26423
+ noFollow?: boolean;
26424
+
26425
+ // Structured data
26426
+ schema?: Record<string, unknown> | Record<string, unknown>[];
26427
+ }
26428
+
26429
+ const SITE_NAME = '${siteName}';
26430
+ const SITE_URL = '${siteUrl}';
26431
+ const DEFAULT_IMAGE = '${image || `${siteUrl}/og-image.png`}';
26432
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
26433
+ const DEFAULT_LOCALE = '${locale || "en_US"}';
26434
+ const DEFAULT_DESCRIPTION = '${description || `${siteName} - A compelling description.`}';
26435
+
26436
+ export function useSEO(options: SEOOptions = {}) {
26437
+ const route = useRoute();
26438
+
26439
+ const {
26440
+ title,
26441
+ description = DEFAULT_DESCRIPTION,
26442
+ image = DEFAULT_IMAGE,
26443
+ url,
26444
+ type = 'website',
26445
+ publishedTime,
26446
+ modifiedTime,
26447
+ author,
26448
+ tags,
26449
+ noIndex = false,
26450
+ noFollow = false,
26451
+ schema,
26452
+ } = options;
26453
+
26454
+ const pageUrl = url || \`\${SITE_URL}\${route.path}\`;
26455
+ const fullTitle = title
26456
+ ? (title.includes(SITE_NAME) ? title : \`\${title} | \${SITE_NAME}\`)
26457
+ : SITE_NAME;
26458
+ const imageUrl = image.startsWith('http') ? image : \`\${SITE_URL}\${image}\`;
26459
+
26460
+ const robotsContent = [
26461
+ noIndex ? 'noindex' : 'index',
26462
+ noFollow ? 'nofollow' : 'follow',
26463
+ ].join(', ');
26464
+
26465
+ // Build meta array
26466
+ const meta = [
26467
+ { name: 'description', content: description },
26468
+ { name: 'robots', content: robotsContent },
26469
+
26470
+ // Open Graph
26471
+ { property: 'og:type', content: type },
26472
+ { property: 'og:url', content: pageUrl },
26473
+ { property: 'og:title', content: fullTitle },
26474
+ { property: 'og:description', content: description },
26475
+ { property: 'og:image', content: imageUrl },
26476
+ { property: 'og:image:width', content: '1200' },
26477
+ { property: 'og:image:height', content: '630' },
26478
+ { property: 'og:site_name', content: SITE_NAME },
26479
+ { property: 'og:locale', content: DEFAULT_LOCALE },
26480
+
26481
+ // Twitter
26482
+ { name: 'twitter:card', content: 'summary_large_image' },
26483
+ { name: 'twitter:title', content: fullTitle },
26484
+ { name: 'twitter:description', content: description },
26485
+ { name: 'twitter:image', content: imageUrl },
26486
+ ];
26487
+
26488
+ // Add Twitter handle if configured
26489
+ if (TWITTER_HANDLE) {
26490
+ meta.push(
26491
+ { name: 'twitter:site', content: TWITTER_HANDLE },
26492
+ { name: 'twitter:creator', content: TWITTER_HANDLE }
26493
+ );
26494
+ }
26495
+
26496
+ // Add article-specific meta
26497
+ if (type === 'article') {
26498
+ if (publishedTime) meta.push({ property: 'article:published_time', content: publishedTime });
26499
+ if (modifiedTime) meta.push({ property: 'article:modified_time', content: modifiedTime });
26500
+ if (author) meta.push({ property: 'article:author', content: author });
26501
+ tags?.forEach(tag => meta.push({ property: 'article:tag', content: tag }));
26502
+ }
26503
+
26504
+ // Build JSON-LD
26505
+ const defaultSchema = {
26506
+ '@context': 'https://schema.org',
26507
+ '@type': 'WebSite',
26508
+ name: SITE_NAME,
26509
+ url: SITE_URL,
26510
+ };
26511
+
26512
+ const jsonLd = schema
26513
+ ? Array.isArray(schema)
26514
+ ? [defaultSchema, ...schema]
26515
+ : [defaultSchema, schema]
26516
+ : [defaultSchema];
26517
+
26518
+ useHead({
26519
+ title: fullTitle,
26520
+ meta,
26521
+ link: [
26522
+ { rel: 'canonical', href: pageUrl },
26523
+ ],
26524
+ script: [
26525
+ {
26526
+ type: 'application/ld+json',
26527
+ innerHTML: JSON.stringify(jsonLd),
26528
+ },
26529
+ ],
26530
+ });
26531
+ }
26532
+
26533
+ /**
26534
+ * Schema generators for common types
26535
+ */
26536
+ export const Schema = {
26537
+ article: (data: {
26538
+ headline: string;
26539
+ description: string;
26540
+ image: string;
26541
+ datePublished: string;
26542
+ dateModified?: string;
26543
+ author: { name: string; url?: string };
26544
+ }) => ({
26545
+ '@context': 'https://schema.org',
26546
+ '@type': 'Article',
26547
+ headline: data.headline,
26548
+ description: data.description,
26549
+ image: data.image,
26550
+ datePublished: data.datePublished,
26551
+ dateModified: data.dateModified || data.datePublished,
26552
+ author: { '@type': 'Person', ...data.author },
26553
+ publisher: {
26554
+ '@type': 'Organization',
26555
+ name: SITE_NAME,
26556
+ url: SITE_URL,
26557
+ },
26558
+ }),
26559
+
26560
+ product: (data: {
26561
+ name: string;
26562
+ description: string;
26563
+ image: string;
26564
+ price: number;
26565
+ currency?: string;
26566
+ availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
26567
+ }) => ({
26568
+ '@context': 'https://schema.org',
26569
+ '@type': 'Product',
26570
+ name: data.name,
26571
+ description: data.description,
26572
+ image: data.image,
26573
+ offers: {
26574
+ '@type': 'Offer',
26575
+ price: data.price,
26576
+ priceCurrency: data.currency || 'USD',
26577
+ availability: \`https://schema.org/\${data.availability || 'InStock'}\`,
26578
+ },
26579
+ }),
26580
+
26581
+ faq: (items: { question: string; answer: string }[]) => ({
26582
+ '@context': 'https://schema.org',
26583
+ '@type': 'FAQPage',
26584
+ mainEntity: items.map(item => ({
26585
+ '@type': 'Question',
26586
+ name: item.question,
26587
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
26588
+ })),
26589
+ }),
26590
+
26591
+ breadcrumb: (items: { name: string; url: string }[]) => ({
26592
+ '@context': 'https://schema.org',
26593
+ '@type': 'BreadcrumbList',
26594
+ itemListElement: items.map((item, i) => ({
26595
+ '@type': 'ListItem',
26596
+ position: i + 1,
26597
+ name: item.name,
26598
+ item: item.url,
26599
+ })),
26600
+ }),
26601
+ };`,
26602
+ explanation: `Nuxt 3 comprehensive SEO composable with:
26603
+ \u2022 Full useHead integration
26604
+ \u2022 Open Graph with article support
26605
+ \u2022 Twitter Cards
26606
+ \u2022 JSON-LD schema generators
26607
+ \u2022 Robots directives
26608
+ \u2022 Canonical URLs
26609
+
26610
+ Usage: useSEO({ title: 'Page', description: '...' })`,
26611
+ additionalFiles: [
26612
+ {
26613
+ file: "server/routes/sitemap.xml.ts",
26614
+ code: `import { SitemapStream, streamToPromise } from 'sitemap';
26615
+ import { Readable } from 'stream';
26616
+
26617
+ export default defineEventHandler(async () => {
26618
+ const baseUrl = '${siteUrl}';
26619
+
26620
+ // Define your pages
26621
+ const pages = [
26622
+ { url: '/', changefreq: 'daily', priority: 1 },
26623
+ { url: '/about', changefreq: 'monthly', priority: 0.8 },
26624
+ { url: '/pricing', changefreq: 'weekly', priority: 0.9 },
26625
+ { url: '/blog', changefreq: 'daily', priority: 0.8 },
26626
+ ];
26627
+
26628
+ // Add dynamic pages from your database
26629
+ // const posts = await $fetch('/api/posts');
26630
+ // posts.forEach(post => pages.push({
26631
+ // url: \`/blog/\${post.slug}\`,
26632
+ // changefreq: 'weekly',
26633
+ // priority: 0.7,
26634
+ // lastmod: post.updatedAt,
26635
+ // }));
26636
+
26637
+ const stream = new SitemapStream({ hostname: baseUrl });
26638
+
26639
+ return streamToPromise(Readable.from(pages).pipe(stream)).then((data) =>
26640
+ data.toString()
26641
+ );
26642
+ });`,
26643
+ explanation: "Dynamic sitemap generator for Nuxt."
26644
+ },
26645
+ {
26646
+ file: "public/robots.txt",
26647
+ code: `User-agent: *
26648
+ Allow: /
26649
+ Disallow: /api/
26650
+ Disallow: /admin/
26651
+
26652
+ User-agent: GPTBot
26653
+ Allow: /
26654
+
26655
+ Sitemap: ${siteUrl}/sitemap.xml`,
26656
+ explanation: "Robots.txt with AI crawler support."
26657
+ }
26658
+ ]
26659
+ };
26660
+ }
26661
+ function generateVueSEOHead(options) {
26662
+ const { siteName, siteUrl, description, image, twitterHandle } = options;
26663
+ return {
26664
+ file: "src/composables/useSEO.ts",
26665
+ code: `import { useHead, useServerHead } from '@unhead/vue';
26666
+ import { computed, unref, MaybeRef } from 'vue';
26667
+ import { useRoute } from 'vue-router';
26668
+
26669
+ interface SEOOptions {
26670
+ title?: MaybeRef<string>;
26671
+ description?: MaybeRef<string>;
26672
+ image?: MaybeRef<string>;
26673
+ type?: 'website' | 'article';
26674
+ noIndex?: boolean;
26675
+ schema?: Record<string, unknown>;
26676
+ }
26677
+
26678
+ const SITE_NAME = '${siteName}';
26679
+ const SITE_URL = '${siteUrl}';
26680
+ const DEFAULT_IMAGE = '${image || `${siteUrl}/og-image.png`}';
26681
+ const DEFAULT_DESCRIPTION = '${description || `${siteName} - A compelling description.`}';
26682
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
26683
+
26684
+ export function useSEO(options: SEOOptions = {}) {
26685
+ const route = useRoute();
26686
+
26687
+ const title = computed(() => {
26688
+ const t = unref(options.title);
26689
+ return t ? (t.includes(SITE_NAME) ? t : \`\${t} | \${SITE_NAME}\`) : SITE_NAME;
26690
+ });
26691
+
26692
+ const description = computed(() => unref(options.description) || DEFAULT_DESCRIPTION);
26693
+ const image = computed(() => {
26694
+ const img = unref(options.image) || DEFAULT_IMAGE;
26695
+ return img.startsWith('http') ? img : \`\${SITE_URL}\${img}\`;
26696
+ });
26697
+ const url = computed(() => \`\${SITE_URL}\${route.path}\`);
26698
+
26699
+ useHead({
26700
+ title,
26701
+ meta: [
26702
+ { name: 'description', content: description },
26703
+ { name: 'robots', content: options.noIndex ? 'noindex, nofollow' : 'index, follow' },
26704
+
26705
+ // Open Graph
26706
+ { property: 'og:type', content: options.type || 'website' },
26707
+ { property: 'og:url', content: url },
26708
+ { property: 'og:title', content: title },
26709
+ { property: 'og:description', content: description },
26710
+ { property: 'og:image', content: image },
26711
+ { property: 'og:site_name', content: SITE_NAME },
26712
+
26713
+ // Twitter
26714
+ { name: 'twitter:card', content: 'summary_large_image' },
26715
+ { name: 'twitter:title', content: title },
26716
+ { name: 'twitter:description', content: description },
26717
+ { name: 'twitter:image', content: image },
26718
+ ...(TWITTER_HANDLE ? [
26719
+ { name: 'twitter:site', content: TWITTER_HANDLE },
26720
+ { name: 'twitter:creator', content: TWITTER_HANDLE },
26721
+ ] : []),
26722
+ ],
26723
+ link: [
26724
+ { rel: 'canonical', href: url },
26725
+ ],
26726
+ script: options.schema ? [
26727
+ { type: 'application/ld+json', innerHTML: JSON.stringify(options.schema) },
26728
+ ] : [],
26729
+ });
26730
+ }`,
26731
+ explanation: `Vue 3 SEO composable using @unhead/vue with:
26732
+ \u2022 Reactive title/description
26733
+ \u2022 Open Graph and Twitter Cards
26734
+ \u2022 JSON-LD schema support
26735
+ \u2022 Canonical URLs
26736
+
26737
+ Install: npm install @unhead/vue`,
26738
+ installCommands: ["npm install @unhead/vue"],
26739
+ additionalFiles: [
26740
+ {
26741
+ file: "src/main.ts",
26742
+ code: `import { createApp } from 'vue';
26743
+ import { createHead } from '@unhead/vue';
26744
+ import { createRouter, createWebHistory } from 'vue-router';
26745
+ import App from './App.vue';
26746
+
26747
+ const app = createApp(App);
26748
+ const head = createHead();
26749
+ const router = createRouter({
26750
+ history: createWebHistory(),
26751
+ routes: [/* your routes */],
26752
+ });
26753
+
26754
+ app.use(head);
26755
+ app.use(router);
26756
+ app.mount('#app');`,
26757
+ explanation: "Vue app setup with @unhead/vue."
26758
+ }
26759
+ ]
26760
+ };
26761
+ }
26762
+ function generateAstroBaseHead(options) {
26763
+ const { siteName, siteUrl, description, image, twitterHandle, locale } = options;
26764
+ return {
26765
+ file: "src/components/BaseHead.astro",
26766
+ code: `---
26767
+ /**
26768
+ * Comprehensive SEO Head Component for Astro
26769
+ *
26770
+ * Features:
26771
+ * - Full Open Graph support
26772
+ * - Twitter Cards
26773
+ * - JSON-LD structured data
26774
+ * - Canonical URLs
26775
+ * - Robots directives
26776
+ * - Performance optimizations
26777
+ */
26778
+
26779
+ interface Props {
26780
+ title?: string;
26781
+ description?: string;
26782
+ image?: string;
26783
+ type?: 'website' | 'article';
26784
+ publishedTime?: string;
26785
+ modifiedTime?: string;
26786
+ author?: string;
26787
+ tags?: string[];
26788
+ noIndex?: boolean;
26789
+ schema?: Record<string, unknown>;
26790
+ }
26791
+
26792
+ const SITE_NAME = '${siteName}';
26793
+ const SITE_URL = '${siteUrl}';
26794
+ const DEFAULT_IMAGE = '${image || "/og-image.png"}';
26795
+ const DEFAULT_DESCRIPTION = '${description || `${siteName} - A compelling description.`}';
26796
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
26797
+ const DEFAULT_LOCALE = '${locale || "en_US"}';
26798
+
26799
+ const {
26800
+ title,
26801
+ description = DEFAULT_DESCRIPTION,
26802
+ image = DEFAULT_IMAGE,
26803
+ type = 'website',
26804
+ publishedTime,
26805
+ modifiedTime,
26806
+ author,
26807
+ tags,
26808
+ noIndex = false,
26809
+ schema,
26810
+ } = Astro.props;
26811
+
26812
+ const canonicalURL = new URL(Astro.url.pathname, Astro.site || SITE_URL);
26813
+ const fullTitle = title
26814
+ ? (title.includes(SITE_NAME) ? title : \`\${title} | \${SITE_NAME}\`)
26815
+ : SITE_NAME;
26816
+ const imageURL = new URL(image, Astro.site || SITE_URL);
26817
+ const robotsContent = noIndex ? 'noindex, nofollow' : 'index, follow';
26818
+
26819
+ // Default website schema
26820
+ const defaultSchema = {
26821
+ '@context': 'https://schema.org',
26822
+ '@type': 'WebSite',
26823
+ name: SITE_NAME,
26824
+ url: SITE_URL,
26825
+ };
26826
+
26827
+ const jsonLd = schema ? [defaultSchema, schema] : [defaultSchema];
26828
+ ---
26829
+
26830
+ <!-- Global Metadata -->
26831
+ <meta charset="utf-8" />
26832
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
26833
+ <meta name="generator" content={Astro.generator} />
26834
+
26835
+ <!-- Favicon -->
26836
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
26837
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
26838
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
26839
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
26840
+ <link rel="manifest" href="/site.webmanifest" />
26841
+
26842
+ <!-- Canonical URL -->
26843
+ <link rel="canonical" href={canonicalURL} />
26844
+
26845
+ <!-- Primary Meta Tags -->
26846
+ <title>{fullTitle}</title>
26847
+ <meta name="title" content={fullTitle} />
26848
+ <meta name="description" content={description} />
26849
+ <meta name="robots" content={robotsContent} />
26850
+
26851
+ <!-- Theme -->
26852
+ <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
26853
+ <meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
26854
+
26855
+ <!-- Open Graph / Facebook -->
26856
+ <meta property="og:type" content={type} />
26857
+ <meta property="og:url" content={canonicalURL} />
26858
+ <meta property="og:title" content={fullTitle} />
26859
+ <meta property="og:description" content={description} />
26860
+ <meta property="og:image" content={imageURL} />
26861
+ <meta property="og:image:width" content="1200" />
26862
+ <meta property="og:image:height" content="630" />
26863
+ <meta property="og:image:alt" content={fullTitle} />
26864
+ <meta property="og:site_name" content={SITE_NAME} />
26865
+ <meta property="og:locale" content={DEFAULT_LOCALE} />
26866
+
26867
+ {type === 'article' && publishedTime && (
26868
+ <meta property="article:published_time" content={publishedTime} />
26869
+ )}
26870
+ {type === 'article' && modifiedTime && (
26871
+ <meta property="article:modified_time" content={modifiedTime} />
26872
+ )}
26873
+ {type === 'article' && author && (
26874
+ <meta property="article:author" content={author} />
26875
+ )}
26876
+ {type === 'article' && tags?.map((tag) => (
26877
+ <meta property="article:tag" content={tag} />
26878
+ ))}
26879
+
26880
+ <!-- Twitter -->
26881
+ <meta name="twitter:card" content="summary_large_image" />
26882
+ <meta name="twitter:url" content={canonicalURL} />
26883
+ <meta name="twitter:title" content={fullTitle} />
26884
+ <meta name="twitter:description" content={description} />
26885
+ <meta name="twitter:image" content={imageURL} />
26886
+ <meta name="twitter:image:alt" content={fullTitle} />
26887
+ {TWITTER_HANDLE && <meta name="twitter:site" content={TWITTER_HANDLE} />}
26888
+ {TWITTER_HANDLE && <meta name="twitter:creator" content={TWITTER_HANDLE} />}
26889
+
26890
+ <!-- Performance: Preconnect to external origins -->
26891
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
26892
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
26893
+
26894
+ <!-- JSON-LD Structured Data -->
26895
+ <script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />`,
26896
+ explanation: `Astro comprehensive SEO component with:
26897
+ \u2022 Full Open Graph with article support
26898
+ \u2022 Twitter Cards
26899
+ \u2022 JSON-LD structured data
26900
+ \u2022 Performance optimizations (preconnect)
26901
+ \u2022 Theme color for PWA
26902
+ \u2022 Favicon configuration
26903
+
26904
+ Usage: <BaseHead title="Page" description="..." />`,
26905
+ additionalFiles: [
26906
+ {
26907
+ file: "src/layouts/BaseLayout.astro",
26908
+ code: `---
26909
+ import BaseHead from '../components/BaseHead.astro';
26910
+
26911
+ interface Props {
26912
+ title?: string;
26913
+ description?: string;
26914
+ image?: string;
26915
+ type?: 'website' | 'article';
26916
+ schema?: Record<string, unknown>;
26917
+ }
26918
+
26919
+ const { title, description, image, type, schema } = Astro.props;
26920
+ ---
26921
+
26922
+ <!DOCTYPE html>
26923
+ <html lang="${(locale || "en_US").split("_")[0]}">
26924
+ <head>
26925
+ <BaseHead
26926
+ title={title}
26927
+ description={description}
26928
+ image={image}
26929
+ type={type}
26930
+ schema={schema}
26931
+ />
26932
+ </head>
26933
+ <body>
26934
+ <slot />
26935
+ </body>
26936
+ </html>`,
26937
+ explanation: "Base layout using the SEO head component."
26938
+ },
26939
+ {
26940
+ file: "public/robots.txt",
26941
+ code: `User-agent: *
26942
+ Allow: /
26943
+ Disallow: /api/
26944
+
26945
+ User-agent: GPTBot
26946
+ Allow: /
26947
+
26948
+ Sitemap: ${siteUrl}/sitemap-index.xml`,
26949
+ explanation: "Robots.txt with AI crawler support."
26950
+ }
26951
+ ]
26952
+ };
26953
+ }
26954
+ function generateSvelteKitSEOHead(options) {
26955
+ const { siteName, siteUrl, description, image, twitterHandle } = options;
26956
+ return {
26957
+ file: "src/lib/components/SEOHead.svelte",
26958
+ code: `<script lang="ts">
26959
+ import { page } from '$app/stores';
26960
+
26961
+ export let title: string | undefined = undefined;
26962
+ export let description: string = '${description || `${siteName} - A compelling description.`}';
26963
+ export let image: string = '${image || `${siteUrl}/og-image.png`}';
26964
+ export let type: 'website' | 'article' = 'website';
26965
+ export let publishedTime: string | undefined = undefined;
26966
+ export let modifiedTime: string | undefined = undefined;
26967
+ export let author: string | undefined = undefined;
26968
+ export let tags: string[] = [];
26969
+ export let noIndex: boolean = false;
26970
+ export let schema: Record<string, unknown> | undefined = undefined;
26971
+
26972
+ const SITE_NAME = '${siteName}';
26973
+ const SITE_URL = '${siteUrl}';
26974
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
26975
+
26976
+ $: fullTitle = title
26977
+ ? (title.includes(SITE_NAME) ? title : \`\${title} | \${SITE_NAME}\`)
26978
+ : SITE_NAME;
26979
+ $: canonicalUrl = $page.url.href;
26980
+ $: imageUrl = image.startsWith('http') ? image : \`\${SITE_URL}\${image}\`;
26981
+ $: robotsContent = noIndex ? 'noindex, nofollow' : 'index, follow';
26982
+
26983
+ $: defaultSchema = {
26984
+ '@context': 'https://schema.org',
26985
+ '@type': 'WebSite',
26986
+ name: SITE_NAME,
26987
+ url: SITE_URL,
26988
+ };
26989
+
26990
+ $: jsonLd = schema ? [defaultSchema, schema] : [defaultSchema];
26991
+ </script>
26992
+
26993
+ <svelte:head>
26994
+ <!-- Primary Meta Tags -->
26995
+ <title>{fullTitle}</title>
26996
+ <meta name="title" content={fullTitle} />
26997
+ <meta name="description" content={description} />
26998
+ <meta name="robots" content={robotsContent} />
26999
+ <link rel="canonical" href={canonicalUrl} />
27000
+
27001
+ <!-- Open Graph / Facebook -->
27002
+ <meta property="og:type" content={type} />
27003
+ <meta property="og:url" content={canonicalUrl} />
27004
+ <meta property="og:title" content={fullTitle} />
27005
+ <meta property="og:description" content={description} />
27006
+ <meta property="og:image" content={imageUrl} />
27007
+ <meta property="og:image:width" content="1200" />
27008
+ <meta property="og:image:height" content="630" />
27009
+ <meta property="og:site_name" content={SITE_NAME} />
27010
+
27011
+ {#if type === 'article'}
27012
+ {#if publishedTime}
27013
+ <meta property="article:published_time" content={publishedTime} />
27014
+ {/if}
27015
+ {#if modifiedTime}
27016
+ <meta property="article:modified_time" content={modifiedTime} />
27017
+ {/if}
27018
+ {#if author}
27019
+ <meta property="article:author" content={author} />
27020
+ {/if}
27021
+ {#each tags as tag}
27022
+ <meta property="article:tag" content={tag} />
27023
+ {/each}
27024
+ {/if}
27025
+
27026
+ <!-- Twitter -->
27027
+ <meta name="twitter:card" content="summary_large_image" />
27028
+ <meta name="twitter:title" content={fullTitle} />
27029
+ <meta name="twitter:description" content={description} />
27030
+ <meta name="twitter:image" content={imageUrl} />
27031
+ {#if TWITTER_HANDLE}
27032
+ <meta name="twitter:site" content={TWITTER_HANDLE} />
27033
+ <meta name="twitter:creator" content={TWITTER_HANDLE} />
27034
+ {/if}
27035
+
27036
+ <!-- JSON-LD Structured Data -->
27037
+ {@html \`<script type="application/ld+json">\${JSON.stringify(jsonLd)}</script>\`}
27038
+ </svelte:head>`,
27039
+ explanation: `SvelteKit comprehensive SEO component with:
27040
+ \u2022 Reactive props
27041
+ \u2022 Full Open Graph with article support
27042
+ \u2022 Twitter Cards
27043
+ \u2022 JSON-LD structured data
27044
+ \u2022 Robots directives
27045
+
27046
+ Usage: <SEOHead title="Page" description="..." />`,
27047
+ additionalFiles: [
27048
+ {
27049
+ file: "src/routes/+layout.svelte",
27050
+ code: `<script lang="ts">
27051
+ import '../app.css';
27052
+ </script>
27053
+
27054
+ <slot />`,
27055
+ explanation: "Root layout."
27056
+ },
27057
+ {
27058
+ file: "static/robots.txt",
27059
+ code: `User-agent: *
27060
+ Allow: /
27061
+ Disallow: /api/
27062
+
27063
+ User-agent: GPTBot
27064
+ Allow: /
27065
+
27066
+ Sitemap: ${siteUrl}/sitemap.xml`,
27067
+ explanation: "Robots.txt with AI crawler support."
27068
+ }
27069
+ ]
27070
+ };
27071
+ }
27072
+ function generateAngularSEOService(options) {
27073
+ const { siteName, siteUrl, description, image, twitterHandle } = options;
27074
+ return {
27075
+ file: "src/app/core/services/seo.service.ts",
27076
+ code: `import { Injectable, Inject } from '@angular/core';
27077
+ import { Meta, Title } from '@angular/platform-browser';
27078
+ import { Router, NavigationEnd } from '@angular/router';
27079
+ import { DOCUMENT } from '@angular/common';
27080
+ import { filter } from 'rxjs/operators';
27081
+
27082
+ interface SEOConfig {
27083
+ title?: string;
27084
+ description?: string;
27085
+ image?: string;
27086
+ type?: 'website' | 'article';
27087
+ publishedTime?: string;
27088
+ modifiedTime?: string;
27089
+ author?: string;
27090
+ tags?: string[];
27091
+ noIndex?: boolean;
27092
+ schema?: Record<string, unknown>;
27093
+ }
27094
+
27095
+ @Injectable({
27096
+ providedIn: 'root'
27097
+ })
27098
+ export class SEOService {
27099
+ private readonly siteName = '${siteName}';
27100
+ private readonly siteUrl = '${siteUrl}';
27101
+ private readonly defaultDescription = '${description || `${siteName} - A compelling description.`}';
27102
+ private readonly defaultImage = '${image || `${siteUrl}/og-image.png`}';
27103
+ private readonly twitterHandle = '${twitterHandle || ""}';
27104
+
27105
+ constructor(
27106
+ private meta: Meta,
27107
+ private titleService: Title,
27108
+ private router: Router,
27109
+ @Inject(DOCUMENT) private document: Document
27110
+ ) {
27111
+ // Update canonical URL on route change
27112
+ this.router.events.pipe(
27113
+ filter(event => event instanceof NavigationEnd)
27114
+ ).subscribe(() => {
27115
+ this.updateCanonical();
27116
+ });
27117
+ }
27118
+
27119
+ /**
27120
+ * Update all SEO meta tags
27121
+ */
27122
+ updateMeta(config: SEOConfig = {}): void {
27123
+ const {
27124
+ title,
27125
+ description = this.defaultDescription,
27126
+ image = this.defaultImage,
27127
+ type = 'website',
27128
+ publishedTime,
27129
+ modifiedTime,
27130
+ author,
27131
+ tags,
27132
+ noIndex = false,
27133
+ schema,
27134
+ } = config;
27135
+
27136
+ const fullTitle = title
27137
+ ? (title.includes(this.siteName) ? title : \`\${title} | \${this.siteName}\`)
27138
+ : this.siteName;
27139
+ const pageUrl = this.siteUrl + this.router.url;
27140
+ const imageUrl = image.startsWith('http') ? image : \`\${this.siteUrl}\${image}\`;
27141
+ const robotsContent = noIndex ? 'noindex, nofollow' : 'index, follow';
27142
+
27143
+ // Title
27144
+ this.titleService.setTitle(fullTitle);
27145
+
27146
+ // Primary Meta Tags
27147
+ this.setMetaTag('description', description);
27148
+ this.setMetaTag('robots', robotsContent);
27149
+
27150
+ // Open Graph
27151
+ this.setMetaProperty('og:type', type);
27152
+ this.setMetaProperty('og:url', pageUrl);
27153
+ this.setMetaProperty('og:title', fullTitle);
27154
+ this.setMetaProperty('og:description', description);
27155
+ this.setMetaProperty('og:image', imageUrl);
27156
+ this.setMetaProperty('og:image:width', '1200');
27157
+ this.setMetaProperty('og:image:height', '630');
27158
+ this.setMetaProperty('og:site_name', this.siteName);
27159
+
27160
+ // Article-specific
27161
+ if (type === 'article') {
27162
+ if (publishedTime) this.setMetaProperty('article:published_time', publishedTime);
27163
+ if (modifiedTime) this.setMetaProperty('article:modified_time', modifiedTime);
27164
+ if (author) this.setMetaProperty('article:author', author);
27165
+ tags?.forEach(tag => this.setMetaProperty('article:tag', tag));
27166
+ }
27167
+
27168
+ // Twitter
27169
+ this.setMetaTag('twitter:card', 'summary_large_image');
27170
+ this.setMetaTag('twitter:title', fullTitle);
27171
+ this.setMetaTag('twitter:description', description);
27172
+ this.setMetaTag('twitter:image', imageUrl);
27173
+ if (this.twitterHandle) {
27174
+ this.setMetaTag('twitter:site', this.twitterHandle);
27175
+ this.setMetaTag('twitter:creator', this.twitterHandle);
27176
+ }
27177
+
27178
+ // Update canonical
27179
+ this.updateCanonical(pageUrl);
27180
+
27181
+ // Update JSON-LD
27182
+ this.updateJsonLd(schema);
27183
+ }
27184
+
27185
+ private setMetaTag(name: string, content: string): void {
27186
+ this.meta.updateTag({ name, content });
27187
+ }
27188
+
27189
+ private setMetaProperty(property: string, content: string): void {
27190
+ this.meta.updateTag({ property, content });
27191
+ }
27192
+
27193
+ private updateCanonical(url?: string): void {
27194
+ const canonicalUrl = url || this.siteUrl + this.router.url;
27195
+ let link = this.document.querySelector('link[rel="canonical"]') as HTMLLinkElement;
27196
+
27197
+ if (!link) {
27198
+ link = this.document.createElement('link');
27199
+ link.setAttribute('rel', 'canonical');
27200
+ this.document.head.appendChild(link);
27201
+ }
27202
+
27203
+ link.setAttribute('href', canonicalUrl);
27204
+ }
27205
+
27206
+ private updateJsonLd(schema?: Record<string, unknown>): void {
27207
+ // Remove existing JSON-LD
27208
+ const existing = this.document.querySelector('script[type="application/ld+json"]');
27209
+ if (existing) existing.remove();
27210
+
27211
+ // Add new JSON-LD
27212
+ const defaultSchema = {
27213
+ '@context': 'https://schema.org',
27214
+ '@type': 'WebSite',
27215
+ name: this.siteName,
27216
+ url: this.siteUrl,
27217
+ };
27218
+
27219
+ const jsonLd = schema ? [defaultSchema, schema] : [defaultSchema];
27220
+
27221
+ const script = this.document.createElement('script');
27222
+ script.type = 'application/ld+json';
27223
+ script.text = JSON.stringify(jsonLd);
27224
+ this.document.head.appendChild(script);
27225
+ }
27226
+
27227
+ /**
27228
+ * Generate Article schema
27229
+ */
27230
+ articleSchema(data: {
27231
+ headline: string;
27232
+ description: string;
27233
+ image: string;
27234
+ datePublished: string;
27235
+ dateModified?: string;
27236
+ author: { name: string; url?: string };
27237
+ }): Record<string, unknown> {
27238
+ return {
27239
+ '@context': 'https://schema.org',
27240
+ '@type': 'Article',
27241
+ headline: data.headline,
27242
+ description: data.description,
27243
+ image: data.image,
27244
+ datePublished: data.datePublished,
27245
+ dateModified: data.dateModified || data.datePublished,
27246
+ author: { '@type': 'Person', ...data.author },
27247
+ publisher: {
27248
+ '@type': 'Organization',
27249
+ name: this.siteName,
27250
+ url: this.siteUrl,
27251
+ },
27252
+ };
27253
+ }
27254
+
27255
+ /**
27256
+ * Generate Product schema
27257
+ */
27258
+ productSchema(data: {
27259
+ name: string;
27260
+ description: string;
27261
+ image: string;
27262
+ price: number;
27263
+ currency?: string;
27264
+ }): Record<string, unknown> {
27265
+ return {
27266
+ '@context': 'https://schema.org',
27267
+ '@type': 'Product',
27268
+ name: data.name,
27269
+ description: data.description,
27270
+ image: data.image,
27271
+ offers: {
27272
+ '@type': 'Offer',
27273
+ price: data.price,
27274
+ priceCurrency: data.currency || 'USD',
27275
+ availability: 'https://schema.org/InStock',
27276
+ },
27277
+ };
27278
+ }
27279
+
27280
+ /**
27281
+ * Generate FAQ schema
27282
+ */
27283
+ faqSchema(items: { question: string; answer: string }[]): Record<string, unknown> {
27284
+ return {
27285
+ '@context': 'https://schema.org',
27286
+ '@type': 'FAQPage',
27287
+ mainEntity: items.map(item => ({
27288
+ '@type': 'Question',
27289
+ name: item.question,
27290
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
27291
+ })),
27292
+ };
27293
+ }
27294
+ }`,
27295
+ explanation: `Angular comprehensive SEO service with:
27296
+ \u2022 Meta and Title service integration
27297
+ \u2022 Dynamic canonical URL updates
27298
+ \u2022 Full Open Graph with article support
27299
+ \u2022 Twitter Cards
27300
+ \u2022 JSON-LD schema generators
27301
+ \u2022 Automatic route change handling
27302
+
27303
+ Usage: Inject SEOService and call updateMeta()`
27304
+ };
27305
+ }
27306
+ function getFrameworkSpecificFix(framework, options) {
27307
+ const name = framework.name.toLowerCase();
27308
+ if (name.includes("next")) {
27309
+ if (framework.router === "app") {
27310
+ return generateNextJsAppRouterMetadata(options);
27311
+ } else {
27312
+ return generateNextJsPagesRouterHead(options);
27313
+ }
27314
+ }
27315
+ if (name.includes("nuxt")) {
27316
+ return generateNuxtSEOHead(options);
27317
+ }
27318
+ if (name.includes("vue")) {
27319
+ return generateVueSEOHead(options);
27320
+ }
27321
+ if (name.includes("astro")) {
27322
+ return generateAstroBaseHead(options);
27323
+ }
27324
+ if (name.includes("svelte")) {
27325
+ return generateSvelteKitSEOHead(options);
27326
+ }
27327
+ if (name.includes("angular")) {
27328
+ return generateAngularSEOService(options);
27329
+ }
27330
+ return generateReactSEOHead(options);
27331
+ }
27332
+ function generateNextJsPagesRouterHead(options) {
27333
+ const { siteName, siteUrl, description, image, twitterHandle } = options;
27334
+ return {
27335
+ file: "components/SEOHead.tsx",
27336
+ code: `import Head from 'next/head';
27337
+ import { useRouter } from 'next/router';
27338
+
27339
+ interface SEOHeadProps {
27340
+ title?: string;
27341
+ description?: string;
27342
+ image?: string;
27343
+ type?: 'website' | 'article';
27344
+ publishedTime?: string;
27345
+ modifiedTime?: string;
27346
+ noIndex?: boolean;
27347
+ schema?: Record<string, unknown>;
27348
+ }
27349
+
27350
+ const SITE_NAME = '${siteName}';
27351
+ const SITE_URL = '${siteUrl}';
27352
+ const DEFAULT_IMAGE = '${image || `${siteUrl}/og-image.png`}';
27353
+ const DEFAULT_DESCRIPTION = '${description || `${siteName} - A compelling description.`}';
27354
+ const TWITTER_HANDLE = '${twitterHandle || ""}';
27355
+
27356
+ export function SEOHead({
27357
+ title,
27358
+ description = DEFAULT_DESCRIPTION,
27359
+ image = DEFAULT_IMAGE,
27360
+ type = 'website',
27361
+ publishedTime,
27362
+ modifiedTime,
27363
+ noIndex = false,
27364
+ schema,
27365
+ }: SEOHeadProps) {
27366
+ const router = useRouter();
27367
+
27368
+ const fullTitle = title
27369
+ ? (title.includes(SITE_NAME) ? title : \`\${title} | \${SITE_NAME}\`)
27370
+ : SITE_NAME;
27371
+ const pageUrl = \`\${SITE_URL}\${router.asPath}\`;
27372
+ const imageUrl = image.startsWith('http') ? image : \`\${SITE_URL}\${image}\`;
27373
+ const robotsContent = noIndex ? 'noindex, nofollow' : 'index, follow';
27374
+
27375
+ const defaultSchema = {
27376
+ '@context': 'https://schema.org',
27377
+ '@type': 'WebSite',
27378
+ name: SITE_NAME,
27379
+ url: SITE_URL,
27380
+ };
27381
+
27382
+ const jsonLd = schema ? [defaultSchema, schema] : [defaultSchema];
27383
+
27384
+ return (
27385
+ <Head>
27386
+ <title>{fullTitle}</title>
27387
+ <meta name="description" content={description} />
27388
+ <meta name="robots" content={robotsContent} />
27389
+ <link rel="canonical" href={pageUrl} />
27390
+
27391
+ <meta property="og:type" content={type} />
27392
+ <meta property="og:url" content={pageUrl} />
27393
+ <meta property="og:title" content={fullTitle} />
27394
+ <meta property="og:description" content={description} />
27395
+ <meta property="og:image" content={imageUrl} />
27396
+ <meta property="og:site_name" content={SITE_NAME} />
27397
+
27398
+ {type === 'article' && publishedTime && (
27399
+ <meta property="article:published_time" content={publishedTime} />
27400
+ )}
27401
+ {type === 'article' && modifiedTime && (
27402
+ <meta property="article:modified_time" content={modifiedTime} />
27403
+ )}
27404
+
27405
+ <meta name="twitter:card" content="summary_large_image" />
27406
+ <meta name="twitter:title" content={fullTitle} />
27407
+ <meta name="twitter:description" content={description} />
27408
+ <meta name="twitter:image" content={imageUrl} />
27409
+ {TWITTER_HANDLE && <meta name="twitter:site" content={TWITTER_HANDLE} />}
27410
+
27411
+ <script
27412
+ type="application/ld+json"
27413
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
27414
+ />
27415
+ </Head>
27416
+ );
27417
+ }`,
27418
+ explanation: `Next.js Pages Router SEO component with:
27419
+ \u2022 Full Open Graph support
27420
+ \u2022 Twitter Cards
27421
+ \u2022 JSON-LD structured data
27422
+ \u2022 Article metadata
27423
+ \u2022 Canonical URLs
27424
+
27425
+ Usage: <SEOHead title="Page" description="..." />`
27426
+ };
27427
+ }
27428
+
27429
+ // src/fixer.ts
25653
27430
  async function generateFixes(issues, options) {
25654
27431
  const { cwd, url } = options;
25655
27432
  let framework = options.framework;
@@ -26019,60 +27796,26 @@ function generateSitemapFix(context, url) {
26019
27796
  };
26020
27797
  }
26021
27798
  function generateSPAMetaFix(context, framework) {
26022
- const { htmlPath } = context;
26023
- const frameworkLower = framework.name.toLowerCase();
26024
- if (frameworkLower.includes("react") || frameworkLower.includes("vite") || framework.name === "Unknown") {
26025
- return {
26026
- issue: { code: "SPA_NO_META_MANAGEMENT", message: "SPA without dynamic meta tag management", severity: "warning" },
26027
- file: "src/components/SEOHead.tsx",
26028
- before: null,
26029
- after: `import { Helmet } from 'react-helmet-async';
26030
-
26031
- interface SEOHeadProps {
26032
- title?: string;
26033
- description?: string;
26034
- image?: string;
26035
- url?: string;
26036
- }
27799
+ const { url } = context;
27800
+ const fullUrl = url || "https://example.com";
27801
+ const siteName = new URL(fullUrl).hostname.replace("www.", "").split(".")[0];
27802
+ const capitalizedName = siteName.charAt(0).toUpperCase() + siteName.slice(1);
27803
+ const generatedCode = getFrameworkSpecificFix(framework, {
27804
+ siteName: capitalizedName,
27805
+ siteUrl: fullUrl,
27806
+ title: `${capitalizedName} - Your tagline here`,
27807
+ description: `${capitalizedName} - A compelling description of your product or service.`,
27808
+ image: `${fullUrl}/og-image.png`
27809
+ });
27810
+ const installInstructions = generatedCode.installCommands ? `
26037
27811
 
26038
- export function SEOHead({
26039
- title = 'Your Site Name',
26040
- description = 'Your site description',
26041
- image = '/og-image.png',
26042
- url = window.location.href,
26043
- }: SEOHeadProps) {
26044
- return (
26045
- <Helmet>
26046
- <title>{title}</title>
26047
- <meta name="description" content={description} />
26048
- <link rel="canonical" href={url} />
26049
-
26050
- {/* Open Graph */}
26051
- <meta property="og:title" content={title} />
26052
- <meta property="og:description" content={description} />
26053
- <meta property="og:image" content={image} />
26054
- <meta property="og:url" content={url} />
26055
- <meta property="og:type" content="website" />
26056
-
26057
- {/* Twitter */}
26058
- <meta name="twitter:card" content="summary_large_image" />
26059
- <meta name="twitter:title" content={title} />
26060
- <meta name="twitter:description" content={description} />
26061
- <meta name="twitter:image" content={image} />
26062
- </Helmet>
26063
- );
26064
- }`,
26065
- explanation: "Created SEOHead component using react-helmet-async for dynamic meta tags. Install: npm install react-helmet-async"
26066
- };
26067
- }
27812
+ Install: ${generatedCode.installCommands.join(" && ")}` : "";
26068
27813
  return {
26069
- issue: { code: "SPA_NO_META_MANAGEMENT", message: "SPA without meta management", severity: "warning" },
26070
- file: htmlPath,
27814
+ issue: { code: "SPA_NO_META_MANAGEMENT", message: "SPA without dynamic meta tag management", severity: "warning" },
27815
+ file: generatedCode.file,
26071
27816
  before: null,
26072
- after: "<!-- Add a meta management library for your framework -->",
26073
- explanation: `Add dynamic meta tag management for ${framework.name}`,
26074
- skipped: true,
26075
- skipReason: `Framework-specific solution needed for ${framework.name}`
27817
+ after: generatedCode.code,
27818
+ explanation: generatedCode.explanation + installInstructions
26076
27819
  };
26077
27820
  }
26078
27821
  function generatePreconnectFix(context) {
@@ -28914,6 +30657,8 @@ export {
28914
30657
  frameworks_exports as frameworks,
28915
30658
  generateAICitableContent,
28916
30659
  generateAllFixes,
30660
+ generateAngularSEOService,
30661
+ generateAstroBaseHead,
28917
30662
  generateAstroMeta,
28918
30663
  generateBlogPost,
28919
30664
  generateBranchName,
@@ -28938,22 +30683,29 @@ export {
28938
30683
  generateKeyFacts,
28939
30684
  generateMarkdownReport,
28940
30685
  generateNextAppMetadata,
30686
+ generateNextJsAppRouterMetadata,
30687
+ generateNextJsPagesRouterHead,
28941
30688
  generateNextPagesHead,
30689
+ generateNuxtSEOHead,
28942
30690
  generatePDFReport,
28943
30691
  generatePRDescription,
28944
30692
  generateReactHelmetSocialMeta,
30693
+ generateReactSEOHead,
28945
30694
  generateRecommendationQueries,
28946
30695
  generateRemixMeta,
28947
30696
  generateSecretsDoc,
28948
30697
  generateSocialMetaFix,
28949
30698
  generateSvelteKitMeta,
30699
+ generateSvelteKitSEOHead,
28950
30700
  generateUncertaintyQuestions,
30701
+ generateVueSEOHead,
28951
30702
  generateWizardQuestions,
28952
30703
  generateWorkflow,
28953
30704
  getAIVisibilitySummary,
28954
30705
  getAutocompleteSuggestions,
28955
30706
  getDateRange,
28956
30707
  getExpandedSuggestions,
30708
+ getFrameworkSpecificFix,
28957
30709
  getGSCSetupInstructions,
28958
30710
  getGitUser,
28959
30711
  getKeywordData,