@davidwells/extract-deps 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidwells/extract-deps",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/dep-graph.js CHANGED
@@ -106,23 +106,34 @@ function verifyImportedValuesExist(dep, targetDeps, targetPath, sourcePath, erro
106
106
  }
107
107
  }
108
108
  }
109
- // For non-destructured imports, just verify there's a default export
109
+ // For non-destructured imports, verify there's a default export
110
+ // Only applies to ESM imports - CJS require() gets whatever module.exports is
110
111
  else {
111
- const hasDefaultExport =
112
- (targetDeps.moduleExports && targetDeps.moduleExports.length > 0) ||
113
- (targetDeps.exports && targetDeps.exports.some(exp => exp.exportType === 'default'))
114
-
115
- if (!hasDefaultExport) {
116
- const error = new Error(
117
- `Import not found: "${targetPath}" does not have a default export.\n` +
118
- `Import statement: ${dep.line}\n` +
119
- `Importing from: ${sourcePath}`
120
- )
121
- if (throwOnFirstError) {
122
- throw error
112
+ if (dep.isESM) {
113
+ // ESM import X from 'pkg' requires a default export
114
+ const hasModuleExports = targetDeps.moduleExports && targetDeps.moduleExports.length > 0
115
+ const hasESMExports = targetDeps.exports && targetDeps.exports.length > 0
116
+ const hasDefaultExport =
117
+ hasModuleExports ||
118
+ (hasESMExports && targetDeps.exports.some(exp => exp.exportType === 'default'))
119
+
120
+ // Only fail if we can detect exports but none are default
121
+ // If we can't detect any exports (e.g., complex/minified file), assume valid
122
+ const hasDetectableExports = hasModuleExports || hasESMExports
123
+
124
+ if (hasDetectableExports && !hasDefaultExport) {
125
+ const error = new Error(
126
+ `Import not found: "${targetPath}" does not have a default export.\n` +
127
+ `Import statement: ${dep.line}\n` +
128
+ `Importing from: ${sourcePath}`
129
+ )
130
+ if (throwOnFirstError) {
131
+ throw error
132
+ }
133
+ errors.push(error)
123
134
  }
124
- errors.push(error)
125
135
  }
136
+ // CJS require() can get any module.exports - no default check needed
126
137
  }
127
138
  }
128
139
 
@@ -166,20 +177,20 @@ function buildGraphRecursively(
166
177
  // Collect all relative dependencies
167
178
  const relativeDeps = []
168
179
  if (deps.requires && deps.requires.length > 0) {
169
- relativeDeps.push(...deps.requires.filter(req => req.isRelative))
180
+ relativeDeps.push(...deps.requires.filter(req => req.isRelative).map(req => ({ ...req, isESM: false })))
170
181
  }
171
182
  if (deps.imports && deps.imports.length > 0) {
172
- relativeDeps.push(...deps.imports.filter(imp => imp.isRelative))
183
+ relativeDeps.push(...deps.imports.filter(imp => imp.isRelative).map(imp => ({ ...imp, isESM: true })))
173
184
  }
174
185
 
175
186
  // Collect all non-relative dependencies (node_modules)
176
187
  const nodeModuleDeps = []
177
188
  if (verifyNodeModules || includeNodeModules) {
178
189
  if (deps.requires && deps.requires.length > 0) {
179
- nodeModuleDeps.push(...deps.requires.filter(req => !req.isRelative))
190
+ nodeModuleDeps.push(...deps.requires.filter(req => !req.isRelative).map(req => ({ ...req, isESM: false })))
180
191
  }
181
192
  if (deps.imports && deps.imports.length > 0) {
182
- nodeModuleDeps.push(...deps.imports.filter(imp => !imp.isRelative))
193
+ nodeModuleDeps.push(...deps.imports.filter(imp => !imp.isRelative).map(imp => ({ ...imp, isESM: true })))
183
194
  }
184
195
  }
185
196
 
@@ -242,11 +253,11 @@ function buildGraphRecursively(
242
253
  continue
243
254
  }
244
255
 
256
+ let resolvedPath
245
257
  try {
246
- // Try to resolve the module using Node's resolution algorithm
258
+ // resolveImport throws if file doesn't exist
247
259
  const resolved = resolveImport(from, parentURL)
248
- //console.log('resolved', resolved)
249
- // If we get here, the module resolved successfully
260
+ resolvedPath = fileURLToPath(resolved)
250
261
  } catch (error) {
251
262
  const err = new Error(
252
263
  `"${line}" could not be resolved in ${filePath}\n\n` +
@@ -258,6 +269,14 @@ function buildGraphRecursively(
258
269
  throw err
259
270
  }
260
271
  errors.push(err)
272
+ continue
273
+ }
274
+
275
+ // Verify import/export styles match (only needed for ESM imports)
276
+ if (dep.isESM) {
277
+ const targetContent = fs.readFileSync(resolvedPath, 'utf8')
278
+ const targetDeps = extractDeps(targetContent)
279
+ verifyImportedValuesExist(dep, targetDeps, resolvedPath, filePath, errors, throwOnFirstError)
261
280
  }
262
281
  }
263
282
  }
@@ -521,4 +521,33 @@ test('resolveDepPaths with returnErrors: true and no errors', () => {
521
521
  assert.is(result.errors.length, 0, 'Should have no errors')
522
522
  })
523
523
 
524
+ // Node module export verification tests
525
+ const NODE_MODULE_NAMED_FIXTURE = path.resolve(__dirname, './fixtures/node-module-named-export/handler.js')
526
+ const NODE_MODULE_NAMED_CORRECT = path.resolve(__dirname, './fixtures/node-module-named-export/handler-correct.js')
527
+
528
+ test('verifyNodeModules: true should catch default import when only named exports exist', () => {
529
+ let errorMessage = ''
530
+ try {
531
+ depGraph(NODE_MODULE_NAMED_FIXTURE, { verifyNodeModules: true })
532
+ } catch (error) {
533
+ errorMessage = error.message
534
+ }
535
+
536
+ assert.ok(errorMessage.includes('does not have a default export'), 'Should detect missing default export')
537
+ assert.ok(errorMessage.includes('fake-named-pkg'), 'Should mention the package')
538
+ })
539
+
540
+ test('verifyNodeModules: true should pass for correct named import', () => {
541
+ assert.not.throws(() => {
542
+ depGraph(NODE_MODULE_NAMED_CORRECT, { verifyNodeModules: true })
543
+ }, 'Should pass for correct named import')
544
+ })
545
+
546
+ test('verifyNodeModules with returnErrors should collect export mismatch errors', () => {
547
+ const result = depGraph(NODE_MODULE_NAMED_FIXTURE, { verifyNodeModules: true, returnErrors: true })
548
+
549
+ assert.ok(result.errors.length > 0, 'Should have errors')
550
+ assert.ok(result.errors[0].message.includes('does not have a default export'), 'Should be default export error')
551
+ })
552
+
524
553
  test.run()
@@ -0,0 +1,6 @@
1
+ // Correct: uses named import for named export
2
+ import { namedFunc } from 'fake-named-pkg'
3
+
4
+ export const hello = async () => {
5
+ return namedFunc()
6
+ }
@@ -0,0 +1,6 @@
1
+ // Incorrect: imports as default when package only has named exports
2
+ import pkg from 'fake-named-pkg'
3
+
4
+ export const hello = async () => {
5
+ return pkg()
6
+ }
@@ -0,0 +1,4 @@
1
+ // Only has named export, no default export
2
+ export function namedFunc() {
3
+ return 'Hello from named export'
4
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "fake-named-pkg",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js"
6
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "node-module-named-export-fixture",
3
+ "type": "module"
4
+ }
@@ -29,7 +29,7 @@ export function depGraph(filePath: string, options?: {
29
29
  * @param {boolean} [options.verifyNodeModules] - If true, verify node_modules packages can be resolved. Default: false
30
30
  * @param {boolean} [options.throwOnFirstError] - If true, throw immediately on first error. Default: false
31
31
  * @param {boolean} [options.returnErrors] - If true, include errors array in result instead of throwing
32
- * @returns {{ files: string[], nodeModules: string[], errors?: Error[] }} - Object with files in topological order and unique node_modules
32
+ * @returns {{ files: string[], nodeModules: string[], builtIns: string[], errors?: Error[] }} - Object with files in topological order, npm packages, and Node.js built-ins
33
33
  */
34
34
  export function resolveDepPaths(filePath: string, options?: {
35
35
  verifyGraph?: boolean;
@@ -39,6 +39,7 @@ export function resolveDepPaths(filePath: string, options?: {
39
39
  }): {
40
40
  files: string[];
41
41
  nodeModules: string[];
42
+ builtIns: string[];
42
43
  errors?: Error[];
43
44
  };
44
45
  //# sourceMappingURL=dep-graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.js"],"names":[],"mappings":"AAmQA;;;;;;;;;;;GAWG;AACH,mCAVW,MAAM,YAEd;IAA0B,UAAU,GAA5B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,kBAAkB,GAApC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAC;IAAC,KAAK,gBAAM;IAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CAAC,GAAC,SAAS,CA8BzE;AAGD;;;;;;;;;GASG;AACH,0CARW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;CAAE,CA4ExE"}
1
+ {"version":3,"file":"dep-graph.d.ts","sourceRoot":"","sources":["../src/dep-graph.js"],"names":[],"mappings":"AA4RA;;;;;;;;;;;GAWG;AACH,mCAVW,MAAM,YAEd;IAA0B,UAAU,GAA5B,OAAO;IACW,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,kBAAkB,GAApC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAC;IAAC,KAAK,gBAAM;IAAC,MAAM,EAAE,KAAK,EAAE,CAAA;CAAC,GAAC,SAAS,CA8BzE;AAGD;;;;;;;;;GASG;AACH,0CARW,MAAM,YAEd;IAA0B,WAAW,GAA7B,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,iBAAiB,GAAnC,OAAO;IACW,YAAY,GAA9B,OAAO;CACf,GAAU;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;CAAE,CAkF5F"}
@@ -1 +1 @@
1
- {"version":3,"file":"detect-syntax.d.ts","sourceRoot":"","sources":["../src/detect-syntax.js"],"names":[],"mappings":";;;;;;;oBAec,OAAO;;AAHrB;;;;GAIG;AAEH;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,OAe7B"}
1
+ {"version":3,"file":"detect-syntax.d.ts","sourceRoot":"","sources":["../src/detect-syntax.js"],"names":[],"mappings":";;;;;;;oBAcc,OAAO;;AAHrB;;;;GAIG;AAEH;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,GACjB,OAAO,CAMnB;AAED;;;;;;GAMG;AACH,mCAJW,MAAM,SACN,mBAAmB,OAe7B"}
@@ -1,4 +1,4 @@
1
- export function getExports(code: any, options?: {}): any[];
1
+ export function getExports(code?: string, options?: {}): any[];
2
2
  export function getDefaultExports(targetText?: string): {
3
3
  line: string;
4
4
  exportType: string;
@@ -1 +1 @@
1
- {"version":3,"file":"get-exports.d.ts","sourceRoot":"","sources":["../src/get-exports.js"],"names":[],"mappings":"AAiBA,2DA+BC;AAED;;;;;;;;IAqDC;AAED;;;;;;IAeC;AAED,uEAuBC;AAED;;;;;;IAeC;AAED;;;;;;;IAgBC;AAED;;;;;;IAeC;AAED;;;;;;;;;;;;IA0BC"}
1
+ {"version":3,"file":"get-exports.d.ts","sourceRoot":"","sources":["../src/get-exports.js"],"names":[],"mappings":"AAiBA,+DAiCC;AAED;;;;;;;;IAqDC;AAED;;;;;;IAeC;AAED,uEAuBC;AAED;;;;;;IAeC;AAED;;;;;;;IAgBC;AAED;;;;;;IAeC;AAED;;;;;;;;;;;;IA0BC"}
@@ -1,4 +1,4 @@
1
- export function getImports(code: any, options?: {}): {
1
+ export function getImports(code?: string, options?: {}): {
2
2
  line: string;
3
3
  varType: string;
4
4
  varName: string;
@@ -7,6 +7,7 @@ export function getImports(code: any, options?: {}): {
7
7
  index: number;
8
8
  isImport: boolean;
9
9
  isRelative: boolean;
10
+ isBuiltIn: boolean;
10
11
  }[];
11
12
  export function getStaticImports(targetText?: string): {
12
13
  line: string;
@@ -17,6 +18,7 @@ export function getStaticImports(targetText?: string): {
17
18
  index: number;
18
19
  isImport: boolean;
19
20
  isRelative: boolean;
21
+ isBuiltIn: boolean;
20
22
  }[];
21
23
  export function getTypeImports(targetText?: string): {
22
24
  importStatement: string;
@@ -26,6 +28,7 @@ export function getTypeImports(targetText?: string): {
26
28
  isImport: boolean;
27
29
  isTypeImport: boolean;
28
30
  isRelative: boolean;
31
+ isBuiltIn: boolean;
29
32
  }[];
30
33
  export function getDynamicImports(targetText?: string): {
31
34
  line: string;
@@ -36,6 +39,7 @@ export function getDynamicImports(targetText?: string): {
36
39
  index: number;
37
40
  isImport: boolean;
38
41
  isRelative: boolean;
42
+ isBuiltIn: boolean;
39
43
  isDynamic: boolean;
40
44
  }[];
41
45
  export function getAssertionImports(targetText?: string): {
@@ -49,6 +53,7 @@ export function getAssertionImports(targetText?: string): {
49
53
  index: number;
50
54
  isImport: boolean;
51
55
  isRelative: boolean;
56
+ isBuiltIn: boolean;
52
57
  isAssertion: boolean;
53
58
  }[];
54
59
  //# sourceMappingURL=get-imports.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-imports.d.ts","sourceRoot":"","sources":["../src/get-imports.js"],"names":[],"mappings":"AAWA;;;;;;;;;IAQC;AAGD;;;;;;;;;IAkBC;AAED;;;;;;;;IAiBC;AAED;;;;;;;;;;IAoBC;AAED;;;;;;;;;;;;IAwBC"}
1
+ {"version":3,"file":"get-imports.d.ts","sourceRoot":"","sources":["../src/get-imports.js"],"names":[],"mappings":"AAWA;;;;;;;;;;IAUC;AAGD;;;;;;;;;;IAmBC;AAED;;;;;;;;;IAkBC;AAED;;;;;;;;;;;IAqBC;AAED;;;;;;;;;;;;;IAyBC"}
@@ -7,6 +7,7 @@ export function getRequires(code: any): {
7
7
  index: number;
8
8
  isRequire: boolean;
9
9
  isRelative: boolean;
10
+ isBuiltIn: boolean;
10
11
  isDynamic: boolean;
11
12
  }[];
12
13
  export function getStaticRequires(targetText?: string): {
@@ -18,6 +19,7 @@ export function getStaticRequires(targetText?: string): {
18
19
  index: number;
19
20
  isRequire: boolean;
20
21
  isRelative: boolean;
22
+ isBuiltIn: boolean;
21
23
  isDynamic: boolean;
22
24
  }[];
23
25
  export function getDynamicRequires(code: any): {
@@ -29,6 +31,7 @@ export function getDynamicRequires(code: any): {
29
31
  index: number;
30
32
  isRequire: boolean;
31
33
  isRelative: boolean;
34
+ isBuiltIn: boolean;
32
35
  isDynamic: boolean;
33
36
  }[];
34
37
  //# sourceMappingURL=get-requires.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-requires.d.ts","sourceRoot":"","sources":["../src/get-requires.js"],"names":[],"mappings":"AA8DA;;;;;;;;;;IAIC;AA5BD;;;;;;;;;;IAsBC;AArDD;;;;;;;;;;IA4BC"}
1
+ {"version":3,"file":"get-requires.d.ts","sourceRoot":"","sources":["../src/get-requires.js"],"names":[],"mappings":"AAgEA;;;;;;;;;;;IAIC;AA7BD;;;;;;;;;;;IAuBC;AAvDD;;;;;;;;;;;IA6BC"}
package/types/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export function extractDeps(code: any, filePath?: string): {
8
8
  index: number;
9
9
  isImport: boolean;
10
10
  isRelative: boolean;
11
+ isBuiltIn: boolean;
11
12
  }[];
12
13
  exports: any[];
13
14
  requires: {
@@ -19,6 +20,7 @@ export function extractDeps(code: any, filePath?: string): {
19
20
  index: number;
20
21
  isRequire: boolean;
21
22
  isRelative: boolean;
23
+ isBuiltIn: boolean;
22
24
  isDynamic: boolean;
23
25
  }[];
24
26
  moduleExports: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;;EAgBC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBC"}
package/types/utils.d.ts CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Strip comments from code, preserving :// in URLs
3
+ * @param {string} code
4
+ * @returns {string}
5
+ */
6
+ export function stripComments(code: string): string;
7
+ /**
8
+ * Check if a module specifier is a Node.js built-in
9
+ * @param {string} moduleName - The module specifier (e.g., 'fs', 'node:fs/promises')
10
+ * @returns {boolean}
11
+ */
12
+ export function isBuiltIn(moduleName: string): boolean;
1
13
  export function isRelativeImport(importLocation: any): boolean;
2
14
  export function isRelativeImportWithQuotes(importLocation: any): boolean;
3
15
  export function getNames(names: any, aliasKey?: string): any;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAEA,+DAEC;AAED,yEAKC;AAgFD,6DA+DC;AAGD,yDAqCC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,oCAHW,MAAM,GACJ,MAAM,CAYlB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAUnB;AAED,+DAEC;AAED,yEAKC;AAgFD,6DA+DC;AAGD,yDAqCC"}