@@ -73,6 +73,40 @@ export class DockerAnalyzer implements Analyzer {
7373 } ,
7474 autoFixSafe : true
7575 } ) ;
76+ } else {
77+ // Check for essential patterns in existing .dockerignore
78+ const dockerignoreQuality = await this . checkDockerignoreQuality ( projectPath ) ;
79+ if ( dockerignoreQuality . missing . length > 0 ) {
80+ findings . push ( {
81+ id : 'docker-022' ,
82+ domain : 'docker' ,
83+ title : 'Incomplete .dockerignore' ,
84+ description : `.dockerignore is missing essential patterns: ${ dockerignoreQuality . missing . join ( ', ' ) } ` ,
85+ evidence : {
86+ file : '.dockerignore' ,
87+ snippet : dockerignoreQuality . content ?. split ( '\n' ) . slice ( 0 , 5 ) . join ( '\n' ) ,
88+ metrics : {
89+ missingPatterns : dockerignoreQuality . missing . length ,
90+ missingList : dockerignoreQuality . missing . slice ( 0 , 5 ) . join ( ', ' )
91+ }
92+ } ,
93+ severity : 'medium' ,
94+ confidence : 'high' ,
95+ impact : {
96+ type : 'size' ,
97+ estimate : `Reduce build context by ${ dockerignoreQuality . missing . length * 20 } MB` ,
98+ confidence : 'medium'
99+ } ,
100+ suggestedFix : {
101+ type : 'modify' ,
102+ file : '.dockerignore' ,
103+ description : 'Add missing patterns to .dockerignore' ,
104+ diff : dockerignoreQuality . missing . map ( p => `+ ${ p } ` ) . join ( '\n' ) ,
105+ autoFixable : true
106+ } ,
107+ autoFixSafe : true
108+ } ) ;
109+ }
76110 }
77111
78112 // Finding: No multistage build
@@ -357,6 +391,57 @@ export class DockerAnalyzer implements Analyzer {
357391 return fs . existsSync ( path . join ( projectPath , '.dockerignore' ) ) ;
358392 }
359393
394+ /**
395+ * Check quality of existing .dockerignore
396+ */
397+ private async checkDockerignoreQuality ( projectPath : string ) : Promise < {
398+ missing : string [ ] ;
399+ content ?: string ;
400+ } > {
401+ const essentialPatterns = [
402+ 'node_modules' ,
403+ '.git' ,
404+ '*.log' ,
405+ 'coverage' ,
406+ '.env' ,
407+ '.DS_Store' ,
408+ 'dist' ,
409+ 'build' ,
410+ '*.md' ,
411+ 'tests' ,
412+ '.github'
413+ ] ;
414+
415+ const dockerignorePath = path . join ( projectPath , '.dockerignore' ) ;
416+ let content : string ;
417+
418+ try {
419+ content = await fs . promises . readFile ( dockerignorePath , 'utf-8' ) ;
420+ } catch {
421+ return { missing : essentialPatterns } ;
422+ }
423+
424+ const lines = content . split ( '\n' ) . map ( l => l . trim ( ) ) . filter ( l => l && ! l . startsWith ( '#' ) ) ;
425+ const missing : string [ ] = [ ] ;
426+
427+ for ( const pattern of essentialPatterns ) {
428+ const hasPattern = lines . some ( line => {
429+ // Check for exact match or wildcard match
430+ if ( line === pattern ) return true ;
431+ if ( pattern . startsWith ( '*' ) && line . includes ( pattern . slice ( 1 ) ) ) return true ;
432+ if ( pattern . endsWith ( '*' ) && line . includes ( pattern . slice ( 0 , - 1 ) ) ) return true ;
433+ // Check if line matches the pattern
434+ return line . includes ( pattern ) ;
435+ } ) ;
436+
437+ if ( ! hasPattern ) {
438+ missing . push ( pattern ) ;
439+ }
440+ }
441+
442+ return { missing, content } ;
443+ }
444+
360445 private hasMultistage ( dockerfile : string ) : boolean {
361446 return / ^ F R O M \s + \S + \s + A S \s + \S + / m. test ( dockerfile ) ;
362447 }
0 commit comments