@@ -162,6 +162,10 @@ const BAD_SUBPATH_RE = /(['"])([^'"]*\/index\.(?:js|ts))(\/[^'"]+)\1/g;
162162let fixedFiles = 0 ;
163163let totalReplacements = 0 ;
164164
165+ function escapeRegExp ( value ) {
166+ return value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
167+ }
168+
165169function appendJsExtensionToRelativeSpecifier ( specifier , filePath ) {
166170 if ( ! specifier . startsWith ( './' ) && ! specifier . startsWith ( '../' ) ) return specifier ;
167171 if ( specifier . includes ( '?' ) || specifier . includes ( '#' ) ) return specifier ;
@@ -207,34 +211,62 @@ function rewriteDocApiPaths(fileContent, filePath) {
207211// paths the consumer can resolve.
208212//
209213// SD-2893 note for pm-adapter: only specific type subpaths are
210- // relocated (see vite.config.js include list). A bare `@superdoc/pm-adapter`
211- // specifier would rewrite to a relative path that does not exist in dist.
212- // The audit gate (RELOCATED_PACKAGES in audit-declarations.cjs) rejects
213- // any unrewritten bare specifier at build time, so this is a build-time
214- // failure rather than a silent consumer break. If a future public type
215- // genuinely needs the pm-adapter barrel, widen the vite include and the
216- // shim drain in lockstep.
214+ // relocated (see vite.config.js include list). Do not add a broad
215+ // `@superdoc/pm-adapter` rule unless the barrel declaration is also
216+ // emitted; otherwise a bare specifier would rewrite to a missing
217+ // relative path and evade the audit gate.
217218const RELOCATION_RULES = [
218- { pkg : '@superdoc/contracts' , distEntry : 'layout-engine/contracts/src/index.d.ts' } ,
219- { pkg : '@superdoc/dom-contract' , distEntry : 'layout-engine/dom-contract/src/index.d.ts' } ,
220- { pkg : '@superdoc/layout-bridge' , distEntry : 'layout-engine/layout-bridge/src/index.d.ts' } ,
221- { pkg : '@superdoc/painter-dom' , distEntry : 'layout-engine/painters/dom/src/index.d.ts' } ,
222- { pkg : '@superdoc/pm-adapter' , distEntry : 'layout-engine/pm-adapter/src/index.d.ts' } ,
219+ { pkg : '@superdoc/contracts' , distEntry : 'layout-engine/contracts/src/index.d.ts' , matchSubpaths : true } ,
220+ { pkg : '@superdoc/dom-contract' , distEntry : 'layout-engine/dom-contract/src/index.d.ts' , matchSubpaths : true } ,
221+ { pkg : '@superdoc/layout-bridge' , distEntry : 'layout-engine/layout-bridge/src/index.d.ts' , matchSubpaths : true } ,
222+ { pkg : '@superdoc/painter-dom' , distEntry : 'layout-engine/painters/dom/src/index.d.ts' , matchSubpaths : true } ,
223+ {
224+ pkg : '@superdoc/pm-adapter/converter-context.js' ,
225+ distEntry : 'layout-engine/pm-adapter/src/converter-context.d.ts' ,
226+ matchSubpaths : false ,
227+ } ,
228+ {
229+ pkg : '@superdoc/pm-adapter/sections/types.js' ,
230+ distEntry : 'layout-engine/pm-adapter/src/sections/types.d.ts' ,
231+ matchSubpaths : false ,
232+ } ,
233+ ] ;
234+
235+ // Guard packages that must never fall back to `_internal-shims.d.ts`.
236+ // `@superdoc/pm-adapter` is guarded as a root package even though only
237+ // two exact subpaths are relocated today; a future bare-barrel leak should
238+ // fail the build rather than ship as `any`.
239+ const RELOCATION_GUARD_PACKAGES = [
240+ '@superdoc/document-api' ,
241+ '@superdoc/contracts' ,
242+ '@superdoc/dom-contract' ,
243+ '@superdoc/layout-bridge' ,
244+ '@superdoc/painter-dom' ,
245+ '@superdoc/pm-adapter' ,
223246] ;
224247
225- function makeRelocationRewriter ( { pkg, distEntry } ) {
248+ function isRelocatedSpecifier ( mod ) {
249+ return RELOCATION_RULES . some ( ( rule ) =>
250+ rule . matchSubpaths
251+ ? mod === rule . pkg || mod . startsWith ( rule . pkg + '/' )
252+ : mod === rule . pkg ,
253+ ) ;
254+ }
255+
256+ function makeRelocationRewriter ( { pkg, distEntry, matchSubpaths } ) {
226257 // Match the package name with optional subpath, e.g. `@superdoc/contracts` or
227258 // `@superdoc/contracts/engines/tabs.js`. Anchored to either side of the
228259 // package segment so `@superdoc/contracts-something` is not matched.
229- const escaped = pkg . replace ( / \/ / g, '\\/' ) ;
230- const re = new RegExp ( `(['"])${ escaped } (\\/[^'"]+)?\\1` , 'g' ) ;
260+ const escaped = escapeRegExp ( pkg ) ;
261+ const subpathPattern = matchSubpaths ? `(\\/[^'"]+)?` : '' ;
262+ const re = new RegExp ( `(['"])${ escaped } ${ subpathPattern } \\1` , 'g' ) ;
231263 return ( fileContent , filePath ) => {
232264 return fileContent . replace ( re , ( _match , quote , subpath = '' ) => {
233265 const target = path . join ( distRoot , distEntry ) ;
234266 let rel = path . relative ( path . dirname ( filePath ) , target ) . split ( path . sep ) . join ( '/' ) ;
235267 if ( ! rel . startsWith ( '.' ) ) rel = './' + rel ;
236268 rel = rel . replace ( / \. d \. t s $ / , '.js' ) ;
237- if ( subpath ) rel = rel . replace ( / \/ i n d e x \. j s $ / , subpath ) ;
269+ if ( matchSubpaths && subpath ) rel = rel . replace ( / \/ i n d e x \. j s $ / , subpath ) ;
238270 return `${ quote } ${ rel } ${ quote } ` ;
239271 } ) ;
240272 } ;
@@ -245,6 +277,23 @@ const RELOCATION_REWRITERS = RELOCATION_RULES.map((rule) => ({
245277 rewrite : makeRelocationRewriter ( rule ) ,
246278} ) ) ;
247279
280+ // Any root specifier added here should also be listed in
281+ // RELOCATION_GUARD_PACKAGES so it cannot fall back to an ambient `any`
282+ // shim after we intentionally skip shim generation.
283+ const UNSHIMMED_PRIVATE_SPECIFIERS = new Set ( [
284+ '@superdoc/pm-adapter' ,
285+ ] ) ;
286+
287+ function shouldSkipWorkspaceShim ( mod ) {
288+ return (
289+ mod . startsWith ( '.' ) ||
290+ mod . startsWith ( '@superdoc/super-editor' ) ||
291+ mod . startsWith ( '@superdoc/document-api' ) ||
292+ isRelocatedSpecifier ( mod ) ||
293+ UNSHIMMED_PRIVATE_SPECIFIERS . has ( mod )
294+ ) ;
295+ }
296+
248297const dtsFiles = findDtsFiles ( distRoot ) ;
249298for ( const filePath of dtsFiles ) {
250299 let fileContent = fs . readFileSync ( filePath , 'utf8' ) ;
@@ -387,7 +436,7 @@ for (const filePath of dtsFiles) {
387436 const mod = m [ 2 ] ;
388437
389438 // Skip relative imports and already-handled packages
390- if ( mod . startsWith ( '.' ) || mod . startsWith ( '@superdoc/super-editor' ) || mod . startsWith ( '@superdoc/document-api' ) || RELOCATION_RULES . some ( ( r ) => mod === r . pkg || mod . startsWith ( r . pkg + '/' ) ) ) continue ;
439+ if ( shouldSkipWorkspaceShim ( mod ) ) continue ;
391440
392441 if ( mod . startsWith ( '@superdoc/' ) ) {
393442 if ( ! workspaceImports . has ( mod ) ) workspaceImports . set ( mod , new Set ( ) ) ;
@@ -400,7 +449,7 @@ for (const filePath of dtsFiles) {
400449 const dynamicImports = fileContent . matchAll ( / i m p o r t \( [ ' " ] ( [ ^ ' " ] + ) [ ' " ] \) \. ( \w + ) / g) ;
401450 for ( const m of dynamicImports ) {
402451 const mod = m [ 1 ] ;
403- if ( mod . startsWith ( '.' ) || mod . startsWith ( '@superdoc/super-editor' ) || mod . startsWith ( '@superdoc/document-api' ) || RELOCATION_RULES . some ( ( r ) => mod === r . pkg || mod . startsWith ( r . pkg + '/' ) ) ) continue ;
452+ if ( shouldSkipWorkspaceShim ( mod ) ) continue ;
404453
405454 if ( mod . startsWith ( '@superdoc/' ) ) {
406455 if ( ! workspaceImports . has ( mod ) ) workspaceImports . set ( mod , new Set ( ) ) ;
@@ -420,7 +469,7 @@ for (const filePath of dtsFiles) {
420469 // resolve through the shim and collapse internal-only types
421470 // (Comment, CommentContent, CommentJSON) to `any`. None of those
422471 // appear on superdoc's public surface, so the collapse is safe.
423- if ( mod . startsWith ( '@superdoc/super-editor' ) || mod . startsWith ( '@superdoc/document-api' ) || RELOCATION_RULES . some ( ( r ) => mod === r . pkg || mod . startsWith ( r . pkg + '/' ) ) ) continue ;
472+ if ( shouldSkipWorkspaceShim ( mod ) ) continue ;
424473 if ( ! workspaceImports . has ( mod ) ) workspaceImports . set ( mod , new Set ( ) ) ;
425474 }
426475}
@@ -501,11 +550,11 @@ console.log(`[ensure-types] ✓ Generated ambient shims for ${wsCount} workspace
501550// rewrite or include for that package and customers would see `any`
502551// for those types again.
503552const shimContent = fs . readFileSync ( shimPath , 'utf8' ) ;
504- const SHIM_FORBIDDEN = [ '@superdoc/document-api' , ... RELOCATION_RULES . map ( ( r ) => r . pkg ) ] ;
553+ const SHIM_FORBIDDEN = RELOCATION_GUARD_PACKAGES ;
505554for ( const pkg of SHIM_FORBIDDEN ) {
506- const re = new RegExp ( `declare module '${ pkg . replace ( / \/ / g , '\\/' ) } (\\/[^']+)?'` ) ;
555+ const re = new RegExp ( `declare module '${ escapeRegExp ( pkg ) } (\\/[^']+)?'` ) ;
507556 if ( re . test ( shimContent ) ) {
508- console . error ( `[ensure-types] ✗ ${ pkg } appears in _internal-shims.d.ts. Its types should resolve via the relocation rewrite, not via an ambient any shim. Investigate the include glob, the rewrite rule, and the shim-skip predicate for this package.` ) ;
557+ console . error ( `[ensure-types] ✗ ${ pkg } appears in _internal-shims.d.ts. Its types should resolve via a relocation rewrite or fail the audit as an unrelocated leak , not via an ambient any shim. Investigate the include glob, the rewrite rule, and the shim-skip predicate for this package.` ) ;
509558 process . exit ( 1 ) ;
510559 }
511560}
0 commit comments