1+ const fs = require ( 'fs' ) . promises ;
2+ const { marked } = require ( 'marked' ) ;
3+ const { mkdirp } = require ( 'mkdirp' ) ;
4+ const path = require ( 'path' ) ;
5+ const Prism = require ( 'prismjs' ) ;
6+ const loadLanguages = require ( 'prismjs/components/' ) ;
7+
8+ // Load additional languages (javascript is included by default)
9+ loadLanguages ( [ 'javascript' , 'js' , 'bash' , 'typescript' ] ) ;
10+
11+ const encoding = 'utf-8' ;
12+
13+ // Configure marked options
14+ marked . use ( {
15+ mangle : false ,
16+ headerIds : true ,
17+ headerPrefix : '' ,
18+ } ) ;
19+
20+ // Create a custom extension that processes links
21+ const linkExtension = {
22+ name : 'mdToHtml' ,
23+ level : 'inline' ,
24+ start ( src ) { return src . match ( / \[ / ) ?. index ; } ,
25+ tokenizer ( src , tokens ) {
26+ const rule = / ^ \[ ( [ ^ \] ] + ) \] \( ( [ ^ ) ] + \. m d ) ( [ ^ ) ] * ) \) / ;
27+ const match = rule . exec ( src ) ;
28+ if ( match ) {
29+ return {
30+ type : 'mdToHtml' ,
31+ raw : match [ 0 ] ,
32+ text : match [ 1 ] ,
33+ href : match [ 2 ] . replace ( / \. m d $ / , '.html' ) ,
34+ title : match [ 3 ]
35+ } ;
36+ }
37+ } ,
38+ renderer ( token ) {
39+ const titleAttr = token . title ? ` title="${ token . title } "` : '' ;
40+ return `<a href="${ token . href } "${ titleAttr } >${ token . text } </a>` ;
41+ }
42+ } ;
43+
44+ // Custom code renderer for syntax highlighting
45+ const codeRenderer = {
46+ name : 'codeRenderer' ,
47+ renderer : {
48+ code ( token ) {
49+ const code = token . text || '' ;
50+ const language = token . lang || '' ;
51+
52+ if ( ! language ) {
53+ // No language specified - escape the code
54+ const escaped = code . replace ( / [ & < > " ' ] / g, ( char ) => {
55+ const escapes = {
56+ '&' : '&' ,
57+ '<' : '<' ,
58+ '>' : '>' ,
59+ '"' : '"' ,
60+ "'" : '''
61+ } ;
62+ return escapes [ char ] ;
63+ } ) ;
64+ return `<pre><code>${ escaped } </code></pre>\n` ;
65+ }
66+
67+ // Use Prism for syntax highlighting if language is supported
68+ const lang = language . toLowerCase ( ) ;
69+ // Map common aliases
70+ const langMap = {
71+ 'js' : 'javascript' ,
72+ 'javascript' : 'javascript' ,
73+ 'ts' : 'typescript' ,
74+ 'typescript' : 'typescript' ,
75+ 'bash' : 'bash' ,
76+ 'sh' : 'bash'
77+ } ;
78+ const mappedLang = langMap [ lang ] || lang ;
79+
80+ if ( Prism . languages [ mappedLang ] ) {
81+ const highlighted = Prism . highlight ( code , Prism . languages [ mappedLang ] , mappedLang ) ;
82+ return `<pre class="language-${ lang } "><code class="language-${ lang } ">${ highlighted } </code></pre>\n` ;
83+ }
84+
85+ // Fallback for unsupported languages - escape the code
86+ const escaped = code . replace ( / [ & < > " ' ] / g, ( char ) => {
87+ const escapes = {
88+ '&' : '&' ,
89+ '<' : '<' ,
90+ '>' : '>' ,
91+ '"' : '"' ,
92+ "'" : '''
93+ } ;
94+ return escapes [ char ] ;
95+ } ) ;
96+ return `<pre class="language-${ lang } "><code class="language-${ lang } ">${ escaped } </code></pre>\n` ;
97+ }
98+ }
99+ } ;
100+
101+ // Register the extensions
102+ marked . use ( { extensions : [ linkExtension ] , renderer : codeRenderer . renderer } ) ;
103+
104+ // Override the heading renderer using the proper API
105+ const headingRenderer = {
106+ name : 'headingRenderer' ,
107+ renderer : {
108+ heading ( token ) {
109+ // Extract text content from the token
110+ let text = '' ;
111+ if ( token . tokens && token . tokens . length > 0 ) {
112+ text = token . tokens . map ( t => t . text || t . raw || '' ) . join ( '' ) ;
113+ } else if ( token . text ) {
114+ text = token . text ;
115+ }
116+
117+ const level = token . depth || 1 ;
118+
119+ // Check for explicit ID syntax {#id}
120+ let id = '' ;
121+ let displayText = text ;
122+ const idMatch = text . match ( / ^ ( .+ ?) \s * \{ # ( .+ ?) \} $ / ) ;
123+
124+ if ( idMatch ) {
125+ displayText = idMatch [ 1 ] . trim ( ) ;
126+ id = idMatch [ 2 ] ;
127+ } else {
128+ // Generate ID from text
129+ id = text . toLowerCase ( ) . replace ( / [ ^ \w ] + / g, '-' ) . replace ( / ^ - + | - + $ / g, '' ) ;
130+ }
131+
132+ return `<h${ level } id="${ id } "><a class="heading-link" href="#${ id } "></a>${ displayText } </h${ level } >\n` ;
133+ }
134+ }
135+ } ;
136+
137+ marked . use ( headingRenderer ) ;
138+
139+ // Also use a walkTokens function to transform .md links in regular links
140+ marked . use ( {
141+ walkTokens ( token ) {
142+ if ( token . type === 'link' && token . href && token . href . endsWith ( '.md' ) ) {
143+ token . href = token . href . replace ( / \. m d $ / , '.html' ) ;
144+ }
145+ }
146+ } ) ;
147+
148+ async function processMarkdownFile ( markdownFile , outputDir , head , tail ) {
149+ console . log ( `Processing ${ markdownFile } ...` ) ;
150+
151+ // Read markdown content
152+ let content = await fs . readFile ( markdownFile , { encoding } ) ;
153+
154+ // Parse markdown
155+ let html = marked ( content ) ;
156+
157+ // Get output filename
158+ const baseName = path . basename ( markdownFile , '.md' ) ;
159+ const outputFile = path . join ( outputDir , `${ baseName } .html` ) ;
160+
161+ // Combine with header and footer
162+ const fullHtml = head + html + tail ;
163+
164+ // Write output file
165+ await fs . writeFile ( outputFile , fullHtml , { encoding } ) ;
166+ console . log ( ` → ${ outputFile } ` ) ;
167+ }
168+
169+ async function build ( ) {
170+ try {
171+ console . log ( 'Building Decimal documentation...\n' ) ;
172+
173+ // Create output directory
174+ const outputDir = path . join ( __dirname , 'out' ) ;
175+ await mkdirp ( outputDir ) ;
176+
177+ // Read header and footer templates
178+ const head = await fs . readFile ( path . join ( __dirname , 'head.html.part' ) , { encoding } ) ;
179+ const tail = await fs . readFile ( path . join ( __dirname , 'tail.html.part' ) , { encoding } ) ;
180+
181+ // Get all markdown files
182+ const files = await fs . readdir ( __dirname ) ;
183+ const mdFiles = files . filter ( f => f . endsWith ( '.md' ) ) ;
184+
185+ // Process each markdown file
186+ for ( const file of mdFiles ) {
187+ await processMarkdownFile ( path . join ( __dirname , file ) , outputDir , head , tail ) ;
188+ }
189+
190+ // Copy prism.css - using okaidia theme for vibrant syntax highlighting
191+ const prismCss = path . join ( __dirname , '..' , 'node_modules/prismjs/themes/prism-okaidia.css' ) ;
192+ const prismDest = path . join ( outputDir , 'prism.css' ) ;
193+ await fs . copyFile ( prismCss , prismDest ) ;
194+ console . log ( ` → ${ prismDest } ` ) ;
195+
196+ // Build and copy proposal-decimal polyfill as browser bundle
197+ const { execSync } = require ( 'child_process' ) ;
198+ const polyfillSrc = path . join ( __dirname , '..' , 'node_modules/proposal-decimal/src/Decimal.mjs' ) ;
199+ const polyfillDest = path . join ( outputDir , 'decimal-polyfill.js' ) ;
200+
201+ // Build IIFE bundle that exposes DecimalPolyfill globally
202+ execSync ( `npx esbuild "${ polyfillSrc } " --bundle --format=iife --global-name=DecimalPolyfill --outfile="${ polyfillDest } "` , {
203+ cwd : __dirname ,
204+ stdio : 'inherit'
205+ } ) ;
206+ console . log ( ` → ${ polyfillDest } ` ) ;
207+
208+ // Also copy the original ES module
209+ const polyfillMjsDest = path . join ( outputDir , 'decimal-polyfill.mjs' ) ;
210+ await fs . copyFile ( polyfillSrc , polyfillMjsDest ) ;
211+ console . log ( ` → ${ polyfillMjsDest } ` ) ;
212+
213+ console . log ( '\nBuild complete!' ) ;
214+ } catch ( error ) {
215+ console . error ( 'Build failed:' , error ) ;
216+ process . exit ( 1 ) ;
217+ }
218+ }
219+
220+ // Run the build
221+ build ( ) ;
0 commit comments