diff --git a/.npmrc.example b/.npmrc.example index 55343a2..66ee9d0 100644 --- a/.npmrc.example +++ b/.npmrc.example @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=YOUR_NPM_AUTOMATION_TOKEN_HERE \ No newline at end of file +//registry.npmjs.org/:_authToken=YOUR_NPM_AUTOMATION_TOKEN_HERE diff --git a/README.md b/README.md index efa1a84..3640485 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,32 @@ -# @llmstxt — `llms.txt`, `llms-full.txt`, and Markdown for agents +# @llmtxt — `llms.txt`, `llms-full.txt`, and Markdown for agents Generate `llms.txt` (table of contents) and `llms-full.txt` (full content) from your app’s pages, and optionally serve **any page as Markdown** via content negotiation (`Accept: text/markdown`). - Standard: [llmstxt.org](https://llmstxt.org) -- Works with: Next.js App Router (`@llmstxt/next`), any Node.js framework (`@llmstxt/core`), and Next.js middleware (`@llmstxt/middleware`) +- Works with: Next.js App Router (`@llmtxt/next`), any Node.js framework (`@llmtxt/core`), and Next.js middleware (`@llmtxt/middleware`) ## Packages | Package | What it does | |---|---| -| [`@llmstxt/core`](./packages/core) | Scan your `app/` directory and generate `llms.txt` + `llms-full.txt` | -| [`@llmstxt/next`](./packages/next) | Next.js App Router route handlers for `/llms.txt` and `/llms-full.txt` | -| [`@llmstxt/middleware`](./packages/middleware) | Next.js middleware: any page responds as Markdown on `Accept: text/markdown` | +| [`@llmtxt/core`](./packages/core) | Scan your `app/` directory and generate `llms.txt` + `llms-full.txt` | +| [`@llmtxt/next`](./packages/next) | Next.js App Router route handlers for `/llms.txt` and `/llms-full.txt` | +| [`@llmtxt/middleware`](./packages/middleware) | Next.js middleware: any page responds as Markdown on `Accept: text/markdown` | +| [`@llmtxt/react`](./packages/react) | Build-time helpers for React SPAs: generate `llms.txt` + `llms-full.txt` from a route list | ## Keywords (SEO) -llms.txt, llms-full.txt, llmstxt, markdown for agents, content negotiation, Next.js, middleware, App Router, AI, LLM, SEO, website indexing, documentation, crawlable content, sitemap alternative +llms.txt, llms-full.txt, llmtxt, markdown for agents, content negotiation, Next.js, middleware, App Router, AI, LLM, SEO, website indexing, documentation, crawlable content, sitemap alternative ## Table of Contents - [Quick Start — Next.js (App Router)](#quick-start--nextjs-app-router) - [Quick Start — Markdown for Agents (Next.js Middleware)](#quick-start--markdown-for-agents-nextjs-middleware) +- [Quick Start — React (Build-time, no backend)](#quick-start--react-build-time-no-backend) - [Quick Start — Other Frameworks](#quick-start--other-frameworks-express-hono-bun-etc) - [How It Works](#how-it-works) -- [API Reference (`@llmstxt/core`)](#api-reference-llmstxtcore) -- [Environment Variables (`@llmstxt/next`)](#environment-variables-llmstxtnext) +- [API Reference (`@llmtxt/core`)](#api-reference-llmtxtcore) +- [Environment Variables (`@llmtxt/next`)](#environment-variables-llmtxtnext) - [Tips for Better Descriptions](#tips-for-better-descriptions) - [Development (this repo)](#development-this-repo) - [License](#license) @@ -36,7 +38,7 @@ llms.txt, llms-full.txt, llmstxt, markdown for agents, content negotiation, Next ### Install ```bash -npm install @llmstxt/next +npm install @llmtxt/next ``` ### Add route handlers @@ -51,12 +53,12 @@ src/app/ **`src/app/llms.txt/route.ts`** (zero-config): ```ts -export { GET } from '@llmstxt/next' +export { GET } from '@llmtxt/next' ``` **`src/app/llms-full.txt/route.ts`** (zero-config): ```ts -import { createLlmsFullTxtHandler } from '@llmstxt/next' +import { createLlmsFullTxtHandler } from '@llmtxt/next' export const GET = createLlmsFullTxtHandler() ``` @@ -64,7 +66,7 @@ Set one of: - `NEXT_PUBLIC_APP_URL` (recommended) - `VERCEL_URL` (usually already set on Vercel) -> Tip: Set `NEXT_PUBLIC_APP_URL` explicitly in local development and production. `@llmstxt/next` will only fall back to `http://localhost:3000` when `NODE_ENV=development`. +> Tip: Set `NEXT_PUBLIC_APP_URL` explicitly in local development and production. `@llmtxt/next` will only fall back to `http://localhost:3000` when `NODE_ENV=development`. Visit: - `/llms.txt` @@ -74,7 +76,7 @@ Visit: ```ts // src/app/llms.txt/route.ts -import { createLlmsTxtHandler } from '@llmstxt/next' +import { createLlmsTxtHandler } from '@llmtxt/next' export const GET = createLlmsTxtHandler({ title: 'My SaaS App', @@ -85,7 +87,7 @@ export const GET = createLlmsTxtHandler({ ```ts // src/app/llms-full.txt/route.ts -import { createLlmsFullTxtHandler } from '@llmstxt/next' +import { createLlmsFullTxtHandler } from '@llmtxt/next' export const GET = createLlmsFullTxtHandler({ fetchTimeoutMs: 8000, @@ -105,14 +107,14 @@ Serve the Markdown version of **any page** when a client sends `Accept: text/mar ### Install ```bash -npm install @llmstxt/middleware +npm install @llmtxt/middleware ``` ### Add middleware ```ts // middleware.ts (project root) -export { middleware, config } from '@llmstxt/middleware' +export { middleware, config } from '@llmtxt/middleware' ``` This returns: @@ -131,14 +133,57 @@ For best Markdown quality in production, plug in `@mozilla/readability` + `turnd --- +## Quick Start — React (Build-time, no backend) + +For React SPAs (Vite/CRA/React Router), generate the files at build time and ship them from `public/`. + +```bash +npm install -D @llmtxt/react +``` + +Create a route list: + +```ts +// llmtxt.routes.ts +import type { LlmtxtRoute } from '@llmtxt/react' + +export const routes: LlmtxtRoute[] = [ + { path: '/', title: 'Home', description: 'What this site is about.' }, + { path: '/docs', title: 'Docs' }, +] +``` + +Generate: + +```ts +// scripts/generate-llms.ts +import path from 'path' +import { writeLlmsFiles } from '@llmtxt/react' +import { routes } from '../llmtxt.routes' + +await writeLlmsFiles({ + routes, + baseUrl: process.env.PUBLIC_SITE_URL!, // e.g. https://example.com + outDir: path.join(process.cwd(), 'public'), +}) +``` + +Run it after deploy (recommended) or against a preview server: + +```bash +PUBLIC_SITE_URL=https://example.com node scripts/generate-llms.ts +``` + +--- + ## Quick Start — Other Frameworks (Express, Hono, Bun, etc.) ```bash -npm install @llmstxt/core +npm install @llmtxt/core ``` ```ts -import { generateLlmsTxt, generateLlmsFullTxt } from '@llmstxt/core' +import { generateLlmsTxt, generateLlmsFullTxt } from '@llmtxt/core' import path from 'path' app.get('/llms.txt', async (req, res) => { @@ -167,7 +212,7 @@ app.get('/llms-full.txt', async (req, res) => { ## How It Works ### `llms.txt` (Table of contents) -`@llmstxt/core` scans your `app/` directory for `page.tsx`/`page.jsx`/`page.ts`/`page.js` files and produces a structured +`@llmtxt/core` scans your `app/` directory for `page.tsx`/`page.jsx`/`page.ts`/`page.js` files and produces a structured index of links + descriptions. LLMs use this like a sitemap to understand what your site covers. Example output: @@ -183,14 +228,14 @@ Example output: - [Advanced Usage](https://example.com/blog/advanced): Deep-dives and recipes --- -*Generated by @llmstxt/core · 2025-01-01T00:00:00.000Z · 12 pages* +*Generated by @llmtxt/core · 2025-01-01T00:00:00.000Z · 12 pages* ``` ### `llms-full.txt` (Full content) Same scan, but also fetches and converts each page to Markdown (or plain text by default). This produces one big file containing your site’s content for ingestion. ### `Accept: text/markdown` (Markdown for agents) -`@llmstxt/middleware` adds content negotiation to your Next.js app: when a client requests any URL with `Accept: text/markdown`, it re-fetches that page as HTML, converts it to Markdown, and returns it with `Content-Type: text/markdown`, `Vary: Accept`, and `x-markdown-tokens`. +`@llmtxt/middleware` adds content negotiation to your Next.js app: when a client requests any URL with `Accept: text/markdown`, it re-fetches that page as HTML, converts it to Markdown, and returns it with `Content-Type: text/markdown`, `Vary: Accept`, and `x-markdown-tokens`. ## Why this helps SEO (and AI discoverability) @@ -199,7 +244,7 @@ These files are primarily for **LLM discoverability**: they provide a structured --- -## API Reference (`@llmstxt/core`) +## API Reference (`@llmtxt/core`) ### `generateLlmsTxt(options)` Generates `llms.txt` as a string. @@ -235,14 +280,14 @@ Generates `llms-full.txt` as a string (fetches pages and converts HTML→text/Ma --- -## Environment Variables (`@llmstxt/next`) +## Environment Variables (`@llmtxt/next`) | Variable | Purpose | |---|---| | `NEXT_PUBLIC_APP_URL` | Your public app root URL, e.g. `https://example.com` — recommended for production and local dev | | `VERCEL_URL` | Automatically set by Vercel; used as a fallback if `NEXT_PUBLIC_APP_URL` is missing | -> Note: When `NODE_ENV=development`, `@llmstxt/next` will default to `http://localhost:3000` if neither variable is set. For predictable results, always set `NEXT_PUBLIC_APP_URL` in `.env.local` or your deployment environment. +> Note: When `NODE_ENV=development`, `@llmtxt/next` will default to `http://localhost:3000` if neither variable is set. For predictable results, always set `NEXT_PUBLIC_APP_URL` in `.env.local` or your deployment environment. --- @@ -250,16 +295,16 @@ Generates `llms-full.txt` as a string (fetches pages and converts HTML→text/Ma This repository publishes three npm packages: -- `@llmstxt/core` — framework-agnostic generator for `llms.txt` and `llms-full.txt` -- `@llmstxt/next` — Next.js App Router route handlers for `llms.txt` and `llms-full.txt` -- `@llmstxt/middleware` — Next.js middleware that serves Markdown on `Accept: text/markdown` +- `@llmtxt/core` — framework-agnostic generator for `llms.txt` and `llms-full.txt` +- `@llmtxt/next` — Next.js App Router route handlers for `llms.txt` and `llms-full.txt` +- `@llmtxt/middleware` — Next.js middleware that serves Markdown on `Accept: text/markdown` Install only the packages you need: ```bash -npm install @llmstxt/core -npm install @llmstxt/next -npm install @llmstxt/middleware +npm install @llmtxt/core +npm install @llmtxt/next +npm install @llmtxt/middleware ``` See each package README for the full option reference. diff --git a/examples/README.md b/examples/README.md index 9902893..427dd05 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,8 +2,8 @@ This folder contains two example projects for local testing of the monorepo packages: -- `basic-test` — a simple TypeScript script that imports `@llmstxt/core` and generates `llms.txt` locally. -- `next-app-test` — a minimal Next.js App Router project that consumes `@llmstxt/next` and exposes `/llms.txt` and `/llms-full.txt`. +- `basic-test` — a simple TypeScript script that imports `@llmtxt/core` and generates `llms.txt` locally. +- `next-app-test` — a minimal Next.js App Router project that consumes `@llmtxt/next` and exposes `/llms.txt` and `/llms-full.txt`. ## Getting started diff --git a/examples/my-react-app/.gitignore b/examples/my-react-app/.gitignore new file mode 100644 index 0000000..703f5d2 --- /dev/null +++ b/examples/my-react-app/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Generated llms files (generated by scripts/generate-llms.js) +public/llms.txt +public/llms-full.txt + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/my-react-app/README.md b/examples/my-react-app/README.md new file mode 100644 index 0000000..48de779 --- /dev/null +++ b/examples/my-react-app/README.md @@ -0,0 +1,145 @@ +# React Demo App with @llmtxt/react + +A comprehensive example React application demonstrating how to use `@llmtxt/react` to generate AI-friendly documentation from your Vite + React SPA. + +## Features + +- **11+ Demo Pages** including Home, Docs, Pricing, Blog, and more +- **React Router** for full SPA navigation +- **Responsive Design** with modern CSS styling +- **@llmtxt/react Integration** for automatic documentation generation + +## Pages Included + +### Core Pages + +- **Home** (`/`) - Welcome and feature overview +- **Documentation** (`/docs`) - Documentation index + - Getting Started (`/docs/getting-started`) + - API Reference (`/docs/api`) +- **Features** (`/features`) - Feature showcase +- **Pricing** (`/pricing`) - Pricing plans +- **Blog** (`/blog`) - Blog post listing + - Individual Posts (`/blog/:slug`) +- **About** (`/about`) - About page +- **Contact** (`/contact`) - Contact form +- **FAQ** (`/faq`) - Frequently asked questions + +## Setup + +### 1. Install Dependencies + +```bash +npm install +``` + +### 2. Development Server + +```bash +npm run dev +``` + +Then visit `http://localhost:5173` + +### 3. Generate Documentation + +After deploying your app, generate the documentation: + +```bash +PUBLIC_SITE_URL=http://localhost:5173 npm run generate:llms +``` + +> Note: Because this is a client-rendered SPA, plain fetch() returns `index.html` for every route. +> For “real” `llms-full.txt` content, use prerender/SSR HTML, provide `route.markdown`, or render with Playwright: +> +> ```bash +> npm i -D playwright +> LLMTXT_RENDER=playwright PUBLIC_SITE_URL=http://localhost:5173 npm run generate:llms +> ``` + +This creates: + +- `public/llms.txt` - Lightweight documentation index +- `public/llms-full.txt` - Full documentation with page content + +## How It Works + +1. **Route Discovery**: + - Static routes are auto-scanned from `src/pages/**` via `scanPagesDirForRoutes()` + - Dynamic routes (e.g. `/blog/:slug`) must be expanded into concrete paths in the generator script + +2. **Generation Script** (`scripts/generate-llms.js`): + - Fetches each route from your running app + - Converts HTML to Markdown + - Writes documentation files to `public/` + +3. **Build Process**: + - `npm run build` runs Vite build + generation script + - Files are included in your deployment + +## File Structure + +``` +my-react-app/ +├── src/ +│ ├── pages/ # Individual page components +│ ├── App.jsx # Main app with routing +│ ├── App.css # Styling +│ └── main.jsx # Entry point +├── scripts/ +│ └── generate-llms.js # Documentation generator +├── src/pages/ # Convention-based pages (used for auto-scan) +└── package.json # Dependencies +``` + +## Customization + +### Add a New Page + +1. Create a component in `src/pages/MyPage.jsx` +2. Add route to `App.jsx` +3. Add/update routes in `scripts/generate-llms.js` + +### Customize Documentation Generation + +Edit `scripts/generate-llms.js`: + +```js +await writeLlmsFiles({ + routes, + baseUrl: process.env.PUBLIC_SITE_URL || 'http://localhost:5173', + outDir: path.join(process.cwd(), 'public'), + fetchTimeoutMs: 5000, // Per-page timeout + // htmlToMarkdown: customConverter // Optional custom converter +}) +``` + +## Viewing Generated Documentation + +After running `npm run generate:llms`: + +```bash +cat public/llms.txt # Lightweight index +cat public/llms-full.txt # Full documentation +``` + +## Environment Variables + +- `PUBLIC_SITE_URL` - Your app's public URL (used for documentation generation) + +For local development: + +```bash +PUBLIC_SITE_URL=http://localhost:5173 +``` + +For production: + +```bash +PUBLIC_SITE_URL=https://your-app.com +``` + +## Learn More + +- [@llmtxt/react](../../packages/react/) - Package documentation +- [llmstxt.org](https://llmstxt.org) - Format specification diff --git a/examples/my-react-app/eslint.config.js b/examples/my-react-app/eslint.config.js new file mode 100644 index 0000000..ea36dd3 --- /dev/null +++ b/examples/my-react-app/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, +]) diff --git a/examples/my-react-app/index.html b/examples/my-react-app/index.html new file mode 100644 index 0000000..7b228d7 --- /dev/null +++ b/examples/my-react-app/index.html @@ -0,0 +1,13 @@ + + + + + + + my-react-app + + +
+ + + diff --git a/examples/my-react-app/package-lock.json b/examples/my-react-app/package-lock.json new file mode 100644 index 0000000..219fec9 --- /dev/null +++ b/examples/my-react-app/package-lock.json @@ -0,0 +1,2322 @@ +{ + "name": "my-react-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-react-app", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.15.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@llmtxt/react": "file:../../packages/react", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "vite": "^8.0.12" + } + }, + "../../packages/react": { + "name": "@llmtxt/react", + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@llmtxt/react": { + "resolved": "../../packages/react", + "link": true + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", + "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", + "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", + "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", + "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", + "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", + "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", + "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", + "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", + "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", + "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", + "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", + "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", + "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", + "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", + "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.353", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", + "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "dev": true + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-router": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz", + "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz", + "integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.15.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", + "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "dev": true, + "dependencies": { + "@oxc-project/types": "=0.129.0", + "@rolldown/pluginutils": "1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0", + "@rolldown/binding-darwin-arm64": "1.0.0", + "@rolldown/binding-darwin-x64": "1.0.0", + "@rolldown/binding-freebsd-x64": "1.0.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", + "@rolldown/binding-linux-arm64-gnu": "1.0.0", + "@rolldown/binding-linux-arm64-musl": "1.0.0", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0", + "@rolldown/binding-linux-s390x-gnu": "1.0.0", + "@rolldown/binding-linux-x64-gnu": "1.0.0", + "@rolldown/binding-linux-x64-musl": "1.0.0", + "@rolldown/binding-openharmony-arm64": "1.0.0", + "@rolldown/binding-wasm32-wasi": "1.0.0", + "@rolldown/binding-win32-arm64-msvc": "1.0.0", + "@rolldown/binding-win32-x64-msvc": "1.0.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", + "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", + "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", + "dev": true, + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/examples/my-react-app/package.json b/examples/my-react-app/package.json new file mode 100644 index 0000000..492b204 --- /dev/null +++ b/examples/my-react-app/package.json @@ -0,0 +1,30 @@ +{ + "name": "my-react-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && npm run generate:llms", + "generate:llms": "node scripts/generate-llms.js", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-router-dom": "^7.15.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@llmtxt/react": "file:../../packages/react", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "vite": "^8.0.12" + } +} \ No newline at end of file diff --git a/examples/my-react-app/public/favicon.svg b/examples/my-react-app/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/examples/my-react-app/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/my-react-app/public/icons.svg b/examples/my-react-app/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/examples/my-react-app/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/my-react-app/scripts/generate-llms.js b/examples/my-react-app/scripts/generate-llms.js new file mode 100644 index 0000000..e2a2ba4 --- /dev/null +++ b/examples/my-react-app/scripts/generate-llms.js @@ -0,0 +1,52 @@ +import path from 'path' +import { scanPagesDirForRoutes, writeLlmsFiles } from '@llmtxt/react' + +const baseUrl = process.env.PUBLIC_SITE_URL || 'http://localhost:5173' +const outDir = path.join(process.cwd(), 'public') +const renderMode = (process.env.LLMTXT_RENDER || '').toLowerCase() +const pagesDir = path.join(process.cwd(), 'src/pages') + +console.log('Generating llms.txt files...') +console.log(` Base URL: ${baseUrl}`) +console.log(` Output: ${outDir}`) +console.log(` Pages dir: ${pagesDir}`) + +async function fetchHtmlWithPlaywright(url, timeoutMs) { + const { chromium } = await import('playwright') + const browser = await chromium.launch() + try { + const page = await browser.newPage() + page.setDefaultNavigationTimeout(timeoutMs) + await page.goto(url, { waitUntil: 'networkidle' }) + return await page.content() + } finally { + await browser.close() + } +} + +const scannedRoutes = await scanPagesDirForRoutes({ pagesDir, skipDynamic: true }) +console.log(` Scanned routes: ${scannedRoutes.length}`) + +await writeLlmsFiles({ + routes: [ + ...scannedRoutes, + // Dynamic routes must be expanded into real paths. + { path: '/blog/introducing-v2', title: 'Introducing v2', description: 'Major release announcement' }, + { path: '/blog/best-practices', title: 'Best Practices', description: 'Development best practices' }, + { path: '/blog/performance-tips', title: 'Performance Tips', description: 'Tips for large sites' }, + { path: '/blog/integrations', title: 'New Integrations', description: 'New integrations available' }, + ], + baseUrl, + outDir, + title: 'React Demo App', + summary: 'Comprehensive demo React application showcasing @llmtxt/react integration', + + // React SPAs are client-rendered. Plain fetch() returns index.html for every route. + // To capture real rendered HTML, set: LLMTXT_RENDER=playwright and install playwright. + fetchHtml: + renderMode === 'playwright' + ? (url, timeoutMs) => fetchHtmlWithPlaywright(url, timeoutMs) + : undefined, +}) + +console.log('✓ Generated llms.txt and llms-full.txt') diff --git a/examples/my-react-app/src/App.css b/examples/my-react-app/src/App.css new file mode 100644 index 0000000..d288421 --- /dev/null +++ b/examples/my-react-app/src/App.css @@ -0,0 +1,850 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary: #0066cc; + --primary-dark: #004499; + --secondary: #666; + --light: #f5f5f5; + --border: #ddd; + --text: #333; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + line-height: 1.6; + color: var(--text); + background: #fff; +} + +.app { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Header & Navigation */ +.header { + background: var(--text); + color: white; + padding: 1rem 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.nav { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + display: flex; + align-items: center; + justify-content: space-between; +} + +.logo { + font-size: 1.5rem; + font-weight: bold; + color: white; + text-decoration: none; + margin-right: 3rem; +} + +.nav-links { + display: flex; + list-style: none; + gap: 2rem; + flex: 1; +} + +.nav-links a { + color: white; + text-decoration: none; + font-size: 0.95rem; + transition: opacity 0.2s; +} + +.nav-links a:hover { + opacity: 0.7; +} + +/* Main Content */ +.main-content { + flex: 1; + max-width: 1200px; + width: 100%; + margin: 0 auto; + padding: 2rem; +} + +.page { + animation: fadeIn 0.3s ease-in; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.page h1 { + font-size: 2.5rem; + margin-bottom: 1rem; + color: var(--text); +} + +.page h2 { + font-size: 1.8rem; + margin-top: 2rem; + margin-bottom: 1rem; + color: var(--text); +} + +.page h3 { + font-size: 1.3rem; + margin-top: 1.5rem; + margin-bottom: 0.8rem; + color: var(--text); +} + +.subtitle { + font-size: 1.1rem; + color: var(--secondary); + margin-bottom: 2rem; +} + +.page p { + margin-bottom: 1rem; + line-height: 1.8; +} + +.page section { + margin-bottom: 3rem; +} + +/* Hero Section */ +.hero { + text-align: center; + padding: 3rem 0; +} + +.hero h1 { + font-size: 3rem; + margin-bottom: 1rem; +} + +.hero p { + font-size: 1.2rem; + color: var(--secondary); + margin-bottom: 2rem; +} + +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +/* Buttons */ +.btn { + padding: 0.8rem 1.5rem; + border: none; + border-radius: 4px; + font-size: 1rem; + cursor: pointer; + text-decoration: none; + display: inline-block; + transition: all 0.2s; + font-weight: 500; +} + +.btn-primary { + background: var(--primary); + color: white; +} + +.btn-primary:hover { + background: var(--primary-dark); +} + +.btn-secondary { + background: var(--light); + color: var(--text); + border: 1px solid var(--border); +} + +.btn-secondary:hover { + background: #e0e0e0; +} + +/* Grid Layouts */ +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin: 2rem 0; +} + +.card { + padding: 1.5rem; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--light); + transition: transform 0.2s, box-shadow 0.2s; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.card h3 { + margin-top: 0; + font-size: 1.2rem; +} + +/* Features Preview */ +.features-preview { + margin: 3rem 0; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin: 2rem 0; +} + +.feature-card { + padding: 2rem; + border: 1px solid var(--border); + border-radius: 8px; + text-align: center; + transition: all 0.3s; +} + +.feature-card:hover { + border-color: var(--primary); + box-shadow: 0 4px 12px rgba(0, 102, 204, 0.1); +} + +.feature-icon { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.feature-card h3 { + margin-top: 0; +} + +/* CTA Section */ +.cta-section { + background: var(--light); + padding: 3rem; + border-radius: 8px; + text-align: center; + margin: 3rem 0; +} + +.cta-section h2 { + margin-top: 0; +} + +/* Docs Grid */ +.docs-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin: 2rem 0; +} + +.doc-card { + padding: 1.5rem; + border: 1px solid var(--border); + border-radius: 8px; + transition: all 0.2s; +} + +.doc-card:hover { + border-color: var(--primary); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.doc-card a { + text-decoration: none; + color: inherit; +} + +.doc-card h2 { + margin-top: 0; + color: var(--primary); + font-size: 1.3rem; +} + +.doc-section { + background: var(--light); + padding: 2rem; + border-radius: 8px; +} + +/* Code Blocks */ +pre { + background: #2d2d2d; + color: #f8f8f2; + padding: 1rem; + border-radius: 4px; + overflow-x: auto; + margin: 1rem 0; + font-family: 'Courier New', monospace; + font-size: 0.9rem; +} + +code { + font-family: 'Courier New', monospace; + background: rgba(0, 0, 0, 0.05); + padding: 0.2rem 0.5rem; + border-radius: 3px; + font-size: 0.9em; +} + +pre code { + background: none; + padding: 0; +} + +/* Pricing */ +.pricing-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin: 2rem 0; +} + +.pricing-card { + padding: 2rem; + border: 1px solid var(--border); + border-radius: 8px; + text-align: center; + transition: all 0.3s; +} + +.pricing-card.highlighted { + border: 2px solid var(--primary); + box-shadow: 0 4px 12px rgba(0, 102, 204, 0.2); +} + +.pricing-card h2 { + margin-top: 0; + font-size: 1.5rem; +} + +.price { + font-size: 2.5rem; + font-weight: bold; + color: var(--primary); + margin: 1rem 0; +} + +.features-list { + list-style: none; + text-align: left; + margin: 2rem 0; +} + +.features-list li { + padding: 0.5rem 0; +} + +.faq-pricing { + background: var(--light); + padding: 2rem; + border-radius: 8px; + margin-top: 3rem; +} + +.faq-item { + margin-bottom: 1.5rem; +} + +.faq-item h3 { + margin-top: 0; +} + +/* Blog */ +.blog-list { + display: grid; + gap: 2rem; +} + +.blog-item { + padding: 1.5rem; + border: 1px solid var(--border); + border-radius: 8px; + transition: all 0.2s; +} + +.blog-item:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.blog-item h2 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +.blog-item a { + text-decoration: none; + color: inherit; +} + +.blog-meta { + display: flex; + gap: 1rem; + color: var(--secondary); + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.date { + font-weight: 500; +} + +.excerpt { + color: var(--secondary); + margin-bottom: 1rem; +} + +.read-more { + color: var(--primary); + text-decoration: none; + font-weight: 500; +} + +.read-more:hover { + text-decoration: underline; +} + +/* Blog Post */ +.blog-post { + max-width: 800px; + margin: 0 auto; +} + +.post-meta { + display: flex; + gap: 2rem; + color: var(--secondary); + margin-bottom: 2rem; + font-size: 0.95rem; +} + +.post-content { + line-height: 1.8; +} + +.post-content h3 { + margin-top: 2rem; +} + +.post-content ul { + margin-left: 1.5rem; + margin-bottom: 1rem; +} + +.post-content li { + margin-bottom: 0.5rem; +} + +/* Contact */ +.contact-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; + margin: 2rem 0; +} + +.contact-info { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.info-item h3 { + margin-top: 0; +} + +.contact-form { + background: var(--light); + padding: 2rem; + border-radius: 8px; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 0.8rem; + border: 1px solid var(--border); + border-radius: 4px; + font-family: inherit; + font-size: 1rem; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.1); +} + +/* FAQ */ +.faq-container { + display: grid; + gap: 1rem; + margin: 2rem 0; +} + +details { + padding: 1rem; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; +} + +details[open] { + background: var(--light); +} + +summary { + font-weight: 500; + user-select: none; +} + +summary:hover { + color: var(--primary); +} + +.faq-answer { + margin-top: 1rem; + color: var(--secondary); + padding-top: 1rem; + border-top: 1px solid var(--border); +} + +.faq-cta { + background: var(--light); + padding: 2rem; + border-radius: 8px; + text-align: center; + margin-top: 3rem; +} + +/* Tables */ +table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; +} + +th, +td { + padding: 0.8rem; + text-align: left; + border-bottom: 1px solid var(--border); +} + +th { + background: var(--light); + font-weight: 600; +} + +tr:hover { + background: rgba(0, 0, 0, 0.02); +} + +/* Lists */ +ul, +ol { + margin-left: 1.5rem; + margin-bottom: 1rem; +} + +li { + margin-bottom: 0.5rem; +} + +a { + color: var(--primary); +} + +a:hover { + text-decoration: underline; +} + +/* Footer */ +.footer { + background: var(--text); + color: white; + padding: 2rem; + text-align: center; + margin-top: auto; +} + +.footer p { + margin-bottom: 1rem; +} + +.footer-links { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +.footer-links a { + color: white; + text-decoration: none; +} + +.footer-links a:hover { + text-decoration: underline; +} + +/* Responsive */ +@media (max-width: 768px) { + .nav-links { + gap: 1rem; + font-size: 0.9rem; + } + + .logo { + margin-right: 1rem; + } + + .page h1 { + font-size: 1.8rem; + } + + .hero h1 { + font-size: 2rem; + } + + .cta-buttons { + flex-direction: column; + } + + .btn { + width: 100%; + text-align: center; + } + + .contact-container { + grid-template-columns: 1fr; + } + + .main-content { + padding: 1rem; + } + + .grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 480px) { + .nav { + padding: 0 1rem; + } + + .nav-links { + gap: 0.5rem; + font-size: 0.8rem; + } + + .page h1 { + font-size: 1.5rem; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + &>div { + flex: 1 1 0; + padding: 32px; + + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + + &::after { + right: 0; + border-right-color: var(--border); + } +} \ No newline at end of file diff --git a/examples/my-react-app/src/App.jsx b/examples/my-react-app/src/App.jsx new file mode 100644 index 0000000..03e129d --- /dev/null +++ b/examples/my-react-app/src/App.jsx @@ -0,0 +1,63 @@ +import { BrowserRouter, Routes, Route, Link } from 'react-router-dom' +import './App.css' +import Home from './pages' +import Docs from './pages/docs' +import DocsGettingStarted from './pages/docs/getting-started' +import DocsAPI from './pages/docs/api' +import Features from './pages/features' +import Pricing from './pages/pricing' +import Blog from './pages/blog' +import BlogPost from './pages/blog/[slug]' +import About from './pages/about' +import Contact from './pages/contact' +import FAQ from './pages/faq' + +function App() { + return ( + +
+
+ +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+ + +
+
+ ) +} + +export default App diff --git a/examples/my-react-app/src/assets/hero.png b/examples/my-react-app/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/examples/my-react-app/src/assets/hero.png differ diff --git a/examples/my-react-app/src/assets/react.svg b/examples/my-react-app/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/examples/my-react-app/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/my-react-app/src/assets/vite.svg b/examples/my-react-app/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/examples/my-react-app/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/examples/my-react-app/src/index.css b/examples/my-react-app/src/index.css new file mode 100644 index 0000000..2c84af0 --- /dev/null +++ b/examples/my-react-app/src/index.css @@ -0,0 +1,111 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +body { + margin: 0; +} + +#root { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} diff --git a/examples/my-react-app/src/main.jsx b/examples/my-react-app/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/examples/my-react-app/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/examples/my-react-app/src/pages/about.jsx b/examples/my-react-app/src/pages/about.jsx new file mode 100644 index 0000000..fe3da7d --- /dev/null +++ b/examples/my-react-app/src/pages/about.jsx @@ -0,0 +1,12 @@ +export default function About() { + return ( +
+

About Us

+

+ This demo app exists to show how to generate llms.txt and{' '} + llms-full.txt for a React SPA using @llmtxt/react. +

+
+ ) +} + diff --git a/examples/my-react-app/src/pages/blog/[slug].jsx b/examples/my-react-app/src/pages/blog/[slug].jsx new file mode 100644 index 0000000..5c80366 --- /dev/null +++ b/examples/my-react-app/src/pages/blog/[slug].jsx @@ -0,0 +1,153 @@ +import { useParams } from 'react-router-dom' + +export default function BlogPost() { + const { slug } = useParams() + + const posts = { + 'introducing-v2': { + title: 'Introducing v2', + date: 'May 12, 2026', + author: 'John Doe', + content: `We're excited to announce the release of @llmtxt/react v2! This major update brings significant improvements and new features. + +## What's New + +### Better Performance +We've optimized the HTML to Markdown conversion process, making it 50% faster on average. Large documentation sites now generate in seconds instead of minutes. + +### Enhanced Configuration +New options allow you to customize how your documentation is generated. Set per-route timeouts, provide custom converters, and more. + +### Improved Error Handling +Better error messages and fallback strategies ensure your build process is reliable and maintainable. + +## Migration Guide + +Upgrading from v1 is easy. In most cases, your existing configuration will work without any changes. See our migration guide for details. + +## Thank You + +Special thanks to our community for feedback and contributions that made this release possible!`, + }, + 'best-practices': { + title: 'Best Practices for Documentation', + date: 'May 5, 2026', + author: 'Jane Smith', + content: `Writing good documentation is an art and a science. Here are our best practices for creating documentation that works well with AI language models. + +## Clear, Concise Content + +Write in short paragraphs with clear headings. AI models perform better when content is well-structured and easy to parse. + +## Use Descriptive Titles + +Page titles should clearly describe what the page is about. This helps both humans and AI understand your content at a glance. + +## Include Code Examples + +Provide real, working code examples. Documentation without examples is harder for AI to understand and learn from. + +## Maintain Consistency + +Use consistent terminology and formatting throughout your documentation. This improves comprehension for both humans and AI. + +## Regular Updates + +Keep your documentation up-to-date with your latest features. Outdated documentation can mislead AI models.`, + }, + 'performance-tips': { + title: 'Performance Tips for Large Sites', + date: 'April 28, 2026', + author: 'Mike Johnson', + content: `If you have a large site with hundreds or thousands of pages, here are some tips to optimize your documentation generation. + +## Use Selective Routes + +You don't need to document every route. Focus on user-facing pages and skip admin pages, API routes, and internal tools. + +## Set Appropriate Timeouts + +Large pages or slow servers may need higher timeouts. Set \`fetchTimeoutMs\` based on your page load times. + +## Parallel Generation + +Generate documentation in parallel across multiple processes or machines for faster builds. + +## Caching Strategy + +Cache frequently accessed resources to avoid redundant fetches during development.`, + }, + integrations: { + title: 'New Integrations Available', + date: 'April 20, 2026', + author: 'Sarah Williams', + content: `We're thrilled to announce new integrations that extend the capabilities of @llmtxt/react. + +## Available Integrations + +### Stripe +Connect your Stripe account to document your payment integrations and pricing pages automatically. + +### Slack +Send documentation updates to your team's Slack channel whenever your docs are regenerated. + +### GitHub +Automatically commit generated documentation files to your GitHub repository. + +### SendGrid +Generate and email updated documentation to stakeholders. + +### Datadog +Monitor the performance of your documentation generation process. + +### Sentry +Track errors that occur during documentation generation and deployment. + +More integrations are coming soon. Let us know what you'd like to see next!`, + }, + } + + const post = posts[slug] + + if (!post) { + return ( +
+

Post not found

+
+ ) + } + + return ( +
+
+

{post.title}

+
+ {post.date} + By {post.author} +
+
+ {post.content.split('\n\n').map((paragraph, idx) => { + if (paragraph.startsWith('#')) { + const level = paragraph.match(/^#+/)[0].length + const text = paragraph.replace(/^#+\s/, '') + const HeadingTag = `h${level + 1}` + return {text} + } + if (paragraph.startsWith('-')) { + const items = paragraph.split('\n').map(line => line.replace(/^-\s/, '')) + return ( +
    + {items.map((item, i) => ( +
  • {item}
  • + ))} +
+ ) + } + return

{paragraph}

+ })} +
+
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/blog/index.jsx b/examples/my-react-app/src/pages/blog/index.jsx new file mode 100644 index 0000000..288c223 --- /dev/null +++ b/examples/my-react-app/src/pages/blog/index.jsx @@ -0,0 +1,55 @@ +import { Link } from 'react-router-dom' + +export default function Blog() { + const posts = [ + { + slug: 'introducing-v2', + title: 'Introducing v2', + date: 'May 12, 2026', + excerpt: 'Major release with new features and improvements for better documentation generation', + }, + { + slug: 'best-practices', + title: 'Best Practices for Documentation', + date: 'May 5, 2026', + excerpt: 'Tips and tricks for writing great documentation that works well with LLMs', + }, + { + slug: 'performance-tips', + title: 'Performance Tips for Large Sites', + date: 'April 28, 2026', + excerpt: 'Optimize your documentation generation for large React applications', + }, + { + slug: 'integrations', + title: 'New Integrations Available', + date: 'April 20, 2026', + excerpt: 'Connect with Stripe, Slack, GitHub, and more from your documentation', + }, + ] + + return ( +
+

Blog

+

Latest news, tips, and updates

+ +
+ {posts.map(post => ( +
+ +

{post.title}

+ +
+ {post.date} +
+

{post.excerpt}

+ + Read More → + +
+ ))} +
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/contact.jsx b/examples/my-react-app/src/pages/contact.jsx new file mode 100644 index 0000000..6997ab8 --- /dev/null +++ b/examples/my-react-app/src/pages/contact.jsx @@ -0,0 +1,9 @@ +export default function Contact() { + return ( +
+

Contact

+

Email: hello@example.com

+
+ ) +} + diff --git a/examples/my-react-app/src/pages/docs/api.jsx b/examples/my-react-app/src/pages/docs/api.jsx new file mode 100644 index 0000000..0409655 --- /dev/null +++ b/examples/my-react-app/src/pages/docs/api.jsx @@ -0,0 +1,43 @@ +export default function DocsAPI() { + return ( +
+

API Reference

+ +
+

writeLlmsFiles(options)

+

Generates llms.txt and llms-full.txt files.

+ +

Common options

+ +
+ +
+

scanPagesDirForRoutes(options)

+

+ Convention-based route discovery for projects that keep page components in{' '} + src/pages/**. Dynamic files like [slug].tsx are skipped by default. +

+
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/docs/getting-started.jsx b/examples/my-react-app/src/pages/docs/getting-started.jsx new file mode 100644 index 0000000..2b7aa23 --- /dev/null +++ b/examples/my-react-app/src/pages/docs/getting-started.jsx @@ -0,0 +1,63 @@ +export default function DocsGettingStarted() { + return ( +
+

Getting Started with @llmtxt/react

+ +
+

Installation

+

Install the package as a dev dependency:

+
npm install -D @llmtxt/react
+
+ +
+

Step 1: Discover routes

+

+ You can either provide an explicit route list, or (if you follow a pages-folder + convention) auto-scan it. +

+
{`import path from 'path'
+import { scanPagesDirForRoutes } from '@llmtxt/react'
+
+const routes = await scanPagesDirForRoutes({
+  pagesDir: path.join(process.cwd(), 'src/pages'),
+  skipDynamic: true,
+})`}
+

+ Dynamic routes like /blog/:slug must be expanded into concrete paths (e.g.{' '} + /blog/introducing-v2) in your generator script. +

+
+ +
+

Step 2: Create generation script

+

Create scripts/generate-llms.js:

+
{`import path from 'path'
+import { scanPagesDirForRoutes, writeLlmsFiles } from '@llmtxt/react'
+
+const baseUrl = process.env.PUBLIC_SITE_URL || 'http://localhost:5173'
+const outDir = path.join(process.cwd(), 'public')
+const pagesDir = path.join(process.cwd(), 'src/pages')
+
+const routes = await scanPagesDirForRoutes({ pagesDir, skipDynamic: true })
+
+await writeLlmsFiles({
+  routes: [
+    ...routes,
+    // expand dynamic routes here:
+    { path: '/blog/introducing-v2', title: 'Introducing v2' },
+  ],
+  baseUrl,
+  outDir,
+})`}
+
+ +
+

Step 3: Run after build/deploy

+

After deploying your app, run:

+
PUBLIC_SITE_URL=https://example.com npm run generate:llms
+

This will fetch your pages and generate llms.txt and llms-full.txt files.

+
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/docs/index.jsx b/examples/my-react-app/src/pages/docs/index.jsx new file mode 100644 index 0000000..7ae71f5 --- /dev/null +++ b/examples/my-react-app/src/pages/docs/index.jsx @@ -0,0 +1,37 @@ +import { Link } from 'react-router-dom' + +export default function Docs() { + return ( +
+

Documentation

+

Complete documentation and resources for using @llmtxt/react

+ +
+
+ +

Getting Started

+

Quick start guide to set up @llmtxt/react in your project

+ +
+ +
+ +

API Reference

+

Complete API documentation with examples and use cases

+ +
+ +
+

Route discovery

+

Auto-scan `src/pages/**` or provide an explicit route list

+
+ +
+

Dynamic routes

+

Expand `/blog/:slug` into real paths like `/blog/introducing-v2`

+
+
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/faq.jsx b/examples/my-react-app/src/pages/faq.jsx new file mode 100644 index 0000000..ba94f4b --- /dev/null +++ b/examples/my-react-app/src/pages/faq.jsx @@ -0,0 +1,18 @@ +export default function FAQ() { + return ( +
+

FAQ

+ +
+ ) +} + diff --git a/examples/my-react-app/src/pages/features.jsx b/examples/my-react-app/src/pages/features.jsx new file mode 100644 index 0000000..541b235 --- /dev/null +++ b/examples/my-react-app/src/pages/features.jsx @@ -0,0 +1,42 @@ +export default function Features() { + const features = [ + { + title: 'Build-Time Generation', + description: 'Generate documentation files during your build process, no runtime overhead', + icon: '⚡', + }, + { + title: 'Zero Runtime Dependencies', + description: 'No additional packages needed in your production bundle', + icon: '📦', + }, + { + title: 'HTML to Markdown', + description: 'Automatically convert your page HTML to clean Markdown', + icon: '🔄', + }, + { + title: 'Route Discovery', + description: 'Auto-scan src/pages or provide an explicit route list', + icon: '🗺️', + }, + ] + + return ( +
+

Features

+

Powerful capabilities for documentation generation

+ +
+ {features.map((feature, idx) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/index.jsx b/examples/my-react-app/src/pages/index.jsx new file mode 100644 index 0000000..68912d9 --- /dev/null +++ b/examples/my-react-app/src/pages/index.jsx @@ -0,0 +1,51 @@ +import { Link } from 'react-router-dom' + +export default function Home() { + return ( +
+
+

Welcome to React Demo App

+

A comprehensive example showcasing @llmtxt/react integration

+
+ + Get Started + + + Learn More + +
+
+ +
+

What We Offer

+
+
+

📚 Documentation

+

Complete guides and API reference to get you started quickly

+
+
+

⚡ High Performance

+

Optimized for speed with minimal bundle size

+
+
+

🔧 Easy Integration

+

Simple setup process that works with your existing React app

+
+
+

📱 Responsive

+

Works perfectly on desktop, tablet, and mobile devices

+
+
+
+ +
+

Ready to Get Started?

+

Check out our documentation to learn more about @llmtxt/react

+ + Read Documentation + +
+
+ ) +} + diff --git a/examples/my-react-app/src/pages/pricing.jsx b/examples/my-react-app/src/pages/pricing.jsx new file mode 100644 index 0000000..50084a9 --- /dev/null +++ b/examples/my-react-app/src/pages/pricing.jsx @@ -0,0 +1,37 @@ +export default function Pricing() { + const plans = [ + { name: 'Starter', price: 'Free', features: ['Basic generation', 'Community support'] }, + { + name: 'Professional', + price: '$49/mo', + features: ['Unlimited routes', 'Custom converters', 'Priority support'], + highlighted: true, + }, + { name: 'Enterprise', price: 'Custom', features: ['Dedicated support', 'Custom SLAs'] }, + ] + + return ( +
+

Pricing

+

Simple, transparent pricing for everyone

+ +
+ {plans.map((plan, idx) => ( +
+

{plan.name}

+
{plan.price}
+
    + {plan.features.map((feature, i) => ( +
  • ✓ {feature}
  • + ))} +
+
+ ))} +
+
+ ) +} + diff --git a/examples/my-react-app/vite.config.js b/examples/my-react-app/vite.config.js new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/examples/my-react-app/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/nextjs-test/README.md b/examples/nextjs-test/README.md index b0c3d73..65e9945 100644 --- a/examples/nextjs-test/README.md +++ b/examples/nextjs-test/README.md @@ -1,10 +1,10 @@ # LLMsText Next.js Example Application -This example demonstrates all features of the @llmstxt packages including: +This example demonstrates all features of the @llmtxt packages including: -- `@llmstxt/core` for scanning and generating documentation -- `@llmstxt/next` for Next.js route handlers -- `@llmstxt/middleware` for markdown content negotiation +- `@llmtxt/core` for scanning and generating documentation +- `@llmtxt/next` for Next.js route handlers +- `@llmtxt/middleware` for markdown content negotiation ## Features Demonstrated @@ -28,7 +28,7 @@ npm run build NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` -> For local development, put this in `.env.local` so Next.js and `@llmstxt/next` can resolve your public site URL. +> For local development, put this in `.env.local` so Next.js and `@llmtxt/next` can resolve your public site URL. 1. Run the development server: diff --git a/examples/nextjs-test/app/about/page.tsx b/examples/nextjs-test/app/about/page.tsx index 2332e7b..2123469 100644 --- a/examples/nextjs-test/app/about/page.tsx +++ b/examples/nextjs-test/app/about/page.tsx @@ -2,7 +2,7 @@ import { Navigation, Card } from '@/components/widgets'; export const metadata = { title: 'About | LLMsText', - description: 'About the llmstxt project', + description: 'About the llmtxt project', }; export default function About() { @@ -39,15 +39,15 @@ export default function About() {

Our Packages

diff --git a/examples/nextjs-test/app/api-reference/page.tsx b/examples/nextjs-test/app/api-reference/page.tsx index 928863f..6b20105 100644 --- a/examples/nextjs-test/app/api-reference/page.tsx +++ b/examples/nextjs-test/app/api-reference/page.tsx @@ -2,7 +2,7 @@ import { Navigation, CodeBlock } from '@/components/widgets'; export const metadata = { title: 'API Reference | LLMsText', - description: 'API reference for llmstxt packages', + description: 'API reference for llmtxt packages', }; export default function APIReference() { @@ -11,11 +11,11 @@ export default function APIReference() {

API Reference

-

Complete API documentation for all llmstxt packages

+

Complete API documentation for all llmtxt packages

-

@llmstxt/core

+

@llmtxt/core

scanAppDirForPages(options)

@@ -56,7 +56,7 @@ export default function APIReference() {
-

@llmstxt/next

+

@llmtxt/next

createLlmsTxtHandler()

@@ -64,7 +64,7 @@ export default function APIReference() { Creates a Next.js route handler for the /llms.txt endpoint.

@@ -88,7 +88,7 @@ export const GET = createLlmsFullTxtHandler();`}
-

@llmstxt/middleware

+

@llmtxt/middleware

createMarkdownMiddleware(options)

@@ -96,7 +96,7 @@ export const GET = createLlmsFullTxtHandler();`} Creates Next.js middleware that serves markdown for requests with Accept: text/markdown header.

- We're excited to announce llmstxt, a framework-agnostic solution for automatically generating + We're excited to announce llmtxt, a framework-agnostic solution for automatically generating AI-friendly documentation from your Next.js applications.

diff --git a/examples/nextjs-test/app/docs/getting-started/page.tsx b/examples/nextjs-test/app/docs/getting-started/page.tsx index 32a1607..7f95084 100644 --- a/examples/nextjs-test/app/docs/getting-started/page.tsx +++ b/examples/nextjs-test/app/docs/getting-started/page.tsx @@ -2,7 +2,7 @@ import { Card, Navigation, CodeBlock } from '@/components/widgets'; export const metadata = { title: 'Getting Started | Docs', - description: 'Getting started with llmstxt documentation', + description: 'Getting started with llmtxt documentation', }; export default function GettingStarted() { @@ -32,9 +32,9 @@ export default function GettingStarted() {

Installation

- Install the @llmstxt packages using npm: + Install the @llmtxt packages using npm:

- +
@@ -44,7 +44,7 @@ export default function GettingStarted() {

diff --git a/examples/nextjs-test/app/features/page.tsx b/examples/nextjs-test/app/features/page.tsx index e985349..bb6dc77 100644 --- a/examples/nextjs-test/app/features/page.tsx +++ b/examples/nextjs-test/app/features/page.tsx @@ -2,7 +2,7 @@ import { Navigation, FeatureCard, Hero } from '@/components/widgets'; export const metadata = { title: 'Features | LLMsText', - description: 'Features of the llmstxt packages', + description: 'Features of the llmtxt packages', }; export default function Features() { diff --git a/examples/nextjs-test/app/llms-full.txt/route.ts b/examples/nextjs-test/app/llms-full.txt/route.ts index 00adebd..36ef253 100644 --- a/examples/nextjs-test/app/llms-full.txt/route.ts +++ b/examples/nextjs-test/app/llms-full.txt/route.ts @@ -1,3 +1,3 @@ -import { createLlmsFullTxtHandler } from '@llmstxt/next'; +import { createLlmsFullTxtHandler } from '@llmtxt/next'; export const GET = createLlmsFullTxtHandler(); diff --git a/examples/nextjs-test/app/llms.txt/route.ts b/examples/nextjs-test/app/llms.txt/route.ts index 70704f5..dd00145 100644 --- a/examples/nextjs-test/app/llms.txt/route.ts +++ b/examples/nextjs-test/app/llms.txt/route.ts @@ -1,8 +1,8 @@ -import { createLlmsTxtHandler } from '@llmstxt/next'; +import { createLlmsTxtHandler } from '@llmtxt/next'; // Create the handler with optional configuration export const GET = createLlmsTxtHandler({ title: 'LLMsText Example Application', summary: - 'This is a comprehensive example application demonstrating all features of the @llmstxt packages including documentation generation, middleware integration, and AI-friendly content serving.', + 'This is a comprehensive example application demonstrating all features of the @llmtxt packages including documentation generation, middleware integration, and AI-friendly content serving.', }); diff --git a/examples/nextjs-test/app/page.tsx b/examples/nextjs-test/app/page.tsx index 3996d2b..bde776a 100644 --- a/examples/nextjs-test/app/page.tsx +++ b/examples/nextjs-test/app/page.tsx @@ -2,7 +2,7 @@ import { Navigation, Hero, Card } from '@/components/widgets'; export const metadata = { title: 'Home | LLMsText Example App', - description: 'Testing all @llmstxt packages - core, next, and middleware', + description: 'Testing all @llmtxt packages - core, next, and middleware', }; export default function Home() { @@ -21,7 +21,7 @@ export default function Home() { />
diff --git a/examples/nextjs-test/app/pricing/page.tsx b/examples/nextjs-test/app/pricing/page.tsx index 628783c..734f159 100644 --- a/examples/nextjs-test/app/pricing/page.tsx +++ b/examples/nextjs-test/app/pricing/page.tsx @@ -2,7 +2,7 @@ import { Navigation, PricingPlan } from '@/components/widgets'; export const metadata = { title: 'Pricing | LLMsText', - description: 'Pricing plans for llmstxt', + description: 'Pricing plans for llmtxt', }; export default function Pricing() { diff --git a/examples/nextjs-test/middleware.ts b/examples/nextjs-test/middleware.ts index 3dfd188..aaea5bb 100644 --- a/examples/nextjs-test/middleware.ts +++ b/examples/nextjs-test/middleware.ts @@ -1,4 +1,4 @@ -import { createMarkdownMiddleware } from '@llmstxt/middleware'; +import { createMarkdownMiddleware } from '@llmtxt/middleware'; /** * Markdown middleware for serving markdown versions of pages diff --git a/examples/nextjs-test/package-lock.json b/examples/nextjs-test/package-lock.json index 21580b4..7637a83 100644 --- a/examples/nextjs-test/package-lock.json +++ b/examples/nextjs-test/package-lock.json @@ -8,9 +8,9 @@ "name": "nextjs-test", "version": "0.1.0", "dependencies": { - "@llmstxt/core": "file:../../packages/core", - "@llmstxt/middleware": "file:../../packages/middleware", - "@llmstxt/next": "file:../../packages/next", + "@llmtxt/core": "file:../../packages/core", + "@llmtxt/middleware": "file:../../packages/middleware", + "@llmtxt/next": "file:../../packages/next", "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4" @@ -27,7 +27,7 @@ } }, "../../packages/core": { - "name": "@llmstxt/core", + "name": "@llmtxt/core", "version": "0.1.0", "license": "MIT", "engines": { @@ -35,7 +35,7 @@ } }, "../../packages/middleware": { - "name": "@llmstxt/middleware", + "name": "@llmtxt/middleware", "version": "0.1.0", "license": "MIT", "engines": { @@ -46,11 +46,14 @@ } }, "../../packages/next": { - "name": "@llmstxt/next", + "name": "@llmtxt/next", "version": "0.1.0", "license": "MIT", "dependencies": { - "@llmstxt/core": "^0.1.0" + "@llmtxt/core": "^0.1.0" + }, + "devDependencies": { + "@types/node": "^18.19.130" }, "engines": { "node": ">=18.0.0" @@ -1007,15 +1010,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@llmstxt/core": { + "node_modules/@llmtxt/core": { "resolved": "../../packages/core", "link": true }, - "node_modules/@llmstxt/middleware": { + "node_modules/@llmtxt/middleware": { "resolved": "../../packages/middleware", "link": true }, - "node_modules/@llmstxt/next": { + "node_modules/@llmtxt/next": { "resolved": "../../packages/next", "link": true }, diff --git a/examples/nextjs-test/package.json b/examples/nextjs-test/package.json index 54182b5..f6fcf30 100644 --- a/examples/nextjs-test/package.json +++ b/examples/nextjs-test/package.json @@ -9,9 +9,9 @@ "lint": "eslint" }, "dependencies": { - "@llmstxt/core": "file:../../packages/core", - "@llmstxt/next": "file:../../packages/next", - "@llmstxt/middleware": "file:../../packages/middleware", + "@llmtxt/core": "file:../../packages/core", + "@llmtxt/next": "file:../../packages/next", + "@llmtxt/middleware": "file:../../packages/middleware", "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4" @@ -26,4 +26,4 @@ "tailwindcss": "^4", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index ecdc08a..fd9f8ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@llmstxt/monorepo", + "name": "@llmtxt/monorepo", "version": "0.0.0-development", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@llmstxt/monorepo", + "name": "@llmtxt/monorepo", "license": "MIT", "workspaces": [ "packages/*" @@ -1853,18 +1853,22 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@llmstxt/core": { + "node_modules/@llmtxt/core": { "resolved": "packages/core", "link": true }, - "node_modules/@llmstxt/middleware": { + "node_modules/@llmtxt/middleware": { "resolved": "packages/middleware", "link": true }, - "node_modules/@llmstxt/next": { + "node_modules/@llmtxt/next": { "resolved": "packages/next", "link": true }, + "node_modules/@llmtxt/react": { + "resolved": "packages/react", + "link": true + }, "node_modules/@next/env": { "version": "16.2.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", @@ -14453,16 +14457,16 @@ } }, "packages/core": { - "name": "@llmstxt/core", - "version": "0.1.0", + "name": "@llmtxt/core", + "version": "0.1.1", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "packages/middleware": { - "name": "@llmstxt/middleware", - "version": "0.1.0", + "name": "@llmtxt/middleware", + "version": "0.1.1", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -14472,11 +14476,11 @@ } }, "packages/next": { - "name": "@llmstxt/next", - "version": "0.1.0", + "name": "@llmtxt/next", + "version": "0.1.2", "license": "MIT", "dependencies": { - "@llmstxt/core": "^0.1.0" + "@llmtxt/core": "^0.1.0" }, "devDependencies": { "@types/node": "^18.19.130" @@ -14487,6 +14491,14 @@ "peerDependencies": { "next": ">=13.0.0" } + }, + "packages/react": { + "name": "@llmtxt/react", + "version": "0.1.4", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } } }, "dependencies": { @@ -15772,20 +15784,23 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "@llmstxt/core": { + "@llmtxt/core": { "version": "file:packages/core" }, - "@llmstxt/middleware": { + "@llmtxt/middleware": { "version": "file:packages/middleware", "requires": {} }, - "@llmstxt/next": { + "@llmtxt/next": { "version": "file:packages/next", "requires": { - "@llmstxt/core": "^0.1.0", + "@llmtxt/core": "^0.1.0", "@types/node": "^18.19.130" } }, + "@llmtxt/react": { + "version": "file:packages/react" + }, "@next/env": { "version": "16.2.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", diff --git a/package.json b/package.json index f92f1e5..e9986c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@llmstxt/monorepo", + "name": "@llmtxt/monorepo", "private": true, - "description": "Monorepo for @llmstxt (llms.txt, llms-full.txt, and Markdown for agents).", + "description": "Monorepo for @llmtxt (llms.txt, llms-full.txt, and Markdown for agents).", "license": "MIT", "workspaces": [ "packages/*" @@ -45,6 +45,6 @@ } }, "lint-staged": { - "*.ts": "eslint --cache --cache-location .eslintcache --fix" + "packages/**/*.ts": "eslint --cache --cache-location .eslintcache --fix" } } diff --git a/packages/core/README.md b/packages/core/README.md index 61ec7e7..77604cf 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,13 +1,13 @@ -# `@llmstxt/core` +# `@llmtxt/core` Framework-agnostic scanner & generator for `llms.txt` and `llms-full.txt` (see [llmstxt.org](https://llmstxt.org)). -Pairs with [`@llmstxt/next`](https://www.npmjs.com/package/@llmstxt/next) for Next.js route handlers and [`@llmstxt/middleware`](https://www.npmjs.com/package/@llmstxt/middleware) for Markdown content negotiation. +Pairs with [`@llmtxt/next`](https://www.npmjs.com/package/@llmtxt/next) for Next.js route handlers and [`@llmtxt/middleware`](https://www.npmjs.com/package/@llmtxt/middleware) for Markdown content negotiation. ## Install ```bash -npm install @llmstxt/core +npm install @llmtxt/core ``` > Requires Node.js 18+ (uses native `fetch()`). Provide a polyfill for older runtimes. @@ -15,7 +15,7 @@ npm install @llmstxt/core ## Quick start ```ts -import { generateLlmsTxt, generateLlmsFullTxt } from '@llmstxt/core' +import { generateLlmsTxt, generateLlmsFullTxt } from '@llmtxt/core' import path from 'path' const appDir = path.join(process.cwd(), 'src/app') @@ -40,7 +40,7 @@ const llmsFullTxt = await generateLlmsFullTxt({ ```ts import express from 'express' -import { generateLlmsTxt, generateLlmsFullTxt } from '@llmstxt/core' +import { generateLlmsTxt, generateLlmsFullTxt } from '@llmtxt/core' import path from 'path' const app = express() @@ -145,7 +145,7 @@ Inherits all shared options, plus: - [Advanced Usage](https://example.com/blog/advanced): Deep dives and recipes --- -*Generated by @llmstxt/core · 2026-01-01T00:00:00.000Z · 12 pages* +*Generated by @llmtxt/core · 2026-01-01T00:00:00.000Z · 12 pages* ``` ## Example output — `llms-full.txt` @@ -174,7 +174,7 @@ npm install turndown @mozilla/readability jsdom ``` ```ts -import { generateLlmsFullTxt } from '@llmstxt/core' +import { generateLlmsFullTxt } from '@llmtxt/core' import TurndownService from 'turndown' import { Readability } from '@mozilla/readability' import { JSDOM } from 'jsdom' diff --git a/packages/core/package.json b/packages/core/package.json index 86a1eb1..c1764b4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { - "name": "@llmstxt/core", - "version": "0.1.0", + "name": "@llmtxt/core", + "version": "0.1.1", "description": "Framework-agnostic scanner & generator for llms.txt and llms-full.txt.", "license": "MIT", "author": "Recordly", @@ -14,7 +14,7 @@ "llms", "llms.txt", "llms-full.txt", - "llmstxt", + "llmtxt", "ai", "llm", "seo", diff --git a/packages/core/src/generate.ts b/packages/core/src/generate.ts index 590413a..7a65159 100644 --- a/packages/core/src/generate.ts +++ b/packages/core/src/generate.ts @@ -57,7 +57,7 @@ async function fetchWithTimeout( try { const res = await fetch(url, { signal: controller.signal, - headers: { 'User-Agent': '@llmstxt/core' }, + headers: { 'User-Agent': '@llmtxt/core' }, }); if (!res.ok) throw new Error( @@ -103,7 +103,7 @@ export async function generateLlmsTxt( lines.push('---'); lines.push( - `*Generated by @llmstxt/core · ${isoNow()} · ${pages.length} pages*` + `*Generated by @llmtxt/core · ${isoNow()} · ${pages.length} pages*` ); lines.push(''); return lines.join('\n'); @@ -119,7 +119,7 @@ export async function generateLlmsFullTxt( lines.push('# llms-full.txt'); lines.push(''); lines.push( - `*Generated by @llmstxt/core · ${isoNow()} · ${pages.length} pages*` + `*Generated by @llmtxt/core · ${isoNow()} · ${pages.length} pages*` ); lines.push(''); lines.push('---'); diff --git a/packages/core/test/scan.spec.ts b/packages/core/test/scan.spec.ts index 0f8d395..006de69 100644 --- a/packages/core/test/scan.spec.ts +++ b/packages/core/test/scan.spec.ts @@ -4,7 +4,7 @@ import path from 'path'; import { scanAppDirForPages } from '../src'; async function mkTmpDir(): Promise { - return await fs.mkdtemp(path.join(os.tmpdir(), 'llmstxt-core-')); + return await fs.mkdtemp(path.join(os.tmpdir(), 'llmtxt-core-')); } async function writeFile(filePath: string, contents: string): Promise { @@ -12,7 +12,7 @@ async function writeFile(filePath: string, contents: string): Promise { await fs.writeFile(filePath, contents, 'utf8'); } -describe('@llmstxt/core scanAppDirForPages', () => { +describe('@llmtxt/core scanAppDirForPages', () => { it('scans Next.js app pages and extracts descriptions', async () => { const dir = await mkTmpDir(); const appDir = path.join(dir, 'src', 'app'); diff --git a/packages/middleware/README.md b/packages/middleware/README.md index 233af4a..31ca817 100644 --- a/packages/middleware/README.md +++ b/packages/middleware/README.md @@ -1,15 +1,15 @@ -# `@llmstxt/middleware` +# `@llmtxt/middleware` Next.js middleware that serves **any page as Markdown** when the request includes `Accept: text/markdown`. This implements the "Markdown for Agents" content-negotiation pattern: AI agents and LLMs can request a clean Markdown version of any page by sending `Accept: text/markdown`, while browsers continue to receive HTML normally. -Also see [`@llmstxt/next`](https://www.npmjs.com/package/@llmstxt/next) for `llms.txt` and `llms-full.txt` route handlers. +Also see [`@llmtxt/next`](https://www.npmjs.com/package/@llmtxt/next) for `llms.txt` and `llms-full.txt` route handlers. ## Install ```bash -npm install @llmstxt/middleware +npm install @llmtxt/middleware ``` **Peer dependency**: `next >= 13.0.0` @@ -18,7 +18,7 @@ npm install @llmstxt/middleware ```ts // middleware.ts (project root) -export { middleware, config } from '@llmstxt/middleware' +export { middleware, config } from '@llmtxt/middleware' ``` Test it: @@ -52,7 +52,7 @@ generated: "2026-01-01T00:00:00.000Z" ```ts // middleware.ts (project root) -import { createMarkdownMiddleware } from '@llmstxt/middleware' +import { createMarkdownMiddleware } from '@llmtxt/middleware' export const middleware = createMarkdownMiddleware({ contentSignal: 'ai-train=no, search=yes, ai-input=yes', @@ -95,7 +95,7 @@ npm install turndown @mozilla/readability jsdom ``` ```ts -import { createMarkdownMiddleware } from '@llmstxt/middleware' +import { createMarkdownMiddleware } from '@llmtxt/middleware' import TurndownService from 'turndown' import { Readability } from '@mozilla/readability' import { JSDOM } from 'jsdom' diff --git a/packages/middleware/package.json b/packages/middleware/package.json index e128c7d..43f5dad 100644 --- a/packages/middleware/package.json +++ b/packages/middleware/package.json @@ -1,6 +1,6 @@ { - "name": "@llmstxt/middleware", - "version": "0.1.0", + "name": "@llmtxt/middleware", + "version": "0.1.1", "description": "Next.js middleware that serves Markdown when requests send Accept: text/markdown.", "license": "MIT", "author": "Recordly", @@ -11,7 +11,7 @@ }, "homepage": "https://github.com/Muhammad-Hashim/llmstxt#readme", "keywords": [ - "llmstxt", + "llmtxt", "markdown", "markdown for agents", "content negotiation", diff --git a/packages/middleware/src/index.ts b/packages/middleware/src/index.ts index b0e9f69..b866725 100644 --- a/packages/middleware/src/index.ts +++ b/packages/middleware/src/index.ts @@ -301,7 +301,7 @@ export function createMarkdownMiddleware( Cookie: request.headers.get('cookie') ?? '', Authorization: request.headers.get('authorization') ?? '', 'Accept-Language': request.headers.get('accept-language') ?? '', - 'User-Agent': 'llmstxt-middleware/0.1.0', + 'User-Agent': 'llmtxt-middleware/0.1.0', }, redirect: 'manual', }); diff --git a/packages/next/README.md b/packages/next/README.md index 1a00bbf..65c14e7 100644 --- a/packages/next/README.md +++ b/packages/next/README.md @@ -1,13 +1,13 @@ -# `@llmstxt/next` +# `@llmtxt/next` Next.js App Router route handlers for `llms.txt` and `llms-full.txt` (see [llmstxt.org](https://llmstxt.org)). -Also see [`@llmstxt/core`](https://www.npmjs.com/package/@llmstxt/core) (framework-agnostic generator) and [`@llmstxt/middleware`](https://www.npmjs.com/package/@llmstxt/middleware) (Markdown content negotiation). +Also see [`@llmtxt/core`](https://www.npmjs.com/package/@llmtxt/core) (framework-agnostic generator) and [`@llmtxt/middleware`](https://www.npmjs.com/package/@llmtxt/middleware) (Markdown content negotiation). ## Install ```bash -npm install @llmstxt/next +npm install @llmtxt/next ``` **Peer dependency**: `next >= 13.0.0` @@ -46,7 +46,7 @@ For `llms.txt`: **`src/app/llms.txt/route.ts`**: ```ts -export { GET } from '@llmstxt/next' +export { GET } from '@llmtxt/next' ``` For `llms-full.txt`: @@ -54,7 +54,7 @@ For `llms-full.txt`: **`src/app/llms-full.txt/route.ts`**: ```ts -import { createLlmsFullTxtHandler } from '@llmstxt/next' +import { createLlmsFullTxtHandler } from '@llmtxt/next' export const GET = createLlmsFullTxtHandler() ``` @@ -65,7 +65,7 @@ Then visit `/llms.txt` and `/llms-full.txt`. **`src/app/llms.txt/route.ts`**: ```ts -import { createLlmsTxtHandler } from '@llmstxt/next' +import { createLlmsTxtHandler } from '@llmtxt/next' export const GET = createLlmsTxtHandler({ title: 'My App', @@ -78,7 +78,7 @@ export const GET = createLlmsTxtHandler({ **`src/app/llms-full.txt/route.ts`**: ```ts -import { createLlmsFullTxtHandler } from '@llmstxt/next' +import { createLlmsFullTxtHandler } from '@llmtxt/next' export const GET = createLlmsFullTxtHandler({ fetchTimeoutMs: 8000, @@ -116,13 +116,13 @@ All options are optional. `appDir` and `baseUrl` are resolved automatically from Both handlers set `Cache-Control: public, max-age=0, s-maxage=3600, stale-while-revalidate=86400` by default, giving you CDN-level caching with fast revalidation. -## With `@llmstxt/middleware` (optional) +## With `@llmtxt/middleware` (optional) To serve any page as Markdown on `Accept: text/markdown`, add a middleware file: ```ts // middleware.ts (project root) -export { middleware, config } from '@llmstxt/middleware' +export { middleware, config } from '@llmtxt/middleware' ``` -See [`@llmstxt/middleware`](https://www.npmjs.com/package/@llmstxt/middleware) for full options. +See [`@llmtxt/middleware`](https://www.npmjs.com/package/@llmtxt/middleware) for full options. diff --git a/packages/next/package.json b/packages/next/package.json index a2b84d6..7c404f3 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { - "name": "@llmstxt/next", - "version": "0.1.0", + "name": "@llmtxt/next", + "version": "0.1.2", "description": "Next.js App Router route handlers for llms.txt and llms-full.txt.", "license": "MIT", "author": "Recordly", @@ -14,7 +14,7 @@ "llms", "llms.txt", "llms-full.txt", - "llmstxt", + "llmtxt", "ai", "llm", "seo", @@ -40,7 +40,7 @@ "next": ">=13.0.0" }, "dependencies": { - "@llmstxt/core": "^0.1.0" + "@llmtxt/core": "^0.1.0" }, "scripts": { "build": "tsc -p tsconfig.build.json", diff --git a/packages/next/src/llms-full-txt-route.ts b/packages/next/src/llms-full-txt-route.ts index 319a441..9a5e839 100644 --- a/packages/next/src/llms-full-txt-route.ts +++ b/packages/next/src/llms-full-txt-route.ts @@ -1,6 +1,6 @@ import path from 'path'; -import { generateLlmsFullTxt } from '@llmstxt/core'; -import type { GenerateLlmsFullTxtOptions } from '@llmstxt/core'; +import { generateLlmsFullTxt } from '@llmtxt/core'; +import type { GenerateLlmsFullTxtOptions } from '@llmtxt/core'; import { resolveBaseUrlFromEnv, resolveNextAppDir } from './shared'; export function createLlmsFullTxtHandler( diff --git a/packages/next/src/llms-txt-route.ts b/packages/next/src/llms-txt-route.ts index fa2f75f..982cbaa 100644 --- a/packages/next/src/llms-txt-route.ts +++ b/packages/next/src/llms-txt-route.ts @@ -1,6 +1,6 @@ import path from 'path'; -import { generateLlmsTxt } from '@llmstxt/core'; -import type { GenerateLlmsTxtOptions } from '@llmstxt/core'; +import { generateLlmsTxt } from '@llmtxt/core'; +import type { GenerateLlmsTxtOptions } from '@llmtxt/core'; import { resolveBaseUrlFromEnv, resolveNextAppDir } from './shared'; export function createLlmsTxtHandler( diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 0000000..aa34e50 --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,112 @@ +# `@llmtxt/react` + +Generate `llms.txt` and `llms-full.txt` for React SPAs **without a backend** by writing the files into your `public/` folder at build time. + +React Router/Vite/CRA apps don’t have a universal “pages directory”, so this package uses an **explicit route list**. + +## Install + +```bash +npm install -D @llmtxt/react +``` + +## Usage + +Create a route list: + +```ts +// llmtxt.routes.ts +import type { LlmtxtRoute } from '@llmtxt/react' + +export const routes: LlmtxtRoute[] = [ + { path: '/', title: 'Home', description: 'What this site is about.' }, + { path: '/docs', title: 'Docs' }, + + // Dynamic routes must be expanded into concrete paths: + // { path: '/blog/hello-world', title: 'Hello World' }, +] +``` + +Generate files: + +```ts +// scripts/generate-llms.ts +import path from 'path' +import { writeLlmsFiles } from '@llmtxt/react' +import { routes } from '../llmtxt.routes' + +await writeLlmsFiles({ + routes, + baseUrl: process.env.PUBLIC_SITE_URL!, // e.g. https://example.com + outDir: path.join(process.cwd(), 'public'), + title: 'My App', + summary: 'My app documentation for AI models.', +}) +``` + +## Optional: auto-scan `src/pages` (convention-based) + +If your React app follows a simple `src/pages/**` convention, you can generate the route list automatically: + +```ts +import path from 'path' +import { scanPagesDirForRoutes, writeLlmsFiles } from '@llmtxt/react' + +const routes = await scanPagesDirForRoutes({ + pagesDir: path.join(process.cwd(), 'src/pages'), + skipDynamic: true, +}) + +await writeLlmsFiles({ + routes, + baseUrl: process.env.PUBLIC_SITE_URL!, + outDir: path.join(process.cwd(), 'public'), +}) +``` + +Mapping rules: +- `src/pages/index.tsx` → `/` +- `src/pages/About.tsx` → `/about` +- `src/pages/docs/GettingStarted.tsx` → `/docs/getting-started` + +Dynamic files like `src/pages/blog/[slug].tsx` are skipped by default. + +## Why `llms-full.txt` may look empty in SPAs + +If your app is a **client-rendered SPA**, fetching `https://yoursite.com/pricing` usually returns the same `index.html` for every route (no page content). In that case, `llms-full.txt` can end up containing only the shell. + +To generate real content you need one of: + +1) **SSR / Prerendered HTML** for each route (best), or +2) Provide `route.markdown` per route (recommended for CMS-driven/dynamic pages), or +3) Provide `fetchHtml` to render routes with a headless browser (Playwright/Puppeteer). + +## API + +### `writeLlmsFiles(options)` + +Writes: +- `llms.txt` +- `llms-full.txt` + +### Types + +```ts +export type LlmtxtRoute = { + path: string + title: string + description?: string + markdown?: string +} + +export type WriteLlmsFilesOptions = { + routes: LlmtxtRoute[] + baseUrl: string + outDir: string + title?: string + summary?: string + fetchTimeoutMs?: number + htmlToMarkdown?: (html: string, url: string) => Promise | string + fetchHtml?: (url: string, timeoutMs: number) => Promise +} +``` diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..cdac3a5 --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,46 @@ +{ + "name": "@llmtxt/react", + "version": "0.1.4", + "description": "Build-time helpers for React SPAs to generate llms.txt and llms-full.txt from an explicit route list.", + "license": "MIT", + "author": "Recordly", + "repository": { + "type": "git", + "url": "https://github.com/Muhammad-Hashim/llmstxt.git", + "directory": "packages/react" + }, + "homepage": "https://github.com/Muhammad-Hashim/llmstxt#readme", + "keywords": [ + "llmtxt", + "llms", + "llms.txt", + "llms-full.txt", + "react", + "spa", + "seo", + "ai", + "llm" + ], + "type": "commonjs", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "readme": "README.md", + "files": [ + "lib/**/*" + ], + "exports": { + ".": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + } + }, + "dependencies": {}, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "prepare": "npm run build", + "clean": "node ../../scripts/rmrf.js lib" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 0000000..fc5a6a0 --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,208 @@ +import fs from 'fs/promises'; +import path from 'path'; +export { scanPagesDirForRoutes } from './scan'; +export type { ScanPagesDirOptions } from './scan'; + +export type LlmtxtRoute = { + path: string; + title: string; + description?: string; + /** + * Optional precomputed Markdown for this route. + * If provided, `llms-full.txt` uses this instead of fetching HTML. + */ + markdown?: string; +}; + +export type WriteLlmsFilesOptions = { + routes: LlmtxtRoute[]; + baseUrl: string; + outDir: string; + fetchTimeoutMs?: number; + htmlToMarkdown?: (html: string, url: string) => Promise | string; + /** + * Optional fetcher for HTML. Use this to support SPAs/dynamic pages by + * rendering with a headless browser (Playwright/Puppeteer) instead of plain fetch. + */ + fetchHtml?: (url: string, timeoutMs: number) => Promise; + title?: string; + summary?: string; +}; + +function normalizeBaseUrl(baseUrl: string): string { + return baseUrl.replace(/\/+$/, ''); +} + +function isoNow(): string { + return new Date().toISOString(); +} + +function stripHtmlToText(html: string): string { + return html + .replace(//gi, '') + .replace(//gi, '') + .replace(/<\/(p|div|section|article|h\d|li|ul|ol|br)\s*>/gi, '\n') + .replace(/<[^>]+>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/'/g, "'") + .replace(/"/g, '"') + .replace(/[ \t]+\n/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .trim(); +} + +function looksLikeClientRenderedSpa(html: string, markdown: string): boolean { + const hasLikelyMount = + /]+id=["']root["'][^>]*><\/div>/i.test(html) || + /]+id=["']app["'][^>]*><\/div>/i.test(html); + const hasScripts = / { + if (typeof fetch !== 'function') { + throw new Error( + 'Global fetch() is not available. Use Node.js >= 18 or provide a fetch polyfill.' + ); + } + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + const res = await fetch(url, { + signal: controller.signal, + headers: { 'User-Agent': '@llmtxt/react' }, + }); + if (!res.ok) { + throw new Error(`Fetch failed for ${url}: ${res.status} ${res.statusText}`); + } + return await res.text(); + } finally { + clearTimeout(timeout); + } +} + +export async function generateLlmsTxtFromRoutes(options: { + routes: LlmtxtRoute[]; + baseUrl: string; + title?: string; + summary?: string; +}): Promise { + const title = options.title ?? 'Documentation'; + const baseUrl = normalizeBaseUrl(options.baseUrl); + const routes = [...options.routes].sort((a, b) => a.path.localeCompare(b.path)); + + const lines: string[] = []; + lines.push(`# ${title}`); + if (options.summary) { + lines.push(''); + lines.push(`> ${options.summary}`); + } + lines.push(''); + + for (const route of routes) { + const url = `${baseUrl}${route.path === '/' ? '' : route.path}`; + const suffix = route.description ? `: ${route.description}` : ''; + lines.push(`- [${route.title}](${url})${suffix}`); + } + + lines.push(''); + lines.push('---'); + lines.push( + `*Generated by @llmtxt/react - ${isoNow()} - ${routes.length} pages*` + ); + lines.push(''); + return lines.join('\n'); +} + +export async function generateLlmsFullTxtFromRoutes(options: { + routes: LlmtxtRoute[]; + baseUrl: string; + fetchTimeoutMs?: number; + htmlToMarkdown?: (html: string, url: string) => Promise | string; + fetchHtml?: (url: string, timeoutMs: number) => Promise; +}): Promise { + const baseUrl = normalizeBaseUrl(options.baseUrl); + const routes = [...options.routes].sort((a, b) => a.path.localeCompare(b.path)); + const fetchTimeoutMs = options.fetchTimeoutMs ?? 8000; + + const lines: string[] = []; + lines.push('# llms-full.txt'); + lines.push(''); + lines.push( + `*Generated by @llmtxt/react - ${isoNow()} - ${routes.length} pages*` + ); + lines.push(''); + lines.push('---'); + lines.push(''); + + for (const route of routes) { + const url = `${baseUrl}${route.path === '/' ? '' : route.path}`; + lines.push(`## ${route.title}`); + lines.push(''); + lines.push(url); + lines.push(''); + try { + if (route.markdown) { + lines.push(route.markdown.trim()); + } else { + const html = + typeof options.fetchHtml === 'function' + ? await options.fetchHtml(url, fetchTimeoutMs) + : await fetchWithTimeout(url, fetchTimeoutMs); + const markdown = + typeof options.htmlToMarkdown === 'function' + ? await options.htmlToMarkdown(html, url) + : stripHtmlToText(html); + const trimmed = markdown.trim(); + lines.push(trimmed); + if (looksLikeClientRenderedSpa(html, trimmed)) { + lines.push(''); + lines.push( + '(Note: This route looks like a client-rendered SPA shell. For full content, provide `route.markdown`, prerender/SSR HTML, or pass `fetchHtml` to render with a headless browser.)' + ); + } + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + lines.push(`(Failed to fetch or convert content: ${msg})`); + } + lines.push(''); + lines.push('---'); + lines.push(''); + } + + return lines.join('\n'); +} + +export async function writeLlmsFiles(options: WriteLlmsFilesOptions): Promise<{ + llmsTxtPath: string; + llmsFullTxtPath: string; +}> { + const outDir = path.resolve(options.outDir); + await fs.mkdir(outDir, { recursive: true }); + + const llmsTxt = await generateLlmsTxtFromRoutes({ + routes: options.routes, + baseUrl: options.baseUrl, + title: options.title, + summary: options.summary, + }); + const llmsFullTxt = await generateLlmsFullTxtFromRoutes({ + routes: options.routes, + baseUrl: options.baseUrl, + fetchTimeoutMs: options.fetchTimeoutMs, + htmlToMarkdown: options.htmlToMarkdown, + fetchHtml: options.fetchHtml, + }); + + const llmsTxtPath = path.join(outDir, 'llms.txt'); + const llmsFullTxtPath = path.join(outDir, 'llms-full.txt'); + await fs.writeFile(llmsTxtPath, llmsTxt, 'utf8'); + await fs.writeFile(llmsFullTxtPath, llmsFullTxt, 'utf8'); + + return { llmsTxtPath, llmsFullTxtPath }; +} diff --git a/packages/react/src/scan.ts b/packages/react/src/scan.ts new file mode 100644 index 0000000..7a9b940 --- /dev/null +++ b/packages/react/src/scan.ts @@ -0,0 +1,146 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { LlmtxtRoute } from './index'; + +export type ScanPagesDirOptions = { + /** + * Directory that contains page components, e.g. `src/pages`. + */ + pagesDir: string; + + /** + * Route prefixes to drop from the path computation, e.g. `['(components)']`. + */ + ignoreSegments?: string[]; + + /** + * Skip dynamic routes by default (files like `[slug].tsx` or `$slug.tsx`). + * @default true + */ + skipDynamic?: boolean; +}; + +const DEFAULT_IGNORE = ['components', '__tests__', '__mocks__']; + +function splitSegments(p: string): string[] { + return p.split(/[\\/]/g).filter(Boolean); +} + +function isDynamicSegment(seg: string): boolean { + return ( + /^\[.+\]$/.test(seg) || // [slug] + /^\[\.\.\..+\]$/.test(seg) || // [...slug] + /^\$.+/.test(seg) // $slug + ); +} + +function kebabCase(input: string): string { + return input + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/[_\s]+/g, '-') + .replace(/-+/g, '-') + .toLowerCase(); +} + +function titleFromSegments(segments: string[]): string { + const last = segments[segments.length - 1] ?? 'home'; + const base = + last.toLowerCase() === 'index' + ? segments[segments.length - 2] ?? 'home' + : last; + const cleaned = base + .replace(/^\[(\.\.\.)?/, '') + .replace(/\]$/, '') + .replace(/^\$/, '') + .replace(/[-_]+/g, ' ') + .trim(); + const normalized = cleaned || 'home'; + const special = normalized.toLowerCase(); + if (special === 'api') return 'API'; + if (special === 'faq') return 'FAQ'; + if (special === 'home') return 'Home'; + return normalized.replace(/\b\w/g, c => c.toUpperCase()); +} + +function descriptionFromFileContents(fileContents: string): string | undefined { + const lines = fileContents.split(/\r?\n/); + for (const line of lines.slice(0, 40)) { + const m = /^\s*\/\/\s*(.+?)\s*$/.exec(line); + if (m?.[1]) return m[1]; + if ( + line.trim() && + !line.trim().startsWith('import') && + !line.trim().startsWith('export') + ) { + break; + } + } + return undefined; +} + +function shouldIgnoreSegment(seg: string, ignoreSegments: string[]): boolean { + const s = seg.toLowerCase(); + if (ignoreSegments.some(x => x.toLowerCase() === s)) return true; + if (s.startsWith('_')) return true; + if (s.startsWith('.')) return true; + return false; +} + +function routePathFromFile(relFileNoExt: string): string | null { + const segs = splitSegments(relFileNoExt); + const normalized = segs.map(s => (s === 'Home' ? 'index' : s)); + const last = normalized[normalized.length - 1]; + const withoutIndex = + last?.toLowerCase() === 'index' ? normalized.slice(0, -1) : normalized; + const parts = withoutIndex.map(kebabCase).filter(Boolean); + return parts.length ? `/${parts.join('/')}` : '/'; +} + +export async function scanPagesDirForRoutes( + options: ScanPagesDirOptions +): Promise { + const pagesDir = path.resolve(options.pagesDir); + const ignoreSegments = [ + ...(options.ignoreSegments ?? []), + ...DEFAULT_IGNORE, + ]; + const skipDynamic = options.skipDynamic ?? true; + + const results: LlmtxtRoute[] = []; + + async function walk(dir: string): Promise { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (shouldIgnoreSegment(entry.name, ignoreSegments)) continue; + await walk(full); + continue; + } + if (!entry.isFile()) continue; + if (!/\.(t|j)sx?$/.test(entry.name)) continue; + + const rel = path.relative(pagesDir, full); + const relNoExt = rel.replace(/\.(t|j)sx?$/, ''); + const relSegs = splitSegments(relNoExt); + if (relSegs.some(s => shouldIgnoreSegment(s, ignoreSegments))) continue; + if (skipDynamic && relSegs.some(isDynamicSegment)) continue; + + const routePath = routePathFromFile(relNoExt); + if (!routePath) continue; + + const fileContents = await fs.readFile(full, 'utf8'); + const description = descriptionFromFileContents(fileContents); + + results.push({ + path: routePath, + title: titleFromSegments(relSegs), + description, + }); + } + } + + await walk(pagesDir); + results.sort((a, b) => a.path.localeCompare(b.path)); + return results; +} diff --git a/packages/react/tsconfig.build.json b/packages/react/tsconfig.build.json new file mode 100644 index 0000000..6feccc5 --- /dev/null +++ b/packages/react/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmit": false + }, + "include": ["src/**/*.ts"] +} + diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000..f7a8479 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./lib" + }, + "include": ["src/**/*.ts"] +} +