@@ -31,6 +31,7 @@ interface SourceSpec {
31
31
inline : string
32
32
dependencies ?: Record < string , string >
33
33
yarnLock ?: string
34
+ tsConfig ?: string
34
35
}
35
36
36
37
interface InputSpec {
@@ -54,6 +55,95 @@ interface Manifest {
54
55
}
55
56
}
56
57
58
+ interface TypeScriptConfig {
59
+ extends ?: string
60
+ compilerOptions ?: {
61
+ baseUrl ?: string
62
+ paths ?: Record < string , string [ ] >
63
+ [ key : string ] : any
64
+ }
65
+ [ key : string ] : any
66
+ }
67
+
68
+ /**
69
+ * Loads and resolves a TypeScript configuration file, handling extends
70
+ * @param configPath Path to the tsconfig.json file
71
+ * @param baseDir Base directory for resolving relative paths
72
+ * @returns Resolved TypeScript configuration
73
+ */
74
+ function loadTsConfig ( configPath : string , _baseDir ?: string ) : TypeScriptConfig | null {
75
+ try {
76
+ if ( ! fs . existsSync ( configPath ) ) {
77
+ return null
78
+ }
79
+
80
+ const configContent = fs . readFileSync ( configPath , { encoding : "utf8" } )
81
+ const config : TypeScriptConfig = JSON . parse ( configContent )
82
+ const configDir = path . dirname ( configPath )
83
+
84
+ // If the config extends another config, load and merge it
85
+ if ( config . extends ) {
86
+ const extendsPath = path . resolve ( configDir , config . extends )
87
+ const baseConfig = loadTsConfig ( extendsPath , configDir )
88
+
89
+ if ( baseConfig ) {
90
+ // Merge configurations, with current config taking precedence
91
+ const mergedConfig : TypeScriptConfig = {
92
+ ...baseConfig ,
93
+ ...config ,
94
+ compilerOptions : {
95
+ ...baseConfig . compilerOptions ,
96
+ ...config . compilerOptions ,
97
+ } ,
98
+ }
99
+ return mergedConfig
100
+ }
101
+ }
102
+
103
+ return config
104
+ } catch ( error ) {
105
+ moduleLogger . debug ( `Failed to load TypeScript config from ${ configPath } : ${ error } ` )
106
+ return null
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Converts TypeScript path mappings to esbuild alias format
112
+ * @param tsConfig TypeScript configuration
113
+ * @param baseDir Base directory for resolving relative paths
114
+ * @returns esbuild alias configuration
115
+ */
116
+ function convertTsPathsToEsbuildAlias (
117
+ tsConfig : TypeScriptConfig ,
118
+ baseDir : string
119
+ ) : Record < string , string > | undefined {
120
+ const { compilerOptions } = tsConfig
121
+ if ( ! compilerOptions ?. paths ) {
122
+ return undefined
123
+ }
124
+
125
+ const { paths } = compilerOptions
126
+ const alias : Record < string , string > = { }
127
+
128
+ for ( const [ pattern , mappings ] of Object . entries ( paths ) ) {
129
+ if ( mappings . length === 0 ) continue
130
+
131
+ // Take the first mapping (most common case)
132
+ const mapping = mappings [ 0 ]
133
+
134
+ // Remove the /* suffix from pattern and mapping if present
135
+ const cleanPattern = pattern . replace ( / \/ \* $ / , "" )
136
+ const cleanMapping = mapping . replace ( / \/ \* $ / , "" )
137
+
138
+ // Resolve the mapping path relative to the base directory
139
+ const resolvedMapping = path . resolve ( baseDir , cleanMapping )
140
+
141
+ alias [ cleanPattern ] = resolvedMapping
142
+ }
143
+
144
+ return Object . keys ( alias ) . length > 0 ? alias : undefined
145
+ }
146
+
57
147
/**
58
148
* Bundles a TypeScript file using esbuild
59
149
* @param filePath Path to the TypeScript file
@@ -73,15 +163,90 @@ async function bundleTypeScript(
73
163
moduleLogger . debug ( `Using temporary directory: ${ tempDir } ` )
74
164
75
165
try {
76
- // Create temp directory
166
+ // Create temp directory for output only
77
167
fs . mkdirSync ( tempDir , { recursive : true } )
78
168
79
169
// Get original file size for logging
80
170
const originalSize = fs . statSync ( filePath ) . size
81
171
82
- // Default esbuild options optimized for readability
172
+ // Check if models/index.ts exists and prepare to auto-import it
173
+ const packageRoot = process . cwd ( )
174
+ const modelsIndexPath = path . join ( packageRoot , "models" , "index.ts" )
175
+ const shouldAutoImportModels = fs . existsSync ( modelsIndexPath )
176
+
177
+ // Create virtual entry content that imports models and re-exports the function
178
+ const relativeFunctionPath = path . relative ( packageRoot , filePath ) . replace ( / \\ / g, "/" )
179
+ let virtualEntryContent = ""
180
+
181
+ if ( shouldAutoImportModels ) {
182
+ virtualEntryContent += `import './models';\n`
183
+ moduleLogger . debug ( `Auto-importing models from: ${ modelsIndexPath } ` )
184
+ }
185
+
186
+ // Import and re-export the function as default
187
+ virtualEntryContent += `export { default } from './${ relativeFunctionPath } ';\n`
188
+
189
+ moduleLogger . debug ( `Virtual entry content:\n${ virtualEntryContent } ` )
190
+
191
+ // Load TypeScript configuration and extract aliases
192
+ const functionDir = path . dirname ( filePath )
193
+ let tsConfig : TypeScriptConfig | null = null
194
+ let esbuildAlias : Record < string , string > | undefined = undefined
195
+
196
+ // Try to load tsconfig.json from function directory first, then from current working directory
197
+ const functionTsConfigPath = path . join ( functionDir , "tsconfig.json" )
198
+ const cwdTsConfigPath = path . join ( process . cwd ( ) , "tsconfig.json" )
199
+
200
+ let configPath : string | null = null
201
+ let configBaseDir : string | null = null
202
+
203
+ if ( fs . existsSync ( functionTsConfigPath ) ) {
204
+ tsConfig = loadTsConfig ( functionTsConfigPath , functionDir )
205
+ configPath = functionTsConfigPath
206
+ configBaseDir = functionDir
207
+ moduleLogger . debug (
208
+ `Loaded TypeScript config from function directory: ${ functionTsConfigPath } `
209
+ )
210
+ } else if ( fs . existsSync ( cwdTsConfigPath ) ) {
211
+ tsConfig = loadTsConfig ( cwdTsConfigPath , process . cwd ( ) )
212
+ configPath = cwdTsConfigPath
213
+ configBaseDir = process . cwd ( )
214
+ moduleLogger . debug (
215
+ `Loaded TypeScript config from current working directory: ${ cwdTsConfigPath } `
216
+ )
217
+ }
218
+
219
+ // Convert TypeScript path aliases to esbuild aliases
220
+ if ( tsConfig && configPath && configBaseDir ) {
221
+ const configDir = path . dirname ( configPath )
222
+ const baseUrl = tsConfig . compilerOptions ?. baseUrl || "."
223
+ const resolvedBaseDir = path . resolve ( configDir , baseUrl )
224
+
225
+ moduleLogger . debug ( `TypeScript config found at: ${ configPath } ` )
226
+ moduleLogger . debug ( `Config directory: ${ configDir } ` )
227
+ moduleLogger . debug ( `Base URL: ${ baseUrl } ` )
228
+ moduleLogger . debug ( `Resolved base directory: ${ resolvedBaseDir } ` )
229
+ moduleLogger . debug ( `TypeScript paths: ${ JSON . stringify ( tsConfig . compilerOptions ?. paths ) } ` )
230
+
231
+ esbuildAlias = convertTsPathsToEsbuildAlias ( tsConfig , resolvedBaseDir )
232
+
233
+ if ( esbuildAlias && Object . keys ( esbuildAlias ) . length > 0 ) {
234
+ moduleLogger . info (
235
+ `Applied TypeScript path aliases to esbuild: ${ JSON . stringify ( esbuildAlias ) } `
236
+ )
237
+ moduleLogger . info ( `Base directory for aliases: ${ resolvedBaseDir } ` )
238
+ } else {
239
+ moduleLogger . debug ( `No TypeScript path aliases found or converted` )
240
+ }
241
+ }
242
+
243
+ // Default esbuild options optimized for readability using stdin
83
244
const defaultOptions : BuildOptions = {
84
- entryPoints : [ filePath ] ,
245
+ stdin : {
246
+ contents : virtualEntryContent ,
247
+ resolveDir : packageRoot ,
248
+ sourcefile : "virtual-entry.ts" ,
249
+ } ,
85
250
bundle : true ,
86
251
format : "esm" ,
87
252
sourcemap : true ,
@@ -96,16 +261,50 @@ async function bundleTypeScript(
96
261
experimentalDecorators : true ,
97
262
} ,
98
263
} ,
264
+ // Add TypeScript path aliases if available
265
+ ...( esbuildAlias && { alias : esbuildAlias } ) ,
99
266
}
100
267
101
268
// If embedDeps is false, add plugin to keep dependencies external
102
269
if ( ! embedDeps ) {
103
- // Create a plugin to mark all non-relative imports as external
270
+ // Create a plugin to mark all non-relative imports as external, except for aliases
104
271
const externalizeNpmDepsPlugin : Plugin = {
105
272
name : "externalize-npm-deps" ,
106
273
setup ( build ) {
107
- // Filter for all import paths that don't start with ./ or ../
274
+ // Filter for imports that don't start with ./ or ../ (non-relative imports)
108
275
build . onResolve ( { filter : / ^ [ ^ . / ] / } , args => {
276
+ moduleLogger . debug ( `Processing import: ${ args . path } from ${ args . importer || "entry" } ` )
277
+
278
+ // Skip if this is an entry point (no importer means it's an entry point)
279
+ if ( ! args . importer ) {
280
+ moduleLogger . debug ( `Skipping entry point: ${ args . path } ` )
281
+ return undefined
282
+ }
283
+
284
+ // Skip built-in Node.js modules
285
+ if ( args . path . startsWith ( "node:" ) ) {
286
+ moduleLogger . debug ( `Skipping Node.js built-in: ${ args . path } ` )
287
+ return undefined
288
+ }
289
+
290
+ // Check if this matches any of our TypeScript path aliases
291
+ // Be more specific about alias matching to avoid false positives with scoped packages
292
+ if ( esbuildAlias ) {
293
+ for ( const alias of Object . keys ( esbuildAlias ) ) {
294
+ // For aliases like "@", only match if it's followed by a slash (e.g., "@/models")
295
+ // This prevents matching scoped packages like "@crossplane-js/sdk"
296
+ if ( alias === "@" && args . path . startsWith ( "@/" ) ) {
297
+ moduleLogger . debug ( `Allowing alias resolution: ${ args . path } ` )
298
+ return undefined // Let esbuild handle the alias resolution
299
+ } else if ( alias !== "@" && args . path . startsWith ( alias ) ) {
300
+ moduleLogger . debug ( `Allowing alias resolution: ${ args . path } ` )
301
+ return undefined // Let esbuild handle the alias resolution
302
+ }
303
+ }
304
+ }
305
+
306
+ // For all other imports (npm packages, scoped packages, etc.), mark as external
307
+ moduleLogger . debug ( `Marking as external: ${ args . path } ` )
109
308
return { path : args . path , external : true }
110
309
} )
111
310
} ,
@@ -124,11 +323,11 @@ async function bundleTypeScript(
124
323
// Bundle with esbuild
125
324
await build ( buildOptions )
126
325
127
- // Read the bundled code
326
+ // Read the bundled code (always single entry point now)
128
327
const bundledCode = fs . readFileSync ( outputFile , { encoding : "utf8" } )
328
+ const bundledSize = fs . statSync ( outputFile ) . size
129
329
130
330
// Log bundle size information
131
- const bundledSize = fs . statSync ( outputFile ) . size
132
331
moduleLogger . debug ( `Bundling complete: ${ originalSize } bytes → ${ bundledSize } bytes` )
133
332
134
333
return bundledCode
@@ -398,6 +597,38 @@ async function compoAction(
398
597
}
399
598
}
400
599
600
+ if ( xfuncjsStep . input . spec . source . tsConfig === "__TSCONFIG__" ) {
601
+ // Check for tsconfig.json in the function directory
602
+ const functionTsConfigPath = path . join ( functionDir , "tsconfig.json" )
603
+ const rootTsConfigPath = path . join ( cwd ( ) , "tsconfig.json" )
604
+ let tsConfig : string | null = null
605
+
606
+ if ( fs . existsSync ( functionTsConfigPath ) ) {
607
+ try {
608
+ // Use tsconfig.json from the function directory
609
+ tsConfig = fs . readFileSync ( functionTsConfigPath , {
610
+ encoding : "utf8" ,
611
+ } )
612
+ moduleLogger . debug ( `Using tsconfig.json from function directory: ${ functionName } ` )
613
+ } catch ( error ) {
614
+ moduleLogger . error ( `Error reading tsconfig.json in function directory: ${ error } ` )
615
+ }
616
+ } else if ( fs . existsSync ( rootTsConfigPath ) ) {
617
+ try {
618
+ // Use tsconfig.json from the current working directory
619
+ tsConfig = fs . readFileSync ( rootTsConfigPath , { encoding : "utf8" } )
620
+ moduleLogger . debug ( `Using tsconfig.json from current working directory` )
621
+ } catch ( error ) {
622
+ moduleLogger . error ( `Error reading tsconfig.json in current working directory: ${ error } ` )
623
+ }
624
+ }
625
+
626
+ // Add tsconfig.json to the manifest if found
627
+ if ( tsConfig ) {
628
+ xfuncjsStep . input . spec . source . tsConfig = tsConfig
629
+ }
630
+ }
631
+
401
632
// Generate final output using the already loaded XRD data
402
633
let finalOutput : string
403
634
0 commit comments