@@ -16,6 +16,8 @@ import title from "https://deno.land/x/lume_markdown_plugins@v0.7.0/title.ts";
1616import toc from "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc.ts" ;
1717// See note below about GFM CSS
1818// import { CSS as GFM_CSS } from "https://jsr.io/@deno/gfm/0.11.0/style.ts";
19+ import { walk } from "@std/fs" ;
20+ import { dirname } from "@std/path" ;
1921import { log } from "lume/core/utils/log.ts" ;
2022import anchor from "npm:markdown-it-anchor@9" ;
2123import admonitionPlugin from "./markdown-it/admonition.ts" ;
@@ -27,6 +29,8 @@ import apiDocumentContentTypeMiddleware from "./middleware/apiDocContentType.ts"
2729import createRoutingMiddleware from "./middleware/functionRoutes.ts" ;
2830import createGAMiddleware from "./middleware/googleAnalytics.ts" ;
2931import redirectsMiddleware from "./middleware/redirects.ts" ;
32+ import createLlmsFilesMiddleware from "./middleware/llmsFiles.ts" ;
33+ import createMarkdownSourceMiddleware from "./middleware/markdownSource.ts" ;
3034import { toFileAndInMemory } from "./utils/redirects.ts" ;
3135import { cliNow } from "./timeUtils.ts" ;
3236
@@ -72,10 +76,12 @@ const site = lume(
7276 server : {
7377 middlewares : [
7478 redirectsMiddleware ,
79+ createMarkdownSourceMiddleware ( { root : "_site" } ) ,
7580 createRoutingMiddleware ( ) ,
7681 createGAMiddleware ( {
7782 addr : { transport : "tcp" , hostname : "localhost" , port : 3000 } ,
7883 } ) ,
84+ createLlmsFilesMiddleware ( { root : "_site" } ) ,
7985 apiDocumentContentTypeMiddleware ,
8086 ] ,
8187 page404 : "/404/" ,
@@ -137,14 +143,13 @@ site.copy("deploy/images");
137143site . copy ( "deploy/classic/images" ) ;
138144site . copy ( "deploy/kv/images" ) ;
139145site . copy ( "deploy/tutorials/images" ) ;
140- site . copy ( "sandboxes /images" ) ;
146+ site . copy ( "sandbox /images" ) ;
141147site . copy ( "runtime/fundamentals/images" ) ;
142148site . copy ( "runtime/getting_started/images" ) ;
143149site . copy ( "runtime/reference/images" ) ;
144150site . copy ( "runtime/contributing/images" ) ;
145151site . copy ( "examples/tutorials/images" ) ;
146152site . copy ( "deploy/manual/images" ) ;
147- site . copy ( "deploy/images" ) ;
148153site . copy ( "examples/scripts" ) ;
149154
150155site . use (
@@ -185,33 +190,111 @@ site.addEventListener("afterBuild", async () => {
185190 /* NOTE: we used to get gfm.css from the jsr.io CDN, but now we simply have a local copy. This is because it needs to be placed on a CSS layer, which isn't possible with an imported file. */
186191 // Deno.writeTextFileSync(site.dest("gfm.css"), GFM_CSS);
187192
193+ // Copy lint rule markdown source files to _site so they're served at /lint/rules/*.md.
194+ // These files are excluded from Lume's processing pipeline (site.ignore) because
195+ // lint_rule.page.tsx handles page generation, but we still want the raw .md accessible.
196+ try {
197+ await Deno . mkdir ( site . dest ( "lint/rules" ) , { recursive : true } ) ;
198+ for await ( const entry of Deno . readDir ( "lint/rules" ) ) {
199+ if ( entry . isFile && entry . name . endsWith ( ".md" ) ) {
200+ await Deno . copyFile (
201+ `lint/rules/${ entry . name } ` ,
202+ site . dest ( `lint/rules/${ entry . name } ` ) ,
203+ ) ;
204+ }
205+ }
206+ log . info ( "Copied lint rule markdown files to _site/lint/rules/" ) ;
207+ } catch ( error ) {
208+ log . error ( "Error copying lint rule markdown files: " + error ) ;
209+ }
210+
188211 // Generate LLMs documentation files directly to _site directory
189212 if ( Deno . env . get ( "BUILD_TYPE" ) == "FULL" ) {
190213 try {
191214 const { default : generateModule } = await import (
192215 "./generate_llms_files.ts"
193216 ) ;
194- const { collectFiles, generateLlmsTxt, generateLlmsFullTxt } =
195- generateModule ;
217+ const {
218+ collectFiles,
219+ generateLlmsSummaryTxt,
220+ generateLlmsFullTxt,
221+ generateLlmsJson,
222+ loadOramaSummaryIndex,
223+ } = generateModule ;
196224
197225 log . info ( "Generating LLM-friendly documentation files..." ) ;
198226
199227 const files = await collectFiles ( ) ;
200228 log . info ( `Collected ${ files . length } documentation files for LLMs` ) ;
201229
202- // Generate llms.txt
203- const llmsTxt = generateLlmsTxt ( files ) ;
204- Deno . writeTextFileSync ( site . dest ( "llms.txt" ) , llmsTxt ) ;
205- log . info ( "Generated llms.txt in site root" ) ;
230+ // Generate llms-summary .txt
231+ const llmsSummaryTxt = generateLlmsSummaryTxt ( files ) ;
232+ Deno . writeTextFileSync ( site . dest ( "llms-summary .txt" ) , llmsSummaryTxt ) ;
233+ log . info ( "Generated llms-summary .txt in site root" ) ;
206234
207235 // Generate llms-full.txt
208236 const llmsFullTxt = generateLlmsFullTxt ( files ) ;
209237 Deno . writeTextFileSync ( site . dest ( "llms-full.txt" ) , llmsFullTxt ) ;
210238 log . info ( "Generated llms-full.txt in site root" ) ;
239+
240+ // Generate llms.json
241+ const oramaSummary = await loadOramaSummaryIndex ( ) ;
242+ if ( oramaSummary ) {
243+ const llmsJson = generateLlmsJson ( oramaSummary ) ;
244+ Deno . writeTextFileSync ( site . dest ( "llms.json" ) , llmsJson ) ;
245+ log . info ( "Generated llms.json in site root" ) ;
246+ } else {
247+ log . warn (
248+ "Skipped llms.json generation (orama-index-summary.json not found)" ,
249+ ) ;
250+ }
211251 } catch ( error ) {
212252 log . error ( "Error generating LLMs files:" + error ) ;
213253 }
214254 }
255+
256+ // Copy source .md files to _site so AI agents can request them directly.
257+ // Excludes "reference/" (dynamically generated, no static .md source files).
258+ const contentDirs = [
259+ "runtime" ,
260+ "deploy" ,
261+ "sandbox" ,
262+ "subhosting" ,
263+ "examples" ,
264+ ] ;
265+ let mdCopied = 0 ;
266+ let mdErrors = false ;
267+ for ( const dir of contentDirs ) {
268+ // Skip directories that don't exist in this build
269+ try {
270+ await Deno . stat ( dir ) ;
271+ } catch ( error ) {
272+ if ( ! ( error instanceof Deno . errors . NotFound ) ) {
273+ log . error ( `Error accessing content directory ${ dir } : ${ error } ` ) ;
274+ }
275+ continue ;
276+ }
277+ try {
278+ for await (
279+ const entry of walk ( dir , { exts : [ ".md" ] , includeDirs : false } )
280+ ) {
281+ const destPath = site . dest ( entry . path ) ;
282+ await Deno . mkdir ( dirname ( destPath ) , { recursive : true } ) ;
283+ await Deno . copyFile ( entry . path , destPath ) ;
284+ mdCopied ++ ;
285+ }
286+ } catch ( error ) {
287+ log . error ( `Error copying markdown files from ${ dir } : ${ error } ` ) ;
288+ mdErrors = true ;
289+ }
290+ }
291+ if ( mdErrors ) {
292+ log . warn (
293+ `Copied ${ mdCopied } source markdown files to _site (some directories had errors, see above)` ,
294+ ) ;
295+ } else {
296+ log . info ( `Copied ${ mdCopied } source markdown files to _site` ) ;
297+ }
215298} ) ;
216299
217300site . copy ( "reference_gen/gen/deno/page.css" , "/api/deno/page.css" ) ;
@@ -289,7 +372,7 @@ site.data("apiCategories", {
289372} ) ;
290373
291374// Do more expensive operations if we're building the full site
292- if ( Deno . env . get ( "BUILD_TYPE" ) == "FULL" ) {
375+ if ( Deno . env . get ( "BUILD_TYPE" ) == "FULL" && ! Deno . env . has ( "SKIP_OG" ) ) {
293376 // Use Lume's built in date function to get the last modified date of the file
294377 // site.data("date", "Git Last Modified");;
295378
0 commit comments