@power-seo/content-analysis 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,24 +12,24 @@ Keyword-focused content analysis with real-time scoring, readability checks, and
12
12
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)
13
13
  [![tree-shakeable](https://img.shields.io/badge/tree--shakeable-yes-brightgreen)](https://bundlephobia.com/package/@power-seo/content-analysis)
14
14
 
15
- `@power-seo/content-analysis` delivers a comprehensive, WordPress SEO plugin–style scoring pipeline for evaluating text content, comparable to Yoast SEO, All in One SEO (AIOSEO), Rank Math, SEOPress, and The SEO Framework. Provide a page title, meta description, body content, focus keyphrase, images, and links — get back structured `good` / `needs-improvement` / `poor` results across all critical SEO factors. Run it server-side in a CMS, client-side in a React editor, or inside a CI content quality gate. All 13 analysis checks are fully configurable and tree-shakeable.
15
+ `@power-seo/content-analysis` delivers a comprehensive, WordPress SEO plugin–style scoring pipeline for evaluating text content, comparable to Yoast SEO, All in One SEO (AIOSEO), Rank Math, SEOPress, and The SEO Framework. Provide a page title, meta description, body content, focus keyphrase, images, and links — get back structured `good` / `ok` / `poor` results across all critical SEO factors. Run it server-side in a CMS, client-side in a React editor, or inside a CI content quality gate. All 13 analysis checks are fully configurable and tree-shakeable.
16
16
 
17
- > **Zero runtime dependencies** — only `@power-seo/core` as a peer.
17
+ > **Zero external runtime dependencies** — `@power-seo/core` is a direct dependency bundled with this package; no extra install needed.
18
18
 
19
19
  ---
20
20
 
21
21
  ## Why @power-seo/content-analysis?
22
22
 
23
- | | Without | With |
24
- |---|---|---|
25
- | Keyphrase check | ❌ Manual grep | ✅ Density + distribution scoring |
26
- | Title validation | ❌ Eye-check only | ✅ Presence, length, keyphrase match |
27
- | Meta description | ❌ Unchecked | ✅ Length (120–160 chars) + keyphrase |
28
- | Heading structure | ❌ Missed H1s | ✅ H1 hierarchy + keyphrase in subheadings |
29
- | Image alt text | ❌ Skipped | ✅ Alt presence + keyphrase in alt |
30
- | Link analysis | ❌ Unknown | ✅ Internal + external link presence |
31
- | SEO score | ❌ Guesswork | ✅ Aggregate score with per-check breakdown |
32
- | Framework support | ❌ WordPress-only | ✅ Next.js, Remix, Vite, Node.js, Edge |
23
+ | | Without | With |
24
+ | ----------------- | ----------------- | ------------------------------------------- |
25
+ | Keyphrase check | ❌ Manual grep | ✅ Density + distribution scoring |
26
+ | Title validation | ❌ Eye-check only | ✅ Presence, length, keyphrase match |
27
+ | Meta description | ❌ Unchecked | ✅ Length (120–160 chars) + keyphrase |
28
+ | Heading structure | ❌ Missed H1s | ✅ H1 hierarchy + keyphrase in subheadings |
29
+ | Image alt text | ❌ Skipped | ✅ Alt presence + keyphrase in alt |
30
+ | Link analysis | ❌ Unknown | ✅ Internal + external link presence |
31
+ | SEO score | ❌ Guesswork | ✅ Aggregate score with per-check breakdown |
32
+ | Framework support | ❌ WordPress-only | ✅ Next.js, Remix, Vite, Node.js, Edge |
33
33
 
34
34
  ![SEO Score Dashboard](../../image/content-analysis/score-dashboard.svg)
35
35
 
@@ -60,20 +60,20 @@ Keyword-focused content analysis with real-time scoring, readability checks, and
60
60
 
61
61
  | Feature | @power-seo/content-analysis | Yoast SEO | next-seo | seo-analyzer | react-helmet |
62
62
  | ------------------------------ | :-------------------------: | :-------: | :------: | :----------: | :----------: |
63
- | Keyphrase density check | | | | Partial | |
64
- | Keyphrase distribution | | | | | |
65
- | Title + meta validation | | | | Partial | |
66
- | Heading structure check | | | | | |
67
- | Image alt + keyphrase check | | | | | |
68
- | Internal / external link check | | | | | |
69
- | Aggregate SEO score | | | | Partial | |
70
- | Per-check disable config | | | | | |
71
- | Works outside WordPress | | | | | |
72
- | TypeScript-first | | | Partial | | |
73
- | Tree-shakeable | | | Partial | | |
74
- | React UI components | | | | | |
75
- | CI / Node.js usage | | | | | |
76
- | Zero runtime dependencies | | | | | |
63
+ | Keyphrase density check | | | | Partial | |
64
+ | Keyphrase distribution | | | | | |
65
+ | Title + meta validation | | | | Partial | |
66
+ | Heading structure check | | | | | |
67
+ | Image alt + keyphrase check | | | | | |
68
+ | Internal / external link check | | | | | |
69
+ | Aggregate SEO score | | | | Partial | |
70
+ | Per-check disable config | | | | | |
71
+ | Works outside WordPress | | | | | |
72
+ | TypeScript-first | | | Partial | | |
73
+ | Tree-shakeable | | | Partial | | |
74
+ | React UI components | | | | | |
75
+ | CI / Node.js usage | | | | | |
76
+ | Zero runtime dependencies | | | | | |
77
77
 
78
78
  ![Keyword Density Analysis](../../image/content-analysis/keyword-density.svg)
79
79
 
@@ -82,15 +82,15 @@ Keyword-focused content analysis with real-time scoring, readability checks, and
82
82
  ## Installation
83
83
 
84
84
  ```bash
85
- npm install @power-seo/content-analysis @power-seo/core
85
+ npm install @power-seo/content-analysis
86
86
  ```
87
87
 
88
88
  ```bash
89
- yarn add @power-seo/content-analysis @power-seo/core
89
+ yarn add @power-seo/content-analysis
90
90
  ```
91
91
 
92
92
  ```bash
93
- pnpm add @power-seo/content-analysis @power-seo/core
93
+ pnpm add @power-seo/content-analysis
94
94
  ```
95
95
 
96
96
  ---
@@ -103,21 +103,22 @@ import { analyzeContent } from '@power-seo/content-analysis';
103
103
  const result = analyzeContent({
104
104
  title: 'Best Running Shoes for Beginners',
105
105
  metaDescription: 'Discover the best running shoes for beginners with our expert guide.',
106
- keyphrase: 'running shoes for beginners',
106
+ focusKeyphrase: 'running shoes for beginners',
107
107
  content: '<h1>Best Running Shoes</h1><p>Finding the right running shoes...</p>',
108
- url: 'https://example.com/best-running-shoes',
109
108
  });
110
109
 
111
- console.log(result.overallStatus); // "good" | "needs-improvement" | "poor"
110
+ console.log(result.score); // e.g. 38
111
+ console.log(result.maxScore); // e.g. 55
112
112
  console.log(result.results);
113
- // [{ id: 'title-presence', status: 'good', message: '...' }, ...]
113
+ // [{ id: 'title-presence', status: 'good', description: '...', score: 5, maxScore: 5 }, ...]
114
114
  ```
115
115
 
116
116
  ![SEO Check Results](../../image/content-analysis/check-results.svg)
117
117
 
118
118
  **Status thresholds (per check):**
119
+
119
120
  - `good` — check fully passes
120
- - `needs-improvement` — check partially passes
121
+ - `ok` — check partially passes
121
122
  - `poor` — check fails
122
123
 
123
124
  ---
@@ -133,17 +134,18 @@ import { analyzeContent } from '@power-seo/content-analysis';
133
134
 
134
135
  const output = analyzeContent({
135
136
  title: 'Next.js SEO Best Practices',
136
- metaDescription: 'Learn how to optimize your Next.js app for search engines with meta tags and structured data.',
137
- keyphrase: 'next.js seo',
137
+ metaDescription:
138
+ 'Learn how to optimize your Next.js app for search engines with meta tags and structured data.',
139
+ focusKeyphrase: 'next.js seo',
138
140
  content: htmlString,
139
- url: 'https://example.com/nextjs-seo',
140
141
  images: imageList,
141
142
  internalLinks: internalLinks,
142
143
  externalLinks: externalLinks,
143
144
  });
144
145
 
145
- // output.overallStatus 'good' | 'needs-improvement' | 'poor'
146
- // output.results AnalysisResult[]
146
+ // output.score number (sum of all check scores)
147
+ // output.maxScore number (maximum possible score)
148
+ // output.results → AnalysisResult[]
147
149
  ```
148
150
 
149
151
  ### Running Individual Checks
@@ -162,20 +164,22 @@ import {
162
164
  } from '@power-seo/content-analysis';
163
165
 
164
166
  const titleResults = checkTitle({
165
- title: 'React SEO Guide',
166
- keyphrase: 'react seo',
167
167
  content: '',
168
+ title: 'React SEO Guide',
169
+ focusKeyphrase: 'react seo',
168
170
  });
169
- // [{ id: 'title-presence', status: 'good', message: '...' },
170
- // { id: 'title-keyphrase', status: 'good', message: '...' }]
171
+ // [
172
+ // { id: 'title-presence', title: 'SEO title', description: '...', status: 'good', score: 5, maxScore: 5 },
173
+ // { id: 'title-keyphrase', title: 'Keyphrase in title', description: '...', status: 'good', score: 5, maxScore: 5 }
174
+ // ]
171
175
 
172
176
  const wcResult = checkWordCount({ content: shortHtml });
173
- // { id: 'word-count', status: 'poor', message: 'Content is 180 words, below minimum of 300.' }
177
+ // { id: 'word-count', title: 'Word count', description: 'The content is 180 words...', status: 'poor', score: 1, maxScore: 5 }
174
178
  ```
175
179
 
176
180
  ### Disabling Specific Checks
177
181
 
178
- Pass `config.disabledChecks` to skip checks that don't apply to your content type:
182
+ Pass `config.disabledChecks` to exclude specific checks from the output. Checks are still executed internally but their results are filtered from `output.results` and excluded from `score`/`maxScore` totals. Invalid check IDs are silently ignored.
179
183
 
180
184
  ```ts
181
185
  import { analyzeContent } from '@power-seo/content-analysis';
@@ -191,6 +195,7 @@ Import from the `/react` entry point for pre-built analysis UI components:
191
195
 
192
196
  ```tsx
193
197
  import { ContentAnalyzer, ScorePanel, CheckList } from '@power-seo/content-analysis/react';
198
+ import { analyzeContent } from '@power-seo/content-analysis';
194
199
  import type { ContentAnalysisInput } from '@power-seo/content-analysis';
195
200
 
196
201
  // All-in-one component
@@ -217,13 +222,13 @@ Block deploys when SEO checks fail:
217
222
  ```ts
218
223
  import { analyzeContent } from '@power-seo/content-analysis';
219
224
 
220
- const output = analyzeContent({ title, metaDescription, keyphrase, content });
225
+ const output = analyzeContent({ title, metaDescription, focusKeyphrase, content });
221
226
 
222
227
  const failures = output.results.filter((r) => r.status === 'poor');
223
228
 
224
229
  if (failures.length > 0) {
225
230
  console.error('SEO checks failed:');
226
- failures.forEach((r) => console.error(' ✗', r.message));
231
+ failures.forEach((r) => console.error(' ✗', r.description));
227
232
  process.exit(1);
228
233
  }
229
234
  ```
@@ -234,10 +239,10 @@ if (failures.length > 0) {
234
239
 
235
240
  ### Entry Points
236
241
 
237
- | Import | Description |
238
- | --- | --- |
239
- | `@power-seo/content-analysis` | Core analyzer and individual check functions |
240
- | `@power-seo/content-analysis/react` | React components for analysis UI |
242
+ | Import | Description |
243
+ | ----------------------------------- | -------------------------------------------- |
244
+ | `@power-seo/content-analysis` | Core analyzer and individual check functions |
245
+ | `@power-seo/content-analysis/react` | React components for analysis UI |
241
246
 
242
247
  ### `analyzeContent()`
243
248
 
@@ -250,36 +255,36 @@ function analyzeContent(
250
255
 
251
256
  #### `ContentAnalysisInput`
252
257
 
253
- | Prop | Type | Required | Description |
254
- | ----------------- | -------------------------------------- | -------- | ---------------------------------------------- |
255
- | `content` | `string` | ✅ | Body HTML string |
256
- | `title` | `string` | — | Page `<title>` content |
257
- | `metaDescription` | `string` | — | Meta description content |
258
- | `keyphrase` | `string` | — | Focus keyphrase to analyze against |
259
- | `url` | `string` | — | Page URL (used for slug analysis) |
260
- | `images` | `Array<{ src: string; alt?: string }>` | — | Images found on the page |
261
- | `internalLinks` | `string[]` | — | Internal link URLs |
262
- | `externalLinks` | `string[]` | — | External link URLs |
258
+ | Prop | Type | Required | Description |
259
+ | ----------------- | -------------------------------------- | -------- | ------------------------------------------- |
260
+ | `content` | `string` | ✅ | Body HTML string |
261
+ | `title` | `string` | — | Page `<title>` content |
262
+ | `metaDescription` | `string` | — | Meta description content |
263
+ | `focusKeyphrase` | `string` | — | Focus keyphrase to analyze against |
264
+ | `slug` | `string` | — | URL slug (used for keyphrase-in-slug check) |
265
+ | `images` | `Array<{ src: string; alt?: string }>` | — | Images found on the page |
266
+ | `internalLinks` | `string[]` | — | Internal link URLs |
267
+ | `externalLinks` | `string[]` | — | External link URLs |
263
268
 
264
269
  #### `ContentAnalysisOutput`
265
270
 
266
- | Field | Type | Description |
267
- | --------------- | ------------------ | --------------------------------------------------------- |
268
- | `overallStatus` | `AnalysisStatus` | `'good'` \| `'needs-improvement'` \| `'poor'` |
269
- | `score` | `number` | Sum of all individual check scores |
270
- | `maxScore` | `number` | Maximum possible score (varies by enabled checks) |
271
- | `results` | `AnalysisResult[]` | Per-check results |
272
- | `recommendations` | `string[]` | Descriptions from all failed or partial checks |
271
+ | Field | Type | Description |
272
+ | ----------------- | ------------------ | ------------------------------------------------- |
273
+ | `score` | `number` | Sum of all individual check scores |
274
+ | `maxScore` | `number` | Maximum possible score (varies by enabled checks) |
275
+ | `results` | `AnalysisResult[]` | Per-check results |
276
+ | `recommendations` | `string[]` | Descriptions from all failed or partial checks |
273
277
 
274
278
  #### `AnalysisResult`
275
279
 
276
- | Field | Type | Description |
277
- | --------- | ---------------- | --------------------------------------------- |
278
- | `id` | `CheckId` | Unique check identifier (see table below) |
279
- | `status` | `AnalysisStatus` | `'good'` \| `'needs-improvement'` \| `'poor'` |
280
- | `message` | `string` | Human-readable actionable feedback |
281
- | `score` | `number` | Points earned for this check |
282
- | `maxScore`| `number` | Maximum points for this check |
280
+ | Field | Type | Description |
281
+ | ------------- | ---------------- | ------------------------------------------------------ |
282
+ | `id` | `string` | Unique check identifier (one of the `CheckId` values) |
283
+ | `title` | `string` | Short display label for the check (e.g. `"SEO title"`) |
284
+ | `description` | `string` | Human-readable actionable feedback |
285
+ | `status` | `AnalysisStatus` | `'good'` \| `'ok'` \| `'poor'` |
286
+ | `score` | `number` | Points earned for this check |
287
+ | `maxScore` | `number` | Maximum points for this check |
283
288
 
284
289
  #### `AnalysisConfig`
285
290
 
@@ -289,26 +294,28 @@ function analyzeContent(
289
294
 
290
295
  ### Individual Check Functions
291
296
 
292
- | Function | Check ID(s) | Checks For |
293
- | ----------------------------- | -------------------------------------------------------------- | -------------------------------------------------- |
294
- | `checkTitle(input)` | `title-presence`, `title-keyphrase` | Title presence, length (30–60 chars), keyphrase |
295
- | `checkMetaDescription(input)` | `meta-description-presence`, `meta-description-keyphrase` | Description presence, length (120–160 chars), keyphrase |
296
- | `checkKeyphraseUsage(input)` | `keyphrase-density`, `keyphrase-distribution` | Density (0.5–2.5%) and occurrence in key areas |
297
- | `checkHeadings(input)` | `heading-structure`, `heading-keyphrase` | H1 presence, hierarchy, keyphrase in subheadings |
298
- | `checkWordCount(input)` | `word-count` | Min 300 words (good at 1,000+) |
299
- | `checkImages(input)` | `image-alt`, `image-keyphrase` | Alt text presence and keyphrase in alt |
300
- | `checkLinks(input)` | `internal-links`, `external-links` | Internal and external link presence |
297
+ | Function | Check ID(s) | Checks For |
298
+ | ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
299
+ | `checkTitle(input)` | `title-presence`, `title-keyphrase` | Title presence **and** length (50–60 chars, validated inside `title-presence`), keyphrase |
300
+ | `checkMetaDescription(input)` | `meta-description-presence`, `meta-description-keyphrase` | Description presence, length (120–160 chars), keyphrase |
301
+ | `checkKeyphraseUsage(input)` | `keyphrase-density`, `keyphrase-distribution` | Density (0.5–2.5%) and occurrence in key areas |
302
+ | `checkHeadings(input)` | `heading-structure`, `heading-keyphrase` | H1 presence, hierarchy, keyphrase in subheadings |
303
+ | `checkWordCount(input)` | `word-count` | Min 300 words (good at 1,000+) |
304
+ | `checkImages(input)` | `image-alt`, `image-keyphrase` | Alt text presence and keyphrase in alt |
305
+ | `checkLinks(input)` | `internal-links`, `external-links` | Internal and external link presence |
306
+
307
+ > **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'`.
301
308
 
302
309
  ### Types
303
310
 
304
- | Type | Description |
305
- | ----------------------- | --------------------------------------------------------- |
306
- | `CheckId` | Union of all 13 built-in check IDs |
307
- | `AnalysisConfig` | `{ disabledChecks?: CheckId[] }` |
308
- | `AnalysisStatus` | `'good' \| 'needs-improvement' \| 'poor'` |
309
- | `ContentAnalysisInput` | Input shape for `analyzeContent()` |
310
- | `ContentAnalysisOutput` | Output shape from `analyzeContent()` |
311
- | `AnalysisResult` | Single check result with id, status, message, score |
311
+ | Type | Description |
312
+ | ----------------------- | ------------------------------------------------------------------------ |
313
+ | `CheckId` | Union of all 13 built-in check IDs |
314
+ | `AnalysisConfig` | `{ disabledChecks?: CheckId[] }` |
315
+ | `AnalysisStatus` | `'good' \| 'ok' \| 'poor'` |
316
+ | `ContentAnalysisInput` | Input shape for `analyzeContent()` |
317
+ | `ContentAnalysisOutput` | Output shape from `analyzeContent()` |
318
+ | `AnalysisResult` | Single check result with id, title, description, status, score, maxScore |
312
319
 
313
320
  ---
314
321
 
@@ -326,7 +333,7 @@ function analyzeContent(
326
333
  ## Architecture Overview
327
334
 
328
335
  - **Pure TypeScript** — no compiled binary, no native modules
329
- - **Zero runtime dependencies** — only `@power-seo/core` as a peer dependency
336
+ - **Zero external runtime dependencies** — `@power-seo/core` ships as a direct dependency, no separate install
330
337
  - **Framework-agnostic** — works in any JavaScript environment
331
338
  - **SSR compatible** — safe to run in Next.js Server Components, Remix loaders, or Express handlers
332
339
  - **Edge runtime safe** — no Node.js-specific APIs; runs in Cloudflare Workers, Vercel Edge, Deno
package/dist/index.cjs CHANGED
@@ -1,8 +1,38 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
- var core = require('@power-seo/core');
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ analyzeContent: () => analyzeContent,
24
+ checkHeadings: () => checkHeadings,
25
+ checkImages: () => checkImages,
26
+ checkKeyphraseUsage: () => checkKeyphraseUsage,
27
+ checkLinks: () => checkLinks,
28
+ checkMetaDescription: () => checkMetaDescription,
29
+ checkTitle: () => checkTitle,
30
+ checkWordCount: () => checkWordCount
31
+ });
32
+ module.exports = __toCommonJS(src_exports);
4
33
 
5
34
  // src/checks/title.ts
35
+ var import_core = require("@power-seo/core");
6
36
  function checkTitle(input) {
7
37
  const results = [];
8
38
  const { title, focusKeyphrase } = input;
@@ -17,7 +47,7 @@ function checkTitle(input) {
17
47
  });
18
48
  return results;
19
49
  }
20
- const validation = core.validateTitle(title);
50
+ const validation = (0, import_core.validateTitle)(title);
21
51
  if (!validation.valid) {
22
52
  results.push({
23
53
  id: "title-presence",
@@ -71,6 +101,9 @@ function checkTitle(input) {
71
101
  }
72
102
  return results;
73
103
  }
104
+
105
+ // src/checks/meta-description.ts
106
+ var import_core2 = require("@power-seo/core");
74
107
  function checkMetaDescription(input) {
75
108
  const results = [];
76
109
  const { metaDescription, focusKeyphrase } = input;
@@ -85,7 +118,7 @@ function checkMetaDescription(input) {
85
118
  });
86
119
  return results;
87
120
  }
88
- const validation = core.validateMetaDescription(metaDescription);
121
+ const validation = (0, import_core2.validateMetaDescription)(metaDescription);
89
122
  if (!validation.valid) {
90
123
  results.push({
91
124
  id: "meta-description-presence",
@@ -139,6 +172,9 @@ function checkMetaDescription(input) {
139
172
  }
140
173
  return results;
141
174
  }
175
+
176
+ // src/checks/keyphrase-usage.ts
177
+ var import_core3 = require("@power-seo/core");
142
178
  function checkKeyphraseUsage(input) {
143
179
  const results = [];
144
180
  const { focusKeyphrase, title, metaDescription, content, slug, images } = input;
@@ -153,7 +189,7 @@ function checkKeyphraseUsage(input) {
153
189
  });
154
190
  return results;
155
191
  }
156
- const occurrences = core.analyzeKeyphraseOccurrences({
192
+ const occurrences = (0, import_core3.analyzeKeyphraseOccurrences)({
157
193
  keyphrase: focusKeyphrase,
158
194
  title,
159
195
  metaDescription,
@@ -161,27 +197,27 @@ function checkKeyphraseUsage(input) {
161
197
  slug,
162
198
  images
163
199
  });
164
- const densityResult = core.calculateKeywordDensity(focusKeyphrase, content);
165
- if (densityResult.density < core.KEYWORD_DENSITY.MIN) {
200
+ const densityResult = (0, import_core3.calculateKeywordDensity)(focusKeyphrase, content);
201
+ if (densityResult.density < import_core3.KEYWORD_DENSITY.MIN) {
166
202
  results.push({
167
203
  id: "keyphrase-density",
168
204
  title: "Keyphrase density",
169
- description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${core.KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,
205
+ description: `Keyphrase density is ${densityResult.density}%, which is below the recommended minimum of ${import_core3.KEYWORD_DENSITY.MIN}%. Use the keyphrase more often.`,
170
206
  status: "poor",
171
207
  score: 1,
172
208
  maxScore: 5
173
209
  });
174
- } else if (densityResult.density > core.KEYWORD_DENSITY.MAX) {
210
+ } else if (densityResult.density > import_core3.KEYWORD_DENSITY.MAX) {
175
211
  results.push({
176
212
  id: "keyphrase-density",
177
213
  title: "Keyphrase density",
178
- description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${core.KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,
214
+ description: `Keyphrase density is ${densityResult.density}%, which exceeds the recommended maximum of ${import_core3.KEYWORD_DENSITY.MAX}%. Reduce usage to avoid keyword stuffing.`,
179
215
  status: "poor",
180
216
  score: 1,
181
217
  maxScore: 5
182
218
  });
183
- } else if (densityResult.density >= core.KEYWORD_DENSITY.MIN && densityResult.density <= core.KEYWORD_DENSITY.MAX) {
184
- const isOptimal = Math.abs(densityResult.density - core.KEYWORD_DENSITY.OPTIMAL) < 0.5;
219
+ } else if (densityResult.density >= import_core3.KEYWORD_DENSITY.MIN && densityResult.density <= import_core3.KEYWORD_DENSITY.MAX) {
220
+ const isOptimal = Math.abs(densityResult.density - import_core3.KEYWORD_DENSITY.OPTIMAL) < 0.5;
185
221
  results.push({
186
222
  id: "keyphrase-density",
187
223
  title: "Keyphrase density",
@@ -227,15 +263,37 @@ function checkKeyphraseUsage(input) {
227
263
  }
228
264
  return results;
229
265
  }
266
+
267
+ // src/checks/headings.ts
268
+ var import_core4 = require("@power-seo/core");
230
269
  function parseHeadings(html) {
231
270
  const headings = [];
232
- const regex = /<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi;
233
- let match;
234
- while ((match = regex.exec(html)) !== null) {
271
+ const lc = html.toLowerCase();
272
+ let pos = 0;
273
+ while (pos < lc.length) {
274
+ let earliest = -1;
275
+ let earliestLevel = 0;
276
+ for (let level = 1; level <= 6; level++) {
277
+ const idx = lc.indexOf(`<h${level}`, pos);
278
+ if (idx !== -1 && (earliest === -1 || idx < earliest)) {
279
+ earliest = idx;
280
+ earliestLevel = level;
281
+ }
282
+ }
283
+ if (earliest === -1) break;
284
+ const contentStart = lc.indexOf(">", earliest);
285
+ if (contentStart === -1) break;
286
+ const closeTag = `</h${earliestLevel}>`;
287
+ const closeIdx = lc.indexOf(closeTag, contentStart + 1);
288
+ if (closeIdx === -1) {
289
+ pos = contentStart + 1;
290
+ continue;
291
+ }
235
292
  headings.push({
236
- level: parseInt(match[1], 10),
237
- text: core.stripHtml(match[2])
293
+ level: earliestLevel,
294
+ text: (0, import_core4.stripHtml)(html.slice(contentStart + 1, closeIdx))
238
295
  });
296
+ pos = closeIdx + closeTag.length;
239
297
  }
240
298
  return headings;
241
299
  }
@@ -327,24 +385,27 @@ function checkHeadings(input) {
327
385
  }
328
386
  return results;
329
387
  }
388
+
389
+ // src/checks/word-count.ts
390
+ var import_core5 = require("@power-seo/core");
330
391
  function checkWordCount(input) {
331
- const words = core.getWords(input.content);
392
+ const words = (0, import_core5.getWords)(input.content);
332
393
  const count = words.length;
333
- if (count < core.MIN_WORD_COUNT) {
394
+ if (count < import_core5.MIN_WORD_COUNT) {
334
395
  return {
335
396
  id: "word-count",
336
397
  title: "Word count",
337
- description: `The content is ${count} words, which is below the recommended minimum of ${core.MIN_WORD_COUNT}. Add more content to improve SEO.`,
398
+ description: `The content is ${count} words, which is below the recommended minimum of ${import_core5.MIN_WORD_COUNT}. Add more content to improve SEO.`,
338
399
  status: "poor",
339
400
  score: 1,
340
401
  maxScore: 5
341
402
  };
342
403
  }
343
- if (count < core.RECOMMENDED_WORD_COUNT) {
404
+ if (count < import_core5.RECOMMENDED_WORD_COUNT) {
344
405
  return {
345
406
  id: "word-count",
346
407
  title: "Word count",
347
- description: `The content is ${count} words. Consider expanding to at least ${core.RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,
408
+ description: `The content is ${count} words. Consider expanding to at least ${import_core5.RECOMMENDED_WORD_COUNT} words for more comprehensive coverage.`,
348
409
  status: "ok",
349
410
  score: 3,
350
411
  maxScore: 5
@@ -512,14 +573,15 @@ function analyzeContent(input, config) {
512
573
  recommendations
513
574
  };
514
575
  }
515
-
516
- exports.analyzeContent = analyzeContent;
517
- exports.checkHeadings = checkHeadings;
518
- exports.checkImages = checkImages;
519
- exports.checkKeyphraseUsage = checkKeyphraseUsage;
520
- exports.checkLinks = checkLinks;
521
- exports.checkMetaDescription = checkMetaDescription;
522
- exports.checkTitle = checkTitle;
523
- exports.checkWordCount = checkWordCount;
524
- //# sourceMappingURL=index.cjs.map
576
+ // Annotate the CommonJS export names for ESM import in node:
577
+ 0 && (module.exports = {
578
+ analyzeContent,
579
+ checkHeadings,
580
+ checkImages,
581
+ checkKeyphraseUsage,
582
+ checkLinks,
583
+ checkMetaDescription,
584
+ checkTitle,
585
+ checkWordCount
586
+ });
525
587
  //# sourceMappingURL=index.cjs.map