dd-trace 6.3.0 → 6.4.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.
Files changed (53) hide show
  1. package/index.electron.js +3 -0
  2. package/package.json +6 -2
  3. package/packages/datadog-instrumentations/src/ai.js +29 -24
  4. package/packages/datadog-instrumentations/src/cucumber.js +4 -3
  5. package/packages/datadog-instrumentations/src/express.js +20 -2
  6. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  7. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  8. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  9. package/packages/datadog-instrumentations/src/jest.js +10 -4
  10. package/packages/datadog-instrumentations/src/router.js +180 -105
  11. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  12. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  13. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  14. package/packages/datadog-plugin-aws-sdk/src/util.js +0 -22
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
  16. package/packages/datadog-plugin-graphql/src/execute.js +90 -14
  17. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  18. package/packages/datadog-plugin-graphql/src/request.js +32 -1
  19. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  20. package/packages/datadog-plugin-graphql/src/validate.js +13 -1
  21. package/packages/datadog-plugin-http2/src/server.js +38 -6
  22. package/packages/datadog-plugin-jest/src/util.js +21 -6
  23. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  24. package/packages/dd-trace/index.electron.js +3 -0
  25. package/packages/dd-trace/index.js +2 -37
  26. package/packages/dd-trace/src/bootstrap.js +39 -0
  27. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +14 -2
  28. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  29. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  30. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +55 -5
  31. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +8 -1
  32. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  33. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  34. package/packages/dd-trace/src/config/supported-configurations.json +17 -0
  35. package/packages/dd-trace/src/constants.js +7 -0
  36. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  37. package/packages/dd-trace/src/feature-registry.js +22 -0
  38. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  39. package/packages/dd-trace/src/llmobs/index.js +10 -2
  40. package/packages/dd-trace/src/llmobs/tagger.js +11 -7
  41. package/packages/dd-trace/src/noop/proxy.js +5 -4
  42. package/packages/dd-trace/src/openfeature/register.js +46 -0
  43. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  44. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  45. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  46. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  47. package/packages/dd-trace/src/plugins/util/test.js +9 -4
  48. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  49. package/packages/dd-trace/src/proxy.js +21 -9
  50. package/packages/dd-trace/src/standalone/index.js +0 -22
  51. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  52. package/vendor/dist/protobufjs/index.js +1 -1
  53. package/vendor/dist/protobufjs/minimal/index.js +1 -1
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('./packages/dd-trace/index.electron')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -12,7 +12,9 @@
12
12
  "bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
13
13
  "dependencies:dedupe": "yarn-deduplicate yarn.lock",
14
14
  "generate:config:types": "node scripts/generate-config-types.js",
15
+ "generate:electron-package": "node scripts/generate-electron-package.js",
15
16
  "verify:config:types": "node scripts/generate-config-types.js --check",
17
+ "verify:electron-package": "node scripts/generate-electron-package.js --check",
16
18
  "generate:supported-integrations": "node scripts/generate-supported-integrations.js",
17
19
  "verify:supported-integrations": "node scripts/generate-supported-integrations.js --check",
18
20
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
@@ -142,6 +144,7 @@
142
144
  "webpack.js",
143
145
  "ext/**/*",
144
146
  "index.d.ts",
147
+ "index.electron.js",
145
148
  "index.js",
146
149
  "init.js",
147
150
  "initialize.mjs",
@@ -151,6 +154,7 @@
151
154
  "LICENSE.BSD3",
152
155
  "loader-hook.mjs",
153
156
  "packages/*/index.js",
157
+ "packages/*/index.electron.js",
154
158
  "packages/*/lib/**/*",
155
159
  "packages/*/src/**/*",
156
160
  "packages/datadog-instrumentations/orchestrion.yml",
@@ -205,7 +209,7 @@
205
209
  "eslint-plugin-mocha": "^11.3.0",
206
210
  "eslint-plugin-n": "^18.2.1",
207
211
  "eslint-plugin-promise": "^7.3.0",
208
- "eslint-plugin-sonarjs": "^4.1.0",
212
+ "eslint-plugin-sonarjs": "^4.2.0",
209
213
  "eslint-plugin-unicorn": "^64.0.0",
210
214
  "express": "^5.1.0",
211
215
  "glob": "^10.4.5",
@@ -136,33 +136,38 @@ function wrapTracer (tracer) {
136
136
 
137
137
  args[args.length - 1] = shimmer.wrapFunction(cb, function (originalCb) {
138
138
  return function (span) {
139
- // the below is necessary in the case that the span is vercel ai's noopSpan.
140
- // while we don't want to patch the noopSpan more than once, we do want to treat each as a
141
- // fresh instance. However, this is really not necessary for non-noop spans, but not sure
142
- // how to differentiate.
143
- const freshSpan = Object.create(span) // TODO: does this cause memory leaks?
144
-
145
- shimmer.wrap(freshSpan, 'end', function (spanEnd) {
146
- return function (...args) {
147
- vercelAiTracingChannel.asyncEnd.publish(ctx)
148
- return spanEnd.apply(this, args)
149
- }
150
- })
151
-
152
- shimmer.wrap(freshSpan, 'setAttributes', function (setAttributes) {
153
- return function (attributes) {
139
+ // A plain delegating wrapper is used instead of Object.create(span) because
140
+ // Object.create (and Proxy) cannot cross private-field brand checks any span
141
+ // method that reads a private field (e.g. BridgeSpanBase#statusCode) would throw
142
+ // "Cannot read private member" on a prototype clone. Every method here calls
143
+ // through to the real span instance so private fields always resolve correctly.
144
+ const freshSpan = {
145
+ spanContext () { return span.spanContext() },
146
+ setAttribute (key, value) { span.setAttribute(key, value); return freshSpan },
147
+ setAttributes (attributes) {
154
148
  vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
155
- return setAttributes.apply(this, arguments)
156
- }
157
- })
158
-
159
- shimmer.wrap(freshSpan, 'recordException', function (recordException) {
160
- return function (exception) {
149
+ span.setAttributes(attributes)
150
+ return freshSpan
151
+ },
152
+ addEvent (name, attributesOrStartTime, startTime) {
153
+ span.addEvent(name, attributesOrStartTime, startTime)
154
+ return freshSpan
155
+ },
156
+ addLink (link, attrs) { span.addLink(link, attrs); return freshSpan },
157
+ addLinks (links) { span.addLinks(links); return freshSpan },
158
+ setStatus (status) { span.setStatus(status); return freshSpan },
159
+ updateName (spanName) { span.updateName(spanName); return freshSpan },
160
+ isRecording () { return span.isRecording() },
161
+ recordException (exception, timeInput) {
161
162
  ctx.error = exception
162
163
  vercelAiTracingChannel.error.publish(ctx)
163
- return recordException.apply(this, arguments)
164
- }
165
- })
164
+ span.recordException(exception, timeInput)
165
+ },
166
+ end (...endArgs) {
167
+ vercelAiTracingChannel.asyncEnd.publish(ctx)
168
+ span.end(...endArgs)
169
+ },
170
+ }
166
171
 
167
172
  return originalCb.call(this, freshSpan)
168
173
  }
@@ -180,7 +180,7 @@ function getSuiteStatusFromTestStatuses (testStatuses) {
180
180
 
181
181
  function getConfiguredEfdRetryCount () {
182
182
  const maxSlowTestRetryCount = getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
183
- return maxSlowTestRetryCount || earlyFlakeDetectionNumRetries
183
+ return maxSlowTestRetryCount ?? earlyFlakeDetectionNumRetries
184
184
  }
185
185
 
186
186
  function publishWorkerEfdRetryCount (pickle, retryCount) {
@@ -1178,7 +1178,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1178
1178
  itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
1179
1179
  }
1180
1180
 
1181
- const success = await start.apply(this, arguments)
1181
+ const result = await start.apply(this, arguments)
1182
+ const success = satisfies(frameworkVersion, '>=13.1.0') ? result.success : result
1182
1183
 
1183
1184
  let untestedCoverage
1184
1185
  if (getCodeCoverageCh.hasSubscribers) {
@@ -1228,7 +1229,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1228
1229
  logTestOptimizationSummary({ attemptToFixExecutions })
1229
1230
  loggedAttemptToFixTests.clear()
1230
1231
  eventDataCollector = null
1231
- return success
1232
+ return result
1232
1233
  }
1233
1234
  }
1234
1235
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { createWrapRouterMethod } = require('./router')
4
+ const { createWrapRouterMethod, createLayerDispatchWrappers } = require('./router')
5
5
  const { addHook, channel, tracingChannel } = require('./helpers/instrument')
6
6
  const { getCompileToRegexp } = require('./path-to-regexp')
7
7
  const {
@@ -164,7 +164,10 @@ addHook({ name: 'express', versions: ['>=4'], file: 'lib/express.js' }, express
164
164
  // It would otherwise produce spans for router and express, and so duplicating them.
165
165
  // We now fall back to router instrumentation
166
166
  addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express => {
167
- const wrapRouterMethod = createWrapRouterMethod('express', getCompileToRegexp())
167
+ // `wrapLegacyHandle` only fires for express <4.6.0 layers, which have no
168
+ // prototype dispatch; 4.6.0+ keeps `handle` pristine (wrapped below).
169
+ const { wrapLegacyHandle } = createLayerDispatchWrappers('express')
170
+ const wrapRouterMethod = createWrapRouterMethod('express', getCompileToRegexp(), wrapLegacyHandle)
168
171
 
169
172
  shimmer.wrap(express.Router, 'use', wrapRouterMethod)
170
173
  shimmer.wrap(express.Router, 'route', wrapRouterMethod)
@@ -172,6 +175,21 @@ addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express =>
172
175
  return express
173
176
  })
174
177
 
178
+ // Express 4 bundles its own Layer; express 5 delegates to the `router` package
179
+ // (wrapped in router.js). Both keep `layer.handle` the user's function and trace
180
+ // via the prototype dispatch. express <4.6.0 has no such dispatch and is traced
181
+ // by the legacy handle wrap instead.
182
+ addHook({ name: 'express', file: 'lib/router/layer.js', versions: ['4'] }, Layer => {
183
+ if (typeof Layer.prototype.handle_request !== 'function') return Layer
184
+
185
+ const { wrapLayerRequest, wrapLayerError } = createLayerDispatchWrappers('express')
186
+
187
+ shimmer.wrap(Layer.prototype, 'handle_request', wrapLayerRequest)
188
+ shimmer.wrap(Layer.prototype, 'handle_error', wrapLayerError)
189
+
190
+ return Layer
191
+ })
192
+
175
193
  const queryParserReadCh = channel('datadog:query:read:finish')
176
194
 
177
195
  function publishQueryParsedAndNext (req, res, next) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { channel, addHook } = require('../helpers/instrument')
4
4
  const shimmer = require('../../../datadog-shimmer')
5
+ const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
5
6
  const types = require('./types')
6
7
 
7
8
  const startChannel = channel('apm:grpc:server:request:start')
@@ -76,6 +77,22 @@ function wrapRegister (register) {
76
77
  }
77
78
  }
78
79
 
80
+ function wrapSetupHandlers (setupHandlers) {
81
+ return function (http2Server) {
82
+ // gRPC builds its transport on a core Node `Http2Server` and drives every
83
+ // call through its own span lifecycle over the raw 'stream' event. Mark the
84
+ // server so the http2 server instrumentation leaves it untraced; otherwise
85
+ // its re-enabled 'stream' path would wrap every gRPC call in a second
86
+ // web.request span and become the top frame. `_setupHandlers` is the single
87
+ // server-creation funnel across all supported versions (the older
88
+ // `createHttp2Server` does not exist), and it runs at bind time, before any
89
+ // request reaches the wrapped `emit`.
90
+ if (http2Server) http2Server[FOREIGN_HTTP2_SERVER] = true
91
+
92
+ return setupHandlers.apply(this, arguments)
93
+ }
94
+ }
95
+
79
96
  function createWrapEmit (call, ctx, onCancel) {
80
97
  return function wrapEmit (emit) {
81
98
  return function (event, arg1) {
@@ -149,6 +166,7 @@ function isEmitter (obj) {
149
166
 
150
167
  addHook({ name: '@grpc/grpc-js', versions: ['>=1.0.3'], file: 'build/src/server.js' }, server => {
151
168
  shimmer.wrap(server.Server.prototype, 'register', wrapRegister)
169
+ shimmer.wrap(server.Server.prototype, '_setupHandlers', wrapSetupHandlers)
152
170
 
153
171
  return server
154
172
  })
@@ -4,7 +4,7 @@ const shimmer = require('../../../datadog-shimmer')
4
4
  const { channel } = require('./instrument')
5
5
 
6
6
  const routerMountPaths = new WeakMap() // to track mount paths for router instances
7
- const layerMatchers = new WeakMap() // to store layer matchers
7
+ const layerMeta = new WeakMap() // per-layer middleware dispatch metadata (resolved name + route matchers)
8
8
  const appMountedRouters = new WeakSet() // to track routers mounted via app.use()
9
9
 
10
10
  const METHODS = [...require('http').METHODS.map(v => v.toLowerCase()), 'all']
@@ -70,7 +70,7 @@ function collectRoutesFromRouter (router, prefix) {
70
70
  // Extract mount path from layer
71
71
  const mountPath = typeof layer.path === 'string'
72
72
  ? layer.path
73
- : getLayerMatchers(layer)?.[0]?.path || ''
73
+ : getLayerMeta(layer)?.matchers?.[0]?.path || ''
74
74
 
75
75
  const nestedPrefix = joinPath(prefix, mountPath)
76
76
  if (nestedPrefix === null) continue
@@ -121,12 +121,12 @@ function getRouterMountPaths (router) {
121
121
  return [...paths]
122
122
  }
123
123
 
124
- function setLayerMatchers (layer, matchers) {
125
- layerMatchers.set(layer, matchers)
124
+ function setLayerMeta (layer, meta) {
125
+ layerMeta.set(layer, meta)
126
126
  }
127
127
 
128
- function getLayerMatchers (layer) {
129
- return layerMatchers.get(layer)
128
+ function getLayerMeta (layer) {
129
+ return layerMeta.get(layer)
130
130
  }
131
131
 
132
132
  function normalizeMethodName (method) {
@@ -224,8 +224,8 @@ module.exports = {
224
224
  setRouterMountPath,
225
225
  getRouterMountPaths,
226
226
  joinPath,
227
- setLayerMatchers,
228
- getLayerMatchers,
227
+ setLayerMeta,
228
+ getLayerMeta,
229
229
  markAppMounted,
230
230
  isAppMounted,
231
231
  normalizeRoutePath,
@@ -1,18 +1,26 @@
1
1
  'use strict'
2
2
 
3
- // Old instrumentation temporarily replaced with compatibility mode only instrumentation.
4
- // See https://github.com/DataDog/dd-trace-js/issues/312
5
-
6
3
  const {
7
4
  channel,
8
5
  addHook,
9
6
  } = require('../helpers/instrument')
10
7
  const shimmer = require('../../../datadog-shimmer')
8
+ const { FOREIGN_HTTP2_SERVER } = require('../../../dd-trace/src/constants')
11
9
 
12
10
  const startServerCh = channel('apm:http2:server:request:start')
13
11
  const errorServerCh = channel('apm:http2:server:request:error')
12
+ const adoptServerCh = channel('apm:http2:server:request:adopt')
14
13
  const emitCh = channel('apm:http2:server:response:emit')
15
14
 
15
+ const HTTP2_HEADER_METHOD = ':method'
16
+ const HTTP2_HEADER_PATH = ':path'
17
+ const HTTP2_HEADER_STATUS = ':status'
18
+
19
+ // Streams whose server span was already created from the 'stream' event. The
20
+ // compatibility layer synthesizes 'request' from that same stream, so the
21
+ // 'request' branch consults this set to avoid creating a second span.
22
+ const tracedStreams = new WeakSet()
23
+
16
24
  addHook({ name: 'http2' }, http2 => {
17
25
  shimmer.wrap(http2, 'createSecureServer', wrapCreateServer)
18
26
  shimmer.wrap(http2, 'createServer', wrapCreateServer)
@@ -36,34 +44,152 @@ function wrapResponseEmit (originalEmit, ctx) {
36
44
  }
37
45
  }
38
46
 
47
+ function wrapStreamEmit (originalEmit, ctx) {
48
+ // Named `emit`/arity-1 mirrors the stream method so the per-stream wrap skips
49
+ // its name/length rewrite. `this` is the Http2Stream; the plugin finishes on
50
+ // 'close', the same finish signal as the compatibility response.
51
+ return function emit (eventName) {
52
+ ctx.eventName = eventName
53
+ return emitCh.runStores(ctx, originalEmit, this, ...arguments)
54
+ }
55
+ }
56
+
39
57
  function wrapEmit (originalEmit) {
40
58
  // Named `emit` mirrors the server method so the one-time wrap skips its name
41
59
  // rewrite; rest params keep the per-event forwarding allocation-free.
42
60
  return function emit (...args) {
43
- if (!startServerCh.hasSubscribers) {
61
+ // A server owned by another instrumentation (e.g. @grpc/grpc-js) drives its
62
+ // own span lifecycle over the raw 'stream' API, so tracing it here would add
63
+ // a spurious web.request span on top of that integration's span and steal
64
+ // the top frame. Skip it entirely; the mark is set at server creation, so
65
+ // this is one property read on servers we do trace.
66
+ if (!startServerCh.hasSubscribers || this[FOREIGN_HTTP2_SERVER]) {
44
67
  return Reflect.apply(originalEmit, this, args)
45
68
  }
46
69
 
47
70
  const eventName = args[0]
48
- if (eventName === 'request') {
71
+ if (eventName === 'stream') {
72
+ // The compatibility layer synthesizes 'request' from an internal 'stream'
73
+ // listener it registers exactly once when a 'request' listener is added,
74
+ // so `listenerCount('stream')` exceeds one only when the application also
75
+ // registered a raw-stream listener. Owning the span here for that case
76
+ // keeps it active while the application's stream listener runs; the
77
+ // synthesized 'request' that fires nested below then reuses it. A
78
+ // request-only server (no raw-stream listener) is left to the 'request'
79
+ // branch so the compatibility response keeps its richer req/res.
80
+ const requestListenerCount = this.listenerCount('request')
81
+ if (requestListenerCount === 0 || this.listenerCount('stream') > 1) {
82
+ const stream = args[1]
83
+ const headers = args[2]
84
+ const ctx = createStreamAdapter(stream, headers)
85
+ // Only a mixed server (a 'request' listener is present) synthesizes a
86
+ // real request off this stream and adopts the span later, so only then
87
+ // does the context need keying on the stream. A raw-stream-only server
88
+ // never adopts; leaving the flag unset keeps the stream->context write
89
+ // off its hot path.
90
+ ctx.adoptable = requestListenerCount !== 0
91
+ tracedStreams.add(stream)
92
+
93
+ return traceServerRequest(ctx, () => {
94
+ shimmer.wrap(stream, 'emit', emit => wrapStreamEmit(emit, ctx))
95
+ return Reflect.apply(originalEmit, this, args)
96
+ })
97
+ }
98
+ } else if (eventName === 'request') {
49
99
  const req = args[1]
50
100
  const res = args[2]
51
101
  res.req = req
52
102
 
53
- const ctx = { req, res }
54
- return startServerCh.runStores(ctx, () => {
55
- shimmer.wrap(res, 'emit', emit => wrapResponseEmit(emit, ctx))
56
-
57
- try {
103
+ // A mixed server (raw-stream + 'request' listeners) already created the
104
+ // span from the 'stream' event above; the stream's single 'close' is the
105
+ // sole finish source, so skip creating a second span. The synthesized
106
+ // request/response are the real objects a user's 'request' handler and
107
+ // the finish `hooks.request` expect, so hand them to the existing
108
+ // stream-backed context rather than leaving it on the throwaway adapter.
109
+ if (tracedStreams.has(req.stream)) {
110
+ adoptServerCh.publish({ req, res })
111
+ } else {
112
+ const ctx = { req, res }
113
+ return traceServerRequest(ctx, () => {
114
+ shimmer.wrap(res, 'emit', emit => wrapResponseEmit(emit, ctx))
58
115
  return Reflect.apply(originalEmit, this, args)
59
- } catch (error) {
60
- ctx.error = error
61
- errorServerCh.publish(ctx)
62
-
63
- throw error
64
- }
65
- })
116
+ })
117
+ }
66
118
  }
119
+
67
120
  return Reflect.apply(originalEmit, this, args)
68
121
  }
69
122
  }
123
+
124
+ // Enter the request context and run `emitEvent` (the original `emit`, wrapped to
125
+ // publish per-event for the matching response/stream), publishing any synchronous
126
+ // throw from a user handler before letting it propagate.
127
+ /**
128
+ * @param {StreamRequestContext | { req: object, res: object }} ctx
129
+ * @param {() => unknown} emitEvent
130
+ */
131
+ function traceServerRequest (ctx, emitEvent) {
132
+ return startServerCh.runStores(ctx, () => {
133
+ try {
134
+ return emitEvent()
135
+ } catch (error) {
136
+ ctx.error = error
137
+ errorServerCh.publish(ctx)
138
+ throw error
139
+ }
140
+ })
141
+ }
142
+
143
+ /**
144
+ * The minimal req/res pair the shared web lifecycle (`web.js`) keys on, built
145
+ * from a core-API `Http2Stream`. The fields below are exactly the surface
146
+ * `web.js` / `url.js` / `ip_extractor.js` read for a stream-backed request; a
147
+ * new read added there must be mirrored here or it resolves to `undefined` on
148
+ * the core path only.
149
+ *
150
+ * @typedef {object} StreamRequestContext
151
+ * @property {object} req
152
+ * @property {import('node:http2').ServerHttp2Stream} req.stream branch key in `web.js`/`url.js`
153
+ * @property {import('node:http2').IncomingHttpHeaders} req.headers raw pseudo-header map
154
+ * @property {string} [req.method]
155
+ * @property {string} [req.url]
156
+ * @property {import('node:net').Socket} [req.socket] peer address source (OTel)
157
+ * @property {object} res
158
+ * @property {object} res.req back-reference used by `wrapResponseEmit`/finish
159
+ * @property {number} res.statusCode read at finish from `stream.sentHeaders`
160
+ * @property {(name: string) => string | number | string[] | undefined} res.getHeader response-header tagging
161
+ */
162
+
163
+ // Present the core-API stream + pseudo-header map as the minimal req/res pair
164
+ // the shared web lifecycle (`web.js`) consumes. The response status and headers
165
+ // are getters because `stream.sentHeaders` is empty until `stream.respond()`
166
+ // runs in the user handler; a snapshot taken here would always be `undefined`.
167
+ /**
168
+ * @param {import('node:http2').ServerHttp2Stream} stream
169
+ * @param {import('node:http2').IncomingHttpHeaders} headers
170
+ * @returns {StreamRequestContext}
171
+ */
172
+ function createStreamAdapter (stream, headers) {
173
+ const req = {
174
+ stream,
175
+ headers,
176
+ method: headers[HTTP2_HEADER_METHOD],
177
+ url: headers[HTTP2_HEADER_PATH],
178
+ socket: stream.session?.socket,
179
+ }
180
+ const res = {
181
+ req,
182
+ get statusCode () {
183
+ // A stream aborted before `stream.respond()` has no `:status`. The
184
+ // compatibility `Http2ServerResponse.statusCode` defaults to 200 in that
185
+ // case, so match it rather than report `undefined` (which `validateStatus`
186
+ // would treat as an error and which drops the `http.status_code` tag).
187
+ return stream.sentHeaders?.[HTTP2_HEADER_STATUS] ?? 200
188
+ },
189
+ getHeader (name) {
190
+ return stream.sentHeaders?.[name]
191
+ },
192
+ }
193
+
194
+ return { req, res }
195
+ }
@@ -1861,10 +1861,16 @@ function resetSuiteSkippingRunState () {
1861
1861
  function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
1862
1862
  if (!isItrEnabled || !isSuitesSkippingEnabled) return originalTests
1863
1863
 
1864
- const suitePathRoot = getRepositoryRootFromTest(originalTests[0], rootDir)
1865
- const jestSuitesToRun = getJestSuitesToRun(skippableSuites, originalTests, suitePathRoot)
1864
+ const repositoryRoot = getRepositoryRootFromTest(originalTests[0], rootDir)
1865
+ const jestSuitesToRun = getJestSuitesToRun(skippableSuites, originalTests, repositoryRoot, rootDir)
1866
1866
  hasFilteredSkippableSuites = true
1867
- log.debug('%d out of %d suites are going to run.', jestSuitesToRun.suitesToRun.length, originalTests.length)
1867
+ log.debug(
1868
+ 'Jest discovered %d suites; skipped %d using %d skippable candidates; %d suites will run.',
1869
+ originalTests.length,
1870
+ jestSuitesToRun.skippedSuites.length,
1871
+ skippableSuites.length,
1872
+ jestSuitesToRun.suitesToRun.length
1873
+ )
1868
1874
  hasUnskippableSuites = jestSuitesToRun.hasUnskippableSuites
1869
1875
  hasForcedToRunSuites = jestSuitesToRun.hasForcedToRunSuites
1870
1876
 
@@ -1877,7 +1883,7 @@ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
1877
1883
  ? getTestContexts(originalTests)
1878
1884
  : undefined
1879
1885
  coverageBackfillFiles = isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
1880
- ? getCoverageBackfillFiles(skippableSuitesCoverage, suitePathRoot, getTestSuitePath)
1886
+ ? getCoverageBackfillFiles(skippableSuitesCoverage, repositoryRoot, getTestSuitePath)
1881
1887
  : undefined
1882
1888
 
1883
1889
  itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })