From 2e461dd3a963cc4ad099d03c1af5fd142eb7be69 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Fri, 26 Jun 2026 18:11:40 -1000 Subject: [PATCH] Publish synced llms files as static assets --- .gitignore | 4 +++ scripts/prepare-docs.mjs | 29 +++++++++++++++ scripts/prepare-docs.test.mjs | 33 +++++++++++++++++ scripts/sync-docs.mjs | 26 ++++++++++++++ scripts/sync-docs.test.mjs | 64 +++++++++++++++++++++++++++++++++ scripts/synced-static-files.mjs | 1 + 6 files changed, 157 insertions(+) create mode 100644 scripts/sync-docs.test.mjs create mode 100644 scripts/synced-static-files.mjs diff --git a/.gitignore b/.gitignore index 1efa5e7..ce745f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,13 @@ content/upstream/docs/ content/upstream/docs-subset/ +content/upstream/static/ content/upstream/sidebars.ts content/upstream/CHANGELOG.md prototypes/docusaurus/docs/ +prototypes/docusaurus/static/llms.txt +prototypes/docusaurus/static/llms-full.txt +prototypes/docusaurus/static/llms-full-pro.txt .DS_Store npm-debug.log* diff --git a/scripts/prepare-docs.mjs b/scripts/prepare-docs.mjs index 3970f9c..cb0182a 100644 --- a/scripts/prepare-docs.mjs +++ b/scripts/prepare-docs.mjs @@ -8,6 +8,7 @@ import { excludeNamesForRootCopy, exists, } from "./docs-layout.mjs"; +import { syncedStaticFiles } from "./synced-static-files.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -847,9 +848,30 @@ async function prepareSidebars(siteRoot, hasArchive) { } } +export async function copySyncedStaticFiles(sourceStaticDir, targetStaticDir) { + await fs.mkdir(targetStaticDir, { recursive: true }); + + const copied = []; + for (const fileName of syncedStaticFiles) { + const sourceFile = path.join(sourceStaticDir, fileName); + const targetFile = path.join(targetStaticDir, fileName); + + await fs.rm(targetFile, { force: true }); + if (!(await exists(sourceFile))) { + continue; + } + + await fs.copyFile(sourceFile, targetFile); + copied.push(fileName); + } + + return copied; +} + async function prepareDocusaurus() { const siteRoot = path.join(workspaceRoot, "prototypes", "docusaurus"); const docsRoot = path.join(siteRoot, "docs"); + const staticRoot = path.join(siteRoot, "static"); const layout = await detectDocsLayout(sourceDocs); const layoutPaths = docsLayoutPaths(sourceDocs, layout); const excludedRootEntries = excludeNamesForRootCopy(layout); @@ -894,6 +916,13 @@ async function prepareDocusaurus() { await convertGitHubAlerts(docsRoot); await prepareSidebars(siteRoot, hasArchive); + const copiedStaticFiles = await copySyncedStaticFiles( + path.join(workspaceRoot, "content", "upstream", "static"), + staticRoot + ); + if (copiedStaticFiles.length > 0) { + console.log(`Prepared static root files: ${copiedStaticFiles.join(", ")}`); + } console.log(`Prepared docusaurus docs from ${sourceDocs} (${layout} layout, pro -> /pro)`); } diff --git a/scripts/prepare-docs.test.mjs b/scripts/prepare-docs.test.mjs index 9b5f080..35cd0fe 100644 --- a/scripts/prepare-docs.test.mjs +++ b/scripts/prepare-docs.test.mjs @@ -6,6 +6,7 @@ import test from "node:test"; import { changelogMarkdown, + copySyncedStaticFiles, docsHomeMarkdown, fixProNodeRendererMdx, injectProFriendlyNotice, @@ -208,3 +209,35 @@ test("Pro node renderer table comparisons are escaped for MDX", () => { "| First request on fresh deploy | 410->retry | Direct render: <50ms |\n" ); }); + +test("prepare docs copies synced llms files to the Docusaurus static root", async () => { + await withTempDir(async (tmpDir) => { + const upstreamStatic = path.join(tmpDir, "upstream-static"); + const docusaurusStatic = path.join(tmpDir, "docusaurus-static"); + await fs.mkdir(upstreamStatic, { recursive: true }); + await fs.mkdir(docusaurusStatic, { recursive: true }); + + await fs.writeFile(path.join(upstreamStatic, "llms.txt"), "llms index\n", "utf8"); + await fs.writeFile(path.join(upstreamStatic, "llms-full.txt"), "oss full\n", "utf8"); + await fs.writeFile(path.join(upstreamStatic, "llms-full-pro.txt"), "pro full\n", "utf8"); + await fs.writeFile(path.join(upstreamStatic, "ignored.txt"), "ignored\n", "utf8"); + await fs.writeFile(path.join(docusaurusStatic, "llms-full.txt"), "stale\n", "utf8"); + + const copied = await copySyncedStaticFiles(upstreamStatic, docusaurusStatic); + + assert.deepEqual(copied.sort(), ["llms-full-pro.txt", "llms-full.txt", "llms.txt"]); + assert.equal(await fs.readFile(path.join(docusaurusStatic, "llms.txt"), "utf8"), "llms index\n"); + assert.equal( + await fs.readFile(path.join(docusaurusStatic, "llms-full.txt"), "utf8"), + "oss full\n" + ); + assert.equal( + await fs.readFile(path.join(docusaurusStatic, "llms-full-pro.txt"), "utf8"), + "pro full\n" + ); + await assert.rejects( + fs.access(path.join(docusaurusStatic, "ignored.txt")), + /ENOENT/ + ); + }); +}); diff --git a/scripts/sync-docs.mjs b/scripts/sync-docs.mjs index 2579b17..3f47d17 100644 --- a/scripts/sync-docs.mjs +++ b/scripts/sync-docs.mjs @@ -5,6 +5,7 @@ import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { detectDocsLayout, docsLayoutPaths, exists, subsetPathsForLayout } from "./docs-layout.mjs"; +import { syncedStaticFiles } from "./synced-static-files.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -16,6 +17,7 @@ const buildSubset = args.has("--subset"); const upstreamRoot = path.join(workspaceRoot, "content", "upstream"); const fullDocsTarget = path.join(upstreamRoot, "docs"); const subsetDocsTarget = path.join(upstreamRoot, "docs-subset"); +const staticTarget = path.join(upstreamRoot, "static"); function cloneRepo(repoUrl, ref) { const tmpDir = mkdtempSync(path.join(os.tmpdir(), "react-on-rails-docs-")); @@ -77,6 +79,25 @@ async function countFiles(rootDir) { return count; } +async function syncRootStaticFiles(sourceRepo) { + await fs.rm(staticTarget, { recursive: true, force: true }); + await fs.mkdir(staticTarget, { recursive: true }); + + const copied = []; + for (const fileName of syncedStaticFiles) { + const sourceFile = path.join(sourceRepo, fileName); + if (!(await exists(sourceFile))) { + console.warn(`Warning: ${fileName} not found in source repo`); + continue; + } + + await fs.copyFile(sourceFile, path.join(staticTarget, fileName)); + copied.push(fileName); + } + + return copied; +} + async function main() { const configuredRepo = process.env.REACT_ON_RAILS_REPO; const localRepo = configuredRepo @@ -125,6 +146,11 @@ async function main() { console.warn("Warning: CHANGELOG.md not found in source repo"); } + const copiedStaticFiles = await syncRootStaticFiles(sourceRepo); + if (copiedStaticFiles.length > 0) { + console.log(`Synced static root files: ${copiedStaticFiles.join(", ")}`); + } + let subsetStats = null; if (buildSubset) { subsetStats = await writeSubset(fullDocsTarget, subsetDocsTarget, layout); diff --git a/scripts/sync-docs.test.mjs b/scripts/sync-docs.test.mjs new file mode 100644 index 0000000..830d897 --- /dev/null +++ b/scripts/sync-docs.test.mjs @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { promisify } from "node:util"; +import { fileURLToPath } from "node:url"; + +const execFileAsync = promisify(execFile); +const __filename = fileURLToPath(import.meta.url); +const workspaceRoot = path.resolve(path.dirname(__filename), ".."); + +async function withTempDir(callback) { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sync-docs-test-")); + try { + return await callback(tmpDir); + } finally { + await fs.rm(tmpDir, { recursive: true, force: true }); + } +} + +async function writeFakeUpstream(upstreamRoot) { + await fs.mkdir(path.join(upstreamRoot, "docs"), { recursive: true }); + await fs.writeFile( + path.join(upstreamRoot, "docs", "README.md"), + "# React on Rails Docs\n", + "utf8" + ); + await fs.writeFile(path.join(upstreamRoot, "llms.txt"), "llms index\n", "utf8"); + await fs.writeFile(path.join(upstreamRoot, "llms-full.txt"), "oss full\n", "utf8"); + await fs.writeFile(path.join(upstreamRoot, "llms-full-pro.txt"), "pro full\n", "utf8"); +} + +test("sync docs stages root llms files for static publishing", async () => { + await withTempDir(async (tmpDir) => { + const upstreamRoot = path.join(tmpDir, "react_on_rails"); + await writeFakeUpstream(upstreamRoot); + + const upstreamContent = path.join(workspaceRoot, "content", "upstream"); + await fs.rm(upstreamContent, { recursive: true, force: true }); + + await execFileAsync(process.execPath, [path.join(workspaceRoot, "scripts", "sync-docs.mjs")], { + cwd: workspaceRoot, + env: { + ...process.env, + REACT_ON_RAILS_REPO: upstreamRoot, + }, + }); + + assert.equal( + await fs.readFile(path.join(upstreamContent, "static", "llms.txt"), "utf8"), + "llms index\n" + ); + assert.equal( + await fs.readFile(path.join(upstreamContent, "static", "llms-full.txt"), "utf8"), + "oss full\n" + ); + assert.equal( + await fs.readFile(path.join(upstreamContent, "static", "llms-full-pro.txt"), "utf8"), + "pro full\n" + ); + }); +}); diff --git a/scripts/synced-static-files.mjs b/scripts/synced-static-files.mjs new file mode 100644 index 0000000..7e1f8c1 --- /dev/null +++ b/scripts/synced-static-files.mjs @@ -0,0 +1 @@ +export const syncedStaticFiles = ["llms.txt", "llms-full.txt", "llms-full-pro.txt"];