@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.
Files changed (34) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/examples/verify-basic.js +2 -0
  3. package/examples/verify-package-entries.js +74 -0
  4. package/package.json +2 -2
  5. package/src/dep-graph.js +82 -14
  6. package/src/get-cjs-exports.js +112 -7
  7. package/src/get-imports.js +1 -0
  8. package/src/utils.js +1 -1
  9. package/src/vendor/inspect-parameters-declaration.js +282 -0
  10. package/src/verify-package-entries.js +228 -0
  11. package/tests/dep-graph.test.js +172 -0
  12. package/tests/fixtures/cjs-broken-example/index.js +8 -0
  13. package/tests/fixtures/cjs-broken-example/one.js +9 -0
  14. package/tests/fixtures/cjs-broken-example/package.json +7 -0
  15. package/tests/fixtures/cjs-broken-example/sub-folder/sub-sub-folder/three.js +6 -0
  16. package/tests/fixtures/cjs-broken-example/sub-folder/two.js +8 -0
  17. package/tests/fixtures/cjs-deep-example/package.json +15 -0
  18. package/tests/fixtures/cjs-example/package.json +7 -0
  19. package/tests/fixtures/dir-index-example/entry.js +6 -0
  20. package/tests/fixtures/dir-index-example/shared-pkg/index.js +4 -0
  21. package/tests/fixtures/esm-broken-fixture/index.js +8 -0
  22. package/tests/fixtures/esm-broken-fixture/one.js +9 -0
  23. package/tests/fixtures/esm-broken-fixture/package.json +15 -0
  24. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder/three.js +6 -0
  25. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/deep.js +6 -0
  26. package/tests/fixtures/esm-broken-fixture/sub-folder/sub-sub-folder-two/even-deeper/wow-deep.js +5 -0
  27. package/tests/fixtures/esm-broken-fixture/sub-folder/two.js +8 -0
  28. package/tests/fixtures/esm-broken-fixture/valid.js +7 -0
  29. package/tests/fixtures/esm-fixture/package.json +11 -0
  30. package/tests/get-cjs-exports.test.js +171 -0
  31. package/tests/get-imports.test.js +4 -1
  32. package/tests/manual/broken-cjs.js +38 -0
  33. package/tests/manual/broken-esm.js +37 -0
  34. package/tests/verify-package-entries.test.js +272 -0
@@ -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": []
@@ -15,6 +15,8 @@ try {
15
15
  console.error(error.message)
16
16
  }
17
17
 
18
+ console.log('foo')
19
+
18
20
  console.log('Verifying ESM fixture...')
19
21
  try {
20
22
  const graph = depGraph(esmFixture)
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidwells/extract-deps",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Get imports and requires from a codebase without AST",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "release:major": "npm version major && npm publish"
23
23
  },
24
24
  "dependencies": {
25
- "inspect-parameters-declaration": "^0.1.0"
25
+ "@dual-bundle/import-meta-resolve": "^4.2.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^5.7.3",
package/src/dep-graph.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // Build dependency graph and optionally verify imports
2
2
  const path = require('path')
3
3
  const fs = require('fs')
4
+ const { pathToFileURL, fileURLToPath } = require('url')
5
+ const { resolve: resolveImport } = require('@dual-bundle/import-meta-resolve')
4
6
  const { extractDeps } = require('./index')
5
7
 
6
8
  function fsExistsSync(path) {
@@ -12,6 +14,14 @@ function fsExistsSync(path) {
12
14
  }
13
15
  }
14
16
 
17
+ function isFileSync(filePath) {
18
+ try {
19
+ return fs.statSync(filePath).isFile()
20
+ } catch (error) {
21
+ return false
22
+ }
23
+ }
24
+
15
25
  /**
16
26
  * Resolve a relative import to an actual file path
17
27
  * @param {string} baseDir - Directory containing the importing file
@@ -21,10 +31,10 @@ function fsExistsSync(path) {
21
31
  function resolveImportPath(baseDir, importPath) {
22
32
  const possibleExtensions = ['.js', '.mjs', '.ts', '.json', '']
23
33
 
24
- // Try direct file resolution
34
+ // Try direct file resolution (must be a file, not directory)
25
35
  for (const ext of possibleExtensions) {
26
36
  const testPath = path.resolve(baseDir, importPath + ext)
27
- if (fsExistsSync(testPath)) {
37
+ if (isFileSync(testPath)) {
28
38
  return testPath
29
39
  }
30
40
  }
@@ -80,10 +90,12 @@ function verifyImportedValuesExist(dep, targetDeps, targetPath, sourcePath, erro
80
90
  // Verify each imported name is exported
81
91
  for (const importedName of importedNames) {
82
92
  if (!exportedNames.has(importedName)) {
93
+ const type = (dep.line.indexOf('import') !== -1 ? 'Import' : 'Require')
83
94
  const error = new Error(
84
- `Import not found: "${importedName}" is not exported from "${targetPath}".\n` +
85
- `Import statement: ${dep.line}\n` +
86
- `Importing from: ${sourcePath}\n` +
95
+ `\n${type} "${importedName}" not found from statement:\n` +
96
+ `${dep.line}\n` +
97
+ `in "${sourcePath} file.\n\n` +
98
+ `The "${importedName}" value is not exported from file "${targetPath}".\n\n` +
87
99
  `Available exports: ${Array.from(exportedNames).join(', ') || 'none'}`
88
100
  )
89
101
  if (throwOnFirstError) {
@@ -119,10 +131,19 @@ function verifyImportedValuesExist(dep, targetDeps, targetPath, sourcePath, erro
119
131
  * @param {Array<Error>} errors - Array to collect errors
120
132
  * @param {boolean} throwOnFirstError - Whether to throw immediately on first error
121
133
  * @param {boolean} verify - Whether to verify imports/exports
134
+ * @param {boolean} verifyNodeModules - Whether to verify node_modules can be resolved
122
135
  * @param {Map<string, string[]>|null} graph - Optional map to collect dependency graph
123
136
  * @param {Set} visited - Set of already-visited file paths to avoid cycles
124
137
  */
125
- function buildGraphRecursively(filePath, errors, throwOnFirstError, verify, graph = null, visited = new Set()) {
138
+ function buildGraphRecursively(
139
+ filePath,
140
+ errors,
141
+ throwOnFirstError,
142
+ verify,
143
+ verifyNodeModules,
144
+ graph = null,
145
+ visited = new Set(),
146
+ ) {
126
147
  // Avoid infinite loops
127
148
  const normalizedPath = fs.realpathSync(filePath)
128
149
  if (visited.has(normalizedPath)) {
@@ -148,6 +169,17 @@ function buildGraphRecursively(filePath, errors, throwOnFirstError, verify, grap
148
169
  relativeDeps.push(...deps.imports.filter(imp => imp.isRelative))
149
170
  }
150
171
 
172
+ // Collect all non-relative dependencies (node_modules)
173
+ const nodeModuleDeps = []
174
+ if (verifyNodeModules) {
175
+ if (deps.requires && deps.requires.length > 0) {
176
+ nodeModuleDeps.push(...deps.requires.filter(req => !req.isRelative))
177
+ }
178
+ if (deps.imports && deps.imports.length > 0) {
179
+ nodeModuleDeps.push(...deps.imports.filter(imp => !imp.isRelative))
180
+ }
181
+ }
182
+
151
183
  // Process each relative dependency
152
184
  const fileDir = path.dirname(filePath)
153
185
  for (const dep of relativeDeps) {
@@ -185,7 +217,34 @@ function buildGraphRecursively(filePath, errors, throwOnFirstError, verify, grap
185
217
  }
186
218
 
187
219
  // Recursively process the imported file
188
- buildGraphRecursively(resolvedPath, errors, throwOnFirstError, verify, graph, visited)
220
+ buildGraphRecursively(resolvedPath, errors, throwOnFirstError, verify, verifyNodeModules, graph, visited)
221
+ }
222
+
223
+ // Verify node_modules dependencies if enabled
224
+ if (verifyNodeModules && nodeModuleDeps.length > 0) {
225
+ const parentURL = pathToFileURL(filePath).href
226
+
227
+ for (const dep of nodeModuleDeps) {
228
+ const { from, line } = dep
229
+
230
+ try {
231
+ // Try to resolve the module using Node's resolution algorithm
232
+ const resolved = resolveImport(from, parentURL)
233
+ //console.log('resolved', resolved)
234
+ // If we get here, the module resolved successfully
235
+ } catch (error) {
236
+ const err = new Error(
237
+ `"${line}" could not be resolved in ${filePath}\n\n` +
238
+ `The package "${from}" is not installed or cannot be found.\n` +
239
+ `Run "npm install ${from}" to fix this issue.\n\n` +
240
+ `Resolution error: ${error.message}`
241
+ )
242
+ if (throwOnFirstError) {
243
+ throw err
244
+ }
245
+ errors.push(err)
246
+ }
247
+ }
189
248
  }
190
249
  }
191
250
 
@@ -195,19 +254,27 @@ function buildGraphRecursively(filePath, errors, throwOnFirstError, verify, grap
195
254
  * @param {object} options - Options
196
255
  * @param {boolean} [options.buildGraph] - If true, build and return dependency graph. Default: true
197
256
  * @param {boolean} [options.verifyGraph] - If true, verify imports/exports. Default: true
257
+ * @param {boolean} [options.verifyNodeModules] - If true, verify node_modules packages can be resolved. Default: false
198
258
  * @param {boolean} [options.throwOnFirstError] - If true, throw immediately on first error. Default: false
199
259
  * @returns {Map<string, string[]>|undefined} - Dependency graph where keys are file paths and values are arrays of dependency paths. Returns undefined if buildGraph is false.
200
260
  */
201
261
  function depGraph(filePath, options = {}) {
202
- const { buildGraph = true, verifyGraph = true, throwOnFirstError = false } = options
262
+ const {
263
+ buildGraph = true,
264
+ verifyGraph = true,
265
+ verifyNodeModules = false,
266
+ throwOnFirstError = false,
267
+ } = options
203
268
  const errors = []
204
269
  const graph = buildGraph ? new Map() : null
205
270
 
206
- buildGraphRecursively(filePath, errors, throwOnFirstError, verifyGraph, graph)
271
+ buildGraphRecursively(filePath, errors, throwOnFirstError, verifyGraph, verifyNodeModules, graph)
207
272
 
208
273
  if (errors.length > 0) {
209
- const errorMessage = errors.map((err, idx) => `Error ${idx + 1}:\n${err.message}`).join('\n\n')
210
- throw new Error(`Found ${errors.length} validation error${errors.length === 1 ? '' : 's'}:\n\n${errorMessage}`)
274
+ const errorMessage = errors.map((err, idx) => `Dep graph error:\n ${err.message}`).join('\n')
275
+ throw new Error(
276
+ `Found ${errors.length} validation error${errors.length === 1 ? '' : 's'}:\n\n${errorMessage}`
277
+ )
211
278
  }
212
279
 
213
280
  if (buildGraph) {
@@ -263,10 +330,11 @@ function resolveDepPaths(filePath, options = {}) {
263
330
 
264
331
  if (require.main === module) {
265
332
  const entry = path.resolve(__dirname, '../tests/fixtures/cjs-deep-example/index.js')
266
- const graph = depGraph(entry)
333
+ const graph = depGraph(entry, {
334
+ verifyNodeModules: false,
335
+ throwOnFirstError: true
336
+ })
267
337
  console.log('graph', graph)
268
- const depPaths = Array.from(graph.values()).flat()
269
- console.log('depPaths', depPaths)
270
338
  const depPathsProper = resolveDepPaths(entry)
271
339
  console.log('depPathsProper', depPathsProper)
272
340
  }
@@ -8,17 +8,22 @@ const MODULE_EXPORTS_ASSIGNMENT_REGEX = /^(?!\s*\/\/)(?!\s*\/\*)\s*(?:exports\s*
8
8
 
9
9
  // Matches: module.exports.propertyName = ... or module.exports['propertyName'] = ...
10
10
  // https://regex101.com/r/example/cjs-2
11
- const MODULE_EXPORTS_PROPERTY_REGEX = /module\.exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s*(?:module\.exports|exports\.|\/\/|\/\*|$))/gs
11
+ const MODULE_EXPORTS_PROPERTY_REGEX = /module\.exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\n\s*\n|(?:module\.exports|exports\.|Object\.defineProperty|\/\/|\/\*|$))/gs
12
12
 
13
13
  // Matches: exports.propertyName = ... or exports['propertyName'] = ...
14
14
  // https://regex101.com/r/example/cjs-3
15
- const EXPORTS_PROPERTY_REGEX = /(?<!module\.)exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\s*(?:module\.exports|exports\.|\/\/|\/\*|$))/gs
15
+ const EXPORTS_PROPERTY_REGEX = /(?<!module\.)exports(?:\.([\w$]+)|\[['"]([^'"]+)['"]\])\s*=\s*(.+?)(?=\n\s*\n|(?:module\.exports|exports\.|Object\.defineProperty|\/\/|\/\*|$))/gs
16
16
 
17
17
  function stripComments(code) {
18
18
  let result = code
19
19
 
20
- // Remove single-line comments but preserve newlines
21
- result = result.replace(/\/\/.*$/gm, '')
20
+ // Remove single-line comments but preserve string positions
21
+ // This is critical: we must preserve the exact length of the string so that
22
+ // indices found in sanitized text match indices in original text
23
+ result = result.replace(/\/\/(.*)$/gm, (match, commentText) => {
24
+ // Replace // and comment text with spaces (same length as original)
25
+ return ' ' + ' '.repeat(commentText.length)
26
+ })
22
27
 
23
28
  // Remove multi-line comments but preserve line structure
24
29
  result = result.replace(/\/\*[\s\S]*?\*\//g, (match) => {
@@ -36,13 +41,22 @@ function getCjsExports(code) {
36
41
  const assignmentExports = getModuleExportsAssignment(code, sanitizedCode)
37
42
  const modulePropertyExports = getModuleExportsProperty(code, sanitizedCode)
38
43
  const propertyExports = getExportsProperty(code, sanitizedCode)
39
- const objectAssignExports = getObjectAssignExports(code, sanitizedCode)
44
+
45
+ // Quick pre-checks to avoid expensive regex operations when not needed
46
+ const objectAssignExports = sanitizedCode.includes('Object.assign')
47
+ ? getObjectAssignExports(code, sanitizedCode)
48
+ : []
49
+
50
+ const definePropertyExports = sanitizedCode.includes('Object.defineProperty')
51
+ ? getDefinePropertyExports(code, sanitizedCode)
52
+ : []
40
53
 
41
54
  /*
42
55
  console.log('assignmentExports', assignmentExports)
43
56
  console.log('modulePropertyExports', modulePropertyExports)
44
57
  console.log('propertyExports', propertyExports)
45
58
  console.log('objectAssignExports', objectAssignExports)
59
+ console.log('definePropertyExports', definePropertyExports)
46
60
  /** */
47
61
 
48
62
  const allExports = []
@@ -50,6 +64,7 @@ function getCjsExports(code) {
50
64
  .concat(modulePropertyExports)
51
65
  .concat(propertyExports)
52
66
  .concat(objectAssignExports)
67
+ .concat(definePropertyExports)
53
68
 
54
69
  return allExports.sort((a, b) => a.index - b.index)
55
70
  }
@@ -283,7 +298,81 @@ function getObjectAssignExports(originalText = '', sanitizedText = originalText)
283
298
  return exportStatements
284
299
  }
285
300
 
286
- if(require.main === module) {
301
+ function getDefinePropertyExports(originalText = '', sanitizedText = originalText) {
302
+ let exportStatements = []
303
+
304
+ // Find all Object.defineProperty calls with module or exports
305
+ // Matches: Object.defineProperty(module, 'exports', ...) or Object.defineProperty(exports, 'propertyName', ...)
306
+ const definePropertyPattern = /Object\.defineProperty\s*\(\s*(module|exports)\s*,\s*['"]([^'"]+)['"]/g
307
+ var result
308
+
309
+ while((result = definePropertyPattern.exec(sanitizedText)) !== null) {
310
+ const target = result[1]
311
+ const propertyName = result[2]
312
+
313
+ // Find the opening parenthesis of Object.defineProperty
314
+ const openParenIndex = result.index + result[0].indexOf('(')
315
+
316
+ // Balance parentheses to find the complete Object.defineProperty call
317
+ let depth = 0
318
+ let i = openParenIndex
319
+ let completeCall = ''
320
+
321
+ while (i < originalText.length) {
322
+ const char = originalText[i]
323
+ if (char === '(') depth++
324
+ else if (char === ')') {
325
+ depth--
326
+ if (depth === 0) {
327
+ completeCall = originalText.substring(result.index, i + 1)
328
+ break
329
+ }
330
+ }
331
+ i++
332
+ }
333
+
334
+ if (!completeCall) {
335
+ continue
336
+ }
337
+
338
+ // Try to extract the getter function if present
339
+ // Matches: get: functionName or get: () => ... or get() { ... }
340
+ const getterMatch = completeCall.match(/get\s*:\s*(\w+)|get\s*:\s*(\([^)]*\)\s*=>)|get\s*\(/)
341
+ let getterValue = ''
342
+ if (getterMatch) {
343
+ if (getterMatch[1]) {
344
+ // Named function: get: functionName
345
+ getterValue = getterMatch[1]
346
+ } else if (getterMatch[2]) {
347
+ // Arrow function: get: () =>
348
+ getterValue = 'arrow function'
349
+ } else {
350
+ // Method: get() { ... }
351
+ getterValue = 'getter method'
352
+ }
353
+ }
354
+
355
+ // Determine if this is a module.exports or property export
356
+ const isModuleExports = target === 'module' && propertyName === 'exports'
357
+
358
+ const exportStatement = {
359
+ line: completeCall.trim(),
360
+ exportType: isModuleExports ? 'default' : 'named',
361
+ exportName: isModuleExports ? '' : propertyName,
362
+ exportValue: completeCall, // Original text with comments
363
+ exportDefinitionName: getterValue || propertyName,
364
+ index: result.index,
365
+ isExport: true,
366
+ isDefineProperty: true
367
+ }
368
+
369
+ exportStatements.push(exportStatement)
370
+ }
371
+
372
+ return exportStatements
373
+ }
374
+
375
+ if (require.main === module) {
287
376
  const code = `
288
377
  // module.exports assignment
289
378
  module.exports = function hello() {}
@@ -309,6 +398,21 @@ module.exports.MyClass = class MyClass {}
309
398
  // exports properties
310
399
  exports.foo = function() {}
311
400
  exports.bar = 'bar'
401
+
402
+ // Object.defineProperty exports
403
+ function assembleStyles() {
404
+ return { color: 'red' }
405
+ }
406
+
407
+ Object.defineProperty(module, 'exports', {
408
+ enumerable: true,
409
+ get: assembleStyles
410
+ })
411
+
412
+ Object.defineProperty(exports, 'myProperty', {
413
+ enumerable: true,
414
+ get: () => 'value'
415
+ })
312
416
  `
313
417
 
314
418
  const exportsFound = getCjsExports(code)
@@ -320,5 +424,6 @@ module.exports = {
320
424
  getModuleExportsAssignment,
321
425
  getModuleExportsProperty,
322
426
  getExportsProperty,
323
- getObjectAssignExports
427
+ getObjectAssignExports,
428
+ getDefinePropertyExports
324
429
  }
@@ -52,6 +52,7 @@ function getTypeImports(targetText = '') {
52
52
  importName: (importName || '').trim(),
53
53
  index: result.index,
54
54
  isImport: true,
55
+ isTypeImport: true,
55
56
  isRelative: isRelativeImport(importPath)
56
57
  })
57
58
  }
package/src/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- const { inspectParameters, getParametersNames } = require('inspect-parameters-declaration')
1
+ const { inspectParameters, getParametersNames } = require('./vendor/inspect-parameters-declaration')
2
2
 
3
3
  function isRelativeImport(importLocation) {
4
4
  return !!importLocation.match(/^\./)