@uknows/setup 0.0.0-stage → 0.1.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.
- package/LICENSE +190 -0
- package/README.md +142 -2
- package/bin/uknows-setup.mjs +14 -0
- package/lib/check.mjs +211 -0
- package/lib/cli.mjs +166 -0
- package/lib/contract-docs.mjs +1154 -0
- package/lib/contract.mjs +66 -0
- package/lib/detect.mjs +575 -0
- package/lib/diff.mjs +147 -0
- package/lib/doc.mjs +98 -0
- package/lib/dockerfile.mjs +372 -0
- package/lib/endpoint.mjs +50 -0
- package/lib/engine.mjs +607 -0
- package/lib/existing.mjs +168 -0
- package/lib/git.mjs +47 -0
- package/lib/hosts.mjs +145 -0
- package/lib/manifest.mjs +112 -0
- package/lib/settings.mjs +280 -0
- package/lib/stacks/dotnet.mjs +165 -0
- package/lib/stacks/guided.mjs +104 -0
- package/lib/stacks/index.mjs +10 -0
- package/lib/stacks/java.mjs +56 -0
- package/lib/stacks/k8s.mjs +31 -0
- package/lib/stacks/node.mjs +235 -0
- package/lib/stacks/otel.mjs +11 -0
- package/lib/stacks/python-bootstrap.mjs +51 -0
- package/lib/stacks/python.mjs +434 -0
- package/lib/types.mjs +161 -0
- package/lib/ui.mjs +106 -0
- package/lib/workspace.mjs +198 -0
- package/package.json +42 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Node.js: add @opentelemetry/api and @opentelemetry/auto-instrumentations-node
|
|
3
|
+
* with the package manager of the repo, then load the register module when
|
|
4
|
+
* node starts: NODE_OPTIONS in the Dockerfile, or the option right after
|
|
5
|
+
* `node` in scripts.start.
|
|
6
|
+
*
|
|
7
|
+
* Safety rule: node crashes at start when the register module is missing, so
|
|
8
|
+
* the start changes only when the packages surely reach the running app.
|
|
9
|
+
* Package managers run with install scripts turned off, so no code of the
|
|
10
|
+
* project or its dependencies runs.
|
|
11
|
+
*/
|
|
12
|
+
import { Doc, shellWords } from '../doc.mjs'
|
|
13
|
+
import { addOption, dockerStart, parseDockerfile } from '../dockerfile.mjs'
|
|
14
|
+
import { NODE_REGISTER, PINS, STACKS } from '../contract.mjs'
|
|
15
|
+
import { list } from '../ui.mjs'
|
|
16
|
+
import { upFrom } from '../workspace.mjs'
|
|
17
|
+
import { envOverrides } from './k8s.mjs'
|
|
18
|
+
|
|
19
|
+
const STACK = STACKS.find((s) => s.id === 'node')
|
|
20
|
+
const LOCKS = [['package-lock.json', 'npm'], ['npm-shrinkwrap.json', 'npm'], ['pnpm-lock.yaml', 'pnpm'], ['yarn.lock', 'yarn'],
|
|
21
|
+
['bun.lockb', 'bun'], ['bun.lock', 'bun']]
|
|
22
|
+
const DEFAULT_LOCK = { npm: 'package-lock.json', pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock' }
|
|
23
|
+
const RUNS_NODE = /^(node|nodejs|npm|npx|yarn|pnpm)$|\.[cm]?js$/
|
|
24
|
+
const BUNDLER = /\b(esbuild|webpack|rollup|tsup|ncc)\b/
|
|
25
|
+
const VALUE_FLAGS = new Set(['-r', '--require', '--import', '--loader', '--experimental-loader', '--env-file', '-C', '--conditions'])
|
|
26
|
+
|
|
27
|
+
/** @type {import('../types.mjs').Applier} */
|
|
28
|
+
export const node = {
|
|
29
|
+
id: 'node',
|
|
30
|
+
plan(service, ctx) {
|
|
31
|
+
const { ws } = ctx
|
|
32
|
+
const out = { summary: [], edits: [], commands: [], extraSettings: [], manual: [], notes: [] }
|
|
33
|
+
const pkgFile = join(service.dir, 'package.json')
|
|
34
|
+
const pkgText = read(ws, pkgFile)
|
|
35
|
+
let pkg = null
|
|
36
|
+
try { pkg = JSON.parse(pkgText) } catch { /* handled below */ }
|
|
37
|
+
if (pkgText === null) {
|
|
38
|
+
out.manual.push({ title: 'Add OpenTelemetry to your Node.js app', lines: [`This tool did not find ${pkgFile}.`,
|
|
39
|
+
'Add the packages with your package manager. Then start node with the option below.'],
|
|
40
|
+
snippets: [{ caption: 'Install', language: 'bash', code: STACK.install }, { caption: 'Start command', language: 'bash', code: STACK.start }] })
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) {
|
|
44
|
+
out.manual.push({ title: `Fix ${pkgFile}`, lines: [`This tool cannot read ${pkgFile} as JSON.`, 'Fix the file. Then run npx @uknows/setup again.'] })
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
// Too old for the packages: with --import or --require of the register module, node would not start.
|
|
48
|
+
const v = /^(\d+)(?:\.(\d+))?/.exec(service.runtimeVersion ?? '')
|
|
49
|
+
const [major, minor] = v ? [Number(v[1]), v[2] === undefined ? 99 : Number(v[2])] : [99, 99]
|
|
50
|
+
if (major < 18 || major === 19 || (major === 18 && minor < 19) || (major === 20 && minor < 6)) {
|
|
51
|
+
out.manual.push({ title: 'Use Node.js 18.19 or later', lines: [`This app uses Node.js ${service.runtimeVersion}.`,
|
|
52
|
+
'The OpenTelemetry packages need Node.js 18.19 or later on version 18, or 20.6 or later.',
|
|
53
|
+
'Update the Node.js version of the app. Then run npx @uknows/setup again.'] })
|
|
54
|
+
return out
|
|
55
|
+
}
|
|
56
|
+
const docker = service.dockerfile ? read(ws, service.dockerfile) : null
|
|
57
|
+
const info = docker !== null ? parseDockerfile(docker) : null
|
|
58
|
+
const start = info ? dockerStart(info) : null
|
|
59
|
+
const script = typeof pkg.scripts?.start === 'string' ? pkg.scripts.start : null
|
|
60
|
+
const entry = entryOf(info ? start?.argv : service.start?.argv ?? shellWords(script ?? ''), script)
|
|
61
|
+
const esm = /\.m[jt]s$/.test(entry ?? '') || (pkg.type === 'module' && !/\.c[jt]s$/.test(entry ?? ''))
|
|
62
|
+
const flag = esm ? PINS.node.esmFlag : PINS.node.requireFlag
|
|
63
|
+
|
|
64
|
+
const guided = guidedBlock(service, ws, pkg, info, entry, flag)
|
|
65
|
+
if (guided) { out.manual.push(guided); return out }
|
|
66
|
+
const pm = packageManager(service.dir, pkg, ctx.repo ?? ws)
|
|
67
|
+
if (pm.outside) {
|
|
68
|
+
out.manual.push({ title: 'Run setup from the repo root', lines: [
|
|
69
|
+
`${service.dir === '.' ? 'This folder' : service.dir} is part of a workspace. Its ${base(pm.outside)} is in ${dirOf(pm.outside)}, `
|
|
70
|
+
+ 'outside the folder this tool works in, so this tool cannot update it.',
|
|
71
|
+
'Run npx @uknows/setup in the repo root without --path. Then pick this service from the list, or use --all.'] })
|
|
72
|
+
out.stop = true
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
const wanted = [...PINS.node.packages, ...(esm ? [PINS.node.esmPackage] : [])]
|
|
76
|
+
if (pm.cmd === 'bun' || pm.cmd === 'other' || pm.pnp) {
|
|
77
|
+
out.manual.push({ title: 'Add OpenTelemetry with your package manager',
|
|
78
|
+
lines: [pm.pnp ? 'This repo uses Yarn Plug\'n\'Play, so node cannot load the packages with --require alone.'
|
|
79
|
+
: 'This tool adds packages with npm, pnpm or yarn only.', 'Add the packages below as normal dependencies.',
|
|
80
|
+
pm.pnp ? 'Start the app with yarn node and the option below.' : 'Start node with the option below.'],
|
|
81
|
+
snippets: [{ caption: 'Packages', language: 'text', code: wanted.join('\n') },
|
|
82
|
+
{ caption: 'Start command', language: 'bash', code: `${pm.pnp ? 'yarn ' : ''}${startLine(flag, entry)}` }] })
|
|
83
|
+
return out
|
|
84
|
+
}
|
|
85
|
+
const added = wanted.filter((name) => !pkg.dependencies?.[name])
|
|
86
|
+
if (added.length) {
|
|
87
|
+
const lock = pm.lock ?? (pm.cmd === 'npm' ? null : join(service.dir, DEFAULT_LOCK[pm.cmd]))
|
|
88
|
+
// No install scripts. npm with a lockfile and pnpm write only package.json and the lockfile.
|
|
89
|
+
const flags = pm.cmd === 'npm' ? ['--ignore-scripts', pm.lock ? '--package-lock-only' : '--no-package-lock']
|
|
90
|
+
: pm.cmd === 'pnpm' ? ['--lockfile-only', ...(ws.exists(join(service.dir, 'pnpm-workspace.yaml')) ? ['-w'] : [])]
|
|
91
|
+
: [pm.berry ? '--mode=skip-build' : '--ignore-scripts']
|
|
92
|
+
const why = `Add ${list(added)} with ${pm.cmd}.`
|
|
93
|
+
const lockOnly = pm.cmd === 'pnpm' || (pm.cmd === 'npm' && Boolean(pm.lock))
|
|
94
|
+
out.commands.push({ cwd: service.dir, cmd: pm.cmd, why, added, lockOnly,
|
|
95
|
+
args: pm.cmd === 'npm' ? ['install', '--save', ...flags, ...added] : ['add', ...flags, ...added],
|
|
96
|
+
undo: { cmd: pm.cmd, args: [pm.cmd === 'npm' ? 'uninstall' : 'remove', ...flags, ...added] },
|
|
97
|
+
touches: [pkgFile, ...(lock ? [lock] : [])] })
|
|
98
|
+
out.summary.push(why)
|
|
99
|
+
if (lockOnly) out.notes.push(`To start the app on this computer, run ${pm.cmd} install first. Your build installs the packages from ${base(lock)}.`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (info) {
|
|
103
|
+
const runsNode = start && (start.argv ?? [start.raw]).flatMap((w) => w.split(/\s+/)).some((w) => RUNS_NODE.test(base(w.replace(/^["']|["']$/g, ''))))
|
|
104
|
+
if (!runsNode) out.manual.push(startBlock(flag, entry, `the start command in ${service.dockerfile}`))
|
|
105
|
+
else {
|
|
106
|
+
const has = (value) => value.includes(NODE_REGISTER)
|
|
107
|
+
const after = addOption(docker, 'NODE_OPTIONS', flag, has)
|
|
108
|
+
if (after !== docker) {
|
|
109
|
+
out.edits.push({ file: service.dockerfile, before: docker, after, why: 'Load OpenTelemetry when node starts.' })
|
|
110
|
+
out.summary.push(`Load OpenTelemetry when node starts (NODE_OPTIONS in ${service.dockerfile}).`)
|
|
111
|
+
if (info.env.has('NODE_OPTIONS') || info.inherited.has('NODE_OPTIONS')) {
|
|
112
|
+
out.notes.push(`${service.dockerfile} already sets NODE_OPTIONS. A new ENV line before the start keeps that value and adds the option.`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
out.manual.push(...envOverrides(service, 'NODE_OPTIONS', flag, has))
|
|
116
|
+
}
|
|
117
|
+
} else if (script !== null && service.start?.kind !== 'procfile' && shellWords(script)?.[0] === 'node') {
|
|
118
|
+
if (!script.includes(NODE_REGISTER)) {
|
|
119
|
+
const after = editStartScript(pkgText, flag)
|
|
120
|
+
if (after === null) out.manual.push(startBlock(flag, entry, `scripts.start in ${pkgFile}`))
|
|
121
|
+
else {
|
|
122
|
+
out.edits.push({ file: pkgFile, before: pkgText, after, why: 'Load OpenTelemetry when node starts.' })
|
|
123
|
+
out.summary.push(`Load OpenTelemetry when node starts (start script in ${pkgFile}).`)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
out.manual.push(startBlock(flag, entry, script !== null ? `scripts.start in ${pkgFile}` : null))
|
|
128
|
+
}
|
|
129
|
+
return out
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Cases where loading the register module cannot work: guided only, no packages. */
|
|
134
|
+
function guidedBlock(service, ws, pkg, info, entry, flag) {
|
|
135
|
+
if (service.frameworks.includes('next')) {
|
|
136
|
+
return { title: 'Next.js needs its own OpenTelemetry setup', lines: ['Next.js builds its server code, so the --require option cannot load OpenTelemetry.',
|
|
137
|
+
'Follow the OpenTelemetry guide in the Next.js docs. It adds an instrumentation.ts file.', 'Keep the 5 UKNOWS settings. That setup reads them.'] }
|
|
138
|
+
}
|
|
139
|
+
if (info && (service.frameworks.includes('nitro') || service.frameworks.includes('nuxt'))) {
|
|
140
|
+
const install = info.final.some((x) => x.keyword === 'RUN' && /\b(npm (ci|install|i)|pnpm (install|i)|yarn)\b/.test(x.args))
|
|
141
|
+
const copies = info.final.filter((x) => x.keyword === 'COPY' || x.keyword === 'ADD')
|
|
142
|
+
if (!install && !copies.some((x) => /node_modules/.test(x.args) || !/\.output/.test(x.args))) {
|
|
143
|
+
return { title: 'Put node_modules in the final image', lines: [`The final stage of ${service.dockerfile} copies only the built server, so node cannot load OpenTelemetry.`,
|
|
144
|
+
'Install the packages in the final stage, or copy node_modules into it.', 'Then run npx @uknows/setup again.'] }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const bundler = BUNDLER.exec(typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '')
|
|
148
|
+
if (bundler && !(entry && ws.exists(join(service.dir, entry.replace(/^\.\//, ''))))) {
|
|
149
|
+
return { title: 'Load OpenTelemetry in a bundled app', lines: [`The build script bundles the app with ${bundler[1]}. OpenTelemetry cannot change bundled packages.`,
|
|
150
|
+
'Add the packages below as normal dependencies. In the bundler config, mark them and the libraries to trace as external.',
|
|
151
|
+
'Start node with the option below.'],
|
|
152
|
+
snippets: [{ caption: 'Install', language: 'bash', code: STACK.install }, { caption: 'Start command', language: 'bash', code: startLine(flag, entry) }] }
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The package manager from the packageManager field, else from the lockfile of the service folder or of
|
|
159
|
+
* the workspace root above it (a parent folder whose package.json has workspaces, or with pnpm-workspace.yaml).
|
|
160
|
+
* The search goes on above the --path folder, up to the top of the git repo (view.top). A workspace root or
|
|
161
|
+
* its lockfile up there is `outside`: this tool cannot update it.
|
|
162
|
+
*/
|
|
163
|
+
function packageManager(dir, pkg, view) {
|
|
164
|
+
const field = typeof pkg.packageManager === 'string' ? /^(npm|pnpm|yarn|bun)@(\d+)/.exec(pkg.packageManager) : null
|
|
165
|
+
let cmd = field ? field[1] : typeof pkg.packageManager === 'string' ? 'other' : null
|
|
166
|
+
let lock = null, outside = null
|
|
167
|
+
for (const d of upFrom(dir, view.top ?? '.')) {
|
|
168
|
+
const pnpmRoot = view.exists(join(d, 'pnpm-workspace.yaml'))
|
|
169
|
+
const marker = pnpmRoot || /"workspaces"\s*:/.test(read(view, join(d, 'package.json')) ?? '')
|
|
170
|
+
const hit = (d === dir || marker) && LOCKS.find(([file, owner]) => (!cmd || cmd === owner) && read(view, join(d, file)) !== null)
|
|
171
|
+
if ((d === '..' || d.startsWith('../')) && (marker || hit)) { outside = join(d, hit ? hit[0] : pnpmRoot ? 'pnpm-workspace.yaml' : 'package.json'); break }
|
|
172
|
+
if (hit) { lock = join(d, hit[0]); cmd = hit[1]; break }
|
|
173
|
+
}
|
|
174
|
+
cmd ??= 'npm'
|
|
175
|
+
const home = lock ? dirOf(lock) : dir
|
|
176
|
+
const rc = read(view, join(home, '.yarnrc.yml'))
|
|
177
|
+
const berry = cmd === 'yarn' && (field ? Number(field[2]) >= 2 : rc !== null || /^__metadata:/m.test(read(view, lock ?? '') ?? ''))
|
|
178
|
+
const pnp = cmd === 'yarn' && (view.exists(join(home, '.pnp.cjs')) || view.exists(join(home, '.pnp.js'))
|
|
179
|
+
|| (berry && !/^\s*nodeLinker\s*:\s*["']?(node-modules|pnpm)/m.test(rc ?? '')))
|
|
180
|
+
return { cmd, lock, pnp, berry, outside }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The JS file that node runs, from a start argv. `npm start` and friends read scripts.start. */
|
|
184
|
+
function entryOf(argv, script) {
|
|
185
|
+
let words = argv ?? null
|
|
186
|
+
if (words && /^(npm|yarn|pnpm)$/.test(base(words[0])) && (words[1] === 'start' || (words[1] === 'run' && words[2] === 'start'))) words = shellWords(script ?? '')
|
|
187
|
+
if (!words?.length) return null
|
|
188
|
+
if (/\.[cm]?[jt]s$/.test(words[0])) return words[0]
|
|
189
|
+
if (base(words[0]) !== 'node') return null
|
|
190
|
+
for (let i = 1; i < words.length; i++) {
|
|
191
|
+
if (!words[i].startsWith('-')) return words[i]
|
|
192
|
+
if (VALUE_FLAGS.has(words[i])) i++
|
|
193
|
+
}
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Insert the option right after `node` in scripts.start. Changes only that JSON string. null when unsure. */
|
|
198
|
+
function editStartScript(text, flag) {
|
|
199
|
+
const doc = new Doc(text)
|
|
200
|
+
const from = doc.find(/"scripts"\s*:/)
|
|
201
|
+
const i = from < 0 ? -1 : doc.find(/"start"\s*:\s*"/, from)
|
|
202
|
+
if (i < 0) return null
|
|
203
|
+
const line = doc.get(i)
|
|
204
|
+
const m = /("start"\s*:\s*)("(?:[^"\\]|\\.)*")/.exec(line)
|
|
205
|
+
if (!m) return null
|
|
206
|
+
const value = JSON.parse(m[2]).replace(/^(\s*node)(?=\s|$)/, `$1 ${flag}`)
|
|
207
|
+
doc.set(i, line.slice(0, m.index + m[1].length) + JSON.stringify(value) + line.slice(m.index + m[0].length))
|
|
208
|
+
const after = doc.toString()
|
|
209
|
+
try {
|
|
210
|
+
const expected = JSON.parse(text)
|
|
211
|
+
expected.scripts.start = value
|
|
212
|
+
return JSON.stringify(JSON.parse(after)) === JSON.stringify(expected) ? after : null
|
|
213
|
+
} catch { return null }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function startLine(flag, entry) {
|
|
217
|
+
return STACK.start.replace(PINS.node.requireFlag, flag).replace(/server\.js$/, entry ?? 'server.js')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function startBlock(flag, entry, where) {
|
|
221
|
+
return { title: 'Load OpenTelemetry when node starts',
|
|
222
|
+
lines: [where ? `This tool cannot change ${where} safely.` : 'This tool did not find the command that starts the app.',
|
|
223
|
+
'Add the option below to the node command that starts the app. Or set NODE_OPTIONS to it where the app runs.',
|
|
224
|
+
...STACK.notes.filter((n) => !entry && /server\.js/.test(n))],
|
|
225
|
+
snippets: [{ caption: 'Start command', language: 'bash', code: startLine(flag, entry) },
|
|
226
|
+
{ caption: 'Or set NODE_OPTIONS', language: 'bash', code: `NODE_OPTIONS="${flag}"` }] }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const base = (word) => (word ?? '').split('/').pop()
|
|
230
|
+
const join = (dir, file) => (dir === '.' ? file : `${dir}/${file}`)
|
|
231
|
+
const dirOf = (path) => (path.includes('/') ? path.slice(0, path.lastIndexOf('/')) : '.')
|
|
232
|
+
|
|
233
|
+
function read(ws, path) {
|
|
234
|
+
try { return path ? ws.read(path) : null } catch { return null }
|
|
235
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// The app already uses OpenTelemetry: keep its packages and start command. settings.mjs adds the 5 settings.
|
|
2
|
+
import { STACKS } from '../contract.mjs'
|
|
3
|
+
|
|
4
|
+
/** @type {import('../types.mjs').Applier} */
|
|
5
|
+
export const otel = {
|
|
6
|
+
id: 'otel',
|
|
7
|
+
plan() {
|
|
8
|
+
return { summary: ['Keep your OpenTelemetry packages. Point them at UKNOWS with the 5 settings.'], edits: [], commands: [],
|
|
9
|
+
extraSettings: [], manual: [], notes: [...STACKS.find((s) => s.id === 'otel').notes] }
|
|
10
|
+
},
|
|
11
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* What `opentelemetry-bootstrap -a requirements` prints, without a Python
|
|
3
|
+
* environment. Source: bootstrap_gen.py of opentelemetry-python-contrib
|
|
4
|
+
* v0.66b1. Upstream checks the installed packages; this tool reads the
|
|
5
|
+
* requirement names instead, plus a few packages that the common frameworks
|
|
6
|
+
* always pull in (IMPLIED).
|
|
7
|
+
*
|
|
8
|
+
* Left out on purpose: openai-v2 and vertexai (not pinned to 0.66b1),
|
|
9
|
+
* psutil -> system-metrics (metrics are off) and the upstream quirk that maps
|
|
10
|
+
* pydantic to tortoiseorm.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Always printed. */
|
|
14
|
+
export const DEFAULTS = ['asyncio', 'dbapi', 'exceptions', 'logging', 'sqlite3', 'threading', 'urllib', 'wsgi']
|
|
15
|
+
|
|
16
|
+
/** [normalized library name, instrumentation suffixes], in upstream order. */
|
|
17
|
+
export const LIBRARIES = [
|
|
18
|
+
['aio-pika', ['aio-pika']], ['aiohttp', ['aiohttp-client', 'aiohttp-server']], ['aiokafka', ['aiokafka']],
|
|
19
|
+
['aiopg', ['aiopg']], ['asgiref', ['asgi']], ['asyncclick', ['asyncclick']], ['asyncpg', ['asyncpg']],
|
|
20
|
+
['boto3', ['boto3sqs']], ['botocore', ['botocore']], ['aiobotocore', ['botocore']],
|
|
21
|
+
['cassandra-driver', ['cassandra']], ['scylla-driver', ['cassandra']], ['celery', ['celery']], ['click', ['click']],
|
|
22
|
+
['confluent-kafka', ['confluent-kafka']], ['django', ['django']], ['falcon', ['falcon']], ['fastapi', ['fastapi']],
|
|
23
|
+
['flask', ['flask']], ['grpcio', ['grpc']], ['httpx', ['httpx']], ['httpx2', ['httpx']], ['jinja2', ['jinja2']],
|
|
24
|
+
['kafka-python', ['kafka-python']], ['mysql-connector-python', ['mysql']], ['mysqlclient', ['mysqlclient']],
|
|
25
|
+
['pika', ['pika']], ['psycopg', ['psycopg']], ['psycopg2', ['psycopg2']], ['psycopg2-binary', ['psycopg2']],
|
|
26
|
+
['pymemcache', ['pymemcache']], ['pymongo', ['pymongo']], ['pymssql', ['pymssql']], ['pymysql', ['pymysql']],
|
|
27
|
+
['pyramid', ['pyramid']], ['redis', ['redis']], ['remoulade', ['remoulade']], ['requests', ['requests']],
|
|
28
|
+
['sqlalchemy', ['sqlalchemy']], ['starlette', ['starlette']], ['structlog', ['structlog']], ['tornado', ['tornado']],
|
|
29
|
+
['tortoise-orm', ['tortoiseorm']], ['urllib3', ['urllib3']],
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/** Packages that a framework always installs, so upstream would see them too. */
|
|
33
|
+
export const IMPLIED = { fastapi: ['starlette'], django: ['asgiref'], flask: ['jinja2'], requests: ['urllib3'], boto3: ['botocore'] }
|
|
34
|
+
|
|
35
|
+
/** PEP 503 name: lower case, runs of - _ . become -. */
|
|
36
|
+
export const normalize = (name) => name.toLowerCase().replace(/[-_.]+/g, '-')
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The instrumentation packages for these requirement names: the defaults,
|
|
40
|
+
* then 1 per matching library in table order, without repeats.
|
|
41
|
+
* @param {Iterable<string>} names normalized requirement names
|
|
42
|
+
* @param {string} version e.g. '0.66b1'
|
|
43
|
+
* @returns {string[]} e.g. ['opentelemetry-instrumentation-asyncio==0.66b1', ...]
|
|
44
|
+
*/
|
|
45
|
+
export function bootstrap(names, version) {
|
|
46
|
+
const have = new Set(names)
|
|
47
|
+
for (const name of [...have]) for (const extra of IMPLIED[name] ?? []) have.add(extra)
|
|
48
|
+
const suffixes = [...DEFAULTS]
|
|
49
|
+
for (const [library, list] of LIBRARIES) if (have.has(library)) suffixes.push(...list)
|
|
50
|
+
return [...new Set(suffixes)].map((x) => `opentelemetry-instrumentation-${x}==${version}`)
|
|
51
|
+
}
|