11#!/usr/bin/env node --experimental-strip-types
2+ /* eslint-disable n/prefer-global/process */
23import { readFileSync , writeFileSync } from 'node:fs' ;
4+ import { execSync } from 'node:child_process' ;
35// Import index.ts to populate the test data via side effect
46// eslint-disable-next-line import/no-unassigned-import, n/file-extension-in-import
57import './index.ts' ;
@@ -10,9 +12,17 @@ import {getTests} from './collector.ts';
1012const dtsPath = './distribution/index.d.ts' ;
1113const dtsContent = readFileSync ( dtsPath , 'utf8' ) ;
1214
15+ // Check if script has already been run
16+ const marker = '/* Examples added by add-examples-to-dts.ts */' ;
17+ if ( dtsContent . includes ( marker ) ) {
18+ console . error ( '❌ Error: Examples have already been added to this file' ) ;
19+ process . exit ( 1 ) ;
20+ }
21+
1322// Process each exported function
1423const lines = dtsContent . split ( '\n' ) ;
1524const outputLines : string [ ] = [ ] ;
25+ let examplesAdded = 0 ;
1626
1727for ( const line of lines ) {
1828 // Check if this is a function declaration
@@ -24,28 +34,122 @@ for (const line of lines) {
2434 const examples = getTests ( functionName ) ;
2535
2636 // Only add examples if they exist and aren't the special 'combinedTestOnly' marker
27- // 'combinedTestOnly' is used to skip tests for combined functions (e.g., isPageA() || isPageB())
2837 if ( examples && examples . length > 0 && examples [ 0 ] !== 'combinedTestOnly' ) {
2938 // Filter to only include actual URLs (not references to other functions)
30- // getTests() recursively expands function references, so we just need to filter the final list
3139 const urlExamples = examples . filter ( ( url : string ) => url . startsWith ( 'http' ) ) ;
3240
3341 if ( urlExamples . length > 0 ) {
34- // Add JSDoc comment with examples before the declaration
35- outputLines . push ( '/**' ) ;
36- for ( const url of urlExamples ) {
37- outputLines . push ( ` * @example ${ url } ` ) ;
42+ // Check if there's an existing JSDoc block immediately before this line
43+ let jsDocumentEndIndex = - 1 ;
44+ let jsDocumentStartIndex = - 1 ;
45+ let isSingleLineJsDocument = false ;
46+
47+ // Look backwards from outputLines to find JSDoc
48+ for ( let index = outputLines . length - 1 ; index >= 0 ; index -- ) {
49+ const previousLine = outputLines [ index ] ;
50+ const trimmed = previousLine . trim ( ) ;
51+
52+ if ( trimmed === '' ) {
53+ continue ; // Skip empty lines
54+ }
55+
56+ // Check for single-line JSDoc: /** ... */
57+ if ( trimmed . startsWith ( '/**' ) && trimmed . endsWith ( '*/' ) && trimmed . length > 5 ) {
58+ jsDocumentStartIndex = index ;
59+ jsDocumentEndIndex = index ;
60+ isSingleLineJsDocument = true ;
61+ break ;
62+ }
63+
64+ // Check for multi-line JSDoc ending
65+ if ( trimmed === '*/' ) {
66+ jsDocumentEndIndex = index ;
67+ // Now find the start of this JSDoc
68+ for ( let k = index - 1 ; k >= 0 ; k -- ) {
69+ if ( outputLines [ k ] . trim ( ) . startsWith ( '/**' ) ) {
70+ jsDocumentStartIndex = k ;
71+ break ;
72+ }
73+ }
74+
75+ break ;
76+ }
77+
78+ // If we hit a non-JSDoc line, there's no JSDoc block
79+ break ;
3880 }
3981
40- outputLines . push ( ' */' ) ;
82+ if ( jsDocumentStartIndex >= 0 && jsDocumentEndIndex >= 0 ) {
83+ // Extend existing JSDoc block
84+ if ( isSingleLineJsDocument ) {
85+ // Convert single-line to multi-line and add examples
86+ const singleLineContent = outputLines [ jsDocumentStartIndex ] ;
87+ // Extract the comment text without /** and */
88+ const commentText = singleLineContent . trim ( ) . slice ( 3 , - 2 ) . trim ( ) ;
89+
90+ // Replace the single line with multi-line format
91+ outputLines [ jsDocumentStartIndex ] = '/**' ;
92+ if ( commentText ) {
93+ outputLines . splice ( jsDocumentStartIndex + 1 , 0 , ` * ${ commentText } ` ) ;
94+ }
95+
96+ // Add examples after the existing content
97+ const insertIndex = jsDocumentStartIndex + ( commentText ? 2 : 1 ) ;
98+ for ( const url of urlExamples ) {
99+ outputLines . splice ( insertIndex + urlExamples . indexOf ( url ) , 0 , ` * @example ${ url } ` ) ;
100+ }
101+
102+ outputLines . splice ( insertIndex + urlExamples . length , 0 , ' */' ) ;
103+ examplesAdded += urlExamples . length ;
104+ } else {
105+ // Insert @example lines before the closing */
106+ for ( const url of urlExamples ) {
107+ outputLines . splice ( jsDocumentEndIndex , 0 , ` * @example ${ url } ` ) ;
108+ }
109+
110+ examplesAdded += urlExamples . length ;
111+ }
112+ } else {
113+ // Add new JSDoc comment with examples before the declaration
114+ outputLines . push ( '/**' ) ;
115+ for ( const url of urlExamples ) {
116+ outputLines . push ( ` * @example ${ url } ` ) ;
117+ }
118+
119+ outputLines . push ( ' */' ) ;
120+ examplesAdded += urlExamples . length ;
121+ }
41122 }
42123 }
43124 }
44125
45126 outputLines . push ( line ) ;
46127}
47128
129+ // Add marker at the beginning
130+ const finalContent = `${ marker } \n${ outputLines . join ( '\n' ) } ` ;
131+
132+ // Validate that we added some examples
133+ if ( examplesAdded === 0 ) {
134+ console . error ( '❌ Error: No examples were added. This likely indicates a problem with the script.' ) ;
135+ process . exit ( 1 ) ;
136+ }
137+
48138// Write the modified content back
49- writeFileSync ( dtsPath , outputLines . join ( '\n' ) , 'utf8' ) ;
139+ writeFileSync ( dtsPath , finalContent , 'utf8' ) ;
50140
51- console . log ( '✓ Added example URLs to index.d.ts' ) ;
141+ console . log ( `✓ Added ${ examplesAdded } example URLs to index.d.ts` ) ;
142+
143+ // Validate with TypeScript
144+ try {
145+ execSync ( 'npx tsc --noEmit distribution/index.d.ts' , {
146+ cwd : process . cwd ( ) ,
147+ stdio : 'pipe' ,
148+ } ) ;
149+ console . log ( '✓ TypeScript validation passed' ) ;
150+ } catch ( error : unknown ) {
151+ console . error ( '❌ TypeScript validation failed:' ) ;
152+ const execError = error as { stdout ? : Uint8Array ; stderr ? : Uint8Array ; message ? : string } ;
153+ console . error ( execError . stdout ?. toString ( ) ?? execError . stderr ?. toString ( ) ?? execError . message ) ;
154+ process . exit ( 1 ) ;
155+ }
0 commit comments