1+ import * as path from "node:path" ;
2+ import { exec } from "node:child_process" ;
3+ import { promisify } from "node:util" ;
4+ import fs from "node:fs/promises" ;
5+
6+ const execAsync = promisify ( exec ) ;
7+
8+ const bins = await import (
9+ path . join ( import . meta. dirname , "../node_modules/rescript/cli/common/bins.js" )
10+ ) ;
11+ const rescriptTools = bins [ "rescript_tools_exe" ] ;
12+
13+ if ( ! rescriptTools ) {
14+ throw new Error ( "rescript-tools not found" ) ;
15+ }
16+
17+ async function getDocJson ( filePath ) {
18+ try {
19+ const command = `${ rescriptTools } doc "${ filePath } "` ;
20+ const options = { maxBuffer : 10 * 1024 * 1024 } ;
21+ const { stdout, stderr } = await execAsync ( command , options ) ;
22+
23+ if ( stderr ) {
24+ throw new Error ( `Error executing command for ${ filePath } : ${ stderr } ` ) ;
25+ }
26+
27+ return JSON . parse ( stdout ) ;
28+ } catch ( error ) {
29+ throw new Error ( `Failed to get documentation JSON for ${ filePath } :` , error ) ;
30+ }
31+ }
32+
33+ async function processFile ( filePath ) {
34+ const json = await getDocJson ( filePath ) ;
35+
36+ const moduleName = "WebAPI." + json . name . replace ( "-WebAPI" , "" ) ;
37+
38+ const types = [ ] ;
39+ const functions = [ ] ;
40+
41+ function mkType ( item ) {
42+ let description = "" ;
43+ if ( item . docstrings . length > 0 ) {
44+ description = "\n Description: " + item . docstrings . join ( "\n" ) ;
45+ }
46+ let fields = "" ;
47+ if ( item . detail && item . detail . kind === "record" ) {
48+ fields =
49+ "\n Fields:\n" +
50+ item . detail . items
51+ . map ( ( field ) => {
52+ let fieldDoc = "" ;
53+ if ( field . docstrings . length > 0 ) {
54+ fieldDoc = " - " + field . docstrings . join ( " " ) ;
55+ }
56+ return ` - ${ field . name } : ${ field . signature } ${ fieldDoc } ` ;
57+ } )
58+ . join ( "\n" ) ;
59+ }
60+ return `- ${ item . signature } ${ description } ${ fields } ` ;
61+ }
62+
63+ function mkFunction ( item ) {
64+ let description = "" ;
65+ if ( item . docstrings . length > 0 ) {
66+ description = "\n Description: " + item . docstrings . join ( "\n" ) ;
67+ }
68+ return `- ${ item . signature } ${ description } ` ;
69+ }
70+
71+ for ( const item of json . items ) {
72+ switch ( item . kind ) {
73+ case "type" :
74+ types . push ( mkType ( item ) ) ;
75+ break ;
76+ case "value" :
77+ functions . push ( mkFunction ( item ) ) ;
78+ break ;
79+ }
80+ }
81+
82+ let typeString = "" ;
83+ if ( types . length > 0 ) {
84+ typeString = "\n\nTypes:\n\n" + types . join ( "\n\n" ) ;
85+ }
86+
87+ let functionString = "" ;
88+ if ( functions . length > 0 ) {
89+ functionString = "\n\nFunctions:\n\n" + functions . join ( "\n\n" ) ;
90+ }
91+
92+ return `File: ${ json . source . filepath }
93+ Module: ${ moduleName } ${ typeString } ${ functionString }
94+ ` ;
95+ }
96+
97+ const pattern = "../src/**/*.res"
98+ const files = [ ] ;
99+ for await ( const file of fs . glob ( pattern , { recursive : true , cwd : import . meta. dirname } ) ) {
100+ files . push ( path . join ( import . meta. dirname , file ) ) ;
101+ }
102+ files . sort ( ) ;
103+
104+ const pages = await Promise . all ( files . map ( processFile ) )
105+ const packageJson = await fs . readFile ( path . join ( import . meta. dirname , "../package.json" ) , "utf-8" ) ;
106+ let version = JSON . parse ( packageJson ) . version ;
107+ const sha = await execAsync ( "git rev-parse --short HEAD" ) . then ( ( { stdout } ) => stdout . trim ( ) ) ;
108+ const fullVersion = `${ version } -${ sha } ` ;
109+ const header = `Experimental Rescript WebAPI Documentation ${ fullVersion }
110+
111+ This is the API documentation for the experimental WebAPI module version ${ fullVersion } .
112+ More information can be found on https://rescript-lang.github.io/experimental-rescript-webapi/
113+
114+ `
115+ const content = pages . join ( "\n---\n\n" ) ;
116+ await fs . writeFile ( path . join ( import . meta. dirname , "public/llm.txt" ) , header + content ) ;
117+ console . log ( "Generated llm.txt" ) ;
0 commit comments