2025-07-21 12:47:20 -07:00
/ * *
* Package Size Breakdown Test
* Analyzes the files that would be included in the npm package and reports their sizes
* /
2025-06-24 11:41:30 -07:00
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
2025-07-21 12:47:20 -07:00
import { describe , it , expect } from 'vitest'
2025-06-24 11:41:30 -07:00
2025-07-21 12:47:20 -07:00
// Get the project root directory
2025-06-24 11:41:30 -07:00
const __filename = fileURLToPath ( import . meta . url )
const __dirname = path . dirname ( __filename )
2025-07-21 12:47:20 -07:00
const projectRoot = path . resolve ( __dirname , '..' )
2025-06-24 11:41:30 -07:00
// Function to get the size of a file in MB
2025-07-21 12:47:20 -07:00
function getFileSizeInMB ( filePath : string ) : number {
2025-06-24 11:41:30 -07:00
const stats = fs . statSync ( filePath )
return stats . size / ( 1024 * 1024 )
}
// Function to check if a file should be included in the package
2025-07-21 12:47:20 -07:00
function shouldIncludeFile (
filePath : string ,
npmignorePatterns : RegExp [ ] ,
includePatterns : RegExp [ ]
) : boolean {
const relativePath = path . relative ( projectRoot , filePath )
2025-06-24 11:41:30 -07:00
// Check if the file matches any npmignore pattern
for ( const pattern of npmignorePatterns ) {
if ( pattern . test ( relativePath ) ) {
return false
}
}
// If we have explicit include patterns, check if the file matches any
if ( includePatterns . length > 0 ) {
for ( const pattern of includePatterns ) {
if ( pattern . test ( relativePath ) ) {
return true
}
}
return false
}
return true
}
// Parse .npmignore file
2025-07-21 12:47:20 -07:00
function parseNpmignore ( ) : RegExp [ ] {
const patterns : RegExp [ ] = [ ]
const npmignorePath = path . join ( projectRoot , '.npmignore' )
if ( fs . existsSync ( npmignorePath ) ) {
const content = fs . readFileSync ( npmignorePath , 'utf8' )
2025-06-24 11:41:30 -07:00
const lines = content . split ( '\n' )
for ( const line of lines ) {
const trimmedLine = line . trim ( )
if ( trimmedLine && ! trimmedLine . startsWith ( '#' ) ) {
// Convert glob pattern to regex
let pattern = trimmedLine
. replace ( /\./g , '\\.' )
. replace ( /\*/g , '.*' )
. replace ( /\?/g , '.' )
// Handle directory patterns
if ( pattern . endsWith ( '/' ) ) {
pattern = ` ${ pattern } .* `
}
patterns . push ( new RegExp ( ` ^ ${ pattern } $ ` ) )
}
}
}
return patterns
}
// Parse package.json files array
2025-07-21 12:47:20 -07:00
function parsePackageFiles ( ) : RegExp [ ] {
const patterns : RegExp [ ] = [ ]
const packageJsonPath = path . join ( projectRoot , 'package.json' )
const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) )
2025-06-24 11:41:30 -07:00
if ( packageJson . files && Array . isArray ( packageJson . files ) ) {
for ( const pattern of packageJson . files ) {
// Convert glob pattern to regex
let regexPattern = pattern
. replace ( /\./g , '\\.' )
. replace ( /\*/g , '.*' )
. replace ( /\?/g , '.' )
// Handle directory patterns
if ( regexPattern . endsWith ( '/' ) ) {
regexPattern = ` ${ regexPattern } .* `
}
patterns . push ( new RegExp ( ` ^ ${ regexPattern } $ ` ) )
}
}
return patterns
}
// Calculate the total size of files that would be included in the package
2025-07-21 12:47:20 -07:00
function calculatePackageSize ( ) : {
totalSize : number ,
includedFiles : { path : string , size : number } [ ]
} {
2025-06-24 11:41:30 -07:00
const npmignorePatterns = parseNpmignore ( )
const includePatterns = parsePackageFiles ( )
let totalSize = 0
2025-07-21 12:47:20 -07:00
const includedFiles : { path : string , size : number } [ ] = [ ]
2025-06-24 11:41:30 -07:00
2025-07-21 12:47:20 -07:00
function processDirectory ( dirPath : string ) {
2025-06-24 11:41:30 -07:00
const entries = fs . readdirSync ( dirPath , { withFileTypes : true } )
for ( const entry of entries ) {
const fullPath = path . join ( dirPath , entry . name )
if ( entry . isDirectory ( ) ) {
processDirectory ( fullPath )
} else if ( entry . isFile ( ) ) {
if ( shouldIncludeFile ( fullPath , npmignorePatterns , includePatterns ) ) {
const sizeInMB = getFileSizeInMB ( fullPath )
totalSize += sizeInMB
includedFiles . push ( { path : fullPath , size : sizeInMB } )
}
}
}
}
2025-07-21 12:47:20 -07:00
processDirectory ( projectRoot )
2025-06-24 11:41:30 -07:00
// Sort files by size (largest first)
includedFiles . sort ( ( a , b ) = > b . size - a . size )
2025-07-21 12:47:20 -07:00
return { totalSize , includedFiles }
2025-06-24 11:41:30 -07:00
}
2025-07-21 12:47:20 -07:00
describe ( 'Package Size Breakdown' , ( ) = > {
it ( 'should report the estimated package size and largest files' , ( ) = > {
const { totalSize , includedFiles } = calculatePackageSize ( )
console . log ( 'Estimated package size: ' + totalSize . toFixed ( 2 ) + ' MB' )
console . log ( '\nLargest files:' )
for ( let i = 0 ; i < Math . min ( 10 , includedFiles . length ) ; i ++ ) {
console . log (
` ${ includedFiles [ i ] . path } : ${ includedFiles [ i ] . size . toFixed ( 2 ) } MB `
)
}
// Basic sanity check
expect ( totalSize ) . toBeGreaterThan ( 0 )
expect ( includedFiles . length ) . toBeGreaterThan ( 0 )
} )
it ( 'should identify files that contribute significantly to package size' , ( ) = > {
const { includedFiles } = calculatePackageSize ( )
// Find files larger than 1MB
const largeFiles = includedFiles . filter ( file = > file . size > 1 )
if ( largeFiles . length > 0 ) {
console . log ( '\nFiles larger than 1MB:' )
largeFiles . forEach ( file = > {
console . log ( ` ${ file . path } : ${ file . size . toFixed ( 2 ) } MB ` )
} )
}
// This is not a failure condition, just informational
expect ( true ) . toBe ( true )
} )
} )