Skip to content

Commit 62915d1

Browse files
olivwalshwatson
andauthored
Inject git metadata in datadog esbuild plugin (#6388)
Automatically retrieve and inject Git tags (`GIT_REPOSITORY_URL` and `GIT_COMMIT_SHA`) in apps bundled with the Datadog esbuild plugin. Co-authored-by: Thomas Watson <thomas.watson@datadoghq.com>
1 parent 622a240 commit 62915d1

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env node
2+
'use strict'
3+
4+
/* eslint-disable no-console */
5+
const fs = require('fs')
6+
const { spawnSync } = require('child_process')
7+
const assert = require('assert')
8+
9+
const ddPlugin = require('../../esbuild')
10+
const esbuild = require('esbuild')
11+
12+
const SCRIPT = './git-tags-out.js'
13+
14+
esbuild.build({
15+
entryPoints: ['basic-test.js'],
16+
bundle: true,
17+
outfile: SCRIPT,
18+
plugins: [ddPlugin],
19+
platform: 'node',
20+
target: ['node18'],
21+
external: [
22+
// dead code paths introduced by knex
23+
'pg',
24+
'mysql2',
25+
'better-sqlite3',
26+
'sqlite3',
27+
'mysql',
28+
'oracledb',
29+
'pg-query-stream',
30+
'tedious'
31+
]
32+
}).then(() => {
33+
const { status, stdout, stderr } = spawnSync('node', [SCRIPT], {
34+
env: { ...process.env, DD_TRACE_DEBUG: 'true' },
35+
encoding: 'utf8'
36+
})
37+
if (stderr.length) {
38+
console.error(stderr)
39+
}
40+
if (status) {
41+
throw new Error(`Generated script exited with unexpected exit code: ${status}`)
42+
}
43+
if (stdout.length === 0) {
44+
throw new Error('No debug output received. Git metadata may not be injected properly')
45+
}
46+
const repositoryURL = stdout.match(/"_dd\.git\.repository_url":"([^"]+)"/)?.[1]
47+
const commitSha = stdout.match(/"_dd\.git\.commit\.sha":"([^"]+)"/)?.[1]
48+
assert.ok(repositoryURL, '_dd.git.repository_url should be present')
49+
assert.ok(commitSha, '_dd.git.commit.sha should be present')
50+
assert.equal(commitSha.length, 40, 'Git commit sha tag should be valid')
51+
console.log('ok')
52+
}).catch((err) => {
53+
console.error(err)
54+
process.exit(1)
55+
}).finally(() => {
56+
fs.rmSync(SCRIPT, { force: true })
57+
})

integration-tests/esbuild/index.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ esbuildVersions.forEach((version) => {
8787
})
8888
})
8989

90+
it('injects Git metadata into bundled applications', () => {
91+
const command = 'node ./build-and-test-git-tags.js'
92+
console.log(command)
93+
chproc.execSync(command, {
94+
timeout: 1000 * 30
95+
})
96+
})
97+
9098
describe('ESM', () => {
9199
afterEach(() => {
92100
fs.rmSync('./out.mjs', { force: true })

packages/datadog-esbuild/index.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const RAW_BUILTINS = require('module').builtinModules
3333
const CHANNEL = 'dd-trace:bundler:load'
3434
const path = require('path')
3535
const fs = require('fs')
36+
const { execSync } = require('child_process')
3637

3738
const builtins = new Set()
3839

@@ -61,6 +62,39 @@ function isESMBuild (build) {
6162
return format === 'esm' || outputFile?.endsWith('.mjs') || outExtension === '.mjs'
6263
}
6364

65+
function getGitMetadata () {
66+
const gitMetadata = {
67+
repositoryURL: null,
68+
commitSHA: null
69+
}
70+
71+
try {
72+
gitMetadata.repositoryURL = execSync('git config --get remote.origin.url', {
73+
encoding: 'utf8',
74+
stdio: ['pipe', 'pipe', 'ignore'],
75+
cwd: process.cwd()
76+
}).trim()
77+
} catch (e) {
78+
if (DEBUG) {
79+
console.warn('Warning: failed to get git repository URL:', e.message)
80+
}
81+
}
82+
83+
try {
84+
gitMetadata.commitSHA = execSync('git rev-parse HEAD', {
85+
encoding: 'utf8',
86+
stdio: ['pipe', 'pipe', 'ignore'],
87+
cwd: process.cwd()
88+
}).trim()
89+
} catch (e) {
90+
if (DEBUG) {
91+
console.warn('Warning: failed to get git commit SHA:', e.message)
92+
}
93+
}
94+
95+
return gitMetadata
96+
}
97+
6498
module.exports.setup = function (build) {
6599
const externalModules = new Set(build.initialOptions.external || [])
66100
if (isESMBuild(build)) {
@@ -77,6 +111,28 @@ ${build.initialOptions.banner.js}`
77111
}
78112
}
79113

114+
// Get git metadata at build time and add it to the banner for both ESM and CommonJS builds
115+
const gitMetadata = getGitMetadata()
116+
if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
117+
build.initialOptions.banner ??= {}
118+
build.initialOptions.banner.js ??= ''
119+
120+
build.initialOptions.banner.js = `if (typeof process === 'object' && process !== null &&
121+
process.env !== null && typeof process.env === 'object') {
122+
${gitMetadata.repositoryURL ? `process.env.DD_GIT_REPOSITORY_URL = '${gitMetadata.repositoryURL}';` : ''}
123+
${gitMetadata.commitSHA ? `process.env.DD_GIT_COMMIT_SHA = '${gitMetadata.commitSHA}';` : ''}
124+
}
125+
${build.initialOptions.banner.js}`
126+
127+
if (DEBUG) {
128+
console.log('Info: automatically injected git metadata:')
129+
console.log(`DD_GIT_REPOSITORY_URL: ${gitMetadata.repositoryURL || 'not available'}`)
130+
console.log(`DD_GIT_COMMIT_SHA: ${gitMetadata.commitSHA || 'not available'}`)
131+
}
132+
} else if (DEBUG) {
133+
console.warn('Warning: No git metadata available - skipping injection')
134+
}
135+
80136
build.onResolve({ filter: /.*/ }, args => {
81137
if (externalModules.has(args.path)) {
82138
// Internal Node.js packages will still be instrumented via require()

0 commit comments

Comments
 (0)