@rimelight/seo 0.0.1 → 0.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/seo",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's SEO Package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -16,26 +16,36 @@
16
16
  "url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
17
17
  },
18
18
  "files": [
19
- "src"
19
+ "dist",
20
+ "src/components"
20
21
  ],
21
22
  "type": "module",
22
23
  "exports": {
23
- ".": "./src/index.ts",
24
- "./types": "./src/types.ts",
25
- "./sitemap": "./src/sitemap.ts",
26
- "./robots": "./src/robots.ts",
24
+ ".": {
25
+ "types": "./dist/index.d.mts",
26
+ "import": "./dist/index.mjs"
27
+ },
28
+ "./types": {
29
+ "types": "./dist/types.d.mts",
30
+ "import": "./dist/types.mjs"
31
+ },
32
+ "./sitemap": {
33
+ "types": "./dist/sitemap.d.mts",
34
+ "import": "./dist/sitemap.mjs"
35
+ },
36
+ "./robots": {
37
+ "types": "./dist/robots.d.mts",
38
+ "import": "./dist/robots.mjs"
39
+ },
27
40
  "./components/*": "./src/components/*",
28
41
  "./*": "./src/*"
29
42
  },
30
43
  "publishConfig": {
31
44
  "access": "public"
32
45
  },
33
- "scripts": {
34
- "check": "pnpm audit --audit-level=moderate && vp check --fix && astro check"
35
- },
36
46
  "devDependencies": {
37
- "@rimelight/config": "workspace:*",
38
- "astro": "7.2.2",
47
+ "@rimelight/config": "0.0.4",
48
+ "astro": "7.3.1",
39
49
  "typescript": "6.0.3"
40
50
  },
41
51
  "peerDependencies": {
@@ -44,5 +54,8 @@
44
54
  "engines": {
45
55
  "node": ">=26.7.0"
46
56
  },
47
- "packageManager": "pnpm@11.22.0"
48
- }
57
+ "scripts": {
58
+ "build": "vp pack",
59
+ "check": "vp check --fix && astro check"
60
+ }
61
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ interface Props {
3
+ /** Absolute or site-relative URL for this page's markdown version. */
4
+ markdownUrl?: string | undefined
5
+ /** Absolute or site-relative URL for the top-level llms.txt index. */
6
+ llmsUrl?: string | undefined
7
+ class?: string | undefined
8
+ }
9
+
10
+ const {
11
+ markdownUrl,
12
+ llmsUrl = "/llms.txt",
13
+ class: className = "sr-only"
14
+ } = Astro.props
15
+ ---
16
+
17
+ <aside class={className} data-ai-agent-directive>
18
+ This page is available in Markdown format. Markdown is recommended for AI and agentic consumption.
19
+ {markdownUrl && (
20
+ <>
21
+ See <a href={markdownUrl}>{markdownUrl}</a> for this page, or{" "}
22
+ </>
23
+ )}
24
+ <a href={llmsUrl}>{llmsUrl}</a> for the full documentation and content index.
25
+ </aside>
@@ -7,6 +7,7 @@ import {
7
7
  buildRobotsMetaContent,
8
8
  buildWebSiteSchema
9
9
  } from "../meta.js"
10
+ import { buildArticleSchema, buildBreadcrumbSchema } from "../schema.js"
10
11
 
11
12
  const {
12
13
  title,
@@ -23,6 +24,10 @@ const {
23
24
  charset = 'utf-8',
24
25
  viewport = 'width=device-width, initial-scale=1',
25
26
  rssFeed,
27
+ articleSchema,
28
+ breadcrumbs,
29
+ markdownUrl,
30
+ versionAlternates = [],
26
31
  } = Astro.props as HeadProps
27
32
 
28
33
  // Resolve defaults from config if available
@@ -85,6 +90,12 @@ const robotsContent = buildRobotsMetaContent({
85
90
  <link rel="alternate" hreflang={alt.hreflang} href={alt.href} />
86
91
  ))}
87
92
 
93
+ <!-- Agent & Version Alternates -->
94
+ {markdownUrl && <link rel="alternate" type="text/markdown" href={markdownUrl} title="Markdown Format" />}
95
+ {versionAlternates.map((alt) => (
96
+ <link rel="alternate" data-version={alt.version} href={alt.href} />
97
+ ))}
98
+
88
99
  <!-- Open Graph / Facebook -->
89
100
  <meta property="og:type" content={ogType} />
90
101
  <meta property="og:locale" content={ogLocale} />
@@ -122,3 +133,16 @@ const robotsContent = buildRobotsMetaContent({
122
133
  buildWebSiteSchema(config, Astro.site?.toString())
123
134
  )} />
124
135
  )}
136
+
137
+ {articleSchema && (
138
+ <script type="application/ld+json" is:inline set:html={JSON.stringify(
139
+ buildArticleSchema(articleSchema)
140
+ )} />
141
+ )}
142
+
143
+ {breadcrumbs && breadcrumbs.length > 0 && (
144
+ <script type="application/ld+json" is:inline set:html={JSON.stringify(
145
+ buildBreadcrumbSchema(breadcrumbs)
146
+ )} />
147
+ )}
148
+
package/src/env.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare module "*.astro" {
2
- export default {} as import("astro/runtime/server").AstroComponentFactory
3
- }
package/src/index.ts DELETED
@@ -1,30 +0,0 @@
1
- export type {
2
- ChangeFreq,
3
- LocaleConfig,
4
- RobotsOptions,
5
- SeoEntry,
6
- SitemapAlternate,
7
- SitemapUrl,
8
- UserAgentRule,
9
- SiteConfig,
10
- OGImage,
11
- RSSFeed,
12
- HeadProps
13
- } from "./types.js"
14
-
15
- export {
16
- buildSitemapUrls,
17
- buildSitemapXml,
18
- buildSitemapIndexXml,
19
- chunkSitemapUrls
20
- } from "./sitemap.js"
21
-
22
- export { buildRobotsTxt } from "./robots.js"
23
-
24
- export {
25
- buildCanonicalUrl,
26
- buildPageTitle,
27
- buildSafeDescription,
28
- buildRobotsMetaContent,
29
- buildWebSiteSchema
30
- } from "./meta.js"
package/src/meta.test.ts DELETED
@@ -1,110 +0,0 @@
1
- import { describe, it, expect } from "vite-plus/test"
2
- import {
3
- buildCanonicalUrl,
4
- buildPageTitle,
5
- buildSafeDescription,
6
- buildRobotsMetaContent,
7
- buildWebSiteSchema
8
- } from "./meta.js"
9
-
10
- const BASE = "https://example.com"
11
-
12
- describe("buildCanonicalUrl", () => {
13
- it("adds trailing slash to clean paths", () => {
14
- expect(buildCanonicalUrl("/about", BASE)).toBe("https://example.com/about/")
15
- })
16
-
17
- it("strips trailing slash from query-param paths", () => {
18
- expect(buildCanonicalUrl("/search?q=hello", BASE)).toBe("https://example.com/search?q=hello")
19
- })
20
-
21
- it("handles root path", () => {
22
- expect(buildCanonicalUrl("/", BASE)).toBe("https://example.com/")
23
- })
24
-
25
- it("resolves relative paths against base", () => {
26
- expect(buildCanonicalUrl(new URL("https://example.com/docs"), BASE)).toBe(
27
- "https://example.com/docs/"
28
- )
29
- })
30
- })
31
-
32
- describe("buildPageTitle", () => {
33
- const opts = { siteName: "My Site", titleTemplate: "%s | My Site" }
34
-
35
- it("applies title template", () => {
36
- expect(buildPageTitle({ ...opts, title: "About" })).toBe("About | My Site")
37
- })
38
-
39
- it("returns siteName when no title", () => {
40
- expect(buildPageTitle(opts)).toBe("My Site")
41
- })
42
-
43
- it("returns 404 label for error pages", () => {
44
- expect(buildPageTitle({ ...opts, is404: true })).toBe("404 - Not Found | My Site")
45
- })
46
-
47
- it("404 overrides title if both provided", () => {
48
- expect(buildPageTitle({ ...opts, title: "Oops", is404: true })).toBe(
49
- "404 - Not Found | My Site"
50
- )
51
- })
52
- })
53
-
54
- describe("buildSafeDescription", () => {
55
- it("returns description unchanged when within limit", () => {
56
- expect(buildSafeDescription("Short desc", 160)).toBe("Short desc")
57
- })
58
-
59
- it("truncates and appends ellipsis when over limit", () => {
60
- const long = "a".repeat(200)
61
- const result = buildSafeDescription(long, 160)
62
- expect(result).toHaveLength(160)
63
- expect(result.endsWith("...")).toBe(true)
64
- })
65
-
66
- it("handles empty string", () => {
67
- expect(buildSafeDescription("", 160)).toBe("")
68
- })
69
-
70
- it("truncates at exact boundary", () => {
71
- const text = "a".repeat(160)
72
- expect(buildSafeDescription(text, 160)).toBe(text)
73
- expect(buildSafeDescription(text + "b", 160)).toHaveLength(160)
74
- })
75
- })
76
-
77
- describe("buildRobotsMetaContent", () => {
78
- it("returns indexable string by default", () => {
79
- expect(buildRobotsMetaContent({})).toBe("index, follow, max-image-preview:large")
80
- })
81
-
82
- it("returns noindex for noindex=true", () => {
83
- expect(buildRobotsMetaContent({ noindex: true })).toBe("noindex, nofollow")
84
- })
85
-
86
- it("returns noindex for is404=true", () => {
87
- expect(buildRobotsMetaContent({ is404: true })).toBe("noindex, nofollow")
88
- })
89
-
90
- it("respects override", () => {
91
- expect(buildRobotsMetaContent({ override: "noarchive" })).toBe("noarchive")
92
- })
93
- })
94
-
95
- describe("buildWebSiteSchema", () => {
96
- const config = { name: "My Site", url: "https://example.com", description: "A test site" }
97
-
98
- it("builds schema.org WebSite object", () => {
99
- const schema = buildWebSiteSchema(config)
100
- expect(schema["@context"]).toBe("https://schema.org")
101
- expect(schema["@type"]).toBe("WebSite")
102
- expect(schema.name).toBe("My Site")
103
- expect(schema.url).toBe("https://example.com")
104
- })
105
-
106
- it("prefers site param over config.url", () => {
107
- const schema = buildWebSiteSchema(config, "https://override.com")
108
- expect(schema.url).toBe("https://override.com")
109
- })
110
- })
package/src/meta.ts DELETED
@@ -1,70 +0,0 @@
1
- import type { SiteConfig } from "./types.js"
2
-
3
- /**
4
- * Build a canonical URL from a page URL and site base. - Strips trailing slash unless the path has
5
- * query params (where trailing slash is removed too). - Returns undefined-safe: pass Astro.url and
6
- * Astro.site directly.
7
- */
8
- export function buildCanonicalUrl(url: string | URL, base: string | URL): string {
9
- const resolved = new URL(url, base)
10
- const path = resolved.toString()
11
- // Keep query params as-is but strip trailing slash; for clean paths also ensure trailing slash
12
- return path.includes("?") ? path.replace(/\/?$/, "") : path.replace(/\/?$/, "/")
13
- }
14
-
15
- /**
16
- * Build a page title using a site's title template. Falls back to siteName for untitled pages; uses
17
- * a 404 label for error pages.
18
- */
19
- export function buildPageTitle(opts: {
20
- title?: string
21
- siteName: string
22
- titleTemplate: string
23
- is404?: boolean
24
- }): string {
25
- const { title, siteName, titleTemplate, is404 } = opts
26
- if (is404) return `404 - Not Found | ${siteName}`
27
- if (!title) return siteName
28
- return titleTemplate.replace("%s", title)
29
- }
30
-
31
- /**
32
- * Truncate a description to a maximum character length, appending "…" if trimmed.
33
- */
34
- export function buildSafeDescription(description: string, maxLength: number): string {
35
- if (!description) return description
36
- if (description.length <= maxLength) return description
37
- return description.slice(0, maxLength - 3) + "..."
38
- }
39
-
40
- /**
41
- * Build the value for the <meta name="robots"> tag. Returns the full directive string including
42
- * max-image-preview for indexable pages.
43
- */
44
- export function buildRobotsMetaContent(opts: {
45
- noindex?: boolean
46
- is404?: boolean
47
- override?: string
48
- }): string {
49
- if (opts.override) return opts.override
50
- return !opts.noindex && !opts.is404
51
- ? "index, follow, max-image-preview:large"
52
- : "noindex, nofollow"
53
- }
54
-
55
- /**
56
- * \n * Build a Schema.org WebSite JSON-LD object.\n * Pass the result to JSON.stringify and emit
57
- * via <script type="application/ld+json">.\n
58
- */
59
- export function buildWebSiteSchema(
60
- config: Pick<SiteConfig, "name" | "url" | "description">,
61
- site?: string
62
- ): Record<string, unknown> {
63
- return {
64
- "@context": "https://schema.org",
65
- "@type": "WebSite",
66
- "name": config.name,
67
- "url": site ?? config.url,
68
- "description": config.description
69
- }
70
- }
@@ -1,75 +0,0 @@
1
- import { describe, it, expect } from "vite-plus/test"
2
- import { buildRobotsTxt } from "./robots.js"
3
- import type { RobotsOptions } from "./types.js"
4
-
5
- describe("buildRobotsTxt", () => {
6
- it("generates basic robots.txt with sitemap", () => {
7
- const options: RobotsOptions = {
8
- sitemapUrl: "https://example.com/sitemap.xml"
9
- }
10
-
11
- const robots = buildRobotsTxt(options)
12
-
13
- expect(robots).toContain("User-agent: *")
14
- expect(robots).toContain("Sitemap: https://example.com/sitemap.xml")
15
- })
16
-
17
- it("includes disallow rules", () => {
18
- const options: RobotsOptions = {
19
- sitemapUrl: "https://example.com/sitemap.xml",
20
- disallow: ["/admin/", "/dashboard/"]
21
- }
22
-
23
- const robots = buildRobotsTxt(options)
24
-
25
- expect(robots).toContain("Disallow: /admin/")
26
- expect(robots).toContain("Disallow: /dashboard/")
27
- })
28
-
29
- it("includes allow rules", () => {
30
- const options: RobotsOptions = {
31
- sitemapUrl: "https://example.com/sitemap.xml",
32
- allow: ["/api/public/"]
33
- }
34
-
35
- const robots = buildRobotsTxt(options)
36
-
37
- expect(robots).toContain("Allow: /api/public/")
38
- })
39
-
40
- it("includes user-agent specific rules", () => {
41
- const options: RobotsOptions = {
42
- sitemapUrl: "https://example.com/sitemap.xml",
43
- userAgentRules: [
44
- {
45
- userAgent: "GPTBot",
46
- disallow: ["/admin/"]
47
- }
48
- ]
49
- }
50
-
51
- const robots = buildRobotsTxt(options)
52
-
53
- expect(robots).toContain("User-agent: GPTBot")
54
- expect(robots).toContain("Disallow: /admin/")
55
- })
56
-
57
- it("orders user-agent specific rules before wildcard", () => {
58
- const options: RobotsOptions = {
59
- sitemapUrl: "https://example.com/sitemap.xml",
60
- userAgentRules: [
61
- {
62
- userAgent: "GPTBot",
63
- disallow: ["/admin/"]
64
- }
65
- ]
66
- }
67
-
68
- const robots = buildRobotsTxt(options)
69
-
70
- const gptBotIndex = robots.indexOf("User-agent: GPTBot")
71
- const wildcardIndex = robots.indexOf("User-agent: *")
72
-
73
- expect(gptBotIndex).toBeLessThan(wildcardIndex)
74
- })
75
- })
package/src/robots.ts DELETED
@@ -1,59 +0,0 @@
1
- import type { RobotsOptions, UserAgentRule } from "./types.js"
2
-
3
- /**
4
- * Build robots.txt content from options
5
- */
6
- export function buildRobotsTxt(options: RobotsOptions): string {
7
- const lines: string[] = []
8
-
9
- // Add user-agent specific rules first
10
- if (options.userAgentRules && options.userAgentRules.length > 0) {
11
- for (const rule of options.userAgentRules) {
12
- lines.push(buildUserAgentSection(rule))
13
- lines.push("")
14
- }
15
- }
16
-
17
- // Add wildcard user-agent (all bots)
18
- lines.push("User-agent: *")
19
-
20
- if (options.disallow && options.disallow.length > 0) {
21
- for (const path of options.disallow) {
22
- lines.push(`Disallow: ${path}`)
23
- }
24
- }
25
-
26
- if (options.allow && options.allow.length > 0) {
27
- for (const path of options.allow) {
28
- lines.push(`Allow: ${path}`)
29
- }
30
- }
31
-
32
- lines.push("")
33
- lines.push(`Sitemap: ${options.sitemapUrl}`)
34
-
35
- return lines.join("\n")
36
- }
37
-
38
- /**
39
- * Build a user-agent specific section
40
- */
41
- function buildUserAgentSection(rule: UserAgentRule): string {
42
- const lines: string[] = []
43
-
44
- lines.push(`User-agent: ${rule.userAgent}`)
45
-
46
- if (rule.disallow && rule.disallow.length > 0) {
47
- for (const path of rule.disallow) {
48
- lines.push(`Disallow: ${path}`)
49
- }
50
- }
51
-
52
- if (rule.allow && rule.allow.length > 0) {
53
- for (const path of rule.allow) {
54
- lines.push(`Allow: ${path}`)
55
- }
56
- }
57
-
58
- return lines.join("\n")
59
- }
@@ -1,191 +0,0 @@
1
- import { describe, it, expect } from "vite-plus/test"
2
- import {
3
- buildSitemapUrls,
4
- buildSitemapXml,
5
- buildSitemapIndexXml,
6
- chunkSitemapUrls
7
- } from "./sitemap.js"
8
- import type { LocaleConfig, SeoEntry } from "./types.js"
9
-
10
- describe("buildSitemapUrls", () => {
11
- const config: LocaleConfig = {
12
- site: "https://example.com",
13
- locales: { en: "en-US", pt: "pt-BR" },
14
- defaultLocale: "en"
15
- }
16
-
17
- it("builds URLs for a single entry in multiple locales", () => {
18
- const entries: SeoEntry[] = [{ path: "/about" }]
19
- const urls = buildSitemapUrls(entries, config)
20
-
21
- expect(urls).toHaveLength(2)
22
- expect(urls[0]!.loc).toBe("https://example.com/about")
23
- expect(urls[1]!.loc).toBe("https://example.com/pt/about")
24
- })
25
-
26
- it("includes alternates for multi-locale entries", () => {
27
- const entries: SeoEntry[] = [{ path: "/about" }]
28
- const urls = buildSitemapUrls(entries, config)
29
-
30
- // Default locale URL should have x-default alternate
31
- const defaultUrl = urls.find((u) => u.loc === "https://example.com/about")
32
- expect(defaultUrl?.alternates).toHaveLength(2)
33
- expect(defaultUrl?.alternates?.[0]!.hreflang).toBe("pt-BR")
34
- expect(defaultUrl?.alternates?.[0]!.href).toBe("https://example.com/pt/about")
35
- expect(defaultUrl?.alternates?.[1]!.hreflang).toBe("x-default")
36
- expect(defaultUrl?.alternates?.[1]!.href).toBe("https://example.com/about")
37
-
38
- // Non-default locale URL should have only the other locale as alternate
39
- const ptUrl = urls.find((u) => u.loc === "https://example.com/pt/about")
40
- expect(ptUrl?.alternates).toHaveLength(1)
41
- expect(ptUrl?.alternates?.[0]!.hreflang).toBe("en-US")
42
- expect(ptUrl?.alternates?.[0]!.href).toBe("https://example.com/about")
43
- })
44
-
45
- it("handles prefixDefaultLocale option", () => {
46
- const configWithPrefix: LocaleConfig = {
47
- ...config,
48
- prefixDefaultLocale: true
49
- }
50
- const entries: SeoEntry[] = [{ path: "/about" }]
51
- const urls = buildSitemapUrls(entries, configWithPrefix)
52
-
53
- expect(urls[0]!.loc).toBe("https://example.com/en/about")
54
- expect(urls[1]!.loc).toBe("https://example.com/pt/about")
55
- })
56
-
57
- it("handles trailingSlash option", () => {
58
- const configWithSlash: LocaleConfig = {
59
- ...config,
60
- trailingSlash: "always"
61
- }
62
- const entries: SeoEntry[] = [{ path: "/about" }]
63
- const urls = buildSitemapUrls(entries, configWithSlash)
64
-
65
- expect(urls[0]!.loc).toBe("https://example.com/about/")
66
- expect(urls[1]!.loc).toBe("https://example.com/pt/about/")
67
- })
68
-
69
- it("includes lastmod when provided", () => {
70
- const date = new Date("2024-01-01")
71
- const entries: SeoEntry[] = [{ path: "/about", lastmod: date }]
72
- const urls = buildSitemapUrls(entries, config)
73
-
74
- expect(urls[0]!.lastmod).toBe(date.toISOString())
75
- })
76
-
77
- it("includes changefreq and priority when provided", () => {
78
- const entries: SeoEntry[] = [{ path: "/about", changefreq: "weekly", priority: 0.8 }]
79
- const urls = buildSitemapUrls(entries, config)
80
-
81
- expect(urls[0]!.changefreq).toBe("weekly")
82
- expect(urls[0]!.priority).toBe(0.8)
83
- })
84
-
85
- it("restricts to specified locales", () => {
86
- const entries: SeoEntry[] = [{ path: "/about", locales: ["en"] }]
87
- const urls = buildSitemapUrls(entries, config)
88
-
89
- expect(urls).toHaveLength(1)
90
- expect(urls[0]!.loc).toBe("https://example.com/about")
91
- })
92
- })
93
-
94
- describe("buildSitemapXml", () => {
95
- it("generates valid XML for URLs", () => {
96
- const urls = [
97
- {
98
- loc: "https://example.com/about",
99
- lastmod: "2024-01-01T00:00:00.000Z",
100
- changefreq: "weekly" as const,
101
- priority: 0.8
102
- }
103
- ]
104
-
105
- const xml = buildSitemapXml(urls)
106
-
107
- expect(xml).toContain('<?xml version="1.0" encoding="UTF-8"?>')
108
- expect(xml).toContain("<urlset")
109
- expect(xml).toContain("https://example.com/about")
110
- expect(xml).toContain("<lastmod>2024-01-01T00:00:00.000Z</lastmod>")
111
- expect(xml).toContain("<changefreq>weekly</changefreq>")
112
- expect(xml).toContain("<priority>0.8</priority>")
113
- })
114
-
115
- it("includes alternates in XML", () => {
116
- const urls = [
117
- {
118
- loc: "https://example.com/about",
119
- alternates: [
120
- { hreflang: "pt-BR", href: "https://example.com/pt/about" },
121
- { hreflang: "x-default", href: "https://example.com/about" }
122
- ]
123
- }
124
- ]
125
-
126
- const xml = buildSitemapXml(urls)
127
-
128
- expect(xml).toContain("xhtml:link")
129
- expect(xml).toContain('hreflang="pt-BR"')
130
- expect(xml).toContain('hreflang="x-default"')
131
- })
132
-
133
- it("escapes XML special characters", () => {
134
- const urls = [
135
- {
136
- loc: "https://example.com/about?query=test&amp;other=value"
137
- }
138
- ]
139
-
140
- const xml = buildSitemapXml(urls)
141
-
142
- expect(xml).toContain("&amp;")
143
- })
144
- })
145
-
146
- describe("buildSitemapIndexXml", () => {
147
- it("generates valid sitemap index XML", () => {
148
- const sitemaps = ["https://example.com/sitemap-0.xml", "https://example.com/sitemap-1.xml"]
149
-
150
- const xml = buildSitemapIndexXml(sitemaps)
151
-
152
- expect(xml).toContain('<?xml version="1.0" encoding="UTF-8"?>')
153
- expect(xml).toContain("<sitemapindex")
154
- expect(xml).toContain("https://example.com/sitemap-0.xml")
155
- expect(xml).toContain("https://example.com/sitemap-1.xml")
156
- })
157
- })
158
-
159
- describe("chunkSitemapUrls", () => {
160
- it("chunks URLs at default limit", () => {
161
- const urls = Array.from({ length: 50000 }, (_, i) => ({
162
- loc: `https://example.com/page-${i}`
163
- }))
164
-
165
- const chunks = chunkSitemapUrls(urls)
166
-
167
- expect(chunks).toHaveLength(2)
168
- expect(chunks[0]).toHaveLength(45000)
169
- expect(chunks[1]).toHaveLength(5000)
170
- })
171
-
172
- it("handles custom limit", () => {
173
- const urls = Array.from({ length: 100 }, (_, i) => ({
174
- loc: `https://example.com/page-${i}`
175
- }))
176
-
177
- const chunks = chunkSitemapUrls(urls, 25)
178
-
179
- expect(chunks).toHaveLength(4)
180
- expect(chunks[0]).toHaveLength(25)
181
- })
182
-
183
- it("returns single chunk for small arrays", () => {
184
- const urls = [{ loc: "https://example.com/page-1" }, { loc: "https://example.com/page-2" }]
185
-
186
- const chunks = chunkSitemapUrls(urls)
187
-
188
- expect(chunks).toHaveLength(1)
189
- expect(chunks[0]).toHaveLength(2)
190
- })
191
- })