@actuate-media/plugin-seo 0.0.1

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.
Files changed (65) hide show
  1. package/dist/admin/GooglePreview.d.ts +9 -0
  2. package/dist/admin/GooglePreview.d.ts.map +1 -0
  3. package/dist/admin/GooglePreview.js +67 -0
  4. package/dist/admin/GooglePreview.js.map +1 -0
  5. package/dist/admin/SEOPanel.d.ts +11 -0
  6. package/dist/admin/SEOPanel.d.ts.map +1 -0
  7. package/dist/admin/SEOPanel.js +65 -0
  8. package/dist/admin/SEOPanel.js.map +1 -0
  9. package/dist/admin/SchemaPreview.d.ts +7 -0
  10. package/dist/admin/SchemaPreview.d.ts.map +1 -0
  11. package/dist/admin/SchemaPreview.js +83 -0
  12. package/dist/admin/SchemaPreview.js.map +1 -0
  13. package/dist/canonical/index.d.ts +16 -0
  14. package/dist/canonical/index.d.ts.map +1 -0
  15. package/dist/canonical/index.js +77 -0
  16. package/dist/canonical/index.js.map +1 -0
  17. package/dist/collections.d.ts +3 -0
  18. package/dist/collections.d.ts.map +1 -0
  19. package/dist/collections.js +2 -0
  20. package/dist/collections.js.map +1 -0
  21. package/dist/fields.d.ts +3 -0
  22. package/dist/fields.d.ts.map +1 -0
  23. package/dist/fields.js +115 -0
  24. package/dist/fields.js.map +1 -0
  25. package/dist/hooks.d.ts +5 -0
  26. package/dist/hooks.d.ts.map +1 -0
  27. package/dist/hooks.js +29 -0
  28. package/dist/hooks.js.map +1 -0
  29. package/dist/index.d.ts +72 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +35 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/link-health/index.d.ts +24 -0
  34. package/dist/link-health/index.d.ts.map +1 -0
  35. package/dist/link-health/index.js +82 -0
  36. package/dist/link-health/index.js.map +1 -0
  37. package/dist/og-image/index.d.ts +11 -0
  38. package/dist/og-image/index.d.ts.map +1 -0
  39. package/dist/og-image/index.js +28 -0
  40. package/dist/og-image/index.js.map +1 -0
  41. package/dist/robots/index.d.ts +14 -0
  42. package/dist/robots/index.d.ts.map +1 -0
  43. package/dist/robots/index.js +48 -0
  44. package/dist/robots/index.js.map +1 -0
  45. package/dist/schema/auto-detect.d.ts +3 -0
  46. package/dist/schema/auto-detect.d.ts.map +1 -0
  47. package/dist/schema/auto-detect.js +28 -0
  48. package/dist/schema/auto-detect.js.map +1 -0
  49. package/dist/schema/composer.d.ts +3 -0
  50. package/dist/schema/composer.d.ts.map +1 -0
  51. package/dist/schema/composer.js +72 -0
  52. package/dist/schema/composer.js.map +1 -0
  53. package/dist/schema/registry.d.ts +170 -0
  54. package/dist/schema/registry.d.ts.map +1 -0
  55. package/dist/schema/registry.js +2 -0
  56. package/dist/schema/registry.js.map +1 -0
  57. package/dist/schema/validator.d.ts +11 -0
  58. package/dist/schema/validator.d.ts.map +1 -0
  59. package/dist/schema/validator.js +107 -0
  60. package/dist/schema/validator.js.map +1 -0
  61. package/dist/sitemap/index.d.ts +78 -0
  62. package/dist/sitemap/index.d.ts.map +1 -0
  63. package/dist/sitemap/index.js +186 -0
  64. package/dist/sitemap/index.js.map +1 -0
  65. package/package.json +37 -0
@@ -0,0 +1,72 @@
1
+ import type { FieldDefinition, CollectionDefinition } from "@actuate/cms-core";
2
+ export interface ActuatePlugin {
3
+ name: string;
4
+ fields?: Record<string, FieldDefinition>;
5
+ collections?: Record<string, CollectionDefinition>;
6
+ adminNavItems?: Array<{
7
+ label: string;
8
+ icon: string;
9
+ path: string;
10
+ }>;
11
+ hooks?: Array<{
12
+ event: string;
13
+ handler: (...args: unknown[]) => Promise<void> | void;
14
+ }>;
15
+ }
16
+ export interface SEOPluginConfig {
17
+ sitemap?: {
18
+ enabled?: boolean;
19
+ excludeCollections?: string[];
20
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
21
+ defaultPriority?: number;
22
+ };
23
+ robots?: {
24
+ enabled?: boolean;
25
+ blockAIBots?: boolean;
26
+ additionalRules?: Array<{
27
+ userAgent: string;
28
+ allow?: string[];
29
+ disallow?: string[];
30
+ }>;
31
+ };
32
+ ogImage?: {
33
+ enabled?: boolean;
34
+ defaultTheme?: "light" | "dark";
35
+ siteName?: string;
36
+ };
37
+ linkHealth?: {
38
+ enabled?: boolean;
39
+ checkInterval?: number;
40
+ };
41
+ structuredData?: {
42
+ enabled?: boolean;
43
+ defaultOrganization?: {
44
+ name: string;
45
+ url: string;
46
+ logo?: string;
47
+ };
48
+ };
49
+ }
50
+ /** Creates an SEO plugin instance for Actuate CMS. */
51
+ export declare function seoPlugin(config?: SEOPluginConfig): ActuatePlugin;
52
+ export { seoFields } from "./fields.js";
53
+ export { seoCollections } from "./collections.js";
54
+ export { seoHooks } from "./hooks.js";
55
+ export { detectSchemaType } from "./schema/auto-detect.js";
56
+ export { composeSchema } from "./schema/composer.js";
57
+ export { validateSchema } from "./schema/validator.js";
58
+ export type { ValidationResult } from "./schema/validator.js";
59
+ export { generateSitemapIndex, generateCollectionSitemap, generateImageSitemap, generateVideoSitemap, generateNewsSitemap, generateHreflangSitemap, buildDocumentUrl, } from "./sitemap/index.js";
60
+ export type { SitemapEntry, ImageSitemapEntry, VideoSitemapEntry, NewsSitemapEntry, HreflangEntry, CollectionSitemapConfig, } from "./sitemap/index.js";
61
+ export { generateRobotsTxt } from "./robots/index.js";
62
+ export type { RobotsConfig } from "./robots/index.js";
63
+ export { generateOGImage } from "./og-image/index.js";
64
+ export type { OGImageOptions } from "./og-image/index.js";
65
+ export { detectBrokenLinks, detectOrphanPages, detectRedirectChains, } from "./link-health/index.js";
66
+ export { detectDuplicates, suggestCanonical } from "./canonical/index.js";
67
+ export type { DuplicateGroup } from "./canonical/index.js";
68
+ export { SEOPanel } from "./admin/SEOPanel.js";
69
+ export { GooglePreview } from "./admin/GooglePreview.js";
70
+ export { SchemaPreview } from "./admin/SchemaPreview.js";
71
+ export type { SchemaOrgThing, SchemaOrgArticle, SchemaOrgPerson, SchemaOrgOrganization, SchemaOrgWebPage, SchemaOrgEvent, SchemaOrgFAQPage, SchemaOrgService, SchemaOrgJobPosting, SchemaOrgLocalBusiness, SchemaOrgReview, SchemaOrgProduct, } from "./schema/registry.js";
72
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ import { seoFields } from "./fields.js";
2
+ import { seoCollections } from "./collections.js";
3
+ import { seoHooks } from "./hooks.js";
4
+ /** Creates an SEO plugin instance for Actuate CMS. */
5
+ export function seoPlugin(config = {}) {
6
+ return {
7
+ name: "plugin-seo",
8
+ fields: { ...seoFields },
9
+ collections: { ...seoCollections },
10
+ adminNavItems: [
11
+ { label: "SEO Overview", icon: "search", path: "/seo" },
12
+ { label: "Sitemap", icon: "map", path: "/seo/sitemap" },
13
+ { label: "Link Health", icon: "link", path: "/seo/link-health" },
14
+ ],
15
+ hooks: seoHooks.map((hook) => ({
16
+ ...hook,
17
+ _pluginConfig: config,
18
+ })),
19
+ };
20
+ }
21
+ export { seoFields } from "./fields.js";
22
+ export { seoCollections } from "./collections.js";
23
+ export { seoHooks } from "./hooks.js";
24
+ export { detectSchemaType } from "./schema/auto-detect.js";
25
+ export { composeSchema } from "./schema/composer.js";
26
+ export { validateSchema } from "./schema/validator.js";
27
+ export { generateSitemapIndex, generateCollectionSitemap, generateImageSitemap, generateVideoSitemap, generateNewsSitemap, generateHreflangSitemap, buildDocumentUrl, } from "./sitemap/index.js";
28
+ export { generateRobotsTxt } from "./robots/index.js";
29
+ export { generateOGImage } from "./og-image/index.js";
30
+ export { detectBrokenLinks, detectOrphanPages, detectRedirectChains, } from "./link-health/index.js";
31
+ export { detectDuplicates, suggestCanonical } from "./canonical/index.js";
32
+ export { SEOPanel } from "./admin/SEOPanel.js";
33
+ export { GooglePreview } from "./admin/GooglePreview.js";
34
+ export { SchemaPreview } from "./admin/SchemaPreview.js";
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA4CtC,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,SAA0B,EAAE;IACpD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,EAAE,GAAG,SAAS,EAAE;QACxB,WAAW,EAAE,EAAE,GAAG,cAAc,EAAE;QAClC,aAAa,EAAE;YACb,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;YACvD,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE;YACvD,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE;SACjE;QACD,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,IAAI;YACP,aAAa,EAAE,MAAM;SACtB,CAAC,CAA2B;KAC9B,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAS5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface LinkHealthResult {
2
+ url: string;
3
+ status: "ok" | "broken" | "redirect" | "timeout" | "error";
4
+ statusCode?: number;
5
+ redirectTarget?: string;
6
+ responseTime?: number;
7
+ source: string;
8
+ }
9
+ export interface RedirectChain {
10
+ initialUrl: string;
11
+ chain: string[];
12
+ finalUrl: string;
13
+ length: number;
14
+ }
15
+ /** Scans HTML content for links and checks their health. */
16
+ export declare function detectBrokenLinks(content: string, baseUrl: string): Promise<LinkHealthResult[]>;
17
+ /** Finds pages that are not linked from any other page. */
18
+ export declare function detectOrphanPages(allPages: string[], linkedPages: string[]): string[];
19
+ /** Detects redirect chains longer than a single hop. */
20
+ export declare function detectRedirectChains(redirects: Array<{
21
+ from: string;
22
+ to: string;
23
+ }>): RedirectChain[];
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/link-health/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,4DAA4D;AAC5D,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAmD7B;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGrF;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,aAAa,EAAE,CA0BjB"}
@@ -0,0 +1,82 @@
1
+ const LINK_REGEX = /href=["']([^"']+)["']/gi;
2
+ /** Scans HTML content for links and checks their health. */
3
+ export async function detectBrokenLinks(content, baseUrl) {
4
+ const base = baseUrl.replace(/\/$/, "");
5
+ const results = [];
6
+ const seen = new Set();
7
+ let match;
8
+ while ((match = LINK_REGEX.exec(content)) !== null) {
9
+ const href = match[1];
10
+ if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("tel:")) {
11
+ continue;
12
+ }
13
+ const url = href.startsWith("http") ? href : `${base}${href.startsWith("/") ? "" : "/"}${href}`;
14
+ if (seen.has(url))
15
+ continue;
16
+ seen.add(url);
17
+ try {
18
+ const controller = new AbortController();
19
+ const timeout = setTimeout(() => controller.abort(), 10_000);
20
+ const response = await fetch(url, {
21
+ method: "HEAD",
22
+ signal: controller.signal,
23
+ redirect: "manual",
24
+ });
25
+ clearTimeout(timeout);
26
+ if (response.status >= 300 && response.status < 400) {
27
+ results.push({
28
+ url,
29
+ status: "redirect",
30
+ statusCode: response.status,
31
+ redirectTarget: response.headers.get("location") ?? undefined,
32
+ source: href,
33
+ });
34
+ }
35
+ else if (response.ok) {
36
+ results.push({ url, status: "ok", statusCode: response.status, source: href });
37
+ }
38
+ else {
39
+ results.push({ url, status: "broken", statusCode: response.status, source: href });
40
+ }
41
+ }
42
+ catch (error) {
43
+ const isAbort = error instanceof DOMException && error.name === "AbortError";
44
+ results.push({
45
+ url,
46
+ status: isAbort ? "timeout" : "error",
47
+ source: href,
48
+ });
49
+ }
50
+ }
51
+ return results;
52
+ }
53
+ /** Finds pages that are not linked from any other page. */
54
+ export function detectOrphanPages(allPages, linkedPages) {
55
+ const linkedSet = new Set(linkedPages.map((p) => p.replace(/\/$/, "").toLowerCase()));
56
+ return allPages.filter((page) => !linkedSet.has(page.replace(/\/$/, "").toLowerCase()));
57
+ }
58
+ /** Detects redirect chains longer than a single hop. */
59
+ export function detectRedirectChains(redirects) {
60
+ const redirectMap = new Map(redirects.map((r) => [r.from, r.to]));
61
+ const chains = [];
62
+ for (const { from } of redirects) {
63
+ const chain = [from];
64
+ let current = from;
65
+ const visited = new Set();
66
+ while (redirectMap.has(current) && !visited.has(current)) {
67
+ visited.add(current);
68
+ current = redirectMap.get(current);
69
+ chain.push(current);
70
+ }
71
+ if (chain.length > 2) {
72
+ chains.push({
73
+ initialUrl: from,
74
+ chain,
75
+ finalUrl: chain[chain.length - 1] ?? "",
76
+ length: chain.length - 1,
77
+ });
78
+ }
79
+ }
80
+ return chains;
81
+ }
82
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/link-health/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface OGImageOptions {
2
+ title: string;
3
+ description?: string;
4
+ siteName?: string;
5
+ theme?: "light" | "dark";
6
+ logo?: string;
7
+ fontSize?: number;
8
+ }
9
+ /** Generates an Open Graph image response compatible with @vercel/og. */
10
+ export declare function generateOGImage(options: OGImageOptions): Promise<Response>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/og-image/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBhF"}
@@ -0,0 +1,28 @@
1
+ /** Generates an Open Graph image response compatible with @vercel/og. */
2
+ export async function generateOGImage(options) {
3
+ const { title, description, siteName, theme = "light" } = options;
4
+ const bg = theme === "dark" ? "#0a0a0a" : "#ffffff";
5
+ const fg = theme === "dark" ? "#fafafa" : "#0a0a0a";
6
+ const muted = theme === "dark" ? "#a1a1aa" : "#71717a";
7
+ const html = `
8
+ <div style="display:flex;flex-direction:column;justify-content:flex-end;width:1200px;height:630px;padding:60px;background:${bg};font-family:system-ui,sans-serif;">
9
+ ${siteName ? `<div style="color:${muted};font-size:24px;margin-bottom:16px;">${escapeHtml(siteName)}</div>` : ""}
10
+ <div style="color:${fg};font-size:${options.fontSize ?? 56}px;font-weight:700;line-height:1.2;max-width:900px;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(title)}</div>
11
+ ${description ? `<div style="color:${muted};font-size:28px;margin-top:20px;max-width:800px;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(description)}</div>` : ""}
12
+ </div>
13
+ `;
14
+ return new Response(html, {
15
+ headers: {
16
+ "Content-Type": "text/html",
17
+ "Cache-Control": "public, max-age=86400, s-maxage=86400",
18
+ },
19
+ });
20
+ }
21
+ function escapeHtml(str) {
22
+ return str
23
+ .replace(/&/g, "&amp;")
24
+ .replace(/</g, "&lt;")
25
+ .replace(/>/g, "&gt;")
26
+ .replace(/"/g, "&quot;");
27
+ }
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/og-image/index.ts"],"names":[],"mappings":"AASA,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB;IAC3D,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC;IAElE,MAAM,EAAE,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,MAAM,EAAE,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,MAAM,KAAK,GAAG,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvD,MAAM,IAAI,GAAG;gIACiH,EAAE;QAC1H,QAAQ,CAAC,CAAC,CAAC,qBAAqB,KAAK,wCAAwC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;0BAC5F,EAAE,cAAc,OAAO,CAAC,QAAQ,IAAI,EAAE,+FAA+F,UAAU,CAAC,KAAK,CAAC;QACxK,WAAW,CAAC,CAAC,CAAC,qBAAqB,KAAK,4FAA4F,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;GAE7K,CAAC;IAEF,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,OAAO,EAAE;YACP,cAAc,EAAE,WAAW;YAC3B,eAAe,EAAE,uCAAuC;SACzD;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface RobotsConfig {
2
+ sitemapUrl?: string;
3
+ rules?: Array<{
4
+ userAgent: string;
5
+ allow?: string[];
6
+ disallow?: string[];
7
+ crawlDelay?: number;
8
+ }>;
9
+ blockAIBots?: boolean;
10
+ additionalAIBots?: string[];
11
+ }
12
+ /** Generates a robots.txt string from the given configuration. */
13
+ export declare function generateRobotsTxt(config: RobotsConfig): string;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/robots/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAaD,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAwC9D"}
@@ -0,0 +1,48 @@
1
+ const DEFAULT_AI_BOTS = [
2
+ "GPTBot",
3
+ "ChatGPT-User",
4
+ "ClaudeBot",
5
+ "Claude-Web",
6
+ "anthropic-ai",
7
+ "Bytespider",
8
+ "CCBot",
9
+ "Google-Extended",
10
+ ];
11
+ /** Generates a robots.txt string from the given configuration. */
12
+ export function generateRobotsTxt(config) {
13
+ const lines = [];
14
+ const rules = config.rules ?? [
15
+ { userAgent: "*", allow: ["/"], disallow: ["/admin", "/api/"] },
16
+ ];
17
+ for (const rule of rules) {
18
+ lines.push(`User-agent: ${rule.userAgent}`);
19
+ if (rule.allow) {
20
+ for (const path of rule.allow) {
21
+ lines.push(`Allow: ${path}`);
22
+ }
23
+ }
24
+ if (rule.disallow) {
25
+ for (const path of rule.disallow) {
26
+ lines.push(`Disallow: ${path}`);
27
+ }
28
+ }
29
+ if (rule.crawlDelay !== undefined) {
30
+ lines.push(`Crawl-delay: ${rule.crawlDelay}`);
31
+ }
32
+ lines.push("");
33
+ }
34
+ if (config.blockAIBots) {
35
+ const bots = [...DEFAULT_AI_BOTS, ...(config.additionalAIBots ?? [])];
36
+ for (const bot of bots) {
37
+ lines.push(`User-agent: ${bot}`);
38
+ lines.push("Disallow: /");
39
+ lines.push("");
40
+ }
41
+ }
42
+ if (config.sitemapUrl) {
43
+ lines.push(`Sitemap: ${config.sitemapUrl}`);
44
+ lines.push("");
45
+ }
46
+ return lines.join("\n");
47
+ }
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/robots/index.ts"],"names":[],"mappings":"AAYA,MAAM,eAAe,GAAG;IACtB,QAAQ;IACR,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,OAAO;IACP,iBAAiB;CAClB,CAAC;AAEF,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI;QAC5B,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE;KAChE,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC5C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,CAAC,GAAG,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,CAAC;QACtE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Maps a collection slug to the most appropriate Schema.org type. */
2
+ export declare function detectSchemaType(collectionSlug: string): string;
3
+ //# sourceMappingURL=auto-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-detect.d.ts","sourceRoot":"","sources":["../../src/schema/auto-detect.ts"],"names":[],"mappings":"AAeA,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAc/D"}
@@ -0,0 +1,28 @@
1
+ const SLUG_TO_SCHEMA = {
2
+ posts: "Article",
3
+ blog: "Article",
4
+ articles: "Article",
5
+ pages: "WebPage",
6
+ team: "Person",
7
+ staff: "Person",
8
+ events: "Event",
9
+ faq: "FAQPage",
10
+ services: "Service",
11
+ jobs: "JobPosting",
12
+ locations: "LocalBusiness",
13
+ testimonials: "Review",
14
+ };
15
+ /** Maps a collection slug to the most appropriate Schema.org type. */
16
+ export function detectSchemaType(collectionSlug) {
17
+ const normalized = collectionSlug.toLowerCase().replace(/[^a-z]/g, "");
18
+ if (SLUG_TO_SCHEMA[normalized]) {
19
+ return SLUG_TO_SCHEMA[normalized];
20
+ }
21
+ for (const [key, schemaType] of Object.entries(SLUG_TO_SCHEMA)) {
22
+ if (normalized.includes(key) || key.includes(normalized)) {
23
+ return schemaType;
24
+ }
25
+ }
26
+ return "WebPage";
27
+ }
28
+ //# sourceMappingURL=auto-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-detect.js","sourceRoot":"","sources":["../../src/schema/auto-detect.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAA2B;IAC7C,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,OAAO;IACf,GAAG,EAAE,SAAS;IACd,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,YAAY;IAClB,SAAS,EAAE,eAAe;IAC1B,YAAY,EAAE,QAAQ;CACvB,CAAC;AAEF,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAAC,cAAsB;IACrD,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAEvE,IAAI,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QAC/D,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Builds a nested JSON-LD structure with @context, @type, and related entities. */
2
+ export declare function composeSchema(type: string, data: Record<string, unknown>, nested?: Record<string, unknown>): Record<string, unknown>;
3
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/schema/composer.ts"],"names":[],"mappings":"AA4BA,oFAAoF;AACpF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2CzB"}
@@ -0,0 +1,72 @@
1
+ const NESTED_ENTITY_MAP = {
2
+ Article: [
3
+ { field: "author", type: "Person" },
4
+ { field: "publisher", type: "Organization" },
5
+ ],
6
+ Event: [
7
+ { field: "organizer", type: "Organization" },
8
+ { field: "location", type: "Place" },
9
+ ],
10
+ JobPosting: [
11
+ { field: "hiringOrganization", type: "Organization" },
12
+ { field: "jobLocation", type: "Place" },
13
+ ],
14
+ Review: [
15
+ { field: "author", type: "Person" },
16
+ { field: "itemReviewed", type: "Thing" },
17
+ ],
18
+ Product: [
19
+ { field: "brand", type: "Organization" },
20
+ ],
21
+ Service: [
22
+ { field: "provider", type: "Organization" },
23
+ ],
24
+ LocalBusiness: [
25
+ { field: "address", type: "PostalAddress" },
26
+ ],
27
+ };
28
+ /** Builds a nested JSON-LD structure with @context, @type, and related entities. */
29
+ export function composeSchema(type, data, nested) {
30
+ const schema = {
31
+ "@context": "https://schema.org",
32
+ "@type": type,
33
+ };
34
+ for (const [key, value] of Object.entries(data)) {
35
+ if (key.startsWith("@"))
36
+ continue;
37
+ if (value !== undefined && value !== null && value !== "") {
38
+ schema[key] = value;
39
+ }
40
+ }
41
+ const nestedDefs = NESTED_ENTITY_MAP[type] ?? [];
42
+ for (const def of nestedDefs) {
43
+ const nestedData = nested?.[def.field];
44
+ if (nestedData) {
45
+ schema[def.field] = {
46
+ "@type": def.type,
47
+ ...nestedData,
48
+ };
49
+ }
50
+ else if (!schema[def.field] && data[def.field] && typeof data[def.field] === "string") {
51
+ schema[def.field] = {
52
+ "@type": def.type,
53
+ name: data[def.field],
54
+ };
55
+ }
56
+ }
57
+ if (nested) {
58
+ for (const [key, value] of Object.entries(nested)) {
59
+ if (nestedDefs.some((d) => d.field === key))
60
+ continue;
61
+ if (value && typeof value === "object" && !Array.isArray(value)) {
62
+ const nestedObj = value;
63
+ schema[key] = {
64
+ "@type": nestedObj["@type"] ?? "Thing",
65
+ ...nestedObj,
66
+ };
67
+ }
68
+ }
69
+ }
70
+ return schema;
71
+ }
72
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.js","sourceRoot":"","sources":["../../src/schema/composer.ts"],"names":[],"mappings":"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"}