Skip to content

Commit 3fc62be

Browse files
authored
chore: add scripts/gen-semconv-ts.js to help generate src/semconv.ts files for unstable semconv consts (#2669)
Refs: https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
1 parent d2a9a20 commit 3fc62be

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed

scripts/gen-semconv-ts.js

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env node
2+
/*
3+
* Copyright The OpenTelemetry Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* Generate a "src/semconv.ts" in the given workspace dir, which includes
20+
* copies of the unstable semconv definitions used in this package.
21+
* (It finds usages by looking for imports from
22+
* `@opentelemetry/semantic-conventions/incubating`.)
23+
*
24+
* This is to support the recommendation from
25+
* https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
26+
* that any usage of unstable Semantic Conventions definitions should be done
27+
* by *copying* those definitions to a local file in this package.
28+
*
29+
* Usage:
30+
* node scripts/gen-semconv-ts.js [WORKSPACE-DIR]
31+
*
32+
* If WORKSPACE-DIR is not given, it defaults to the current dir.
33+
*/
34+
35+
const fs = require('fs');
36+
const { execSync } = require('child_process');
37+
const path = require('path');
38+
const { globSync } = require('glob');
39+
const rimraf = require('rimraf');
40+
41+
const TOP = path.resolve(__dirname, '..');
42+
const BUILD_DIR = path.resolve(TOP, 'build', 'gen-semconv-ts');
43+
44+
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR?.length > 0;
45+
46+
let numWarns = 0;
47+
function warn(...args) {
48+
numWarns += 1;
49+
if (USE_COLOR) {
50+
process.stdout.write('\x1b[31m');
51+
}
52+
process.stdout.write('gen-semconv-ts warning: ');
53+
if (USE_COLOR) {
54+
process.stdout.write('\x1b[39m');
55+
}
56+
console.log(...args);
57+
}
58+
59+
function getAllWorkspaceDirs() {
60+
const pj = JSON.parse(
61+
fs.readFileSync(path.join(TOP, 'package.json'), 'utf8')
62+
);
63+
return pj.workspaces
64+
.map((wsGlob) => globSync(path.join(wsGlob, 'package.json')))
65+
.flat()
66+
.map(path.dirname);
67+
}
68+
69+
function genSemconvTs(wsDir) {
70+
const semconvPath = require.resolve('@opentelemetry/semantic-conventions',
71+
{paths: [path.join(wsDir, 'node_modules')]});
72+
const semconvStable = require(semconvPath);
73+
const semconvVer = require(path.resolve(semconvPath, '../../../package.json')).version;
74+
75+
// Gather unstable semconv imports. Consider any imports from
76+
// '@opentelemetry/semantic-conventions/incubating' or from an existing local
77+
// '.../semconv'.
78+
const srcFiles = globSync(path.join(wsDir, '{src,test}', '**', '*.ts'));
79+
const importRes = [
80+
/import\s+{([^}]*)}\s+from\s+'@opentelemetry\/semantic-conventions\/incubating'/s,
81+
/import\s+{([^}]*)}\s+from\s+'\.[^']*\/semconv'/s,
82+
];
83+
const names = new Set();
84+
for (const srcFile of srcFiles) {
85+
const srcText = fs.readFileSync(srcFile, 'utf8');
86+
for (const importRe of importRes) {
87+
const match = importRe.exec(srcText);
88+
if (match) {
89+
match[1].trim().split(/,/g).forEach(n => {
90+
n = n.trim();
91+
if (n) {
92+
if (semconvStable[n]) {
93+
warn(`${wsDir}/${srcFile}: '${n}' export is available on the stable "@opentelemetry/semantic-conventions" entry-point. This definition will not be included in the generated semconv.ts. Instead use:\n import { ${n} } from '@opentelemetry/semantic-conventions';`)
94+
} else {
95+
names.add(n);
96+
}
97+
}
98+
});
99+
}
100+
}
101+
}
102+
if (names.size === 0) {
103+
console.log(`Did not find any usage of unstable semconv exports in "${wsDir}/{src,test}/**/*.ts".`);
104+
console.log('No changes made.');
105+
return;
106+
} else {
107+
console.log(`Found import of ${names.size} unstable semconv definitions.`)
108+
}
109+
110+
// Find or get a
111+
let srcIsLocal = false;
112+
try {
113+
const gitRemoteUrl = execSync(`git -C "${wsDir}" remote get-url origin`, {encoding: 'utf8'}).trim();
114+
if (gitRemoteUrl.endsWith('/opentelemetry-js.git')) {
115+
srcIsLocal = true;
116+
}
117+
} catch {}
118+
119+
// Find or get semconv sources from a opentelemetry-js.git clone.
120+
let semconvSrcDir;
121+
if (srcIsLocal) {
122+
const gitRootDir = execSync(`git -C "${wsDir}" rev-parse --show-toplevel`, {encoding: 'utf8'}).trim();
123+
semconvSrcDir = path.join(gitRootDir, 'semantic-conventions');
124+
console.log(`Using local sources at "${semconvSrcDir}"`);
125+
} else {
126+
const tag = `semconv/v${semconvVer}`;
127+
console.log(`Cloning opentelemetry-js.git#${tag} to working dir "${BUILD_DIR}"`);
128+
rimraf.sync(BUILD_DIR);
129+
fs.mkdirSync(BUILD_DIR, { recursive: true });
130+
execSync(`git clone --depth 1 --branch ${tag} https://github.com/open-telemetry/opentelemetry-js.git`, {
131+
cwd: BUILD_DIR,
132+
stdio: 'ignore'
133+
});
134+
semconvSrcDir = path.join(BUILD_DIR, 'opentelemetry-js', 'semantic-conventions');
135+
console.log(`Using sources at "${semconvSrcDir}"`);
136+
}
137+
const srcPaths = globSync(path.join(semconvSrcDir, 'src', 'experimental_*.ts'));
138+
const src = srcPaths
139+
.map(f => fs.readFileSync(f))
140+
.join('\n\n');
141+
142+
const sortedNames = Array.from(names).sort();
143+
const chunks = [];
144+
for (let name of sortedNames) {
145+
const re = new RegExp(`^export const ${name} = .*;$`, 'm')
146+
const match = re.exec(src);
147+
if (!match) {
148+
throw new Error(`could not find "${name}" export in semconv build files: ${re} did not match in content from ${srcPaths.join(', ')}`);
149+
}
150+
151+
// Find a preceding block comment, if any.
152+
const WHITESPACE_CHARS = [' ', '\t', '\n', '\r'];
153+
let idx = match.index - 1;
154+
while (idx >=1 && WHITESPACE_CHARS.includes(src[idx])) {
155+
idx--;
156+
}
157+
if (src.slice(idx-1, idx+1) !== '*/') {
158+
// There is not a block comment preceding the export.
159+
chunks.push(match[0]);
160+
continue;
161+
}
162+
idx -= 2;
163+
while (idx >= 0) {
164+
if (src[idx] === '/' && src[idx+1] === '*') {
165+
// Found the start of the block comment.
166+
chunks.push(src.slice(idx, match.index) + match[0]);
167+
break;
168+
}
169+
idx--;
170+
}
171+
}
172+
173+
const semconvTsPath = path.join(wsDir, 'src', 'semconv.ts');
174+
fs.writeFileSync(
175+
semconvTsPath,
176+
`/*
177+
* Copyright The OpenTelemetry Authors
178+
*
179+
* Licensed under the Apache License, Version 2.0 (the "License");
180+
* you may not use this file except in compliance with the License.
181+
* You may obtain a copy of the License at
182+
*
183+
* https://www.apache.org/licenses/LICENSE-2.0
184+
*
185+
* Unless required by applicable law or agreed to in writing, software
186+
* distributed under the License is distributed on an "AS IS" BASIS,
187+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
188+
* See the License for the specific language governing permissions and
189+
* limitations under the License.
190+
*/
191+
192+
/*
193+
* This file contains a copy of unstable semantic convention definitions
194+
* used by this package.
195+
* @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
196+
*/
197+
198+
${chunks.join('\n\n')}
199+
`,
200+
{ encoding: 'utf8' }
201+
);
202+
console.log(`Generated "${semconvTsPath}".`);
203+
204+
console.log(`Running 'npx eslint --fix src/semconv.ts' to fix formatting.`);
205+
execSync(`npx eslint --fix src/semconv.ts`, { cwd: wsDir });
206+
}
207+
208+
// mainline
209+
const wsDir = process.argv[2] || '.';
210+
genSemconvTs(wsDir);
211+
if (numWarns > 0) {
212+
process.exitCode = 1;
213+
}

0 commit comments

Comments
 (0)