feat(testfile-directives): Add per-test file directives to control runtime permissions and flags (Deno, Node, Bun, Chromium)

This commit is contained in:
2026-03-06 08:12:28 +00:00
parent 4b4ec78328
commit 69263b3efc
10 changed files with 593 additions and 114 deletions

View File

@@ -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

View File

@@ -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"
},

View File

@@ -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"

170
pnpm-lock.yaml generated
View File

@@ -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:

View File

@@ -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

View File

@@ -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();

View File

@@ -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.'
}

View File

@@ -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

View File

@@ -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<string>(['deno', 'node', 'bun', 'chromium']);
const DENO_PERMISSION_MAP: Record<string, string> = {
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:<rest>
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<IParsedDirectives> {
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<string, string> = {};
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<string, string> = {};
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);
}

View File

@@ -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();
}