@lynxflow/seo-engine 1.8.2 → 1.8.4
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/connectors/wordpress/lynxseo-admin-app.js +25 -0
- package/connectors/wordpress/lynxseo-connector.php +24 -0
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/extended-schemas.d.ts +2 -2
- package/dist/index.js +698 -338
- package/dist/index.mjs +10 -7
- package/package.json +1 -1
- package/src/extended-schemas.ts +8 -4
- package/src/features-knowledge-harvester.ts +190 -0
- package/src/index.ts +2 -1
- package/src/og-image-generator.ts +5 -5
- package/src/power-words-psychology.ts +269 -160
package/dist/index.mjs
CHANGED
|
@@ -3084,10 +3084,13 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3084
3084
|
};
|
|
3085
3085
|
}
|
|
3086
3086
|
static buildAggregateRating(opts) {
|
|
3087
|
+
if (!opts.ratingValue || !opts.reviewCount || opts.reviewCount <= 0) {
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3087
3090
|
return {
|
|
3088
3091
|
"@type": "AggregateRating",
|
|
3089
|
-
ratingValue: opts.ratingValue
|
|
3090
|
-
reviewCount: opts.reviewCount
|
|
3092
|
+
ratingValue: opts.ratingValue,
|
|
3093
|
+
reviewCount: opts.reviewCount,
|
|
3091
3094
|
bestRating: opts.bestRating ?? 5,
|
|
3092
3095
|
worstRating: opts.worstRating ?? 1,
|
|
3093
3096
|
itemReviewed: {
|
|
@@ -3305,8 +3308,8 @@ class OgImageGenerator {
|
|
|
3305
3308
|
const badge = opts.badgeText || "OFFICIAL";
|
|
3306
3309
|
const title = opts.title.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3307
3310
|
const loc = opts.locationName ? `\uD83D\uDCCD ${opts.locationName}` : "⚡ Instant Cloud Setup";
|
|
3308
|
-
const rating = opts.ratingValue
|
|
3309
|
-
const reviews = opts.reviewCount
|
|
3311
|
+
const rating = opts.ratingValue;
|
|
3312
|
+
const reviews = opts.reviewCount;
|
|
3310
3313
|
const accent = opts.brandColor || "#6366F1";
|
|
3311
3314
|
return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
|
|
3312
3315
|
<defs>
|
|
@@ -3349,12 +3352,12 @@ class OgImageGenerator {
|
|
|
3349
3352
|
${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
|
|
3350
3353
|
</text>
|
|
3351
3354
|
|
|
3352
|
-
<!-- Trust Stars & Social Proof -->
|
|
3353
|
-
|
|
3355
|
+
<!-- Trust Stars & Social Proof (Only if real verified reviews exist) -->
|
|
3356
|
+
${rating && reviews ? `<g transform="translate(80, 470)">
|
|
3354
3357
|
<text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
|
|
3355
3358
|
<text x="140" y="30" fill="#F8FAFC" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="20" font-weight="800">${rating} / 5</text>
|
|
3356
3359
|
<text x="210" y="30" fill="#64748B" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="16" font-weight="500">(${reviews.toLocaleString()} verified reviews)</text>
|
|
3357
|
-
</g
|
|
3360
|
+
</g>` : ""}
|
|
3358
3361
|
|
|
3359
3362
|
<!-- Footer Brand -->
|
|
3360
3363
|
<g transform="translate(80, 540)">
|
package/package.json
CHANGED
package/src/extended-schemas.ts
CHANGED
|
@@ -278,7 +278,7 @@ export class ExtendedSchemaGraphBuilder {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
|
-
* 7. AggregateRating (Google Gold Stars in SERPs) -
|
|
281
|
+
* 7. AggregateRating (Google Gold Stars in SERPs) - Strictly Dynamic (No Fake Defaults)
|
|
282
282
|
*/
|
|
283
283
|
static buildAggregateRating(opts: {
|
|
284
284
|
ratingValue?: number;
|
|
@@ -286,11 +286,15 @@ export class ExtendedSchemaGraphBuilder {
|
|
|
286
286
|
bestRating?: number;
|
|
287
287
|
worstRating?: number;
|
|
288
288
|
itemReviewedName: string;
|
|
289
|
-
}): Record<string, unknown> {
|
|
289
|
+
}): Record<string, unknown> | null {
|
|
290
|
+
// Google Strict Compliance: Never emit fake or default hardcoded reviews
|
|
291
|
+
if (!opts.ratingValue || !opts.reviewCount || opts.reviewCount <= 0) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
290
294
|
return {
|
|
291
295
|
"@type": "AggregateRating",
|
|
292
|
-
ratingValue: opts.ratingValue
|
|
293
|
-
reviewCount: opts.reviewCount
|
|
296
|
+
ratingValue: opts.ratingValue,
|
|
297
|
+
reviewCount: opts.reviewCount,
|
|
294
298
|
bestRating: opts.bestRating ?? 5,
|
|
295
299
|
worstRating: opts.worstRating ?? 1,
|
|
296
300
|
itemReviewed: {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📚 Features & Documentation Knowledge Harvester (Automated Content Extractor)
|
|
3
|
+
*
|
|
4
|
+
* Automatically crawls and ingests public documentation, feature pages, and WordPress sitemaps.
|
|
5
|
+
* Extracts structured knowledge chunks:
|
|
6
|
+
* - Module Name, Category & H1 Title
|
|
7
|
+
* - Executive Summaries & Key Paragraphs (up to 15 in-depth paragraphs)
|
|
8
|
+
* - Workflows & Step-by-Step Guides
|
|
9
|
+
* - Key Capabilities & Benefit Bullet Points
|
|
10
|
+
* - Structured FAQs & Objection Handling
|
|
11
|
+
*
|
|
12
|
+
* Provides instant in-memory retrieval for programmatic page generation with zero manual writing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ExtractedFeatureKnowledge {
|
|
16
|
+
slug: string;
|
|
17
|
+
url: string;
|
|
18
|
+
title: string;
|
|
19
|
+
headline?: string;
|
|
20
|
+
category: string;
|
|
21
|
+
executiveSummary: string;
|
|
22
|
+
paragraphs: string[]; // Up to 15 clean thematic paragraphs
|
|
23
|
+
keyBenefits: string[];
|
|
24
|
+
workflowSteps: { step: number; title: string; description: string }[];
|
|
25
|
+
faqs: { question: string; answer: string }[];
|
|
26
|
+
rawTextLength: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class FeaturesKnowledgeHarvester {
|
|
30
|
+
private static knowledgeStore: Map<string, ExtractedFeatureKnowledge> = new Map();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract knowledge from raw HTML or Markdown content of a public page
|
|
34
|
+
*/
|
|
35
|
+
static extractFromHtml(url: string, html: string, slug?: string): ExtractedFeatureKnowledge {
|
|
36
|
+
const pageSlug = slug || url.split("/").filter(Boolean).pop() || "feature";
|
|
37
|
+
|
|
38
|
+
// Extract Title (H1 or <title>)
|
|
39
|
+
const titleMatch = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i) || html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
40
|
+
const title = titleMatch ? titleMatch[1].replace(/<[^>]+>/g, "").trim() : "Module " + pageSlug;
|
|
41
|
+
|
|
42
|
+
// Extract Paragraphs (<p>)
|
|
43
|
+
const paragraphMatches = html.matchAll(/<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
44
|
+
const paragraphs: string[] = [];
|
|
45
|
+
for (const match of paragraphMatches) {
|
|
46
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
47
|
+
if (clean.length > 40 && !clean.includes("Cookie") && !clean.includes("Copyright")) {
|
|
48
|
+
paragraphs.push(clean);
|
|
49
|
+
if (paragraphs.length >= 15) break; // Keep the top 15 rich paragraphs
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Extract List Items (<li>) as Key Benefits
|
|
54
|
+
const liMatches = html.matchAll(/<li[^>]*>([\s\S]*?)<\/li>/gi);
|
|
55
|
+
const keyBenefits: string[] = [];
|
|
56
|
+
for (const match of liMatches) {
|
|
57
|
+
const clean = match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
58
|
+
if (clean.length > 20 && clean.length < 200) {
|
|
59
|
+
keyBenefits.push(clean);
|
|
60
|
+
if (keyBenefits.length >= 8) break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Extract FAQs (H3/H4 questions with following text or <details>/<summary>)
|
|
65
|
+
const faqs: { question: string; answer: string }[] = [];
|
|
66
|
+
const faqMatches = html.matchAll(/<summary[^>]*>([\s\S]*?)<\/summary>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/gi);
|
|
67
|
+
for (const match of faqMatches) {
|
|
68
|
+
const question = match[1].replace(/<[^>]+>/g, "").trim();
|
|
69
|
+
const answer = match[2].replace(/<[^>]+>/g, "").trim();
|
|
70
|
+
if (question && answer) {
|
|
71
|
+
faqs.push({ question, answer });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Default workflow steps if not parsed
|
|
76
|
+
const workflowSteps = [
|
|
77
|
+
{ step: 1, title: "Configuration Automatique", description: `Activation du module ${title} en 1 clic.` },
|
|
78
|
+
{ step: 2, title: "Résolution en Mémoire Vive", description: "Traitement ultra-rapide sans latence SQL." },
|
|
79
|
+
{ step: 3, title: "Déploiement Continu", description: "Mise à jour en temps réel sur l'ensemble de vos pages." },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const executiveSummary = paragraphs.slice(0, 2).join(" ") || `Module de haute performance ${title} optimisé pour Google et le Search IA.`;
|
|
83
|
+
|
|
84
|
+
const knowledge: ExtractedFeatureKnowledge = {
|
|
85
|
+
slug: pageSlug,
|
|
86
|
+
url,
|
|
87
|
+
title,
|
|
88
|
+
category: "Feature Suite",
|
|
89
|
+
executiveSummary,
|
|
90
|
+
paragraphs,
|
|
91
|
+
keyBenefits,
|
|
92
|
+
workflowSteps,
|
|
93
|
+
faqs,
|
|
94
|
+
rawTextLength: paragraphs.join(" ").length,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
this.knowledgeStore.set(pageSlug, knowledge);
|
|
98
|
+
return knowledge;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Batch ingest multiple pages from a sitemap or list of URLs
|
|
103
|
+
*/
|
|
104
|
+
static async ingestPagesFromSitemap(sitemapUrl: string): Promise<number> {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(sitemapUrl);
|
|
107
|
+
if (!res.ok) return 0;
|
|
108
|
+
const xml = await res.text();
|
|
109
|
+
const urls = [...xml.matchAll(/<loc>([\s\S]*?)<\/loc>/gi)].map((m) => m[1].trim());
|
|
110
|
+
|
|
111
|
+
let count = 0;
|
|
112
|
+
for (const u of urls.slice(0, 50)) {
|
|
113
|
+
try {
|
|
114
|
+
const pageRes = await fetch(u);
|
|
115
|
+
if (pageRes.ok) {
|
|
116
|
+
const html = await pageRes.text();
|
|
117
|
+
this.extractFromHtml(u, html);
|
|
118
|
+
count++;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// ignore network timeout
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return count;
|
|
125
|
+
} catch {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Retrieve extracted knowledge for any module by slug
|
|
132
|
+
*/
|
|
133
|
+
static getKnowledge(slug: string): ExtractedFeatureKnowledge | undefined {
|
|
134
|
+
return this.knowledgeStore.get(slug);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get all registered module knowledge chunks
|
|
139
|
+
*/
|
|
140
|
+
static getAllKnowledge(): ExtractedFeatureKnowledge[] {
|
|
141
|
+
return Array.from(this.knowledgeStore.values());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate an ultra-complete, 1000-word programmatic page using harvested knowledge
|
|
146
|
+
*/
|
|
147
|
+
static buildEnrichedPage(slug: string, locationOrSector: string): string {
|
|
148
|
+
const k = this.getKnowledge(slug);
|
|
149
|
+
if (!k) {
|
|
150
|
+
return `# Solution ${slug} à ${locationOrSector}\n\nExplications complètes et déploiement immédiat.`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `
|
|
154
|
+
# ${k.title} à ${locationOrSector} (Guide Complet & Déploiement 2026)
|
|
155
|
+
|
|
156
|
+
> **Résumé Exécutif :** ${k.executiveSummary}
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 📖 Analyse Approfondie & Fonctionnement
|
|
161
|
+
|
|
162
|
+
${k.paragraphs.slice(0, 6).join("\n\n")}
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 💎 Fonctionnalités Clés & Avantages Concrets :
|
|
167
|
+
|
|
168
|
+
${k.keyBenefits.map((b) => `* **${b}**`).join("\n")}
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🛠️ Guide de Mise en Œuvre en 3 Étapes :
|
|
173
|
+
|
|
174
|
+
${k.workflowSteps.map((s) => `${s.step}. **${s.title}** : ${s.description}`).join("\n")}
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
${k.paragraphs.slice(6, 12).join("\n\n")}
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## ❓ Questions Fréquentes & Réponses Officielles :
|
|
183
|
+
|
|
184
|
+
${k.faqs.length > 0
|
|
185
|
+
? k.faqs.map((f) => `### ${f.question}\n${f.answer}`).join("\n\n")
|
|
186
|
+
: `### Comment activer ${k.title} à ${locationOrSector} ?\nL'activation est instantanée en 1 clic sans compétences techniques.`
|
|
187
|
+
}
|
|
188
|
+
`.trim();
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -145,9 +145,10 @@ export const LynxSeo = {
|
|
|
145
145
|
adIntelligence: AdIntelligenceCroEngine,
|
|
146
146
|
rankMathScore: RankMathParityEngine,
|
|
147
147
|
yoastReadability: YoastParityEngine,
|
|
148
|
-
socialGrowth: SocialGrowthSuite,
|
|
149
148
|
rbac: TeamRbacEngine,
|
|
150
149
|
realReviews: RealReviewsSyncEngine,
|
|
150
|
+
knowledgeHarvester: FeaturesKnowledgeHarvester,
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
+
export { FeaturesKnowledgeHarvester, type ExtractedFeatureKnowledge } from "./features-knowledge-harvester";
|
|
153
154
|
export default LynxSeo;
|
|
@@ -46,8 +46,8 @@ export class OgImageGenerator {
|
|
|
46
46
|
const badge = opts.badgeText || "OFFICIAL";
|
|
47
47
|
const title = opts.title.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
48
|
const loc = opts.locationName ? `📍 ${opts.locationName}` : "⚡ Instant Cloud Setup";
|
|
49
|
-
const rating = opts.ratingValue
|
|
50
|
-
const reviews = opts.reviewCount
|
|
49
|
+
const rating = opts.ratingValue;
|
|
50
|
+
const reviews = opts.reviewCount;
|
|
51
51
|
const accent = opts.brandColor || "#6366F1";
|
|
52
52
|
|
|
53
53
|
return `<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -91,12 +91,12 @@ export class OgImageGenerator {
|
|
|
91
91
|
${title.length > 38 ? `<tspan x="80" dy="68">${title.slice(38, 80)}</tspan>` : ""}
|
|
92
92
|
</text>
|
|
93
93
|
|
|
94
|
-
<!-- Trust Stars & Social Proof -->
|
|
95
|
-
|
|
94
|
+
<!-- Trust Stars & Social Proof (Only if real verified reviews exist) -->
|
|
95
|
+
${rating && reviews ? `<g transform="translate(80, 470)">
|
|
96
96
|
<text x="0" y="32" fill="#FBBF24" font-size="24">★★★★★</text>
|
|
97
97
|
<text x="140" y="30" fill="#F8FAFC" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="20" font-weight="800">${rating} / 5</text>
|
|
98
98
|
<text x="210" y="30" fill="#64748B" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="16" font-weight="500">(${reviews.toLocaleString()} verified reviews)</text>
|
|
99
|
-
</g
|
|
99
|
+
</g>` : ""}
|
|
100
100
|
|
|
101
101
|
<!-- Footer Brand -->
|
|
102
102
|
<g transform="translate(80, 540)">
|