@davidwells/extract-deps 0.0.5 → 0.0.7
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/.claude/settings.local.json +7 -1
- package/examples/verify-basic.js +2 -0
- package/examples/verify-package-entries.js +74 -0
- package/package.json +2 -2
- package/src/dep-graph.js +82 -14
- package/src/get-cjs-exports.js +112 -7
- package/src/get-imports.js +1 -0
- package/src/utils.js +1 -1
- package/src/vendor/inspect-parameters-declaration.js +282 -0
- package/src/verify-package-entries.js +228 -0
- package/tests/dep-graph.test.js +172 -0
- package/tests/fixtures/cjs-broken-example/index.js +8 -0
- package/tests/fixtures/cjs-broken-example/one.js +9 -0
- package/tests/fixtures/cjs-broken-example/package.json +7 -0
- package/tests/fixtures/cjs-broken-example/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/cjs-broken-example/sub-folder/two.js +8 -0
- package/tests/fixtures/cjs-deep-example/package.json +15 -0
- package/tests/fixtures/cjs-example/package.json +7 -0
- package/tests/fixtures/dir-index-example/entry.js +6 -0
- package/tests/fixtures/dir-index-example/shared-pkg/index.js +4 -0
- package/tests/fixtures/esm-broken-fixture/index.js +8 -0
- package/tests/fixtures/esm-broken-fixture/one.js +9 -0
- package/tests/fixtures/esm-broken-fixture/package.json +15 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder/three.js +6 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/deep.js +6 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/even-deeper/wow-deep.js +5 -0
- package/tests/fixtures/esm-broken-fixture/sub-folder/two.js +8 -0
- package/tests/fixtures/esm-broken-fixture/valid.js +7 -0
- package/tests/fixtures/esm-fixture/package.json +11 -0
- package/tests/get-cjs-exports.test.js +171 -0
- package/tests/get-imports.test.js +4 -1
- package/tests/manual/broken-cjs.js +38 -0
- package/tests/manual/broken-esm.js +37 -0
- package/tests/verify-package-entries.test.js +272 -0
|
@@ -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
|
+
}
|
package/tests/dep-graph.test.js
CHANGED
|
@@ -210,4 +210,176 @@ test('resolveDepPaths should pass options to depGraph', () => {
|
|
|
210
210
|
assert.ok(deps.length > 0, 'Should return dependencies')
|
|
211
211
|
})
|
|
212
212
|
|
|
213
|
+
// verifyNodeModules tests
|
|
214
|
+
test('verifyNodeModules should default to false', () => {
|
|
215
|
+
// CJS_DEEP_FIXTURE_PATH requires 'lodash' but without verifyNodeModules
|
|
216
|
+
// it should not check if lodash is installed
|
|
217
|
+
assert.not.throws(() => {
|
|
218
|
+
depGraph(CJS_DEEP_FIXTURE_PATH)
|
|
219
|
+
}, 'Should not verify node_modules by default')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('verifyNodeModules: false should skip node_modules verification', () => {
|
|
223
|
+
const graph = depGraph(CJS_DEEP_FIXTURE_PATH, { verifyNodeModules: false })
|
|
224
|
+
|
|
225
|
+
assert.ok(graph instanceof Map, 'Should return a Map')
|
|
226
|
+
assert.ok(graph.size > 0, 'Graph should not be empty')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('verifyNodeModules: true should verify installed packages', () => {
|
|
230
|
+
// CJS_DEEP_FIXTURE_PATH requires 'lodash' which should be installed
|
|
231
|
+
assert.not.throws(() => {
|
|
232
|
+
depGraph(CJS_DEEP_FIXTURE_PATH, { verifyNodeModules: true })
|
|
233
|
+
}, 'Should verify and find installed packages')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('verifyNodeModules: true should build graph successfully with valid packages', () => {
|
|
237
|
+
const graph = depGraph(CJS_DEEP_FIXTURE_PATH, { verifyNodeModules: true })
|
|
238
|
+
|
|
239
|
+
assert.ok(graph instanceof Map, 'Should return a Map')
|
|
240
|
+
assert.ok(graph.size > 0, 'Graph should not be empty')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('verifyNodeModules: true should throw error for missing packages', () => {
|
|
244
|
+
const fs = require('fs')
|
|
245
|
+
const testFile = path.resolve(__dirname, 'fixtures/test-missing-pkg.js')
|
|
246
|
+
|
|
247
|
+
// Create temporary test file with missing package
|
|
248
|
+
fs.writeFileSync(testFile, `const fake = require('this-package-does-not-exist-xyz-12345')\nmodule.exports = { fake }`)
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
assert.throws(() => {
|
|
252
|
+
depGraph(testFile, { verifyNodeModules: true })
|
|
253
|
+
}, 'Should throw error for missing package')
|
|
254
|
+
} finally {
|
|
255
|
+
fs.unlinkSync(testFile)
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('verifyNodeModules: true error message should be helpful', () => {
|
|
260
|
+
const fs = require('fs')
|
|
261
|
+
const testFile = path.resolve(__dirname, 'fixtures/test-missing-pkg-2.js')
|
|
262
|
+
const missingPkg = 'nonexistent-package-xyz'
|
|
263
|
+
|
|
264
|
+
fs.writeFileSync(testFile, `const fake = require('${missingPkg}')\nmodule.exports = { fake }`)
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
let errorMessage = ''
|
|
268
|
+
try {
|
|
269
|
+
depGraph(testFile, { verifyNodeModules: true })
|
|
270
|
+
} catch (error) {
|
|
271
|
+
errorMessage = error.message
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
assert.ok(errorMessage.includes(missingPkg), 'Error should mention the missing package name')
|
|
275
|
+
assert.ok(errorMessage.includes('could not be resolved'), 'Error should say it could not be resolved')
|
|
276
|
+
} finally {
|
|
277
|
+
fs.unlinkSync(testFile)
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('verifyNodeModules: true should work with both CJS and ESM', () => {
|
|
282
|
+
// Both fixtures should work (they only have relative imports)
|
|
283
|
+
assert.not.throws(() => {
|
|
284
|
+
depGraph(CJS_FIXTURE_PATH, { verifyNodeModules: true })
|
|
285
|
+
}, 'Should work with CJS')
|
|
286
|
+
|
|
287
|
+
assert.not.throws(() => {
|
|
288
|
+
depGraph(ESM_FIXTURE_PATH, { verifyNodeModules: true })
|
|
289
|
+
}, 'Should work with ESM')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('verifyNodeModules should work with all other options', () => {
|
|
293
|
+
const graph = depGraph(CJS_DEEP_FIXTURE_PATH, {
|
|
294
|
+
buildGraph: true,
|
|
295
|
+
verifyGraph: true,
|
|
296
|
+
verifyNodeModules: true,
|
|
297
|
+
throwOnFirstError: false
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
assert.ok(graph instanceof Map, 'Should return a Map')
|
|
301
|
+
assert.ok(graph.size > 0, 'Graph should not be empty')
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('verifyNodeModules with throwOnFirstError: true should throw immediately', () => {
|
|
305
|
+
const fs = require('fs')
|
|
306
|
+
const testFile = path.resolve(__dirname, 'fixtures/test-throw-first.js')
|
|
307
|
+
|
|
308
|
+
fs.writeFileSync(testFile, `
|
|
309
|
+
const missing1 = require('missing-package-1')
|
|
310
|
+
const missing2 = require('missing-package-2')
|
|
311
|
+
module.exports = { missing1, missing2 }
|
|
312
|
+
`)
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
let errorThrown = false
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
depGraph(testFile, { verifyNodeModules: true, throwOnFirstError: true })
|
|
319
|
+
} catch (error) {
|
|
320
|
+
errorThrown = true
|
|
321
|
+
// When throwOnFirstError is true, should throw immediately on first error
|
|
322
|
+
assert.ok(error.message.includes('missing-package-1'), 'Should throw on first missing package')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
assert.ok(errorThrown, 'Should throw an error')
|
|
326
|
+
} finally {
|
|
327
|
+
fs.unlinkSync(testFile)
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('verifyNodeModules with throwOnFirstError: false should collect all errors', () => {
|
|
332
|
+
const fs = require('fs')
|
|
333
|
+
const testFile = path.resolve(__dirname, 'fixtures/test-collect-errors.js')
|
|
334
|
+
|
|
335
|
+
fs.writeFileSync(testFile, `
|
|
336
|
+
const missing1 = require('missing-package-1')
|
|
337
|
+
const missing2 = require('missing-package-2')
|
|
338
|
+
module.exports = { missing1, missing2 }
|
|
339
|
+
`)
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
let errorMessage = ''
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
depGraph(testFile, { verifyNodeModules: true, throwOnFirstError: false })
|
|
346
|
+
} catch (error) {
|
|
347
|
+
errorMessage = error.message
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
assert.ok(errorMessage.includes('missing-package-1'), 'Should include first error')
|
|
351
|
+
assert.ok(errorMessage.includes('missing-package-2'), 'Should include second error')
|
|
352
|
+
assert.ok(errorMessage.includes('2 validation error'), 'Should mention 2 errors')
|
|
353
|
+
} finally {
|
|
354
|
+
fs.unlinkSync(testFile)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// Directory to index.js resolution tests
|
|
359
|
+
const DIR_INDEX_FIXTURE_PATH = path.resolve(__dirname, './fixtures/dir-index-example/entry.js')
|
|
360
|
+
|
|
361
|
+
test('should resolve directory import to index.js', () => {
|
|
362
|
+
const graph = depGraph(DIR_INDEX_FIXTURE_PATH)
|
|
363
|
+
|
|
364
|
+
assert.ok(graph instanceof Map, 'Should return a Map')
|
|
365
|
+
assert.ok(graph.size === 2, 'Graph should have 2 files (entry.js and shared-pkg/index.js)')
|
|
366
|
+
|
|
367
|
+
const entries = Array.from(graph.keys())
|
|
368
|
+
const hasEntry = entries.some(key => key.includes('entry.js'))
|
|
369
|
+
const hasIndex = entries.some(key => key.includes('shared-pkg/index.js'))
|
|
370
|
+
|
|
371
|
+
assert.ok(hasEntry, 'Graph should include entry.js')
|
|
372
|
+
assert.ok(hasIndex, 'Graph should resolve directory to index.js')
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test('resolveDepPaths should work with directory imports', () => {
|
|
376
|
+
const deps = resolveDepPaths(DIR_INDEX_FIXTURE_PATH)
|
|
377
|
+
|
|
378
|
+
assert.ok(Array.isArray(deps), 'Should return an array')
|
|
379
|
+
assert.is(deps.length, 2, 'Should have 2 files')
|
|
380
|
+
|
|
381
|
+
assert.ok(deps.some(p => p.includes('shared-pkg/index.js')), 'Should include resolved index.js')
|
|
382
|
+
assert.ok(deps.some(p => p.includes('entry.js')), 'Should include entry.js')
|
|
383
|
+
})
|
|
384
|
+
|
|
213
385
|
test.run()
|