@davidwells/extract-deps 0.0.4 → 0.0.6

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.
Files changed (61) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/examples/verify-basic.js +45 -0
  3. package/examples/verify-build-graph.js +58 -0
  4. package/examples/verify-build-order.js +113 -0
  5. package/examples/verify-find-dependents.js +74 -0
  6. package/examples/verify-impact-analysis.js +97 -0
  7. package/examples/verify-package-entries.js +74 -0
  8. package/package.json +6 -2
  9. package/src/dep-graph.js +337 -0
  10. package/src/get-cjs-exports.js +112 -7
  11. package/src/get-imports.js +1 -0
  12. package/src/utils.js +1 -1
  13. package/src/vendor/inspect-parameters-declaration.js +282 -0
  14. package/src/verify-package-entries.js +228 -0
  15. package/tests/dep-graph.test.js +358 -0
  16. package/tests/fixtures/cjs-broken-example/index.js +8 -0
  17. package/tests/fixtures/cjs-broken-example/one.js +9 -0
  18. package/tests/fixtures/cjs-broken-example/package.json +7 -0
  19. package/tests/fixtures/cjs-broken-example/sub-folder/sub-sub-folder/three.js +6 -0
  20. package/tests/fixtures/cjs-broken-example/sub-folder/two.js +8 -0
  21. package/tests/fixtures/cjs-deep-example/d-one.js +10 -0
  22. package/tests/fixtures/cjs-deep-example/d-sub-folder/d-sub-sub-folder/d-three.js +7 -0
  23. package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two-a.js +8 -0
  24. package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two-b.js +8 -0
  25. package/tests/fixtures/cjs-deep-example/d-sub-folder/d-two.js +8 -0
  26. package/tests/fixtures/cjs-deep-example/index.js +8 -0
  27. package/tests/fixtures/cjs-deep-example/package.json +15 -0
  28. package/tests/fixtures/cjs-example/index.js +8 -0
  29. package/tests/fixtures/cjs-example/one.js +9 -0
  30. package/tests/fixtures/cjs-example/package.json +7 -0
  31. package/tests/fixtures/cjs-example/sub-folder/sub-sub-folder/three.js +6 -0
  32. package/tests/fixtures/cjs-example/sub-folder/two.js +8 -0
  33. package/tests/fixtures/cjs-parent.js +5 -0
  34. package/tests/fixtures/esm-broken-fixture/index.js +8 -0
  35. package/tests/fixtures/esm-broken-fixture/one.js +9 -0
  36. package/tests/fixtures/esm-broken-fixture/package.json +15 -0
  37. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder/three.js +6 -0
  38. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/deep.js +6 -0
  39. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/even-deeper/wow-deep.js +5 -0
  40. package/tests/fixtures/esm-broken-fixture/sub-folder/two.js +8 -0
  41. package/tests/fixtures/esm-broken-fixture/valid.js +7 -0
  42. package/tests/fixtures/esm-fixture/index.js +8 -0
  43. package/tests/fixtures/esm-fixture/one.js +9 -0
  44. package/tests/fixtures/esm-fixture/package.json +11 -0
  45. package/tests/fixtures/esm-fixture/sub-folder/sub-sub-folder/three.js +6 -0
  46. package/tests/fixtures/esm-fixture/sub-folder/two.js +8 -0
  47. package/tests/get-cjs-exports.test.js +171 -0
  48. package/tests/get-imports.test.js +4 -1
  49. package/tests/manual/broken-cjs.js +38 -0
  50. package/tests/manual/broken-esm.js +37 -0
  51. package/tests/verify-package-entries.test.js +272 -0
  52. package/types/dep-graph.d.ts +22 -0
  53. package/types/dep-graph.d.ts.map +1 -0
  54. package/types/get-cjs-exports.d.ts +35 -0
  55. package/types/get-cjs-exports.d.ts.map +1 -0
  56. package/types/get-exports.d.ts +3 -1
  57. package/types/get-exports.d.ts.map +1 -1
  58. package/types/index.d.ts +7 -3
  59. package/types/index.d.ts.map +1 -1
  60. package/types/utils.d.ts +2 -1
  61. package/types/utils.d.ts.map +1 -1
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(node:*)"
4
+ "Bash(node:*)",
5
+ "Bash(npm audit:*)",
6
+ "Bash(npm view:*)",
7
+ "Bash(npm pack:*)",
8
+ "Bash(tar:*)",
9
+ "Bash(npm install)",
10
+ "Bash(npm test)"
5
11
  ],
6
12
  "deny": [],
7
13
  "ask": []
@@ -0,0 +1,45 @@
1
+ // Basic verification of dependency graph
2
+ const path = require('path')
3
+ const { depGraph } = require('../src/dep-graph')
4
+
5
+ const cjsFixture = path.resolve(__dirname, '../tests/fixtures/cjs-example/index.js')
6
+ const esmFixture = path.resolve(__dirname, '../tests/fixtures/esm-fixture/index.js')
7
+
8
+ console.log('Verifying CJS fixture...')
9
+ try {
10
+ const graph = depGraph(cjsFixture)
11
+ console.log('✓ CJS fixture is valid - all imports resolve correctly')
12
+ console.log(` Found ${graph.size} files in dependency graph\n`)
13
+ } catch (error) {
14
+ console.error('✗ CJS fixture has errors:')
15
+ console.error(error.message)
16
+ }
17
+
18
+ console.log('foo')
19
+
20
+ console.log('Verifying ESM fixture...')
21
+ try {
22
+ const graph = depGraph(esmFixture)
23
+ console.log('✓ ESM fixture is valid - all imports resolve correctly')
24
+ console.log(` Found ${graph.size} files in dependency graph\n`)
25
+ } catch (error) {
26
+ console.error('✗ ESM fixture has errors:')
27
+ console.error(error.message)
28
+ }
29
+
30
+ console.log('Verifying with throwOnFirstError option...')
31
+ try {
32
+ depGraph(cjsFixture, { throwOnFirstError: true })
33
+ console.log('✓ Validation passed (would have thrown on first error)\n')
34
+ } catch (error) {
35
+ console.error('✗ First error encountered:')
36
+ console.error(error.message)
37
+ }
38
+
39
+ console.log('Building graph without verification (faster)...')
40
+ const graph = depGraph(cjsFixture, { verifyGraph: false })
41
+ console.log(`✓ Graph built without verification - ${graph.size} files`)
42
+
43
+ console.log('\nVerification only (no graph building)...')
44
+ depGraph(cjsFixture, { buildGraph: false })
45
+ console.log('✓ Verification passed (no graph returned)')
@@ -0,0 +1,58 @@
1
+ // Build and explore dependency graph
2
+ const path = require('path')
3
+ const { depGraph } = require('../src/dep-graph')
4
+
5
+ const fixture = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
6
+
7
+ console.log('Building dependency graph...\n')
8
+
9
+ const graph = depGraph(fixture)
10
+
11
+ console.log('=== Dependency Graph ===\n')
12
+
13
+ // Display the graph
14
+ for (const [file, deps] of graph) {
15
+ const fileName = path.basename(file)
16
+ const fileDir = path.relative(__dirname, path.dirname(file))
17
+
18
+ console.log(`${fileName} (${fileDir})`)
19
+
20
+ if (deps.length === 0) {
21
+ console.log(' └─ (no dependencies - leaf node)\n')
22
+ } else {
23
+ deps.forEach((dep, idx) => {
24
+ const depName = path.basename(dep)
25
+ const isLast = idx === deps.length - 1
26
+ const prefix = isLast ? ' └─' : ' ├─'
27
+ console.log(`${prefix} ${depName}`)
28
+ })
29
+ console.log()
30
+ }
31
+ }
32
+
33
+ console.log(`\nTotal files: ${graph.size}`)
34
+
35
+ // Find leaf nodes (files with no dependencies)
36
+ const leafNodes = []
37
+ for (const [file, deps] of graph) {
38
+ if (deps.length === 0) {
39
+ leafNodes.push(path.basename(file))
40
+ }
41
+ }
42
+
43
+ console.log(`Leaf nodes: ${leafNodes.join(', ')}`)
44
+
45
+ // Find root nodes (files not depended on by others in the graph)
46
+ const allDeps = new Set()
47
+ for (const deps of graph.values()) {
48
+ deps.forEach(dep => allDeps.add(dep))
49
+ }
50
+
51
+ const rootNodes = []
52
+ for (const file of graph.keys()) {
53
+ if (!allDeps.has(file)) {
54
+ rootNodes.push(path.basename(file))
55
+ }
56
+ }
57
+
58
+ console.log(`Root nodes: ${rootNodes.join(', ')}`)
@@ -0,0 +1,113 @@
1
+ // Calculate build order using topological sort
2
+ const path = require('path')
3
+ const { depGraph } = require('../src/dep-graph')
4
+
5
+ /**
6
+ * Calculate build order using topological sort (Kahn's algorithm)
7
+ * @param {Map<string, string[]>} graph - Dependency graph
8
+ * @returns {string[]} - Files in build order (dependencies first)
9
+ */
10
+ function calculateBuildOrder(graph) {
11
+ // Calculate in-degree (number of dependencies) for each file
12
+ const inDegree = new Map()
13
+ for (const [file, deps] of graph) {
14
+ inDegree.set(file, deps.length)
15
+ }
16
+
17
+ // Queue of files with no dependencies (can be built first)
18
+ const queue = []
19
+ for (const [file, degree] of inDegree) {
20
+ if (degree === 0) {
21
+ queue.push(file)
22
+ }
23
+ }
24
+
25
+ const buildOrder = []
26
+
27
+ while (queue.length > 0) {
28
+ const file = queue.shift()
29
+ buildOrder.push(file)
30
+
31
+ // Find all files that depend on this file and decrement their in-degree
32
+ for (const [dependent, deps] of graph) {
33
+ if (deps.includes(file)) {
34
+ const newDegree = inDegree.get(dependent) - 1
35
+ inDegree.set(dependent, newDegree)
36
+ if (newDegree === 0) {
37
+ queue.push(dependent)
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ return buildOrder
44
+ }
45
+
46
+ /**
47
+ * Group files by build level (parallel build opportunities)
48
+ * @param {Map<string, string[]>} graph - Dependency graph
49
+ * @returns {Array<string[]>} - Array of levels, each containing files that can be built in parallel
50
+ */
51
+ function calculateBuildLevels(graph) {
52
+ const inDegree = new Map()
53
+ for (const [file, deps] of graph) {
54
+ inDegree.set(file, deps.length)
55
+ }
56
+
57
+ const levels = []
58
+ const processed = new Set()
59
+
60
+ while (processed.size < graph.size) {
61
+ const currentLevel = []
62
+
63
+ for (const [file, degree] of inDegree) {
64
+ if (degree === 0 && !processed.has(file)) {
65
+ currentLevel.push(file)
66
+ }
67
+ }
68
+
69
+ if (currentLevel.length === 0) break
70
+
71
+ levels.push(currentLevel)
72
+
73
+ for (const file of currentLevel) {
74
+ processed.add(file)
75
+ for (const [dependent, deps] of graph) {
76
+ if (deps.includes(file)) {
77
+ inDegree.set(dependent, inDegree.get(dependent) - 1)
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ return levels
84
+ }
85
+
86
+ const fixture = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
87
+ const graph = depGraph(fixture)
88
+
89
+ console.log('=== Build Order Analysis ===\n')
90
+
91
+ // Sequential build order
92
+ const buildOrder = calculateBuildOrder(graph)
93
+ console.log('Sequential build order (dependencies first):\n')
94
+ buildOrder.forEach((file, index) => {
95
+ console.log(`${index + 1}. ${path.basename(file)}`)
96
+ })
97
+
98
+ console.log('\n=== Parallel Build Opportunities ===\n')
99
+
100
+ // Parallel build levels
101
+ const levels = calculateBuildLevels(graph)
102
+ console.log('Files grouped by build level (same level can build in parallel):\n')
103
+
104
+ levels.forEach((level, index) => {
105
+ console.log(`Level ${index + 1}: (${level.length} file${level.length === 1 ? '' : 's'})`)
106
+ level.forEach(file => {
107
+ console.log(` - ${path.basename(file)}`)
108
+ })
109
+ console.log()
110
+ })
111
+
112
+ console.log(`Total build levels: ${levels.length}`)
113
+ console.log(`Maximum parallelism: ${Math.max(...levels.map(l => l.length))} files`)
@@ -0,0 +1,74 @@
1
+ // Find all files that depend on a specific file
2
+ const path = require('path')
3
+ const { depGraph } = require('../src/dep-graph')
4
+
5
+ /**
6
+ * Find all files that directly depend on a target file
7
+ * @param {Map<string, string[]>} graph - Dependency graph
8
+ * @param {string} targetFile - File to find dependents for
9
+ * @returns {string[]} - Array of files that depend on targetFile
10
+ */
11
+ function findDirectDependents(graph, targetFile) {
12
+ const dependents = []
13
+ for (const [file, deps] of graph) {
14
+ if (deps.includes(targetFile)) {
15
+ dependents.push(file)
16
+ }
17
+ }
18
+ return dependents
19
+ }
20
+
21
+ /**
22
+ * Find all files that transitively depend on a target file
23
+ * @param {Map<string, string[]>} graph - Dependency graph
24
+ * @param {string} targetFile - File to find dependents for
25
+ * @returns {Set<string>} - Set of all files that depend on targetFile (directly or indirectly)
26
+ */
27
+ function findAllDependents(graph, targetFile) {
28
+ const allDependents = new Set()
29
+ const queue = [targetFile]
30
+
31
+ while (queue.length > 0) {
32
+ const current = queue.shift()
33
+ const directDependents = findDirectDependents(graph, current)
34
+
35
+ for (const dependent of directDependents) {
36
+ if (!allDependents.has(dependent)) {
37
+ allDependents.add(dependent)
38
+ queue.push(dependent)
39
+ }
40
+ }
41
+ }
42
+
43
+ return allDependents
44
+ }
45
+
46
+ const fixture = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
47
+ const graph = depGraph(fixture)
48
+
49
+ // Find a commonly used file
50
+ const targetFile = Array.from(graph.keys()).find(key => key.includes('d-three.js'))
51
+
52
+ if (targetFile) {
53
+ console.log(`Analyzing dependencies for: ${path.basename(targetFile)}\n`)
54
+
55
+ const directDeps = findDirectDependents(graph, targetFile)
56
+ console.log('Direct dependents:')
57
+ if (directDeps.length === 0) {
58
+ console.log(' (none)')
59
+ } else {
60
+ directDeps.forEach(dep => {
61
+ console.log(` - ${path.basename(dep)}`)
62
+ })
63
+ }
64
+
65
+ const allDeps = findAllDependents(graph, targetFile)
66
+ console.log(`\nAll dependents (transitive): ${allDeps.size}`)
67
+ if (allDeps.size > 0) {
68
+ Array.from(allDeps).forEach(dep => {
69
+ console.log(` - ${path.basename(dep)}`)
70
+ })
71
+ }
72
+
73
+ console.log(`\nImpact: Changing this file affects ${allDeps.size} other file(s)`)
74
+ }
@@ -0,0 +1,97 @@
1
+ // Analyze impact of changing files in the dependency graph
2
+ const path = require('path')
3
+ const { depGraph } = require('../src/dep-graph')
4
+
5
+ /**
6
+ * Calculate impact metrics for each file in the graph
7
+ * @param {Map<string, string[]>} graph - Dependency graph
8
+ * @returns {Map<string, object>} - Map of file paths to impact metrics
9
+ */
10
+ function analyzeImpact(graph) {
11
+ const impact = new Map()
12
+
13
+ for (const file of graph.keys()) {
14
+ // Count direct dependencies
15
+ const directDeps = graph.get(file).length
16
+
17
+ // Count direct dependents
18
+ let directDependents = 0
19
+ for (const [_, deps] of graph) {
20
+ if (deps.includes(file)) {
21
+ directDependents++
22
+ }
23
+ }
24
+
25
+ // Calculate transitive dependents
26
+ const transitiveDependents = new Set()
27
+ const queue = [file]
28
+ const visited = new Set([file])
29
+
30
+ while (queue.length > 0) {
31
+ const current = queue.shift()
32
+ for (const [f, deps] of graph) {
33
+ if (deps.includes(current) && !visited.has(f)) {
34
+ visited.add(f)
35
+ transitiveDependents.add(f)
36
+ queue.push(f)
37
+ }
38
+ }
39
+ }
40
+
41
+ impact.set(file, {
42
+ directDeps,
43
+ directDependents,
44
+ transitiveDependents: transitiveDependents.size,
45
+ isLeaf: directDeps === 0,
46
+ isRoot: directDependents === 0
47
+ })
48
+ }
49
+
50
+ return impact
51
+ }
52
+
53
+ const fixture = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
54
+ const graph = depGraph(fixture)
55
+
56
+ console.log('=== Dependency Impact Analysis ===\n')
57
+
58
+ const impact = analyzeImpact(graph)
59
+
60
+ // Sort files by impact (most dependents first)
61
+ const sortedFiles = Array.from(impact.entries())
62
+ .sort((a, b) => b[1].transitiveDependents - a[1].transitiveDependents)
63
+
64
+ console.log('Files sorted by impact (files that affect the most others):\n')
65
+
66
+ sortedFiles.forEach(([file, metrics], index) => {
67
+ const fileName = path.basename(file)
68
+ const impactLevel = metrics.transitiveDependents > 3 ? '🔴 HIGH' :
69
+ metrics.transitiveDependents > 1 ? '🟡 MEDIUM' :
70
+ '🟢 LOW'
71
+
72
+ console.log(`${index + 1}. ${fileName}`)
73
+ console.log(` Impact: ${impactLevel}`)
74
+ console.log(` Direct dependencies: ${metrics.directDeps}`)
75
+ console.log(` Direct dependents: ${metrics.directDependents}`)
76
+ console.log(` Total files affected: ${metrics.transitiveDependents}`)
77
+
78
+ if (metrics.isLeaf) {
79
+ console.log(` Type: Leaf node (no dependencies)`)
80
+ }
81
+ if (metrics.isRoot) {
82
+ console.log(` Type: Root node (nothing depends on it)`)
83
+ }
84
+
85
+ console.log()
86
+ })
87
+
88
+ console.log('=== Summary ===')
89
+ console.log(`Total files: ${graph.size}`)
90
+
91
+ const leafNodes = Array.from(impact.values()).filter(m => m.isLeaf).length
92
+ const rootNodes = Array.from(impact.values()).filter(m => m.isRoot).length
93
+ const highImpact = sortedFiles.filter(([_, m]) => m.transitiveDependents > 3).length
94
+
95
+ console.log(`Leaf nodes: ${leafNodes}`)
96
+ console.log(`Root nodes: ${rootNodes}`)
97
+ console.log(`High impact files: ${highImpact}`)
@@ -0,0 +1,74 @@
1
+ // Verify package.json entry points
2
+ const path = require('path')
3
+ const { getPackageEntryPoints, verifyPackageEntries } = require('../src/verify-package-json-entries')
4
+
5
+ const pkgPath = path.resolve(__dirname, '../package.json')
6
+
7
+ console.log('=== Extract All Package Entry Points ===\n')
8
+ const entryPoints = getPackageEntryPoints(pkgPath)
9
+ console.log(`Found ${entryPoints.length} entry points:\n`)
10
+ entryPoints.forEach(ep => {
11
+ console.log(` ${ep.field}: ${ep.path}`)
12
+ })
13
+
14
+ console.log('\n=== Verify Entries and Collect Dependencies ===\n')
15
+ const results = verifyPackageEntries(pkgPath, {
16
+ collectDeps: true, // Collect dependency paths
17
+ verifyGraph: false // Skip verification (faster, for CI use case)
18
+ })
19
+
20
+ console.log('Results per entry point:\n')
21
+ results.entryPoints.forEach(ep => {
22
+ const status = ep.valid ? '✓' : '✗'
23
+ console.log(`${status} ${ep.field}`)
24
+ if (ep.deps) {
25
+ console.log(` ${ep.deps.length} dependencies`)
26
+ }
27
+ if (ep.error) {
28
+ console.log(` Error: ${ep.error.message}`)
29
+ }
30
+ console.log()
31
+ })
32
+
33
+ console.log(`\n=== Summary ===`)
34
+ console.log(`Total entry points: ${results.entryPoints.length}`)
35
+ console.log(`Valid: ${results.entryPoints.filter(ep => ep.valid).length}`)
36
+ console.log(`Errors: ${results.errors.length}`)
37
+ console.log(`Total unique dependencies across all entry points: ${results.allDeps.size}`)
38
+
39
+ if (results.allDeps.size > 0) {
40
+ console.log('\n=== All Dependencies (for git change tracking) ===')
41
+ const allDeps = Array.from(results.allDeps)
42
+ allDeps.forEach(dep => {
43
+ console.log(` ${path.relative(process.cwd(), dep)}`)
44
+ })
45
+ }
46
+
47
+ console.log('\n=== Example: CI Use Case ===')
48
+ console.log(`
49
+ // In your CI script, you can use this to check if any deps changed:
50
+ const { verifyPackageEntries } = require('extract-deps/verify-package-json-entries')
51
+ const { execSync } = require('child_process')
52
+
53
+ const pkgPath = './package.json'
54
+ const results = verifyPackageEntries(pkgPath, { verifyGraph: false })
55
+
56
+ // Get all unique dependency file paths
57
+ const allDepPaths = Array.from(results.allDeps)
58
+
59
+ // Check which ones changed in git
60
+ const changedFiles = execSync('git diff --name-only HEAD~1 HEAD')
61
+ .toString()
62
+ .split('\\n')
63
+ .filter(Boolean)
64
+
65
+ const changedDeps = allDepPaths.filter(dep => {
66
+ const relativePath = path.relative(process.cwd(), dep)
67
+ return changedFiles.includes(relativePath)
68
+ })
69
+
70
+ console.log(\`Changed dependencies: \${changedDeps.length}\`)
71
+ if (changedDeps.length > 0) {
72
+ console.log('Need to rebuild/retest')
73
+ }
74
+ `)
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@davidwells/extract-deps",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
+ "exports": {
7
+ ".": "./src/index.js",
8
+ "./dep-graph": "./src/dep-graph.js"
9
+ },
6
10
  "types": "types/index.d.ts",
7
11
  "author": "David Wells",
8
12
  "license": "MIT",
@@ -18,7 +22,7 @@
18
22
  "release:major": "npm version major && npm publish"
19
23
  },
20
24
  "dependencies": {
21
- "inspect-parameters-declaration": "^0.1.0"
25
+ "@dual-bundle/import-meta-resolve": "^4.2.1"
22
26
  },
23
27
  "devDependencies": {
24
28
  "typescript": "^5.7.3",