diff --git a/dist/modules/seo/buildLlmsTxt.js b/dist/modules/seo/buildLlmsTxt.js index 34084e1..cfc27f1 100644 --- a/dist/modules/seo/buildLlmsTxt.js +++ b/dist/modules/seo/buildLlmsTxt.js @@ -37,16 +37,16 @@ import { buildLocalizedPath } from '../i18n/index.js'; }); const name = settings.siteName?.trim() || 'Website'; const description = settings.siteDescription?.trim(); + // NO where:{_status} filter — collections without drafts enabled don't + // register the _status field, and querying it throws + // "path cannot be queried: _status" (a real bug report). Draft filtering + // happens in memory below, which is safe for every collection. Same as + // buildSitemapEntries and generateStaticParams. const result = await payload.find({ collection: pagesSlug, depth: 0, limit: 1000, - locale: loc, - where: { - _status: { - not_equals: 'draft' - } - } + locale: loc }); const lines = [ `# ${name}`, diff --git a/dist/modules/seo/buildLlmsTxt.js.map b/dist/modules/seo/buildLlmsTxt.js.map index 6de6de1..b9fedc4 100644 --- a/dist/modules/seo/buildLlmsTxt.js.map +++ b/dist/modules/seo/buildLlmsTxt.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/modules/seo/buildLlmsTxt.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig } from '../i18n/index.js'\n\nimport { buildLocalizedPath } from '../i18n/index.js'\n\ntype BuildLlmsTxtArgs = {\n /** Absolute site URL (https://…). */\n baseUrl: string\n config: I18nConfig\n /** Locale for names/descriptions. Defaults to config.defaultLocale. */\n locale?: string\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Site settings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\ntype SettingsShape = {\n siteDescription?: null | string\n siteName?: null | string\n}\n\ntype PageRow = {\n _status?: string\n id: number | string\n meta?: { description?: unknown; noindex?: boolean } | null\n slug?: unknown\n title?: unknown\n}\n\n/**\n * Builds the body of /llms.txt — a Markdown file describing the site for AI\n * agents / LLM crawlers, per the llmstxt.org convention. Mirrors buildRobots /\n * buildSitemapEntries: the plugin already knows the site's name, description,\n * and pages, so it can generate this automatically.\n *\n * Structure (llmstxt.org): H1 site name, a blockquote/summary, then a list of\n * key pages as Markdown links with short descriptions. Agents read this to\n * understand the site quickly without crawling everything.\n *\n * Wire it as a route that returns text/plain:\n *\n * // app/llms.txt/route.ts\n * import { buildLlmsTxt } from '@intecion/ipal-kit'\n * import { getCachedPayload } from '@/lib/content'\n * import { i18nConfig } from '@/i18n.config'\n * export const dynamic = 'force-dynamic'\n * export async function GET() {\n * const body = await buildLlmsTxt({\n * payload: await getCachedPayload(),\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,\n * })\n * return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } })\n * }\n *\n * Data comes from the panel (siteName, siteDescription, pages) — nothing\n * hardcoded. Skips drafts and noindex pages (same as the sitemap).\n */\nexport async function buildLlmsTxt({\n baseUrl,\n config,\n locale,\n pagesSlug = 'pages',\n payload,\n settingsSlug = 'site-settings',\n}: BuildLlmsTxtArgs): Promise {\n const loc = locale ?? config.defaultLocale\n const origin = baseUrl.replace(/\\/$/, '')\n\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 0,\n locale: loc as never,\n })) as SettingsShape\n\n const name = settings.siteName?.trim() || 'Website'\n const description = settings.siteDescription?.trim()\n\n const result = await payload.find({\n collection: pagesSlug as never,\n depth: 0,\n limit: 1000,\n locale: loc as never,\n where: { _status: { not_equals: 'draft' } } as never,\n })\n\n const lines: string[] = [`# ${name}`, '']\n if (description) {\n lines.push(`> ${description}`, '')\n }\n\n const pageLinks: string[] = []\n for (const raw of result.docs as PageRow[]) {\n if (raw._status && raw._status !== 'published') {continue}\n if (raw.meta?.noindex) {continue}\n\n const title = typeof raw.title === 'string' ? raw.title : undefined\n const slug = typeof raw.slug === 'string' ? raw.slug : undefined\n if (!title || !slug) {continue}\n\n const path = buildLocalizedPath({ config, locale: loc, slugs: { [loc]: slug } })\n if (!path) {continue}\n\n const pageDesc = typeof raw.meta?.description === 'string' ? ` — ${raw.meta.description}` : ''\n pageLinks.push(`- [${title}](${origin}${path})${pageDesc}`)\n }\n\n if (pageLinks.length > 0) {\n lines.push('## Strony', '', ...pageLinks, '')\n }\n\n return lines.join('\\n')\n}\n"],"names":["buildLocalizedPath","buildLlmsTxt","baseUrl","config","locale","pagesSlug","payload","settingsSlug","loc","defaultLocale","origin","replace","settings","findGlobal","slug","depth","name","siteName","trim","description","siteDescription","result","find","collection","limit","where","_status","not_equals","lines","push","pageLinks","raw","docs","meta","noindex","title","undefined","path","slugs","pageDesc","length","join"],"mappings":"AAIA,SAASA,kBAAkB,QAAQ,mBAAkB;AA4BrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,MAAM,EACNC,YAAY,OAAO,EACnBC,OAAO,EACPC,eAAe,eAAe,EACb;IACjB,MAAMC,MAAMJ,UAAUD,OAAOM,aAAa;IAC1C,MAAMC,SAASR,QAAQS,OAAO,CAAC,OAAO;IAEtC,MAAMC,WAAY,MAAMN,QAAQO,UAAU,CAAC;QACzCC,MAAMP;QACNQ,OAAO;QACPX,QAAQI;IACV;IAEA,MAAMQ,OAAOJ,SAASK,QAAQ,EAAEC,UAAU;IAC1C,MAAMC,cAAcP,SAASQ,eAAe,EAAEF;IAE9C,MAAMG,SAAS,MAAMf,QAAQgB,IAAI,CAAC;QAChCC,YAAYlB;QACZU,OAAO;QACPS,OAAO;QACPpB,QAAQI;QACRiB,OAAO;YAAEC,SAAS;gBAAEC,YAAY;YAAQ;QAAE;IAC5C;IAEA,MAAMC,QAAkB;QAAC,CAAC,EAAE,EAAEZ,MAAM;QAAE;KAAG;IACzC,IAAIG,aAAa;QACfS,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEV,aAAa,EAAE;IACjC;IAEA,MAAMW,YAAsB,EAAE;IAC9B,KAAK,MAAMC,OAAOV,OAAOW,IAAI,CAAe;QAC1C,IAAID,IAAIL,OAAO,IAAIK,IAAIL,OAAO,KAAK,aAAa;YAAC;QAAQ;QACzD,IAAIK,IAAIE,IAAI,EAAEC,SAAS;YAAC;QAAQ;QAEhC,MAAMC,QAAQ,OAAOJ,IAAII,KAAK,KAAK,WAAWJ,IAAII,KAAK,GAAGC;QAC1D,MAAMtB,OAAO,OAAOiB,IAAIjB,IAAI,KAAK,WAAWiB,IAAIjB,IAAI,GAAGsB;QACvD,IAAI,CAACD,SAAS,CAACrB,MAAM;YAAC;QAAQ;QAE9B,MAAMuB,OAAOrC,mBAAmB;YAAEG;YAAQC,QAAQI;YAAK8B,OAAO;gBAAE,CAAC9B,IAAI,EAAEM;YAAK;QAAE;QAC9E,IAAI,CAACuB,MAAM;YAAC;QAAQ;QAEpB,MAAME,WAAW,OAAOR,IAAIE,IAAI,EAAEd,gBAAgB,WAAW,CAAC,GAAG,EAAEY,IAAIE,IAAI,CAACd,WAAW,EAAE,GAAG;QAC5FW,UAAUD,IAAI,CAAC,CAAC,GAAG,EAAEM,MAAM,EAAE,EAAEzB,SAAS2B,KAAK,CAAC,EAAEE,UAAU;IAC5D;IAEA,IAAIT,UAAUU,MAAM,GAAG,GAAG;QACxBZ,MAAMC,IAAI,CAAC,aAAa,OAAOC,WAAW;IAC5C;IAEA,OAAOF,MAAMa,IAAI,CAAC;AACpB"} \ No newline at end of file +{"version":3,"sources":["../../../src/modules/seo/buildLlmsTxt.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { I18nConfig } from '../i18n/index.js'\n\nimport { buildLocalizedPath } from '../i18n/index.js'\n\ntype BuildLlmsTxtArgs = {\n /** Absolute site URL (https://…). */\n baseUrl: string\n config: I18nConfig\n /** Locale for names/descriptions. Defaults to config.defaultLocale. */\n locale?: string\n /** Pages collection slug. Defaults to 'pages'. */\n pagesSlug?: string\n payload: BasePayload\n /** Site settings global slug. Defaults to 'site-settings'. */\n settingsSlug?: string\n}\n\ntype SettingsShape = {\n siteDescription?: null | string\n siteName?: null | string\n}\n\ntype PageRow = {\n _status?: string\n id: number | string\n meta?: { description?: unknown; noindex?: boolean } | null\n slug?: unknown\n title?: unknown\n}\n\n/**\n * Builds the body of /llms.txt — a Markdown file describing the site for AI\n * agents / LLM crawlers, per the llmstxt.org convention. Mirrors buildRobots /\n * buildSitemapEntries: the plugin already knows the site's name, description,\n * and pages, so it can generate this automatically.\n *\n * Structure (llmstxt.org): H1 site name, a blockquote/summary, then a list of\n * key pages as Markdown links with short descriptions. Agents read this to\n * understand the site quickly without crawling everything.\n *\n * Wire it as a route that returns text/plain:\n *\n * // app/llms.txt/route.ts\n * import { buildLlmsTxt } from '@intecion/ipal-kit'\n * import { getCachedPayload } from '@/lib/content'\n * import { i18nConfig } from '@/i18n.config'\n * export const dynamic = 'force-dynamic'\n * export async function GET() {\n * const body = await buildLlmsTxt({\n * payload: await getCachedPayload(),\n * config: i18nConfig,\n * baseUrl: process.env.NEXT_PUBLIC_SERVER_URL!,\n * })\n * return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } })\n * }\n *\n * Data comes from the panel (siteName, siteDescription, pages) — nothing\n * hardcoded. Skips drafts and noindex pages (same as the sitemap).\n */\nexport async function buildLlmsTxt({\n baseUrl,\n config,\n locale,\n pagesSlug = 'pages',\n payload,\n settingsSlug = 'site-settings',\n}: BuildLlmsTxtArgs): Promise {\n const loc = locale ?? config.defaultLocale\n const origin = baseUrl.replace(/\\/$/, '')\n\n const settings = (await payload.findGlobal({\n slug: settingsSlug as never,\n depth: 0,\n locale: loc as never,\n })) as SettingsShape\n\n const name = settings.siteName?.trim() || 'Website'\n const description = settings.siteDescription?.trim()\n\n // NO where:{_status} filter — collections without drafts enabled don't\n // register the _status field, and querying it throws\n // \"path cannot be queried: _status\" (a real bug report). Draft filtering\n // happens in memory below, which is safe for every collection. Same as\n // buildSitemapEntries and generateStaticParams.\n const result = await payload.find({\n collection: pagesSlug as never,\n depth: 0,\n limit: 1000,\n locale: loc as never,\n })\n\n const lines: string[] = [`# ${name}`, '']\n if (description) {\n lines.push(`> ${description}`, '')\n }\n\n const pageLinks: string[] = []\n for (const raw of result.docs as PageRow[]) {\n if (raw._status && raw._status !== 'published') {continue}\n if (raw.meta?.noindex) {continue}\n\n const title = typeof raw.title === 'string' ? raw.title : undefined\n const slug = typeof raw.slug === 'string' ? raw.slug : undefined\n if (!title || !slug) {continue}\n\n const path = buildLocalizedPath({ config, locale: loc, slugs: { [loc]: slug } })\n if (!path) {continue}\n\n const pageDesc = typeof raw.meta?.description === 'string' ? ` — ${raw.meta.description}` : ''\n pageLinks.push(`- [${title}](${origin}${path})${pageDesc}`)\n }\n\n if (pageLinks.length > 0) {\n lines.push('## Strony', '', ...pageLinks, '')\n }\n\n return lines.join('\\n')\n}\n"],"names":["buildLocalizedPath","buildLlmsTxt","baseUrl","config","locale","pagesSlug","payload","settingsSlug","loc","defaultLocale","origin","replace","settings","findGlobal","slug","depth","name","siteName","trim","description","siteDescription","result","find","collection","limit","lines","push","pageLinks","raw","docs","_status","meta","noindex","title","undefined","path","slugs","pageDesc","length","join"],"mappings":"AAIA,SAASA,kBAAkB,QAAQ,mBAAkB;AA4BrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,eAAeC,aAAa,EACjCC,OAAO,EACPC,MAAM,EACNC,MAAM,EACNC,YAAY,OAAO,EACnBC,OAAO,EACPC,eAAe,eAAe,EACb;IACjB,MAAMC,MAAMJ,UAAUD,OAAOM,aAAa;IAC1C,MAAMC,SAASR,QAAQS,OAAO,CAAC,OAAO;IAEtC,MAAMC,WAAY,MAAMN,QAAQO,UAAU,CAAC;QACzCC,MAAMP;QACNQ,OAAO;QACPX,QAAQI;IACV;IAEA,MAAMQ,OAAOJ,SAASK,QAAQ,EAAEC,UAAU;IAC1C,MAAMC,cAAcP,SAASQ,eAAe,EAAEF;IAE9C,uEAAuE;IACvE,qDAAqD;IACrD,yEAAyE;IACzE,uEAAuE;IACvE,gDAAgD;IAChD,MAAMG,SAAS,MAAMf,QAAQgB,IAAI,CAAC;QAChCC,YAAYlB;QACZU,OAAO;QACPS,OAAO;QACPpB,QAAQI;IACV;IAEA,MAAMiB,QAAkB;QAAC,CAAC,EAAE,EAAET,MAAM;QAAE;KAAG;IACzC,IAAIG,aAAa;QACfM,MAAMC,IAAI,CAAC,CAAC,EAAE,EAAEP,aAAa,EAAE;IACjC;IAEA,MAAMQ,YAAsB,EAAE;IAC9B,KAAK,MAAMC,OAAOP,OAAOQ,IAAI,CAAe;QAC1C,IAAID,IAAIE,OAAO,IAAIF,IAAIE,OAAO,KAAK,aAAa;YAAC;QAAQ;QACzD,IAAIF,IAAIG,IAAI,EAAEC,SAAS;YAAC;QAAQ;QAEhC,MAAMC,QAAQ,OAAOL,IAAIK,KAAK,KAAK,WAAWL,IAAIK,KAAK,GAAGC;QAC1D,MAAMpB,OAAO,OAAOc,IAAId,IAAI,KAAK,WAAWc,IAAId,IAAI,GAAGoB;QACvD,IAAI,CAACD,SAAS,CAACnB,MAAM;YAAC;QAAQ;QAE9B,MAAMqB,OAAOnC,mBAAmB;YAAEG;YAAQC,QAAQI;YAAK4B,OAAO;gBAAE,CAAC5B,IAAI,EAAEM;YAAK;QAAE;QAC9E,IAAI,CAACqB,MAAM;YAAC;QAAQ;QAEpB,MAAME,WAAW,OAAOT,IAAIG,IAAI,EAAEZ,gBAAgB,WAAW,CAAC,GAAG,EAAES,IAAIG,IAAI,CAACZ,WAAW,EAAE,GAAG;QAC5FQ,UAAUD,IAAI,CAAC,CAAC,GAAG,EAAEO,MAAM,EAAE,EAAEvB,SAASyB,KAAK,CAAC,EAAEE,UAAU;IAC5D;IAEA,IAAIV,UAAUW,MAAM,GAAG,GAAG;QACxBb,MAAMC,IAAI,CAAC,aAAa,OAAOC,WAAW;IAC5C;IAEA,OAAOF,MAAMc,IAAI,CAAC;AACpB"} \ No newline at end of file diff --git a/src/modules/seo/buildLlmsTxt.ts b/src/modules/seo/buildLlmsTxt.ts index f9e52ad..ec82bbf 100644 --- a/src/modules/seo/buildLlmsTxt.ts +++ b/src/modules/seo/buildLlmsTxt.ts @@ -79,12 +79,16 @@ export async function buildLlmsTxt({ const name = settings.siteName?.trim() || 'Website' const description = settings.siteDescription?.trim() + // NO where:{_status} filter — collections without drafts enabled don't + // register the _status field, and querying it throws + // "path cannot be queried: _status" (a real bug report). Draft filtering + // happens in memory below, which is safe for every collection. Same as + // buildSitemapEntries and generateStaticParams. const result = await payload.find({ collection: pagesSlug as never, depth: 0, limit: 1000, locale: loc as never, - where: { _status: { not_equals: 'draft' } } as never, }) const lines: string[] = [`# ${name}`, '']