@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
@@ -0,0 +1,282 @@
1
+ // Vendored from inspect-parameters-declaration@0.1.0 to avoid vulnerable transitive deps
2
+ // Original packages: split-skip, unpack-string, stringify-parameters, inspect-parameters-declaration
3
+
4
+ 'use strict'
5
+
6
+ // split-skip@0.0.2
7
+ function splitSkip(str, separator, skipState) {
8
+ const result = []
9
+
10
+ if (!str) {
11
+ return [str]
12
+ }
13
+
14
+ let indexStart = 0
15
+ let indexEnd = 0
16
+ let state = { skip: 0 }
17
+
18
+ for (let i = 0; i < str.length; i++) {
19
+ const char = separator.length === 1 ? str[i] : str.substr(i, separator.length)
20
+
21
+ state = skipState ? skipState(state, char, i) : state
22
+ const compareSeparator = char === separator
23
+
24
+ if ((!state.skip && compareSeparator) || i === str.length - 1) {
25
+ indexEnd = i === str.length - 1 ? i + 1 : i
26
+ result.push(str.substring(indexStart, indexEnd))
27
+ indexStart = indexEnd + separator.length
28
+ }
29
+ }
30
+
31
+ return result
32
+ }
33
+
34
+ // unpack-string@0.0.2
35
+ function unpackString(str, openingChar, closingChar) {
36
+ let knownOpeningChars = '([{<'
37
+ let knownClosingChars = ')]}>'
38
+
39
+ let indexStart
40
+ let indexEnd
41
+ let skip = 0
42
+
43
+ for (let i = 0; i < str.length; i++) {
44
+ const char = str[i]
45
+
46
+ if (!openingChar) {
47
+ if (knownOpeningChars.indexOf(char) < 0) {
48
+ continue
49
+ }
50
+ openingChar = char
51
+ }
52
+
53
+ closingChar = closingChar || knownClosingChars[knownOpeningChars.indexOf(openingChar)]
54
+
55
+ let matchOpeningChar = char === openingChar
56
+ let matchClosingChar = char === closingChar
57
+
58
+ if (typeof(indexStart) === 'undefined' && matchOpeningChar) {
59
+ indexStart = i
60
+ continue
61
+ }
62
+
63
+ if (matchOpeningChar) {
64
+ skip += 1
65
+ continue
66
+ }
67
+
68
+ if (skip && matchClosingChar) {
69
+ skip -= 1
70
+ continue
71
+ }
72
+
73
+ if (matchClosingChar) {
74
+ indexEnd = i
75
+ break
76
+ }
77
+ }
78
+
79
+ return str.substring(indexStart + 1, indexEnd)
80
+ }
81
+
82
+ // stringify-parameters@0.0.7
83
+ function stringifyParameters(fn) {
84
+ const asyncFunctionConstructor = Object.getPrototypeOf(async function(){}).constructor
85
+ if (!fn || (fn.constructor !== String && fn.constructor !== Function && fn.constructor !== asyncFunctionConstructor)) {
86
+ return
87
+ }
88
+
89
+ let fnString = fn.toString().replace(/^\s+|\s+$/g, '')
90
+ if (!fnString) {
91
+ return
92
+ }
93
+
94
+ const isDeclaredWithFunctionKeyword = /^function/i
95
+ const isNotArrowFunction = /^[^\s=>]+\(/i
96
+
97
+ // Arrow function with param declared without parenthesis and no default value
98
+ if (!isDeclaredWithFunctionKeyword.test(fnString) && !isNotArrowFunction.test(fnString)) {
99
+ let arrowWithoutParenthesis = fnString.match(/^(.*?)=>/)
100
+ if (arrowWithoutParenthesis) {
101
+ return unpackString(arrowWithoutParenthesis[1]).replace(/^\s+|\s+$/g, '')
102
+ }
103
+ }
104
+
105
+ return unpackString(fnString).replace(/^\s+|\s+$/g, '')
106
+ }
107
+
108
+ // inspect-parameters-declaration@0.1.0
109
+ const isArrayLike = p => p.match(/^\[+/)
110
+ const isObjectLike = p => p.match(/^{+/)
111
+ const matchObjectProperty = p => p.match(/^([^{]+):(.*)/)
112
+
113
+ function splitSkipBrackets(string, delimiter) {
114
+ return splitSkip(string, delimiter, (state, char, i) => {
115
+ if ('{[('.indexOf(char) >= 0) {
116
+ state.skip += 1
117
+ }
118
+ if ('}])'.indexOf(char) >= 0) {
119
+ state.skip -= 1
120
+ }
121
+ return state
122
+ })
123
+ }
124
+
125
+ function getParameterSpec(param) {
126
+ if (!param) {
127
+ return
128
+ }
129
+
130
+ const [parameter, defaultValue] = splitSkipBrackets(param, '=').map(item => item.trim().replace(/^["']|["']$/g, ''))
131
+ const parameterSpec = { parameter }
132
+
133
+ if (defaultValue) {
134
+ parameterSpec.defaultValue = defaultValue
135
+ }
136
+
137
+ const expectsDestructuring = !!isArrayLike(param) || !!isObjectLike(param)
138
+ if (expectsDestructuring) {
139
+ parameterSpec.expectsDestructuring = true
140
+ }
141
+
142
+ const isRestParameter = parameterSpec.parameter.match(/^\.{3}/)
143
+ if (isRestParameter) {
144
+ parameterSpec.parameter = parameterSpec.parameter.replace(/^\.{3}/, '')
145
+ parameterSpec.isRestParameter = true
146
+ }
147
+
148
+ parameterSpec.declaration = param
149
+ return parameterSpec
150
+ }
151
+
152
+ function getParametersArray(paramsString) {
153
+ if (!paramsString) {
154
+ return []
155
+ }
156
+
157
+ paramsString = paramsString.trim()
158
+ const result = splitSkipBrackets(paramsString, ',')
159
+ return result.map(item => item.trim()).filter(item => !!item)
160
+ }
161
+
162
+ function destructureParametersFromArray(param, parameters = []) {
163
+ let parametersArray = getParametersArray(unpackString(param))
164
+
165
+ parametersArray.forEach(param => {
166
+ if (isArrayLike(param) || isObjectLike(param)) {
167
+ return destructureParameter(param, parameters)
168
+ }
169
+ parameters.push(getParameterSpec(param))
170
+ })
171
+
172
+ return parameters
173
+ }
174
+
175
+ function destructureParametersFromObject(param, parameters = []) {
176
+ let parametersArray = getParametersArray(unpackString(param))
177
+
178
+ parametersArray.forEach(param => {
179
+ let objectProperty = matchObjectProperty(param)
180
+ if (objectProperty) {
181
+ let [, key, value] = objectProperty.map(v => v.trim())
182
+ if (isArrayLike(value) || isObjectLike(value)) {
183
+ return destructureParameter(value, parameters)
184
+ }
185
+ }
186
+ parameters.push(getParameterSpec(param))
187
+ })
188
+
189
+ return parameters
190
+ }
191
+
192
+ function destructureParameter(parameter, parameters) {
193
+ if (isArrayLike(parameter)) {
194
+ return destructureParametersFromArray(parameter, parameters)
195
+ }
196
+ if (isObjectLike(parameter)) {
197
+ return destructureParametersFromObject(parameter, parameters)
198
+ }
199
+ }
200
+
201
+ function destructureParameters(param) {
202
+ const parametersArray = getParametersArray(param)
203
+
204
+ return parametersArray.reduce((parameters, parameter) => {
205
+ if (isArrayLike(parameter)) {
206
+ return parameters.concat(destructureParametersFromArray(parameter))
207
+ }
208
+ if (isObjectLike(parameter)) {
209
+ return parameters.concat(destructureParametersFromObject(parameter))
210
+ }
211
+ return parameters.concat(getParameterSpec(parameter))
212
+ }, [])
213
+ }
214
+
215
+ function inspectParameterFromString(parameter) {
216
+ const parameterSpec = getParameterSpec(parameter)
217
+ if (!parameterSpec || (!parameterSpec.parameter && !parameterSpec.declaration)) {
218
+ return
219
+ }
220
+
221
+ if (parameterSpec.expectsDestructuring) {
222
+ parameterSpec.destructuredParameters = destructureParameters(parameter)
223
+ }
224
+
225
+ return parameterSpec
226
+ }
227
+
228
+ function inspectParametersFromString(parameters) {
229
+ const parametersArray = getParametersArray(parameters)
230
+
231
+ const inspectedParameters = parametersArray.reduce((result, parameter) => {
232
+ const parameterSpec = inspectParameterFromString(parameter)
233
+ return result.concat(parameterSpec)
234
+ }, [])
235
+
236
+ return inspectedParameters
237
+ }
238
+
239
+ function inspectParametersFromFunction(fn) {
240
+ const parametersStringified = stringifyParameters(fn)
241
+ return inspectParametersFromString(parametersStringified)
242
+ }
243
+
244
+ function getAllInspectedParametersNames(inspectedParameters) {
245
+ if (!inspectedParameters) {
246
+ return []
247
+ }
248
+
249
+ inspectedParameters = inspectedParameters.constructor === Array ? inspectedParameters : [inspectedParameters]
250
+ return inspectedParameters.reduce((result, item) => {
251
+ if (item.expectsDestructuring) {
252
+ return result.concat(item.destructuredParameters.map(item => item.parameter))
253
+ }
254
+ return result.concat(item.parameter)
255
+ }, [])
256
+ }
257
+
258
+ function getParametersNames(source) {
259
+ const inspectedParameters = inspectParameters(source)
260
+ return getAllInspectedParametersNames(inspectedParameters)
261
+ }
262
+
263
+ function getParametersNamesFromInspection(inspectedParameters) {
264
+ return getAllInspectedParametersNames(inspectedParameters)
265
+ }
266
+
267
+ function inspectParameters(source) {
268
+ if (!source) {
269
+ return
270
+ }
271
+
272
+ const asyncFunctionConstructor = Object.getPrototypeOf(async function(){}).constructor
273
+ if (source.constructor === Function || source.constructor === asyncFunctionConstructor) {
274
+ return inspectParametersFromFunction(source)
275
+ }
276
+
277
+ if (source.constructor === String) {
278
+ return inspectParametersFromString(source)
279
+ }
280
+ }
281
+
282
+ module.exports = { inspectParameters, getParametersNames, getParametersNamesFromInspection }
@@ -0,0 +1,228 @@
1
+ // Verify package.json entry points and their dependencies
2
+ const path = require('path')
3
+ const fs = require('fs')
4
+ const { depGraph, resolveDepPaths } = require('./dep-graph')
5
+
6
+ /**
7
+ * Extract all entry points from package.json
8
+ * @param {string} packageJsonPath - Path to package.json
9
+ * @returns {Array<{field: string, path: string, resolvedPath: string}>} - Array of entry point objects
10
+ */
11
+ function getPackageEntryPoints(packageJsonPath) {
12
+ const packageDir = path.dirname(packageJsonPath)
13
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
14
+
15
+ return collectEntryPoints(packageJson, packageDir)
16
+ }
17
+
18
+ function collectEntryPoints(packageJson, packageDir) {
19
+ const entryPoints = []
20
+
21
+ /**
22
+ * Add entry point if it's a valid JS file
23
+ * @param {string} field - Field name from package.json
24
+ * @param {string} filePath - Relative file path
25
+ */
26
+ function addEntryPoint(field, filePath) {
27
+ if (!filePath) return
28
+
29
+ // Skip .d.ts files
30
+ if (filePath.endsWith('.d.ts')) return
31
+
32
+ // Skip .json files
33
+ if (filePath.endsWith('.json')) return
34
+
35
+ const resolvedPath = path.resolve(packageDir, filePath)
36
+ entryPoints.push({
37
+ field,
38
+ path: filePath,
39
+ resolvedPath
40
+ })
41
+ }
42
+
43
+ // Handle main, module, browser, types
44
+ if (packageJson.main) {
45
+ addEntryPoint('main', packageJson.main)
46
+ }
47
+ if (packageJson.module) {
48
+ addEntryPoint('module', packageJson.module)
49
+ }
50
+ if (packageJson.browser) {
51
+ if (typeof packageJson.browser === 'string') {
52
+ addEntryPoint('browser', packageJson.browser)
53
+ }
54
+ // browser can also be an object mapping, skip for now
55
+ }
56
+ if (packageJson.types) {
57
+ // Skip .d.ts files
58
+ if (!packageJson.types.endsWith('.d.ts')) {
59
+ addEntryPoint('types', packageJson.types)
60
+ }
61
+ }
62
+
63
+ // Handle bin
64
+ if (packageJson.bin) {
65
+ if (typeof packageJson.bin === 'string') {
66
+ addEntryPoint('bin', packageJson.bin)
67
+ } else if (typeof packageJson.bin === 'object') {
68
+ for (const [binName, binPath] of Object.entries(packageJson.bin)) {
69
+ addEntryPoint(`bin.${binName}`, binPath)
70
+ }
71
+ }
72
+ }
73
+
74
+ // Handle exports
75
+ if (packageJson.exports) {
76
+ extractExportsEntryPoints(packageJson.exports, 'exports', addEntryPoint)
77
+ }
78
+
79
+ return entryPoints
80
+ }
81
+
82
+ /**
83
+ * Recursively extract entry points from exports field
84
+ * @param {any} exportsValue - Value from exports field
85
+ * @param {string} fieldPrefix - Current field path
86
+ * @param {Function} addEntryPoint - Callback to add entry point
87
+ */
88
+ function extractExportsEntryPoints(exportsValue, fieldPrefix, addEntryPoint) {
89
+ if (typeof exportsValue === 'string') {
90
+ addEntryPoint(fieldPrefix, exportsValue)
91
+ } else if (typeof exportsValue === 'object' && exportsValue !== null) {
92
+ for (const [key, value] of Object.entries(exportsValue)) {
93
+ // Build field name with bracket notation for paths that start with . or contain special chars
94
+ const needsBrackets = key.startsWith('.') || key.startsWith('/')
95
+ const newFieldPrefix = needsBrackets ? `${fieldPrefix}["${key}"]` : `${fieldPrefix}.${key}`
96
+
97
+ // Handle conditional exports (import, require, browser, types, etc.)
98
+ if (key === 'import' || key === 'require' || key === 'browser' || key === 'default') {
99
+ if (typeof value === 'string') {
100
+ addEntryPoint(newFieldPrefix, value)
101
+ }
102
+ } else if (key === 'types') {
103
+ // Skip .d.ts files
104
+ if (typeof value === 'string' && !value.endsWith('.d.ts')) {
105
+ addEntryPoint(newFieldPrefix, value)
106
+ }
107
+ } else {
108
+ // Recurse for nested exports
109
+ extractExportsEntryPoints(value, newFieldPrefix, addEntryPoint)
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Verify all package.json entry points and collect dependencies
117
+ * @param {string} packagePath - Path to package.json
118
+ * @param {object} options - Options
119
+ * @param {boolean} [options.collectDeps] - If true, collect dependency paths for each entry point. Default: true
120
+ * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
121
+ * @param {boolean} [options.throwIfErrors] - If true, throw an error if there are any errors. Default: false
122
+ * @returns {{entryPoints: Array, sourceFiles: Set, errors: Array}} - Verification results
123
+ */
124
+ function verifyPackageEntries(packagePath, options = {}) {
125
+ const { collectDeps = true, verifyGraph = true } = options
126
+
127
+ const entryPoints = getPackageEntryPoints(packagePath)
128
+ const sourceFilesSet = new Set()
129
+ const allErrors = []
130
+ const results = []
131
+
132
+ for (const entryPoint of entryPoints) {
133
+ const result = {
134
+ field: entryPoint.field,
135
+ path: entryPoint.path,
136
+ resolvedPath: entryPoint.resolvedPath,
137
+ valid: false,
138
+ sourceFiles: [],
139
+ error: undefined
140
+ }
141
+
142
+ try {
143
+ // Check if file exists
144
+ if (!fs.existsSync(entryPoint.resolvedPath)) {
145
+ throw new Error(
146
+ `package.json "${entryPoint.field}" points to non-existent file: "${entryPoint.resolvedPath}"`
147
+ )
148
+ }
149
+
150
+ if (collectDeps) {
151
+ // Collect dependencies using resolveDepPaths
152
+ const relativeDeps = resolveDepPaths(entryPoint.resolvedPath, { verifyGraph, verifyNodeModules: true })
153
+ result.sourceFiles = relativeDeps
154
+
155
+ // Add to sourceFiles set
156
+ relativeDeps.forEach(dep => sourceFilesSet.add(dep))
157
+ } else if (verifyGraph) {
158
+ // Just verify without collecting deps
159
+ depGraph(entryPoint.resolvedPath, { buildGraph: false, verifyGraph: true })
160
+ }
161
+
162
+ result.valid = true
163
+ } catch (error) {
164
+ result.error = error
165
+ allErrors.push({
166
+ packagePath,
167
+ field: entryPoint.field,
168
+ path: entryPoint.path,
169
+ errorMsg: error.message,
170
+ error: error
171
+ })
172
+ }
173
+
174
+ results.push(result)
175
+ }
176
+
177
+ if (options.throwIfErrors && allErrors.length) {
178
+ throw new Error(
179
+ `Found ${allErrors.length} validation error${allErrors.length === 1 ? '' : 's'}:
180
+ ${JSON.stringify(allErrors, null, 2)}`
181
+ )
182
+ }
183
+
184
+ return {
185
+ entryPoints: results,
186
+ sourceFiles: sourceFilesSet,
187
+ errors: allErrors
188
+ }
189
+ }
190
+
191
+ if (require.main === module) {
192
+ let pkgPath = path.resolve(__dirname, '../tests/fixtures/esm-broken-fixture/package.json')
193
+ //pkgPath = path.resolve(__dirname, '../tests/fixtures/cjs-broken-example/package.json')
194
+ console.log('=== Package Entry Points ===\n')
195
+ const entryPoints = getPackageEntryPoints(pkgPath)
196
+ entryPoints.forEach(ep => {
197
+ console.log(`${ep.field}: ${ep.path}`)
198
+ })
199
+
200
+ console.log('\n=== Verification Results ===\n')
201
+ const throwIfErrors = false
202
+ const results = verifyPackageEntries(pkgPath, { throwIfErrors })
203
+ console.log('results', results)
204
+
205
+ results.entryPoints.forEach(ep => {
206
+
207
+ console.log(`\nField "${ep.field}": ${ep.valid ? '' : ''}`)
208
+ if (ep.sourceFiles) {
209
+ console.log(` - Dependencies: ${ep.sourceFiles.length} files`)
210
+ ep.sourceFiles.forEach(sf => {
211
+ console.log(`- ${sf}`)
212
+ })
213
+ }
214
+ if (ep.error) {
215
+ console.log(`- Error: ${ep.error.message}`)
216
+ }
217
+ console.log('───────────────────────────────')
218
+ })
219
+
220
+ console.log(`\nTotal unique dependencies: ${results.sourceFiles.size}`)
221
+ console.log(`Total errors: ${results.errors.length}`)
222
+ }
223
+
224
+ module.exports = {
225
+ getPackageEntryPoints,
226
+ verifyPackageEntries,
227
+ collectEntryPoints
228
+ }