Skip to content

Commit beee73e

Browse files
BridgeARleoromanovsky
authored andcommitted
fix(openfeature): support file-traced optional provider loading (#9324)
Next.js standalone builds using pnpm omitted the OpenFeature provider because the shared optional-peer wrapper was invisible to nft, leaving tracer.openfeature as the no-op provider at runtime. Keep the bundler escape hatch while exposing a file-traceable fallback entrypoint for tools that cannot recognize the wrapper. Also document CommonJS and ESM entrypoints. Fixes: #8635
1 parent 238ab51 commit beee73e

15 files changed

Lines changed: 351 additions & 103 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ Regardless of where you open the issue, someone at Datadog will try to help.
9393

9494
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.
9595

96+
When using the experimental OpenFeature provider, file-traced deployments can force the optional provider and
97+
its dependencies into the output with a side-effect import before accessing `tracer.openfeature`:
98+
99+
CommonJS:
100+
101+
```js
102+
require('dd-trace/openfeature')
103+
```
104+
105+
ES modules:
106+
107+
```js
108+
import 'dd-trace/openfeature.js'
109+
```
110+
111+
This is a fallback for build tools that do not recognize the provider's optional-require wrapper.
112+
96113

97114
## Security Vulnerabilities
98115

openfeature.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

openfeature.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use strict'
2+
3+
// Static fallback for file tracers that do not recognize the optional-peer wrapper.
4+
require('@datadog/openfeature-node-server')

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@
153153
"LICENSE.Apache",
154154
"LICENSE.BSD3",
155155
"loader-hook.mjs",
156+
"openfeature.d.ts",
157+
"openfeature.js",
156158
"packages/*/index.js",
157159
"packages/*/index.electron.js",
158160
"packages/*/lib/**/*",
@@ -196,6 +198,7 @@
196198
"@types/mocha": "^10.0.10",
197199
"@types/node": "^18.19.106",
198200
"@types/sinon": "^22.0.0",
201+
"@vercel/nft": "^0.29.4",
199202
"axios": "^1.18.1",
200203
"benchmark": "^2.1.4",
201204
"body-parser": "^2.3.0",

packages/datadog-esbuild/test/plugin.spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ function captureOptionalPeerOnLoad () {
1111
initialOptions: {},
1212
onResolve () {},
1313
onLoad (options, callback) {
14-
if (options.filter.source.includes('flagging_provider')) onLoad = callback
14+
if (options.filter.source.includes('require-provider')) onLoad = callback
1515
},
1616
})
1717
return onLoad
1818
}
1919

2020
describe('datadog-esbuild plugin', () => {
2121
describe('optional peer bundling', () => {
22-
it('rewrites the installed peer load in flagging_provider into a literal require', () => {
22+
it('rewrites the installed peer load in require-provider into a literal require', () => {
2323
const onLoad = captureOptionalPeerOnLoad()
24-
const providerPath = require.resolve('../../dd-trace/src/openfeature/flagging_provider')
24+
const providerPath = require.resolve('../../dd-trace/src/openfeature/require-provider')
2525

2626
const result = onLoad({ path: providerPath })
2727

@@ -35,7 +35,7 @@ describe('datadog-esbuild plugin', () => {
3535
it('ignores files that match the filter but are not an optional-peer file', () => {
3636
const onLoad = captureOptionalPeerOnLoad()
3737

38-
assert.strictEqual(onLoad({ path: '/somewhere/else/flagging_provider.js' }), undefined)
38+
assert.strictEqual(onLoad({ path: '/somewhere/else/require-provider.js' }), undefined)
3939
})
4040
})
4141
})

packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ const path = require('node:path')
44

55
// Build-time half of the optional-peer mechanism shared by the webpack and esbuild plugins.
66
//
7-
// Runtime files load an optional peer through `requireOptionalPeer('name')` (see
8-
// `require-optional-peer.js`), which bundlers cannot follow, so a build that does not opt into
9-
// the feature never pulls in the peer's (possibly optional) dependency chain (#8635). When the
10-
// peer is installed at build time the user has opted in, so the plugins rewrite the call into a
11-
// literal `require('name')` and let the bundler inline the peer, which keeps it working after
12-
// the bundle is relocated to a tree without the peer on disk (#8980). Peers that are absent at
13-
// build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
7+
// Runtime files load an optional peer through a local `requireOptionalPeer('name')` wrapper.
8+
// File tracers recognize its bound-require shape, while bundlers cannot follow the dynamic
9+
// argument, so a build that does not opt into the feature never pulls in the peer's dependency
10+
// chain (#8635). When the peer is installed at build time the user has opted in, so the plugins
11+
// rewrite the call into a literal `require('name')` and let the bundler inline the peer, which
12+
// keeps it working after the bundle is relocated without the peer on disk (#8980). Peers that
13+
// are absent at build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
1414

1515
// Files that load an optional peer this way, as suffixes of the resolved module path. The same
1616
// suffix matches the repo layout and `node_modules/dd-trace`. Add a file here to extend the
1717
// mechanism to a new optional peer; no plugin change is needed.
1818
const OPTIONAL_PEER_FILES = [
19-
'packages/dd-trace/src/openfeature/flagging_provider.js',
19+
'packages/dd-trace/src/openfeature/require-provider.js',
2020
]
2121

2222
// Captures the peer name from `requireOptionalPeer('name')` / `requireOptionalPeer("name")`.

packages/datadog-instrumentations/src/helpers/require-optional-peer.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/datadog-instrumentations/test/helpers/optional-peer-bundler.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('optional-peer-bundler', () => {
3939
describe('matchesOptionalPeerFile', () => {
4040
it('matches a registered optional-peer file suffix', () => {
4141
assert.strictEqual(
42-
matchesOptionalPeerFile('/app/node_modules/dd-trace/packages/dd-trace/src/openfeature/flagging_provider.js'),
42+
matchesOptionalPeerFile('/app/node_modules/dd-trace/packages/dd-trace/src/openfeature/require-provider.js'),
4343
true
4444
)
4545
})

packages/datadog-instrumentations/test/helpers/require-optional-peer.spec.js

Lines changed: 0 additions & 47 deletions
This file was deleted.

packages/datadog-webpack/test/plugin.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ describe('DatadogWebpackPlugin', () => {
6868
return afterResolve
6969
}
7070

71-
it('applies the optional-peer loader to flagging_provider', () => {
72-
const createData = { resource: require.resolve('../../dd-trace/src/openfeature/flagging_provider') }
71+
it('applies the optional-peer loader to require-provider', () => {
72+
const createData = { resource: require.resolve('../../dd-trace/src/openfeature/require-provider') }
7373

7474
captureAfterResolve()({ createData })
7575

0 commit comments

Comments
 (0)