Skip to content

Commit

Permalink
chore: add 'lint:semconv-deps'
Browse files Browse the repository at this point in the history
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: open-telemetry#2549 (comment)
Refs: open-telemetry/opentelemetry-js#5182
  • Loading branch information
trentm committed Nov 30, 2024
1 parent 0fc1ed3 commit 6f688f4
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 2 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
"test:ci:changed": "nx affected -t test --base=origin/main --head=HEAD",
"test-all-versions": "nx run-many -t test-all-versions",
"changelog": "lerna-changelog",
"lint": "nx run-many -t lint && npm run lint:readme && npm run lint:markdown",
"lint": "nx run-many -t lint && npm run lint:readme && npm run lint:markdown && npm run lint:semconv-deps",
"lint:fix": "nx run-many -t lint:fix && npm run lint:markdown:fix",
"lint:deps": "npx --yes [email protected] --dependencies --production --tags=-knipignore",
"lint:examples": "eslint ./examples/**/*.js",
"lint:examples:fix": "eslint ./examples/**/*.js --fix",
"lint:markdown": "markdownlint-cli2 $(git ls-files '*.md')",
"lint:markdown:fix": "markdownlint-cli2 --fix $(git ls-files '*.md')",
"lint:readme": "nx run-many -t lint:readme"
"lint:readme": "nx run-many -t lint:readme",
"lint:semconv-deps": "./scripts/lint-semconv-deps.mjs"
},
"keywords": [
"opentelemetry",
Expand Down
109 changes: 109 additions & 0 deletions scripts/lint-semconv-deps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env node
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Lint the usage of `@opentelemetry/semantic-conventions` in packages in
* the workspace.
*
* See "Rule:" comments for things that are checked.
*
* Usage:
* node scripts/lint-semconv-deps.js
*/

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { globSync } from 'glob';

const TOP = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
const SEMCONV = '@opentelemetry/semantic-conventions';
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR?.length > 0;

function problem(...args) {
if (USE_COLOR) {
process.stdout.write('\x1b[31m');
}
args.unshift('lint-semconv-deps error:')
console.log(...args);
if (USE_COLOR) {
process.stdout.write('\x1b[39m');
}
}

function getAllWorkspaceDirs() {
const pj = JSON.parse(
fs.readFileSync(path.join(TOP, 'package.json'), 'utf8')
);
return pj.workspaces
.map((wsGlob) => globSync(path.join(wsGlob, 'package.json')))
.flat()
.map(path.dirname);
}

function lintSemconvDeps() {
let numProbs = 0;
const wsDirs = getAllWorkspaceDirs();

for (let wsDir of wsDirs) {
const pj = JSON.parse(
fs.readFileSync(path.join(wsDir, 'package.json'), 'utf8')
);
const depRange = pj?.dependencies?.[SEMCONV];
if (!depRange) {
continue;
}

// Is incubating entry-point in use?
const srcFiles = globSync(path.join(wsDir, 'src', '**', '*.ts'));
let usesIncubating = false;
const usesIncubatingRe = /import \{?[^\{]* from '@opentelemetry\/semantic-conventions\/incubating'/s;
for (let srcFile of srcFiles) {
const srcText = fs.readFileSync(srcFile, 'utf8');
const match = usesIncubatingRe.exec(srcText);
if (match) {
usesIncubating = true;
break;
}
}

// Rule: If the semconv "incubating" entry-point is used, then the dep
// should be pinned. Otherwise it should not be pinned.
const pinnedVerRe = /^\d+\.\d+\.\d+$/;
const pins = Boolean(pinnedVerRe.exec(depRange));
if (usesIncubating) {
if (!pins) {
problem(`package ${pj.name} (in ${wsDir}) imports "${SEMCONV}/incubating" but does not *pin* the dependency: \`"${SEMCONV}": "${depRange}"\``);
numProbs += 1;
}
} else {
if (pins) {
problem(`package ${pj.name} (in ${wsDir}) does not import "${SEMCONV}/incubating" but pins the dependency: \`"${SEMCONV}": "${depRange}"\` (it could use a caret-range)`);
numProbs += 1;
}
}
}

return numProbs;
}

// mainline
const numProbs = await lintSemconvDeps();
if (numProbs > 0) {
process.exitCode = 1;
}

0 comments on commit 6f688f4

Please sign in to comment.