diff --git a/.github/actions/node/action.yml b/.github/actions/node/action.yml index 7fd4acecc0..c1513f2c5f 100644 --- a/.github/actions/node/action.yml +++ b/.github/actions/node/action.yml @@ -18,7 +18,8 @@ runs: - if: steps.attempt.outcome == 'failure' shell: bash run: sleep 60 - - if: steps.attempt.outcome == 'failure' + - id: retry + if: steps.attempt.outcome == 'failure' uses: ./.github/actions/node/setup with: version: ${{ inputs.version }} diff --git a/.github/actions/node/setup/action.yml b/.github/actions/node/setup/action.yml index 0cb6136972..745c7ba6fd 100644 --- a/.github/actions/node/setup/action.yml +++ b/.github/actions/node/setup/action.yml @@ -27,7 +27,8 @@ runs: } case "$VERSION" in eol) version=16 ;; - oldest) version=$(node_version 18) ;; + oldest) oldest_major=$(awk '/"node"/{match($0,/[0-9]+/);print substr($0,RSTART,RLENGTH);exit}' package.json) + version=$(node_version "$oldest_major") ;; maintenance) version=$(node_version 20) ;; active) version=$(node_version 24) ;; latest) version=${LATEST_VERSION:-$(node_version 26)} diff --git a/.github/workflows/apm-integrations.yml b/.github/workflows/apm-integrations.yml index b1357f682e..fbcc1bd4ae 100644 --- a/.github/workflows/apm-integrations.yml +++ b/.github/workflows/apm-integrations.yml @@ -58,6 +58,7 @@ jobs: aerospike-version: ce-6.4.0.3 image: aerospike@sha256:f268d48f5075010ff268b29fbae28518418070dfb31935daa31f0b45c59f4da9 # ce-6.4.0.3 test-image: ubuntu-latest + runs-on: ${{ matrix.test-image }} permissions: id-token: write @@ -332,7 +333,7 @@ jobs: - "^3.0.7" - ">=4.0.0 <4.2.0" include: - - node-version: 18 + - node-version: oldest range: ">=4.2.0" runs-on: ubuntu-latest permissions: @@ -1122,7 +1123,7 @@ jobs: - node-version: 18 range: ">=6.1.0 <7.0.0" range_clean: gte.6.16.0.and.lt.7.0.0 - - node-version: "latest" + - node-version: latest range: "" range_clean: all runs-on: ubuntu-latest diff --git a/.nvmrc b/.nvmrc index 3c032078a4..2bd5a0a98a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18 +22 diff --git a/integration-tests/init.spec.js b/integration-tests/init.spec.js index 7934012057..965c604d9d 100644 --- a/integration-tests/init.spec.js +++ b/integration-tests/init.spec.js @@ -14,7 +14,7 @@ const telemetryAbort = ['abort', 'reason:incompatible_runtime', 'abort.runtime', const telemetryForced = ['complete', 'injection_forced:true'] const telemetryGood = ['complete', 'injection_forced:false'] -const { engines } = require('../package.json') +const { engines, nodeMaxMajor: MAX_NODE_MAJOR } = require('../package.json') const { runAndCheckWithTelemetry: testFile, useEnv, @@ -122,15 +122,15 @@ function testRuntimeVersionChecks (arg, filename) { it('should be able to use the engines field', () => { const engines = require(`${sandboxCwd()}/node_modules/dd-trace/package.json`).engines.node - assert.match(engines, /^>=\d+ <\d+$/) + assert.match(engines, /^>=\d+$/) }) - context('when node version is too recent', () => { + context('when node version is too old', () => { useEnv({ NODE_OPTIONS }) before(() => { const pkg = JSON.parse(pkgStr) - pkg.engines.node = `>=${NODE_MAJOR - 1} <${NODE_MAJOR}` + pkg.engines.node = `>=${NODE_MAJOR + 1}` fs.writeFileSync(pkgPath, JSON.stringify(pkg)) }) @@ -151,14 +151,14 @@ function testRuntimeVersionChecks (arg, filename) { it('should not initialize the tracer', () => doTest(`Aborting application instrumentation due to incompatible_runtime. Found incompatible runtime Node.js ${process.versions.node}, Supported runtimes: Node.js \ ->=${NODE_MAJOR - 1} <${NODE_MAJOR}. +>=${NODE_MAJOR + 1} <${MAX_NODE_MAJOR}. false `, telemetryAbort)) it('should initialize the tracer, if DD_INJECT_FORCE', () => doTestForced(`Aborting application instrumentation due to incompatible_runtime. Found incompatible runtime Node.js ${process.versions.node}, Supported runtimes: Node.js \ ->=${NODE_MAJOR - 1} <${NODE_MAJOR}. +>=${NODE_MAJOR + 1} <${MAX_NODE_MAJOR}. DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing. Application instrumentation bootstrapping complete true @@ -167,12 +167,12 @@ true }) }) - context('when node version is too old', () => { + context('when node version is too recent', () => { useEnv({ NODE_OPTIONS }) before(() => { const pkg = JSON.parse(pkgStr) - pkg.engines.node = `>=${NODE_MAJOR + 1} <${NODE_MAJOR + 2}` + pkg.nodeMaxMajor = NODE_MAJOR fs.writeFileSync(pkgPath, JSON.stringify(pkg)) }) @@ -193,14 +193,14 @@ true it('should not initialize the tracer', () => doTest(`Aborting application instrumentation due to incompatible_runtime. Found incompatible runtime Node.js ${process.versions.node}, Supported runtimes: Node.js \ ->=${NODE_MAJOR + 1} <${NODE_MAJOR + 2}. +${engines.node} <${NODE_MAJOR}. false `, telemetryAbort)) it('should initialize the tracer, if DD_INJECT_FORCE', () => doTestForced(`Aborting application instrumentation due to incompatible_runtime. Found incompatible runtime Node.js ${process.versions.node}, Supported runtimes: Node.js \ ->=${NODE_MAJOR + 1} <${NODE_MAJOR + 2}. +${engines.node} <${NODE_MAJOR}. DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing. Application instrumentation bootstrapping complete true @@ -215,7 +215,8 @@ true before(() => { const pkg = JSON.parse(pkgStr) - pkg.engines.node = '>=0 <1000' + pkg.engines.node = '>=0' + pkg.nodeMaxMajor = 1000 fs.writeFileSync(pkgPath, JSON.stringify(pkg)) }) diff --git a/package.json b/package.json index 60396a3b4d..b7a7972e6d 100644 --- a/package.json +++ b/package.json @@ -130,8 +130,9 @@ }, "homepage": "https://github.com/DataDog/dd-trace-js#readme", "engines": { - "node": ">=18 <27" + "node": ">=18" }, + "nodeMaxMajor": 27, "files": [ "/package.json", "ci/**/*", diff --git a/packages/dd-trace/src/guardrails/index.js b/packages/dd-trace/src/guardrails/index.js index eb8a30ecb9..08e94d470c 100644 --- a/packages/dd-trace/src/guardrails/index.js +++ b/packages/dd-trace/src/guardrails/index.js @@ -14,11 +14,13 @@ function guard (fn) { var initBailout = false var clobberBailout = false var forced = isTrue(process.env.DD_INJECT_FORCE) - var engines = require('../../../../package.json').engines - var versions = engines.node.match(/^>=(\d+) <(\d+)$/) + var pkg = require('../../../../package.json') + var engines = pkg.engines + var versions = engines.node.match(/^>=(\d+)$/) var minMajor = versions[1] - var nextMajor = versions[2] + var nextMajor = pkg.nodeMaxMajor var version = process.versions.node + var supportedRange = engines.node + ' <' + nextMajor if (process.env.DD_INJECTION_ENABLED) { // If we're running via single-step install, and we're in the app's @@ -45,16 +47,17 @@ function guard (fn) { // should not initialize the tracer. if (!clobberBailout && (NODE_MAJOR < minMajor || NODE_MAJOR >= nextMajor)) { initBailout = true + var runtimeInfo = 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + supportedRange telemetry([ { name: 'abort', tags: ['reason:incompatible_runtime'] }, { name: 'abort.runtime', tags: [] } ], undefined, { result: 'abort', result_class: 'incompatible_runtime', - result_reason: 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + engines.node + result_reason: runtimeInfo }) log.info('Aborting application instrumentation due to incompatible_runtime.') - log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, engines.node) + log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, supportedRange) if (forced) { log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.') } diff --git a/packages/dd-trace/test/guardrails/index.spec.js b/packages/dd-trace/test/guardrails/index.spec.js new file mode 100644 index 0000000000..6f1e0f75ad --- /dev/null +++ b/packages/dd-trace/test/guardrails/index.spec.js @@ -0,0 +1,38 @@ +'use strict' + +const assert = require('node:assert/strict') +const { after, before, describe, it } = require('mocha') +const proxyquire = require('proxyquire') + +const { NODE_MAJOR } = require('../../../../version') + +describe('guard', () => { + let originalInjectionEnabled + + before(() => { + originalInjectionEnabled = process.env.DD_INJECTION_ENABLED + delete process.env.DD_INJECTION_ENABLED + }) + + after(() => { + if (originalInjectionEnabled !== undefined) { + process.env.DD_INJECTION_ENABLED = originalInjectionEnabled + } + }) + + it('should abort when node version exceeds nodeMaxMajor', () => { + const guard = proxyquire('../../src/guardrails/index', { + '../../../../package.json': { + engines: { node: `>=${NODE_MAJOR}` }, + nodeMaxMajor: NODE_MAJOR, + }, + './log': { info: () => {} }, + './telemetry': () => {}, + }) + + let called = false + const result = guard(() => { called = true }) + assert.strictEqual(called, false) + assert.strictEqual(result, undefined) + }) +}) diff --git a/packages/dd-trace/test/setup/mocha.js b/packages/dd-trace/test/setup/mocha.js index bd98250967..1618ee3e68 100644 --- a/packages/dd-trace/test/setup/mocha.js +++ b/packages/dd-trace/test/setup/mocha.js @@ -13,6 +13,8 @@ const semver = require('semver') const sinon = require('sinon') require('./core') +const { engines, nodeMaxMajor } = require('../../../../package.json') + const externals = require('../plugins/externals') const runtimeMetrics = require('../../src/runtime_metrics') const Nomenclature = require('../../src/service-naming') @@ -258,6 +260,9 @@ function withVersions (plugin, modules, range, cb) { range = undefined } + if (!process.env.DD_INJECT_FORCE && + !semver.satisfies(process.version, `${engines.node} <${nodeMaxMajor}`)) return + const instrumentations = typeof plugin === 'string' ? getInstrumentation(plugin) : [plugin] const names = new Set(instrumentations.map(instrumentation => instrumentation.name)) diff --git a/scripts/generate-supported-integrations.js b/scripts/generate-supported-integrations.js index 99906e046e..f8958cd156 100644 --- a/scripts/generate-supported-integrations.js +++ b/scripts/generate-supported-integrations.js @@ -200,8 +200,12 @@ function toCsv (rows) { } async function generateSupportedIntegrations () { + const pkg = JSON.parse(readFileSync(ROOT_PACKAGE, 'utf8')) const plugins = readPluginMap() - const engines = parseEnginesRange(JSON.parse(readFileSync(ROOT_PACKAGE, 'utf8')).engines.node) + const engines = parseEnginesRange(pkg.engines.node) + if (engines.maxMajor === undefined && pkg.nodeMaxMajor !== undefined) { + engines.maxMajor = pkg.nodeMaxMajor - 1 + } const ranges = readInstrumentationRanges(engines) const versions = JSON.parse(readFileSync(VERSIONS_PACKAGE, 'utf8')).dependencies ?? {}