From b55fba64f32769a21b1df673abe5d8bcb0d31646 Mon Sep 17 00:00:00 2001 From: Serhiy Morenko Date: Thu, 9 Jul 2026 22:35:23 +0200 Subject: [PATCH 1/2] feat!: migrate to native ES modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - package.json: type=module, exports map, engines node>=22.12 - explicit .js extensions on all relative imports; tsconfig es2024 + skipLibCheck + verbatimModuleSyntax with import type annotations - host package.json reads (instrumentation name/version, traced pkg name) use readFileSync/JSON.parse instead of require() — no module semantics needed for JSON - spec: package manifest read via import.meta.url; the no-package.json fallback test loads a fresh module copy via query-busted dynamic import (the ES module registry is immutable) BREAKING CHANGE: package is ESM-only; CJS consumers require Node >=22.12 (require(esm)) and TypeScript >=5.8 to type-check Verified: 21/21 tests on Node 24.16.0, docker node:22 (22.23) and docker node:24 (24.18); CJS require(esm) interop smoke-tested --- index.ts | 15 +++++++++------ package.json | 13 ++++++++++++- src/enums/index.ts | 6 +++--- src/imq/types.ts | 2 +- src/index.ts | 6 +++--- src/instrumentation.ts | 19 +++++++++++++------ src/types.ts | 2 +- test/instrumentation.spec.ts | 30 +++++++++++++++++------------- tsconfig.json | 7 +++++-- 9 files changed, 64 insertions(+), 36 deletions(-) diff --git a/index.ts b/index.ts index 47fd754..2c14dd2 100644 --- a/index.ts +++ b/index.ts @@ -19,17 +19,18 @@ * purchase a proprietary commercial license. Please contact us at * to get commercial licensing options. */ -import { Span, trace, SpanKind, SpanStatusCode } from '@opentelemetry/api'; +import { readFileSync } from 'node:fs'; +import { type Span, trace, SpanKind, SpanStatusCode } from '@opentelemetry/api'; import * as path from 'path'; import { SpanNames, TraceKind, - TracedOptions, + type TracedOptions, AttributeNames, - TraceAttributes, -} from './src'; + type TraceAttributes, +} from './src/index.js'; -export * from './src/instrumentation'; +export * from './src/instrumentation.js'; const traces: { [name: string]: Span } = {}; const componentName = 'imq'; @@ -92,7 +93,9 @@ const DEFAULT_TRACED_OPTIONS: TracedOptions = { let pkgName = ''; try { - pkgName = require(`${path.resolve('.')}${path.sep}package.json`).name; + pkgName = JSON.parse( + readFileSync(`${path.resolve('.')}${path.sep}package.json`, 'utf8'), + ).name; } catch { /* ignore */ } diff --git a/package.json b/package.json index f979bb3..79a3ec2 100644 --- a/package.json +++ b/package.json @@ -50,5 +50,16 @@ "typescript": "^7.0.2" }, "main": "index.js", - "types": "index.d.ts" + "types": "index.d.ts", + "type": "module", + "engines": { + "node": ">=22.12.0" + }, + "exports": { + ".": { + "types": "./index.d.ts", + "default": "./index.js" + }, + "./package.json": "./package.json" + } } diff --git a/src/enums/index.ts b/src/enums/index.ts index b2ef6bd..0a3adce 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -19,6 +19,6 @@ * purchase a proprietary commercial license. Please contact us at * to get commercial licensing options. */ -export * from './TraceKind'; -export * from './AttributeNames'; -export * from './SpanNames'; +export * from './TraceKind.js'; +export * from './AttributeNames.js'; +export * from './SpanNames.js'; diff --git a/src/imq/types.ts b/src/imq/types.ts index b28fb62..3f1bbf9 100644 --- a/src/imq/types.ts +++ b/src/imq/types.ts @@ -13,7 +13,7 @@ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ -import { Span } from '@opentelemetry/api'; +import { type Span } from '@opentelemetry/api'; export interface IMQClient { name: string; diff --git a/src/index.ts b/src/index.ts index 047dbcb..fe026e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,6 @@ * purchase a proprietary commercial license. Please contact us at * to get commercial licensing options. */ -export * from './instrumentation'; -export * from './types'; -export * from './enums'; +export * from './instrumentation.js'; +export * from './types.js'; +export * from './enums/index.js'; diff --git a/src/instrumentation.ts b/src/instrumentation.ts index ade065b..3e93f0f 100644 --- a/src/instrumentation.ts +++ b/src/instrumentation.ts @@ -15,19 +15,24 @@ */ import { InstrumentationBase, - InstrumentationConfig, + type InstrumentationConfig, InstrumentationNodeModuleDefinition, } from '@opentelemetry/instrumentation'; -import { IMQClient, IMQRPCRequest, IMQServiceOptions } from './imq/types'; +import { + type IMQClient, + type IMQRPCRequest, + type IMQServiceOptions, +} from './imq/types.js'; import { context, propagation, SpanKind, trace, - Tracer, + type Tracer, } from '@opentelemetry/api'; -import { AttributeNames, SpanNames, TraceKind } from './enums'; -import path from 'path'; +import { AttributeNames, SpanNames, TraceKind } from './enums/index.js'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; let packageJson: { name: string; version: string }; let instrumentationName = '@imqueue/opentelemetry-instrumentation-imqueue'; @@ -37,7 +42,9 @@ const versions = ['>=1.10']; const componentName = 'imq'; try { - packageJson = require(`${path.resolve('.')}${path.sep}package.json`); + packageJson = JSON.parse( + readFileSync(`${path.resolve('.')}${path.sep}package.json`, 'utf8'), + ); instrumentationName = packageJson.name; instrumentationVersion = packageJson.version; } catch { diff --git a/src/types.ts b/src/types.ts index d987734..a8f435f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,7 +19,7 @@ * purchase a proprietary commercial license. Please contact us at * to get commercial licensing options. */ -import { TraceKind } from './enums'; +import { TraceKind } from './enums/index.js'; export interface TracedOptions { kind: TraceKind; diff --git a/test/instrumentation.spec.ts b/test/instrumentation.spec.ts index 377e54c..edba3f9 100644 --- a/test/instrumentation.spec.ts +++ b/test/instrumentation.spec.ts @@ -17,12 +17,15 @@ */ import { describe, it, type TestContext } from 'node:test'; import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { propagation, trace } from '@opentelemetry/api'; -import { IMQClient, IMQRPCRequest } from '../src/imq/types'; -import { ImqueueInstrumentation } from '..'; +import { type IMQClient, type IMQRPCRequest } from '../src/imq/types.js'; +import { ImqueueInstrumentation } from '../index.js'; -const self = require('../package.json'); +const self = JSON.parse( + readFileSync(new URL('../package.json', import.meta.url), 'utf8'), +); const client: IMQClient = { name: 'client-name', @@ -89,25 +92,26 @@ describe('ImqueueInstrumentation', () => { assert.equal(instrumentation.instrumentationVersion, self.version); }); - it('should fall back to defaults when no package.json exists', () => { + it('should fall back to defaults when no package.json exists', async () => { const cwd = process.cwd(); - const modulePath = require.resolve('../src/instrumentation'); - delete require.cache[modulePath]; process.chdir(tmpdir()); try { - // a fresh module load from a directory without package.json - // exercises the fallback branch - const { - ImqueueInstrumentation: Fallback, - } = require('../src/instrumentation'); + // a fresh, query-busted copy evaluates from a directory + // without package.json, exercising the fallback branch (the + // ES module registry is immutable, hence the unique URL) + const href = new URL( + '../src/instrumentation.js', + import.meta.url, + ).href; + const { ImqueueInstrumentation: Fallback } = await import( + `${href}?fallback=1` + ); assert.ok(new Fallback() instanceof Fallback); } finally { process.chdir(cwd); - delete require.cache[modulePath]; - require('../src/instrumentation'); } }); }); diff --git a/tsconfig.json b/tsconfig.json index e3bd55a..8b0e4ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,14 @@ { "compilerOptions": { - "target": "es2023", - "lib": ["es2023"], + "target": "es2024", + "lib": ["es2024"], "moduleDetection": "force", "module": "nodenext", "moduleResolution": "nodenext", "resolveJsonModule": true, "esModuleInterop": true, + "verbatimModuleSyntax": true, "types": ["node"], @@ -19,6 +20,8 @@ "removeComments": false, "newLine": "lf", + "skipLibCheck": true, + "strict": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true From e47e8a456521ed08135693837266b143cc02b6cb Mon Sep 17 00:00:00 2001 From: Serhiy Morenko Date: Thu, 9 Jul 2026 22:37:33 +0200 Subject: [PATCH 2/2] 3.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cce9f68..f5ee920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@imqueue/opentelemetry-instrumentation-imqueue", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@imqueue/opentelemetry-instrumentation-imqueue", - "version": "3.0.0", + "version": "3.1.0", "license": "GPL-3.0-only", "dependencies": { "@opentelemetry/api": "^1.9.1", diff --git a/package.json b/package.json index 79a3ec2..c45a86e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imqueue/opentelemetry-instrumentation-imqueue", - "version": "3.0.0", + "version": "3.1.0", "description": "This module provides OpenTelemetry instrumentation for @imqueue", "keywords": [ "imqueue",