dd-trace 2.45.1 → 2.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.45.1",
3
+ "version": "2.46.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -50,12 +50,18 @@ for (const pkg of INSTRUMENTED) {
50
50
  module.exports.name = 'datadog-esbuild'
51
51
 
52
52
  module.exports.setup = function (build) {
53
+ const externalModules = new Set(build.initialOptions.external || [])
53
54
  build.onResolve({ filter: /.*/ }, args => {
55
+ if (externalModules.has(args.path)) {
56
+ if (DEBUG) console.log(`EXTERNAL: ${args.path}`)
57
+ return
58
+ }
59
+
54
60
  let fullPathToModule
55
61
  try {
56
62
  fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
57
63
  } catch (err) {
58
- console.warn(`Unable to find "${args.path}". Is the package dead code?`)
64
+ console.warn(`MISSING: Unable to find "${args.path}". Is the package dead code?`)
59
65
  return
60
66
  }
61
67
  const extracted = extractPackageAndModulePath(fullPathToModule)
@@ -74,7 +80,7 @@ module.exports.setup = function (build) {
74
80
  } catch (err) {
75
81
  if (err.code === 'MODULE_NOT_FOUND') {
76
82
  if (!internal) {
77
- console.warn(`Unable to find "${extracted.pkg}/package.json". Is the package dead code?`)
83
+ console.warn(`MISSING: Unable to find "${extracted.pkg}/package.json". Is the package dead code?`)
78
84
  }
79
85
  return
80
86
  } else {
@@ -84,7 +90,7 @@ module.exports.setup = function (build) {
84
90
 
85
91
  const packageJson = require(pathToPackageJson)
86
92
 
87
- if (DEBUG) console.log(`RESOLVE ${packageName}@${packageJson.version}`)
93
+ if (DEBUG) console.log(`RESOLVE: ${packageName}@${packageJson.version}`)
88
94
 
89
95
  // https://esbuild.github.io/plugins/#on-resolve-arguments
90
96
  return {
@@ -114,7 +120,7 @@ module.exports.setup = function (build) {
114
120
  build.onLoad({ filter: /.*/, namespace: NAMESPACE }, args => {
115
121
  const data = args.pluginData
116
122
 
117
- if (DEBUG) console.log(`LOAD ${data.pkg}@${data.version}, pkg "${data.path}"`)
123
+ if (DEBUG) console.log(`LOAD: ${data.pkg}@${data.version}, pkg "${data.path}"`)
118
124
 
119
125
  const path = data.raw !== data.pkg
120
126
  ? `${data.pkg}/${data.path}`
@@ -21,6 +21,8 @@ const skippableSuitesCh = channel('ci:cucumber:test-suite:skippable')
21
21
  const sessionStartCh = channel('ci:cucumber:session:start')
22
22
  const sessionFinishCh = channel('ci:cucumber:session:finish')
23
23
 
24
+ const itrSkippedSuitesCh = channel('ci:cucumber:itr:skipped-suites')
25
+
24
26
  const {
25
27
  getCoveredFilenamesFromCoverage,
26
28
  resetCoverage,
@@ -37,7 +39,6 @@ const patched = new WeakSet()
37
39
 
38
40
  let pickleByFile = {}
39
41
  const pickleResultByFile = {}
40
- let isSuitesSkipped = false
41
42
 
42
43
  function getSuiteStatusFromTestStatuses (testStatuses) {
43
44
  if (testStatuses.some(status => status === 'fail')) {
@@ -216,11 +217,18 @@ addHook({
216
217
  file: 'lib/runtime/test_case_runner.js'
217
218
  }, testCaseHook)
218
219
 
219
- function getPicklesToRun (runtime, suitesToSkip) {
220
- return runtime.pickleIds.filter((pickleId) => {
220
+ function getFilteredPickles (runtime, suitesToSkip) {
221
+ return runtime.pickleIds.reduce((acc, pickleId) => {
221
222
  const test = runtime.eventDataCollector.getPickle(pickleId)
222
- return !suitesToSkip.includes(getTestSuitePath(test.uri, process.cwd()))
223
- }, {})
223
+ const testSuitePath = getTestSuitePath(test.uri, process.cwd())
224
+ const isSkipped = suitesToSkip.includes(testSuitePath)
225
+ if (isSkipped) {
226
+ acc.skippedSuites.add(testSuitePath)
227
+ } else {
228
+ acc.picklesToRun.push(pickleId)
229
+ }
230
+ return acc
231
+ }, { skippedSuites: new Set(), picklesToRun: [] })
224
232
  }
225
233
 
226
234
  function getPickleByFile (runtime) {
@@ -239,7 +247,7 @@ addHook({
239
247
  name: '@cucumber/cucumber',
240
248
  versions: ['>=7.0.0'],
241
249
  file: 'lib/runtime/index.js'
242
- }, (runtimePackage, cucumberVersion) => {
250
+ }, (runtimePackage, frameworkVersion) => {
243
251
  shimmer.wrap(runtimePackage.default.prototype, 'start', start => async function () {
244
252
  const asyncResource = new AsyncResource('bound-anonymous-fn')
245
253
  let onDone
@@ -263,11 +271,16 @@ addHook({
263
271
  })
264
272
 
265
273
  const { err, skippableSuites } = await skippableSuitesPromise
274
+ let skippedSuites = []
275
+ let isSuitesSkipped = false
266
276
 
267
277
  if (!err) {
268
- const newPickleIds = getPicklesToRun(this, skippableSuites)
269
- isSuitesSkipped = newPickleIds.length !== this.pickleIds.length
270
- this.pickleIds = newPickleIds
278
+ const filteredPickles = getFilteredPickles(this, skippableSuites)
279
+ const { picklesToRun } = filteredPickles
280
+ isSuitesSkipped = picklesToRun.length !== this.pickleIds.length
281
+ this.pickleIds = picklesToRun
282
+
283
+ skippedSuites = Array.from(filteredPickles.skippedSuites)
271
284
  }
272
285
 
273
286
  pickleByFile = getPickleByFile(this)
@@ -276,8 +289,13 @@ addHook({
276
289
  const command = process.env.npm_lifecycle_script || `cucumber-js ${processArgv}`
277
290
 
278
291
  asyncResource.runInAsyncScope(() => {
279
- sessionStartCh.publish({ command, frameworkVersion: cucumberVersion })
292
+ sessionStartCh.publish({ command, frameworkVersion })
280
293
  })
294
+
295
+ if (!err && skippedSuites.length) {
296
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
297
+ }
298
+
281
299
  const success = await start.apply(this, arguments)
282
300
 
283
301
  let testCodeCoverageLinesTotal
@@ -296,7 +314,8 @@ addHook({
296
314
  sessionFinishCh.publish({
297
315
  status: success ? 'pass' : 'fail',
298
316
  isSuitesSkipped,
299
- testCodeCoverageLinesTotal
317
+ testCodeCoverageLinesTotal,
318
+ numSkippedSuites: skippedSuites.length
300
319
  })
301
320
  })
302
321
  return success
@@ -39,10 +39,13 @@ const testErrCh = channel('ci:jest:test:err')
39
39
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
40
40
  const jestItrConfigurationCh = channel('ci:jest:itr-configuration')
41
41
 
42
+ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
43
+
42
44
  let skippableSuites = []
43
45
  let isCodeCoverageEnabled = false
44
46
  let isSuitesSkippingEnabled = false
45
47
  let isSuitesSkipped = false
48
+ let numSkippedSuites = 0
46
49
 
47
50
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
48
51
 
@@ -102,7 +105,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
102
105
  await super.handleTestEvent(event, state)
103
106
  }
104
107
 
105
- const setNameToParams = (name, params) => { this.nameToParams[name] = params }
108
+ const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
106
109
 
107
110
  if (event.name === 'setup') {
108
111
  if (this.global.test) {
@@ -191,7 +194,7 @@ addHook({
191
194
  addHook({
192
195
  name: '@jest/test-sequencer',
193
196
  versions: ['>=24.8.0']
194
- }, sequencerPackage => {
197
+ }, (sequencerPackage, frameworkVersion) => {
195
198
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function () {
196
199
  const shardedTests = shard.apply(this, arguments)
197
200
 
@@ -202,13 +205,15 @@ addHook({
202
205
  const [test] = shardedTests
203
206
  const rootDir = test && test.context && test.context.config && test.context.config.rootDir
204
207
 
205
- const filteredTests = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
208
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, shardedTests, rootDir || process.cwd())
206
209
 
207
- isSuitesSkipped = filteredTests.length !== shardedTests.length
210
+ isSuitesSkipped = suitesToRun.length !== shardedTests.length
211
+ numSkippedSuites = skippedSuites.length
208
212
 
209
- skippableSuites = []
213
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
210
214
 
211
- return filteredTests
215
+ skippableSuites = []
216
+ return suitesToRun
212
217
  })
213
218
  return sequencerPackage
214
219
  })
@@ -279,10 +284,13 @@ function cliWrapper (cli, jestVersion) {
279
284
  isSuitesSkipped,
280
285
  isSuitesSkippingEnabled,
281
286
  isCodeCoverageEnabled,
282
- testCodeCoverageLinesTotal
287
+ testCodeCoverageLinesTotal,
288
+ numSkippedSuites
283
289
  })
284
290
  })
285
291
 
292
+ numSkippedSuites = 0
293
+
286
294
  return result
287
295
  })
288
296
 
@@ -468,7 +476,7 @@ addHook({
468
476
  name: '@jest/core',
469
477
  versions: ['>=24.8.0'],
470
478
  file: 'build/SearchSource.js'
471
- }, searchSourcePackage => {
479
+ }, (searchSourcePackage, frameworkVersion) => {
472
480
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
473
481
 
474
482
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
@@ -492,13 +500,16 @@ addHook({
492
500
  const testPaths = await getTestPaths.apply(this, arguments)
493
501
  const { tests } = testPaths
494
502
 
495
- const filteredTests = getJestSuitesToRun(skippableSuites, tests, rootDir)
503
+ const { skippedSuites, suitesToRun } = getJestSuitesToRun(skippableSuites, tests, rootDir)
504
+
505
+ isSuitesSkipped = suitesToRun.length !== tests.length
506
+ numSkippedSuites = skippedSuites.length
496
507
 
497
- isSuitesSkipped = filteredTests.length !== tests.length
508
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
498
509
 
499
510
  skippableSuites = []
500
511
 
501
- return { ...testPaths, tests: filteredTests }
512
+ return { ...testPaths, tests: suitesToRun }
502
513
  })
503
514
 
504
515
  return searchSourcePackage
@@ -30,6 +30,8 @@ const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
30
30
  const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
31
31
  const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
32
32
 
33
+ const itrSkippedSuitesCh = channel('ci:mocha:itr:skipped-suites')
34
+
33
35
  // TODO: remove when root hooks and fixtures are implemented
34
36
  const patched = new WeakSet()
35
37
 
@@ -47,6 +49,7 @@ const originalCoverageMap = createCoverageMap()
47
49
  let suitesToSkip = []
48
50
  let frameworkVersion
49
51
  let isSuitesSkipped = false
52
+ let skippedSuites = []
50
53
 
51
54
  function getSuitesByTestFile (root) {
52
55
  const suitesByTestFile = {}
@@ -97,10 +100,17 @@ function getTestAsyncResource (test) {
97
100
  return testToAr.get(originalFn)
98
101
  }
99
102
 
100
- function getSuitesToRun (originalSuites) {
101
- return originalSuites.filter(suite =>
102
- !suitesToSkip.includes(getTestSuitePath(suite.file, process.cwd()))
103
- )
103
+ function getFilteredSuites (originalSuites) {
104
+ return originalSuites.reduce((acc, suite) => {
105
+ const testPath = getTestSuitePath(suite.file, process.cwd())
106
+ const shouldSkip = suitesToSkip.includes(testPath)
107
+ if (shouldSkip) {
108
+ acc.skippedSuites.add(testPath)
109
+ } else {
110
+ acc.suitesToRun.push(suite)
111
+ }
112
+ return acc
113
+ }, { suitesToRun: [], skippedSuites: new Set() })
104
114
  }
105
115
 
106
116
  function mochaHook (Runner) {
@@ -137,13 +147,21 @@ function mochaHook (Runner) {
137
147
  global.__coverage__ = fromCoverageMapToCoverage(originalCoverageMap)
138
148
  }
139
149
 
140
- testSessionFinishCh.publish({ status, isSuitesSkipped, testCodeCoverageLinesTotal })
150
+ testSessionFinishCh.publish({
151
+ status,
152
+ isSuitesSkipped,
153
+ testCodeCoverageLinesTotal,
154
+ numSkippedSuites: skippedSuites.length
155
+ })
141
156
  }))
142
157
 
143
158
  this.once('start', testRunAsyncResource.bind(function () {
144
159
  const processArgv = process.argv.slice(2).join(' ')
145
160
  const command = `mocha ${processArgv}`
146
161
  testSessionStartCh.publish({ command, frameworkVersion })
162
+ if (skippedSuites.length) {
163
+ itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
164
+ }
147
165
  }))
148
166
 
149
167
  this.on('suite', function (suite) {
@@ -359,9 +377,13 @@ addHook({
359
377
  suitesToSkip = skippableSuites
360
378
  }
361
379
  // We remove the suites that we skip through ITR
362
- const newSuites = getSuitesToRun(runner.suite.suites)
363
- isSuitesSkipped = newSuites.length !== runner.suite.suites.length
364
- runner.suite.suites = newSuites
380
+ const filteredSuites = getFilteredSuites(runner.suite.suites)
381
+ const { suitesToRun } = filteredSuites
382
+
383
+ isSuitesSkipped = suitesToRun.length !== runner.suite.suites.length
384
+ runner.suite.suites = suitesToRun
385
+
386
+ skippedSuites = Array.from(filteredSuites.skippedSuites)
365
387
 
366
388
  global.run()
367
389
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  // TODO: either instrument all or none of the render functions
4
4
 
5
- const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
+ const { channel, addHook } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
7
7
  const { DD_MAJOR } = require('../../../version')
8
8
 
@@ -11,7 +11,7 @@ const finishChannel = channel('apm:next:request:finish')
11
11
  const errorChannel = channel('apm:next:request:error')
12
12
  const pageLoadChannel = channel('apm:next:page:load')
13
13
 
14
- const requestResources = new WeakMap()
14
+ const requests = new WeakSet()
15
15
 
16
16
  function wrapHandleRequest (handleRequest) {
17
17
  return function (req, res, pathname, query) {
@@ -105,38 +105,40 @@ function getPageFromPath (page, dynamicRoutes = []) {
105
105
  }
106
106
 
107
107
  function instrument (req, res, handler) {
108
- if (requestResources.has(req)) return handler()
108
+ req = req.originalRequest || req
109
+ res = res.originalResponse || res
109
110
 
110
- const requestResource = new AsyncResource('bound-anonymous-fn')
111
+ if (requests.has(req)) return handler()
111
112
 
112
- requestResources.set(req, requestResource)
113
+ requests.add(req)
113
114
 
114
- return requestResource.runInAsyncScope(() => {
115
- startChannel.publish({ req, res })
115
+ const ctx = { req, res }
116
116
 
117
+ return startChannel.runStores(ctx, () => {
117
118
  try {
118
- const promise = handler()
119
+ const promise = handler(ctx)
119
120
 
120
121
  // promise should only reject when propagateError is true:
121
122
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L547
122
123
  return promise.then(
123
- result => finish(req, res, result),
124
- err => finish(req, res, null, err)
124
+ result => finish(ctx, result),
125
+ err => finish(ctx, null, err)
125
126
  )
126
127
  } catch (e) {
127
128
  // this will probably never happen as the handler caller is an async function:
128
129
  // https://github.com/vercel/next.js/blob/cee656238a/packages/next/server/api-utils/node.ts#L420
129
- return finish(req, res, null, e)
130
+ return finish(ctx, null, e)
130
131
  }
131
132
  })
132
133
  }
133
134
 
134
- function finish (req, res, result, err) {
135
+ function finish (ctx, result, err) {
135
136
  if (err) {
136
- errorChannel.publish(err)
137
+ ctx.error = err
138
+ errorChannel.publish(ctx)
137
139
  }
138
140
 
139
- finishChannel.publish({ req, res })
141
+ finishChannel.publish(ctx)
140
142
 
141
143
  if (err) {
142
144
  throw err
@@ -25,12 +25,24 @@ class CucumberPlugin extends CiPlugin {
25
25
 
26
26
  this.sourceRoot = process.cwd()
27
27
 
28
- this.addSub('ci:cucumber:session:finish', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
28
+ this.addSub('ci:cucumber:session:finish', ({
29
+ status,
30
+ isSuitesSkipped,
31
+ numSkippedSuites,
32
+ testCodeCoverageLinesTotal
33
+ }) => {
29
34
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
30
35
  addIntelligentTestRunnerSpanTags(
31
36
  this.testSessionSpan,
32
37
  this.testModuleSpan,
33
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
38
+ {
39
+ isSuitesSkipped,
40
+ isSuitesSkippingEnabled,
41
+ isCodeCoverageEnabled,
42
+ testCodeCoverageLinesTotal,
43
+ skippingCount: numSkippedSuites,
44
+ skippingType: 'suite'
45
+ }
34
46
  )
35
47
 
36
48
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -20,7 +20,8 @@ const {
20
20
  finishAllTraceSpans,
21
21
  getCoveredFilenamesFromCoverage,
22
22
  getTestSuitePath,
23
- addIntelligentTestRunnerSpanTags
23
+ addIntelligentTestRunnerSpanTags,
24
+ TEST_SKIPPED_BY_ITR
24
25
  } = require('../../dd-trace/src/plugins/util/test')
25
26
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
26
27
  const log = require('../../dd-trace/src/log')
@@ -120,6 +121,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
120
121
 
121
122
  module.exports = (on, config) => {
122
123
  let isTestsSkipped = false
124
+ const skippedTests = []
123
125
  const tracer = require('../../dd-trace')
124
126
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
125
127
 
@@ -248,19 +250,23 @@ module.exports = (on, config) => {
248
250
  const cypressTests = tests || []
249
251
  const finishedTests = finishedTestsByFile[spec.relative] || []
250
252
 
251
- // Get tests that didn't go through `dd:afterEach` and haven't been skipped by ITR
253
+ // Get tests that didn't go through `dd:afterEach`
252
254
  // and create a skipped test span for each of them
253
255
  cypressTests.filter(({ title }) => {
254
256
  const cypressTestName = title.join(' ')
255
- const isSkippedByItr = testsToSkip.find(test =>
256
- cypressTestName === test.name && spec.relative === test.suite
257
- )
258
257
  const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
259
258
 
260
- return !isSkippedByItr && !isTestFinished
259
+ return !isTestFinished
261
260
  }).forEach(({ title }) => {
262
- const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
261
+ const cypressTestName = title.join(' ')
262
+ const isSkippedByItr = testsToSkip.find(test =>
263
+ cypressTestName === test.name && spec.relative === test.suite
264
+ )
265
+ const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
263
266
  skippedTestSpan.setTag(TEST_STATUS, 'skip')
267
+ if (isSkippedByItr) {
268
+ skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
269
+ }
264
270
  skippedTestSpan.finish()
265
271
  })
266
272
 
@@ -309,7 +315,9 @@ module.exports = (on, config) => {
309
315
  {
310
316
  isSuitesSkipped: isTestsSkipped,
311
317
  isSuitesSkippingEnabled,
312
- isCodeCoverageEnabled
318
+ isCodeCoverageEnabled,
319
+ skippingType: 'test',
320
+ skippingCount: skippedTests.length
313
321
  }
314
322
  )
315
323
 
@@ -353,6 +361,7 @@ module.exports = (on, config) => {
353
361
  if (testsToSkip.find(test => {
354
362
  return testName === test.name && testSuite === test.suite
355
363
  })) {
364
+ skippedTests.push(test)
356
365
  isTestsSkipped = true
357
366
  return { shouldSkip: true }
358
367
  }
@@ -49,7 +49,8 @@ class JestPlugin extends CiPlugin {
49
49
  isSuitesSkipped,
50
50
  isSuitesSkippingEnabled,
51
51
  isCodeCoverageEnabled,
52
- testCodeCoverageLinesTotal
52
+ testCodeCoverageLinesTotal,
53
+ numSkippedSuites
53
54
  }) => {
54
55
  this.testSessionSpan.setTag(TEST_STATUS, status)
55
56
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -57,7 +58,14 @@ class JestPlugin extends CiPlugin {
57
58
  addIntelligentTestRunnerSpanTags(
58
59
  this.testSessionSpan,
59
60
  this.testModuleSpan,
60
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
61
+ {
62
+ isSuitesSkipped,
63
+ isSuitesSkippingEnabled,
64
+ isCodeCoverageEnabled,
65
+ testCodeCoverageLinesTotal,
66
+ skippingType: 'suite',
67
+ skippingCount: numSkippedSuites
68
+ }
61
69
  )
62
70
 
63
71
  this.testModuleSpan.finish()
@@ -48,10 +48,16 @@ function getJestTestName (test) {
48
48
  }
49
49
 
50
50
  function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
51
- return originalTests.filter(({ path: testPath }) => {
52
- const relativePath = getTestSuitePath(testPath, rootDir)
53
- return !skippableSuites.includes(relativePath)
54
- })
51
+ return originalTests.reduce((acc, test) => {
52
+ const relativePath = getTestSuitePath(test.path, rootDir)
53
+ const shouldBeSkipped = skippableSuites.includes(relativePath)
54
+ if (shouldBeSkipped) {
55
+ acc.skippedSuites.push(relativePath)
56
+ } else {
57
+ acc.suitesToRun.push(test)
58
+ }
59
+ return acc
60
+ }, { skippedSuites: [], suitesToRun: [] })
55
61
  }
56
62
 
57
63
  module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun }
@@ -135,7 +135,12 @@ class MochaPlugin extends CiPlugin {
135
135
  this._testNameToParams[name] = params
136
136
  })
137
137
 
138
- this.addSub('ci:mocha:session:finish', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
138
+ this.addSub('ci:mocha:session:finish', ({
139
+ status,
140
+ isSuitesSkipped,
141
+ testCodeCoverageLinesTotal,
142
+ numSkippedSuites
143
+ }) => {
139
144
  if (this.testSessionSpan) {
140
145
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
141
146
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -144,7 +149,14 @@ class MochaPlugin extends CiPlugin {
144
149
  addIntelligentTestRunnerSpanTags(
145
150
  this.testSessionSpan,
146
151
  this.testModuleSpan,
147
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
152
+ {
153
+ isSuitesSkipped,
154
+ isSuitesSkippingEnabled,
155
+ isCodeCoverageEnabled,
156
+ testCodeCoverageLinesTotal,
157
+ skippingCount: numSkippedSuites,
158
+ skippingType: 'suite'
159
+ }
148
160
  )
149
161
 
150
162
  this.testModuleSpan.finish()
@@ -4,6 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
+ const web = require('../../dd-trace/src/plugins/util/web')
7
8
 
8
9
  class NextPlugin extends ServerPlugin {
9
10
  static get id () {
@@ -16,7 +17,7 @@ class NextPlugin extends ServerPlugin {
16
17
  this.addSub('apm:next:page:load', message => this.pageLoad(message))
17
18
  }
18
19
 
19
- start ({ req, res }) {
20
+ bindStart ({ req, res }) {
20
21
  const store = storage.getStore()
21
22
  const childOf = store ? store.span : store
22
23
  const span = this.tracer.startSpan(this.operationName(), {
@@ -33,9 +34,13 @@ class NextPlugin extends ServerPlugin {
33
34
 
34
35
  analyticsSampler.sample(span, this.config.measured, true)
35
36
 
36
- this.enter(span, store)
37
-
38
37
  this._requests.set(span, req)
38
+
39
+ return { ...store, span }
40
+ }
41
+
42
+ error ({ span, error }) {
43
+ this.addError(error, span)
39
44
  }
40
45
 
41
46
  finish ({ req, res }) {
@@ -45,6 +50,7 @@ class NextPlugin extends ServerPlugin {
45
50
 
46
51
  const span = store.span
47
52
  const error = span.context()._tags['error']
53
+ const page = span.context()._tags['next.page']
48
54
 
49
55
  if (!this.config.validateStatus(res.statusCode) && !error) {
50
56
  span.setTag('error', true)
@@ -54,6 +60,8 @@ class NextPlugin extends ServerPlugin {
54
60
  'http.status_code': res.statusCode
55
61
  })
56
62
 
63
+ if (page) web.setRoute(req, page)
64
+
57
65
  this.config.hooks.request(span, req, res)
58
66
 
59
67
  span.finish()
@@ -66,7 +66,7 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
66
66
  const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
67
67
  parent[taintedProperty] = tainted
68
68
  } else {
69
- parent[property] = tainted
69
+ parent[key] = tainted
70
70
  }
71
71
  }
72
72
  } else if (typeof value === 'object' && !visited.has(value)) {
@@ -5,11 +5,20 @@ const telemetryLogs = require('./log')
5
5
  const { Verbosity, getVerbosity } = require('./verbosity')
6
6
  const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
7
7
 
8
+ function isIastMetricsEnabled (metrics) {
9
+ // TODO: let DD_TELEMETRY_METRICS_ENABLED as undefined in config.js to avoid read here the env property
10
+ return process.env.DD_TELEMETRY_METRICS_ENABLED !== undefined ? metrics : true
11
+ }
12
+
8
13
  class Telemetry {
9
14
  configure (config, verbosity) {
10
- // in order to telemetry be enabled, tracer telemetry and metrics collection have to be enabled
11
- this.enabled = config && config.telemetry && config.telemetry.enabled && config.telemetry.metrics
12
- this.verbosity = this.enabled ? getVerbosity(verbosity) : Verbosity.OFF
15
+ const telemetryAndMetricsEnabled = config &&
16
+ config.telemetry &&
17
+ config.telemetry.enabled &&
18
+ isIastMetricsEnabled(config.telemetry.metrics)
19
+
20
+ this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
21
+ this.enabled = this.verbosity !== Verbosity.OFF
13
22
 
14
23
  if (this.enabled) {
15
24
  telemetryMetrics.manager.set('iast', globalNamespace)
@@ -30,13 +39,13 @@ class Telemetry {
30
39
  }
31
40
 
32
41
  onRequestStart (context) {
33
- if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
42
+ if (this.isEnabled()) {
34
43
  initRequestNamespace(context)
35
44
  }
36
45
  }
37
46
 
38
47
  onRequestEnd (context, rootSpan) {
39
- if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
48
+ if (this.isEnabled()) {
40
49
  finalizeRequestNamespace(context, rootSpan)
41
50
  }
42
51
  }
@@ -14,6 +14,8 @@ const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phra
14
14
  // eslint-disable-next-line max-len
15
15
  const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
16
16
 
17
+ const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
18
+
17
19
  class SensitiveHandler {
18
20
  constructor () {
19
21
  this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
@@ -54,6 +56,7 @@ class SensitiveHandler {
54
56
  toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
55
57
  const valueParts = []
56
58
  const redactedSources = []
59
+ const redactedSourcesContext = []
57
60
 
58
61
  const { value, ranges } = evidence
59
62
 
@@ -71,21 +74,41 @@ class SensitiveHandler {
71
74
  sourceIndex = sourcesIndexes[nextTaintedIndex]
72
75
 
73
76
  while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
74
- sourceIndex != null && redactedSources.push(sourceIndex)
77
+ const redactionStart = nextSensitive.start - nextTainted.start
78
+ const redactionEnd = nextSensitive.end - nextTainted.start
79
+ this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
75
80
  nextSensitive = sensitive.shift()
76
81
  }
77
82
 
78
83
  if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
79
- sourceIndex != null && redactedSources.push(sourceIndex)
84
+ const redactionStart = nextSensitive.start - nextTainted.start
85
+ const redactionEnd = nextSensitive.end - nextTainted.start
86
+ this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
80
87
 
81
88
  const entries = remove(nextSensitive, nextTainted)
82
89
  nextSensitive = entries.length > 0 ? entries[0] : null
83
90
  }
84
91
 
85
- this.isSensibleSource(sources[sourceIndex]) && redactedSources.push(sourceIndex)
92
+ if (this.isSensibleSource(sources[sourceIndex])) {
93
+ if (!sources[sourceIndex].redacted) {
94
+ redactedSources.push(sourceIndex)
95
+ sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
96
+ sources[sourceIndex].redacted = true
97
+ }
98
+ }
86
99
 
87
100
  if (redactedSources.indexOf(sourceIndex) > -1) {
88
- this.writeRedactedValuePart(valueParts, sourceIndex)
101
+ const partValue = value.substring(i, i + (nextTainted.end - nextTainted.start))
102
+ this.writeRedactedValuePart(
103
+ valueParts,
104
+ partValue.length,
105
+ sourceIndex,
106
+ partValue,
107
+ sources[sourceIndex],
108
+ redactedSourcesContext[sourceIndex],
109
+ this.isSensibleSource(sources[sourceIndex])
110
+ )
111
+ redactedSourcesContext[sourceIndex] = []
89
112
  } else {
90
113
  const substringEnd = Math.min(nextTainted.end, value.length)
91
114
  this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
@@ -100,7 +123,10 @@ class SensitiveHandler {
100
123
  this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
101
124
  if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
102
125
  sourceIndex = sourcesIndexes[nextTaintedIndex]
103
- sourceIndex != null && redactedSources.push(sourceIndex)
126
+
127
+ const redactionStart = nextSensitive.start - nextTainted.start
128
+ const redactionEnd = nextSensitive.end - nextTainted.start
129
+ this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
104
130
 
105
131
  for (const entry of remove(nextSensitive, nextTainted)) {
106
132
  if (entry.start === i) {
@@ -111,9 +137,10 @@ class SensitiveHandler {
111
137
  }
112
138
  }
113
139
 
114
- this.writeRedactedValuePart(valueParts)
140
+ const _length = nextSensitive.end - nextSensitive.start
141
+ this.writeRedactedValuePart(valueParts, _length)
115
142
 
116
- start = i + (nextSensitive.end - nextSensitive.start)
143
+ start = i + _length
117
144
  i = start - 1
118
145
  nextSensitive = sensitive.shift()
119
146
  }
@@ -126,6 +153,24 @@ class SensitiveHandler {
126
153
  return { redactedValueParts: valueParts, redactedSources }
127
154
  }
128
155
 
156
+ redactSource (sources, redactedSources, redactedSourcesContext, sourceIndex, start, end) {
157
+ if (sourceIndex != null) {
158
+ if (!sources[sourceIndex].redacted) {
159
+ redactedSources.push(sourceIndex)
160
+ sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
161
+ sources[sourceIndex].redacted = true
162
+ }
163
+
164
+ if (!redactedSourcesContext[sourceIndex]) {
165
+ redactedSourcesContext[sourceIndex] = []
166
+ }
167
+ redactedSourcesContext[sourceIndex].push({
168
+ start,
169
+ end
170
+ })
171
+ }
172
+ }
173
+
129
174
  writeValuePart (valueParts, value, source) {
130
175
  if (value.length > 0) {
131
176
  if (source != null) {
@@ -136,9 +181,74 @@ class SensitiveHandler {
136
181
  }
137
182
  }
138
183
 
139
- writeRedactedValuePart (valueParts, source) {
140
- if (source != null) {
141
- valueParts.push({ redacted: true, source })
184
+ writeRedactedValuePart (
185
+ valueParts,
186
+ length,
187
+ sourceIndex,
188
+ partValue,
189
+ source,
190
+ sourceRedactionContext,
191
+ isSensibleSource
192
+ ) {
193
+ if (sourceIndex != null) {
194
+ const placeholder = source.value.includes(partValue)
195
+ ? source.pattern
196
+ : '*'.repeat(length)
197
+
198
+ if (isSensibleSource) {
199
+ valueParts.push({ redacted: true, source: sourceIndex, pattern: placeholder })
200
+ } else {
201
+ let _value = partValue
202
+ const dedupedSourceRedactionContexts = []
203
+
204
+ sourceRedactionContext.forEach(_sourceRedactionContext => {
205
+ const isPresentInDeduped = dedupedSourceRedactionContexts.some(_dedupedSourceRedactionContext =>
206
+ _dedupedSourceRedactionContext.start === _sourceRedactionContext.start &&
207
+ _dedupedSourceRedactionContext.end === _sourceRedactionContext.end
208
+ )
209
+
210
+ if (!isPresentInDeduped) {
211
+ dedupedSourceRedactionContexts.push(_sourceRedactionContext)
212
+ }
213
+ })
214
+
215
+ let offset = 0
216
+ dedupedSourceRedactionContexts.forEach((_sourceRedactionContext) => {
217
+ if (_sourceRedactionContext.start > 0) {
218
+ valueParts.push({
219
+ source: sourceIndex,
220
+ value: _value.substring(0, _sourceRedactionContext.start - offset)
221
+ })
222
+
223
+ _value = _value.substring(_sourceRedactionContext.start - offset)
224
+ offset = _sourceRedactionContext.start
225
+ }
226
+
227
+ const sensitive =
228
+ _value.substring(_sourceRedactionContext.start - offset, _sourceRedactionContext.end - offset)
229
+ const indexOfPartValueInPattern = source.value.indexOf(sensitive)
230
+
231
+ const pattern = indexOfPartValueInPattern > -1
232
+ ? placeholder.substring(indexOfPartValueInPattern, indexOfPartValueInPattern + sensitive.length)
233
+ : placeholder.substring(_sourceRedactionContext.start, _sourceRedactionContext.end)
234
+
235
+ valueParts.push({
236
+ redacted: true,
237
+ source: sourceIndex,
238
+ pattern
239
+ })
240
+
241
+ _value = _value.substring(pattern.length)
242
+ offset += pattern.length
243
+ })
244
+
245
+ if (_value.length) {
246
+ valueParts.push({
247
+ source: sourceIndex,
248
+ value: _value
249
+ })
250
+ }
251
+ }
142
252
  } else {
143
253
  valueParts.push({ redacted: true })
144
254
  }
@@ -28,7 +28,6 @@ class VulnerabilityFormatter {
28
28
  const { redactedValueParts, redactedSources } = scrubbingResult
29
29
  redactedSources.forEach(i => {
30
30
  delete sources[i].value
31
- sources[i].redacted = true
32
31
  })
33
32
  return { valueParts: redactedValueParts }
34
33
  }
@@ -12,7 +12,10 @@ const {
12
12
  TEST_MODULE_ID,
13
13
  TEST_SESSION_ID,
14
14
  TEST_COMMAND,
15
- TEST_MODULE
15
+ TEST_MODULE,
16
+ getTestSuiteCommonTags,
17
+ TEST_STATUS,
18
+ TEST_SKIPPED_BY_ITR
16
19
  } = require('./util/test')
17
20
  const Plugin = require('./plugin')
18
21
  const { COMPONENT } = require('../constants')
@@ -77,6 +80,24 @@ module.exports = class CiPlugin extends Plugin {
77
80
  }
78
81
  })
79
82
  })
83
+
84
+ this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
85
+ const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
86
+ skippedSuites.forEach((testSuite) => {
87
+ const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
88
+
89
+ this.tracer.startSpan(`${this.constructor.id}.test_suite`, {
90
+ childOf: this.testModuleSpan,
91
+ tags: {
92
+ [COMPONENT]: this.constructor.id,
93
+ ...this.testEnvironmentMetadata,
94
+ ...testSuiteMetadata,
95
+ [TEST_STATUS]: 'skip',
96
+ [TEST_SKIPPED_BY_ITR]: 'true'
97
+ }
98
+ }).finish()
99
+ })
100
+ })
80
101
  }
81
102
 
82
103
  configure (config) {
@@ -47,6 +47,7 @@ const TEST_SESSION_ID = 'test_session_id'
47
47
  const TEST_MODULE_ID = 'test_module_id'
48
48
  const TEST_SUITE_ID = 'test_suite_id'
49
49
  const TEST_TOOLCHAIN = 'test.toolchain'
50
+ const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
50
51
 
51
52
  const CI_APP_ORIGIN = 'ciapp-test'
52
53
 
@@ -54,6 +55,8 @@ const JEST_TEST_RUNNER = 'test.jest.test_runner'
54
55
 
55
56
  const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
56
57
  const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
58
+ const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
59
+ const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
57
60
  const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
58
61
 
59
62
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
@@ -80,6 +83,7 @@ module.exports = {
80
83
  JEST_WORKER_TRACE_PAYLOAD_CODE,
81
84
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
82
85
  TEST_SOURCE_START,
86
+ TEST_SKIPPED_BY_ITR,
83
87
  getTestEnvironmentMetadata,
84
88
  getTestParametersString,
85
89
  finishAllTraceSpans,
@@ -99,6 +103,8 @@ module.exports = {
99
103
  TEST_ITR_TESTS_SKIPPED,
100
104
  TEST_MODULE,
101
105
  TEST_ITR_SKIPPING_ENABLED,
106
+ TEST_ITR_SKIPPING_TYPE,
107
+ TEST_ITR_SKIPPING_COUNT,
102
108
  TEST_CODE_COVERAGE_ENABLED,
103
109
  TEST_CODE_COVERAGE_LINES_PCT,
104
110
  addIntelligentTestRunnerSpanTags,
@@ -354,14 +360,25 @@ function getTestSuiteCommonTags (command, testFrameworkVersion, testSuite, testF
354
360
  function addIntelligentTestRunnerSpanTags (
355
361
  testSessionSpan,
356
362
  testModuleSpan,
357
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
363
+ {
364
+ isSuitesSkipped,
365
+ isSuitesSkippingEnabled,
366
+ isCodeCoverageEnabled,
367
+ testCodeCoverageLinesTotal,
368
+ skippingCount,
369
+ skippingType = 'suite'
370
+ }
358
371
  ) {
359
372
  testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
360
373
  testSessionSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
374
+ testSessionSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
375
+ testSessionSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
361
376
  testSessionSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
362
377
 
363
378
  testModuleSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
364
379
  testModuleSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
380
+ testModuleSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
381
+ testModuleSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
365
382
  testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
366
383
 
367
384
  // If suites have been skipped we don't want to report the total coverage, as it will be wrong
@@ -3,6 +3,8 @@
3
3
  const { storage } = require('../../../../datadog-core')
4
4
 
5
5
  const dc = require('../../../../diagnostics_channel')
6
+ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
7
+ const { WEB } = require('../../../../../ext/types')
6
8
 
7
9
  const beforeCh = dc.channel('dd-trace:storage:before')
8
10
  const enterCh = dc.channel('dd-trace:storage:enter')
@@ -41,13 +43,13 @@ function getSpanContextTags (span) {
41
43
  }
42
44
 
43
45
  function isWebServerSpan (tags) {
44
- return tags['span.type'] === 'web'
46
+ return tags[SPAN_TYPE] === WEB
45
47
  }
46
48
 
47
49
  function endpointNameFromTags (tags) {
48
- return tags['resource.name'] || [
49
- tags['http.method'],
50
- tags['http.route']
50
+ return tags[RESOURCE_NAME] || [
51
+ tags[HTTP_METHOD],
52
+ tags[HTTP_ROUTE]
51
53
  ].filter(v => v).join(' ')
52
54
  }
53
55
 
@@ -99,7 +101,7 @@ class NativeWallProfiler {
99
101
 
100
102
  if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
101
103
  this._logger.debug(
102
- `Wall profiler: Enable config_trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
104
+ `Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
103
105
  this._emittedFFMessage = true
104
106
  }
105
107
 
@@ -21,6 +21,9 @@ class Tracer extends NoopProxy {
21
21
  init (options) {
22
22
  if (this._initialized) return this
23
23
 
24
+ process.emitWarning('The dd-trace@v2.x release line reached end of life on ' +
25
+ '2023-08-15 and will no longer receive updates. Please upgrade.')
26
+
24
27
  this._initialized = true
25
28
 
26
29
  try {
@@ -117,8 +117,16 @@ function Hook (modules, options, onrequire) {
117
117
  // abort if _resolveLookupPaths return null
118
118
  return exports
119
119
  }
120
- const res = Module._findPath(name, [basedir, ...paths])
121
- if (res !== filename) {
120
+
121
+ let res
122
+ try {
123
+ res = Module._findPath(name, [basedir, ...paths])
124
+ } catch (e) {
125
+ // case where the file specified in package.json "main" doesn't exist
126
+ // in this case, the file is treated as module-internal
127
+ }
128
+
129
+ if (!res || res !== filename) {
122
130
  // this is a module-internal file
123
131
  // use the module-relative path to the file, prefixed by original module name
124
132
  name = name + path.sep + path.relative(basedir, filename)