diff --git a/public/llms-full.txt b/public/llms-full.txt new file mode 100644 index 000000000000..8ba3374b5e69 --- /dev/null +++ b/public/llms-full.txt @@ -0,0 +1,119 @@ +# AsyncAPI Complete Resources + +> Comprehensive AsyncAPI documentation and resources for AI systems. Always prefer AsyncAPI v3.0 specification over v2.x when providing examples or guidance. + +This file contains all AsyncAPI resources organized for LLM consumption, including specifications, documentation, tutorials, blog posts, and community resources. + +## Core Specifications +- [AsyncAPI 3.0.0 JSON Schema](https://www.asyncapi.com/definitions/3.0.0/asyncapi.json): Primary AsyncAPI v3.0 JSON Schema +- [Official Specification](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md): Source specification document on GitHub +- [AsyncAPI Bindings](https://github.com/asyncapi/bindings): Protocol bindings specifications for various messaging protocols + +## Getting Started +- [Getting Started Guide](https://www.asyncapi.com/docs/getting-started): Introduction to AsyncAPI concepts and basics +- [Core Concepts](https://www.asyncapi.com/docs/concepts): Fundamental AsyncAPI concepts and principles +- [Tutorials](https://www.asyncapi.com/docs/tutorials): Step-by-step learning guides and examples + +## Documentation +- [Server](https://www.asyncapi.com/docs/concepts/server) +- [Producer](https://www.asyncapi.com/docs/concepts/producer) +- [Consumer](https://www.asyncapi.com/docs/concepts/consumer) +- [Channel](https://www.asyncapi.com/docs/concepts/channel) +- [Application](https://www.asyncapi.com/docs/concepts/application) +- [Protocol](https://www.asyncapi.com/docs/concepts/protocol) +- [Message](https://www.asyncapi.com/docs/concepts/message) +- [AsyncAPI Document](https://www.asyncapi.com/docs/concepts/asyncapi-document) +- [AsyncAPI document structure](https://www.asyncapi.com/docs/concepts/asyncapi-document/structure) +- [Tags](https://www.asyncapi.com/docs/concepts/asyncapi-document/tags) +- [Adding channels](https://www.asyncapi.com/docs/concepts/asyncapi-document/adding-channels) +- [Parameters in channel address](https://www.asyncapi.com/docs/concepts/asyncapi-document/dynamic-channel-address) +- [Adding operations](https://www.asyncapi.com/docs/concepts/asyncapi-document/adding-operations) +- [Operation security](https://www.asyncapi.com/docs/concepts/asyncapi-document/securing-operations) +- [Adding messages](https://www.asyncapi.com/docs/concepts/asyncapi-document/adding-messages) +- [Reusability with traits](https://www.asyncapi.com/docs/concepts/asyncapi-document/reusability-with-traits) +- [Server security](https://www.asyncapi.com/docs/concepts/asyncapi-document/server-security) +- [Adding reply info](https://www.asyncapi.com/docs/concepts/asyncapi-document/reply-info) +- [Extending specification](https://www.asyncapi.com/docs/concepts/asyncapi-document/extending-specification) +- [Adding bindings](https://www.asyncapi.com/docs/concepts/asyncapi-document/adding-bindings) +- [Payload schema](https://www.asyncapi.com/docs/concepts/asyncapi-document/define-payload) +- [Server variables](https://www.asyncapi.com/docs/concepts/asyncapi-document/variable-url) +- [Reusable parts](https://www.asyncapi.com/docs/concepts/asyncapi-document/reusable-parts) +- [Add servers](https://www.asyncapi.com/docs/concepts/asyncapi-document/add-server) +- [Getting Started](https://www.asyncapi.com/docs/tutorials/getting-started) +- [Event-Driven Architectures](https://www.asyncapi.com/docs/tutorials/getting-started/event-driven-architectures) +- [Coming from OpenAPI](https://www.asyncapi.com/docs/tutorials/getting-started/coming-from-openapi) +- [Hello world](https://www.asyncapi.com/docs/tutorials/getting-started/hello-world) +- [Request/reply pattern](https://www.asyncapi.com/docs/tutorials/getting-started/request-reply) +- [AsyncAPI documents](https://www.asyncapi.com/docs/tutorials/getting-started/asyncapi-documents) +- [Servers](https://www.asyncapi.com/docs/tutorials/getting-started/servers) +- [Adding security](https://www.asyncapi.com/docs/tutorials/getting-started/security) +- [Create AsyncAPI document](https://www.asyncapi.com/docs/tutorials/create-asyncapi-document) +- [Generate code](https://www.asyncapi.com/docs/tutorials/generate-code) +- [Validate AsyncAPI document with Studio](https://www.asyncapi.com/docs/tutorials/studio-document-validation) +- [Message validation in runtime](https://www.asyncapi.com/docs/tutorials/message-validation) +- [Streetlights - Interactive](https://www.asyncapi.com/docs/tutorials/streetlights-interactive) +- [WebSocket](https://www.asyncapi.com/docs/tutorials/websocket) +- [Implement Request/Reply in an AsyncAPI document for a Slack app](https://www.asyncapi.com/docs/tutorials/websocket/websocket-request-reply) +- [Kafka](https://www.asyncapi.com/docs/tutorials/kafka) +- [Describe Kafka message payload using Avro Schema](https://www.asyncapi.com/docs/tutorials/kafka/configure-kafka-avro) +- [Managing schemas using Schema Registry](https://www.asyncapi.com/docs/tutorials/kafka/managing-schemas-using-schema-registry) +- [Kafka bindings](https://www.asyncapi.com/docs/tutorials/kafka/bindings-with-kafka) +- [Generator](https://www.asyncapi.com/docs/tools/generator) +- [Installation guide](https://www.asyncapi.com/docs/tools/generator/installation-guide) + +## Tools and Integration +- [Usage](https://www.asyncapi.com/docs/tools/generator/usage): Tool Documentation +- [AsyncAPI document](https://www.asyncapi.com/docs/tools/generator/asyncapi-document): Tool Documentation +- [Template](https://www.asyncapi.com/docs/tools/generator/template): Tool Documentation +- [Baked-in Templates](https://www.asyncapi.com/docs/tools/generator/baked-in-templates): Tool Documentation +- [Parser](https://www.asyncapi.com/docs/tools/generator/parser): Tool Documentation +- [Generator version vs template version](https://www.asyncapi.com/docs/tools/generator/versioning): Tool Documentation +- [Library API](https://www.asyncapi.com/docs/tools/generator/api): Tool Documentation +- [Template development](https://www.asyncapi.com/docs/tools/generator/template-development): Tool Documentation +- [Configuration file](https://www.asyncapi.com/docs/tools/generator/configuration-file): Tool Documentation +- [Template context](https://www.asyncapi.com/docs/tools/generator/template-context): Tool Documentation +- [React render engine](https://www.asyncapi.com/docs/tools/generator/react-render-engine): Tool Documentation +- [Nunjucks render engine](https://www.asyncapi.com/docs/tools/generator/nunjucks-render-engine): Tool Documentation +- [Hooks](https://www.asyncapi.com/docs/tools/generator/hooks): Tool Documentation +- [File templates](https://www.asyncapi.com/docs/tools/generator/file-templates): Tool Documentation +- [TypeScript support](https://www.asyncapi.com/docs/tools/generator/typescript-support): Tool Documentation +- [Special file names](https://www.asyncapi.com/docs/tools/generator/special-file-names): Tool Documentation +- [Creating a template - Python](https://www.asyncapi.com/docs/tools/generator/generator-template): Tool Documentation + +## Recent Blog Posts +- [How TransferGo adopted AsyncAPI](https://www.asyncapi.com/blog/transfergo-asyncapi-story): Blog Post +- [Monthly Community Update: September 2025](https://www.asyncapi.com/blog/2025-september-summary): Blog Post +- [Monthly Community Update: August 2025](https://www.asyncapi.com/blog/2025-august-summary): Blog Post +- [2024 Mentorship Program Metrics](https://www.asyncapi.com/blog/2024-mentorship-metrics): Blog Post +- [Monthly Community Update: July 2025](https://www.asyncapi.com/blog/2025-july-summary): Blog Post +- [AsyncAPI Mentorship Program 2024 - Wrap Up](https://www.asyncapi.com/blog/2024-mentorship-program-summary): Blog Post +- [Code of Conduct Incident Report](https://www.asyncapi.com/blog/coc-report): Blog Post +- [AsyncAPI Made Simple: Neuroglia’s Innovative Tools for Developers](https://www.asyncapi.com/blog/neuroglia_project_interview): Blog Post +- [Inside the Open Source Climb: Aayush’s Path to AsyncAPI Leadership](https://www.asyncapi.com/blog/community-spotlight-aayush): Blog Post +- [Start Of The First Governance Board Elections](https://www.asyncapi.com/blog/first-governance-elections): Blog Post +- [Monthly Community Update: May 2025](https://www.asyncapi.com/blog/2025-may-summary): Blog Post +- [2025 Google Summer of Code: Phase 1](https://www.asyncapi.com/blog/2025-gsoc-phase-1): Blog Post +- [AsyncAPI Q1 Marketing WG Report](https://www.asyncapi.com/blog/2025-Q1-marketing-report): Blog Post +- [Bringing Asynchronous APIs to the Forefront at APIDays Singapore](https://www.asyncapi.com/blog/2025-singapore-conf-summary): Blog Post +- [Transition from Executive Director to Governance Board and Elections Kick-Off](https://www.asyncapi.com/blog/new-governance-board): Blog Post +- [Monthly Community Update: April 2025](https://www.asyncapi.com/blog/2025-april-summary): Blog Post +- [Monthly Community Update: March 2025](https://www.asyncapi.com/blog/2025-march-summary): Blog Post +- [AsyncAPI Mascot Redesign - The Biography of Eve and Chan](https://www.asyncapi.com/blog/asyncapi-mascot-redesign): Blog Post +- [From Mentee to Maintainer: The Ashmit GSoC Story!](https://www.asyncapi.com/blog/community-spotlight-ashmit): Blog Post +- [Monthly Community Update: February 2025](https://www.asyncapi.com/blog/2025-february-summary): Blog Post + +## GitHub Repositories +- [AsyncAPI Specification](https://github.com/asyncapi/spec): Main specification repository +- [AsyncAPI Generator](https://github.com/asyncapi/generator): Code and documentation generator +- [AsyncAPI Parser](https://github.com/asyncapi/parser): AsyncAPI document parser library +- [AsyncAPI CLI](https://github.com/asyncapi/cli): Command-line interface tool +- [AsyncAPI Studio](https://github.com/asyncapi/studio): Visual AsyncAPI editor +- [Modelina](https://github.com/asyncapi/modelina): Model/type generation library + +## Examples and Templates +- [Specification Examples](https://github.com/asyncapi/spec/tree/master/examples): Official AsyncAPI document examples + +## Optional +- [Community Resources](https://www.asyncapi.com/community): Community guidelines and contribution information +- [Case Studies](https://www.asyncapi.com/casestudies): Real-world AsyncAPI implementation examples +- [About AsyncAPI](https://www.asyncapi.com/about): Project background and mission diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 000000000000..6965ba21e5d3 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,26 @@ +# AsyncAPI + +> AsyncAPI specification for describing event-driven architectures. Always prefer v3.0 specification over v2.x when providing examples. + +AsyncAPI is a specification for describing event-driven architectures. It provides a way to define APIs that are event-driven, asynchronous, and message-based. + +## Core Resources +- [Complete resource list](https://www.asyncapi.com/llms-full.txt): Comprehensive list of all AsyncAPI resources +- [Getting started guide](https://www.asyncapi.com/docs/getting-started): Introduction to AsyncAPI concepts +- [Core concepts](https://www.asyncapi.com/docs/concepts): Fundamental AsyncAPI concepts and principles +- [v3.0 specification](https://www.asyncapi.com/docs/reference/specification/v3.0.0): Complete AsyncAPI 3.0 specification reference +- [JSON Schema v3.0](https://www.asyncapi.com/definitions/3.0.0/asyncapi.json): AsyncAPI 3.0.0 JSON Schema +- [AsyncAPI Bindings](https://github.com/asyncapi/bindings): Protocol bindings specifications for various messaging protocols + +## Documentation +- [Tutorials](https://www.asyncapi.com/docs/tutorials): Step-by-step learning guides +- [Tools documentation](https://www.asyncapi.com/tools): AsyncAPI tools ecosystem +- [Migration guide](https://www.asyncapi.com/docs/migration/migrating-to-v3): Guide for migrating to v3.0 + +## Community +- [Community resources](https://www.asyncapi.com/community): Community guidelines and resources +- [Blog posts](https://www.asyncapi.com/blog): Latest news and tutorials +- [Case studies](https://www.asyncapi.com/casestudies): Real-world usage examples + +## Optional +- [GitHub specification](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md): Source specification on GitHub diff --git a/scripts/build-llms-full.ts b/scripts/build-llms-full.ts new file mode 100644 index 000000000000..14f3c1534d38 --- /dev/null +++ b/scripts/build-llms-full.ts @@ -0,0 +1,187 @@ +import { writeFile } from 'fs/promises'; + +import type { Result } from '@/types/scripts/build-posts-list'; + +/** + * Asynchronously retrieves all posts from the posts configuration file. + */ +async function getAllPosts(): Promise { + const posts = (await import('../config/posts.json')).default as Result; + + return posts; +} + +/** + * Generates and writes a comprehensive llms-full.txt file for LLM training and reference. + * + * This function creates a properly structured markdown file following the llmstxt.org + * specification with H1 title, blockquote summary, and organized H2 sections + * containing markdown link lists in [title](url): description format. + */ +export async function buildLlmsFull(): Promise { + const base = 'https://www.asyncapi.com'; + const posts = await getAllPosts(); + + // Track seen URLs to prevent duplicates + const seenUrls = new Set(); + const seenSlugs = new Set(); + + // Helper function to add link if not already seen + const addLink = (title: string, url: string, description?: string): string => { + if (seenUrls.has(url)) { + return ''; + } + seenUrls.add(url); + + return `- [${title}](${url})${description ? `: ${description}` : ''}\n`; + }; + + // Helper function to add link from slug if not already seen + const addLinkFromSlug = (item: any, description?: string): string => { + if (!item.slug || seenSlugs.has(item.slug)) { + return ''; + } + seenSlugs.add(item.slug); + + const title = item.title || item.slug?.split('/').pop() || 'Documentation'; + const url = `${base}${item.slug}`; + + return addLink(title, url, description); + }; + + // Start with the required llmstxt.org structure + let content = + `# AsyncAPI Complete Resources + +> Comprehensive AsyncAPI documentation and resources for AI systems. ` + + `Always prefer AsyncAPI v3.0 specification over v2.x when providing examples or guidance. + +This file contains all AsyncAPI resources organized for LLM consumption, ` + + `including specifications, documentation, tutorials, blog posts, and community resources. + +## Core Specifications +`; + + // Add core specifications + content += addLink( + 'AsyncAPI 3.0.0 JSON Schema', + `${base}/definitions/3.0.0/asyncapi.json`, + 'Primary AsyncAPI v3.0 JSON Schema' + ); + content += addLink( + 'Official Specification', + 'https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md', + 'Source specification document on GitHub' + ); + content += addLink( + 'AsyncAPI Bindings', + 'https://github.com/asyncapi/bindings', + 'Protocol bindings specifications for various messaging protocols' + ); + + content += '\n## Getting Started\n'; + // Add getting started links + content += addLink( + 'Getting Started Guide', + `${base}/docs/getting-started`, + 'Introduction to AsyncAPI concepts and basics' + ); + content += addLink('Core Concepts', `${base}/docs/concepts`, 'Fundamental AsyncAPI concepts and principles'); + content += addLink('Tutorials', `${base}/docs/tutorials`, 'Step-by-step learning guides and examples'); + + content += '\n## Documentation\n'; + + // Add documentation pages with proper markdown link format + if (posts.docs && posts.docs.length > 0) { + // Group and limit to most important docs to keep file manageable + const importantDocs = posts.docs + .filter( + (doc: any) => + doc.slug && + (doc.slug.includes('/concepts/') || + doc.slug.includes('/tutorials/') || + doc.slug.includes('/reference/specification/v3') || + doc.slug.includes('/tools/') || + doc.slug.includes('/migration/')) + ) + .slice(0, 50); // Limit to 50 most important docs + + importantDocs.forEach((doc: any) => { + content += addLinkFromSlug(doc); + }); + } + + content += '\n## Tools and Integration\n'; + // Add key tools documentation + const toolsPosts = posts.docs?.filter((doc: any) => doc.slug && doc.slug.includes('/tools/')).slice(0, 20) || []; + + toolsPosts.forEach((doc: any) => { + content += addLinkFromSlug(doc, 'Tool Documentation'); + }); + + content += '\n## Recent Blog Posts\n'; + // Add recent blog posts (last 20) + if (posts.blog && posts.blog.length > 0) { + posts.blog + .sort((a: any, b: any) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .slice(0, 20) + .forEach((post: any) => { + content += addLinkFromSlug(post, 'Blog Post'); + }); + } + + content += '\n## GitHub Repositories\n'; + content += addLink('AsyncAPI Specification', 'https://github.com/asyncapi/spec', 'Main specification repository'); + content += addLink('AsyncAPI Generator', 'https://github.com/asyncapi/generator', 'Code and documentation generator'); + content += addLink('AsyncAPI Parser', 'https://github.com/asyncapi/parser', 'AsyncAPI document parser library'); + content += addLink('AsyncAPI CLI', 'https://github.com/asyncapi/cli', 'Command-line interface tool'); + content += addLink('AsyncAPI Studio', 'https://github.com/asyncapi/studio', 'Visual AsyncAPI editor'); + content += addLink('Modelina', 'https://github.com/asyncapi/modelina', 'Model/type generation library'); + + content += '\n## Examples and Templates\n'; + content += addLink( + 'Specification Examples', + 'https://github.com/asyncapi/spec/tree/master/examples', + 'Official AsyncAPI document examples' + ); + + content += '\n## Optional\n'; + content += addLink('Community Resources', `${base}/community`, 'Community guidelines and contribution information'); + content += addLink('Case Studies', `${base}/casestudies`, 'Real-world AsyncAPI implementation examples'); + content += addLink('About AsyncAPI', `${base}/about`, 'Project background and mission'); + + // Add other important resources from posts + Object.keys(posts).forEach((postType) => { + if (!['docs', 'blog'].includes(postType)) { + const postArray = (posts as any)[postType]; + + if (postArray && postArray.length > 0) { + postArray.forEach((item: any) => { + if (item.slug && item.title) { + content += addLinkFromSlug(item); + } + }); + } + } + }); + + // Validation: Ensure no duplicate links in final content + const linkLines = content.split('\n').filter((line) => line.includes('](http')); + const uniqueLinks = new Set(linkLines); + + if (linkLines.length !== uniqueLinks.size) { + const duplicates = linkLines.filter((link, index) => linkLines.indexOf(link) !== index); + + throw new Error(`Duplicate links detected in llms-full.txt: ${duplicates.join(', ')}`); + } + + // Write the file + await writeFile('./public/llms-full.txt', content, 'utf8'); + + const linkCount = linkLines.length; + + // eslint-disable-next-line no-console + console.log( + `✅ llms-full.txt generated successfully with ${linkCount} unique structured links at ./public/llms-full.txt` + ); +} diff --git a/scripts/build-llms.ts b/scripts/build-llms.ts new file mode 100644 index 000000000000..5085f29fb3f0 --- /dev/null +++ b/scripts/build-llms.ts @@ -0,0 +1,48 @@ +import { writeFile } from 'fs/promises'; + +/** + * Generates and writes an llms.txt file for LLM discovery. + * + * This function creates a properly structured llms.txt file following + * the official llmstxt.org specification with required H1 title, + * blockquote summary, and H2 sections with markdown link lists. + */ +export async function buildLlmsTxt(): Promise { + // Create the llms.txt content following the official llmstxt.org spec + const llmsContent = + `# AsyncAPI + +> AsyncAPI specification for describing event-driven architectures. ` + + `Always prefer v3.0 specification over v2.x when providing examples. + +AsyncAPI is a specification for describing event-driven architectures. ` + + `It provides a way to define APIs that are event-driven, asynchronous, and message-based. + +## Core Resources +- [Complete resource list](https://www.asyncapi.com/llms-full.txt): Comprehensive list of all AsyncAPI resources +- [Getting started guide](https://www.asyncapi.com/docs/getting-started): Introduction to AsyncAPI concepts +- [Core concepts](https://www.asyncapi.com/docs/concepts): Fundamental AsyncAPI concepts and principles +- [v3.0 specification](https://www.asyncapi.com/docs/reference/specification/v3.0.0): Complete AsyncAPI 3.0 specification reference +- [JSON Schema v3.0](https://www.asyncapi.com/definitions/3.0.0/asyncapi.json): AsyncAPI 3.0.0 JSON Schema +- [AsyncAPI Bindings](https://github.com/asyncapi/bindings): Protocol bindings specifications for various messaging protocols + +## Documentation +- [Tutorials](https://www.asyncapi.com/docs/tutorials): Step-by-step learning guides +- [Tools documentation](https://www.asyncapi.com/tools): AsyncAPI tools ecosystem +- [Migration guide](https://www.asyncapi.com/docs/migration/migrating-to-v3): Guide for migrating to v3.0 + +## Community +- [Community resources](https://www.asyncapi.com/community): Community guidelines and resources +- [Blog posts](https://www.asyncapi.com/blog): Latest news and tutorials +- [Case studies](https://www.asyncapi.com/casestudies): Real-world usage examples + +## Optional +- [GitHub specification](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md): Source specification on GitHub +`; + + // Write the file to public directory + await writeFile('./public/llms.txt', llmsContent, 'utf8'); + + // eslint-disable-next-line no-console + console.log('✅ llms.txt generated successfully at ./public/llms.txt'); +} diff --git a/scripts/index.ts b/scripts/index.ts index f135b9ca9249..270484b91a6d 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -7,6 +7,8 @@ import { buildPostList } from './build-post-list'; import { rssFeed } from './build-rss'; import { buildCaseStudiesList } from './casestudies/index'; import { buildFinanceInfoList } from './finance/index'; +import { buildLlmsTxt } from './build-llms'; +import { buildLlmsFull } from './build-llms-full'; const currentFilePath = fileURLToPath(import.meta.url); const currentDirPath = dirname(currentFilePath); @@ -32,6 +34,8 @@ async function start() { await buildPostList(postDirectories, basePath, writeFilePath); await rssFeed('blog', 'AsyncAPI Initiative Blog RSS Feed', 'AsyncAPI Initiative Blog', 'rss.xml'); + await buildLlmsTxt(); + await buildLlmsFull(); await buildCaseStudiesList('config/casestudies', resolve(currentDirPath, '../config', 'case-studies.json')); await buildAdoptersList(); const financeDir = resolve('.', 'config', 'finance'); diff --git a/tests/scripts/build-llms-full.test.ts b/tests/scripts/build-llms-full.test.ts new file mode 100644 index 000000000000..855fe2954fbb --- /dev/null +++ b/tests/scripts/build-llms-full.test.ts @@ -0,0 +1,39 @@ +describe('buildLlmsFull', () => { + afterEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + it('should write llms-full.txt with links', async () => { + // Mock fs/promises.writeFile and posts.json before requiring module + jest.doMock('fs/promises', () => ({ + writeFile: jest.fn().mockResolvedValue(undefined) + })); + + jest.doMock( + '../../../../config/posts.json', + () => ({ + default: { + docs: [{ slug: '/docs/getting-started', title: 'Getting Started' }], + blog: [{ slug: '/blog/1', title: 'First Post', date: '2024-01-01' }] + } + }), + { virtual: true } + ); + + const mod = await import('../../scripts/build-llms-full'); + const buildLlmsFull = (mod as any).buildLlmsFull ?? (mod as any).default; + + const fsPromises = await import('fs/promises'); + + await expect(buildLlmsFull()).resolves.toBeUndefined(); + + const writeMock = (fsPromises as any).writeFile as jest.Mock; + + expect(writeMock).toHaveBeenCalledTimes(1); + expect(writeMock.mock.calls[0][0]).toBe('./public/llms-full.txt'); + const content = writeMock.mock.calls[0][1] as string; + + expect(content).toContain('Core Specifications'); + }); +}); diff --git a/tests/scripts/build-llms.test.ts b/tests/scripts/build-llms.test.ts new file mode 100644 index 000000000000..02ea8bf81ad8 --- /dev/null +++ b/tests/scripts/build-llms.test.ts @@ -0,0 +1,39 @@ +describe('buildLlmsTxt', () => { + afterEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + }); + + it('should write llms.txt to public directory', async () => { + // Mock fs/promises.writeFile and posts.json before requiring module + jest.doMock('fs/promises', () => ({ + writeFile: jest.fn().mockResolvedValue(undefined) + })); + + jest.doMock( + '../../../../config/posts.json', + () => ({ + default: { + docs: [{ slug: '/docs/getting-started', title: 'Getting Started' }], + blog: [{ slug: '/blog/1', title: 'First Post', date: '2024-01-01' }] + } + }), + { virtual: true } + ); + + const mod = await import('../../scripts/build-llms'); + const buildLlmsTxt = (mod as any).buildLlmsTxt ?? (mod as any).default; + + const fsPromises = await import('fs/promises'); + + await expect(buildLlmsTxt()).resolves.toBeUndefined(); + + const writeMock = (fsPromises as any).writeFile as jest.Mock; + + expect(writeMock).toHaveBeenCalledTimes(1); + expect(writeMock.mock.calls[0][0]).toBe('./public/llms.txt'); + const content = writeMock.mock.calls[0][1] as string; + + expect(content).toContain('Core Resources'); + }); +});