docs: Add llms.txt and llms-full.txt (#4603)

Co-authored-by: Pieczasz <bartekp854@gmail.com>
This commit is contained in:
Krzysztof Moch
2025-07-13 18:48:54 +02:00
committed by GitHub
parent bf810386b9
commit 31915e432b
8 changed files with 258 additions and 3 deletions

View File

@@ -13,7 +13,7 @@ runs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.x node-version: 20.x
- name: Cache dependencies - name: Cache dependencies
id: yarn-cache id: yarn-cache

View File

@@ -31,6 +31,11 @@ jobs:
${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }} ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}
${{ runner.os }}-nextjs- ${{ runner.os }}-nextjs-
- name: Generate llms.txt and llms-full.txt
run: |
bun run docs:llms
bun run docs:llms-full
- name: Build docs - name: Build docs
run: | run: |
bun --cwd docs build bun --cwd docs build

View File

@@ -29,6 +29,11 @@ jobs:
${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }} ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}
${{ runner.os }}-nextjs- ${{ runner.os }}-nextjs-
- name: Generate llms.txt and llms-full.txt
run: |
bun run docs:llms
bun run docs:llms-full
- name: Build docs - name: Build docs
run: | run: |
bun --cwd docs build bun --cwd docs build

2
docs/.gitignore vendored
View File

@@ -1,3 +1,5 @@
node_modules/ node_modules/
out/ out/
.next/ .next/
public/llms.txt
public/llms-full.txt

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -43,6 +43,8 @@
"prepare": "yarn build", "prepare": "yarn build",
"xbasic": "yarn --cwd examples/basic", "xbasic": "yarn --cwd examples/basic",
"docs": "yarn --cwd docs build", "docs": "yarn --cwd docs build",
"docs:llms": "node scripts/generate-llms.js",
"docs:llms-full": "node scripts/generate-llms-full.js",
"release": "release-it", "release": "release-it",
"test": "echo no test available", "test": "echo no test available",
"check-ios": "scripts/swift-format.sh && scripts/swift-lint.sh && scripts/clang-format.sh", "check-ios": "scripts/swift-format.sh && scripts/swift-lint.sh && scripts/clang-format.sh",

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
const CWD = process.cwd();
const STATIC_DIR = path.join(CWD, 'docs', 'public');
const LLMS_FILE = path.join(STATIC_DIR, 'llms.txt');
const OUTPUT_PATH_STATIC = path.join(STATIC_DIR, 'llms-full.txt');
function readIfExists(p) {
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
}
function* walk(dir) {
for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
yield* walk(full);
} else if (/\.(md|mdx)$/.test(entry.name)) {
yield full;
}
}
}
function gatherDocs() {
// Crawl all markdown files in the docs directory (excluding the static subdir)
const docsDir = path.join(CWD, 'docs', 'pages');
if (!fs.existsSync(docsDir)) {
throw new Error(`Docs directory not found! Tried to read ${docsDir}`);
}
return Array.from(walk(docsDir)).sort();
}
function renderFileContent(filepath) {
const rel = path.relative(CWD, filepath);
const content = readIfExists(filepath);
return `\n\n## ${rel}\n\n${content}\n`;
}
function main() {
const parts = [];
// 1. quick overview
parts.push(readIfExists(LLMS_FILE));
// 2. root README & package README (if present)
const rootReadme = path.join(CWD, 'README.md');
if (fs.existsSync(rootReadme)) {
parts.push('\n\n## README (root)\n\n' + readIfExists(rootReadme));
}
// 3. all docs
for (const file of gatherDocs()) {
parts.push(renderFileContent(file));
}
const output = parts.join('\n');
fs.mkdirSync(STATIC_DIR, {recursive: true});
// Write to docs/public so it will be available at <site>/llms-full.txt
fs.writeFileSync(OUTPUT_PATH_STATIC, output, 'utf8');
console.log(
`✔︎ wrote llms-full.txt (size: ${output.length.toLocaleString()} chars)`,
);
}
main();

173
scripts/generate-llms.js Normal file
View File

@@ -0,0 +1,173 @@
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
/** Convenience utils */
const CWD = process.cwd();
/** Simple helper that returns the first N lines of README.md (sans markdown headings). */
function getProjectOverview(maxLines = 30) {
const readmePath = path.join(CWD, 'README.md');
if (!fs.existsSync(readmePath)) {
return '';
}
const lines = fs.readFileSync(readmePath, 'utf8').split(/\r?\n/);
return lines.slice(0, maxLines).join('\n');
}
/** Recursively build a filtered tree representation depth-limited for brevity. */
function buildFileTree(startPath, depth = 0, maxDepth = 2) {
const ignore = new Set([
'node_modules',
'.git',
'build',
'android',
'ios',
'.docusaurus',
'.next',
'.expo',
]);
if (depth > maxDepth) {
return [];
}
const entries = fs.readdirSync(startPath, {withFileTypes: true});
const lines = [];
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
if (ignore.has(entry.name) || entry.name.startsWith('.')) {
continue;
}
const relPath = `${' '.repeat(depth)}- ${entry.name}${
entry.isDirectory() ? '/' : ''
}`;
lines.push(relPath);
if (entry.isDirectory()) {
const childLines = buildFileTree(
path.join(startPath, entry.name),
depth + 1,
maxDepth,
);
lines.push(...childLines);
}
}
return lines;
}
/** Minimal usage examples that are helpful for agents. */
function getUsageExamples() {
return "1. Basic playback\n\n ```tsx\n import Video, { VideoRef } from 'react-native-video';\n import { useRef } from 'react';\n \n export default function Example() {\n const videoRef = useRef<VideoRef>(null);\n \n return (\n <Video\n source={{ uri: 'https://example.com/video.mp4' }}\n ref={videoRef}\n style={{ width: '100%', aspectRatio: 16 / 9 }}\n controls\n />\n );\n }\n ```\n\n2. Advanced control (seek & pause)\n\n ```tsx\n // Using ref to control playback\n videoRef.current?.setNativeProps({ paused: true });\n videoRef.current?.seek(10); // seconds\n ```";
}
/** Load docs metadata and build navigation structure */
function generateDocsSections() {
const docsRoot = path.join(CWD, 'docs', 'pages');
if (!fs.existsSync(docsRoot)) {
return [];
}
const baseUrl = 'https://docs.thewidlarzgroup.com/react-native-video';
const sections = [];
const dirEntries = fs.readdirSync(docsRoot, {withFileTypes: true});
for (const entry of dirEntries) {
const absPath = path.join(docsRoot, entry.name);
if (entry.isDirectory()) {
const title = toTitle(entry.name);
const items = collectMarkdownLinks(absPath, `${baseUrl}/${entry.name}`);
if (items.length) {
sections.push({title, items});
}
} else if (entry.isFile() && entry.name.endsWith('.md')) {
// root-level docs
const title =
getMarkdownTitle(absPath) || toTitle(entry.name.replace(/\.mdx?$/, ''));
sections.push({
title: 'General',
items: [
{title, url: `${baseUrl}/${entry.name.replace(/\.mdx?$/, '')}`},
],
});
}
}
return sections;
}
function toTitle(slug) {
return slug
.replace(/[-_]/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase())
.trim();
}
function getMarkdownTitle(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const match = content.match(/^#\s+(.+)/m);
return match ? match[1].trim() : null;
}
function collectMarkdownLinks(dir, urlPrefix) {
const links = [];
for (const file of fs.readdirSync(dir)) {
if (!file.endsWith('.md') && !file.endsWith('.mdx')) {
continue;
}
if (file.startsWith('_')) {
continue; // skip _category_.json etc
}
const abs = path.join(dir, file);
const title = getMarkdownTitle(abs) || toTitle(file.replace(/\.mdx?$/, ''));
links.push({
title,
url: `${urlPrefix}/${file.replace(/\.mdx?$/, '')}`,
});
}
return links;
}
function formatDocsSections(sections) {
if (!sections.length) {
return '';
}
let md = '\n## Documentation\n';
sections.forEach((sec) => {
md += `\n### ${sec.title}\n`;
sec.items.forEach((it) => {
md += `- [${it.title}](${it.url})\n`;
});
});
return md + '\n';
}
function generateContent() {
const overview = getProjectOverview();
const treeLines = buildFileTree(path.join(CWD), 0, 2).join('\n');
const usage = getUsageExamples();
const docsSections = formatDocsSections(generateDocsSections());
return `# react-native-video\n\n> A <Video /> element for React Native - the most battle-tested video player component with support for DRM, offline playback, HLS/DASH streaming, and more.\n\n## Project Overview\n${overview}\n\n## Key Concepts & Terminology\n- **Video**: Main React component for rendering video content\n- **VideoRef**: Imperative API for controlling playback via refs\n- **Source**: Video source configuration (local files, URLs, streams)\n- **DRM**: Built-in support for Widevine & FairPlay DRM\n- **Tracks**: Audio, video, and text track selection and management\n- **Events**: Comprehensive event system for playback state, progress, errors, etc.\n${docsSections}\n## Repository Structure (truncated)\n${treeLines}\n\n## Usage Examples\n${usage}\n\n## Version\nPackage version: ${getPackageVersion()}\n`;
}
function getPackageVersion() {
const pkgPath = path.join(CWD, 'package.json');
if (!fs.existsSync(pkgPath)) {
return 'unknown';
}
try {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
return pkg.version || 'unknown';
} catch {
return 'unknown';
}
}
function writeFileSafe(targetPath, content) {
fs.mkdirSync(path.dirname(targetPath), {recursive: true});
fs.writeFileSync(targetPath, content, 'utf8');
console.log(`✔︎ wrote ${path.relative(CWD, targetPath)}`);
}
function main() {
const content = generateContent();
writeFileSafe(path.join(CWD, 'docs', 'public', 'llms.txt'), content);
}
main();