@lynxflow/seo-engine 1.8.27 → 1.8.29
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 +875 -31
- package/connectors/wordpress/lynxseo-connector.php +404 -10
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/index.js +132 -0
- package/package.json +1 -1
- package/src/engine.test.ts +46 -0
- package/src/extended-schemas.ts +208 -0
package/src/engine.test.ts
CHANGED
|
@@ -91,6 +91,52 @@ describe("Extended Schema.org Builder", () => {
|
|
|
91
91
|
expect(schema["@type"]).toBe("Organization");
|
|
92
92
|
expect(schema.sameAs).toContain("https://wikidata.org/wiki/Q123");
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
it("Builds complete Speakable schema for voice search & assistant", () => {
|
|
96
|
+
const speakable = ExtendedSchemaGraphBuilder.buildSpeakable({
|
|
97
|
+
cssSelectors: [".headline", ".direct-answer"],
|
|
98
|
+
});
|
|
99
|
+
expect(speakable["@type"]).toBe("SpeakableSpecification");
|
|
100
|
+
expect(speakable.cssSelector).toEqual([".headline", ".direct-answer"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("Builds complete PodcastEpisode and Restaurant schemas", () => {
|
|
104
|
+
const podcast = ExtendedSchemaGraphBuilder.buildPodcastEpisode({
|
|
105
|
+
title: "SEO Trends 2026",
|
|
106
|
+
description: "Deep dive into GEO",
|
|
107
|
+
audioUrl: "https://example.com/ep1.mp3",
|
|
108
|
+
publishedDate: "2026-08-28",
|
|
109
|
+
seriesName: "LynxCast",
|
|
110
|
+
});
|
|
111
|
+
expect(podcast["@type"]).toBe("PodcastEpisode");
|
|
112
|
+
expect(podcast.name).toBe("SEO Trends 2026");
|
|
113
|
+
|
|
114
|
+
const restaurant = ExtendedSchemaGraphBuilder.buildRestaurant({
|
|
115
|
+
name: "Le Gourmet",
|
|
116
|
+
url: "https://legourmet.fr",
|
|
117
|
+
servesCuisine: ["French", "Mediterranean"],
|
|
118
|
+
address: { street: "12 Rue de Paris", city: "Paris", postalCode: "75001", country: "FR" },
|
|
119
|
+
});
|
|
120
|
+
expect(restaurant["@type"]).toBe("Restaurant");
|
|
121
|
+
expect(restaurant.servesCuisine).toContain("French");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("Builds Book, Dataset, FactCheck, Carousel, and QAPage schemas", () => {
|
|
125
|
+
const book = ExtendedSchemaGraphBuilder.buildBook({ name: "SEO in 2026", author: "LynxFlow" });
|
|
126
|
+
expect(book["@type"]).toBe("Book");
|
|
127
|
+
|
|
128
|
+
const dataset = ExtendedSchemaGraphBuilder.buildDataset({ name: "Search Data", description: "Keywords dataset", url: "https://data.com", keywords: ["seo", "ai"] });
|
|
129
|
+
expect(dataset["@type"]).toBe("Dataset");
|
|
130
|
+
|
|
131
|
+
const factCheck = ExtendedSchemaGraphBuilder.buildFactCheck({ claimReviewed: "AI replaces search", ratingValue: "4", authorName: "LynxFact", url: "https://fact.com" });
|
|
132
|
+
expect(factCheck["@type"]).toBe("ClaimReview");
|
|
133
|
+
|
|
134
|
+
const carousel = ExtendedSchemaGraphBuilder.buildCarousel([{ name: "Item 1", url: "https://item1.com", position: 1 }]);
|
|
135
|
+
expect(carousel["@type"]).toBe("ItemList");
|
|
136
|
+
|
|
137
|
+
const qa = ExtendedSchemaGraphBuilder.buildQAPage({ questionText: "How to rank #1?", authorName: "User1", answerText: "Use LynxSEO.", answerAuthor: "LynxBot" });
|
|
138
|
+
expect(qa["@type"]).toBe("QAPage");
|
|
139
|
+
});
|
|
94
140
|
});
|
|
95
141
|
|
|
96
142
|
describe("Master Programmatic Matrix Engine (English Default)", () => {
|
package/src/extended-schemas.ts
CHANGED
|
@@ -378,4 +378,212 @@ export class ExtendedSchemaGraphBuilder {
|
|
|
378
378
|
: undefined,
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 11. Speakable Schema (Google Assistant & Voice Search) - Rank Math Pro Parity
|
|
384
|
+
*/
|
|
385
|
+
static buildSpeakable(opts: {
|
|
386
|
+
cssSelectors?: string[];
|
|
387
|
+
xpaths?: string[];
|
|
388
|
+
}): Record<string, unknown> {
|
|
389
|
+
return {
|
|
390
|
+
"@type": "SpeakableSpecification",
|
|
391
|
+
cssSelector: opts.cssSelectors || [".headline", ".summary", ".direct-answer", "article h1", "article p:first-of-type"],
|
|
392
|
+
xpath: opts.xpaths,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 12. Podcast & PodcastEpisode Schema
|
|
398
|
+
*/
|
|
399
|
+
static buildPodcastEpisode(opts: {
|
|
400
|
+
title: string;
|
|
401
|
+
description: string;
|
|
402
|
+
audioUrl: string;
|
|
403
|
+
duration?: string;
|
|
404
|
+
publishedDate: string;
|
|
405
|
+
seriesName: string;
|
|
406
|
+
}): Record<string, unknown> {
|
|
407
|
+
return {
|
|
408
|
+
"@context": "https://schema.org",
|
|
409
|
+
"@type": "PodcastEpisode",
|
|
410
|
+
name: opts.title,
|
|
411
|
+
description: opts.description,
|
|
412
|
+
datePublished: opts.publishedDate,
|
|
413
|
+
timeRequired: opts.duration || "PT15M",
|
|
414
|
+
associatedMedia: {
|
|
415
|
+
"@type": "AudioObject",
|
|
416
|
+
contentUrl: opts.audioUrl,
|
|
417
|
+
},
|
|
418
|
+
partOfSeries: {
|
|
419
|
+
"@type": "PodcastSeries",
|
|
420
|
+
name: opts.seriesName,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 13. Restaurant Schema (with Menu & Cuisine)
|
|
427
|
+
*/
|
|
428
|
+
static buildRestaurant(opts: {
|
|
429
|
+
name: string;
|
|
430
|
+
url: string;
|
|
431
|
+
servesCuisine: string[];
|
|
432
|
+
menuUrl?: string;
|
|
433
|
+
priceRange?: string;
|
|
434
|
+
telephone?: string;
|
|
435
|
+
address: { street: string; city: string; postalCode: string; country: string };
|
|
436
|
+
}): Record<string, unknown> {
|
|
437
|
+
return {
|
|
438
|
+
"@context": "https://schema.org",
|
|
439
|
+
"@type": "Restaurant",
|
|
440
|
+
name: opts.name,
|
|
441
|
+
url: opts.url,
|
|
442
|
+
servesCuisine: opts.servesCuisine,
|
|
443
|
+
hasMenu: opts.menuUrl,
|
|
444
|
+
priceRange: opts.priceRange || "€€",
|
|
445
|
+
telephone: opts.telephone,
|
|
446
|
+
address: {
|
|
447
|
+
"@type": "PostalAddress",
|
|
448
|
+
streetAddress: opts.address.street,
|
|
449
|
+
addressLocality: opts.address.city,
|
|
450
|
+
postalCode: opts.address.postalCode,
|
|
451
|
+
addressCountry: opts.address.country,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 14. Book Schema
|
|
458
|
+
*/
|
|
459
|
+
static buildBook(opts: {
|
|
460
|
+
name: string;
|
|
461
|
+
author: string;
|
|
462
|
+
isbn?: string;
|
|
463
|
+
bookFormat?: string;
|
|
464
|
+
url?: string;
|
|
465
|
+
}): Record<string, unknown> {
|
|
466
|
+
return {
|
|
467
|
+
"@context": "https://schema.org",
|
|
468
|
+
"@type": "Book",
|
|
469
|
+
name: opts.name,
|
|
470
|
+
author: {
|
|
471
|
+
"@type": "Person",
|
|
472
|
+
name: opts.author,
|
|
473
|
+
},
|
|
474
|
+
isbn: opts.isbn,
|
|
475
|
+
bookFormat: opts.bookFormat || "https://schema.org/EBook",
|
|
476
|
+
url: opts.url,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 15. Dataset Schema (Data Search & Research)
|
|
482
|
+
*/
|
|
483
|
+
static buildDataset(opts: {
|
|
484
|
+
name: string;
|
|
485
|
+
description: string;
|
|
486
|
+
url: string;
|
|
487
|
+
keywords: string[];
|
|
488
|
+
temporalCoverage?: string;
|
|
489
|
+
distributionUrl?: string;
|
|
490
|
+
}): Record<string, unknown> {
|
|
491
|
+
return {
|
|
492
|
+
"@context": "https://schema.org",
|
|
493
|
+
"@type": "Dataset",
|
|
494
|
+
name: opts.name,
|
|
495
|
+
description: opts.description,
|
|
496
|
+
url: opts.url,
|
|
497
|
+
keywords: opts.keywords,
|
|
498
|
+
temporalCoverage: opts.temporalCoverage || "2024/2026",
|
|
499
|
+
distribution: opts.distributionUrl
|
|
500
|
+
? [
|
|
501
|
+
{
|
|
502
|
+
"@type": "DataDownload",
|
|
503
|
+
encodingFormat: "application/json",
|
|
504
|
+
contentUrl: opts.distributionUrl,
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
: undefined,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 16. FactCheck Schema (Google ClaimReview)
|
|
513
|
+
*/
|
|
514
|
+
static buildFactCheck(opts: {
|
|
515
|
+
claimReviewed: string;
|
|
516
|
+
ratingValue: string;
|
|
517
|
+
authorName: string;
|
|
518
|
+
url: string;
|
|
519
|
+
}): Record<string, unknown> {
|
|
520
|
+
return {
|
|
521
|
+
"@context": "https://schema.org",
|
|
522
|
+
"@type": "ClaimReview",
|
|
523
|
+
claimReviewed: opts.claimReviewed,
|
|
524
|
+
url: opts.url,
|
|
525
|
+
reviewRating: {
|
|
526
|
+
"@type": "Rating",
|
|
527
|
+
ratingValue: opts.ratingValue,
|
|
528
|
+
bestRating: "5",
|
|
529
|
+
worstRating: "1",
|
|
530
|
+
},
|
|
531
|
+
author: {
|
|
532
|
+
"@type": "Organization",
|
|
533
|
+
name: opts.authorName,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* 17. Carousel / ItemList Schema
|
|
540
|
+
*/
|
|
541
|
+
static buildCarousel(items: Array<{ name: string; url: string; position: number; image?: string }>): Record<string, unknown> {
|
|
542
|
+
return {
|
|
543
|
+
"@context": "https://schema.org",
|
|
544
|
+
"@type": "ItemList",
|
|
545
|
+
itemListElement: items.map((item) => ({
|
|
546
|
+
"@type": "ListItem",
|
|
547
|
+
position: item.position,
|
|
548
|
+
url: item.url,
|
|
549
|
+
name: item.name,
|
|
550
|
+
image: item.image,
|
|
551
|
+
})),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* 18. QAPage Schema (bbPress / Forums / Community QA)
|
|
557
|
+
*/
|
|
558
|
+
static buildQAPage(opts: {
|
|
559
|
+
questionText: string;
|
|
560
|
+
authorName: string;
|
|
561
|
+
answerText: string;
|
|
562
|
+
answerAuthor: string;
|
|
563
|
+
upvoteCount?: number;
|
|
564
|
+
}): Record<string, unknown> {
|
|
565
|
+
return {
|
|
566
|
+
"@context": "https://schema.org",
|
|
567
|
+
"@type": "QAPage",
|
|
568
|
+
mainEntity: {
|
|
569
|
+
"@type": "Question",
|
|
570
|
+
name: opts.questionText,
|
|
571
|
+
text: opts.questionText,
|
|
572
|
+
author: {
|
|
573
|
+
"@type": "Person",
|
|
574
|
+
name: opts.authorName,
|
|
575
|
+
},
|
|
576
|
+
answerCount: 1,
|
|
577
|
+
acceptedAnswer: {
|
|
578
|
+
"@type": "Answer",
|
|
579
|
+
text: opts.answerText,
|
|
580
|
+
upvoteCount: opts.upvoteCount ?? 12,
|
|
581
|
+
author: {
|
|
582
|
+
"@type": "Person",
|
|
583
|
+
name: opts.answerAuthor,
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
381
589
|
}
|