Skip to content

Commit 1685420

Browse files
authored
chore(bitrot): add checking of instrumentations not supporting the 'latest' release of a package (#3167)
The bitrot script compares the 'Supported Versions' range from the README to the 'latest' version published to npm. The value from the README is used because that was easiest -- gleaning the package and version range from the instrumentation code is not straightforward.
1 parent 7ffc32f commit 1685420

File tree

6 files changed

+132
-6
lines changed

6 files changed

+132
-6
lines changed

packages/instrumentation-fastify/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ npm install --save @opentelemetry/instrumentation-http @opentelemetry/instrument
2323

2424
### Supported Versions
2525

26-
- [`fastify`](https://www.npmjs.com/package/fastify) versions `>=3.0.0 <5`
26+
- [`fastify`](https://www.npmjs.com/package/fastify) versions `>=3.0.0 <6`
2727

2828
## Usage
2929

packages/instrumentation-lru-memoizer/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ npm install --save @opentelemetry/instrumentation-lru-memoizer
1717

1818
## Supported Versions
1919

20-
- [`lru-memorizer`](https://www.npmjs.com/package/lru-memoizer) versions `>=1.3.0 <3`
20+
- [`lru-memoizer`](https://www.npmjs.com/package/lru-memoizer) versions `>=1.3.0 <3`
2121

2222
## Usage
2323

packages/instrumentation-openai/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ npm install --save @opentelemetry/instrumentation-openai
1515

1616
## Supported Versions
1717

18-
- openai `>=4.19.0 <7`
18+
- [`openai`](https://www.npmjs.com/package/openai) versions `>=4.19.0 <7`
1919

2020
## Usage
2121

packages/instrumentation-pino/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ npm install --save @opentelemetry/instrumentation-pino
1717

1818
## Supported Versions
1919

20-
- [`pino`](https://www.npmjs.com/package/pino) versions `>=5.14.0 <10`
20+
- [`pino`](https://www.npmjs.com/package/pino) versions `>=5.14.0 <11`
2121
- The "log sending" feature is only supported in pino v7 and later.
2222

2323
## Usage

packages/instrumentation-typeorm/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ Compatible with OpenTelemetry JS API `^1.3.0` and SDK `2.0+`.
1515
npm install --save @opentelemetry/instrumentation-typeorm
1616
```
1717

18-
### Supported versions
18+
## Supported versions
1919

20-
- `>=0.3.0 <1`
20+
- [`typeorm`](https://www.npmjs.com/package/typeorm) versions `>=0.3.0 <1`
2121

2222
## Usage
2323

scripts/bitrot.mjs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import * as fs from 'fs';
3232
import * as path from 'path';
3333
import { fileURLToPath } from 'url';
3434
import { globSync } from 'glob';
35+
import { satisfies } from 'semver';
3536

3637
const TOP = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..');
3738
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR?.length > 0;
@@ -50,6 +51,17 @@ function problem(...args) {
5051
}
5152
}
5253

54+
function warn(...args) {
55+
if (USE_COLOR) {
56+
process.stdout.write('\x1b[33m');
57+
}
58+
args.unshift('bitrot warn:');
59+
console.warn(...args);
60+
if (USE_COLOR) {
61+
process.stdout.write('\x1b[39m');
62+
}
63+
}
64+
5365
function gitCloneSync(repo, dir) {
5466
execSync(`git clone ${repo} "${dir}"`)
5567
}
@@ -108,8 +120,122 @@ function bitrotRenovateCoreExperimental() {
108120
}
109121
}
110122

123+
function getNpmInfo(name) {
124+
const CACHE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
125+
const cache = ensureCacheLoaded('npmInfo');
126+
const cacheEntry = cache[name];
127+
if (cacheEntry) {
128+
if (cacheEntry.timestamp + CACHE_TIMEOUT_MS > Date.now()) {
129+
return cacheEntry.value;
130+
} else {
131+
delete cache[name];
132+
}
133+
}
134+
135+
// Limited security guard on exec'ing given `name`.
136+
const PKG_NAME_RE = /^(@[\w_.-]+\/)?([\w_.-]+)$/;
137+
if (!PKG_NAME_RE.test(name)) {
138+
throw new Error(
139+
`${JSON.stringify(name)} does not look like a valid npm package name`,
140+
);
141+
}
142+
143+
const stdout = execSync(`npm info -j "${name}"`);
144+
const npmInfo = JSON.parse(stdout);
145+
146+
cache[name] = {
147+
timestamp: Date.now(),
148+
value: npmInfo,
149+
};
150+
saveCache();
151+
return npmInfo;
152+
}
153+
154+
/**
155+
* BITROT: Check if instrumentations are missing support for major releases of
156+
* the target package.
157+
*
158+
* Limitations:
159+
* - This doesn't currently handle both `pg` and `pg-pool` from instr-pg.
160+
* - This doesn't currently support instr-aws-sdk, because of the wildcard in
161+
* the packages supported: `@aws-sdk/client-*`.
162+
*/
163+
function bitrotInstrumentations() {
164+
const instrReadmes = globSync(path.join(TOP, 'packages/instrumentation-*/README.md'));
165+
166+
// Match examples:
167+
// - [`undici`](https://www.npmjs.com/package/undici) version `>=5.12.0`
168+
// - [`pino`](https://www.npmjs.com/package/pino) versions `>=5.14.0 <10`
169+
// - [`tedious`](https://www.npmjs.com/package/tedious) `>=1.11.0 <20`
170+
// - [socket.io](https://www.npmjs.com/package/socket.io) versions `>=2.0.0 <5`
171+
const supVerRe = /^###? Supported Versions\n\n*^- \[`?([\w\.@\/-]+)`?\].* `(.*?)`$/mi
172+
173+
// Skip some instrumentations for which the supported version range is N/A.
174+
const SKIP_INSTRS = [
175+
'user-interaction',
176+
'long-task',
177+
'document-load',
178+
'runtime-node',
179+
'dns',
180+
'net',
181+
'fs',
182+
'redis-4',
183+
'aws-sdk',
184+
'aws-lambda',
185+
]
186+
187+
for (const readmePath of instrReadmes) {
188+
const instrName = path.basename(path.dirname(readmePath)).slice('instrumentation-'.length)
189+
if (SKIP_INSTRS.includes(instrName)) {
190+
continue;
191+
}
192+
const content = fs.readFileSync(readmePath, 'utf8');
193+
const match = supVerRe.exec(content)
194+
if (match) delete match.input;
195+
if (!match) {
196+
warn(`could not determine supported versions from "packages/instrumentation-${instrName}/README.md`);
197+
continue;
198+
}
199+
const pkgName = match[1];
200+
const supVerRange = match[2];
201+
202+
const npmInfo = getNpmInfo(pkgName);
203+
const latestVer = npmInfo['dist-tags'].latest;
204+
if (!satisfies(latestVer, supVerRange)) {
205+
problem(`packages/instrumentation-${instrName}/README.md: the "latest" version of \`${pkgName}\` (${latestVer}) is not supported: the supported range is \`${supVerRange}\``);
206+
}
207+
}
208+
}
209+
111210
function bitrot() {
112211
bitrotRenovateCoreExperimental();
212+
bitrotInstrumentations();
213+
}
214+
215+
// ---- caching
216+
217+
const gCachePath = path.join(BUILD_DIR, 'bitrot.cache.json');
218+
let gCache = null;
219+
220+
function ensureCacheLoaded(ns) {
221+
if (gCache === null) {
222+
try {
223+
gCache = JSON.parse(fs.readFileSync(gCachePath));
224+
} catch (loadErr) {
225+
gCache = {};
226+
}
227+
}
228+
if (!(ns in gCache)) {
229+
gCache[ns] = {};
230+
}
231+
return gCache[ns];
232+
}
233+
234+
function saveCache() {
235+
if (gCache !== null) {
236+
fs.mkdirSync(path.dirname(gCachePath), { recursive: true });
237+
fs.writeFileSync(gCachePath, JSON.stringify(gCache, null, 2));
238+
}
113239
}
114240

115241
// ---- mainline

0 commit comments

Comments
 (0)