From be6b4f65f78cfdf72aab83cd5142f087233deead Mon Sep 17 00:00:00 2001 From: Rowan Manning <138944+rowanmanning@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:50:55 +0100 Subject: [PATCH] feat(wip): add a node-test plugin This adds a node-test plugin using `node:test#run()`. There are quite a few gotchas with this one: * If we want to support Node.js 18 and 20 then we can't use glob patterns (the `globPatterns` option) because it's only supported in Node.js 22. I think glob patterns are important so I've replicated wth the glob package. * We don't support Node.js 18 less than v18.17.0, this is because there wasn't a consistent JavaScript API for the built-in test reporters before this version. This is captured in the engines. An alternative would be to drop Node.js 18 support in this package as we'll be doing this in April 2025 anyway. * There is no way of configuring the built-in test runner via a config file or similar. This means we need to expose more options than Jest or Mocha in the Tool Kit config file. --- lib/schemas/src/tasks.ts | 2 + lib/schemas/src/tasks/node-test.ts | 22 ++ package-lock.json | 411 ++++++++++++++++++++++- package.json | 2 +- plugins/node-test/.toolkitrc.yml | 8 + plugins/node-test/jest.config.js | 5 + plugins/node-test/package.json | 42 +++ plugins/node-test/src/tasks/node-test.ts | 61 ++++ plugins/node-test/tsconfig.json | 17 + release-please-config.json | 1 + tsconfig.json | 3 + 11 files changed, 558 insertions(+), 16 deletions(-) create mode 100644 lib/schemas/src/tasks/node-test.ts create mode 100644 plugins/node-test/.toolkitrc.yml create mode 100644 plugins/node-test/jest.config.js create mode 100644 plugins/node-test/package.json create mode 100644 plugins/node-test/src/tasks/node-test.ts create mode 100644 plugins/node-test/tsconfig.json diff --git a/lib/schemas/src/tasks.ts b/lib/schemas/src/tasks.ts index d037042c7..f1d53f309 100644 --- a/lib/schemas/src/tasks.ts +++ b/lib/schemas/src/tasks.ts @@ -5,6 +5,7 @@ import { JestSchema } from './tasks/jest' import { MochaSchema } from './tasks/mocha' import { NodeSchema } from './tasks/node' import { NodemonSchema } from './tasks/nodemon' +import { NodeTestSchema } from './tasks/node-test' import { PrettierSchema } from './tasks/prettier' import { TypeScriptSchema } from './tasks/typescript' import { UploadAssetsToS3Schema } from './tasks/upload-assets-to-s3' @@ -29,6 +30,7 @@ export const TaskSchemas = { Mocha: MochaSchema, Node: NodeSchema, Nodemon: NodemonSchema, + NodeTest: NodeTestSchema, NpmPrune: z.object({}).describe('Prune development npm dependencies.'), NpmPublish: z.object({}).describe('Publish package to the npm registry.'), NTest: SmokeTestSchema, diff --git a/lib/schemas/src/tasks/node-test.ts b/lib/schemas/src/tasks/node-test.ts new file mode 100644 index 000000000..7459b57c5 --- /dev/null +++ b/lib/schemas/src/tasks/node-test.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +export const NodeTestSchema = z + .object({ + concurrency: z + .number() + .int() + .or(z.boolean()) + .default(false) + .describe('The number of tests to run in parallel. See https://nodejs.org/api/test.html#runoptions'), + files: z.string().array().optional().describe('The glob patterns for test files'), + ignore: z.string().array().optional().describe('Glob patterns for test files to ignore'), + forceExit: z + .boolean() + .default(false) + .describe('Whether to force exit the process once all tests have finished executing') + }) + .describe('Runs the built-in Node.js test runner to execute tests.') + +export type NodeTestOptions = z.infer + +export const Schema = NodeTestSchema diff --git a/package-lock.json b/package-lock.json index 90857ba17..f8db999a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@financial-times/eslint-config-next": "^6.0.0", "@tsconfig/node16": "^1.0.3", "@types/jest": "^27.4.0", - "@types/node": "^16.18.23", + "@types/node": "^18.19.50", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "check-engines": "^1.5.0", @@ -88,9 +88,9 @@ } }, "core/cli/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "version": "16.18.108", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.108.tgz", + "integrity": "sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==", "dev": true }, "core/cli/node_modules/globby": { @@ -994,9 +994,9 @@ } }, "core/create/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "version": "16.18.108", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.108.tgz", + "integrity": "sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==", "dev": true }, "core/create/node_modules/code-suggester": { @@ -1469,9 +1469,9 @@ } }, "lib/wait-for-ok/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "version": "16.18.108", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.108.tgz", + "integrity": "sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==", "dev": true }, "lib/wait-for-ok/node_modules/tslib": { @@ -6523,6 +6523,10 @@ "resolved": "plugins/node", "link": true }, + "node_modules/@dotcom-tool-kit/node-test": { + "resolved": "plugins/node-test", + "link": true + }, "node_modules/@dotcom-tool-kit/nodemon": { "resolved": "plugins/nodemon", "link": true @@ -7462,6 +7466,95 @@ "dev": true, "license": "ISC" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "license": "ISC", @@ -9472,6 +9565,15 @@ "@octokit/openapi-types": "^11.2.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@quarterto/parse-makefile-rules": { "version": "1.1.0" }, @@ -10328,9 +10430,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "16.18.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", - "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==" + "version": "18.19.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", + "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -10342,6 +10447,11 @@ "form-data": "^3.0.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/@types/nodemon": { "version": "1.19.1", "dev": true, @@ -14658,8 +14768,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "peer": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ecc-jsbn": { "version": "0.1.2", @@ -16354,6 +16463,78 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "license": "Apache-2.0", @@ -18416,6 +18597,23 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/java-invoke-local": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/java-invoke-local/-/java-invoke-local-0.0.6.tgz", @@ -23556,6 +23754,11 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/package-json/node_modules/semver": { "version": "6.3.0", "license": "ISC", @@ -24090,6 +24293,37 @@ "version": "1.0.7", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-type": { "version": "4.0.0", "license": "MIT", @@ -27568,6 +27802,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -27643,6 +27891,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "license": "MIT", @@ -28793,6 +29053,12 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "node_modules/uni-global": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/uni-global/-/uni-global-1.0.0.tgz", @@ -30111,6 +30377,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "license": "ISC" @@ -32238,6 +32521,104 @@ "dotcom-tool-kit": "4.x" } }, + "plugins/node-test": { + "name": "@dotcom-tool-kit/node-test", + "version": "4.0.0", + "license": "ISC", + "dependencies": { + "@dotcom-tool-kit/base": "^1.0.0", + "@dotcom-tool-kit/error": "^4.0.0", + "glob": "^11.0.0" + }, + "devDependencies": { + "@dotcom-tool-kit/schemas": "^1.0.0", + "@types/glob": "^8.1.0" + }, + "engines": { + "node": "^18.17 || 20.x", + "npm": "7.x || 8.x || 9.x || 10.x" + }, + "peerDependencies": { + "dotcom-tool-kit": "4.x" + } + }, + "plugins/node-test/node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "plugins/node-test/node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "plugins/node-test/node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "plugins/node-test/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "plugins/node-test/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "plugins/node-test/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "plugins/node-test/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "plugins/node/node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", diff --git a/package.json b/package.json index 54bd3bf64..8fe96e54f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@financial-times/eslint-config-next": "^6.0.0", "@tsconfig/node16": "^1.0.3", "@types/jest": "^27.4.0", - "@types/node": "^16.18.23", + "@types/node": "^18.19.50", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "check-engines": "^1.5.0", diff --git a/plugins/node-test/.toolkitrc.yml b/plugins/node-test/.toolkitrc.yml new file mode 100644 index 000000000..e2a80b889 --- /dev/null +++ b/plugins/node-test/.toolkitrc.yml @@ -0,0 +1,8 @@ +tasks: + NodeTest: './lib/tasks/node-test' + +commands: + 'test:local': NodeTest + 'test:ci': NodeTest + +version: 2 diff --git a/plugins/node-test/jest.config.js b/plugins/node-test/jest.config.js new file mode 100644 index 000000000..2ea38bb31 --- /dev/null +++ b/plugins/node-test/jest.config.js @@ -0,0 +1,5 @@ +const base = require('../../jest.config.base') + +module.exports = { + ...base.config +} diff --git a/plugins/node-test/package.json b/plugins/node-test/package.json new file mode 100644 index 000000000..1a3067ed0 --- /dev/null +++ b/plugins/node-test/package.json @@ -0,0 +1,42 @@ +{ + "name": "@dotcom-tool-kit/node-test", + "version": "4.0.0", + "description": "", + "main": "lib", + "scripts": { + "test": "cd ../../ ; npx jest --silent --projects plugins/node-test" + }, + "keywords": [], + "author": "FT.com Platforms Team ", + "license": "ISC", + "dependencies": { + "@dotcom-tool-kit/base": "^1.0.0", + "@dotcom-tool-kit/error": "^4.0.0", + "glob": "^11.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/financial-times/dotcom-tool-kit.git", + "directory": "plugins/node-test" + }, + "bugs": "https://github.com/financial-times/dotcom-tool-kit/issues", + "homepage": "https://github.com/financial-times/dotcom-tool-kit/tree/main/plugins/node-test", + "devDependencies": { + "@dotcom-tool-kit/schemas": "^1.0.0", + "@types/glob": "^8.1.0" + }, + "files": [ + "/lib", + ".toolkitrc.yml" + ], + "volta": { + "extends": "../../package.json" + }, + "peerDependencies": { + "dotcom-tool-kit": "4.x" + }, + "engines": { + "node": "^18.17 || 20.x", + "npm": "7.x || 8.x || 9.x || 10.x" + } +} diff --git a/plugins/node-test/src/tasks/node-test.ts b/plugins/node-test/src/tasks/node-test.ts new file mode 100644 index 000000000..4b3894494 --- /dev/null +++ b/plugins/node-test/src/tasks/node-test.ts @@ -0,0 +1,61 @@ +import { glob } from 'glob' +import { NodeTestSchema } from '@dotcom-tool-kit/schemas/lib/tasks/node-test' +import { run } from 'node:test' +import { Task } from '@dotcom-tool-kit/base' +import { ToolKitError } from '@dotcom-tool-kit/error' + +// HACK: this is a nasty list of file patterns to maintain however it's unavoidable +// if we want to make this plugin work consistently between Node.js 18–22. In future +// (when we drop Node.js 20 support) we will be able to remove this and rely on the +// built-in patterns. +// +// See https://nodejs.org/api/test.html#running-tests-from-the-command-line +const defaultFilePatterns = [ + '**/*.test.?(c|m)js', + '**/*-test.?(c|m)js', + '**/*_test.?(c|m)js', + '**/test-*.?(c|m)js', + '**/test.?(c|m)js', + '**/test/**/*.?(c|m)js' +] + +export default class NodeTest extends Task<{ task: typeof NodeTestSchema }> { + async run(): Promise { + // HACK: Node.js <18.17 errors cryptically because this module doesn't + // exist. It's better to be explicit. We can switch this to a regular + // import when we drop Node.js 18 support. + try { + require.resolve('node:test/reporters') + } catch (error) { + throw new ToolKitError('This plugin requires Node.js 18.17+') + } + // HACK: eslint-plugin-import doesn't seem to know this module exists + // eslint-disable-next-line import/no-unresolved + const { spec } = await import('node:test/reporters') + + const concurrency = this.options.concurrency || false + const cwd = process.cwd() + const filePatterns = this.options.files ?? defaultFilePatterns + const forceExit = this.options.forceExit + const ignore = ['**/node_modules/**', ...(this.options.ignore ?? [])] + + const files = await glob(filePatterns, { cwd, ignore }) + + await new Promise((resolve, reject) => { + let hasFails = false + run({ concurrency, files, forceExit }) + .on('test:fail', () => { + hasFails = true + }) + .compose(new spec()) + .on('close', () => { + if (hasFails) { + return reject(new ToolKitError('Some tests failed')) + } + this.logger.info('All tests passed') + return resolve() + }) + .pipe(process.stdout) + }) + } +} diff --git a/plugins/node-test/tsconfig.json b/plugins/node-test/tsconfig.json new file mode 100644 index 000000000..4fee1d3ce --- /dev/null +++ b/plugins/node-test/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.settings.json", + "references": [ + { + "path": "../../lib/base" + }, + { + "path": "../../lib/schemas" + } + ], + "include": ["src/**/*"], + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "types": ["glob"] + } +} diff --git a/release-please-config.json b/release-please-config.json index 8212ec7d3..dc4b99b8e 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -43,6 +43,7 @@ "plugins/n-test": {}, "plugins/next-router": {}, "plugins/node": {}, + "plugins/node-test": {}, "plugins/nodemon": {}, "plugins/npm": {}, "plugins/package-json-hook": {}, diff --git a/tsconfig.json b/tsconfig.json index 9c507516b..27d7aae1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -82,6 +82,9 @@ { "path": "plugins/node" }, + { + "path": "plugins/node-test" + }, { "path": "plugins/next-router" },