docs: Add llms.txt and llms-full.txt (#4603)
Co-authored-by: Pieczasz <bartekp854@gmail.com>
This commit is contained in:
2
.github/actions/setup-node/action.yml
vendored
2
.github/actions/setup-node/action.yml
vendored
@@ -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
|
||||||
|
|||||||
5
.github/workflows/deploy-docs.yml
vendored
5
.github/workflows/deploy-docs.yml
vendored
@@ -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
|
||||||
|
|||||||
5
.github/workflows/test-build-docs.yml
vendored
5
.github/workflows/test-build-docs.yml
vendored
@@ -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
|
||||||
|
|||||||
4
docs/.gitignore
vendored
4
docs/.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
out/
|
out/
|
||||||
.next/
|
.next/
|
||||||
|
public/llms.txt
|
||||||
|
public/llms-full.txt
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
68
scripts/generate-llms-full.js
Normal file
68
scripts/generate-llms-full.js
Normal 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
173
scripts/generate-llms.js
Normal 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();
|
||||||
Reference in New Issue
Block a user