Skip to content

Commit 6f688f4

Browse files
committed
chore: add 'lint:semconv-deps'
This adds a lint step that checks that packages in the workspace are following our 'rule' that uses of the semconv pkg 'incubating' entry-point should *pin* the '@opentelemetry/semantic-conventions' dep. This is because (though rare) the incubating/unstable semconv exports can have breaking changes in minors. Refs: #2549 (comment) Refs: open-telemetry/opentelemetry-js#5182
1 parent 0fc1ed3 commit 6f688f4

File tree

2 files changed

+112
-2
lines changed

2 files changed

+112
-2
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
"test:ci:changed": "nx affected -t test --base=origin/main --head=HEAD",
2121
"test-all-versions": "nx run-many -t test-all-versions",
2222
"changelog": "lerna-changelog",
23-
"lint": "nx run-many -t lint && npm run lint:readme && npm run lint:markdown",
23+
"lint": "nx run-many -t lint && npm run lint:readme && npm run lint:markdown && npm run lint:semconv-deps",
2424
"lint:fix": "nx run-many -t lint:fix && npm run lint:markdown:fix",
2525
"lint:deps": "npx --yes [email protected] --dependencies --production --tags=-knipignore",
2626
"lint:examples": "eslint ./examples/**/*.js",
2727
"lint:examples:fix": "eslint ./examples/**/*.js --fix",
2828
"lint:markdown": "markdownlint-cli2 $(git ls-files '*.md')",
2929
"lint:markdown:fix": "markdownlint-cli2 --fix $(git ls-files '*.md')",
30-
"lint:readme": "nx run-many -t lint:readme"
30+
"lint:readme": "nx run-many -t lint:readme",
31+
"lint:semconv-deps": "./scripts/lint-semconv-deps.mjs"
3132
},
3233
"keywords": [
3334
"opentelemetry",

scripts/lint-semconv-deps.mjs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
* Lint the usage of `@opentelemetry/semantic-conventions` in packages in
20+
* the workspace.
21+
*
22+
* See "Rule:" comments for things that are checked.
23+
*
24+
* Usage:
25+
* node scripts/lint-semconv-deps.js
26+
*/
27+
28+
import fs from 'fs';
29+
import path from 'path';
30+
import { fileURLToPath } from 'url';
31+
import { globSync } from 'glob';
32+
33+
const TOP = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
34+
const SEMCONV = '@opentelemetry/semantic-conventions';
35+
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR?.length > 0;
36+
37+
function problem(...args) {
38+
if (USE_COLOR) {
39+
process.stdout.write('\x1b[31m');
40+
}
41+
args.unshift('lint-semconv-deps error:')
42+
console.log(...args);
43+
if (USE_COLOR) {
44+
process.stdout.write('\x1b[39m');
45+
}
46+
}
47+
48+
function getAllWorkspaceDirs() {
49+
const pj = JSON.parse(
50+
fs.readFileSync(path.join(TOP, 'package.json'), 'utf8')
51+
);
52+
return pj.workspaces
53+
.map((wsGlob) => globSync(path.join(wsGlob, 'package.json')))
54+
.flat()
55+
.map(path.dirname);
56+
}
57+
58+
function lintSemconvDeps() {
59+
let numProbs = 0;
60+
const wsDirs = getAllWorkspaceDirs();
61+
62+
for (let wsDir of wsDirs) {
63+
const pj = JSON.parse(
64+
fs.readFileSync(path.join(wsDir, 'package.json'), 'utf8')
65+
);
66+
const depRange = pj?.dependencies?.[SEMCONV];
67+
if (!depRange) {
68+
continue;
69+
}
70+
71+
// Is incubating entry-point in use?
72+
const srcFiles = globSync(path.join(wsDir, 'src', '**', '*.ts'));
73+
let usesIncubating = false;
74+
const usesIncubatingRe = /import \{?[^\{]* from '@opentelemetry\/semantic-conventions\/incubating'/s;
75+
for (let srcFile of srcFiles) {
76+
const srcText = fs.readFileSync(srcFile, 'utf8');
77+
const match = usesIncubatingRe.exec(srcText);
78+
if (match) {
79+
usesIncubating = true;
80+
break;
81+
}
82+
}
83+
84+
// Rule: If the semconv "incubating" entry-point is used, then the dep
85+
// should be pinned. Otherwise it should not be pinned.
86+
const pinnedVerRe = /^\d+\.\d+\.\d+$/;
87+
const pins = Boolean(pinnedVerRe.exec(depRange));
88+
if (usesIncubating) {
89+
if (!pins) {
90+
problem(`package ${pj.name} (in ${wsDir}) imports "${SEMCONV}/incubating" but does not *pin* the dependency: \`"${SEMCONV}": "${depRange}"\``);
91+
numProbs += 1;
92+
}
93+
} else {
94+
if (pins) {
95+
problem(`package ${pj.name} (in ${wsDir}) does not import "${SEMCONV}/incubating" but pins the dependency: \`"${SEMCONV}": "${depRange}"\` (it could use a caret-range)`);
96+
numProbs += 1;
97+
}
98+
}
99+
}
100+
101+
return numProbs;
102+
}
103+
104+
// mainline
105+
const numProbs = await lintSemconvDeps();
106+
if (numProbs > 0) {
107+
process.exitCode = 1;
108+
}
109+

0 commit comments

Comments
 (0)