@power-seo/content-analysis
Advanced tools
+21
| MIT License | ||
| Copyright (c) 2026 CCBD SEO Contributors | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/content-analysis — Public API\n// ============================================================================\n\nexport { analyzeContent } from './analyzer.js';\nexport { checkTitle } from './checks/title.js';\nexport { checkMetaDescription } from './checks/meta-description.js';\nexport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nexport { checkHeadings } from './checks/headings.js';\nexport { checkWordCount } from './checks/word-count.js';\nexport { checkImages } from './checks/images.js';\nexport { checkLinks } from './checks/links.js';\n\nexport type {\n CheckId,\n AnalysisConfig,\n ContentAnalysisInput,\n ContentAnalysisOutput,\n AnalysisResult,\n AnalysisStatus,\n} from './types.js';\n","// ============================================================================\n// @power-seo/content-analysis — Title Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Meta Description Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Keyphrase Usage Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Headings Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Word Count Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// ============================================================================\n// @power-seo/content-analysis — Images Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Links Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ============================================================================\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core"]} | ||
| {"version":3,"sources":["../src/index.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// @power-seo/content-analysis — Public API\n// ----------------------------------------------------------------------------\n\nexport { analyzeContent } from './analyzer.js';\nexport { checkTitle } from './checks/title.js';\nexport { checkMetaDescription } from './checks/meta-description.js';\nexport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nexport { checkHeadings } from './checks/headings.js';\nexport { checkWordCount } from './checks/word-count.js';\nexport { checkImages } from './checks/images.js';\nexport { checkLinks } from './checks/links.js';\n\nexport type {\n CheckId,\n AnalysisConfig,\n ContentAnalysisInput,\n ContentAnalysisOutput,\n AnalysisResult,\n AnalysisStatus,\n} from './types.js';\n","// @power-seo/content-analysis — Title Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Meta Description Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Keyphrase Usage Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Headings Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Word Count Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// @power-seo/content-analysis — Images Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Links Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/content-analysis — Title Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Meta Description Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Keyphrase Usage Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Headings Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Word Count Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// ============================================================================\n// @power-seo/content-analysis — Images Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Links Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ============================================================================\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// @power-seo/content-analysis — Title Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Meta Description Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Keyphrase Usage Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Headings Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Word Count Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// @power-seo/content-analysis — Images Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Links Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":[]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/react.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/content-analysis — React Components\n// ============================================================================\n\nimport { createElement, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig } from './types.js';\nimport { analyzeContent } from './analyzer.js';\n\n// --- ScorePanel ---\n\nexport interface ScorePanelProps {\n score: number;\n maxScore: number;\n}\n\nfunction getScoreColor(percentage: number): string {\n if (percentage >= 70) return '#1e8e3e';\n if (percentage >= 40) return '#f29900';\n return '#d93025';\n}\n\nfunction getScoreLabel(percentage: number): string {\n if (percentage >= 70) return 'Good';\n if (percentage >= 40) return 'OK';\n return 'Needs improvement';\n}\n\n/**\n * Displays an overall SEO score as a colored bar with label.\n */\nexport function ScorePanel({ score, maxScore }: ScorePanelProps) {\n const percentage = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0;\n const color = getScoreColor(percentage);\n const label = getScoreLabel(percentage);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n padding: '16px',\n borderRadius: '8px',\n border: '1px solid #e0e0e0',\n backgroundColor: '#fff',\n },\n },\n createElement(\n 'div',\n {\n style: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: '8px',\n },\n },\n createElement(\n 'span',\n { style: { fontWeight: 600, fontSize: '14px', color: '#333' } },\n 'SEO Score',\n ),\n createElement(\n 'span',\n { style: { fontWeight: 700, fontSize: '18px', color } },\n `${percentage}%`,\n ),\n ),\n createElement(\n 'div',\n {\n style: {\n width: '100%',\n height: '8px',\n backgroundColor: '#e8e8e8',\n borderRadius: '4px',\n overflow: 'hidden',\n },\n },\n createElement('div', {\n style: {\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: color,\n borderRadius: '4px',\n transition: 'width 0.3s ease',\n },\n }),\n ),\n createElement(\n 'div',\n { style: { marginTop: '4px', fontSize: '12px', color: '#666' } },\n `${label} — ${score}/${maxScore} points`,\n ),\n );\n}\n\n// --- CheckList ---\n\nexport interface CheckListProps {\n results: AnalysisResult[];\n}\n\nconst STATUS_ICONS: Record<string, string> = {\n good: '\\u2705',\n ok: '\\u26a0\\ufe0f',\n poor: '\\u274c',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n good: '#1e8e3e',\n ok: '#f29900',\n poor: '#d93025',\n};\n\n/**\n * Renders analysis results as a list with status icons.\n */\nexport function CheckList({ results }: CheckListProps) {\n return createElement(\n 'ul',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n ...results.map((result) =>\n createElement(\n 'li',\n {\n key: result.id,\n style: {\n padding: '10px 12px',\n borderBottom: '1px solid #f0f0f0',\n display: 'flex',\n gap: '10px',\n alignItems: 'flex-start',\n },\n },\n createElement(\n 'span',\n { style: { flexShrink: 0, fontSize: '14px' } },\n STATUS_ICONS[result.status] ?? '',\n ),\n createElement(\n 'div',\n { style: { flex: 1 } },\n createElement(\n 'div',\n {\n style: {\n fontWeight: 600,\n fontSize: '13px',\n color: STATUS_COLORS[result.status] ?? '#333',\n },\n },\n result.title,\n ),\n createElement(\n 'div',\n { style: { fontSize: '12px', color: '#555', marginTop: '2px' } },\n result.description,\n ),\n ),\n ),\n ),\n );\n}\n\n// --- ContentAnalyzer ---\n\nexport interface ContentAnalyzerProps {\n input: ContentAnalysisInput;\n config?: AnalysisConfig;\n children?: ReactNode;\n}\n\n/**\n * All-in-one component that runs analysis and renders score + check list.\n */\nexport function ContentAnalyzer({ input, config, children }: ContentAnalyzerProps) {\n const output = useMemo(() => analyzeContent(input, config), [input, config]);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n createElement(ScorePanel, { score: output.score, maxScore: output.maxScore }),\n createElement('div', { style: { height: '12px' } }),\n createElement(CheckList, { results: output.results }),\n children ?? null,\n );\n}\n","// ============================================================================\n// @power-seo/content-analysis — Title Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Meta Description Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Keyphrase Usage Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Headings Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Word Count Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// ============================================================================\n// @power-seo/content-analysis — Images Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Links Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ============================================================================\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core"]} | ||
| {"version":3,"sources":["../src/react.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// @power-seo/content-analysis — React Components\n// ----------------------------------------------------------------------------\n\nimport { createElement, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig } from './types.js';\nimport { analyzeContent } from './analyzer.js';\n\n// --- ScorePanel ---\n\nexport interface ScorePanelProps {\n score: number;\n maxScore: number;\n}\n\nfunction getScoreColor(percentage: number): string {\n if (percentage >= 70) return '#1e8e3e';\n if (percentage >= 40) return '#f29900';\n return '#d93025';\n}\n\nfunction getScoreLabel(percentage: number): string {\n if (percentage >= 70) return 'Good';\n if (percentage >= 40) return 'OK';\n return 'Needs improvement';\n}\n\n/**\n * Displays an overall SEO score as a colored bar with label.\n */\nexport function ScorePanel({ score, maxScore }: ScorePanelProps) {\n const percentage = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0;\n const color = getScoreColor(percentage);\n const label = getScoreLabel(percentage);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n padding: '16px',\n borderRadius: '8px',\n border: '1px solid #e0e0e0',\n backgroundColor: '#fff',\n },\n },\n createElement(\n 'div',\n {\n style: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: '8px',\n },\n },\n createElement(\n 'span',\n { style: { fontWeight: 600, fontSize: '14px', color: '#333' } },\n 'SEO Score',\n ),\n createElement(\n 'span',\n { style: { fontWeight: 700, fontSize: '18px', color } },\n `${percentage}%`,\n ),\n ),\n createElement(\n 'div',\n {\n style: {\n width: '100%',\n height: '8px',\n backgroundColor: '#e8e8e8',\n borderRadius: '4px',\n overflow: 'hidden',\n },\n },\n createElement('div', {\n style: {\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: color,\n borderRadius: '4px',\n transition: 'width 0.3s ease',\n },\n }),\n ),\n createElement(\n 'div',\n { style: { marginTop: '4px', fontSize: '12px', color: '#666' } },\n `${label} — ${score}/${maxScore} points`,\n ),\n );\n}\n\n// --- CheckList ---\n\nexport interface CheckListProps {\n results: AnalysisResult[];\n}\n\nconst STATUS_ICONS: Record<string, string> = {\n good: '\\u2705',\n ok: '\\u26a0\\ufe0f',\n poor: '\\u274c',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n good: '#1e8e3e',\n ok: '#f29900',\n poor: '#d93025',\n};\n\n/**\n * Renders analysis results as a list with status icons.\n */\nexport function CheckList({ results }: CheckListProps) {\n return createElement(\n 'ul',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n ...results.map((result) =>\n createElement(\n 'li',\n {\n key: result.id,\n style: {\n padding: '10px 12px',\n borderBottom: '1px solid #f0f0f0',\n display: 'flex',\n gap: '10px',\n alignItems: 'flex-start',\n },\n },\n createElement(\n 'span',\n { style: { flexShrink: 0, fontSize: '14px' } },\n STATUS_ICONS[result.status] ?? '',\n ),\n createElement(\n 'div',\n { style: { flex: 1 } },\n createElement(\n 'div',\n {\n style: {\n fontWeight: 600,\n fontSize: '13px',\n color: STATUS_COLORS[result.status] ?? '#333',\n },\n },\n result.title,\n ),\n createElement(\n 'div',\n { style: { fontSize: '12px', color: '#555', marginTop: '2px' } },\n result.description,\n ),\n ),\n ),\n ),\n );\n}\n\n// --- ContentAnalyzer ---\n\nexport interface ContentAnalyzerProps {\n input: ContentAnalysisInput;\n config?: AnalysisConfig;\n children?: ReactNode;\n}\n\n/**\n * All-in-one component that runs analysis and renders score + check list.\n */\nexport function ContentAnalyzer({ input, config, children }: ContentAnalyzerProps) {\n const output = useMemo(() => analyzeContent(input, config), [input, config]);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n createElement(ScorePanel, { score: output.score, maxScore: output.maxScore }),\n createElement('div', { style: { height: '12px' } }),\n createElement(CheckList, { results: output.results }),\n children ?? null,\n );\n}\n","// @power-seo/content-analysis — Title Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Meta Description Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Keyphrase Usage Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Headings Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Word Count Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// @power-seo/content-analysis — Images Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Links Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","import_core"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/react.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/content-analysis — React Components\n// ============================================================================\n\nimport { createElement, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig } from './types.js';\nimport { analyzeContent } from './analyzer.js';\n\n// --- ScorePanel ---\n\nexport interface ScorePanelProps {\n score: number;\n maxScore: number;\n}\n\nfunction getScoreColor(percentage: number): string {\n if (percentage >= 70) return '#1e8e3e';\n if (percentage >= 40) return '#f29900';\n return '#d93025';\n}\n\nfunction getScoreLabel(percentage: number): string {\n if (percentage >= 70) return 'Good';\n if (percentage >= 40) return 'OK';\n return 'Needs improvement';\n}\n\n/**\n * Displays an overall SEO score as a colored bar with label.\n */\nexport function ScorePanel({ score, maxScore }: ScorePanelProps) {\n const percentage = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0;\n const color = getScoreColor(percentage);\n const label = getScoreLabel(percentage);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n padding: '16px',\n borderRadius: '8px',\n border: '1px solid #e0e0e0',\n backgroundColor: '#fff',\n },\n },\n createElement(\n 'div',\n {\n style: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: '8px',\n },\n },\n createElement(\n 'span',\n { style: { fontWeight: 600, fontSize: '14px', color: '#333' } },\n 'SEO Score',\n ),\n createElement(\n 'span',\n { style: { fontWeight: 700, fontSize: '18px', color } },\n `${percentage}%`,\n ),\n ),\n createElement(\n 'div',\n {\n style: {\n width: '100%',\n height: '8px',\n backgroundColor: '#e8e8e8',\n borderRadius: '4px',\n overflow: 'hidden',\n },\n },\n createElement('div', {\n style: {\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: color,\n borderRadius: '4px',\n transition: 'width 0.3s ease',\n },\n }),\n ),\n createElement(\n 'div',\n { style: { marginTop: '4px', fontSize: '12px', color: '#666' } },\n `${label} — ${score}/${maxScore} points`,\n ),\n );\n}\n\n// --- CheckList ---\n\nexport interface CheckListProps {\n results: AnalysisResult[];\n}\n\nconst STATUS_ICONS: Record<string, string> = {\n good: '\\u2705',\n ok: '\\u26a0\\ufe0f',\n poor: '\\u274c',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n good: '#1e8e3e',\n ok: '#f29900',\n poor: '#d93025',\n};\n\n/**\n * Renders analysis results as a list with status icons.\n */\nexport function CheckList({ results }: CheckListProps) {\n return createElement(\n 'ul',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n ...results.map((result) =>\n createElement(\n 'li',\n {\n key: result.id,\n style: {\n padding: '10px 12px',\n borderBottom: '1px solid #f0f0f0',\n display: 'flex',\n gap: '10px',\n alignItems: 'flex-start',\n },\n },\n createElement(\n 'span',\n { style: { flexShrink: 0, fontSize: '14px' } },\n STATUS_ICONS[result.status] ?? '',\n ),\n createElement(\n 'div',\n { style: { flex: 1 } },\n createElement(\n 'div',\n {\n style: {\n fontWeight: 600,\n fontSize: '13px',\n color: STATUS_COLORS[result.status] ?? '#333',\n },\n },\n result.title,\n ),\n createElement(\n 'div',\n { style: { fontSize: '12px', color: '#555', marginTop: '2px' } },\n result.description,\n ),\n ),\n ),\n ),\n );\n}\n\n// --- ContentAnalyzer ---\n\nexport interface ContentAnalyzerProps {\n input: ContentAnalysisInput;\n config?: AnalysisConfig;\n children?: ReactNode;\n}\n\n/**\n * All-in-one component that runs analysis and renders score + check list.\n */\nexport function ContentAnalyzer({ input, config, children }: ContentAnalyzerProps) {\n const output = useMemo(() => analyzeContent(input, config), [input, config]);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n createElement(ScorePanel, { score: output.score, maxScore: output.maxScore }),\n createElement('div', { style: { height: '12px' } }),\n createElement(CheckList, { results: output.results }),\n children ?? null,\n );\n}\n","// ============================================================================\n// @power-seo/content-analysis — Title Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Meta Description Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Keyphrase Usage Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Headings Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Word Count Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// ============================================================================\n// @power-seo/content-analysis — Images Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Links Check\n// ============================================================================\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// ============================================================================\n// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ============================================================================\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/react.ts","../src/checks/title.ts","../src/checks/meta-description.ts","../src/checks/keyphrase-usage.ts","../src/checks/headings.ts","../src/checks/word-count.ts","../src/checks/images.ts","../src/checks/links.ts","../src/analyzer.ts"],"sourcesContent":["// @power-seo/content-analysis — React Components\n// ----------------------------------------------------------------------------\n\nimport { createElement, useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig } from './types.js';\nimport { analyzeContent } from './analyzer.js';\n\n// --- ScorePanel ---\n\nexport interface ScorePanelProps {\n score: number;\n maxScore: number;\n}\n\nfunction getScoreColor(percentage: number): string {\n if (percentage >= 70) return '#1e8e3e';\n if (percentage >= 40) return '#f29900';\n return '#d93025';\n}\n\nfunction getScoreLabel(percentage: number): string {\n if (percentage >= 70) return 'Good';\n if (percentage >= 40) return 'OK';\n return 'Needs improvement';\n}\n\n/**\n * Displays an overall SEO score as a colored bar with label.\n */\nexport function ScorePanel({ score, maxScore }: ScorePanelProps) {\n const percentage = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0;\n const color = getScoreColor(percentage);\n const label = getScoreLabel(percentage);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n padding: '16px',\n borderRadius: '8px',\n border: '1px solid #e0e0e0',\n backgroundColor: '#fff',\n },\n },\n createElement(\n 'div',\n {\n style: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: '8px',\n },\n },\n createElement(\n 'span',\n { style: { fontWeight: 600, fontSize: '14px', color: '#333' } },\n 'SEO Score',\n ),\n createElement(\n 'span',\n { style: { fontWeight: 700, fontSize: '18px', color } },\n `${percentage}%`,\n ),\n ),\n createElement(\n 'div',\n {\n style: {\n width: '100%',\n height: '8px',\n backgroundColor: '#e8e8e8',\n borderRadius: '4px',\n overflow: 'hidden',\n },\n },\n createElement('div', {\n style: {\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: color,\n borderRadius: '4px',\n transition: 'width 0.3s ease',\n },\n }),\n ),\n createElement(\n 'div',\n { style: { marginTop: '4px', fontSize: '12px', color: '#666' } },\n `${label} — ${score}/${maxScore} points`,\n ),\n );\n}\n\n// --- CheckList ---\n\nexport interface CheckListProps {\n results: AnalysisResult[];\n}\n\nconst STATUS_ICONS: Record<string, string> = {\n good: '\\u2705',\n ok: '\\u26a0\\ufe0f',\n poor: '\\u274c',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n good: '#1e8e3e',\n ok: '#f29900',\n poor: '#d93025',\n};\n\n/**\n * Renders analysis results as a list with status icons.\n */\nexport function CheckList({ results }: CheckListProps) {\n return createElement(\n 'ul',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n ...results.map((result) =>\n createElement(\n 'li',\n {\n key: result.id,\n style: {\n padding: '10px 12px',\n borderBottom: '1px solid #f0f0f0',\n display: 'flex',\n gap: '10px',\n alignItems: 'flex-start',\n },\n },\n createElement(\n 'span',\n { style: { flexShrink: 0, fontSize: '14px' } },\n STATUS_ICONS[result.status] ?? '',\n ),\n createElement(\n 'div',\n { style: { flex: 1 } },\n createElement(\n 'div',\n {\n style: {\n fontWeight: 600,\n fontSize: '13px',\n color: STATUS_COLORS[result.status] ?? '#333',\n },\n },\n result.title,\n ),\n createElement(\n 'div',\n { style: { fontSize: '12px', color: '#555', marginTop: '2px' } },\n result.description,\n ),\n ),\n ),\n ),\n );\n}\n\n// --- ContentAnalyzer ---\n\nexport interface ContentAnalyzerProps {\n input: ContentAnalysisInput;\n config?: AnalysisConfig;\n children?: ReactNode;\n}\n\n/**\n * All-in-one component that runs analysis and renders score + check list.\n */\nexport function ContentAnalyzer({ input, config, children }: ContentAnalyzerProps) {\n const output = useMemo(() => analyzeContent(input, config), [input, config]);\n\n return createElement(\n 'div',\n {\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n },\n },\n createElement(ScorePanel, { score: output.score, maxScore: output.maxScore }),\n createElement('div', { style: { height: '12px' } }),\n createElement(CheckList, { results: output.results }),\n children ?? null,\n );\n}\n","// @power-seo/content-analysis — Title Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateTitle } from '@power-seo/core';\n\nexport function checkTitle(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { title, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!title || title.trim().length === 0) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: 'No title has been set. Add a title to improve search visibility.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateTitle(title);\n\n if (!validation.valid) {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-presence',\n title: 'SEO title',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in title check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const titleLower = title.toLowerCase();\n\n if (titleLower.includes(kp)) {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description: 'The focus keyphrase appears in the SEO title. Good job!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'title-keyphrase',\n title: 'Keyphrase in title',\n description:\n 'The focus keyphrase does not appear in the SEO title. Add it to improve relevance.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Meta Description Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { validateMetaDescription } from '@power-seo/core';\n\nexport function checkMetaDescription(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { metaDescription, focusKeyphrase } = input;\n\n // --- Presence & validity check ---\n if (!metaDescription || metaDescription.trim().length === 0) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description:\n 'No meta description has been set. Add one to control how your page appears in search results.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n return results;\n }\n\n const validation = validateMetaDescription(metaDescription);\n\n if (!validation.valid) {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else if (validation.severity === 'warning') {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-presence',\n title: 'Meta description',\n description: validation.message,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in meta description check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const descLower = metaDescription.toLowerCase();\n\n if (descLower.includes(kp)) {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description: 'The focus keyphrase appears in the meta description. Well done!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'meta-description-keyphrase',\n title: 'Keyphrase in meta description',\n description:\n 'The focus keyphrase does not appear in the meta description. Add it to improve click-through rate.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Keyphrase Usage Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport {\n analyzeKeyphraseOccurrences,\n calculateKeywordDensity,\n KEYWORD_DENSITY,\n} from '@power-seo/core';\n\nexport function checkKeyphraseUsage(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { focusKeyphrase, title, metaDescription, content, slug, images } = input;\n\n if (!focusKeyphrase || focusKeyphrase.trim().length === 0) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: 'No focus keyphrase set. Set one to get keyphrase analysis.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n return results;\n }\n\n const occurrences = analyzeKeyphraseOccurrences({\n keyphrase: focusKeyphrase,\n title,\n metaDescription,\n content,\n slug,\n images,\n });\n\n const densityResult = calculateKeywordDensity(focusKeyphrase, content);\n\n // --- Density check ---\n if (densityResult.density < KEYWORD_DENSITY.MIN) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (densityResult.density > KEYWORD_DENSITY.MAX) {\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n } else if (\n densityResult.density >= KEYWORD_DENSITY.MIN &&\n densityResult.density <= KEYWORD_DENSITY.MAX\n ) {\n const isOptimal = Math.abs(densityResult.density - KEYWORD_DENSITY.OPTIMAL) < 0.5;\n results.push({\n id: 'keyphrase-density',\n title: 'Keyphrase density',\n description: `Keyphrase density is ${densityResult.density}%.${isOptimal ? ' Great — this is close to the optimal density.' : ' This is within the recommended range.'}`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n // --- Distribution check ---\n const distributionPoints: string[] = [];\n if (!occurrences.inFirstParagraph) distributionPoints.push('introduction');\n if (!occurrences.inH1 && occurrences.inHeadings === 0) distributionPoints.push('headings');\n if (!occurrences.inSlug) distributionPoints.push('slug');\n if (occurrences.inAltText === 0 && images && images.length > 0)\n distributionPoints.push('image alt text');\n\n if (distributionPoints.length === 0) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description:\n 'The focus keyphrase is well-distributed across the introduction, headings, slug, and image alt text.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (distributionPoints.length <= 2) {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `Consider adding the keyphrase to: ${distributionPoints.join(', ')}.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'keyphrase-distribution',\n title: 'Keyphrase distribution',\n description: `The keyphrase is missing from: ${distributionPoints.join(', ')}. Distribute it more broadly.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Headings Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { stripHtml } from '@power-seo/core';\n\ninterface HeadingInfo {\n level: number;\n text: string;\n}\n\n/**\n * Parse all headings (h1-h6) from HTML using plain string search.\n * Avoids regex ReDoS on crafted inputs with many repeated heading-like tags.\n */\nfunction parseHeadings(html: string): HeadingInfo[] {\n const headings: HeadingInfo[] = [];\n const lc = html.toLowerCase();\n let pos = 0;\n\n while (pos < lc.length) {\n // Find the next heading tag of any level\n let earliest = -1;\n let earliestLevel = 0;\n for (let level = 1; level <= 6; level++) {\n const idx = lc.indexOf(`<h${level}`, pos);\n if (idx !== -1 && (earliest === -1 || idx < earliest)) {\n earliest = idx;\n earliestLevel = level;\n }\n }\n if (earliest === -1) break;\n\n const contentStart = lc.indexOf('>', earliest);\n if (contentStart === -1) break;\n\n const closeTag = `</h${earliestLevel}>`;\n const closeIdx = lc.indexOf(closeTag, contentStart + 1);\n if (closeIdx === -1) {\n pos = contentStart + 1;\n continue;\n }\n\n headings.push({\n level: earliestLevel,\n text: stripHtml(html.slice(contentStart + 1, closeIdx)),\n });\n pos = closeIdx + closeTag.length;\n }\n\n return headings;\n}\n\nexport function checkHeadings(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { content, focusKeyphrase } = input;\n const headings = parseHeadings(content);\n\n // --- H1 & structure check ---\n const h1s = headings.filter((h) => h.level === 1);\n\n if (h1s.length === 0) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'No H1 heading found. Add exactly one H1 as the main heading of your page.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else if (h1s.length > 1) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: `Found ${h1s.length} H1 headings. Use exactly one H1 per page for proper SEO.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n // Check heading hierarchy\n let hasSkippedLevel = false;\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!;\n const curr = headings[i]!;\n if (curr.level > prev.level + 1) {\n hasSkippedLevel = true;\n break;\n }\n }\n\n if (hasSkippedLevel) {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description:\n 'The heading hierarchy skips levels (e.g., H2 to H4). Use sequential heading levels for better accessibility and SEO.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-structure',\n title: 'Heading structure',\n description: 'The heading structure looks good with a single H1 and proper hierarchy.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n }\n\n // --- Keyphrase in headings check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const subheadings = headings.filter((h) => h.level >= 2);\n const hasKeyphraseInSubheading = subheadings.some((h) => h.text.toLowerCase().includes(kp));\n\n if (subheadings.length === 0) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'No subheadings (H2-H6) found. Add subheadings to structure your content and include the focus keyphrase.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else if (hasKeyphraseInSubheading) {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description: 'The focus keyphrase appears in at least one subheading. Nice!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'heading-keyphrase',\n title: 'Keyphrase in subheadings',\n description:\n 'The focus keyphrase does not appear in any subheading. Consider adding it to an H2 or H3.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Word Count Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\nimport { getWords, MIN_WORD_COUNT, RECOMMENDED_WORD_COUNT } from '@power-seo/core';\n\nexport function checkWordCount(input: ContentAnalysisInput): AnalysisResult {\n const words = getWords(input.content);\n const count = words.length;\n\n if (count < MIN_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words, which is below the recommended minimum of ${MIN_WORD_COUNT}. Add more content to improve SEO.`,\n status: 'poor',\n score: 1,\n maxScore: 5,\n };\n }\n\n if (count < RECOMMENDED_WORD_COUNT) {\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Consider expanding to at least ${RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,\n status: 'ok',\n score: 3,\n maxScore: 5,\n };\n }\n\n return {\n id: 'word-count',\n title: 'Word count',\n description: `The content is ${count} words. Good — this provides enough depth for search engines.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n };\n}\n","// @power-seo/content-analysis — Images Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkImages(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { images, focusKeyphrase } = input;\n\n if (!images || images.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'No images found. Consider adding images to make your content more engaging.',\n status: 'ok',\n score: 3,\n maxScore: 5,\n });\n return results;\n }\n\n // --- Alt text check ---\n const missingAlt = images.filter((img) => !img.alt || img.alt.trim().length === 0);\n\n if (missingAlt.length === 0) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: 'All images have alt text. Great for accessibility and SEO!',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else if (missingAlt.length === images.length) {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description:\n 'None of the images have alt text. Add descriptive alt attributes for accessibility and SEO.',\n status: 'poor',\n score: 0,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-alt',\n title: 'Image alt attributes',\n description: `${missingAlt.length} of ${images.length} images are missing alt text. Add alt attributes to all images.`,\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n\n // --- Keyphrase in alt text check ---\n if (focusKeyphrase && focusKeyphrase.trim().length > 0) {\n const kp = focusKeyphrase.toLowerCase().trim();\n const hasKeyphraseInAlt = images.some((img) => img.alt && img.alt.toLowerCase().includes(kp));\n\n if (hasKeyphraseInAlt) {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description: 'The focus keyphrase appears in at least one image alt attribute.',\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'image-keyphrase',\n title: 'Keyphrase in image alt',\n description:\n 'The focus keyphrase does not appear in any image alt attribute. Add it to at least one image.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n }\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Links Check\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, AnalysisResult } from '@power-seo/core';\n\nexport function checkLinks(input: ContentAnalysisInput): AnalysisResult[] {\n const results: AnalysisResult[] = [];\n const { internalLinks, externalLinks } = input;\n\n const hasInternal = internalLinks && internalLinks.length > 0;\n const hasExternal = externalLinks && externalLinks.length > 0;\n\n if (!hasInternal) {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description:\n 'No internal links found. Add links to other pages on your site to improve crawlability and distribute link equity.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'internal-links',\n title: 'Internal links',\n description: `Found ${internalLinks!.length} internal link${internalLinks!.length === 1 ? '' : 's'}. Good for site structure and SEO.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n if (!hasExternal) {\n results.push({\n id: 'external-links',\n title: 'External links',\n description:\n 'No external links found. Consider adding outbound links to authoritative sources to strengthen your content.',\n status: 'ok',\n score: 2,\n maxScore: 5,\n });\n } else {\n results.push({\n id: 'external-links',\n title: 'External links',\n description: `Found ${externalLinks!.length} external link${externalLinks!.length === 1 ? '' : 's'}. Linking to quality sources adds credibility.`,\n status: 'good',\n score: 5,\n maxScore: 5,\n });\n }\n\n return results;\n}\n","// @power-seo/content-analysis — Content Analyzer Orchestrator\n// ----------------------------------------------------------------------------\n\nimport type { ContentAnalysisInput, ContentAnalysisOutput, AnalysisResult } from '@power-seo/core';\nimport type { AnalysisConfig, CheckId } from './types.js';\nimport { checkTitle } from './checks/title.js';\nimport { checkMetaDescription } from './checks/meta-description.js';\nimport { checkKeyphraseUsage } from './checks/keyphrase-usage.js';\nimport { checkHeadings } from './checks/headings.js';\nimport { checkWordCount } from './checks/word-count.js';\nimport { checkImages } from './checks/images.js';\nimport { checkLinks } from './checks/links.js';\n\n/**\n * Run all SEO content analysis checks and return aggregated results.\n *\n * @example\n * ```ts\n * const output = analyzeContent({\n * title: 'My Blog Post',\n * metaDescription: 'A description of my blog post about SEO.',\n * content: '<h1>My Blog Post</h1><p>Content goes here...</p>',\n * focusKeyphrase: 'blog post',\n * });\n * console.log(output.score, output.maxScore, output.recommendations);\n * ```\n */\nexport function analyzeContent(\n input: ContentAnalysisInput,\n config?: AnalysisConfig,\n): ContentAnalysisOutput {\n const disabled = new Set<CheckId>(config?.disabledChecks ?? []);\n\n const allResults: AnalysisResult[] = [];\n\n // Run each check group and collect results\n const titleResults = checkTitle(input);\n const metaResults = checkMetaDescription(input);\n const keyphraseResults = checkKeyphraseUsage(input);\n const headingResults = checkHeadings(input);\n const wordCountResult = checkWordCount(input);\n const imageResults = checkImages(input);\n const linkResults = checkLinks(input);\n\n // Flatten all results\n const candidateResults = [\n ...titleResults,\n ...metaResults,\n ...keyphraseResults,\n ...headingResults,\n wordCountResult,\n ...imageResults,\n ...linkResults,\n ];\n\n // Filter out disabled checks\n for (const result of candidateResults) {\n if (!disabled.has(result.id as CheckId)) {\n allResults.push(result);\n }\n }\n\n // Sum scores\n const score = allResults.reduce((sum, r) => sum + r.score, 0);\n const maxScore = allResults.reduce((sum, r) => sum + r.maxScore, 0);\n\n // Generate recommendations from poor/ok results\n const recommendations = allResults\n .filter((r) => r.status === 'poor' || r.status === 'ok')\n .map((r) => r.description);\n\n return {\n score,\n maxScore,\n results: allResults,\n recommendations,\n };\n}\n"],"mappings":";AAGA,SAAS,eAAe,eAAe;;;ACCvC,SAAS,qBAAqB;AAEvB,SAAS,WAAW,OAA+C;AACxE,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,OAAO,eAAe,IAAI;AAGlC,MAAI,CAAC,SAAS,MAAM,KAAK,EAAE,WAAW,GAAG;AACvC,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,cAAc,KAAK;AAEtC,MAAI,CAAC,WAAW,OAAO;AACrB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,WAAW,aAAa,WAAW;AAC5C,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,kBAAkB,eAAe,KAAK,EAAE,SAAS,GAAG;AACtD,UAAM,KAAK,eAAe,YAAY,EAAE,KAAK;AAC7C,UAAM,aAAa,MAAM,YAAY;AAErC,QAAI,WAAW,SAAS,EAAE,GAAG;AAC3B,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;AC9EA,SAAS,+BAA+B;AAEjC,SAAS,qBAAqB,OAA+C;AAClF,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,iBAAiB,eAAe,IAAI;AAG5C,MAAI,CAAC,mBAAmB,gBAAgB,KAAK,EAAE,WAAW,GAAG;AAC3D,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,wBAAwB,eAAe;AAE1D,MAAI,CAAC,WAAW,OAAO;AACrB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,WAAW,aAAa,WAAW;AAC5C,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,WAAW;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,kBAAkB,eAAe,KAAK,EAAE,SAAS,GAAG;AACtD,UAAM,KAAK,eAAe,YAAY,EAAE,KAAK;AAC7C,UAAM,YAAY,gBAAgB,YAAY;AAE9C,QAAI,UAAU,SAAS,EAAE,GAAG;AAC1B,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;AC/EA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,SAAS,oBAAoB,OAA+C;AACjF,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,gBAAgB,OAAO,iBAAiB,SAAS,MAAM,OAAO,IAAI;AAE1E,MAAI,CAAC,kBAAkB,eAAe,KAAK,EAAE,WAAW,GAAG;AACzD,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,4BAA4B;AAAA,IAC9C,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,gBAAgB,wBAAwB,gBAAgB,OAAO;AAGrE,MAAI,cAAc,UAAU,gBAAgB,KAAK;AAC/C,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,wBAAwB,cAAc,OAAO,gDAAgD,gBAAgB,GAAG;AAAA,MAC7H,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,cAAc,UAAU,gBAAgB,KAAK;AACtD,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,wBAAwB,cAAc,OAAO,+CAA+C,gBAAgB,GAAG;AAAA,MAC5H,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WACE,cAAc,WAAW,gBAAgB,OACzC,cAAc,WAAW,gBAAgB,KACzC;AACA,UAAM,YAAY,KAAK,IAAI,cAAc,UAAU,gBAAgB,OAAO,IAAI;AAC9E,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,wBAAwB,cAAc,OAAO,KAAK,YAAY,wDAAmD,wCAAwC;AAAA,MACtK,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,QAAM,qBAA+B,CAAC;AACtC,MAAI,CAAC,YAAY,iBAAkB,oBAAmB,KAAK,cAAc;AACzE,MAAI,CAAC,YAAY,QAAQ,YAAY,eAAe,EAAG,oBAAmB,KAAK,UAAU;AACzF,MAAI,CAAC,YAAY,OAAQ,oBAAmB,KAAK,MAAM;AACvD,MAAI,YAAY,cAAc,KAAK,UAAU,OAAO,SAAS;AAC3D,uBAAmB,KAAK,gBAAgB;AAE1C,MAAI,mBAAmB,WAAW,GAAG;AACnC,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,mBAAmB,UAAU,GAAG;AACzC,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,qCAAqC,mBAAmB,KAAK,IAAI,CAAC;AAAA,MAC/E,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,kCAAkC,mBAAmB,KAAK,IAAI,CAAC;AAAA,MAC5E,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AC1GA,SAAS,iBAAiB;AAW1B,SAAS,cAAc,MAA6B;AAClD,QAAM,WAA0B,CAAC;AACjC,QAAM,KAAK,KAAK,YAAY;AAC5B,MAAI,MAAM;AAEV,SAAO,MAAM,GAAG,QAAQ;AAEtB,QAAI,WAAW;AACf,QAAI,gBAAgB;AACpB,aAAS,QAAQ,GAAG,SAAS,GAAG,SAAS;AACvC,YAAM,MAAM,GAAG,QAAQ,KAAK,KAAK,IAAI,GAAG;AACxC,UAAI,QAAQ,OAAO,aAAa,MAAM,MAAM,WAAW;AACrD,mBAAW;AACX,wBAAgB;AAAA,MAClB;AAAA,IACF;AACA,QAAI,aAAa,GAAI;AAErB,UAAM,eAAe,GAAG,QAAQ,KAAK,QAAQ;AAC7C,QAAI,iBAAiB,GAAI;AAEzB,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM,WAAW,GAAG,QAAQ,UAAU,eAAe,CAAC;AACtD,QAAI,aAAa,IAAI;AACnB,YAAM,eAAe;AACrB;AAAA,IACF;AAEA,aAAS,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,MAAM,UAAU,KAAK,MAAM,eAAe,GAAG,QAAQ,CAAC;AAAA,IACxD,CAAC;AACD,UAAM,WAAW,SAAS;AAAA,EAC5B;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,OAA+C;AAC3E,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,SAAS,eAAe,IAAI;AACpC,QAAM,WAAW,cAAc,OAAO;AAGtC,QAAM,MAAM,SAAS,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC;AAEhD,MAAI,IAAI,WAAW,GAAG;AACpB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,IAAI,SAAS,GAAG;AACzB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,SAAS,IAAI,MAAM;AAAA,MAChC,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AAEL,QAAI,kBAAkB;AACtB,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,OAAO,SAAS,IAAI,CAAC;AAC3B,YAAM,OAAO,SAAS,CAAC;AACvB,UAAI,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAC/B,0BAAkB;AAClB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,iBAAiB;AACnB,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,kBAAkB,eAAe,KAAK,EAAE,SAAS,GAAG;AACtD,UAAM,KAAK,eAAe,YAAY,EAAE,KAAK;AAC7C,UAAM,cAAc,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AACvD,UAAM,2BAA2B,YAAY,KAAK,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1F,QAAI,YAAY,WAAW,GAAG;AAC5B,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,WAAW,0BAA0B;AACnC,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;ACpJA,SAAS,UAAU,gBAAgB,8BAA8B;AAE1D,SAAS,eAAe,OAA6C;AAC1E,QAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,QAAM,QAAQ,MAAM;AAEpB,MAAI,QAAQ,gBAAgB;AAC1B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,kBAAkB,KAAK,qDAAqD,cAAc;AAAA,MACvG,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,QAAQ,wBAAwB;AAClC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,kBAAkB,KAAK,0CAA0C,sBAAsB;AAAA,MACpG,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa,kBAAkB,KAAK;AAAA,IACpC,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,UAAU;AAAA,EACZ;AACF;;;ACnCO,SAAS,YAAY,OAA+C;AACzE,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,QAAQ,eAAe,IAAI;AAEnC,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AACD,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,IAAI,IAAI,KAAK,EAAE,WAAW,CAAC;AAEjF,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,WAAW,WAAW,WAAW,OAAO,QAAQ;AAC9C,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,GAAG,WAAW,MAAM,OAAO,OAAO,MAAM;AAAA,MACrD,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,kBAAkB,eAAe,KAAK,EAAE,SAAS,GAAG;AACtD,UAAM,KAAK,eAAe,YAAY,EAAE,KAAK;AAC7C,UAAM,oBAAoB,OAAO,KAAK,CAAC,QAAQ,IAAI,OAAO,IAAI,IAAI,YAAY,EAAE,SAAS,EAAE,CAAC;AAE5F,QAAI,mBAAmB;AACrB,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;AC7EO,SAAS,WAAW,OAA+C;AACxE,QAAM,UAA4B,CAAC;AACnC,QAAM,EAAE,eAAe,cAAc,IAAI;AAEzC,QAAM,cAAc,iBAAiB,cAAc,SAAS;AAC5D,QAAM,cAAc,iBAAiB,cAAc,SAAS;AAE5D,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,SAAS,cAAe,MAAM,iBAAiB,cAAe,WAAW,IAAI,KAAK,GAAG;AAAA,MAClG,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,aAAa;AAChB,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,OAAO;AACL,YAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,SAAS,cAAe,MAAM,iBAAiB,cAAe,WAAW,IAAI,KAAK,GAAG;AAAA,MAClG,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AC5BO,SAAS,eACd,OACA,QACuB;AACvB,QAAM,WAAW,IAAI,IAAa,QAAQ,kBAAkB,CAAC,CAAC;AAE9D,QAAM,aAA+B,CAAC;AAGtC,QAAM,eAAe,WAAW,KAAK;AACrC,QAAM,cAAc,qBAAqB,KAAK;AAC9C,QAAM,mBAAmB,oBAAoB,KAAK;AAClD,QAAM,iBAAiB,cAAc,KAAK;AAC1C,QAAM,kBAAkB,eAAe,KAAK;AAC5C,QAAM,eAAe,YAAY,KAAK;AACtC,QAAM,cAAc,WAAW,KAAK;AAGpC,QAAM,mBAAmB;AAAA,IACvB,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH;AAAA,IACA,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAGA,aAAW,UAAU,kBAAkB;AACrC,QAAI,CAAC,SAAS,IAAI,OAAO,EAAa,GAAG;AACvC,iBAAW,KAAK,MAAM;AAAA,IACxB;AAAA,EACF;AAGA,QAAM,QAAQ,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AAC5D,QAAM,WAAW,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,UAAU,CAAC;AAGlE,QAAM,kBAAkB,WACrB,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,EAAE,WAAW,IAAI,EACtD,IAAI,CAAC,MAAM,EAAE,WAAW;AAE3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EACF;AACF;;;AR7DA,SAAS,cAAc,YAA4B;AACjD,MAAI,cAAc,GAAI,QAAO;AAC7B,MAAI,cAAc,GAAI,QAAO;AAC7B,SAAO;AACT;AAEA,SAAS,cAAc,YAA4B;AACjD,MAAI,cAAc,GAAI,QAAO;AAC7B,MAAI,cAAc,GAAI,QAAO;AAC7B,SAAO;AACT;AAKO,SAAS,WAAW,EAAE,OAAO,SAAS,GAAoB;AAC/D,QAAM,aAAa,WAAW,IAAI,KAAK,MAAO,QAAQ,WAAY,GAAG,IAAI;AACzE,QAAM,QAAQ,cAAc,UAAU;AACtC,QAAM,QAAQ,cAAc,UAAU;AAEtC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,QACT,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,iBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,SAAS;AAAA,UACT,gBAAgB;AAAA,UAChB,YAAY;AAAA,UACZ,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA;AAAA,QACE;AAAA,QACA,EAAE,OAAO,EAAE,YAAY,KAAK,UAAU,QAAQ,OAAO,OAAO,EAAE;AAAA,QAC9D;AAAA,MACF;AAAA,MACA;AAAA,QACE;AAAA,QACA,EAAE,OAAO,EAAE,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAE;AAAA,QACtD,GAAG,UAAU;AAAA,MACf;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,iBAAiB;AAAA,UACjB,cAAc;AAAA,UACd,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,cAAc,OAAO;AAAA,QACnB,OAAO;AAAA,UACL,OAAO,GAAG,UAAU;AAAA,UACpB,QAAQ;AAAA,UACR,iBAAiB;AAAA,UACjB,cAAc;AAAA,UACd,YAAY;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA;AAAA,MACE;AAAA,MACA,EAAE,OAAO,EAAE,WAAW,OAAO,UAAU,QAAQ,OAAO,OAAO,EAAE;AAAA,MAC/D,GAAG,KAAK,WAAM,KAAK,IAAI,QAAQ;AAAA,IACjC;AAAA,EACF;AACF;AAQA,IAAM,eAAuC;AAAA,EAC3C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,MAAM;AACR;AAEA,IAAM,gBAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,MAAM;AACR;AAKO,SAAS,UAAU,EAAE,QAAQ,GAAmB;AACrD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,YAAY;AAAA,MACd;AAAA,IACF;AAAA,IACA,GAAG,QAAQ;AAAA,MAAI,CAAC,WACd;AAAA,QACE;AAAA,QACA;AAAA,UACE,KAAK,OAAO;AAAA,UACZ,OAAO;AAAA,YACL,SAAS;AAAA,YACT,cAAc;AAAA,YACd,SAAS;AAAA,YACT,KAAK;AAAA,YACL,YAAY;AAAA,UACd;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA,EAAE,OAAO,EAAE,YAAY,GAAG,UAAU,OAAO,EAAE;AAAA,UAC7C,aAAa,OAAO,MAAM,KAAK;AAAA,QACjC;AAAA,QACA;AAAA,UACE;AAAA,UACA,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;AAAA,UACrB;AAAA,YACE;AAAA,YACA;AAAA,cACE,OAAO;AAAA,gBACL,YAAY;AAAA,gBACZ,UAAU;AAAA,gBACV,OAAO,cAAc,OAAO,MAAM,KAAK;AAAA,cACzC;AAAA,YACF;AAAA,YACA,OAAO;AAAA,UACT;AAAA,UACA;AAAA,YACE;AAAA,YACA,EAAE,OAAO,EAAE,UAAU,QAAQ,OAAO,QAAQ,WAAW,MAAM,EAAE;AAAA,YAC/D,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAaO,SAAS,gBAAgB,EAAE,OAAO,QAAQ,SAAS,GAAyB;AACjF,QAAM,SAAS,QAAQ,MAAM,eAAe,OAAO,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC;AAE3E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,QACL,YAAY;AAAA,MACd;AAAA,IACF;AAAA,IACA,cAAc,YAAY,EAAE,OAAO,OAAO,OAAO,UAAU,OAAO,SAAS,CAAC;AAAA,IAC5E,cAAc,OAAO,EAAE,OAAO,EAAE,QAAQ,OAAO,EAAE,CAAC;AAAA,IAClD,cAAc,WAAW,EAAE,SAAS,OAAO,QAAQ,CAAC;AAAA,IACpD,YAAY;AAAA,EACd;AACF;","names":[]} |
+13
-13
| { | ||
| "name": "@power-seo/content-analysis", | ||
| "version": "1.0.7", | ||
| "version": "1.0.8", | ||
| "description": "Yoast-style SEO content analysis engine with scoring, checks, and React components", | ||
@@ -25,14 +25,4 @@ "license": "MIT", | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "eslint src/", | ||
| "lint:fix": "eslint src/ --fix", | ||
| "clean": "rimraf dist" | ||
| }, | ||
| "dependencies": { | ||
| "@power-seo/core": "workspace:*" | ||
| "@power-seo/core": "1.0.3" | ||
| }, | ||
@@ -97,3 +87,13 @@ "peerDependencies": { | ||
| "url": "https://github.com/sponsors/cybercraftbd" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "eslint src/", | ||
| "lint:fix": "eslint src/ --fix", | ||
| "clean": "rimraf dist" | ||
| } | ||
| } | ||
| } |
+71
-73
@@ -23,12 +23,12 @@ # @power-seo/content-analysis | ||
| | | Without | With | | ||
| | ----------------- | ----------------- | ------------------------------------------- | | ||
| | Keyphrase check | ❌ Manual grep | ✅ Density + distribution scoring | | ||
| | Title validation | ❌ Eye-check only | ✅ Presence, length, keyphrase match | | ||
| | Meta description | ❌ Unchecked | ✅ Length (120–160 chars) + keyphrase | | ||
| | Heading structure | ❌ Missed H1s | ✅ H1 hierarchy + keyphrase in subheadings | | ||
| | Image alt text | ❌ Skipped | ✅ Alt presence + keyphrase in alt | | ||
| | Link analysis | ❌ Unknown | ✅ Internal + external link presence | | ||
| | SEO score | ❌ Guesswork | ✅ Aggregate score with per-check breakdown | | ||
| | Framework support | ❌ WordPress-only | ✅ Next.js, Remix, Vite, Node.js, Edge | | ||
| | | Without | With | | ||
| |---|---|---| | ||
| | Keyphrase check | ❌ Manual grep | ✅ Density + distribution scoring | | ||
| | Title validation | ❌ Eye-check only | ✅ Presence, length, keyphrase match | | ||
| | Meta description | ❌ Unchecked | ✅ Length (120–160 chars) + keyphrase | | ||
| | Heading structure | ❌ Missed H1s | ✅ H1 hierarchy + keyphrase in subheadings | | ||
| | Image alt text | ❌ Skipped | ✅ Alt presence + keyphrase in alt | | ||
| | Link analysis | ❌ Unknown | ✅ Internal + external link presence | | ||
| | SEO score | ❌ Guesswork | ✅ Aggregate score with per-check breakdown | | ||
| | Framework support | ❌ WordPress-only | ✅ Next.js, Remix, Vite, Node.js, Edge | | ||
@@ -64,16 +64,16 @@  | ||
| | ------------------------------ | :-------------------------: | :-------: | :------: | :----------: | :----------: | | ||
| | Keyphrase density check | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Keyphrase distribution | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Title + meta validation | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Heading structure check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Image alt + keyphrase check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Internal / external link check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Aggregate SEO score | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Per-check disable config | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | Works outside WordPress | ✅ | ❌ | ✅ | ✅ | ✅ | | ||
| | TypeScript-first | ✅ | ❌ | Partial | ❌ | ❌ | | ||
| | Tree-shakeable | ✅ | ❌ | Partial | ❌ | ❌ | | ||
| | React UI components | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | CI / Node.js usage | ✅ | ❌ | ❌ | ✅ | ❌ | | ||
| | Zero runtime dependencies | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | Keyphrase density check | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Keyphrase distribution | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Title + meta validation | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Heading structure check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Image alt + keyphrase check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Internal / external link check | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | Aggregate SEO score | ✅ | ✅ | ❌ | Partial | ❌ | | ||
| | Per-check disable config | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | Works outside WordPress | ✅ | ❌ | ✅ | ✅ | ✅ | | ||
| | TypeScript-first | ✅ | ❌ | Partial | ❌ | ❌ | | ||
| | Tree-shakeable | ✅ | ❌ | Partial | ❌ | ❌ | | ||
| | React UI components | ✅ | ✅ | ❌ | ❌ | ❌ | | ||
| | CI / Node.js usage | ✅ | ❌ | ❌ | ✅ | ❌ | | ||
| | Zero runtime dependencies | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
@@ -112,4 +112,4 @@  | ||
| console.log(result.score); // e.g. 38 | ||
| console.log(result.maxScore); // e.g. 55 | ||
| console.log(result.score); // e.g. 38 | ||
| console.log(result.maxScore); // e.g. 55 | ||
| console.log(result.results); | ||
@@ -122,3 +122,2 @@ // [{ id: 'title-presence', status: 'good', description: '...', score: 5, maxScore: 5 }, ...] | ||
| **Status thresholds (per check):** | ||
| - `good` — check fully passes | ||
@@ -141,4 +140,3 @@ - `ok` — check partially passes | ||
| title: 'Next.js SEO Best Practices', | ||
| metaDescription: | ||
| 'Learn how to optimize your Next.js app for search engines with meta tags and structured data.', | ||
| metaDescription: 'Learn how to optimize your Next.js app for search engines with meta tags and structured data.', | ||
| focusKeyphrase: 'next.js seo', | ||
@@ -247,6 +245,6 @@ content: htmlString, | ||
| | Import | Description | | ||
| | ----------------------------------- | -------------------------------------------- | | ||
| | `@power-seo/content-analysis` | Core analyzer and individual check functions | | ||
| | `@power-seo/content-analysis/react` | React components for analysis UI | | ||
| | Import | Description | | ||
| | --- | --- | | ||
| | `@power-seo/content-analysis` | Core analyzer and individual check functions | | ||
| | `@power-seo/content-analysis/react` | React components for analysis UI | | ||
@@ -264,32 +262,32 @@ ### `analyzeContent()` | ||
| | Prop | Type | Required | Description | | ||
| | ----------------- | -------------------------------------- | -------- | ------------------------------------------- | | ||
| | `content` | `string` | ✅ | Body HTML string | | ||
| | `title` | `string` | — | Page `<title>` content | | ||
| | `metaDescription` | `string` | — | Meta description content | | ||
| | `focusKeyphrase` | `string` | — | Focus keyphrase to analyze against | | ||
| | `slug` | `string` | — | URL slug (used for keyphrase-in-slug check) | | ||
| | `images` | `Array<{ src: string; alt?: string }>` | — | Images found on the page | | ||
| | `internalLinks` | `string[]` | — | Internal link URLs | | ||
| | `externalLinks` | `string[]` | — | External link URLs | | ||
| | Prop | Type | Required | Description | | ||
| | ----------------- | -------------------------------------- | -------- | ---------------------------------------------- | | ||
| | `content` | `string` | ✅ | Body HTML string | | ||
| | `title` | `string` | — | Page `<title>` content | | ||
| | `metaDescription` | `string` | — | Meta description content | | ||
| | `focusKeyphrase` | `string` | — | Focus keyphrase to analyze against | | ||
| | `slug` | `string` | — | URL slug (used for keyphrase-in-slug check) | | ||
| | `images` | `Array<{ src: string; alt?: string }>` | — | Images found on the page | | ||
| | `internalLinks` | `string[]` | — | Internal link URLs | | ||
| | `externalLinks` | `string[]` | — | External link URLs | | ||
| #### `ContentAnalysisOutput` | ||
| | Field | Type | Description | | ||
| | ----------------- | ------------------ | ------------------------------------------------- | | ||
| | `score` | `number` | Sum of all individual check scores | | ||
| | `maxScore` | `number` | Maximum possible score (varies by enabled checks) | | ||
| | `results` | `AnalysisResult[]` | Per-check results | | ||
| | `recommendations` | `string[]` | Descriptions from all failed or partial checks | | ||
| | Field | Type | Description | | ||
| | ----------------- | ------------------ | ----------------------------------------------------- | | ||
| | `score` | `number` | Sum of all individual check scores | | ||
| | `maxScore` | `number` | Maximum possible score (varies by enabled checks) | | ||
| | `results` | `AnalysisResult[]` | Per-check results | | ||
| | `recommendations` | `string[]` | Descriptions from all failed or partial checks | | ||
| #### `AnalysisResult` | ||
| | Field | Type | Description | | ||
| | ------------- | ---------------- | ------------------------------------------------------ | | ||
| | `id` | `string` | Unique check identifier (one of the `CheckId` values) | | ||
| | `title` | `string` | Short display label for the check (e.g. `"SEO title"`) | | ||
| | `description` | `string` | Human-readable actionable feedback | | ||
| | `status` | `AnalysisStatus` | `'good'` \| `'ok'` \| `'poor'` | | ||
| | `score` | `number` | Points earned for this check | | ||
| | `maxScore` | `number` | Maximum points for this check | | ||
| | Field | Type | Description | | ||
| | ------------- | ---------------- | --------------------------------------------------------- | | ||
| | `id` | `string` | Unique check identifier (one of the `CheckId` values) | | ||
| | `title` | `string` | Short display label for the check (e.g. `"SEO title"`) | | ||
| | `description` | `string` | Human-readable actionable feedback | | ||
| | `status` | `AnalysisStatus` | `'good'` \| `'ok'` \| `'poor'` | | ||
| | `score` | `number` | Points earned for this check | | ||
| | `maxScore` | `number` | Maximum points for this check | | ||
@@ -304,11 +302,11 @@ #### `AnalysisConfig` | ||
| | Function | Check ID(s) | Checks For | | ||
| | ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | ||
| | `checkTitle(input)` | `title-presence`, `title-keyphrase` | Title presence **and** length (50–60 chars, validated inside `title-presence`), keyphrase | | ||
| | `checkMetaDescription(input)` | `meta-description-presence`, `meta-description-keyphrase` | Description presence, length (120–160 chars), keyphrase | | ||
| | `checkKeyphraseUsage(input)` | `keyphrase-density`, `keyphrase-distribution` | Density (0.5–2.5%) and occurrence in key areas | | ||
| | `checkHeadings(input)` | `heading-structure`, `heading-keyphrase` | H1 presence, hierarchy, keyphrase in subheadings | | ||
| | `checkWordCount(input)` | `word-count` | Min 300 words (good at 1,000+) | | ||
| | `checkImages(input)` | `image-alt`, `image-keyphrase` | Alt text presence and keyphrase in alt | | ||
| | `checkLinks(input)` | `internal-links`, `external-links` | Internal and external link presence | | ||
| | Function | Check ID(s) | Checks For | | ||
| | ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | ||
| | `checkTitle(input)` | `title-presence`, `title-keyphrase` | Title presence **and** length (50–60 chars, validated inside `title-presence`), keyphrase | | ||
| | `checkMetaDescription(input)` | `meta-description-presence`, `meta-description-keyphrase` | Description presence, length (120–160 chars), keyphrase | | ||
| | `checkKeyphraseUsage(input)` | `keyphrase-density`, `keyphrase-distribution` | Density (0.5–2.5%) and occurrence in key areas | | ||
| | `checkHeadings(input)` | `heading-structure`, `heading-keyphrase` | H1 presence, hierarchy, keyphrase in subheadings | | ||
| | `checkWordCount(input)` | `word-count` | Min 300 words (good at 1,000+) | | ||
| | `checkImages(input)` | `image-alt`, `image-keyphrase` | Alt text presence and keyphrase in alt | | ||
| | `checkLinks(input)` | `internal-links`, `external-links` | Internal and external link presence | | ||
@@ -319,9 +317,9 @@ > **Note:** There is no separate `title-length` check ID. Title length validation (50–60 chars) is evaluated inside the `title-presence` check — a title that exists but is outside the recommended range returns `status: 'ok'` rather than `'good'`. | ||
| | Type | Description | | ||
| | ----------------------- | ------------------------------------------------------------------------ | | ||
| | `CheckId` | Union of all 13 built-in check IDs | | ||
| | `AnalysisConfig` | `{ disabledChecks?: CheckId[] }` | | ||
| | `AnalysisStatus` | `'good' \| 'ok' \| 'poor'` | | ||
| | `ContentAnalysisInput` | Input shape for `analyzeContent()` | | ||
| | `ContentAnalysisOutput` | Output shape from `analyzeContent()` | | ||
| | Type | Description | | ||
| | ----------------------- | --------------------------------------------------------- | | ||
| | `CheckId` | Union of all 13 built-in check IDs | | ||
| | `AnalysisConfig` | `{ disabledChecks?: CheckId[] }` | | ||
| | `AnalysisStatus` | `'good' \| 'ok' \| 'poor'` | | ||
| | `ContentAnalysisInput` | Input shape for `analyzeContent()` | | ||
| | `ContentAnalysisOutput` | Output shape from `analyzeContent()` | | ||
| | `AnalysisResult` | Single check result with id, title, description, status, score, maxScore | | ||
@@ -328,0 +326,0 @@ |
17
6.25%259229
-0.81%396
-0.5%Updated