@@ -253,22 +253,22 @@ test('esbuild bundling with esbuild options', () => {
253253 } ) ;
254254
255255 // Correctly bundles with esbuild
256- const defineInstructions = "' --define:process.env.KEY=\ "VALUE\"' '--define:process.env.BOOL=true' '--define:process.env.NUMBER=7777' '--define:process.env.STRING=\ "this is a \\\ "test\\\"\"'" ;
256+ const defineInstructions = '\' --define:process.env.KEY="VALUE"\' \ '--define:process.env.BOOL=true\' \ '--define:process.env.NUMBER=7777\' \ '--define:process.env.STRING="this is a \\"test\\""\'' ;
257257 expect ( Code . fromAsset ) . toHaveBeenCalledWith ( path . dirname ( depsLockFilePath ) , {
258258 assetHashType : AssetHashType . OUTPUT ,
259259 bundling : expect . objectContaining ( {
260260 command : [
261261 'bash' ,
262262 '-c' ,
263263 [
264- `' esbuild' '--bundle' '/asset-input/lib/handler.ts'` ,
265- `' --target=es2020' '--platform=node' '--format=esm' '--outfile=/asset-output/index.mjs'` ,
264+ '\' esbuild\' \ '--bundle\' \ '/asset-input/lib/handler.ts\'' ,
265+ '\' --target=es2020\' \ '--platform=node\' \ '--format=esm\' \ '--outfile=/asset-output/index.mjs\'' ,
266266 `'--minify' '--sourcemap' '--sources-content=false' '--external:${ STANDARD_EXTERNAL } ' '--loader:.png=dataurl'` ,
267267 defineInstructions ,
268- `' --log-level=silent' '--keep-names' '--tsconfig=/asset-input/lib/custom-tsconfig.ts'` ,
269- `' --metafile=/asset-output/index.meta.json' '--banner:js=/* comments */' '--footer:js=/* comments */'` ,
270- `' --main-fields=module,main' '--inject:./my-shim.js' '--inject:./path with space/second-shim.js'` ,
271- `' --log-limit=0' '--resolve-extensions=.ts,.js' '--splitting' '--keep-names' '--out-extension:.js=.mjs'` ,
268+ '\' --log-level=silent\' \ '--keep-names\' \ '--tsconfig=/asset-input/lib/custom-tsconfig.ts\'' ,
269+ '\' --metafile=/asset-output/index.meta.json\' \ '--banner:js=/* comments */\' \ '--footer:js=/* comments */\'' ,
270+ '\' --main-fields=module,main\' \ '--inject:./my-shim.js\' \ '--inject:./path with space/second-shim.js\'' ,
271+ '\' --log-limit=0\' \ '--resolve-extensions=.ts,.js\' \ '--splitting\' \ '--keep-names\' \ '--out-extension:.js=.mjs\'' ,
272272 ] . join ( ' ' ) ,
273273 ] ,
274274 } ) ,
@@ -394,7 +394,7 @@ test('esbuild bundling with feature flag enabled using Node Latest', () => {
394394 bundling : expect . objectContaining ( {
395395 command : [
396396 'bash' , '-c' ,
397- `' esbuild' '--bundle' '/asset-input/lib/handler.ts' '--target=node22' '--platform=node' '--outfile=/asset-output/index.js'` ,
397+ '\' esbuild\' \ '--bundle\' \ '/asset-input/lib/handler.ts\' \ '--target=node22\' \ '--platform=node\' \ '--outfile=/asset-output/index.js\'' ,
398398 ] ,
399399 } ) ,
400400 } ) ;
@@ -421,7 +421,7 @@ test('esbuild bundling with feature flag enabled using Node 16', () => {
421421 bundling : expect . objectContaining ( {
422422 command : [
423423 'bash' , '-c' ,
424- `' esbuild' '--bundle' '/asset-input/lib/handler.ts' '--target=node16' '--platform=node' '--outfile=/asset-output/index.js' '--external:aws-sdk'` ,
424+ '\' esbuild\' \ '--bundle\' \ '/asset-input/lib/handler.ts\' \ '--target=node16\' \ '--platform=node\' \ '--outfile=/asset-output/index.js\' \ '--external:aws-sdk\'' ,
425425 ] ,
426426 } ) ,
427427 } ) ;
@@ -869,8 +869,8 @@ test('esbuild bundling with pre compilations', () => {
869869 architecture : Architecture . X86_64 ,
870870 } ) ;
871871
872- const compilerOptions = util . getTsconfigCompilerOptions ( findParentTsConfigPath ( __dirname ) ) ;
873- const quotedCompilerOptions = compilerOptions . split ( / \s + / ) . filter ( Boolean ) . map ( ( a : string ) => `'${ a } '` ) . join ( ' ' ) ;
872+ const compilerOptionsArray = util . getTsconfigCompilerOptionsArray ( findParentTsConfigPath ( __dirname ) ) ;
873+ const quotedCompilerOptions = compilerOptionsArray . map ( ( a : string ) => `'${ a } '` ) . join ( ' ' ) ;
874874
875875 // Correctly bundles with esbuild
876876 expect ( Code . fromAsset ) . toHaveBeenCalledWith ( path . dirname ( packageLock ) , {
@@ -1087,7 +1087,7 @@ test('bundling using NODEJS_LATEST doesn\'t externalize anything by default', ()
10871087 bundling : expect . objectContaining ( {
10881088 command : [
10891089 'bash' , '-c' ,
1090- `' esbuild' '--bundle' '/asset-input/lib/handler.ts' '--target=node22' '--platform=node' '--outfile=/asset-output/index.js'` ,
1090+ '\' esbuild\' \ '--bundle\' \ '/asset-input/lib/handler.ts\' \ '--target=node22\' \ '--platform=node\' \ '--outfile=/asset-output/index.js\'' ,
10911091 ] ,
10921092 } ) ,
10931093 } ) ;
@@ -1188,6 +1188,76 @@ test('Node 16 runtimes warn about sdk v2 upgrades', () => {
11881188 ) ;
11891189} ) ;
11901190
1191+ // --- Regression tests for PR review findings ---
1192+
1193+ test ( 'Docker bundling with preCompilation uses getTsconfigCompilerOptionsArray for proper escaping' , ( ) => {
1194+ const packageLock = path . join ( __dirname , '..' , 'package-lock.json' ) ;
1195+
1196+ Bundling . bundle ( stack , {
1197+ entry : __filename . replace ( '.js' , '.ts' ) ,
1198+ projectRoot : path . dirname ( packageLock ) ,
1199+ depsLockFilePath : packageLock ,
1200+ runtime : STANDARD_RUNTIME ,
1201+ preCompilation : true ,
1202+ forceDockerBundling : true ,
1203+ architecture : Architecture . X86_64 ,
1204+ } ) ;
1205+
1206+ // The Docker tsc command should use getTsconfigCompilerOptionsArray (not naive string splitting)
1207+ // to properly handle compiler option values that may contain spaces.
1208+ // Verify the Docker command matches what getTsconfigCompilerOptionsArray produces.
1209+ const compilerOptionsArray = util . getTsconfigCompilerOptionsArray ( findParentTsConfigPath ( __dirname ) ) ;
1210+ const quotedCompilerOptions = compilerOptionsArray . map ( ( a : string ) => `'${ a } '` ) . join ( ' ' ) ;
1211+
1212+ expect ( Code . fromAsset ) . toHaveBeenCalledWith ( path . dirname ( packageLock ) , {
1213+ assetHashType : AssetHashType . OUTPUT ,
1214+ bundling : expect . objectContaining ( {
1215+ command : [
1216+ 'bash' , '-c' ,
1217+ [
1218+ `'tsc' '/asset-input/test/bundling.test.ts' ${ quotedCompilerOptions } &&` ,
1219+ `'esbuild' '--bundle' '/asset-input/test/bundling.test.js' '--target=${ STANDARD_TARGET } ' '--platform=node' '--outfile=/asset-output/index.js' '--external:${ STANDARD_EXTERNAL } '` ,
1220+ ] . join ( ' ' ) ,
1221+ ] ,
1222+ } ) ,
1223+ } ) ;
1224+ } ) ;
1225+
1226+ test ( 'Local bundling callback failure includes contextual error message' , ( ) => {
1227+ const spawnSyncMock = jest . spyOn ( child_process , 'spawnSync' ) . mockReturnValue ( {
1228+ status : 0 ,
1229+ stderr : Buffer . from ( 'stderr' ) ,
1230+ stdout : Buffer . from ( 'stdout' ) ,
1231+ pid : 123 ,
1232+ output : [ 'stdout' , 'stderr' ] ,
1233+ signal : null ,
1234+ } ) ;
1235+ const writeFileSyncMock = jest . spyOn ( fs , 'writeFileSync' ) . mockImplementation ( ( ) => {
1236+ throw new Error ( 'EACCES: permission denied' ) ;
1237+ } ) ;
1238+ const copyFileSyncMock = jest . spyOn ( fs , 'copyFileSync' ) . mockReturnValue ( ) ;
1239+
1240+ const packageLock = path . join ( __dirname , '..' , 'package-lock.json' ) ;
1241+ const bundler = new Bundling ( stack , {
1242+ entry : __filename ,
1243+ projectRoot : path . dirname ( packageLock ) ,
1244+ depsLockFilePath : packageLock ,
1245+ runtime : STANDARD_RUNTIME ,
1246+ architecture : Architecture . X86_64 ,
1247+ externalModules : [ 'abc' ] ,
1248+ nodeModules : [ 'delay' ] ,
1249+ } ) ;
1250+
1251+ // The callback step should wrap fs errors with context
1252+ expect ( ( ) => {
1253+ bundler . local ?. tryBundle ( '/outdir' , { image : STANDARD_RUNTIME . bundlingDockerImage } ) ;
1254+ } ) . toThrow ( / L o c a l b u n d l i n g f i l e o p e r a t i o n f a i l e d .* E A C C E S / ) ;
1255+
1256+ spawnSyncMock . mockRestore ( ) ;
1257+ writeFileSyncMock . mockRestore ( ) ;
1258+ copyFileSyncMock . mockRestore ( ) ;
1259+ } ) ;
1260+
11911261// --- Local bundling spawn tests ---
11921262
11931263const spawnSyncMockReturnValue = {
0 commit comments