-
Notifications
You must be signed in to change notification settings - Fork 407
Expand file tree
/
Copy pathbuild-and-test-openfeature.js
More file actions
151 lines (137 loc) · 5.55 KB
/
Copy pathbuild-and-test-openfeature.js
File metadata and controls
151 lines (137 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env node
'use strict'
/* eslint-disable no-console */
// End-to-end coverage for the OpenFeature optional peer chain
// `@datadog/openfeature-node-server` -> `@openfeature/server-sdk` -> `@openfeature/core`
// under webpack. Two scenarios pin the two failure modes:
//
// 1. #8635: without the dd-trace plugin, the require stays opaque so webpack never
// follows the optional chain. A user who bundles dd-trace without opting into
// feature flagging must not have their build fail on the missing chain.
//
// 2. #8980: with the dd-trace plugin and the peer installed, the plugin bundles the
// peer into the output. Feature flagging then survives the bundle being relocated
// to a tree where the peer is not on disk (e.g. a standalone deploy), instead of
// silently falling back to the no-op provider.
const fs = require('fs')
const os = require('os')
const path = require('path')
const assert = require('assert')
const { execFileSync } = require('child_process')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
const experiments = require('./webpack-experiments')
const ENTRY = path.join(__dirname, 'openfeature-app.js')
const FLAGGING_PROVIDER = path.join('openfeature', 'flagging_provider')
const EXTERNALS = [
'diagnostics_channel',
'pg', 'mysql2', 'better-sqlite3', 'sqlite3', 'mysql', 'oracledb', 'pg-query-stream', 'tedious',
'@yaacovcr/transform',
// Optional native dd-trace modules (kept consistent with `build.js`).
'@datadog/native-appsec', '@datadog/native-iast-taint-tracking', '@datadog/native-metrics',
'@datadog/pprof', '@datadog/libdatadog',
// NOTE: `@datadog/openfeature-node-server` is deliberately absent. dd-trace must keep
// the require opaque without help from the user's webpack config.
]
/**
* @param {string} outfile - Absolute path of the bundle to emit
* @param {Array<object>} plugins - Webpack plugins to apply
* @returns {Promise<object>} The webpack stats object
*/
function build (outfile, plugins) {
return new Promise((resolve, reject) => {
webpack({
mode: 'development',
entry: ENTRY,
target: 'node',
externalsType: 'commonjs',
...(experiments && { experiments }),
output: { filename: path.basename(outfile), path: path.dirname(outfile), hashFunction: 'sha256' },
externals: EXTERNALS,
plugins,
}, (err, stats) => {
if (err) return reject(err)
if (stats.hasErrors()) return reject(new Error(stats.toString({ errors: true })))
resolve(stats)
})
})
}
/**
* @param {object} stats - Webpack stats object
* @returns {Array<object>} `Critical dependency` warnings attributable to flagging_provider
*/
function flaggingProviderWarnings (stats) {
return stats.compilation.warnings.filter((warning) =>
/Critical dependency/.test(warning.message) &&
(String(warning.module?.resource).includes(FLAGGING_PROVIDER) || /flagging_provider/.test(warning.message))
)
}
async function main () {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-openfeature-'))
try {
// Scenario 1 (#8635): no dd-trace plugin -> the require stays opaque.
const opaqueOut = path.join(__dirname, 'openfeature-out-opaque.js')
const opaqueStats = await build(opaqueOut, [])
try {
assert.strictEqual(
flaggingProviderWarnings(opaqueStats).length,
0,
'flagging_provider tripped the webpack expression-dependency path; resolve through ' +
'`__non_webpack_require__`, not bare `require.resolve`'
)
const opaqueBundle = fs.readFileSync(opaqueOut).toString()
assert(
!opaqueBundle.includes('@datadog/flagging-core'),
'bundle leaked `@datadog/flagging-core`; webpack must not statically follow the optional peer'
)
assert(
!opaqueBundle.includes('node_modules/@openfeature/server-sdk'),
'bundle leaked `@openfeature/server-sdk` paths; webpack must not statically follow the optional peer'
)
} finally {
fs.rmSync(opaqueOut, { force: true })
}
// Scenario 2 (#8980): with the dd-trace plugin and the peer installed, the peer is
// bundled, so the relocated bundle loads the real provider instead of the no-op.
assert.strictEqual(
isResolvable('@datadog/openfeature-node-server', __dirname),
true,
'the optional peer must be installed for this scenario; run `yarn install` with devDependencies'
)
const bundledOut = path.join(__dirname, 'openfeature-out-bundled.js')
await build(bundledOut, [new DatadogWebpackPlugin()])
const relocated = path.join(tmpDir, 'out.js')
fs.copyFileSync(bundledOut, relocated)
fs.rmSync(bundledOut, { force: true })
assert.strictEqual(
isResolvable('@datadog/openfeature-node-server', tmpDir),
false,
'the relocation dir must not resolve the peer, otherwise the test proves nothing'
)
const runOutput = execFileSync(process.execPath, [relocated], { encoding: 'utf8' })
assert(
runOutput.includes('PROVIDER_OK'),
`relocated bundle did not load the real OpenFeature provider:\n${runOutput}`
)
console.log('ok')
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true })
}
}
/**
* @param {string} request - Module specifier
* @param {string} fromDir - Directory to resolve from
* @returns {boolean} Whether the module resolves from `fromDir`
*/
function isResolvable (request, fromDir) {
try {
require.resolve(request, { paths: [fromDir] })
return true
} catch {
return false
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})