From af9e66d3bbf498897f3e4be20a27c780f0ee2afe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:13:02 +0000 Subject: [PATCH 1/5] Bump @types/node from 22.13.7 to 26.2.0 Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.7 to 26.2.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.1.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 28 ++++++++++++++-------------- packages/azure-store/package.json | 2 +- packages/file-store/package.json | 2 +- packages/gcs-store/package.json | 2 +- packages/s3-store/package.json | 2 +- packages/server/package.json | 2 +- packages/utils/package.json | 2 +- test/package.json | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5eff7366..3a400f5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2862,13 +2862,13 @@ } }, "node_modules/@types/node": { - "version": "22.13.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.7.tgz", - "integrity": "sha512-oU2q+BsQldB9lYxHNp/5aZO+/Bs0Usa74Abo9mAKulz4ahQyXRHK6UVKYIN8KSC8HXwhWSi7b49JnX+txuac0w==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~8.3.0" } }, "node_modules/@types/request": { @@ -5969,9 +5969,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, @@ -6198,7 +6198,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, @@ -6217,7 +6217,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, @@ -6241,7 +6241,7 @@ "@tus/server": "^2.4.0", "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, @@ -6266,7 +6266,7 @@ "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", "@types/multistream": "^4.1.3", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, @@ -6289,7 +6289,7 @@ "@types/debug": "^4.1.12", "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "@types/set-cookie-parser": "^2.4.10", "@types/sinon": "^17.0.3", "@types/supertest": "^7.2.0", @@ -6314,7 +6314,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "ioredis": "^5.4.1", "mocha": "^11.7.3", "should": "^13.2.3" @@ -6332,7 +6332,7 @@ }, "devDependencies": { "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "@types/rimraf": "^3.0.2", "@types/sinon": "^17.0.3", "@types/supertest": "^7.2.0", diff --git a/packages/azure-store/package.json b/packages/azure-store/package.json index 19e7ca8b..8f2981ab 100644 --- a/packages/azure-store/package.json +++ b/packages/azure-store/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, diff --git a/packages/file-store/package.json b/packages/file-store/package.json index 5294d8f7..b2a39fe5 100644 --- a/packages/file-store/package.json +++ b/packages/file-store/package.json @@ -27,7 +27,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, diff --git a/packages/gcs-store/package.json b/packages/gcs-store/package.json index c721a1e9..f5bf7973 100644 --- a/packages/gcs-store/package.json +++ b/packages/gcs-store/package.json @@ -29,7 +29,7 @@ "@tus/server": "^2.4.0", "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, diff --git a/packages/s3-store/package.json b/packages/s3-store/package.json index b97fcab4..12663b57 100644 --- a/packages/s3-store/package.json +++ b/packages/s3-store/package.json @@ -30,7 +30,7 @@ "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", "@types/multistream": "^4.1.3", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "mocha": "^11.7.3", "should": "^13.2.3" }, diff --git a/packages/server/package.json b/packages/server/package.json index 584ee6ed..6754f051 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -31,7 +31,7 @@ "@types/debug": "^4.1.12", "@types/lodash.throttle": "^4.1.9", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "@types/set-cookie-parser": "^2.4.10", "@types/sinon": "^17.0.3", "@types/supertest": "^7.2.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index 37521102..aa4904d9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/debug": "^4.1.12", "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "ioredis": "^5.4.1", "mocha": "^11.7.3", "should": "^13.2.3" diff --git a/test/package.json b/test/package.json index 29a90b3a..175d382c 100644 --- a/test/package.json +++ b/test/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/mocha": "^10.0.6", - "@types/node": "^22.13.7", + "@types/node": "^26.2.0", "@types/rimraf": "^3.0.2", "@types/sinon": "^17.0.3", "@types/supertest": "^7.2.0", From 7c9bec4f8f02b94d49fbbbca0b698bbb31d4a629 Mon Sep 17 00:00:00 2001 From: Murderlon Date: Sat, 17 Oct 2026 12:06:58 +0200 Subject: [PATCH 2/5] @tus/server: preserve context declarations with Node 26 types Name the protected return type with the existing CancellationContext contract. Preserve current main brace-expansion lock entries when merging the dependency branch. --- package-lock.json | 12 ++++++------ packages/server/src/server.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b18e2f87..4eb62bbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3164,9 +3164,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -4653,9 +4653,9 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index c2fe7f12..adef2cdd 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -305,7 +305,7 @@ export class Server extends EventEmitter { return this.datastore.deleteExpired() } - protected createContext() { + protected createContext(): CancellationContext { // Initialize two AbortControllers: // 1. `requestAbortController` for instant request termination, particularly useful for stopping clients to upload when errors occur. // 2. `abortWithDelayController` to introduce a delay before aborting, allowing the server time to complete ongoing operations. From b4aba932b1dbcaac1544f2a0830cefc2a947bb0e Mon Sep 17 00:00:00 2001 From: Murderlon Date: Sat, 17 Oct 2026 12:06:58 +0200 Subject: [PATCH 3/5] @tus/server: check Node types consumer compatibility in CI Compile packed declarations with TypeScript 5.8.2 and Node 20 and 22 typings without skipLibCheck. Exercise packaged cancellation and HTTP uploads on supported Node runtimes. --- .../fixtures/node-types-consumer/consumer.ts | 54 ++++++++++++ .../fixtures/node-types-consumer/package.json | 5 ++ .../fixtures/node-types-consumer/runtime.mjs | 84 +++++++++++++++++++ .../node-types-consumer/tsconfig.json | 12 +++ .../workflows/node-types-compatibility.yml | 68 +++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 .github/fixtures/node-types-consumer/consumer.ts create mode 100644 .github/fixtures/node-types-consumer/package.json create mode 100644 .github/fixtures/node-types-consumer/runtime.mjs create mode 100644 .github/fixtures/node-types-consumer/tsconfig.json create mode 100644 .github/workflows/node-types-compatibility.yml diff --git a/.github/fixtures/node-types-consumer/consumer.ts b/.github/fixtures/node-types-consumer/consumer.ts new file mode 100644 index 00000000..e5a06b88 --- /dev/null +++ b/.github/fixtures/node-types-consumer/consumer.ts @@ -0,0 +1,54 @@ +import type {IncomingMessage, ServerResponse} from 'node:http' +import {Readable} from 'node:stream' +import type {AzureStore} from '@tus/azure-store' +import {FileStore} from '@tus/file-store' +import type {GCSStore} from '@tus/gcs-store' +import type {S3Store} from '@tus/s3-store' +import {MemoryLocker, Server} from '@tus/server' +import {type CancellationContext, DataStore, StreamLimiter, Upload} from '@tus/utils' + +export class ConsumerServer extends Server { + protected override createContext() { + const context = super.createContext() + const signal: AbortSignal = context.signal + const compatible: CancellationContext = context + signal.addEventListener('abort', () => compatible.abort(), {once: true}) + return {signal, abort: context.abort, cancel: context.cancel} + } + + context(): CancellationContext { + return this.createContext() + } +} + +export function consumeStores( + file: FileStore, + azure: AzureStore, + gcs: GCSStore, + s3: S3Store, + req: IncomingMessage, + res: ServerResponse +) { + const stores: DataStore[] = [file, azure, gcs, s3] + const upload = new Upload({id: 'consumer', size: 4, offset: 0}) + const server = new ConsumerServer({ + path: '/files', + datastore: file, + onUploadCreate: async (request, created) => { + const signal: AbortSignal = request.signal + signal.throwIfAborted() + return {metadata: created.metadata} + }, + }) + const context: CancellationContext = server.context() + const lock = new MemoryLocker().newLock(upload.id) + const acquiring: Promise = lock.lock(context.signal, context.cancel) + const handling: Promise = server.handle(req, res) + const response: Promise = server.handleWeb(new Request('http://localhost/files')) + const written: Promise = server.write(context, new Headers(), 200) + const writing: Promise[] = stores.map((store) => + store.write(Readable.from([Buffer.from('test')]), upload.id, 0) + ) + const limited: Readable = Readable.from([Buffer.from('test')]).pipe(new StreamLimiter(4)) + return {acquiring, handling, response, written, writing, limited} +} diff --git a/.github/fixtures/node-types-consumer/package.json b/.github/fixtures/node-types-consumer/package.json new file mode 100644 index 00000000..4350980b --- /dev/null +++ b/.github/fixtures/node-types-consumer/package.json @@ -0,0 +1,5 @@ +{ + "name": "tus-node-types-consumer", + "private": true, + "type": "module" +} diff --git a/.github/fixtures/node-types-consumer/runtime.mjs b/.github/fixtures/node-types-consumer/runtime.mjs new file mode 100644 index 00000000..bbb5c07f --- /dev/null +++ b/.github/fixtures/node-types-consumer/runtime.mjs @@ -0,0 +1,84 @@ +import assert from 'node:assert/strict' +import {once} from 'node:events' +import {mkdtemp, rm} from 'node:fs/promises' +import {tmpdir} from 'node:os' +import {join} from 'node:path' +import {AzureStore} from '@tus/azure-store' +import {FileStore} from '@tus/file-store' +import {GCSStore} from '@tus/gcs-store' +import {S3Store} from '@tus/s3-store' +import {ERRORS, MemoryLocker, Server} from '@tus/server' +import {DataStore} from '@tus/utils' + +class ConsumerServer extends Server { + context() { + return this.createContext() + } +} + +for (const Store of [AzureStore, FileStore, GCSStore, S3Store]) { + assert.ok(Store.prototype instanceof DataStore) +} + +const directory = await mkdtemp(join(tmpdir(), 'tus-node-types-')) +const store = new FileStore({directory}) +const server = new ConsumerServer({path: '/files', datastore: store, lockDrainTimeout: 1}) +const listener = server.listen(0, '127.0.0.1') + +try { + const immediate = server.context() + immediate.abort() + assert.equal(immediate.signal.aborted, true) + assert.equal(immediate.signal.reason, ERRORS.ABORTED) + + const delayed = server.context() + const aborted = once(delayed.signal, 'abort') + delayed.cancel() + assert.equal(delayed.signal.aborted, false) + await aborted + assert.equal(delayed.signal.aborted, true) + + const context = server.context() + const lock = new MemoryLocker().newLock('consumer') + await lock.lock(context.signal, context.cancel) + await lock.unlock() + + if (!listener.listening) await once(listener, 'listening') + const address = listener.address() + assert.ok(address && typeof address === 'object') + const endpoint = `http://127.0.0.1:${address.port}/files` + const created = await fetch(endpoint, { + method: 'POST', + headers: {'Tus-Resumable': '1.0.0', 'Upload-Length': '4'}, + }) + assert.equal(created.status, 201) + const location = created.headers.get('location') + assert.ok(location) + const uploadUrl = new URL(location, endpoint) + const patched = await fetch(uploadUrl, { + method: 'PATCH', + headers: { + 'Tus-Resumable': '1.0.0', + 'Upload-Offset': '0', + 'Content-Type': 'application/offset+octet-stream', + }, + body: Buffer.from('test'), + }) + assert.equal(patched.status, 204) + assert.equal(patched.headers.get('upload-offset'), '4') + const head = await fetch(uploadUrl, {method: 'HEAD', headers: {'Tus-Resumable': '1.0.0'}}) + assert.equal(head.status, 200) + assert.equal(head.headers.get('upload-offset'), '4') + const chunks = [] + for await (const chunk of store.read(uploadUrl.pathname.split('/').pop())) { + chunks.push(chunk) + } + assert.equal(Buffer.concat(chunks).toString(), 'test') + console.log(`Packaged Node types runtime checks passed on ${process.version}`) +} finally { + await new Promise((resolve, reject) => { + listener.close((error) => (error ? reject(error) : resolve())) + listener.closeAllConnections() + }) + await rm(directory, {recursive: true, force: true}) +} diff --git a/.github/fixtures/node-types-consumer/tsconfig.json b/.github/fixtures/node-types-consumer/tsconfig.json new file mode 100644 index 00000000..86eaff19 --- /dev/null +++ b/.github/fixtures/node-types-consumer/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "strict": true, + "skipLibCheck": false, + "noEmit": true, + "types": ["node"] + }, + "files": ["consumer.ts"] +} diff --git a/.github/workflows/node-types-compatibility.yml b/.github/workflows/node-types-compatibility.yml new file mode 100644 index 00000000..6a5ea0ef --- /dev/null +++ b/.github/workflows/node-types-compatibility.yml @@ -0,0 +1,68 @@ +name: Node types compatibility + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: [main] + merge_group: + types: [checks_requested] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}--${{ github.ref }} + cancel-in-progress: true + +jobs: + consumers: + name: TS 5.8 / Node ${{ matrix.node }} / types ${{ matrix.types }} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - node: 20.19.0 + types: 20.19.0 + - node: 22.x + types: 22.13.7 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: Install development dependencies + run: npm ci --no-fund --no-audit + + - name: Build with the repository compiler and Node 26 types + run: npm run build + + - name: Pack the published libraries + run: | + mkdir -p "$RUNNER_TEMP/node-types-packages" + npm pack --workspace packages/utils --workspace packages/server --workspace packages/file-store --workspace packages/s3-store --workspace packages/gcs-store --workspace packages/azure-store --pack-destination "$RUNNER_TEMP/node-types-packages" + + - name: Install an isolated consumer + env: + NODE_TYPES_VERSION: ${{ matrix.types }} + run: | + mkdir -p "$RUNNER_TEMP/node-types-consumer" + cp .github/fixtures/node-types-consumer/* "$RUNNER_TEMP/node-types-consumer/" + cd "$RUNNER_TEMP/node-types-consumer" + npm pkg set "overrides.@types/node=$NODE_TYPES_VERSION" + npm install --no-fund --no-audit --save-exact typescript@5.8.2 "@types/node@$NODE_TYPES_VERSION" "$RUNNER_TEMP"/node-types-packages/*.tgz + npm ls @types/node typescript @tus/utils @tus/server + + - name: Exercise the packaged runtime on supported Node.js + id: runtime + working-directory: ${{ runner.temp }}/node-types-consumer + run: node runtime.mjs + + - name: Compile the published declarations without skipLibCheck + if: ${{ !cancelled() && steps.runtime.outcome != 'skipped' }} + working-directory: ${{ runner.temp }}/node-types-consumer + run: ./node_modules/.bin/tsc --project tsconfig.json From 89bbca0e83666e9abd5b858dd83cc5d236bb526a Mon Sep 17 00:00:00 2001 From: Murderlon Date: Sat, 17 Oct 2026 12:16:13 +0200 Subject: [PATCH 4/5] @tus/server: compare strict consumers against the exact base Use one locked external dependency installation for both packed revisions, retain strict diagnostics and runtime evidence, and add a patch changeset for the existing context return annotation. --- .changeset/small-context-types.md | 5 + .github/scripts/node-types-compatibility.mjs | 148 ++++++++++++++++++ .../workflows/node-types-compatibility.yml | 72 +++++---- 3 files changed, 197 insertions(+), 28 deletions(-) create mode 100644 .changeset/small-context-types.md create mode 100644 .github/scripts/node-types-compatibility.mjs diff --git a/.changeset/small-context-types.md b/.changeset/small-context-types.md new file mode 100644 index 00000000..b378317c --- /dev/null +++ b/.changeset/small-context-types.md @@ -0,0 +1,5 @@ +--- +"@tus/server": patch +--- + +Use the existing `CancellationContext` return type for `Server.createContext` so declaration generation succeeds with newer Node.js typings. This preserves the existing context shape and runtime behavior. diff --git a/.github/scripts/node-types-compatibility.mjs b/.github/scripts/node-types-compatibility.mjs new file mode 100644 index 00000000..8250ef34 --- /dev/null +++ b/.github/scripts/node-types-compatibility.mjs @@ -0,0 +1,148 @@ +import assert from 'node:assert/strict' +import {execFileSync, spawnSync} from 'node:child_process' +import {appendFileSync, cpSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync} from 'node:fs' +import {createRequire} from 'node:module' +import {join, resolve} from 'node:path' + +const [base, head, output] = process.argv.slice(2).map((path) => resolve(path)) +const nodeTypes = process.env.NODE_TYPES_VERSION +assert(nodeTypes, 'NODE_TYPES_VERSION is required') +const readJson = (path) => JSON.parse(readFileSync(path, 'utf8')) +const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`) + +function pack(root, label) { + const destination = join(output, label) + mkdirSync(destination, {recursive: true}) + const workspaces = readdirSync(join(root, 'packages')).filter( + (name) => !readJson(join(root, 'packages', name, 'package.json')).private + ) + const packages = JSON.parse(execFileSync('npm', [ + 'pack', '--json', '--ignore-scripts', '--pack-destination', destination, + ...workspaces.map((name) => `--workspace=./packages/${name}`), + ], {cwd: root, encoding: 'utf8'})) + return new Map(packages.map((pkg) => { + const tarball = join(destination, pkg.filename) + const manifest = JSON.parse(execFileSync('tar', ['-xOf', tarball, 'package/package.json'], {encoding: 'utf8'})) + return [pkg.name, {tarball, manifest}] + })) +} + +const before = pack(base, 'base') +const after = pack(head, 'head') +assert(before.size > 0, 'No published packages found') +assert.deepEqual([...after.keys()].sort(), [...before.keys()].sort(), 'Published packages changed') +for (const [name, pkg] of before) { + for (const field of ['dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta', 'engines']) { + assert.deepEqual(after.get(name).manifest[field], pkg.manifest[field], `${name} ${field} changed; reassess the shared consumer installation`) + } +} + +const consumer = join(output, 'consumer') +cpSync(join(head, '.github/fixtures/node-types-consumer'), consumer, {recursive: true}) +const manifest = { + ...readJson(join(consumer, 'package.json')), + dependencies: { + ...Object.fromEntries([...before].map(([name, pkg]) => [name, `file:${pkg.tarball}`])), + typescript: '5.8.2', + '@types/node': nodeTypes, + }, + overrides: {'@types/node': nodeTypes}, +} +writeJson(join(consumer, 'package.json'), manifest) +const baseLock = readJson(join(base, 'package-lock.json')) +writeJson(join(consumer, 'package-lock.json'), { + lockfileVersion: 3, + requires: true, + packages: { + ...Object.fromEntries(Object.entries(baseLock.packages).filter( + ([path, pkg]) => path.startsWith('node_modules/') && !pkg.link + )), + '': manifest, + }, +}) +execFileSync('npm', ['install', '--ignore-scripts', '--no-fund', '--no-audit'], {cwd: consumer, stdio: 'inherit'}) +const require = createRequire(join(consumer, 'package.json')) +const environment = { + node: process.version, + typescript: require('typescript/package.json').version, + nodeTypes: require('@types/node/package.json').version, + srvx: readJson(join(consumer, 'node_modules/srvx/package.json')).version, + base: execFileSync('git', ['rev-parse', 'HEAD'], {cwd: base, encoding: 'utf8'}).trim(), + head: execFileSync('git', ['rev-parse', 'HEAD'], {cwd: head, encoding: 'utf8'}).trim(), +} +writeJson(join(output, 'environment.json'), environment) +console.log(environment) +assert.equal(environment.typescript, '5.8.2') +assert.equal(environment.nodeTypes, nodeTypes) +assert.equal(environment.srvx, baseLock.packages['node_modules/srvx'].version) +for (const [path, pkg] of Object.entries(readJson(join(consumer, 'package-lock.json')).packages)) { + if (path.endsWith('node_modules/@types/node')) assert.equal(pkg.version, nodeTypes, path) + if (/(?:^|\/)node_modules\/@tus\//.test(path)) { + assert([...before.keys()].some((name) => path === `node_modules/${name}`), `Unexpected nested tus package at ${path}`) + } +} + +function run(label, args) { + const result = spawnSync(process.execPath, args, { + cwd: consumer, encoding: 'utf8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, + }) + writeFileSync(join(output, `${label}.stdout.log`), result.stdout ?? '') + writeFileSync(join(output, `${label}.stderr.log`), result.stderr ?? '') + writeJson(join(output, `${label}.status.json`), { + status: result.status, signal: result.signal, error: result.error?.message, + }) + console.log(`${label} exited with ${result.status}`) + process.stdout.write(result.stdout ?? '') + process.stderr.write(result.stderr ?? '') + return result +} + +const tsc = [require.resolve('typescript/bin/tsc'), '--project', 'tsconfig.json', '--pretty', 'false'] +const baseTypes = run('base-types', tsc) +const baseRuntime = run('base-runtime', ['runtime.mjs']) + +// Never reinstall between checks. Preserve even nested external dependencies. +for (const [name, pkg] of after) { + const directory = join(consumer, 'node_modules', name) + for (const entry of readdirSync(directory)) { + if (entry !== 'node_modules') rmSync(join(directory, entry), {recursive: true, force: true}) + } + execFileSync('tar', ['-xzf', pkg.tarball, '-C', directory, '--strip-components=1']) +} +const headTypes = run('head-types', tsc) +const headRuntime = run('head-runtime', ['runtime.mjs']) + +function diagnostics(result, label) { + assert.ifError(result.error) + assert.equal(result.signal, null, `${label} compiler was interrupted`) + assert.equal(result.stderr, '', `${label} compiler wrote unexpected stderr`) + let normalized = result.stdout + for (const [root, replacement] of [[consumer, ''], [base, ''], [head, ''], [output, '']]) { + normalized = normalized.replaceAll(`${root}/`, `${replacement}/`) + } + const entries = normalized.split(/(?=^(?:[^\n]+\(\d+,\d+\): )?error TS\d+: )/m).filter(Boolean) + assert(entries.every((entry) => /^(?:[^\n]+\(\d+,\d+\): )?error TS\d+: /.test(entry)), `${label} compiler output was not diagnostics`) + assert.equal(result.status, entries.length ? 2 : 0, `${label} compiler exited unexpectedly`) + writeJson(join(output, `${label}-diagnostics.json`), entries) + return entries +} + +const baseline = diagnostics(baseTypes, 'base') +const proposed = diagnostics(headTypes, 'head') +const remaining = [...baseline] +const added = proposed.filter((diagnostic) => { + const index = remaining.indexOf(diagnostic) + if (index === -1) return true + remaining.splice(index, 1) + return false +}) +writeJson(join(output, 'new-diagnostics.json'), added) +const summary = `Strict consumer comparison: base ${baseline.length} diagnostics, head ${proposed.length}, new or changed ${added.length}.\n${proposed.length ? 'Head is NOT a clean strict typecheck. See retained baseline and head diagnostics.' : 'Head strict typecheck is clean.'}\n` +console.log(summary) +if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary) +if (baseline.length) console.log('::warning::The exact base has strict consumer errors. This job checks for regressions and retains all diagnostics.') +assert.deepEqual(added, [], 'New or changed strict consumer diagnostics') +for (const [label, result] of [['base-runtime', baseRuntime], ['head-runtime', headRuntime]]) { + assert.ifError(result.error) + assert.equal(result.status, 0, `${label} failed; see retained runtime logs`) +} diff --git a/.github/workflows/node-types-compatibility.yml b/.github/workflows/node-types-compatibility.yml index 6a5ea0ef..62980e03 100644 --- a/.github/workflows/node-types-compatibility.yml +++ b/.github/workflows/node-types-compatibility.yml @@ -19,7 +19,7 @@ jobs: consumers: name: TS 5.8 / Node ${{ matrix.node }} / types ${{ matrix.types }} runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -29,40 +29,56 @@ jobs: - node: 22.x types: 22.13.7 steps: - - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - name: Checkout proposed head + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + path: head + persist-credentials: false + + - name: Checkout exact base + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }} + path: base + persist-credentials: false + + - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node }} cache: npm + cache-dependency-path: | + base/package-lock.json + head/package-lock.json - - name: Install development dependencies - run: npm ci --no-fund --no-audit - - - name: Build with the repository compiler and Node 26 types - run: npm run build + - name: Build base packages with their locked development dependencies + working-directory: base + run: | + npm ci --no-fund --no-audit + npm run build - - name: Pack the published libraries + - name: Build head packages with their locked development dependencies + working-directory: head run: | - mkdir -p "$RUNNER_TEMP/node-types-packages" - npm pack --workspace packages/utils --workspace packages/server --workspace packages/file-store --workspace packages/s3-store --workspace packages/gcs-store --workspace packages/azure-store --pack-destination "$RUNNER_TEMP/node-types-packages" + npm ci --no-fund --no-audit + npm run build - - name: Install an isolated consumer + - name: Compare strict consumer diagnostics and exercise both packed runtimes env: NODE_TYPES_VERSION: ${{ matrix.types }} - run: | - mkdir -p "$RUNNER_TEMP/node-types-consumer" - cp .github/fixtures/node-types-consumer/* "$RUNNER_TEMP/node-types-consumer/" - cd "$RUNNER_TEMP/node-types-consumer" - npm pkg set "overrides.@types/node=$NODE_TYPES_VERSION" - npm install --no-fund --no-audit --save-exact typescript@5.8.2 "@types/node@$NODE_TYPES_VERSION" "$RUNNER_TEMP"/node-types-packages/*.tgz - npm ls @types/node typescript @tus/utils @tus/server + COMPAT_OUTPUT: ${{ runner.temp }}/node-types-compatibility + run: node head/.github/scripts/node-types-compatibility.mjs base head "$COMPAT_OUTPUT" - - name: Exercise the packaged runtime on supported Node.js - id: runtime - working-directory: ${{ runner.temp }}/node-types-consumer - run: node runtime.mjs - - - name: Compile the published declarations without skipLibCheck - if: ${{ !cancelled() && steps.runtime.outcome != 'skipped' }} - working-directory: ${{ runner.temp }}/node-types-consumer - run: ./node_modules/.bin/tsc --project tsconfig.json + - name: Retain packed packages, dependency lock, and raw diagnostics + if: always() + uses: actions/upload-artifact@v4 + with: + name: node-types-compatibility-${{ matrix.types }} + path: | + ${{ runner.temp }}/node-types-compatibility/**/*.tgz + ${{ runner.temp }}/node-types-compatibility/*.log + ${{ runner.temp }}/node-types-compatibility/*.json + ${{ runner.temp }}/node-types-compatibility/consumer/package*.json + ${{ runner.temp }}/node-types-compatibility/consumer/tsconfig.json + ${{ runner.temp }}/node-types-compatibility/consumer/consumer.ts + ${{ runner.temp }}/node-types-compatibility/consumer/runtime.mjs From ff2a83ed8588832e0c39cf8e25e99087c6f753aa Mon Sep 17 00:00:00 2001 From: Murderlon Date: Sat, 17 Oct 2026 12:46:18 +0200 Subject: [PATCH 5/5] @tus/server: remove Node types upgrade validation scaffolding --- .../fixtures/node-types-consumer/consumer.ts | 54 ------- .../fixtures/node-types-consumer/package.json | 5 - .../fixtures/node-types-consumer/runtime.mjs | 84 ---------- .../node-types-consumer/tsconfig.json | 12 -- .github/scripts/node-types-compatibility.mjs | 148 ------------------ .../workflows/node-types-compatibility.yml | 84 ---------- 6 files changed, 387 deletions(-) delete mode 100644 .github/fixtures/node-types-consumer/consumer.ts delete mode 100644 .github/fixtures/node-types-consumer/package.json delete mode 100644 .github/fixtures/node-types-consumer/runtime.mjs delete mode 100644 .github/fixtures/node-types-consumer/tsconfig.json delete mode 100644 .github/scripts/node-types-compatibility.mjs delete mode 100644 .github/workflows/node-types-compatibility.yml diff --git a/.github/fixtures/node-types-consumer/consumer.ts b/.github/fixtures/node-types-consumer/consumer.ts deleted file mode 100644 index e5a06b88..00000000 --- a/.github/fixtures/node-types-consumer/consumer.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type {IncomingMessage, ServerResponse} from 'node:http' -import {Readable} from 'node:stream' -import type {AzureStore} from '@tus/azure-store' -import {FileStore} from '@tus/file-store' -import type {GCSStore} from '@tus/gcs-store' -import type {S3Store} from '@tus/s3-store' -import {MemoryLocker, Server} from '@tus/server' -import {type CancellationContext, DataStore, StreamLimiter, Upload} from '@tus/utils' - -export class ConsumerServer extends Server { - protected override createContext() { - const context = super.createContext() - const signal: AbortSignal = context.signal - const compatible: CancellationContext = context - signal.addEventListener('abort', () => compatible.abort(), {once: true}) - return {signal, abort: context.abort, cancel: context.cancel} - } - - context(): CancellationContext { - return this.createContext() - } -} - -export function consumeStores( - file: FileStore, - azure: AzureStore, - gcs: GCSStore, - s3: S3Store, - req: IncomingMessage, - res: ServerResponse -) { - const stores: DataStore[] = [file, azure, gcs, s3] - const upload = new Upload({id: 'consumer', size: 4, offset: 0}) - const server = new ConsumerServer({ - path: '/files', - datastore: file, - onUploadCreate: async (request, created) => { - const signal: AbortSignal = request.signal - signal.throwIfAborted() - return {metadata: created.metadata} - }, - }) - const context: CancellationContext = server.context() - const lock = new MemoryLocker().newLock(upload.id) - const acquiring: Promise = lock.lock(context.signal, context.cancel) - const handling: Promise = server.handle(req, res) - const response: Promise = server.handleWeb(new Request('http://localhost/files')) - const written: Promise = server.write(context, new Headers(), 200) - const writing: Promise[] = stores.map((store) => - store.write(Readable.from([Buffer.from('test')]), upload.id, 0) - ) - const limited: Readable = Readable.from([Buffer.from('test')]).pipe(new StreamLimiter(4)) - return {acquiring, handling, response, written, writing, limited} -} diff --git a/.github/fixtures/node-types-consumer/package.json b/.github/fixtures/node-types-consumer/package.json deleted file mode 100644 index 4350980b..00000000 --- a/.github/fixtures/node-types-consumer/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "tus-node-types-consumer", - "private": true, - "type": "module" -} diff --git a/.github/fixtures/node-types-consumer/runtime.mjs b/.github/fixtures/node-types-consumer/runtime.mjs deleted file mode 100644 index bbb5c07f..00000000 --- a/.github/fixtures/node-types-consumer/runtime.mjs +++ /dev/null @@ -1,84 +0,0 @@ -import assert from 'node:assert/strict' -import {once} from 'node:events' -import {mkdtemp, rm} from 'node:fs/promises' -import {tmpdir} from 'node:os' -import {join} from 'node:path' -import {AzureStore} from '@tus/azure-store' -import {FileStore} from '@tus/file-store' -import {GCSStore} from '@tus/gcs-store' -import {S3Store} from '@tus/s3-store' -import {ERRORS, MemoryLocker, Server} from '@tus/server' -import {DataStore} from '@tus/utils' - -class ConsumerServer extends Server { - context() { - return this.createContext() - } -} - -for (const Store of [AzureStore, FileStore, GCSStore, S3Store]) { - assert.ok(Store.prototype instanceof DataStore) -} - -const directory = await mkdtemp(join(tmpdir(), 'tus-node-types-')) -const store = new FileStore({directory}) -const server = new ConsumerServer({path: '/files', datastore: store, lockDrainTimeout: 1}) -const listener = server.listen(0, '127.0.0.1') - -try { - const immediate = server.context() - immediate.abort() - assert.equal(immediate.signal.aborted, true) - assert.equal(immediate.signal.reason, ERRORS.ABORTED) - - const delayed = server.context() - const aborted = once(delayed.signal, 'abort') - delayed.cancel() - assert.equal(delayed.signal.aborted, false) - await aborted - assert.equal(delayed.signal.aborted, true) - - const context = server.context() - const lock = new MemoryLocker().newLock('consumer') - await lock.lock(context.signal, context.cancel) - await lock.unlock() - - if (!listener.listening) await once(listener, 'listening') - const address = listener.address() - assert.ok(address && typeof address === 'object') - const endpoint = `http://127.0.0.1:${address.port}/files` - const created = await fetch(endpoint, { - method: 'POST', - headers: {'Tus-Resumable': '1.0.0', 'Upload-Length': '4'}, - }) - assert.equal(created.status, 201) - const location = created.headers.get('location') - assert.ok(location) - const uploadUrl = new URL(location, endpoint) - const patched = await fetch(uploadUrl, { - method: 'PATCH', - headers: { - 'Tus-Resumable': '1.0.0', - 'Upload-Offset': '0', - 'Content-Type': 'application/offset+octet-stream', - }, - body: Buffer.from('test'), - }) - assert.equal(patched.status, 204) - assert.equal(patched.headers.get('upload-offset'), '4') - const head = await fetch(uploadUrl, {method: 'HEAD', headers: {'Tus-Resumable': '1.0.0'}}) - assert.equal(head.status, 200) - assert.equal(head.headers.get('upload-offset'), '4') - const chunks = [] - for await (const chunk of store.read(uploadUrl.pathname.split('/').pop())) { - chunks.push(chunk) - } - assert.equal(Buffer.concat(chunks).toString(), 'test') - console.log(`Packaged Node types runtime checks passed on ${process.version}`) -} finally { - await new Promise((resolve, reject) => { - listener.close((error) => (error ? reject(error) : resolve())) - listener.closeAllConnections() - }) - await rm(directory, {recursive: true, force: true}) -} diff --git a/.github/fixtures/node-types-consumer/tsconfig.json b/.github/fixtures/node-types-consumer/tsconfig.json deleted file mode 100644 index 86eaff19..00000000 --- a/.github/fixtures/node-types-consumer/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2022"], - "module": "NodeNext", - "strict": true, - "skipLibCheck": false, - "noEmit": true, - "types": ["node"] - }, - "files": ["consumer.ts"] -} diff --git a/.github/scripts/node-types-compatibility.mjs b/.github/scripts/node-types-compatibility.mjs deleted file mode 100644 index 8250ef34..00000000 --- a/.github/scripts/node-types-compatibility.mjs +++ /dev/null @@ -1,148 +0,0 @@ -import assert from 'node:assert/strict' -import {execFileSync, spawnSync} from 'node:child_process' -import {appendFileSync, cpSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync} from 'node:fs' -import {createRequire} from 'node:module' -import {join, resolve} from 'node:path' - -const [base, head, output] = process.argv.slice(2).map((path) => resolve(path)) -const nodeTypes = process.env.NODE_TYPES_VERSION -assert(nodeTypes, 'NODE_TYPES_VERSION is required') -const readJson = (path) => JSON.parse(readFileSync(path, 'utf8')) -const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`) - -function pack(root, label) { - const destination = join(output, label) - mkdirSync(destination, {recursive: true}) - const workspaces = readdirSync(join(root, 'packages')).filter( - (name) => !readJson(join(root, 'packages', name, 'package.json')).private - ) - const packages = JSON.parse(execFileSync('npm', [ - 'pack', '--json', '--ignore-scripts', '--pack-destination', destination, - ...workspaces.map((name) => `--workspace=./packages/${name}`), - ], {cwd: root, encoding: 'utf8'})) - return new Map(packages.map((pkg) => { - const tarball = join(destination, pkg.filename) - const manifest = JSON.parse(execFileSync('tar', ['-xOf', tarball, 'package/package.json'], {encoding: 'utf8'})) - return [pkg.name, {tarball, manifest}] - })) -} - -const before = pack(base, 'base') -const after = pack(head, 'head') -assert(before.size > 0, 'No published packages found') -assert.deepEqual([...after.keys()].sort(), [...before.keys()].sort(), 'Published packages changed') -for (const [name, pkg] of before) { - for (const field of ['dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta', 'engines']) { - assert.deepEqual(after.get(name).manifest[field], pkg.manifest[field], `${name} ${field} changed; reassess the shared consumer installation`) - } -} - -const consumer = join(output, 'consumer') -cpSync(join(head, '.github/fixtures/node-types-consumer'), consumer, {recursive: true}) -const manifest = { - ...readJson(join(consumer, 'package.json')), - dependencies: { - ...Object.fromEntries([...before].map(([name, pkg]) => [name, `file:${pkg.tarball}`])), - typescript: '5.8.2', - '@types/node': nodeTypes, - }, - overrides: {'@types/node': nodeTypes}, -} -writeJson(join(consumer, 'package.json'), manifest) -const baseLock = readJson(join(base, 'package-lock.json')) -writeJson(join(consumer, 'package-lock.json'), { - lockfileVersion: 3, - requires: true, - packages: { - ...Object.fromEntries(Object.entries(baseLock.packages).filter( - ([path, pkg]) => path.startsWith('node_modules/') && !pkg.link - )), - '': manifest, - }, -}) -execFileSync('npm', ['install', '--ignore-scripts', '--no-fund', '--no-audit'], {cwd: consumer, stdio: 'inherit'}) -const require = createRequire(join(consumer, 'package.json')) -const environment = { - node: process.version, - typescript: require('typescript/package.json').version, - nodeTypes: require('@types/node/package.json').version, - srvx: readJson(join(consumer, 'node_modules/srvx/package.json')).version, - base: execFileSync('git', ['rev-parse', 'HEAD'], {cwd: base, encoding: 'utf8'}).trim(), - head: execFileSync('git', ['rev-parse', 'HEAD'], {cwd: head, encoding: 'utf8'}).trim(), -} -writeJson(join(output, 'environment.json'), environment) -console.log(environment) -assert.equal(environment.typescript, '5.8.2') -assert.equal(environment.nodeTypes, nodeTypes) -assert.equal(environment.srvx, baseLock.packages['node_modules/srvx'].version) -for (const [path, pkg] of Object.entries(readJson(join(consumer, 'package-lock.json')).packages)) { - if (path.endsWith('node_modules/@types/node')) assert.equal(pkg.version, nodeTypes, path) - if (/(?:^|\/)node_modules\/@tus\//.test(path)) { - assert([...before.keys()].some((name) => path === `node_modules/${name}`), `Unexpected nested tus package at ${path}`) - } -} - -function run(label, args) { - const result = spawnSync(process.execPath, args, { - cwd: consumer, encoding: 'utf8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, - }) - writeFileSync(join(output, `${label}.stdout.log`), result.stdout ?? '') - writeFileSync(join(output, `${label}.stderr.log`), result.stderr ?? '') - writeJson(join(output, `${label}.status.json`), { - status: result.status, signal: result.signal, error: result.error?.message, - }) - console.log(`${label} exited with ${result.status}`) - process.stdout.write(result.stdout ?? '') - process.stderr.write(result.stderr ?? '') - return result -} - -const tsc = [require.resolve('typescript/bin/tsc'), '--project', 'tsconfig.json', '--pretty', 'false'] -const baseTypes = run('base-types', tsc) -const baseRuntime = run('base-runtime', ['runtime.mjs']) - -// Never reinstall between checks. Preserve even nested external dependencies. -for (const [name, pkg] of after) { - const directory = join(consumer, 'node_modules', name) - for (const entry of readdirSync(directory)) { - if (entry !== 'node_modules') rmSync(join(directory, entry), {recursive: true, force: true}) - } - execFileSync('tar', ['-xzf', pkg.tarball, '-C', directory, '--strip-components=1']) -} -const headTypes = run('head-types', tsc) -const headRuntime = run('head-runtime', ['runtime.mjs']) - -function diagnostics(result, label) { - assert.ifError(result.error) - assert.equal(result.signal, null, `${label} compiler was interrupted`) - assert.equal(result.stderr, '', `${label} compiler wrote unexpected stderr`) - let normalized = result.stdout - for (const [root, replacement] of [[consumer, ''], [base, ''], [head, ''], [output, '']]) { - normalized = normalized.replaceAll(`${root}/`, `${replacement}/`) - } - const entries = normalized.split(/(?=^(?:[^\n]+\(\d+,\d+\): )?error TS\d+: )/m).filter(Boolean) - assert(entries.every((entry) => /^(?:[^\n]+\(\d+,\d+\): )?error TS\d+: /.test(entry)), `${label} compiler output was not diagnostics`) - assert.equal(result.status, entries.length ? 2 : 0, `${label} compiler exited unexpectedly`) - writeJson(join(output, `${label}-diagnostics.json`), entries) - return entries -} - -const baseline = diagnostics(baseTypes, 'base') -const proposed = diagnostics(headTypes, 'head') -const remaining = [...baseline] -const added = proposed.filter((diagnostic) => { - const index = remaining.indexOf(diagnostic) - if (index === -1) return true - remaining.splice(index, 1) - return false -}) -writeJson(join(output, 'new-diagnostics.json'), added) -const summary = `Strict consumer comparison: base ${baseline.length} diagnostics, head ${proposed.length}, new or changed ${added.length}.\n${proposed.length ? 'Head is NOT a clean strict typecheck. See retained baseline and head diagnostics.' : 'Head strict typecheck is clean.'}\n` -console.log(summary) -if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary) -if (baseline.length) console.log('::warning::The exact base has strict consumer errors. This job checks for regressions and retains all diagnostics.') -assert.deepEqual(added, [], 'New or changed strict consumer diagnostics') -for (const [label, result] of [['base-runtime', baseRuntime], ['head-runtime', headRuntime]]) { - assert.ifError(result.error) - assert.equal(result.status, 0, `${label} failed; see retained runtime logs`) -} diff --git a/.github/workflows/node-types-compatibility.yml b/.github/workflows/node-types-compatibility.yml deleted file mode 100644 index 62980e03..00000000 --- a/.github/workflows/node-types-compatibility.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Node types compatibility - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: [main] - merge_group: - types: [checks_requested] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}--${{ github.ref }} - cancel-in-progress: true - -jobs: - consumers: - name: TS 5.8 / Node ${{ matrix.node }} / types ${{ matrix.types }} - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - include: - - node: 20.19.0 - types: 20.19.0 - - node: 22.x - types: 22.13.7 - steps: - - name: Checkout proposed head - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - path: head - persist-credentials: false - - - name: Checkout exact base - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }} - path: base - persist-credentials: false - - - uses: actions/setup-node@v7 - with: - node-version: ${{ matrix.node }} - cache: npm - cache-dependency-path: | - base/package-lock.json - head/package-lock.json - - - name: Build base packages with their locked development dependencies - working-directory: base - run: | - npm ci --no-fund --no-audit - npm run build - - - name: Build head packages with their locked development dependencies - working-directory: head - run: | - npm ci --no-fund --no-audit - npm run build - - - name: Compare strict consumer diagnostics and exercise both packed runtimes - env: - NODE_TYPES_VERSION: ${{ matrix.types }} - COMPAT_OUTPUT: ${{ runner.temp }}/node-types-compatibility - run: node head/.github/scripts/node-types-compatibility.mjs base head "$COMPAT_OUTPUT" - - - name: Retain packed packages, dependency lock, and raw diagnostics - if: always() - uses: actions/upload-artifact@v4 - with: - name: node-types-compatibility-${{ matrix.types }} - path: | - ${{ runner.temp }}/node-types-compatibility/**/*.tgz - ${{ runner.temp }}/node-types-compatibility/*.log - ${{ runner.temp }}/node-types-compatibility/*.json - ${{ runner.temp }}/node-types-compatibility/consumer/package*.json - ${{ runner.temp }}/node-types-compatibility/consumer/tsconfig.json - ${{ runner.temp }}/node-types-compatibility/consumer/consumer.ts - ${{ runner.temp }}/node-types-compatibility/consumer/runtime.mjs