From 69263b3efc78e42d03ec8d750bef8f5d67614654 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 6 Mar 2026 08:12:28 +0000 Subject: [PATCH] feat(testfile-directives): Add per-test file directives to control runtime permissions and flags (Deno, Node, Bun, Chromium) --- changelog.md | 10 + npmextra.json | 2 +- package.json | 16 +- pnpm-lock.yaml | 170 +++++++++-------- readme.md | 64 +++++++ test/test.directives.node.ts | 153 +++++++++++++++ ts/00_commitinfo_data.ts | 4 +- ts/tstest.classes.runtime.deno.ts | 36 ++-- ts/tstest.classes.testfile.directives.ts | 226 +++++++++++++++++++++++ ts/tstest.classes.tstest.ts | 26 ++- 10 files changed, 593 insertions(+), 114 deletions(-) create mode 100644 test/test.directives.node.ts create mode 100644 ts/tstest.classes.testfile.directives.ts diff --git a/changelog.md b/changelog.md index 6246902..70303c5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2026-03-06 - 3.3.0 - feat(testfile-directives) +Add per-test file directives to control runtime permissions and flags (Deno, Node, Bun, Chromium) + +- Introduce test file directive parser (ts/tstest.classes.testfile.directives.ts) to parse comments like // tstest:deno:allowAll and map them to runtime options. +- Add DENO_DEFAULT_PERMISSIONS constant and centralize Deno default flags (ts/tstest.classes.runtime.deno.ts) to avoid repeating the list. +- Integrate directives into the test runner (ts/tstest.classes.tstest.ts): read directives from test files and optional 00init.ts, merge them, and pass runtime-specific options to adapters. +- Documentation: add a "Test File Directives" section to readme.md with examples and available directives. +- Add automated tests for directives behavior (test/test.directives.node.ts). +- Bump package metadata and minor dependency updates; update package description and npmextra.json to reflect new functionality. + ## 2026-03-03 - 3.2.0 - feat(tapbundle_serverside) add network port discovery utilities and migrate file I/O to smartfs; refactor runtimes to use Node fs and SmartFs, update server APIs and bump dependencies diff --git a/npmextra.json b/npmextra.json index 4ae23f7..6fa1f0b 100644 --- a/npmextra.json +++ b/npmextra.json @@ -5,7 +5,7 @@ "githost": "code.foss.global", "gitscope": "git.zone", "gitrepo": "tstest", - "description": "a test utility to run tests that match test/**/*.ts", + "description": "A powerful, modern test runner for TypeScript with multi-runtime support (Node.js, Deno, Bun, Chromium) and a batteries-included test framework.", "npmPackagename": "@git.zone/tstest", "license": "MIT" }, diff --git a/package.json b/package.json index a44f2a5..06d4a09 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@git.zone/tstest", "version": "3.2.0", "private": false, - "description": "a test utility to run tests that match test/**/*.ts", + "description": "A powerful, modern test runner for TypeScript with multi-runtime support (Node.js, Deno, Bun, Chromium) and a batteries-included test framework.", "exports": { ".": "./dist_ts/index.js", "./tapbundle": "./dist_ts_tapbundle/index.js", @@ -25,12 +25,12 @@ "buildDocs": "tsdoc" }, "devDependencies": { - "@git.zone/tsbuild": "^4.1.2", - "@types/node": "^22.15.21" + "@git.zone/tsbuild": "^4.3.0", + "@types/node": "^25.3.5" }, "dependencies": { - "@api.global/typedserver": "^8.4.0", - "@git.zone/tsbundle": "^2.9.0", + "@api.global/typedserver": "^8.4.2", + "@git.zone/tsbundle": "^2.9.1", "@git.zone/tsrun": "^2.0.1", "@push.rocks/consolecolor": "^2.0.3", "@push.rocks/qenv": "^6.1.3", @@ -40,7 +40,7 @@ "@push.rocks/smartenv": "^6.0.0", "@push.rocks/smartexpect": "^2.5.0", "@push.rocks/smartfile": "^13.1.2", - "@push.rocks/smartfs": "^1.3.1", + "@push.rocks/smartfs": "^1.4.0", "@push.rocks/smartjson": "^6.0.0", "@push.rocks/smartlog": "^3.2.1", "@push.rocks/smartmongo": "^5.1.0", @@ -49,9 +49,9 @@ "@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smarts3": "^5.3.0", - "@push.rocks/smartshell": "^3.3.0", - "@push.rocks/smartwatch": "^6.3.0", + "@push.rocks/smartshell": "^3.3.7", "@push.rocks/smarttime": "^4.2.3", + "@push.rocks/smartwatch": "^6.3.0", "@types/ws": "^8.18.1", "figures": "^6.1.0", "ws": "^8.19.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 020686c..9da68b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@api.global/typedserver': - specifier: ^8.4.0 - version: 8.4.0(@tiptap/pm@2.27.2) + specifier: ^8.4.2 + version: 8.4.2(@tiptap/pm@2.27.2) '@git.zone/tsbundle': - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^2.9.1 + version: 2.9.1 '@git.zone/tsrun': specifier: ^2.0.1 version: 2.0.1 @@ -42,8 +42,8 @@ importers: specifier: ^13.1.2 version: 13.1.2 '@push.rocks/smartfs': - specifier: ^1.3.1 - version: 1.3.1 + specifier: ^1.4.0 + version: 1.4.0 '@push.rocks/smartjson': specifier: ^6.0.0 version: 6.0.0 @@ -69,8 +69,8 @@ importers: specifier: ^5.3.0 version: 5.3.0 '@push.rocks/smartshell': - specifier: ^3.3.0 - version: 3.3.0 + specifier: ^3.3.7 + version: 3.3.7 '@push.rocks/smarttime': specifier: ^4.2.3 version: 4.2.3 @@ -88,11 +88,11 @@ importers: version: 8.19.0 devDependencies: '@git.zone/tsbuild': - specifier: ^4.1.2 - version: 4.1.2 + specifier: ^4.3.0 + version: 4.3.0 '@types/node': - specifier: ^22.15.21 - version: 22.19.1 + specifier: ^25.3.5 + version: 25.3.5 packages: @@ -108,8 +108,8 @@ packages: '@api.global/typedrequest@3.2.7': resolution: {integrity: sha512-9CC8EojPDraKlwWK3ZjM8/wJ9jguY/kc+pCgcd61epHFXTIKC8jYts3vKPmEkBPno5Ejn3JZgqp/GRzplCC51w==} - '@api.global/typedserver@8.4.0': - resolution: {integrity: sha512-qOa5jUwiuHEoY1ZuLZiuU1unPl5JNd99Vv+hNAwgEIgAZd4TTy/mjdTp7lyviBzkGf2pROeCmAbDJJF8YQFCSA==} + '@api.global/typedserver@8.4.2': + resolution: {integrity: sha512-eESOcWvrbqkshR4s4OeTX1AK74bNCeGgiRebKgjxIzJ+b0+rkPQyn2DOaMtyXjFZRNgRHyytLm5Iqj5fdazeqw==} '@api.global/typedsocket@4.1.2': resolution: {integrity: sha512-fZFuJY9ucFCICjF4wi6OvK8drsv6UcwVVsfamOT1HxFj7OBOYw6QHOceQ+cAQ8IrWbX817sf8gzlesl+jlG8JA==} @@ -505,16 +505,16 @@ packages: resolution: {integrity: sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==} engines: {node: '>=6'} - '@git.zone/tsbuild@4.1.2': - resolution: {integrity: sha512-S518ulKveO76pS6jrAELrnFaCw5nDAIZD9j6QzVmLYDiZuJmlRwPK3/2E8ugQ+b7ffpkwJ9MT685ooEGDcWQ4Q==} + '@git.zone/tsbuild@4.3.0': + resolution: {integrity: sha512-lb6eMQ8RQPaJqAB4kC++GIElOiTAH1pClmoND/q7XHuiMZxv6cXz2/U/sZt339mon2c40dXRG2tkLF2jRsP0pQ==} hasBin: true - '@git.zone/tsbundle@2.9.0': - resolution: {integrity: sha512-itXX/oiJjrRHUlIGTHUEqSwPuGwsG4Cq8kh7aqFOm8mYzJwtXYE1gBqLJTWZma6gI5n+xAk5qTxTyfikuPgWQA==} + '@git.zone/tsbundle@2.9.1': + resolution: {integrity: sha512-JW1xjSv7UjAm2lwAQPxhCWs14wqs+UIq5FqIGUPuI6rrDBWIMT2d0gpP6iP6TqXqgm6XpBlfU4rHcHheUXzXbQ==} hasBin: true - '@git.zone/tspublish@1.11.0': - resolution: {integrity: sha512-dkgaDBTzZJ53lAV72r7OW/W7l/KqpkncFuPojr11JO35OKAbjjDhZbAwPv4oGX9NplyXrhC5VJRPNX/orqNTHA==} + '@git.zone/tspublish@1.11.2': + resolution: {integrity: sha512-BcGap1OzXDgXpfQXMh9W17r/CkWNhPsJ3WzjG2wrGE+ePUJCJAm9w6+J8G5WdZZcZKPqTB07cp707LbSiksc5A==} hasBin: true '@git.zone/tsrun@2.0.1': @@ -804,9 +804,6 @@ packages: '@push.rocks/smartcache@1.0.18': resolution: {integrity: sha512-3+cmLu9chbnmi4yD4kjlFP/Tn4NReaZIoicEcGTtwbcokTrSDMs3YPdJzIpDZkAs83PW7OcVSHa3Ak5KU5OWzA==} - '@push.rocks/smartcli@4.0.19': - resolution: {integrity: sha512-s1jZSgDZWi/az26AY4TJ2HPuG1qZzGC5R9fKWaECLmwnSpk6y9JXL5dnJAUohcdu50kdXCWEcRmLfYxOt81vEA==} - '@push.rocks/smartcli@4.0.20': resolution: {integrity: sha512-gCo4ItvsPj8WoVAJw/6vkuoGA5FtIoACux2ktcCeH0nrFe7/xGR6waJ1aZcYAi7QN4gi52TlsgwuKz7BzXqhmQ==} @@ -837,6 +834,9 @@ packages: '@push.rocks/smartexit@1.0.23': resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==} + '@push.rocks/smartexit@2.0.3': + resolution: {integrity: sha512-ZWpZ3Elorpv/rKtUcCUejUHG4BIE5B3QWysBAgb7lTcA7y0vGdFY32Y5/Q5tHpZM6PPxl/WTdUOYtSojQTq+pA==} + '@push.rocks/smartexpect@2.5.0': resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==} @@ -852,8 +852,8 @@ packages: '@push.rocks/smartfile@13.1.2': resolution: {integrity: sha512-DaEhwmnGEpX4coeeToaw4cZe3pNBhH7CY1iGr+d3pIXihozREvzzAR9/0i2r7bUXXL5+Lgy8YYIk5ZS+fwxMKA==} - '@push.rocks/smartfs@1.3.1': - resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==} + '@push.rocks/smartfs@1.4.0': + resolution: {integrity: sha512-4PgteGOyMBiUWKLfTXOjxrZz+sXPLnvcmHeAEHY2gwZJflfp5/YDVPhodctOydersXzkynO359dIGLSCyQnnAQ==} '@push.rocks/smartguard@3.1.0': resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} @@ -960,8 +960,8 @@ packages: '@push.rocks/smartserve@2.0.1': resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==} - '@push.rocks/smartshell@3.3.0': - resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} + '@push.rocks/smartshell@3.3.7': + resolution: {integrity: sha512-b3st2+FjHUVhZZRlXfw93+SQA0UMVlURqe55uVpWdjJX7jeGXTTeszuYygtiR99zC5iZ8WZhGDct3N2L1qc/qw==} '@push.rocks/smartsitemap@2.0.4': resolution: {integrity: sha512-76dYWG/o/EjV4vYCK7ZKM35T9xgrI+oHEiiIE6E2MDaFIU6QnSfciTfbscH5nc0vxx8Ah+I0HPEJO94BM2S39w==} @@ -1697,6 +1697,9 @@ packages: '@types/node@22.19.1': resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + '@types/node@25.3.5': + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} + '@types/ping@0.4.4': resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} @@ -2601,9 +2604,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} isopen@1.3.0: resolution: {integrity: sha512-AN6Q9J0UlqHFl1fN/2xJCHCBLCBCFDjZhpGBO1gh3wzgRPsFSFBUL36I2Lbfd9qkuoj58axmE7j83iejTQsk8Q==} @@ -3653,6 +3656,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3729,9 +3735,9 @@ packages: engines: {node: '>= 8'} hasBin: true - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true wrap-ansi@6.2.0: @@ -3837,7 +3843,7 @@ snapshots: '@push.rocks/webrequest': 4.0.5 '@push.rocks/webstream': 1.0.10 - '@api.global/typedserver@8.4.0(@tiptap/pm@2.27.2)': + '@api.global/typedserver@8.4.2(@tiptap/pm@2.27.2)': dependencies: '@api.global/typedrequest': 3.2.7 '@api.global/typedrequest-interfaces': 3.0.19 @@ -3845,12 +3851,12 @@ snapshots: '@cloudflare/workers-types': 4.20260305.0 '@design.estate/dees-catalog': 3.43.3(@tiptap/pm@2.27.2) '@design.estate/dees-comms': 1.0.30 - '@push.rocks/lik': 6.2.2 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartfeed': 1.4.0 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartlog-destination-devtools': 1.0.12 @@ -3870,10 +3876,10 @@ snapshots: '@push.rocks/smarttime': 4.2.3 '@push.rocks/smartwatch': 6.3.0 '@push.rocks/taskbuffer': 3.5.0 - '@push.rocks/webrequest': 4.0.1 + '@push.rocks/webrequest': 4.0.5 '@push.rocks/webstore': 2.0.20 '@tsclass/tsclass': 9.3.0 - lit: 3.3.1 + lit: 3.3.2 transitivePeerDependencies: - '@nuxt/kit' - '@tiptap/pm' @@ -4467,7 +4473,7 @@ snapshots: dependencies: '@api.global/typedrequest': 3.2.7 '@design.estate/dees-comms': 1.0.30 - '@push.rocks/lik': 6.2.2 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartmarkdown': 3.0.3 @@ -4637,14 +4643,14 @@ snapshots: dependencies: '@fortawesome/fontawesome-common-types': 7.2.0 - '@git.zone/tsbuild@4.1.2': + '@git.zone/tsbuild@4.3.0': dependencies: - '@git.zone/tspublish': 1.11.0 + '@git.zone/tspublish': 1.11.2 '@push.rocks/early': 4.0.4 - '@push.rocks/smartcli': 4.0.19 + '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 @@ -4658,13 +4664,13 @@ snapshots: - supports-color - vue - '@git.zone/tsbundle@2.9.0': + '@git.zone/tsbundle@2.9.1': dependencies: '@push.rocks/early': 4.0.4 '@push.rocks/npmextra': 5.3.3 '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartinteract': 2.0.16 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartlog-destination-local': 9.0.2 @@ -4684,19 +4690,19 @@ snapshots: - supports-color - vue - '@git.zone/tspublish@1.11.0': + '@git.zone/tspublish@1.11.2': dependencies: '@push.rocks/consolecolor': 2.0.3 '@push.rocks/npmextra': 5.3.3 - '@push.rocks/smartcli': 4.0.19 + '@push.rocks/smartcli': 4.0.20 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartnpm': 2.0.6 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartrequest': 5.0.1 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.7 transitivePeerDependencies: - '@nuxt/kit' - aws-crt @@ -4709,7 +4715,7 @@ snapshots: '@git.zone/tsrun@2.0.1': dependencies: '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.7 tsx: 4.21.0 '@happy-dom/global-registrator@15.11.7': @@ -5066,7 +5072,7 @@ snapshots: '@push.rocks/levelcache@3.2.0': dependencies: - '@push.rocks/lik': 6.2.2 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartbucket': 3.3.10 '@push.rocks/smartcache': 1.0.18 '@push.rocks/smartenv': 5.0.13 @@ -5077,7 +5083,7 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartstring': 4.1.0 '@push.rocks/smartunique': 3.0.9 - '@push.rocks/taskbuffer': 3.4.0 + '@push.rocks/taskbuffer': 3.5.0 '@tsclass/tsclass': 9.3.0 transitivePeerDependencies: - '@nuxt/kit' @@ -5135,7 +5141,7 @@ snapshots: '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 - '@push.rocks/taskbuffer': 3.4.0 + '@push.rocks/taskbuffer': 3.5.0 '@tsclass/tsclass': 9.3.0 transitivePeerDependencies: - '@nuxt/kit' @@ -5213,15 +5219,6 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smarttime': 4.2.3 - '@push.rocks/smartcli@4.0.19': - dependencies: - '@push.rocks/lik': 6.2.2 - '@push.rocks/smartlog': 3.2.1 - '@push.rocks/smartobject': 1.0.12 - '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrx': 3.0.10 - yargs-parser: 22.0.0 - '@push.rocks/smartcli@4.0.20': dependencies: '@push.rocks/lik': 6.2.2 @@ -5315,6 +5312,11 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 tree-kill: 1.2.2 + '@push.rocks/smartexit@2.0.3': + dependencies: + '@push.rocks/lik': 6.3.1 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartexpect@2.5.0': dependencies: '@push.rocks/smartdelay': 3.0.5 @@ -5351,7 +5353,7 @@ snapshots: '@push.rocks/lik': 6.3.1 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfile-interfaces': 1.0.7 - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smarthash': 3.2.6 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartmime': 2.0.4 @@ -5363,9 +5365,10 @@ snapshots: glob: 11.1.0 js-yaml: 4.1.1 - '@push.rocks/smartfs@1.3.1': + '@push.rocks/smartfs@1.4.0': dependencies: '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartrust': 1.3.1 '@push.rocks/smartguard@3.1.0': dependencies: @@ -5384,7 +5387,7 @@ snapshots: '@push.rocks/smartinteract@2.0.16': dependencies: - '@push.rocks/lik': 6.2.2 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartobject': 1.0.12 '@push.rocks/smartpromise': 4.2.3 inquirer: 11.1.0 @@ -5484,7 +5487,7 @@ snapshots: dependencies: '@push.rocks/mongodump': 1.1.0(socks@2.8.7) '@push.rocks/smartdata': 5.16.7(socks@2.8.7) - '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smartfs': 1.4.0 '@push.rocks/smartpath': 5.1.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrx': 3.0.10 @@ -5540,7 +5543,7 @@ snapshots: '@push.rocks/smartntml@2.0.8': dependencies: - '@design.estate/dees-element': 2.1.3 + '@design.estate/dees-element': 2.1.6 '@happy-dom/global-registrator': 15.11.7 '@push.rocks/smartpromise': 4.2.3 fake-indexeddb: 6.2.5 @@ -5597,7 +5600,7 @@ snapshots: '@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.7 puppeteer: 24.31.0(typescript@5.9.3) tree-kill: 1.2.2 transitivePeerDependencies: @@ -5659,7 +5662,7 @@ snapshots: dependencies: '@api.global/typedrequest': 3.2.7 '@cfworker/json-schema': 4.1.1 - '@push.rocks/lik': 6.2.2 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpath': 6.0.0 @@ -5668,14 +5671,13 @@ snapshots: - bufferutil - utf-8-validate - '@push.rocks/smartshell@3.3.0': + '@push.rocks/smartshell@3.3.7': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartexit': 1.0.23 + '@push.rocks/smartexit': 2.0.3 '@push.rocks/smartpromise': 4.2.3 '@types/which': 3.0.4 - tree-kill: 1.2.2 - which: 5.0.0 + which: 6.0.1 '@push.rocks/smartsitemap@2.0.4': dependencies: @@ -5683,7 +5685,7 @@ snapshots: '@push.rocks/smartfeed': 1.4.0 '@push.rocks/smartxml': 2.0.0 '@push.rocks/smartyaml': 3.0.4 - '@push.rocks/webrequest': 4.0.1 + '@push.rocks/webrequest': 4.0.5 '@tsclass/tsclass': 9.3.0 '@push.rocks/smartspawn@3.0.3': @@ -5779,8 +5781,8 @@ snapshots: '@push.rocks/taskbuffer@3.5.0': dependencies: - '@design.estate/dees-element': 2.1.3 - '@push.rocks/lik': 6.2.2 + '@design.estate/dees-element': 2.1.6 + '@push.rocks/lik': 6.3.1 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartpromise': 4.2.3 @@ -6518,7 +6520,7 @@ snapshots: '@types/clean-css@4.2.11': dependencies: - '@types/node': 22.19.1 + '@types/node': 25.3.5 source-map: 0.6.1 '@types/connect@3.4.38': @@ -6600,7 +6602,7 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.19.1 + '@types/node': 25.3.5 '@types/node-forge@1.3.14': dependencies: @@ -6610,6 +6612,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@25.3.5': + dependencies: + undici-types: 7.18.2 + '@types/ping@0.4.4': {} '@types/qs@6.14.0': {} @@ -7581,7 +7587,7 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: {} + isexe@4.0.0: {} isopen@1.3.0: {} @@ -8924,6 +8930,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.18.2: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -9003,9 +9011,9 @@ snapshots: dependencies: isexe: 2.0.0 - which@5.0.0: + which@6.0.1: dependencies: - isexe: 3.1.1 + isexe: 4.0.0 wrap-ansi@6.2.0: dependencies: diff --git a/readme.md b/readme.md index 9d3dfa4..f6dc579 100644 --- a/readme.md +++ b/readme.md @@ -442,6 +442,70 @@ const s3 = await tapNodeTools.createSmarts3(); await s3.stop(); ``` +## Test File Directives + +Control runtime behavior directly from your test files using special comment directives at the top of the file. Directives must appear before any `import` statements. + +### Deno Permissions + +By default, Deno tests run with `--allow-read`, `--allow-env`, `--allow-net`, `--allow-write`, `--allow-sys`, and `--allow-import`. Add directives to request additional permissions: + +```typescript +// tstest:deno:allowAll +import { tap, expect } from '@git.zone/tstest/tapbundle'; + +tap.test('test with full Deno permissions', async () => { + // Runs with --allow-all (e.g., for FFI, subprocess spawning, etc.) +}); + +export default tap.start(); +``` + +### Available Directives + +| Directive | Effect | +|---|---| +| `// tstest:deno:allowAll` | Grants all Deno permissions (`--allow-all`) | +| `// tstest:deno:allowRun` | Adds `--allow-run` for subprocess spawning | +| `// tstest:deno:allowFfi` | Adds `--allow-ffi` for native library calls | +| `// tstest:deno:allowHrtime` | Adds `--allow-hrtime` for high-res timers | +| `// tstest:deno:flag:--unstable-ffi` | Passes any arbitrary Deno flag | +| `// tstest:node:flag:--max-old-space-size=4096` | Passes flags to Node.js | +| `// tstest:bun:flag:--smol` | Passes flags to Bun | + +### Multiple Directives + +Combine as many directives as needed: + +```typescript +// tstest:deno:allowRun +// tstest:deno:allowFfi +// tstest:deno:flag:--unstable-ffi +import { tap, expect } from '@git.zone/tstest/tapbundle'; + +tap.test('test with Rust FFI', async () => { + // Has --allow-run, --allow-ffi, and --unstable-ffi in addition to defaults +}); + +export default tap.start(); +``` + +### Shared Directives via 00init.ts + +Directives in a `00init.ts` file apply to all test files in that directory. Test file directives are merged with (and extend) init file directives. + +```typescript +// test/00init.ts +// tstest:deno:allowRun +``` + +```typescript +// test/test.mytest.deno.ts +// tstest:deno:allowFfi +// Both --allow-run (from 00init.ts) and --allow-ffi are active +import { tap, expect } from '@git.zone/tstest/tapbundle'; +``` + ## Advanced Features ### Watch Mode diff --git a/test/test.directives.node.ts b/test/test.directives.node.ts new file mode 100644 index 0000000..10cac7c --- /dev/null +++ b/test/test.directives.node.ts @@ -0,0 +1,153 @@ +import { expect, tap } from '../ts_tapbundle/index.js'; +import { + parseDirectivesFromContent, + mergeDirectives, + directivesToRuntimeOptions, + hasDirectives, +} from '../ts/tstest.classes.testfile.directives.js'; + +tap.test('parseDirectivesFromContent - deno allowAll', async () => { + const content = `// tstest:deno:allowAll +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(1); + expect(directives.deno[0].key).toEqual('allowAll'); + expect(directives.deno[0].scope).toEqual('deno'); +}); + +tap.test('parseDirectivesFromContent - multiple deno directives', async () => { + const content = `// tstest:deno:allowRun +// tstest:deno:allowFfi +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(2); + expect(directives.deno[0].key).toEqual('allowRun'); + expect(directives.deno[1].key).toEqual('allowFfi'); +}); + +tap.test('parseDirectivesFromContent - flag directive with value', async () => { + const content = `// tstest:deno:flag:--unstable-ffi +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(1); + expect(directives.deno[0].key).toEqual('flag'); + expect(directives.deno[0].value).toEqual('--unstable-ffi'); +}); + +tap.test('parseDirectivesFromContent - node flag directive', async () => { + const content = `// tstest:node:flag:--max-old-space-size=4096 +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.node.length).toEqual(1); + expect(directives.node[0].key).toEqual('flag'); + expect(directives.node[0].value).toEqual('--max-old-space-size=4096'); +}); + +tap.test('parseDirectivesFromContent - empty lines before directives', async () => { + const content = ` +// tstest:deno:allowAll +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(1); + expect(directives.deno[0].key).toEqual('allowAll'); +}); + +tap.test('parseDirectivesFromContent - stops at first non-comment line', async () => { + const content = `// tstest:deno:allowRun +import { tap } from '../tapbundle/index.js'; +// tstest:deno:allowFfi +`; + const directives = parseDirectivesFromContent(content); + // Should only find allowRun, not allowFfi (after import) + expect(directives.deno.length).toEqual(1); + expect(directives.deno[0].key).toEqual('allowRun'); +}); + +tap.test('parseDirectivesFromContent - no directives returns empty', async () => { + const content = `import { tap } from '../tapbundle/index.js'; +tap.test('foo', async () => {}); +`; + const directives = parseDirectivesFromContent(content); + expect(hasDirectives(directives)).toEqual(false); +}); + +tap.test('parseDirectivesFromContent - regular comments are skipped', async () => { + const content = `// This is a regular comment +// tstest:deno:allowAll +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(1); +}); + +tap.test('parseDirectivesFromContent - mixed runtime directives', async () => { + const content = `// tstest:deno:allowRun +// tstest:bun:flag:--smol +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + expect(directives.deno.length).toEqual(1); + expect(directives.bun.length).toEqual(1); + expect(directives.bun[0].key).toEqual('flag'); + expect(directives.bun[0].value).toEqual('--smol'); +}); + +tap.test('directivesToRuntimeOptions - deno allowAll', async () => { + const content = `// tstest:deno:allowAll +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + const options = directivesToRuntimeOptions(directives, 'deno') as any; + expect(options).toBeTruthy(); + expect(options.permissions).toContain('--allow-all'); + expect(options.permissions).not.toContain('--allow-read'); +}); + +tap.test('directivesToRuntimeOptions - deno extra permissions', async () => { + const content = `// tstest:deno:allowRun +// tstest:deno:allowFfi +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + const options = directivesToRuntimeOptions(directives, 'deno') as any; + expect(options).toBeTruthy(); + expect(options.permissions).toContain('--allow-run'); + expect(options.permissions).toContain('--allow-ffi'); + // Should still contain defaults + expect(options.permissions).toContain('--allow-read'); + expect(options.permissions).toContain('--allow-env'); +}); + +tap.test('directivesToRuntimeOptions - no directives returns undefined', async () => { + const directives = parseDirectivesFromContent('import { tap } from "tapbundle";'); + const options = directivesToRuntimeOptions(directives, 'deno'); + expect(options).toBeUndefined(); +}); + +tap.test('directivesToRuntimeOptions - node flag directive', async () => { + const content = `// tstest:node:flag:--max-old-space-size=4096 +import { tap } from '../tapbundle/index.js'; +`; + const directives = parseDirectivesFromContent(content); + const options = directivesToRuntimeOptions(directives, 'node'); + expect(options).toBeTruthy(); + expect(options.extraArgs).toContain('--max-old-space-size=4096'); +}); + +tap.test('mergeDirectives - combines directives from init and test file', async () => { + const init = parseDirectivesFromContent(`// tstest:deno:allowRun +`); + const testFile = parseDirectivesFromContent(`// tstest:deno:allowFfi +`); + const merged = mergeDirectives(init, testFile); + expect(merged.deno.length).toEqual(2); + expect(merged.deno[0].key).toEqual('allowRun'); + expect(merged.deno[1].key).toEqual('allowFfi'); +}); + +export default tap.start(); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 7fd0df4..f6e0c8d 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tstest', - version: '3.2.0', - description: 'a test utility to run tests that match test/**/*.ts' + version: '3.3.0', + description: 'A powerful, modern test runner for TypeScript with multi-runtime support (Node.js, Deno, Bun, Chromium) and a batteries-included test framework.' } diff --git a/ts/tstest.classes.runtime.deno.ts b/ts/tstest.classes.runtime.deno.ts index 0348b17..c957db7 100644 --- a/ts/tstest.classes.runtime.deno.ts +++ b/ts/tstest.classes.runtime.deno.ts @@ -10,6 +10,20 @@ import { TapParser } from './tstest.classes.tap.parser.js'; import { TsTestLogger } from './tstest.logging.js'; import type { Runtime } from './tstest.classes.runtime.parser.js'; +/** + * Default Deno permissions used when no directives override them. + */ +export const DENO_DEFAULT_PERMISSIONS = [ + '--allow-read', + '--allow-env', + '--allow-net', + '--allow-write', + '--allow-sys', + '--allow-import', + '--node-modules-dir', + '--sloppy-imports', +]; + /** * Deno runtime adapter * Executes tests using the Deno runtime @@ -45,16 +59,7 @@ export class DenoRuntimeAdapter extends RuntimeAdapter { return { ...super.getDefaultOptions(), configPath, - permissions: [ - '--allow-read', - '--allow-env', - '--allow-net', - '--allow-write', - '--allow-sys', // Allow system info access - '--allow-import', // Allow npm/node imports - '--node-modules-dir', // Enable Node.js compatibility mode - '--sloppy-imports', // Allow .js imports to resolve to .ts files - ], + permissions: [...DENO_DEFAULT_PERMISSIONS], }; } @@ -102,16 +107,7 @@ export class DenoRuntimeAdapter extends RuntimeAdapter { const args: string[] = ['run']; // Add permissions - const permissions = mergedOptions.permissions || [ - '--allow-read', - '--allow-env', - '--allow-net', - '--allow-write', - '--allow-sys', - '--allow-import', - '--node-modules-dir', - '--sloppy-imports', - ]; + const permissions = mergedOptions.permissions || [...DENO_DEFAULT_PERMISSIONS]; args.push(...permissions); // Add config file if specified diff --git a/ts/tstest.classes.testfile.directives.ts b/ts/tstest.classes.testfile.directives.ts new file mode 100644 index 0000000..0e603ab --- /dev/null +++ b/ts/tstest.classes.testfile.directives.ts @@ -0,0 +1,226 @@ +import * as plugins from './tstest.plugins.js'; +import type { DenoOptions, RuntimeOptions } from './tstest.classes.runtime.adapter.js'; +import type { Runtime } from './tstest.classes.runtime.parser.js'; +import { DENO_DEFAULT_PERMISSIONS } from './tstest.classes.runtime.deno.js'; + +type DirectiveScope = Runtime | 'global'; + +export interface ITestFileDirective { + scope: DirectiveScope; + key: string; + value?: string; +} + +export interface IParsedDirectives { + deno: ITestFileDirective[]; + node: ITestFileDirective[]; + bun: ITestFileDirective[]; + chromium: ITestFileDirective[]; + global: ITestFileDirective[]; +} + +const VALID_SCOPES = new Set(['deno', 'node', 'bun', 'chromium']); + +const DENO_PERMISSION_MAP: Record = { + allowAll: '--allow-all', + allowRun: '--allow-run', + allowFfi: '--allow-ffi', + allowHrtime: '--allow-hrtime', + allowRead: '--allow-read', + allowWrite: '--allow-write', + allowNet: '--allow-net', + allowEnv: '--allow-env', + allowSys: '--allow-sys', +}; + +function createEmptyDirectives(): IParsedDirectives { + return { deno: [], node: [], bun: [], chromium: [], global: [] }; +} + +/** + * Parse tstest directives from file content. + * Scans comments at the top of the file (before any code). + */ +export function parseDirectivesFromContent(content: string): IParsedDirectives { + const result = createEmptyDirectives(); + const lines = content.split('\n'); + const maxLines = Math.min(lines.length, 30); + + for (let i = 0; i < maxLines; i++) { + const line = lines[i].trim(); + + // Skip empty lines + if (line === '') continue; + + // Stop at first non-comment line + if (!line.startsWith('//')) break; + + // Match tstest directive: // tstest: + const match = line.match(/^\/\/\s*tstest:(.+)$/); + if (!match) continue; + + const parts = match[1].split(':'); + if (parts.length < 2) { + console.warn(`Warning: malformed tstest directive: "${line}"`); + continue; + } + + const scopeStr = parts[0].trim(); + const key = parts[1].trim(); + const value = parts.length > 2 ? parts.slice(2).join(':').trim() : undefined; + + // Handle global directives (env, timeout) + if (scopeStr === 'env' || scopeStr === 'timeout') { + result.global.push({ + scope: 'global', + key: scopeStr, + value: key + (value !== undefined ? ':' + value : ''), + }); + continue; + } + + if (!VALID_SCOPES.has(scopeStr)) { + console.warn(`Warning: unknown tstest directive scope "${scopeStr}" in: "${line}"`); + continue; + } + + const scope = scopeStr as Runtime; + result[scope].push({ scope, key, value }); + } + + return result; +} + +/** + * Parse directives from a test file on disk. + */ +export async function parseDirectivesFromFile(filePath: string): Promise { + try { + const content = plugins.fs.readFileSync(filePath, 'utf8'); + return parseDirectivesFromContent(content); + } catch { + return createEmptyDirectives(); + } +} + +/** + * Merge directives from 00init.ts and the test file. + * Test file directives are appended (take effect after init directives). + */ +export function mergeDirectives(init: IParsedDirectives, testFile: IParsedDirectives): IParsedDirectives { + return { + deno: [...init.deno, ...testFile.deno], + node: [...init.node, ...testFile.node], + bun: [...init.bun, ...testFile.bun], + chromium: [...init.chromium, ...testFile.chromium], + global: [...init.global, ...testFile.global], + }; +} + +/** + * Check if any directives exist for any scope. + */ +export function hasDirectives(directives: IParsedDirectives): boolean { + return ( + directives.deno.length > 0 || + directives.node.length > 0 || + directives.bun.length > 0 || + directives.chromium.length > 0 || + directives.global.length > 0 + ); +} + +/** + * Convert parsed directives into DenoOptions. + */ +function directivesToDenoOptions(directives: IParsedDirectives): DenoOptions | undefined { + const denoDirectives = directives.deno; + if (denoDirectives.length === 0 && directives.global.length === 0) return undefined; + + const options: DenoOptions = {}; + const extraPermissions: string[] = []; + const extraArgs: string[] = []; + const env: Record = {}; + let useAllowAll = false; + + for (const d of denoDirectives) { + if (d.key === 'allowAll') { + useAllowAll = true; + } else if (DENO_PERMISSION_MAP[d.key]) { + extraPermissions.push(DENO_PERMISSION_MAP[d.key]); + } else if (d.key === 'flag' && d.value) { + extraArgs.push(d.value); + } + } + + // Process global directives + for (const d of directives.global) { + if (d.key === 'env' && d.value) { + const eqIndex = d.value.indexOf('='); + if (eqIndex > 0) { + env[d.value.substring(0, eqIndex)] = d.value.substring(eqIndex + 1); + } + } + } + + if (useAllowAll) { + // --allow-all replaces individual permissions, but keep compatibility flags + options.permissions = ['--allow-all', '--node-modules-dir', '--sloppy-imports']; + } else if (extraPermissions.length > 0) { + // Start with defaults and add extra permissions (deduplicated) + const allPermissions = [...DENO_DEFAULT_PERMISSIONS]; + for (const p of extraPermissions) { + if (!allPermissions.includes(p)) { + allPermissions.push(p); + } + } + options.permissions = allPermissions; + } + + if (extraArgs.length > 0) options.extraArgs = extraArgs; + if (Object.keys(env).length > 0) options.env = env; + + // Return undefined if nothing was set + if (!options.permissions && !options.extraArgs && !options.env) return undefined; + return options; +} + +/** + * Convert parsed directives into RuntimeOptions for Node/Bun (flag directives only). + */ +function directivesToGenericOptions(directives: ITestFileDirective[], globalDirectives: ITestFileDirective[]): RuntimeOptions | undefined { + const extraArgs: string[] = []; + const env: Record = {}; + + for (const d of directives) { + if (d.key === 'flag' && d.value) { + extraArgs.push(d.value); + } + } + + for (const d of globalDirectives) { + if (d.key === 'env' && d.value) { + const eqIndex = d.value.indexOf('='); + if (eqIndex > 0) { + env[d.value.substring(0, eqIndex)] = d.value.substring(eqIndex + 1); + } + } + } + + if (extraArgs.length === 0 && Object.keys(env).length === 0) return undefined; + + const options: RuntimeOptions = {}; + if (extraArgs.length > 0) options.extraArgs = extraArgs; + if (Object.keys(env).length > 0) options.env = env; + return options; +} + +/** + * Convert parsed directives into RuntimeOptions for a specific runtime. + */ +export function directivesToRuntimeOptions(directives: IParsedDirectives, runtime: Runtime): RuntimeOptions | undefined { + if (runtime === 'deno') { + return directivesToDenoOptions(directives); + } + return directivesToGenericOptions(directives[runtime] || [], directives.global); +} diff --git a/ts/tstest.classes.tstest.ts b/ts/tstest.classes.tstest.ts index 8fa7acd..f75ca78 100644 --- a/ts/tstest.classes.tstest.ts +++ b/ts/tstest.classes.tstest.ts @@ -19,6 +19,14 @@ import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js'; import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js'; import { DockerRuntimeAdapter } from './tstest.classes.runtime.docker.js'; +// Test file directives +import { + parseDirectivesFromFile, + mergeDirectives, + directivesToRuntimeOptions, + hasDirectives, +} from './tstest.classes.testfile.directives.js'; + export class TsTest { public testDir: TestDirectory; public executionMode: TestExecutionMode; @@ -256,18 +264,32 @@ export class TsTest { return; } + // Parse directives from the test file (e.g., // tstest:deno:allowAll) + let directives = await parseDirectivesFromFile(fileNameArg); + + // Also check for directives in 00init.ts + const testDir = plugins.path.dirname(fileNameArg); + const initFile = plugins.path.join(testDir, '00init.ts'); + const initFileExists = await plugins.smartfsInstance.file(initFile).exists(); + if (initFileExists) { + const initDirectives = await parseDirectivesFromFile(initFile); + directives = mergeDirectives(initDirectives, directives); + } + // Execute tests for each runtime if (adapters.length === 1) { // Single runtime - no sections needed const adapter = adapters[0]; - const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles); + const options = hasDirectives(directives) ? directivesToRuntimeOptions(directives, adapter.id) : undefined; + const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles, options); tapCombinator.addTapParser(tapParser); } else { // Multiple runtimes - use sections for (let i = 0; i < adapters.length; i++) { const adapter = adapters[i]; this.logger.sectionStart(`Part ${i + 1}: ${adapter.displayName}`); - const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles); + const options = hasDirectives(directives) ? directivesToRuntimeOptions(directives, adapter.id) : undefined; + const tapParser = await adapter.run(fileNameArg, fileIndex, totalFiles, options); tapCombinator.addTapParser(tapParser); this.logger.sectionEnd(); }