@@ -296,13 +296,12 @@ func validateYarnBerryProject(dir string, silent bool) {
296296}
297297
298298func validateBunProject (dir string , silent bool ) {
299- bunLockPath := filepath .Join (dir , "bun.lockb " )
299+ bunLockPath := filepath .Join (dir , "bun.lock " )
300300 if _ , err := os .Stat (bunLockPath ); err != nil {
301301 if ! silent {
302- fmt .Printf ("! Warning: Bun project detected but %s file not found\n " , util .Accented ("bun.lockb" ))
303- fmt .Printf (" Consider running %s to generate %s for reproducible builds\n " , util .Accented ("bun install" ), util .Accented ("bun.lockb" ))
304- fmt .Printf (" This ensures consistent dependency versions across environments\n " )
305- fmt .Printf (" Note: bun.lockb is a binary file, ensure it's committed to version control\n \n " )
302+ fmt .Printf ("! Warning: Bun project detected but %s file not found\n " , util .Accented ("bun.lock" ))
303+ fmt .Printf (" Consider running %s to generate %s for reproducible builds\n " , util .Accented ("bun install" ), util .Accented ("bun.lock" ))
304+ fmt .Printf (" This ensures consistent dependency versions across environments\n \n " )
306305 }
307306 }
308307}
@@ -320,18 +319,55 @@ func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreConten
320319 return "" , fmt .Errorf ("failed to create pattern matcher: %w" , err )
321320 }
322321
323- // Recursively find all relevant files, respecting dockerignore
322+ // For Node.js projects, we need to check build output directories even if dockerignore excludes them
323+ // because these directories will be created during the Docker build
324+ allowedPaths := make (map [string ]bool )
325+ if projectType .IsNode () {
326+ // Try to detect the output directory from tsconfig.json or use defaults per package manager
327+ // Default TypeScript output directories by convention
328+ switch projectType {
329+ case ProjectTypeNodeNPM , ProjectTypeNodePNPM :
330+ // npm and pnpm typically use 'dist' for TypeScript builds
331+ allowedPaths ["dist" ] = true
332+ allowedPaths ["build" ] = true
333+ case ProjectTypeNodeYarn , ProjectTypeNodeYarnBerry :
334+ // Yarn projects often use 'dist' or 'lib'
335+ allowedPaths ["dist" ] = true
336+ allowedPaths ["lib" ] = true
337+ case ProjectTypeNodeBun :
338+ // Bun can use 'dist' or 'out'
339+ allowedPaths ["dist" ] = true
340+ allowedPaths ["out" ] = true
341+ }
342+
343+ // TODO: Parse tsconfig.json "outDir" if it exists to get the actual output directory
344+ // For now, we're using common conventions
345+ }
346+
347+ // Recursively find all relevant files, respecting dockerignore (with exceptions)
324348 fileMap := make (map [string ]bool )
325349 if err := filepath .WalkDir (dir , func (path string , d os.DirEntry , err error ) error {
326350 if err != nil {
327351 return err
328352 }
329353
330- // Skip files that match .dockerignore patterns
331- if ignored , err := matcher .MatchesOrParentMatches (path ); ignored {
332- return nil
333- } else if err != nil {
334- return err
354+ // Check if this path should be allowed regardless of dockerignore
355+ relPath , _ := filepath .Rel (dir , path )
356+ shouldAllow := false
357+ for allowedDir := range allowedPaths {
358+ if strings .HasPrefix (relPath , allowedDir + string (filepath .Separator )) || relPath == allowedDir {
359+ shouldAllow = true
360+ break
361+ }
362+ }
363+
364+ // Skip files that match .dockerignore patterns (unless explicitly allowed)
365+ if ! shouldAllow {
366+ if ignored , err := matcher .MatchesOrParentMatches (path ); ignored {
367+ return nil
368+ } else if err != nil {
369+ return err
370+ }
335371 }
336372
337373 // Only include files with the correct extension
@@ -359,31 +395,46 @@ func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreConten
359395
360396 if projectType .IsPython () {
361397 // Common Python entry point patterns in order of preference
362- priorityOrder = []string {
363- "agent.py" , // LiveKit agents
364- "src/agent.py" , // LiveKit agents in src
365- "main.py" , // Most common
366- "src/main.py" , // Modern src layout
367- "app.py" , // Flask/web apps
368- "src/app.py" , // Flask/web apps in src
369- "__main__.py" , // Python module entry
370- "src/__main__.py" , // Python module entry in src
398+ priorityDirs := []string {
399+ "" ,
400+ "src" ,
401+ }
402+ priorityFiles := []string {
403+ "agent.py" ,
404+ "main.py" ,
405+ "app.py" ,
406+ }
407+
408+ // search the for the files in priority order in each of the directories in priority order
409+ for _ , file := range priorityFiles {
410+ for _ , dir := range priorityDirs {
411+ if dir != "" {
412+ dir = dir + "/"
413+ }
414+ priorityOrder = append (priorityOrder , fmt .Sprintf ("%s%s" , dir , file ))
415+ }
371416 }
372417 } else if projectType .IsNode () {
373418 // Common Node.js entry point patterns
374- priorityOrder = []string {
375- "dist/agent.js" , // Built TypeScript output
376- "dist/index.js" , // Built TypeScript output
377- "dist/main.js" , // Built TypeScript output
378- "dist/app.js" , // Built TypeScript output
419+ priorityDirs := []string {
420+ "dist" ,
421+ "" ,
422+ "src" ,
423+ }
424+ priorityFiles := []string {
379425 "agent.js" ,
380426 "index.js" ,
381427 "main.js" ,
382428 "app.js" ,
383- "src/agent.js" ,
384- "src/index.js" ,
385- "src/main.js" ,
386- "src/app.js" ,
429+ }
430+ // search through each directory in order for .js files per the priority order
431+ for _ , dir := range priorityDirs {
432+ for _ , file := range priorityFiles {
433+ if dir != "" {
434+ dir = dir + "/"
435+ }
436+ priorityOrder = append (priorityOrder , fmt .Sprintf ("%s%s.js" , dir , file ))
437+ }
387438 }
388439 }
389440
@@ -406,8 +457,40 @@ func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreConten
406457 return candidates [0 ], nil
407458 }
408459
409- // If no matching files found, return early
460+ // If no matching files found, check for TypeScript files and provide helpful message
410461 if len (candidates ) == 0 {
462+ // For Node.js projects, check if there are TypeScript files
463+ if projectType .IsNode () {
464+ hasTSFiles := false
465+ for fileName := range fileMap {
466+ // Check if any .ts files exist (but not .d.ts)
467+ if strings .HasSuffix (fileName , ".ts" ) && ! strings .HasSuffix (fileName , ".d.ts" ) {
468+ hasTSFiles = true
469+ break
470+ }
471+ }
472+
473+ if hasTSFiles {
474+ // Check specifically for common TypeScript source files
475+ tsFiles := []string {"agent.ts" , "index.ts" , "main.ts" , "app.ts" , "src/agent.ts" , "src/index.ts" , "src/main.ts" , "src/app.ts" }
476+ foundTS := []string {}
477+ for _ , tsFile := range tsFiles {
478+ if info , err := os .Stat (filepath .Join (dir , tsFile )); err == nil && ! info .IsDir () {
479+ foundTS = append (foundTS , tsFile )
480+ }
481+ }
482+
483+ if len (foundTS ) > 0 {
484+ displayCount := 3
485+ if len (foundTS ) < displayCount {
486+ displayCount = len (foundTS )
487+ }
488+ fmt .Printf ("\n TypeScript files detected (%s) but no compiled JavaScript files found.\n " , strings .Join (foundTS [:displayCount ], ", " ))
489+ fmt .Printf ("Please build your project first using: %s\n \n " , util .Accented ("npm run build" ))
490+ return "" , fmt .Errorf ("no compiled JavaScript files found - run build command first" )
491+ }
492+ }
493+ }
411494 return "" , nil
412495 }
413496
@@ -493,6 +576,14 @@ func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreConten
493576 return nil , err
494577 }
495578
579+ // Check if entrypoint is empty and provide helpful error message
580+ if newEntrypoint == "" {
581+ if projectType .IsNode () {
582+ return nil , fmt .Errorf ("failed to detect entrypoint script - no JavaScript files found. If using TypeScript, run 'npm run build' (or equivalent) first" )
583+ }
584+ return nil , fmt .Errorf ("failed to detect entrypoint script - no %s files found in the project" , projectType .FileExt ())
585+ }
586+
496587 tpl := template .Must (template .New ("Dockerfile" ).Parse (string (dockerfileContent )))
497588 buf := & bytes.Buffer {}
498589 tpl .Execute (buf , map [string ]string {
0 commit comments