Skip to content

Commit fcaaf43

Browse files
authored
Add Node.js 26 support; drop 25 (#3450)
* Sanitize line numbers for lib/ files in reporter logs * Remove v25 snapshots * Test with Node.js 26 External assertion snapshot cannot be updated, since AVA 8 emits corrupt snapshots with Node.js 26. * Update support statement now that odd-numbered Node.js releases will become LTS * Upgrade to cbor2 for Node.js 26 snapshot compatibility * Skip tests failing due to Node.js 26 bug
1 parent a913804 commit fcaaf43

33 files changed

Lines changed: 110 additions & 143 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
node-version: [^22.20, ^24.12, ^25]
21+
node-version: [^22.20, ^24.12, ^26]
2222
os: [ubuntu-latest, windows-latest, macos-latest]
2323
steps:
2424
- uses: actions/checkout@v6

docs/support-statement.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ AVA supports the latest release of any major version that [is supported by Node.
88

99
When we drop support for an LTS-covered major version we will bump AVA's major version number.
1010

11-
We will drop support for odd-numbered Node.js versions (e.g. `21` or `23`) *without* bumping AVA's major version number.
12-
1311
We try to avoid *accidentally* dropping support for non-latest Node.js releases. If such breakage does occur we'll accept pull requests to restore functionality. We might decide to deprecate the offending AVA release and bump AVA's major version number instead.
1412

1513
Whenever we bump AVA's major version number, we *will* explicitly drop support for non-latest Node.js releases. This ensures we can rely on backported APIs or the availability of newer V8 releases in later Node.js versions, either in AVA itself or one of our dependencies.

lib/snapshot-manager.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import path from 'node:path';
66
import {fileURLToPath} from 'node:url';
77
import zlib from 'node:zlib';
88

9-
import cbor from 'cbor';
9+
import {decode as decodeCbor, encode as encodeCbor, TypeEncoderMap} from 'cbor2';
10+
import {writeArray, writeUint8Array} from 'cbor2/encoder';
11+
import {sortLengthFirstDeterministic} from 'cbor2/sorts';
1012
import concordance from 'concordance';
1113
import indentString from 'indent-string';
1214
import memoize from 'memoize';
@@ -92,7 +94,7 @@ function formatEntry(snapshot, index) {
9294
} = snapshot;
9395

9496
const description = data
95-
? concordance.formatDescriptor(concordance.deserialize(data), concordanceOptions)
97+
? concordance.formatDescriptor(concordance.deserialize(Buffer.from(data.buffer, data.byteOffset, data.byteLength)), concordanceOptions)
9698
: '<No Data>';
9799

98100
const blockquote = label.split(/\n/).map(line => '> ' + line).join('\n');
@@ -180,10 +182,36 @@ function sortBlocks(blocksByTitle, blockIndices) {
180182
});
181183
}
182184

183-
async function encodeSnapshots(snapshotData) {
184-
const encoded = await cbor.encodeAsync(snapshotData, {
185-
omitUndefinedProperties: true,
186-
canonical: true,
185+
function normalizeBeforeEncoding(value) {
186+
if (Array.isArray(value)) {
187+
return value.map(v => normalizeBeforeEncoding(v));
188+
}
189+
190+
// FIXME: The decode code path unexpectedly returns Buffers not Uint8Arrays.
191+
if (Buffer.isBuffer(value)) {
192+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
193+
}
194+
195+
if (value !== null && typeof value === 'object' && !(value instanceof Uint8Array)) {
196+
return Object.fromEntries(Object.entries(value)
197+
.filter(([, v]) => v !== undefined)
198+
.map(([k, v]) => [k, normalizeBeforeEncoding(v)]));
199+
}
200+
201+
return value;
202+
}
203+
204+
// FIXME: This seems to be a bug in cbor2, ignoreGlobalTags shouldn't break (Uint8)Array encoding.
205+
const types = new TypeEncoderMap();
206+
types.registerEncoder(Array, writeArray);
207+
types.registerEncoder(Uint8Array, writeUint8Array);
208+
209+
function encodeSnapshots(snapshotData) {
210+
const encoded = encodeCbor(normalizeBeforeEncoding(snapshotData), {
211+
ignoreGlobalTags: true,
212+
rejectUndefined: true,
213+
sortKeys: sortLengthFirstDeterministic,
214+
types,
187215
});
188216
const compressed = zlib.gzipSync(encoded);
189217
compressed[9] = 0x03; // Override the GZip header containing the OS to always be Linux
@@ -233,7 +261,9 @@ function decodeSnapshots(buffer, snapPath) {
233261
}
234262

235263
const decompressed = zlib.gunzipSync(compressed);
236-
return cbor.decode(decompressed);
264+
return decodeCbor(decompressed, {
265+
ignoreGlobalTags: true,
266+
});
237267
}
238268

239269
class Manager {
@@ -282,7 +312,7 @@ class Manager {
282312
return {pass: true};
283313
}
284314

285-
const actual = concordance.deserialize(data, concordanceOptions);
315+
const actual = concordance.deserialize(Buffer.from(data.buffer, data.byteOffset, data.byteLength), concordanceOptions);
286316
const expected = concordance.describe(options.expected, concordanceOptions);
287317
const pass = concordance.compareDescriptors(actual, expected);
288318

@@ -311,7 +341,8 @@ class Manager {
311341
deferRecord(options) {
312342
const {expected, belongsTo, label, index} = options;
313343
const descriptor = concordance.describe(expected, concordanceOptions);
314-
const data = concordance.serialize(descriptor);
344+
const buffer = concordance.serialize(descriptor);
345+
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
315346

316347
return () => { // Must be called in order!
317348
this.hasChanges = true;
@@ -369,7 +400,7 @@ class Manager {
369400
blocks: sortBlocks(this.newBlocksByTitle, this.blockIndices).map(([title, block]) => ({title, ...block})),
370401
};
371402

372-
const buffer = await encodeSnapshots(snapshots);
403+
const buffer = encodeSnapshots(snapshots);
373404
const reportBuffer = generateReport(relFile, snapFile, snapshots);
374405

375406
await fs.promises.mkdir(dir, {recursive: true});

package-lock.json

Lines changed: 25 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
},
2828
"type": "module",
2929
"engines": {
30-
"node": "^22.20 || ^24.12 || >=25"
30+
"node": "^22.20 || ^24.12 || >=26"
3131
},
3232
"scripts": {
3333
"test": "./scripts/test.sh"
@@ -81,7 +81,7 @@
8181
"arrgv": "^1.0.2",
8282
"arrify": "^3.0.0",
8383
"callsites": "^4.2.0",
84-
"cbor": "^10.0.12",
84+
"cbor2": "^2.3.0",
8585
"chalk": "^5.6.2",
8686
"chunkd": "^2.0.1",
8787
"ci-info": "^4.4.0",

test-tap/helper/report.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ exports.sanitizers = {
4545
esmLoader: string => string.split('\n').filter(line => !line.includes('› async node:internal/modules/esm/loader:')).join('\n'),
4646
experimentalWarning: string => string.replaceAll(/^\(node:\d+\) ExperimentalWarning.+\n/g, ''),
4747
lineEndings: string => string.replaceAll('\r\n', '\n'),
48-
// The following are invjected by tap@18.
48+
libLineNumbers: string => string.replaceAll(/\((\/lib\/.+\.js):\d+:\d+\)/g, '($1)'),
49+
// The following are injected by tap@18.
4950
posix: string => string.replaceAll('\\', '/'),
5051
tapLoaders: string => string.replaceAll(/.+(Module\._compile|node_modules.pirates|require\.extensions).+\r?\n/g, ''),
5152
timers: string => string.replaceAll(/timers\.js:\d+:\d+/g, 'timers.js'),
File renamed without changes.
File renamed without changes.
File renamed without changes.

test-tap/reporters/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ test(async t => {
2323
report.sanitizers.cwd,
2424
report.sanitizers.esmLoader,
2525
report.sanitizers.experimentalWarning,
26+
report.sanitizers.libLineNumbers,
2627
report.sanitizers.posix,
2728
report.sanitizers.tapLoaders,
2829
report.sanitizers.timers,

0 commit comments

Comments
 (0)