@@ -2,24 +2,49 @@ const fs = require('fs').promises;
22const path = require ( 'path' ) ;
33const glob = require ( 'glob' ) ;
44const util = require ( 'util' ) ;
5+ const crypto = require ( 'crypto' ) ;
56const exec = util . promisify ( require ( 'child_process' ) . exec ) ;
67
8+ // Promisify the glob function to enable async/await usage
9+ const globPromise = util . promisify ( glob ) ;
10+
11+ // Constants
712const SRC_DIR = './src' ;
813const BUILD_DIR = './build' ;
914const TEMP_DIR = path . join ( BUILD_DIR , 'temp' ) ;
1015const MD_FILES_DIR = path . join ( './docs' , 'API-Reference' ) ;
16+ const TEMP_CHECK_DIR = path . join ( BUILD_DIR , 'check_copy' ) ;
17+ const BATCH_SIZE = 12 ;
18+
19+
20+ /**
21+ * Create directory
22+ * @param {string } dirPath - The path where the directory will be created
23+ */
24+ async function createDir ( dirPath ) {
25+ await fs . mkdir ( dirPath , { recursive : true } ) ;
26+ }
27+
28+
29+ /**
30+ * Remove directory
31+ * @param {string } dirPath - The path to the directory to remove
32+ */
33+ async function removeDir ( dirPath ) {
34+ await fs . rm ( dirPath , { recursive : true , force : true } ) ;
35+ }
1136
12- const globPromise = util . promisify ( glob ) ;
1337
1438/**
1539 * Responsible to get the JS Files that are to be included in API DOCS
16- * @returns {Promise<string[]> } Promise resolving to list of JS Files for API Docs
40+ * @returns {Promise<string[]> } Promise resolving, list of JS Files for API Docs
1741 */
1842async function getJsFiles ( ) {
1943 const allJsFiles = await globPromise ( `${ SRC_DIR } /**/*.js` ) ;
2044 const requiredJSfiles = [ ] ;
2145
2246 await Promise . all ( allJsFiles . map ( async ( file ) => {
47+ // Check if the path points to a valid file
2348 const stats = await fs . stat ( file ) ;
2449 if ( stats . isFile ( ) ) {
2550 const content = await fs . readFile ( file , 'utf-8' ) ;
@@ -32,32 +57,18 @@ async function getJsFiles() {
3257 return requiredJSfiles ;
3358}
3459
35- /**
36- * Creates a directory
37- * @param {string } dirPath creates dir at the given path
38- */
39- async function createDir ( dirPath ) {
40- await fs . mkdir ( dirPath , { recursive : true } ) ;
41- }
42-
43- /**
44- * Deletes a directory
45- * @param {string } dirPath deletes dir from the given path
46- */
47- async function removeDir ( dirPath ) {
48- await fs . rm ( dirPath , { recursive : true , force : true } ) ;
49- }
5060
5161/**
5262 * Adjusts JavaScript content for compatibility with jsdoc-to-markdown
5363 * @param {string } content The content to be modified
54- * @param {string } fileName To replace the define block with this name
5564 * @returns {string } Updated content
5665 */
57- function modifyJs ( content , fileName ) {
66+ function modifyJs ( content ) {
5867 if ( content . includes ( '\n(function () {' ) ) {
5968 content = content . replace ( / \( f u n c t i o n \( \) \{ / , '' ) ;
69+
6070 if ( content . trim ( ) . endsWith ( '}());' ) ) {
71+ // Remove trailing IIFE closing
6172 content = content . trim ( ) . slice ( 0 , - 5 ) ;
6273 }
6374
@@ -67,35 +78,42 @@ function modifyJs(content, fileName) {
6778 bracketCount ++ ;
6879 } else if ( content [ indx ] === '}' ) {
6980 bracketCount -- ;
70- if ( bracketCount < 0 ) {
71- let tempIndx = indx ;
72- while ( content [ indx ] && content [ indx ] !== ')' ) {
73- indx -- ;
74- }
75- content = content . slice ( 0 , indx ) + content . slice ( tempIndx + 1 ) ;
76- bracketCount ++ ;
77- break ;
81+ }
82+
83+ // Remove any unmatched closing brackets
84+ if ( bracketCount < 0 ) {
85+ let tempIndx = indx ;
86+ while ( content [ indx ] && content [ indx ] !== ')' ) {
87+ indx -- ;
7888 }
89+ content = content . slice ( 0 , indx ) + content . slice ( tempIndx + 1 ) ;
90+ bracketCount ++ ;
91+ break ;
7992 }
8093 }
8194 } else if ( content . includes ( 'define(function' ) ) {
8295 content = content . replace ( / d e f i n e \( f u n c t i o n \s * \( [ ^ ) ] * \) \s * { / , '' ) ;
8396 if ( content . trim ( ) . endsWith ( '});' ) ) {
97+ // Remove AMD-style wrapper
8498 content = content . trim ( ) . slice ( 0 , - 3 ) ;
8599 }
86100 }
87101 return content ;
88102}
89103
104+
90105/**
91- * Modifies markdown content for compatibility with docusaurus
92- * @param {string } content markdown file content
93- * @param {string } relativePath Relative path of the file from MD_FILES_DIR
94- * @returns {string } updated markdown file content
106+ * Adjusts markdown content for compatibility with Docusaurus
107+ * Adds import examples and fixes formatting issues
108+ * @param {string } content - Original markdown content
109+ * @param {string } relativePath - Relative path to the JS file
110+ * @returns {string } - Modified markdown content
95111 */
96112function modifyMarkdown ( content , relativePath ) {
97113 const anchorIndex = content . indexOf ( '<a name' ) ;
98114 if ( anchorIndex !== - 1 ) {
115+ // Remove every content that appears before anchor tag
116+ // as non-required content might get generated
99117 content = content . slice ( anchorIndex ) ;
100118 }
101119
@@ -104,13 +122,47 @@ function modifyMarkdown(content, relativePath) {
104122 path . basename ( relativePath , '.md' )
105123 ) . replace ( / \\ / g, '/' ) ;
106124
107- const importStatement = '### Import :\n' +
108- `\`\`\`js\nconst ${ path . basename ( relativePath , '.md' ) } = ` +
125+ const importStatement = `### Import :\n` +
126+ `\`\`\`js\n` +
127+ `const ${ path . basename ( relativePath , '.md' ) } = ` +
109128 `brackets.getModule("${ modulePath } ")\n\`\`\`\n\n` ;
110129
130+ // brackets~getModule is wrong
131+ // brackets.getModule
111132 return importStatement + content . replace ( / ~ / g, '.' ) ;
112133}
113134
135+
136+ /**
137+ * Normalizes line endings to LF (\n)
138+ * to ensure consistent comparisons between files
139+ * @param {string } content - Content with potentially mixed line endings
140+ * @returns {string } - Content with normalized line endings
141+ */
142+ function normalizeLineEndings ( content ) {
143+ return content . replace ( / \r \n | \r / g, '\n' ) ;
144+ }
145+
146+
147+ /**
148+ * Compare two files based on their MD5 hash values
149+ * @param {string } file1 - Path to the first file
150+ * @param {string } file2 - Path to the second file
151+ * @returns {Promise<boolean> } - True if files are different, false otherwise
152+ */
153+ async function areFilesDifferent ( file1 , file2 ) {
154+ const [ content1 , content2 ] = await Promise . all ( [
155+ fs . readFile ( file1 , 'utf-8' ) . then ( normalizeLineEndings ) ,
156+ fs . readFile ( file2 , 'utf-8' ) . then ( normalizeLineEndings )
157+ ] ) ;
158+
159+ const hash1 = crypto . createHash ( 'md5' ) . update ( content1 ) . digest ( 'hex' ) ;
160+ const hash2 = crypto . createHash ( 'md5' ) . update ( content2 ) . digest ( 'hex' ) ;
161+
162+ return hash1 !== hash2 ;
163+ }
164+
165+
114166/**
115167 * Generates markdown documentation for a given JavaScript file
116168 * @param {string } file Path to the JavaScript file
@@ -127,15 +179,50 @@ async function generateMarkdown(file, relativePath) {
127179 await createDir ( outputDir ) ;
128180
129181 const outputFileName = path . join ( outputDir , `${ fileName } .md` ) ;
130- await exec ( `npx jsdoc-to-markdown ${ file } > ${ outputFileName } ` ) ;
182+ const tempOutputFileName = path . join (
183+ TEMP_CHECK_DIR , `${ fileName } _temp.md`
184+ ) ;
185+
186+ await createDir ( TEMP_CHECK_DIR ) ;
187+
188+ // Generate markdown to a temporary file
189+ await exec ( `npx jsdoc-to-markdown ${ file } > ${ tempOutputFileName } ` ) ;
190+
191+ let markdownContent = await fs . readFile ( tempOutputFileName , 'utf-8' ) ;
192+ const updatedMarkdownContent = modifyMarkdown (
193+ markdownContent , path . join ( relativePath , fileName )
194+ ) ;
195+
196+ await fs . writeFile ( tempOutputFileName , updatedMarkdownContent , 'utf-8' ) ;
197+
198+ const fileExists = await fs . access ( outputFileName ) . then ( ( ) => true ) . catch (
199+ ( ) => false
200+ ) ;
201+
202+ const shouldUpdate = ! fileExists || await areFilesDifferent (
203+ outputFileName , tempOutputFileName
204+ ) ;
131205
132- const markdownContent = await fs . readFile ( outputFileName , 'utf-8' ) ;
133- const updatedMarkdownContent = modifyMarkdown ( markdownContent , path . join ( relativePath , fileName ) ) ;
134- await fs . writeFile ( outputFileName , updatedMarkdownContent , 'utf-8' ) ;
206+ if ( shouldUpdate ) {
207+ await fs . rename ( tempOutputFileName , outputFileName ) ;
208+ console . log ( `Updated ${ outputFileName } ` ) ;
209+ } else {
210+ await fs . unlink ( tempOutputFileName ) ;
211+ console . log ( `No changes in ${ outputFileName } ` ) ;
212+ }
135213}
136214
215+
216+ /**
217+ * Cleans up temp directories
218+ */
219+ async function cleanupTempDir ( ) {
220+ await removeDir ( TEMP_CHECK_DIR ) ;
221+ }
222+
223+
137224/**
138- * Handles the execution and control flow of the program
225+ * Driver function
139226 */
140227async function driver ( ) {
141228 try {
@@ -146,12 +233,12 @@ async function driver() {
146233 await createDir ( TEMP_DIR ) ;
147234 await createDir ( MD_FILES_DIR ) ;
148235
149- // Process files in batches to avoid overwhelming the system
150- const BATCH_SIZE = 12 ;
151236 for ( let i = 0 ; i < jsFiles . length ; i += BATCH_SIZE ) {
152237 const batch = jsFiles . slice ( i , i + BATCH_SIZE ) ;
153238 await Promise . all ( batch . map ( async ( file ) => {
154- const relativePath = path . relative ( SRC_DIR , path . dirname ( file ) ) ;
239+ const relativePath = path . relative (
240+ SRC_DIR , path . dirname ( file )
241+ ) ;
155242 const tempDirPath = path . join ( TEMP_DIR , relativePath ) ;
156243 await createDir ( tempDirPath ) ;
157244
@@ -165,11 +252,12 @@ async function driver() {
165252 }
166253
167254 await removeDir ( TEMP_DIR ) ;
255+ await cleanupTempDir ( ) ;
168256 console . log ( "All files processed successfully!" ) ;
169257 } catch ( error ) {
170258 console . error ( "An error occurred:" , error ) ;
171- // Cleanup temp directory in case of error
172259 await removeDir ( TEMP_DIR ) . catch ( ( ) => { } ) ;
260+ await cleanupTempDir ( ) . catch ( ( ) => { } ) ;
173261 }
174262}
175263
0 commit comments