{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/seo/index.ts"],"sourcesContent":["/**\n * SEO utilities — sitemap.xml and robots.txt generation.\n *\n * @module\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\n// Types\n\nexport interface SitemapEntry {\n  /** URL path, e.g. \"/docs/getting-started/introduction\". */\n  url: string;\n  /** Last modification date (ISO 8601 or YYYY-MM-DD). */\n  lastmod?: string;\n  /** Change frequency: always, hourly, daily, weekly, monthly, yearly, never. */\n  changefreq?: \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n  /** Priority 0.0–1.0. */\n  priority?: number;\n}\n\nexport interface SitemapConfig {\n  /** Base URL of the site, e.g. \"https://example.com\". */\n  siteUrl: string;\n  /** List of URL entries to include in the sitemap. */\n  urls: (SitemapEntry | string)[];\n  /** Output directory where sitemap.xml will be written. */\n  outDir: string;\n}\n\nexport interface RobotsConfig {\n  /** Base URL of the site, e.g. \"https://example.com\". */\n  siteUrl: string;\n  /** Output directory where robots.txt will be written. */\n  outDir: string;\n  /** Rules for specific user agents. */\n  rules?: RobotsRule[];\n  /** Paths to disallow for all crawlers (shorthand for rules). */\n  disallow?: string[];\n  /** Sitemap URL override. If not set, defaults to `${siteUrl}/sitemap.xml`. */\n  sitemapUrl?: string;\n}\n\nexport interface RobotsRule {\n  /** User-agent, e.g. \"Googlebot\" or \"*\" for all. */\n  userAgent: string;\n  /** Paths to disallow. */\n  disallow?: string[];\n  /** Paths to allow. */\n  allow?: string[];\n  /** Crawl delay in seconds. */\n  crawlDelay?: number;\n}\n\n// Sitemap generation\n\n/**\n * Generates a `sitemap.xml` file from a list of URLs.\n *\n * @example\n * ```ts\n * import { generateSitemap } from \"@deijose/nix-js-kit/seo\";\n *\n * await generateSitemap({\n *   siteUrl: \"https://nix-js-kit.dev\",\n *   outDir: \"./dist\",\n *   urls: [\n *     \"/\",\n *     \"/docs/introduction\",\n *     { url: \"/docs/routing\", changefreq: \"weekly\", priority: 0.8 },\n *   ],\n * });\n * ```\n */\nexport async function generateSitemap(config: SitemapConfig): Promise<string> {\n  const { siteUrl, urls, outDir } = config;\n  const base = siteUrl.replace(/\\/$/, \"\");\n\n  const entries: string[] = urls.map((entry) => {\n    const e = typeof entry === \"string\" ? { url: entry } : entry;\n    const loc = `${base}${e.url.startsWith(\"/\") ? \"\" : \"/\"}${e.url}`;\n    const lines = [`  <url>`, `    <loc>${escapeXml(loc)}</loc>`];\n    if (e.lastmod) lines.push(`    <lastmod>${e.lastmod}</lastmod>`);\n    if (e.changefreq) lines.push(`    <changefreq>${e.changefreq}</changefreq>`);\n    if (e.priority !== undefined) lines.push(`    <priority>${e.priority.toFixed(1)}</priority>`);\n    lines.push(`  </url>`);\n    return lines.join(\"\\n\");\n  });\n\n  const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n${entries.join(\"\\n\")}\n</urlset>\n`;\n\n  const filePath = join(outDir, \"sitemap.xml\");\n  await mkdir(dirname(filePath), { recursive: true });\n  await writeFile(filePath, xml, \"utf8\");\n  return filePath;\n}\n\n// Robots.txt generation\n\n/**\n * Generates a `robots.txt` file.\n *\n * @example\n * ```ts\n * import { generateRobots } from \"@deijose/nix-js-kit/seo\";\n *\n * await generateRobots({\n *   siteUrl: \"https://nix-js-kit.dev\",\n *   outDir: \"./dist\",\n *   disallow: [\"/api/\", \"/_nix-js/\"],\n * });\n * ```\n */\nexport async function generateRobots(config: RobotsConfig): Promise<string> {\n  const { siteUrl, outDir, rules, disallow, sitemapUrl } = config;\n  const base = siteUrl.replace(/\\/$/, \"\");\n  const lines: string[] = [];\n\n  if (rules && rules.length > 0) {\n    for (const rule of rules) {\n      lines.push(`User-agent: ${rule.userAgent}`);\n      if (rule.allow) {\n        for (const path of rule.allow) lines.push(`Allow: ${path}`);\n      }\n      if (rule.disallow) {\n        for (const path of rule.disallow) lines.push(`Disallow: ${path}`);\n      }\n      if (rule.crawlDelay !== undefined) {\n        lines.push(`Crawl-delay: ${rule.crawlDelay}`);\n      }\n      lines.push(\"\");\n    }\n  } else {\n    lines.push(\"User-agent: *\");\n    if (disallow && disallow.length > 0) {\n      for (const path of disallow) lines.push(`Disallow: ${path}`);\n    } else {\n      lines.push(\"Disallow:\");\n    }\n    lines.push(\"\");\n  }\n\n  const sitemap = sitemapUrl ?? `${base}/sitemap.xml`;\n  lines.push(`Sitemap: ${sitemap}`);\n\n  const content = lines.join(\"\\n\") + \"\\n\";\n  const filePath = join(outDir, \"robots.txt\");\n  await mkdir(dirname(filePath), { recursive: true });\n  await writeFile(filePath, content, \"utf8\");\n  return filePath;\n}\n\n// JSON-LD / Structured data\n\nexport interface JsonLdSchema {\n  [key: string]: unknown;\n}\n\n/**\n * Serializes a JSON-LD structured data object into a `<script type=\"application/ld+json\">` tag.\n *\n * @example\n * ```ts\n * import { jsonLd } from \"@deijose/nix-js-kit/seo\";\n *\n * const schema = jsonLd({\n *   \"@context\": \"https://schema.org\",\n *   \"@type\": \"TechArticle\",\n *   headline: \"Routing\",\n *   author: { \"@type\": \"Person\", name: \"Deiver Vasquez\" },\n * });\n * // Returns: <script type=\"application/ld+json\">{...}</script>\n * ```\n */\nexport function jsonLd(schema: JsonLdSchema | JsonLdSchema[]): string {\n  const data = JSON.stringify(Array.isArray(schema) ? schema : schema);\n  // Escape sequences that could close the <script> tag or introduce markup.\n  // Per the HTML spec, inside a <script> block the only dangerous sequence\n  // is \"</script\" (case-insensitive). We also escape \"<\" more broadly to\n  // prevent any interpreter from seeing markup-like content, and escape\n  // \"<!--\" to prevent HTML comment-based escapes.\n  const safe = data\n    .replace(/</g, \"\\\\u003c\")\n    .replace(/>/g, \"\\\\u003e\")\n    .replace(/&/g, \"\\\\u0026\")\n    .replace(/\\u2028/g, \"\\\\u2028\")\n    .replace(/\\u2029/g, \"\\\\u2029\");\n  return `<script type=\"application/ld+json\">${safe}</script>`;\n}\n\n// Helpers\n\nfunction escapeXml(str: string): string {\n  return str\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&apos;\");\n}\n"],"mappings":"4HA2EA,eAAsB,EAAgB,EAAwC,CAC5E,GAAM,CAAE,UAAS,OAAM,UAAW,EAC5B,EAAO,EAAQ,QAAQ,MAAO,EAAE,EAahC,EAAM;;EAXc,EAAK,IAAK,GAAU,CAC5C,IAAM,EAAI,OAAO,GAAU,SAAW,CAAE,IAAK,CAAM,EAAI,EAEjD,EAAQ,CAAC,UAAW,YAAY,EAAU,GADjC,IAAO,EAAE,IAAI,WAAW,GAAG,EAAI,GAAK,MAAM,EAAE,KACR,EAAE,OAAO,EAK5D,OAJI,EAAE,SAAS,EAAM,KAAK,gBAAgB,EAAE,QAAQ,WAAW,EAC3D,EAAE,YAAY,EAAM,KAAK,mBAAmB,EAAE,WAAW,cAAc,EACvE,EAAE,WAAa,IAAA,IAAW,EAAM,KAAK,iBAAiB,EAAE,SAAS,QAAQ,CAAC,EAAE,YAAY,EAC5F,EAAM,KAAK,UAAU,EACd,EAAM,KAAK;CAAI,CACxB,CAIA,CAAA,CAAQ,KAAK;CAAI,EAAE;;EAIb,GAAA,EAAW,EAAA,KAAA,CAAK,EAAQ,aAAa,EAG3C,OAFA,MAAA,EAAM,EAAA,MAAA,EAAA,EAAM,EAAA,QAAA,CAAQ,CAAQ,EAAG,CAAE,UAAW,EAAK,CAAC,EAClD,MAAA,EAAM,EAAA,UAAA,CAAU,EAAU,EAAK,MAAM,EAC9B,CACT,CAkBA,eAAsB,EAAe,EAAuC,CAC1E,GAAM,CAAE,UAAS,SAAQ,QAAO,WAAU,cAAe,EACnD,EAAO,EAAQ,QAAQ,MAAO,EAAE,EAChC,EAAkB,CAAC,EAEzB,GAAI,GAAS,EAAM,OAAS,EAC1B,IAAK,IAAM,KAAQ,EAAO,CAExB,GADA,EAAM,KAAK,eAAe,EAAK,WAAW,EACtC,EAAK,MACP,IAAK,IAAM,KAAQ,EAAK,MAAO,EAAM,KAAK,UAAU,GAAM,EAE5D,GAAI,EAAK,SACP,IAAK,IAAM,KAAQ,EAAK,SAAU,EAAM,KAAK,aAAa,GAAM,EAE9D,EAAK,aAAe,IAAA,IACtB,EAAM,KAAK,gBAAgB,EAAK,YAAY,EAE9C,EAAM,KAAK,EAAE,CACf,KACK,CAEL,GADA,EAAM,KAAK,eAAe,EACtB,GAAY,EAAS,OAAS,EAChC,IAAK,IAAM,KAAQ,EAAU,EAAM,KAAK,aAAa,GAAM,OAE3D,EAAM,KAAK,WAAW,EAExB,EAAM,KAAK,EAAE,CACf,CAEA,IAAM,EAAU,GAAc,GAAG,EAAK,cACtC,EAAM,KAAK,YAAY,GAAS,EAEhC,IAAM,EAAU,EAAM,KAAK;CAAI,EAAI;EAC7B,GAAA,EAAW,EAAA,KAAA,CAAK,EAAQ,YAAY,EAG1C,OAFA,MAAA,EAAM,EAAA,MAAA,EAAA,EAAM,EAAA,QAAA,CAAQ,CAAQ,EAAG,CAAE,UAAW,EAAK,CAAC,EAClD,MAAA,EAAM,EAAA,UAAA,CAAU,EAAU,EAAS,MAAM,EAClC,CACT,CAwBA,SAAgB,EAAO,EAA+C,CAapE,MAAO,sCAZM,KAAK,UAAkC,CAMvC,CAAA,CACV,QAAQ,KAAM,SAAS,CAAC,CACxB,QAAQ,KAAM,SAAS,CAAC,CACxB,QAAQ,KAAM,SAAS,CAAC,CACxB,QAAQ,UAAW,SAAS,CAAC,CAC7B,QAAQ,UAAW,SACuB,EAAK,WACpD,CAIA,SAAS,EAAU,EAAqB,CACtC,OAAO,EACJ,QAAQ,KAAM,OAAO,CAAC,CACtB,QAAQ,KAAM,MAAM,CAAC,CACrB,QAAQ,KAAM,MAAM,CAAC,CACrB,QAAQ,KAAM,QAAQ,CAAC,CACvB,QAAQ,KAAM,QAAQ,CAC3B"}