Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@
/index.electron.js @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/electron/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/esbuild/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/turbopack/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/webpack/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/pino/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/integration-tests/pino.spec.js @DataDog/dd-trace-js @DataDog/apm-idm-js

/packages/datadog-esbuild/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/packages/datadog-turbopack/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/packages/datadog-webpack/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/packages/datadog-plugin-*/ @DataDog/dd-trace-js @DataDog/apm-idm-js
/packages/datadog-instrumentations/ @DataDog/dd-trace-js @DataDog/apm-idm-js
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/instrumentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ jobs:
with:
flags: platform-esbuild

turbopack:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:turbopack:ci
- uses: ./.github/actions/node/latest
- run: npm run test:turbopack:ci
- run: npm run test:integration:turbopack
- uses: ./.github/actions/coverage
with:
flags: platform-turbopack

webpack:
runs-on: ubuntu-latest
permissions:
Expand Down
3 changes: 3 additions & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@
"dd-trace","https://github.com/DataDog/dd-trace-js","['(Apache-2.0 OR BSD-3-Clause)']","['Datadog Inc. <info@datadoghq.com>']"
"debug","https://github.com/debug-js/debug","['MIT']","['Josh Junon']"
"detect-newline","https://github.com/sindresorhus/detect-newline","['MIT']","['Sindre Sorhus']"
"enhanced-resolve","https://github.com/webpack/enhanced-resolve","['MIT']","['JS Foundation and other contributors']"
"es-module-lexer","https://github.com/guybedford/es-module-lexer","['MIT']","['Guy Bedford']"
"escape-string-regexp","https://github.com/sindresorhus/escape-string-regexp","['MIT']","['Sindre Sorhus']"
"esquery","https://github.com/estools/esquery","['BSD-3-Clause']","['Joel Feenstra']"
"estraverse","https://github.com/estools/estraverse","['BSD-2-Clause']","['estools']"
"fast-fifo","https://github.com/mafintosh/fast-fifo","['MIT']","['Mathias Buus']"
"graceful-fs","https://github.com/isaacs/node-graceful-fs","['ISC']","['Isaac Z. Schlueter, Ben Noordhuis, and Contributors']"
"https-proxy-agent","https://github.com/TooTallNate/proxy-agents","['MIT']","['Nathan Rajlich']"
"import-in-the-middle","https://github.com/nodejs/import-in-the-middle","['Apache-2.0']","['Bryan English']"
"istanbul-lib-coverage","https://github.com/istanbuljs/istanbuljs","['BSD-3-Clause']","['Krishnan Anantheswaran']"
Expand Down Expand Up @@ -87,6 +89,7 @@
"shell-quote","https://github.com/ljharb/shell-quote","['MIT']","['James Halliday']"
"source-map","https://github.com/mozilla/source-map","['BSD-3-Clause']","['Nick Fitzgerald']"
"spark-md5","https://github.com/satazor/js-spark-md5","['(WTFPL OR MIT)']","['André Cruz']"
"tapable","https://github.com/webpack/tapable","['MIT']","['JS Foundation and other contributors']"
"tlhunter-sorted-set","https://github.com/tlhunter/node-sorted-set","['MIT']","['Thomas Hunter II']"
"tslib","https://github.com/microsoft/tslib","['0BSD']","['Microsoft Corp.']"
"ttl-set","https://github.com/watson/ttl-set","['MIT']","['Thomas Watson']"
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ Regardless of where you open the issue, someone at Datadog will try to help.

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.

Next.js applications that use Turbopack can wrap their existing configuration:

```javascript
const { withDatadogTurbopack } = require('dd-trace/next')

module.exports = withDatadogTurbopack({})
```

Preload `dd-trace/init` before application modules load. The generated modules can then publish their exports to the tracer.

See the [Turbopack implementation](packages/datadog-turbopack/README.md) for the internal build and runtime flow.

## Security Vulnerabilities

Expand Down
15 changes: 15 additions & 0 deletions integration-tests/turbopack/app/api/esm/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict'

async function GET () {
// eslint-disable-next-line n/no-missing-import -- dependency is installed in the integration sandbox
const { generateText } = await import('ai')
const model = require('../../model')
const result = await generateText({
model,
prompt: 'Say ok',
experimental_telemetry: { isEnabled: true },
})
return Response.json({ dependency: typeof generateText === 'function' ? 'ai' : 'missing', text: result.text })
}

module.exports = { GET }
19 changes: 19 additions & 0 deletions integration-tests/turbopack/app/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict'

module.exports = {
specificationVersion: 'v3',
provider: 'turbopack-test',
modelId: 'turbopack-test',
supportedUrls: {},
doGenerate () {
return Promise.resolve({
content: [{ type: 'text', text: 'ok' }],
finishReason: { unified: 'stop', raw: undefined },
usage: {
inputTokens: { total: 1 },
outputTokens: { total: 1 },
},
warnings: [],
})
},
}
67 changes: 67 additions & 0 deletions integration-tests/turbopack/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict'

const assert = require('node:assert/strict')
const { execSync } = require('node:child_process')
const path = require('node:path')

const axios = require('axios')

const {
FakeAgent,
checkSpansForServiceName,
sandboxCwd,
spawnPluginIntegrationTestProc,
stopProc,
useSandbox,
} = require('../helpers')

for (const nextVersion of ['15.5.0', 'latest']) {
describe(`Turbopack integration with Next.js ${nextVersion}`, () => {
useSandbox([`next@${nextVersion}`, 'react', 'react-dom', 'ai', 'express'], false, [__dirname])

let agent
let proc

before(function () {
this.timeout(300_000)
execSync('npm exec -- next build --turbopack', { cwd: appCwd(), stdio: 'inherit' })
})

beforeEach(async () => {
agent = await new FakeAgent().start()
proc = await spawnPluginIntegrationTestProc(appCwd(), 'server.js', agent.port, {
NODE_OPTIONS: '-r dd-trace/init',
})
})

afterEach(async () => {
await stopProc(proc)
await agent.stop()
})

it('runs bundled CommonJS and ESM dependencies', async () => {
const assertCjsTrace = agent.assertMessageReceived(({ payload }) => {
assert.strictEqual(checkSpansForServiceName(payload, 'next.request'), true)
assert.strictEqual(checkSpansForServiceName(payload, 'express.request'), true)
assert.strictEqual(checkSpansForServiceName(payload, 'generateText'), true)
}, 10_000, 1, true)

const response = await axios.get(`${proc.url}/api/cjs`)
assert.deepStrictEqual(response.data, { dependency: 'express', text: 'ok' })
await assertCjsTrace

const assertEsmTrace = agent.assertMessageReceived(({ payload }) => {
assert.strictEqual(checkSpansForServiceName(payload, 'next.request'), true)
assert.strictEqual(checkSpansForServiceName(payload, 'generateText'), true)
}, 10_000, 1, true)

const esmResponse = await axios.get(`${proc.url}/api/esm`)
assert.deepStrictEqual(esmResponse.data, { dependency: 'ai', text: 'ok' })
await assertEsmTrace
})
})
}

function appCwd () {
return path.join(sandboxCwd(), 'turbopack')
}
9 changes: 9 additions & 0 deletions integration-tests/turbopack/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

const path = require('node:path')

const { withDatadogTurbopack } = require('dd-trace/next')

const root = path.dirname(__dirname)

module.exports = withDatadogTurbopack({ turbopack: { root } }, { projectDir: root })
18 changes: 18 additions & 0 deletions integration-tests/turbopack/pages/api/cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict'

const express = require('express')
const { generateText } = require('ai')

const model = require('../../app/model')

const app = express()
app.use(async (_request, response) => {
const result = await generateText({
model,
prompt: 'Say ok',
experimental_telemetry: { isEnabled: true },
})
response.json({ dependency: 'express', text: result.text })
})

module.exports = (request, response) => app(request, response)
20 changes: 20 additions & 0 deletions integration-tests/turbopack/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

const { createServer } = require('node:http')

const next = require('next')

const nextApp = next({ dev: false })
const handle = nextApp.getRequestHandler()

async function start () {
await nextApp.prepare()

const server = createServer((request, response) => handle(request, response))
server.listen(0, () => {
const port = server.address().port
process.send({ port })
})
}

start()
15 changes: 15 additions & 0 deletions next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { NextConfig } from 'next'

export interface DatadogTurbopackOptions {
projectDir?: string
}

export function withDatadogTurbopack<TArguments extends unknown[]> (
nextConfig: (...args: TArguments) => NextConfig | Promise<NextConfig>,
options?: DatadogTurbopackOptions
): (...args: TArguments) => Promise<NextConfig>

export function withDatadogTurbopack (
nextConfig?: NextConfig | Promise<NextConfig>,
options?: DatadogTurbopackOptions
): Promise<NextConfig>
3 changes: 3 additions & 0 deletions next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict'

module.exports = require('./packages/datadog-turbopack')
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"test:trace:guardrails:ci": "node scripts/c8-ci.js test:trace:guardrails",
"test:esbuild": "mocha \"packages/datadog-esbuild/test/**/*.spec.js\"",
"test:esbuild:ci": "node scripts/c8-ci.js test:esbuild",
"test:turbopack": "mocha \"packages/datadog-turbopack/test/**/*.spec.js\"",
"test:turbopack:ci": "node scripts/c8-ci.js test:turbopack",
"test:webpack": "mocha \"packages/datadog-webpack/test/**/*.spec.js\"",
"test:webpack:ci": "node scripts/c8-ci.js test:webpack",
"test:instrumentations": "mocha --fail-zero \"packages/datadog-instrumentations/test/@(${PLUGINS}).spec.js\"",
Expand Down Expand Up @@ -91,6 +93,7 @@
"test:integration:electron": "mocha \"integration-tests/electron/*.spec.js\"",
"test:integration:esbuild": "mocha --timeout 60000 \"integration-tests/esbuild/*.spec.js\"",
"test:integration:esbuild:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/esbuild/*.spec.js\"",
"test:integration:turbopack": "mocha --timeout 60000 \"integration-tests/turbopack/*.spec.js\"",
"test:integration:webpack": "mocha --timeout 60000 \"integration-tests/webpack/*.spec.js\"",
"test:integration:openfeature": "mocha --timeout 60000 \"integration-tests/openfeature/*.spec.js\"",
"test:integration:openfeature:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/openfeature/*.spec.js\"",
Expand Down Expand Up @@ -150,6 +153,8 @@
"ci/**/*",
"cypress/**/*",
"esbuild.js",
"next.js",
"next.d.ts",
"webpack.js",
"ext/**/*",
"index.d.ts",
Expand All @@ -169,6 +174,7 @@
"packages/*/lib/**/*",
"packages/*/src/**/*",
"packages/datadog-instrumentations/orchestrion.yml",
"packages/datadog-turbopack/README.md",
"README.md",
"register.js",
"vendor/dist/**/*.d.ts",
Expand All @@ -179,6 +185,7 @@
],
"dependencies": {
"dc-polyfill": "^0.1.11",
"enhanced-resolve": "^5.17.1",
"import-in-the-middle": "^3.3.2",
"opentracing": ">=0.14.7"
},
Expand All @@ -196,7 +203,10 @@
"devDependencies": {
"@actions/core": "^3.0.1",
"@actions/github": "^9.1.1",
"@babel/generator": "^7.29.7",
"@babel/helpers": "^8.0.0",
"@babel/parser": "^7.29.7",
"@babel/traverse": "^7.29.7",
"@eslint/eslintrc": "^3.3.6",
"@eslint/js": "^10.0.1",
"@eslint/plugin-kit": "^0.7.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/datadog-esbuild/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ async function processModule ({ path, internal = false, context, excludeDefault
* @returns {boolean}
*/
function isESMFile (fullPathToModule, modulePackageJsonPath, packageJson = {}) {
if (fullPathToModule.endsWith('.mjs')) return true
if (fullPathToModule.endsWith('.cjs')) return false
if (fullPathToModule.endsWith('.mjs') || fullPathToModule.endsWith('.mts')) return true
if (fullPathToModule.endsWith('.cjs') || fullPathToModule.endsWith('.cts')) return false

const pathParts = fullPathToModule.split(path.sep)
do {
Expand Down
8 changes: 8 additions & 0 deletions packages/datadog-esbuild/test/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ describe('esbuild utils', () => {
assert.strictEqual(isESMFile('/path/to/test.cjs'), false)
})

it('should return true if the file has a .mts extension in a CommonJS package', () => {
assert.strictEqual(isESMFile('/path/to/test.mts', '/path/to/package.json', { type: 'commonjs' }), true)
})

it('should return false if the file has a .cts extension in an ESM package', () => {
assert.strictEqual(isESMFile('/path/to/test.cts', '/path/to/package.json', { type: 'module' }), false)
})

it('should return true if the file is in a directory with a package.json that has a type of module', () => {
assert.strictEqual(isESMFile('/path/to/test.js', '/path/to/package.json', { type: 'module' }), true)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

const BUNDLER_DC_GLOBAL = 'dd-trace:bundler:dc'

module.exports = { BUNDLER_DC_GLOBAL }
Loading
Loading