dd-trace 6.14.0 → 6.16.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 (98) hide show
  1. package/README.md +15 -0
  2. package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
  3. package/ci/test-optimization-validation/package-check.js +4 -1
  4. package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
  5. package/index.d.ts +12 -0
  6. package/next.d.ts +27 -0
  7. package/next.js +3 -0
  8. package/package.json +17 -13
  9. package/packages/datadog-esbuild/index.js +14 -1
  10. package/packages/datadog-esbuild/src/resolver.js +284 -0
  11. package/packages/datadog-esbuild/src/utils.js +163 -119
  12. package/packages/datadog-instrumentations/src/ai.js +39 -97
  13. package/packages/datadog-instrumentations/src/anthropic.js +76 -134
  14. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
  15. package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
  16. package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
  17. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  18. package/packages/datadog-instrumentations/src/bullmq.js +1 -1
  19. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  20. package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
  21. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  22. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
  23. package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
  24. package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
  25. package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
  33. package/packages/datadog-instrumentations/src/jest.js +141 -25
  34. package/packages/datadog-instrumentations/src/langchain.js +1 -1
  35. package/packages/datadog-instrumentations/src/langgraph.js +1 -1
  36. package/packages/datadog-instrumentations/src/mercurius.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  38. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
  39. package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
  40. package/packages/datadog-instrumentations/src/openai.js +54 -76
  41. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  42. package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
  45. package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
  48. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
  49. package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
  50. package/packages/datadog-turbopack/index.js +212 -0
  51. package/packages/datadog-turbopack/src/constants.js +5 -0
  52. package/packages/datadog-turbopack/src/loader.js +500 -0
  53. package/packages/dd-trace/src/aiguard/index.js +2 -1
  54. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
  55. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
  56. package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
  57. package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
  58. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
  59. package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
  60. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
  64. package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
  65. package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
  66. package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
  67. package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
  68. package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
  69. package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
  70. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  71. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  72. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  73. package/packages/dd-trace/src/config/helper.js +10 -2
  74. package/packages/dd-trace/src/config/index.js +20 -5
  75. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  76. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  77. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
  78. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  79. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  80. package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
  81. package/packages/dd-trace/src/evp_proxy/path.js +12 -3
  82. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  83. package/packages/dd-trace/src/exporters/common/docker.js +4 -1
  84. package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
  85. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
  88. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
  90. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  91. package/packages/dd-trace/src/plugin_manager.js +9 -3
  92. package/packages/dd-trace/src/plugins/index.js +1 -0
  93. package/packages/dd-trace/src/plugins/util/test.js +4 -1
  94. package/packages/dd-trace/src/priority_sampler.js +4 -0
  95. package/packages/dd-trace/src/sampling_rule.js +3 -1
  96. package/packages/dd-trace/src/span_processor.js +18 -3
  97. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
  98. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
package/README.md CHANGED
@@ -93,6 +93,21 @@ Regardless of where you open the issue, someone at Datadog will try to help.
93
93
 
94
94
  If you would like to trace your bundled application then please read this page on [bundling and dd-trace](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/#bundling). It includes information on how to use our ESBuild plugin and includes caveats for other bundlers.
95
95
 
96
+ Next.js 15.5 or newer applications that use Turbopack can wrap their existing configuration:
97
+
98
+ ```javascript
99
+ const { withDatadogTurbopack } = require('dd-trace/next')
100
+
101
+ const nextConfig = { reactStrictMode: true }
102
+
103
+ module.exports = withDatadogTurbopack(nextConfig)
104
+ ```
105
+
106
+ Preload `dd-trace/init` before the application loads. The wrapper instruments CommonJS dependencies and supported
107
+ source-rewrite targets in Node.js bundles.
108
+
109
+ The wrapper does not instrument client or edge bundles. It also excludes generic ESM export replacement, linked
110
+ workspaces, Prisma clients outside `node_modules`, and Yarn Plug'n'Play packages.
96
111
 
97
112
  ## Security Vulnerabilities
98
113
 
@@ -282,7 +282,9 @@ function readProfileDefinitions (filename) {
282
282
 
283
283
  function getJavascriptProfileDefinitions (source) {
284
284
  const syntax = maskJavascriptCommentsAndStrings(source)
285
- const exports = [...syntax.matchAll(/^\s*(?:module\s*\.\s*exports\s*=|export\s+default)\s*\{/gm)]
285
+ const exports = [...syntax.matchAll(
286
+ /(?:^|\S[\t\v\f\p{Zs}\uFEFF]*[\n\r\u2028\u2029])\s*(?:module\s*\.\s*exports\s*=|export\s+default)\s*\{/gu
287
+ )]
286
288
  if (exports.length === 0) return new Map()
287
289
  if (exports.length !== 1) throw new Error('configuration must export one literal profile object')
288
290
 
@@ -492,7 +494,7 @@ function getYamlStringProfileDefinition (source) {
492
494
  const value = source.trimStart()
493
495
  const quote = value[0]
494
496
  if (quote !== '"' && quote !== "'") {
495
- const comment = /\s+#/.exec(value)
497
+ const comment = /[ \t]#/.exec(value)
496
498
  const definition = value.slice(0, comment?.index ?? value.length).trimEnd()
497
499
  return definition || undefined
498
500
  }
@@ -88,7 +88,10 @@ function getFailureDetail (result) {
88
88
  }
89
89
 
90
90
  function sanitizeLine (value) {
91
- return sanitizeString(String(value)).replaceAll(/\p{Cc}+/gu, ' ').trim().slice(0, 1000).replace(/\.+$/, '')
91
+ const line = sanitizeString(String(value)).replaceAll(/\p{Cc}+/gu, ' ').trim().slice(0, 1000)
92
+ let end = line.length
93
+ while (end > 0 && line[end - 1] === '.') end--
94
+ return line.slice(0, end)
92
95
  }
93
96
 
94
97
  module.exports = { checkInstalledPackage, getInstalledPackageFailure }
@@ -442,7 +442,7 @@ function hasUnavailableRemoteCiCommand (ci) {
442
442
  })) return true
443
443
 
444
444
  return evidence.some(item => {
445
- const reference = /^\s*uses:\s*["']?([^"'\s]+)["']?\s*$/im.exec(item)?.[1]
445
+ const reference = /^[ \t]*uses:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/im.exec(item)?.[1]
446
446
  return reference && !reference.startsWith('./')
447
447
  })
448
448
  }
package/index.d.ts CHANGED
@@ -466,6 +466,13 @@ declare namespace tracer {
466
466
  * Maximum number of traces matching this rule to sample per second.
467
467
  */
468
468
  maxPerSecond?: number
469
+
470
+ /**
471
+ * When `true`, a trace chunk rejected by this rule is fully dropped:
472
+ * it is excluded from client-side stats and never sent to the Agent.
473
+ * @default false
474
+ */
475
+ discard?: boolean
469
476
  }
470
477
 
471
478
  /**
@@ -650,6 +657,7 @@ declare namespace tracer {
650
657
  * Sampling rules to apply to priority sampling. Each rule matches against a trace's
651
658
  * `service`, `name`, `resource`, and `tags`, and applies the rule's `sampleRate`. Use a
652
659
  * `sampleRate` of `0` to drop matching traces (for example to filter out unwanted resources).
660
+ * Specify `"discard": true` to fully drop it from stats as well.
653
661
  * If not specified, will defer to global sampling rate for all spans.
654
662
  * @default []
655
663
  * @env DD_TRACE_SAMPLING_RULES
@@ -3279,6 +3287,10 @@ declare namespace tracer {
3279
3287
  }
3280
3288
 
3281
3289
  export namespace opentelemetry {
3290
+ export interface MeterProvider {
3291
+ shutdown(callback?: (error: Error | null) => void): void;
3292
+ }
3293
+
3282
3294
  /**
3283
3295
  * A registry for creating named {@link Tracer}s.
3284
3296
  */
package/next.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { NextConfig } from 'next'
2
+
3
+ type NextConfigValue = NextConfig | undefined
4
+
5
+ export function withDatadogTurbopack<TArguments extends unknown[]> (
6
+ nextConfig: (...args: TArguments) => Promise<NextConfigValue>
7
+ ): (...args: TArguments) => Promise<NextConfig>
8
+
9
+ export function withDatadogTurbopack<TArguments extends unknown[]> (
10
+ nextConfig: (...args: TArguments) => NextConfigValue
11
+ ): (...args: TArguments) => NextConfig
12
+
13
+ export function withDatadogTurbopack<TArguments extends unknown[]> (
14
+ nextConfig: (...args: TArguments) => NextConfigValue | Promise<NextConfigValue>
15
+ ): (...args: TArguments) => NextConfig | Promise<NextConfig>
16
+
17
+ export function withDatadogTurbopack (
18
+ nextConfig: Promise<NextConfigValue>
19
+ ): (phase?: string) => Promise<NextConfig>
20
+
21
+ export function withDatadogTurbopack (
22
+ nextConfig?: NextConfigValue
23
+ ): (phase?: string) => NextConfig
24
+
25
+ export function withDatadogTurbopack (
26
+ nextConfig: NextConfigValue | Promise<NextConfigValue>
27
+ ): (phase?: string) => NextConfig | Promise<NextConfig>
package/next.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = require('./packages/datadog-turbopack')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.14.0",
3
+ "version": "6.16.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -22,12 +22,11 @@
22
22
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
23
23
  "type:doc:build": "cd docs && yarn && yarn build",
24
24
  "type:doc:test": "cd docs && yarn && yarn test",
25
- "lint": "node scripts/check_licenses.js && node scripts/check-agents-md-size.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 && npm run verify:carrier-fields && npm run lint:codeowners:ci && npm run verify-exercised-tests",
25
+ "lint": "bash ./scripts/lint.sh",
26
26
  "lint:fix": "node scripts/check_licenses.js && node scripts/check-agents-md-size.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 --fix",
27
27
  "verify:carrier-fields": "node scripts/verify-carrier-fields.mjs",
28
28
  "lint:inspect": "npx @eslint/config-inspector@latest",
29
29
  "lint:codeowners": "codeowners-audit",
30
- "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**' --glob='packages/dd-trace/src/guardrails/**'",
31
30
  "lint:editorconfig": "docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec",
32
31
  "release:proposal": "node scripts/release/proposal",
33
32
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
@@ -47,6 +46,8 @@
47
46
  "test:trace:guardrails:ci": "node scripts/c8-ci.js test:trace:guardrails",
48
47
  "test:esbuild": "mocha \"packages/datadog-esbuild/test/**/*.spec.js\"",
49
48
  "test:esbuild:ci": "node scripts/c8-ci.js test:esbuild",
49
+ "test:turbopack": "mocha \"packages/datadog-turbopack/test/**/*.spec.js\"",
50
+ "test:turbopack:ci": "node scripts/c8-ci.js test:turbopack",
50
51
  "test:webpack": "mocha \"packages/datadog-webpack/test/**/*.spec.js\"",
51
52
  "test:webpack:ci": "node scripts/c8-ci.js test:webpack",
52
53
  "test:instrumentations": "mocha --fail-zero \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\"",
@@ -91,6 +92,7 @@
91
92
  "test:integration:electron": "mocha \"integration-tests/electron/*.spec.js\"",
92
93
  "test:integration:esbuild": "mocha --timeout 60000 \"integration-tests/esbuild/*.spec.js\"",
93
94
  "test:integration:esbuild:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/esbuild/*.spec.js\"",
95
+ "test:integration:turbopack": "mocha --timeout 60000 \"integration-tests/turbopack/*.spec.js\"",
94
96
  "test:integration:webpack": "mocha --timeout 60000 \"integration-tests/webpack/*.spec.js\"",
95
97
  "test:integration:openfeature": "mocha --timeout 60000 \"integration-tests/openfeature/*.spec.js\"",
96
98
  "test:integration:openfeature:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/openfeature/*.spec.js\"",
@@ -150,6 +152,8 @@
150
152
  "ci/**/*",
151
153
  "cypress/**/*",
152
154
  "esbuild.js",
155
+ "next.js",
156
+ "next.d.ts",
153
157
  "webpack.js",
154
158
  "ext/**/*",
155
159
  "index.d.ts",
@@ -179,12 +183,12 @@
179
183
  ],
180
184
  "dependencies": {
181
185
  "dc-polyfill": "^0.1.11",
182
- "import-in-the-middle": "^3.3.2",
186
+ "import-in-the-middle": "3.5.1",
183
187
  "opentracing": ">=0.14.7"
184
188
  },
185
189
  "optionalDependencies": {
186
- "@datadog/libdatadog": "^0.20.0",
187
- "@datadog/libdatadog-extras": "^0.20.0",
190
+ "@datadog/libdatadog": "^0.21.0",
191
+ "@datadog/libdatadog-extras": "^0.21.0",
188
192
  "@datadog/native-appsec": "11.0.2",
189
193
  "@datadog/native-iast-taint-tracking": "4.2.1",
190
194
  "@datadog/native-metrics": "4.0.0",
@@ -198,9 +202,9 @@
198
202
  "@actions/core": "^3.0.1",
199
203
  "@actions/github": "^9.1.1",
200
204
  "@babel/helpers": "^8.0.0",
201
- "@eslint/eslintrc": "^3.3.6",
205
+ "@eslint/eslintrc": "^3.3.7",
202
206
  "@eslint/js": "^10.0.1",
203
- "@eslint/plugin-kit": "^0.7.2",
207
+ "@eslint/plugin-kit": "^0.7.3",
204
208
  "@datadog/openfeature-node-server": "2.2.0",
205
209
  "@msgpack/msgpack": "^3.1.3",
206
210
  "@openfeature/core": "^1.12.0",
@@ -220,16 +224,16 @@
220
224
  "eslint": "^10.9.1",
221
225
  "eslint-plugin-cypress": "^7.0.1",
222
226
  "eslint-plugin-import-x": "^4.16.2",
223
- "eslint-plugin-jsdoc": "^64.2.1",
227
+ "eslint-plugin-jsdoc": "^64.3.5",
224
228
  "eslint-plugin-mocha": "^12.0.2",
225
229
  "eslint-plugin-n": "^18.3.0",
226
230
  "eslint-plugin-promise": "^7.3.0",
227
- "eslint-plugin-regexp": "^3.1.0",
231
+ "eslint-plugin-regexp": "^3.2.0",
228
232
  "eslint-plugin-sonarjs": "^4.2.0",
229
233
  "eslint-plugin-unicorn": "^74.0.0",
230
234
  "express": "^5.1.0",
231
235
  "glob": "^10.4.5",
232
- "globals": "^17.11.0",
236
+ "globals": "^17.12.0",
233
237
  "graphql": "*",
234
238
  "husky": "^9.1.7",
235
239
  "istanbul-lib-coverage": "^3.2.2",
@@ -241,12 +245,12 @@
241
245
  "mocha": "^11.8.0",
242
246
  "mocha-junit-reporter": "^2.2.1",
243
247
  "mocha-multi-reporters": "^1.5.1",
244
- "multer": "^2.2.0",
248
+ "multer": "^2.3.0",
245
249
  "nock": "^14.0.17",
246
250
  "node-preload": "^0.2.1",
247
251
  "nyc": "^18.0.0",
248
252
  "octokit": "^5.0.3",
249
- "p-limit": "^7.3.1",
253
+ "p-limit": "^7.3.2",
250
254
  "proxyquire": "^2.1.3",
251
255
  "retry": "^0.13.1",
252
256
  "semifies": "^1.0.0",
@@ -8,8 +8,9 @@ const { pathToFileURL, fileURLToPath } = require('node:url')
8
8
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
9
9
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
10
10
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
11
- const { processModule, isESMFile } = require('./src/utils')
12
11
  const log = require('./src/log')
12
+ const { createEsmResolver } = require('./src/resolver')
13
+ const { isESMFile, processModule } = require('./src/utils')
13
14
 
14
15
  const ESM_INTERCEPTED_SUFFIX = '._dd_esbuild_intercepted'
15
16
  const INTERNAL_ESM_INTERCEPTED_PREFIX = '/_dd_esm_internal_/'
@@ -166,6 +167,13 @@ ${build.initialOptions.banner.js}`
166
167
 
167
168
  // first time is intercepted, proxy should be created, next time the original should be loaded
168
169
  const interceptedESMModules = new Set()
170
+ let resolver
171
+
172
+ build.onEnd(async () => {
173
+ const activeResolver = resolver
174
+ resolver = undefined
175
+ await activeResolver?.close()
176
+ })
169
177
 
170
178
  build.onResolve({ filter: /.*/ }, args => {
171
179
  if (externalModules.has(args.path)) {
@@ -316,10 +324,15 @@ ${build.initialOptions.banner.js}`
316
324
 
317
325
  interceptedESMModules.add(args.path)
318
326
 
327
+ resolver ??= createEsmResolver()
319
328
  const setters = await processModule({
320
329
  path: args.path,
321
330
  internal: data.internal,
322
331
  context: { format: 'module' },
332
+ excludeDefault: false,
333
+ moduleSources: new Map(),
334
+ resolver,
335
+ transform: build.esbuild.transformSync,
323
336
  })
324
337
 
325
338
  const iitmPath = require.resolve('import-in-the-middle/lib/register.js')
@@ -0,0 +1,284 @@
1
+ 'use strict'
2
+
3
+ const childProcess = require('node:child_process')
4
+ const readline = require('node:readline')
5
+
6
+ const { getEnvironmentVariables } = require('../../dd-trace/src/config/helper')
7
+
8
+ const MAX_PENDING_REQUESTS = 1024
9
+ const MAX_STDERR_LENGTH = 16 * 1024
10
+ const LOAD_OPERATION = 0
11
+ const RESOLVE_OPERATION = 1
12
+ const RESOLVER_SOURCE = String.raw`
13
+ import { createReadStream, createWriteStream } from 'node:fs'
14
+ import readline from 'node:readline'
15
+
16
+ const requests = createReadStream(null, { fd: 3, autoClose: false })
17
+ const responses = createWriteStream(null, { fd: 4, autoClose: false })
18
+ const lines = readline.createInterface({ input: requests })
19
+
20
+ for await (const line of lines) {
21
+ let request
22
+ try {
23
+ request = JSON.parse(line)
24
+ const url = await import.meta.resolve(request.specifier, request.parentURL)
25
+ responses.write(JSON.stringify({ id: request.id, url }) + '\n')
26
+ } catch (error) {
27
+ responses.write(JSON.stringify({
28
+ error: {
29
+ code: error?.code,
30
+ message: String(error?.message ?? error),
31
+ },
32
+ id: request?.id,
33
+ }) + '\n')
34
+ }
35
+ }
36
+ responses.end()
37
+ `
38
+
39
+ /** @typedef {{ specifier: string, parentURL: string }} StarReexport */
40
+
41
+ /**
42
+ * @typedef {{ exportNames: Iterable<string>, starReexports?: StarReexport[] }} ModuleExports
43
+ */
44
+
45
+ /**
46
+ * @typedef {Set<string>|ModuleExports} GetExportsValue
47
+ */
48
+
49
+ /**
50
+ * @typedef {[typeof LOAD_OPERATION, URL, object] | [typeof RESOLVE_OPERATION, string, object]} GetExportsOperation
51
+ */
52
+
53
+ /**
54
+ * @typedef {{ done: false, value: GetExportsOperation } | { done: true, value: GetExportsValue }} GetExportsResult
55
+ */
56
+
57
+ /**
58
+ * @typedef {{
59
+ * next: (value?: unknown) => GetExportsResult,
60
+ * throw: (error?: unknown) => GetExportsResult,
61
+ * }} GetExportsGenerator
62
+ */
63
+
64
+ class EsmResolver {
65
+ #child
66
+ #closed = false
67
+ #closedPromise
68
+ #failure
69
+ #nextId = 0
70
+ #pending = new Map()
71
+ #readline
72
+ #rejectClosed
73
+ #request
74
+ #resolveClosed
75
+ #stderr = ''
76
+
77
+ constructor () {
78
+ this.#closedPromise = new Promise((resolve, reject) => {
79
+ this.#resolveClosed = resolve
80
+ this.#rejectClosed = reject
81
+ })
82
+ // Mark lifecycle failures handled immediately. close() still returns the original promise.
83
+ this.#closedPromise.catch(() => {})
84
+ }
85
+
86
+ /**
87
+ * @param {string} specifier
88
+ * @param {URL|string} parentURL
89
+ * @returns {Promise<string>}
90
+ */
91
+ resolve (specifier, parentURL) {
92
+ if (this.#closed) return Promise.reject(this.#failure ?? new Error('The ESM resolver is closed'))
93
+ if (this.#pending.size >= MAX_PENDING_REQUESTS) {
94
+ return Promise.reject(new Error(`The ESM resolver has more than ${MAX_PENDING_REQUESTS} pending requests`))
95
+ }
96
+
97
+ try {
98
+ this.#start()
99
+ } catch (error) {
100
+ this.#failure = error
101
+ this.#closed = true
102
+ this.#resolveClosed()
103
+ return Promise.reject(error)
104
+ }
105
+ const id = this.#nextId++
106
+ return new Promise((resolve, reject) => {
107
+ this.#pending.set(id, { reject, resolve })
108
+ this.#request.write(`${JSON.stringify({ id, parentURL: String(parentURL), specifier })}\n`, error => {
109
+ if (error) this.#rejectRequest(id, error)
110
+ })
111
+ })
112
+ }
113
+
114
+ /** @returns {Promise<void>} */
115
+ close () {
116
+ if (!this.#closed) {
117
+ this.#closed = true
118
+ if (this.#child) this.#request.end()
119
+ else this.#resolveClosed()
120
+ }
121
+ return this.#closedPromise
122
+ }
123
+
124
+ #start () {
125
+ if (this.#child) return
126
+ const env = getEnvironmentVariables()
127
+ env.DD_CIVISIBILITY_ENABLED = 'false'
128
+ env.DD_INSTRUMENTATION_TELEMETRY_ENABLED = 'false'
129
+ env.DD_TRACE_ENABLED = 'false'
130
+ this.#child = childProcess.spawn(process.execPath, [
131
+ '--no-warnings',
132
+ '--experimental-import-meta-resolve',
133
+ '--input-type=module',
134
+ '--eval',
135
+ RESOLVER_SOURCE,
136
+ ], {
137
+ env,
138
+ stdio: ['ignore', 'ignore', 'pipe', 'pipe', 'pipe'],
139
+ })
140
+ this.#request = this.#child.stdio[3]
141
+ this.#readline = readline.createInterface({ input: this.#child.stdio[4] })
142
+ this.#readline.on('line', this.#handleLine)
143
+ this.#request.once('error', this.#handleError)
144
+ this.#child.stdio[4].once('error', this.#handleError)
145
+ this.#child.stderr.on('data', this.#handleStderr)
146
+ this.#child.once('error', this.#handleError)
147
+ this.#child.once('close', this.#handleClose)
148
+ }
149
+
150
+ /** @param {string} line */
151
+ #handleLine = (line) => {
152
+ let response
153
+ try {
154
+ response = JSON.parse(line)
155
+ } catch (error) {
156
+ this.#fail(new Error(`The ESM resolver returned malformed JSON: ${error.message}`))
157
+ return
158
+ }
159
+
160
+ const id = response?.id
161
+ if (!Number.isSafeInteger(id) || !this.#pending.has(id)) {
162
+ this.#fail(new Error('The ESM resolver returned an unknown request identifier'))
163
+ return
164
+ }
165
+ if (typeof response.url === 'string' && response.error === undefined) {
166
+ const request = this.#pending.get(id)
167
+ this.#pending.delete(id)
168
+ request.resolve(response.url)
169
+ return
170
+ }
171
+ if (typeof response.error?.message !== 'string' || response.url !== undefined) {
172
+ this.#rejectRequest(id, new Error('The ESM resolver returned a malformed response'))
173
+ return
174
+ }
175
+
176
+ const error = /** @type {Error & { code?: string }} */ (new Error(response.error.message))
177
+ if (typeof response.error.code === 'string') error.code = response.error.code
178
+ this.#rejectRequest(id, error)
179
+ }
180
+
181
+ /** @param {Buffer|string} chunk */
182
+ #handleStderr = (chunk) => {
183
+ if (this.#stderr.length >= MAX_STDERR_LENGTH) return
184
+ this.#stderr += String(chunk).slice(0, MAX_STDERR_LENGTH - this.#stderr.length)
185
+ }
186
+
187
+ /** @param {Error} error */
188
+ #handleError = (error) => {
189
+ this.#fail(error)
190
+ }
191
+
192
+ /**
193
+ * @param {number|null} code
194
+ * @param {string|null} signal
195
+ */
196
+ #handleClose = (code, signal) => {
197
+ this.#readline?.close()
198
+ this.#closed = true
199
+ let error = this.#failure
200
+ if (!error && (code !== 0 || this.#pending.size > 0)) {
201
+ const reason = signal ? `signal ${signal}` : `status ${code}`
202
+ const stderr = this.#stderr.trim()
203
+ error = new Error(`The ESM resolver exited with ${reason}${stderr ? `: ${stderr}` : ''}`)
204
+ this.#failure = error
205
+ }
206
+ if (error) {
207
+ this.#rejectAll(error)
208
+ this.#rejectClosed(error)
209
+ } else {
210
+ this.#resolveClosed()
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @param {number} id
216
+ * @param {Error} error
217
+ */
218
+ #rejectRequest (id, error) {
219
+ const request = this.#pending.get(id)
220
+ if (!request) return
221
+ this.#pending.delete(id)
222
+ request.reject(error)
223
+ }
224
+
225
+ /** @param {Error} error */
226
+ #fail (error) {
227
+ if (this.#failure) return
228
+ this.#failure = error
229
+ this.#closed = true
230
+ this.#rejectAll(error)
231
+ this.#request?.destroy()
232
+ this.#child?.kill()
233
+ }
234
+
235
+ /** @param {Error} error */
236
+ #rejectAll (error) {
237
+ for (const request of this.#pending.values()) request.reject(error)
238
+ this.#pending.clear()
239
+ }
240
+ }
241
+
242
+ /** @returns {EsmResolver} */
243
+ function createEsmResolver () {
244
+ return new EsmResolver()
245
+ }
246
+
247
+ /**
248
+ * Drives the generator returned by import-in-the-middle >=3.1.0 export discovery.
249
+ *
250
+ * @param {GetExportsGenerator} exportsGenerator
251
+ * @param {(url: URL, context: object) => { source: string, format: string }} getSource
252
+ * @param {(specifier: string, context: object) => Promise<{ format: string, url: URL }>} resolve
253
+ * @returns {Promise<ModuleExports>}
254
+ */
255
+ async function driveGetExportsGenerator (exportsGenerator, getSource, resolve) {
256
+ let next = exportsGenerator.next()
257
+ while (next.done === false) {
258
+ let result
259
+ let failure
260
+ let threw = false
261
+
262
+ try {
263
+ const operation = next.value
264
+ const operationType = operation[0]
265
+
266
+ if (operationType === LOAD_OPERATION) {
267
+ result = getSource(operation[1], operation[2])
268
+ } else if (operationType === RESOLVE_OPERATION) {
269
+ // eslint-disable-next-line no-await-in-loop
270
+ result = await resolve(operation[1], operation[2])
271
+ } else {
272
+ throw new Error(`Unsupported import-in-the-middle getExports operation: ${operationType}`)
273
+ }
274
+ } catch (error) {
275
+ threw = true
276
+ failure = error
277
+ }
278
+
279
+ next = threw ? exportsGenerator.throw(failure) : exportsGenerator.next(result)
280
+ }
281
+ return next.value instanceof Set ? { exportNames: next.value } : next.value
282
+ }
283
+
284
+ module.exports = { createEsmResolver, driveGetExportsGenerator }