@@ -2138,13 +2138,58 @@ async function patchModuleAugmentations() {
21382138
21392139 const innerContent = content . slice ( braceStart + 1 , braceEnd ) . trim ( ) ;
21402140
2141- // Merge augmented types into the target .d.ts file
2141+ // Merge only NEW type declarations into the target .d.ts file.
2142+ // Interfaces that already exist (e.g., ExpectStatic, Assertion, MatcherState) must NOT
2143+ // be re-declared, as that would shadow extends clauses and break call signatures.
21422144 if ( innerContent && existsSync ( targetFile ) ) {
21432145 let targetContent = await readFile ( targetFile , 'utf-8' ) ;
2144- const exportBlock = innerContent . replace ( / ^ ( \t * ) i n t e r f a c e / gm, '$1export interface ' ) ;
2145- targetContent += `\n// Merged from module augmentation: declare module "${ bareSpecifier } "\n${ exportBlock } \n` ;
2146- await writeFile ( targetFile , targetContent , 'utf-8' ) ;
2147- console . log ( ` Merged augmentation "${ bareSpecifier } " into ${ basename ( targetFile ) } ` ) ;
2146+
2147+ // Extract individual interface blocks from the augmentation content
2148+ const interfaceRegex = / (?: e x p o r t \s + ) ? i n t e r f a c e \s + ( \w + ) (?: < [ ^ > ] * > ) ? \s * \{ / g;
2149+ let match ;
2150+ const newDeclarations : string [ ] = [ ] ;
2151+
2152+ while ( ( match = interfaceRegex . exec ( innerContent ) ) !== null ) {
2153+ const name = match [ 1 ] ;
2154+ // Only merge if this interface does NOT already exist in the target file.
2155+ // Check both direct declarations (interface Name) and re-exports (export type { Name }).
2156+ const hasDirectDecl = new RegExp ( `\\binterface\\s+${ name } \\b` ) . test ( targetContent ) ;
2157+ const exportTypeMatch = targetContent . match ( / e x p o r t \s + t y p e \s * \{ ( [ ^ } ] * ) \} / ) ;
2158+ const isReExported = exportTypeMatch != null && new RegExp ( `\\b${ name } \\b` ) . test ( exportTypeMatch [ 1 ] ) ;
2159+ if ( hasDirectDecl || isReExported ) {
2160+ console . log ( ` Skipped existing interface "${ name } " (already in ${ basename ( targetFile ) } )` ) ;
2161+ continue ;
2162+ }
2163+
2164+ // Extract this interface block using brace matching
2165+ const ifaceStart = match . index ;
2166+ const ifaceBraceStart = innerContent . indexOf ( '{' , ifaceStart ) ;
2167+ let ifaceDepth = 0 ;
2168+ let ifaceBraceEnd = - 1 ;
2169+ for ( let i = ifaceBraceStart ; i < innerContent . length ; i ++ ) {
2170+ if ( innerContent [ i ] === '{' ) ifaceDepth ++ ;
2171+ else if ( innerContent [ i ] === '}' ) {
2172+ ifaceDepth -- ;
2173+ if ( ifaceDepth === 0 ) {
2174+ ifaceBraceEnd = i ;
2175+ break ;
2176+ }
2177+ }
2178+ }
2179+ if ( ifaceBraceEnd === - 1 ) continue ;
2180+
2181+ let block = innerContent . slice ( ifaceStart , ifaceBraceEnd + 1 ) . trim ( ) ;
2182+ if ( ! block . startsWith ( 'export' ) ) {
2183+ block = `export ${ block } ` ;
2184+ }
2185+ newDeclarations . push ( block ) ;
2186+ console . log ( ` Merged new interface "${ name } " into ${ basename ( targetFile ) } ` ) ;
2187+ }
2188+
2189+ if ( newDeclarations . length > 0 ) {
2190+ targetContent += `\n// Merged from module augmentation: declare module "${ bareSpecifier } "\n${ newDeclarations . join ( '\n' ) } \n` ;
2191+ await writeFile ( targetFile , targetContent , 'utf-8' ) ;
2192+ }
21482193 }
21492194
21502195 // Rewrite declare module path to relative
@@ -2169,6 +2214,46 @@ async function patchModuleAugmentations() {
21692214 console . log ( ' Added BrowserCommands re-export to context.d.ts' ) ;
21702215 }
21712216 }
2217+
2218+ // Validate: ensure no duplicate top-level interface declarations were introduced by merging.
2219+ // Only count interfaces at the module scope (not nested inside declare global, namespace, etc.)
2220+ for ( const [ bareSpecifier , { targetFile } ] of Object . entries ( augmentationMappings ) ) {
2221+ if ( ! existsSync ( targetFile ) ) continue ;
2222+ const finalContent = await readFile ( targetFile , 'utf-8' ) ;
2223+
2224+ // Extract top-level interface names by tracking brace depth
2225+ const topLevelInterfaces : string [ ] = [ ] ;
2226+ let depth = 0 ;
2227+ for ( let i = 0 ; i < finalContent . length ; i ++ ) {
2228+ if ( finalContent [ i ] === '{' ) {
2229+ depth ++ ;
2230+ } else if ( finalContent [ i ] === '}' ) {
2231+ depth -- ;
2232+ } else if ( depth === 0 ) {
2233+ const remaining = finalContent . slice ( i ) ;
2234+ const m = remaining . match ( / ^ i n t e r f a c e \s + ( \w + ) / ) ;
2235+ if ( m ) {
2236+ topLevelInterfaces . push ( m [ 1 ] ) ;
2237+ i += m [ 0 ] . length - 1 ;
2238+ }
2239+ }
2240+ }
2241+
2242+ const counts = new Map < string , number > ( ) ;
2243+ for ( const name of topLevelInterfaces ) {
2244+ counts . set ( name , ( counts . get ( name ) || 0 ) + 1 ) ;
2245+ }
2246+
2247+ for ( const [ name , count ] of counts ) {
2248+ if ( count > 1 ) {
2249+ throw new Error (
2250+ `Interface "${ name } " is declared ${ count } times at top level in ${ basename ( targetFile ) } . ` +
2251+ `Module augmentation merge for "${ bareSpecifier } " likely created a duplicate ` +
2252+ `declaration that will shadow extends clauses and break type signatures.` ,
2253+ ) ;
2254+ }
2255+ }
2256+ }
21722257}
21732258
21742259/**
0 commit comments