1+ import fs from 'node:fs' ;
2+ import path from 'node:path' ;
3+
14const CALLBACK_BOOTSTRAP_IMPORT = './src/runtime/initServerCallback.ts' ;
25const CALLBACK_BOOTSTRAP_PREFIX = './src/runtime/' ;
36const USERLAND_EXPOSE_PREFIX = './' ;
@@ -7,6 +10,21 @@ const CALLBACK_BOOTSTRAP_EXPOSE_KEY_PATTERN =
710 / ^ \. \/ (?: R e m o t e C l i e n t [ \w - ] * | a c t i o n s | n e s t e d A c t i o n s | d e f a u l t A c t i o n | a c t i o n B u n d l e ) $ / ;
811const CALLBACK_BOOTSTRAP_IMPORT_PATH_PATTERN =
912 / \/ (?: R e m o t e C l i e n t [ \w - ] * | a c t i o n s | n e s t e d A c t i o n s | d e f a u l t A c t i o n | a c t i o n B u n d l e ) \. [ c m ] ? [ j t ] s x ? $ / ;
13+ const LOCAL_MODULE_SPECIFIER_PATTERN = / ^ \. { 1 , 2 } \/ / ;
14+ const SOURCE_DIRECTIVE_PATTERN = / ^ \s * [ ' " ] u s e (?: c l i e n t | s e r v e r ) [ ' " ] \s * ; ? / m;
15+ const EXPORT_FROM_SPECIFIER_PATTERN =
16+ / e x p o r t \s + (?: \* \s + f r o m | \{ [ ^ } ] * \} \s + f r o m ) \s * [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / g;
17+ const SOURCE_ENTRY_EXTENSIONS = [
18+ '.ts' ,
19+ '.tsx' ,
20+ '.js' ,
21+ '.jsx' ,
22+ '.mts' ,
23+ '.cts' ,
24+ '.mjs' ,
25+ '.cjs' ,
26+ ] as const ;
27+ const REMOTE_PROJECT_ROOT = path . resolve ( __dirname , '../..' ) ;
1028
1129type ExposeImportInput = string | string [ ] ;
1230export type ExposeDefinitionInput =
@@ -121,9 +139,87 @@ const shouldInjectCallbackBootstrap = (
121139 importPaths : string [ ] ,
122140) =>
123141 CALLBACK_BOOTSTRAP_EXPOSE_KEY_PATTERN . test ( exposeKey ) ||
124- importPaths . some ( importPath =>
125- CALLBACK_BOOTSTRAP_IMPORT_PATH_PATTERN . test ( importPath ) ,
142+ importPaths . some (
143+ importPath =>
144+ CALLBACK_BOOTSTRAP_IMPORT_PATH_PATTERN . test ( importPath ) ||
145+ referencesCallbackCapableSourceModule ( importPath ) ,
146+ ) ;
147+
148+ const readSourceFile = ( filePath : string ) => {
149+ try {
150+ return fs . readFileSync ( filePath , 'utf8' ) ;
151+ } catch {
152+ return undefined ;
153+ }
154+ } ;
155+
156+ const resolveUserlandImportPathToFile = (
157+ importPath : string ,
158+ fromFilePath ?: string ,
159+ ) => {
160+ const importPathWithoutPrefix = importPath . replace ( / ^ \. \/ / , '' ) ;
161+ const basePath = fromFilePath
162+ ? path . resolve ( path . dirname ( fromFilePath ) , importPath )
163+ : path . resolve ( REMOTE_PROJECT_ROOT , importPathWithoutPrefix ) ;
164+ const hasExplicitExtension = SOURCE_ENTRY_EXTENSIONS . some ( extension =>
165+ basePath . endsWith ( extension ) ,
126166 ) ;
167+ if ( hasExplicitExtension ) {
168+ return fs . existsSync ( basePath ) ? basePath : undefined ;
169+ }
170+ const extensionCandidates = SOURCE_ENTRY_EXTENSIONS . map (
171+ extension => `${ basePath } ${ extension } ` ,
172+ ) ;
173+ const indexCandidates = SOURCE_ENTRY_EXTENSIONS . map ( extension =>
174+ path . join ( basePath , `index${ extension } ` ) ,
175+ ) ;
176+ return [ ...extensionCandidates , ...indexCandidates ] . find ( candidatePath =>
177+ fs . existsSync ( candidatePath ) ,
178+ ) ;
179+ } ;
180+
181+ const referencesCallbackCapableSourceModule = ( importPath : string ) => {
182+ const rootSourceFile = resolveUserlandImportPathToFile ( importPath ) ;
183+ if ( ! rootSourceFile ) {
184+ return false ;
185+ }
186+
187+ const visitedFiles = new Set < string > ( ) ;
188+ const hasCallbackDirective = ( filePath : string ) : boolean => {
189+ if ( visitedFiles . has ( filePath ) ) {
190+ return false ;
191+ }
192+ visitedFiles . add ( filePath ) ;
193+ const sourceText = readSourceFile ( filePath ) ;
194+ if ( ! sourceText ) {
195+ return false ;
196+ }
197+ if ( SOURCE_DIRECTIVE_PATTERN . test ( sourceText ) ) {
198+ return true ;
199+ }
200+
201+ const exportFromMatches = sourceText . matchAll (
202+ EXPORT_FROM_SPECIFIER_PATTERN ,
203+ ) ;
204+ for ( const match of exportFromMatches ) {
205+ const moduleSpecifier = match [ 1 ] ;
206+ if ( ! LOCAL_MODULE_SPECIFIER_PATTERN . test ( moduleSpecifier ) ) {
207+ continue ;
208+ }
209+ const childModuleFilePath = resolveUserlandImportPathToFile (
210+ moduleSpecifier ,
211+ filePath ,
212+ ) ;
213+ if ( childModuleFilePath && hasCallbackDirective ( childModuleFilePath ) ) {
214+ return true ;
215+ }
216+ }
217+
218+ return false ;
219+ } ;
220+
221+ return hasCallbackDirective ( rootSourceFile ) ;
222+ } ;
127223
128224const assertValidExposeConfig = (
129225 normalizedExposeImportPaths : NormalizedExposeImportPaths ,
0 commit comments