@@ -7,6 +7,8 @@ import { promises as fs } from 'node:fs'
77import path from 'node:path'
88import { fileURLToPath } from 'node:url'
99
10+ import { parse } from '@babel/parser'
11+ import traverse from '@babel/traverse'
1012import { describe , expect , it } from 'vitest'
1113
1214const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) )
@@ -44,7 +46,7 @@ function hasAbsolutePaths(content: string): {
4446}
4547
4648/**
47- * Check if bundle contains inlined dependencies.
49+ * Check if bundle contains inlined dependencies using AST analysis .
4850 * Reads package.json dependencies and ensures they are NOT bundled inline.
4951 */
5052async function checkBundledDependencies ( content : string ) : Promise < {
@@ -58,62 +60,77 @@ async function checkBundledDependencies(content: string): Promise<{
5860
5961 const bundledDeps : string [ ] = [ ]
6062
61- // If we have NO dependencies, check that no external packages are bundled.
63+ // Parse the bundle into an AST.
64+ const ast = parse ( content , {
65+ sourceType : 'module' ,
66+ plugins : [ 'typescript' ] ,
67+ } )
68+
69+ // Collect all import sources from the AST.
70+ const importSources = new Set < string > ( )
71+ // @ts -expect-error - traverse types are complex
72+ traverse ( ast , {
73+ ImportDeclaration ( path ) {
74+ const source = path . node . source . value
75+ importSources . add ( source )
76+ } ,
77+ } )
78+
79+ // Packages that should always be external (never bundled).
80+ const socketPackagePatterns = [
81+ / @ s o c k e t s e c u r i t y \/ l i b / ,
82+ / @ s o c k e t r e g i s t r y \/ p a c k a g e u r l - j s / ,
83+ / @ s o c k e t s e c u r i t y \/ s d k / ,
84+ / @ s o c k e t s e c u r i t y \/ r e g i s t r y / ,
85+ ]
86+
87+ // Check if we have runtime dependencies.
6288 if ( Object . keys ( dependencies ) . length === 0 ) {
63- // Look for signs of bundled npm packages in ESM format.
64- // Bundled ESM packages have patterns like:
65- // - import { x } from "inline-bundled-code"
66- // - Functions from external packages inlined directly.
67- const bundledPackagePatterns = [
68- // Socket packages that should always be external.
69- / @ s o c k e t s e c u r i t y \/ l i b / ,
70- / @ s o c k e t s e c u r i t y \/ p a c k a g e u r l - j s / ,
71- / @ s o c k e t s e c u r i t y \/ s d k / ,
72- / @ s o c k e t s e c u r i t y \/ r e g i s t r y / ,
73- ]
74-
75- for ( const pattern of bundledPackagePatterns ) {
76- // For ESM bundles, check if imports are to external packages (good)
77- // vs having the code inlined (bad).
78- // Look for: import ... from "@socketsecurity/lib" (external - good)
79- // vs: no imports but code from the package is present (bundled - bad).
80- const hasExternalImport = new RegExp (
81- `import\\s+.*?from\\s+["']${ pattern . source } ` ,
82- ) . test ( content )
83-
84- // If no external import found, check if package code might be bundled.
85- // This is a heuristic - look for multiple characteristic functions.
86- if ( ! hasExternalImport ) {
87- // Check for signs of bundled code from this package.
88- // This would mean the package wasn't properly externalized.
89- const bundlePattern = new RegExp (
90- `(?:var|const|let)\\s+\\w+.*${ pattern . source } ` ,
91- )
89+ // No runtime dependencies - check that Socket packages aren't bundled.
90+ for ( const pattern of socketPackagePatterns ) {
91+ const hasExternalImport = Array . from ( importSources ) . some ( source =>
92+ pattern . test ( source ) ,
93+ )
9294
93- if ( bundlePattern . test ( content ) ) {
95+ if ( ! hasExternalImport ) {
96+ // Check if this package name appears in the content at all.
97+ // If it's just in string literals (like constants), that's fine.
98+ // Use AST to check if it appears in any meaningful way.
99+ let foundInCode = false
100+ // @ts -expect-error - traverse types are complex
101+ traverse ( ast , {
102+ StringLiteral ( path ) {
103+ // Skip string literals - these are fine
104+ if ( pattern . test ( path . node . value ) ) {
105+ // It's in a string literal, which is fine
106+ }
107+ } ,
108+ Identifier ( path ) {
109+ // Check if the package name appears in identifiers or other code
110+ if (
111+ pattern . test ( path . node . name ) ||
112+ ( path . node . name . includes ( 'socketsecurity' ) &&
113+ pattern . test ( path . node . name ) )
114+ ) {
115+ foundInCode = true
116+ }
117+ } ,
118+ } )
119+
120+ // Only flag if we found it in actual code, not just string literals
121+ if ( foundInCode ) {
94122 bundledDeps . push ( pattern . source )
95123 }
96124 }
97125 }
98126 } else {
99- // If we have dependencies, check that they remain external (not bundled) .
127+ // We have runtime dependencies - check that they remain external.
100128 for ( const dep of Object . keys ( dependencies ) ) {
101- const escapedDep = dep . replace ( / [ / \\ ^ $ * + ? . ( ) | [ \] { } ] / g, '\\$&' )
102-
103- // Check if dependency is properly external.
104- const hasExternalImport = new RegExp (
105- `import\\s+.*?from\\s+["']${ escapedDep } ` ,
106- ) . test ( content )
129+ const hasExternalImport = importSources . has ( dep )
107130
108- // If no external import, it might be bundled.
109131 if ( ! hasExternalImport ) {
110- const bundlePattern = new RegExp (
111- `(?:var|const|let)\\s+\\w+.*${ escapedDep } ` ,
112- )
113-
114- if ( bundlePattern . test ( content ) ) {
115- bundledDeps . push ( dep )
116- }
132+ // Dependency isn't imported externally - it might be bundled
133+ bundledDeps . push ( dep )
117134 }
118135 }
119136 }
0 commit comments