Compare commits
	
		
			11 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b94089652e | |||
| ef6f21fc9c | |||
| 592a4f33c0 | |||
| 1ea3b37d18 | |||
| 062c6e384b | |||
| b1f2eceb75 | |||
| de53b3f00c | |||
| b3f8a28766 | |||
| 86db2491a3 | |||
| b9fd8c7b02 | |||
| d6842326ad | 
							
								
								
									
										39
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,44 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-10-26 - 2.7.0 - feat(tapbundle_protocol) | ||||
| Add package export for tapbundle_protocol to expose protocol utilities | ||||
|  | ||||
| - Add './tapbundle_protocol' export in package.json pointing to './dist_ts_tapbundle_protocol/index.js'. | ||||
| - Allows consumers to import protocol utilities (ProtocolEmitter, ProtocolParser, types) via '@git.zone/tstest/tapbundle_protocol'. | ||||
| - Non-breaking: only extends package exports surface. | ||||
|  | ||||
| ## 2025-10-17 - 2.6.2 - fix(@push.rocks/smartrequest) | ||||
| Bump @push.rocks/smartrequest from ^4.3.1 to ^4.3.2 | ||||
|  | ||||
| - Update dependency @push.rocks/smartrequest from ^4.3.1 to ^4.3.2 | ||||
|  | ||||
| ## 2025-10-17 - 2.6.1 - fix(runtime-adapters) | ||||
| Silence shell version checks for Bun and Deno; add local Claude settings | ||||
|  | ||||
| - Replace smartshell.exec with execSilent in ts/tstest.classes.runtime.bun.ts to suppress output when checking Bun availability | ||||
| - Replace smartshell.exec with execSilent in ts/tstest.classes.runtime.deno.ts to suppress output when checking Deno availability | ||||
| - Add .claude/settings.local.json to record local Claude agent permissions/config used for development | ||||
|  | ||||
| ## 2025-10-17 - 2.6.0 - feat(runtime-adapters) | ||||
| Add runtime environment availability check and logger output; normalize runtime version strings | ||||
|  | ||||
| - Introduce checkEnvironment() in TsTest and invoke it at the start of run() to detect available runtimes before executing tests. | ||||
| - Add environmentCheck(availability) to TsTestLogger to print a human-friendly environment summary (with JSON and quiet-mode handling). | ||||
| - Normalize reported runtime version strings from adapters: prefix Deno and Bun versions with 'v' and simplify Chromium version text. | ||||
| - Display runtime availability information to the user before moving previous logs or running tests. | ||||
| - Includes addition of local .claude/settings.local.json (local dev/tooling settings). | ||||
|  | ||||
| ## 2025-10-17 - 2.5.2 - fix(runtime.node) | ||||
| Improve Node runtime adapter to use tsrun.spawnPath, strengthen tsrun detection, and improve process lifecycle and loader handling; update tsrun dependency. | ||||
|  | ||||
| - Use tsrun.spawnPath to spawn Node test processes and pass structured spawn options (cwd, env, args, stdio). | ||||
| - Detect tsrun availability via plugins.tsrun and require spawnPath; provide a clearer error message when tsrun is missing or outdated. | ||||
| - Pass --web via spawn args and set TSTEST_FILTER_TAGS on the spawned process env instead of mutating the parent process.env. | ||||
| - When a 00init.ts exists, create a temporary loader that imports both 00init.ts and the test file, run the loader via tsrun.spawnPath, and clean up the loader after execution. | ||||
| - Use tsrunProcess.terminate()/kill for timeouts to ensure proper process termination and improve cleanup handling. | ||||
| - Export tsrun from ts/tstest.plugins.ts so runtime code can access tsrun APIs via the plugins object. | ||||
| - Bump dependency @git.zone/tsrun from ^1.3.4 to ^1.6.2 in package.json. | ||||
|  | ||||
| ## 2025-10-16 - 2.5.1 - fix(deps) | ||||
| Bump dependencies and add local tooling settings | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| { | ||||
|   "name": "@git.zone/tstest", | ||||
|   "version": "2.5.1", | ||||
|   "version": "2.7.0", | ||||
|   "private": false, | ||||
|   "description": "a test utility to run tests that match test/**/*.ts", | ||||
|   "exports": { | ||||
|     ".": "./dist_ts/index.js", | ||||
|     "./tapbundle": "./dist_ts_tapbundle/index.js", | ||||
|     "./tapbundle_node": "./dist_ts_tapbundle_node/index.js" | ||||
|     "./tapbundle_node": "./dist_ts_tapbundle_node/index.js", | ||||
|     "./tapbundle_protocol": "./dist_ts_tapbundle_protocol/index.js" | ||||
|   }, | ||||
|   "type": "module", | ||||
|   "author": "Lossless GmbH", | ||||
| @@ -30,7 +31,7 @@ | ||||
|   "dependencies": { | ||||
|     "@api.global/typedserver": "^3.0.79", | ||||
|     "@git.zone/tsbundle": "^2.5.1", | ||||
|     "@git.zone/tsrun": "^1.3.4", | ||||
|     "@git.zone/tsrun": "^1.6.2", | ||||
|     "@push.rocks/consolecolor": "^2.0.3", | ||||
|     "@push.rocks/qenv": "^6.1.3", | ||||
|     "@push.rocks/smartbrowser": "^2.0.8", | ||||
| @@ -46,7 +47,7 @@ | ||||
|     "@push.rocks/smartnetwork": "^4.4.0", | ||||
|     "@push.rocks/smartpath": "^6.0.0", | ||||
|     "@push.rocks/smartpromise": "^4.2.3", | ||||
|     "@push.rocks/smartrequest": "^4.3.1", | ||||
|     "@push.rocks/smartrequest": "^4.3.2", | ||||
|     "@push.rocks/smarts3": "^2.2.6", | ||||
|     "@push.rocks/smartshell": "^3.3.0", | ||||
|     "@push.rocks/smarttime": "^4.1.1", | ||||
|   | ||||
							
								
								
									
										246
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										246
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -15,8 +15,8 @@ importers: | ||||
|         specifier: ^2.5.1 | ||||
|         version: 2.5.1 | ||||
|       '@git.zone/tsrun': | ||||
|         specifier: ^1.3.4 | ||||
|         version: 1.3.4 | ||||
|         specifier: ^1.6.2 | ||||
|         version: 1.6.2 | ||||
|       '@push.rocks/consolecolor': | ||||
|         specifier: ^2.0.3 | ||||
|         version: 2.0.3 | ||||
| @@ -63,8 +63,8 @@ importers: | ||||
|         specifier: ^4.2.3 | ||||
|         version: 4.2.3 | ||||
|       '@push.rocks/smartrequest': | ||||
|         specifier: ^4.3.1 | ||||
|         version: 4.3.1 | ||||
|         specifier: ^4.3.2 | ||||
|         version: 4.3.2 | ||||
|       '@push.rocks/smarts3': | ||||
|         specifier: ^2.2.6 | ||||
|         version: 2.2.6 | ||||
| @@ -425,8 +425,8 @@ packages: | ||||
|   '@emnapi/wasi-threads@1.0.4': | ||||
|     resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} | ||||
|  | ||||
|   '@esbuild/aix-ppc64@0.25.10': | ||||
|     resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} | ||||
|   '@esbuild/aix-ppc64@0.25.11': | ||||
|     resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [ppc64] | ||||
|     os: [aix] | ||||
| @@ -437,8 +437,8 @@ packages: | ||||
|     cpu: [ppc64] | ||||
|     os: [aix] | ||||
|  | ||||
|   '@esbuild/android-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} | ||||
|   '@esbuild/android-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [android] | ||||
| @@ -449,8 +449,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [android] | ||||
|  | ||||
|   '@esbuild/android-arm@0.25.10': | ||||
|     resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} | ||||
|   '@esbuild/android-arm@0.25.11': | ||||
|     resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm] | ||||
|     os: [android] | ||||
| @@ -461,8 +461,8 @@ packages: | ||||
|     cpu: [arm] | ||||
|     os: [android] | ||||
|  | ||||
|   '@esbuild/android-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} | ||||
|   '@esbuild/android-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [android] | ||||
| @@ -473,8 +473,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [android] | ||||
|  | ||||
|   '@esbuild/darwin-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} | ||||
|   '@esbuild/darwin-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [darwin] | ||||
| @@ -485,8 +485,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [darwin] | ||||
|  | ||||
|   '@esbuild/darwin-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} | ||||
|   '@esbuild/darwin-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [darwin] | ||||
| @@ -497,8 +497,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [darwin] | ||||
|  | ||||
|   '@esbuild/freebsd-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} | ||||
|   '@esbuild/freebsd-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [freebsd] | ||||
| @@ -509,8 +509,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [freebsd] | ||||
|  | ||||
|   '@esbuild/freebsd-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} | ||||
|   '@esbuild/freebsd-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [freebsd] | ||||
| @@ -521,8 +521,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [freebsd] | ||||
|  | ||||
|   '@esbuild/linux-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} | ||||
|   '@esbuild/linux-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [linux] | ||||
| @@ -533,8 +533,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-arm@0.25.10': | ||||
|     resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} | ||||
|   '@esbuild/linux-arm@0.25.11': | ||||
|     resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm] | ||||
|     os: [linux] | ||||
| @@ -545,8 +545,8 @@ packages: | ||||
|     cpu: [arm] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-ia32@0.25.10': | ||||
|     resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} | ||||
|   '@esbuild/linux-ia32@0.25.11': | ||||
|     resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [ia32] | ||||
|     os: [linux] | ||||
| @@ -557,8 +557,8 @@ packages: | ||||
|     cpu: [ia32] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-loong64@0.25.10': | ||||
|     resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} | ||||
|   '@esbuild/linux-loong64@0.25.11': | ||||
|     resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [loong64] | ||||
|     os: [linux] | ||||
| @@ -569,8 +569,8 @@ packages: | ||||
|     cpu: [loong64] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-mips64el@0.25.10': | ||||
|     resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} | ||||
|   '@esbuild/linux-mips64el@0.25.11': | ||||
|     resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [mips64el] | ||||
|     os: [linux] | ||||
| @@ -581,8 +581,8 @@ packages: | ||||
|     cpu: [mips64el] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-ppc64@0.25.10': | ||||
|     resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} | ||||
|   '@esbuild/linux-ppc64@0.25.11': | ||||
|     resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [ppc64] | ||||
|     os: [linux] | ||||
| @@ -593,8 +593,8 @@ packages: | ||||
|     cpu: [ppc64] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-riscv64@0.25.10': | ||||
|     resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} | ||||
|   '@esbuild/linux-riscv64@0.25.11': | ||||
|     resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [riscv64] | ||||
|     os: [linux] | ||||
| @@ -605,8 +605,8 @@ packages: | ||||
|     cpu: [riscv64] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-s390x@0.25.10': | ||||
|     resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} | ||||
|   '@esbuild/linux-s390x@0.25.11': | ||||
|     resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [s390x] | ||||
|     os: [linux] | ||||
| @@ -617,8 +617,8 @@ packages: | ||||
|     cpu: [s390x] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/linux-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} | ||||
|   '@esbuild/linux-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [linux] | ||||
| @@ -629,8 +629,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [linux] | ||||
|  | ||||
|   '@esbuild/netbsd-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} | ||||
|   '@esbuild/netbsd-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [netbsd] | ||||
| @@ -641,8 +641,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [netbsd] | ||||
|  | ||||
|   '@esbuild/netbsd-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} | ||||
|   '@esbuild/netbsd-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [netbsd] | ||||
| @@ -653,8 +653,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [netbsd] | ||||
|  | ||||
|   '@esbuild/openbsd-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} | ||||
|   '@esbuild/openbsd-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [openbsd] | ||||
| @@ -665,8 +665,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [openbsd] | ||||
|  | ||||
|   '@esbuild/openbsd-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} | ||||
|   '@esbuild/openbsd-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [openbsd] | ||||
| @@ -677,8 +677,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [openbsd] | ||||
|  | ||||
|   '@esbuild/openharmony-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} | ||||
|   '@esbuild/openharmony-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [openharmony] | ||||
| @@ -689,8 +689,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [openharmony] | ||||
|  | ||||
|   '@esbuild/sunos-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} | ||||
|   '@esbuild/sunos-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [sunos] | ||||
| @@ -701,8 +701,8 @@ packages: | ||||
|     cpu: [x64] | ||||
|     os: [sunos] | ||||
|  | ||||
|   '@esbuild/win32-arm64@0.25.10': | ||||
|     resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} | ||||
|   '@esbuild/win32-arm64@0.25.11': | ||||
|     resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [arm64] | ||||
|     os: [win32] | ||||
| @@ -713,8 +713,8 @@ packages: | ||||
|     cpu: [arm64] | ||||
|     os: [win32] | ||||
|  | ||||
|   '@esbuild/win32-ia32@0.25.10': | ||||
|     resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} | ||||
|   '@esbuild/win32-ia32@0.25.11': | ||||
|     resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [ia32] | ||||
|     os: [win32] | ||||
| @@ -725,8 +725,8 @@ packages: | ||||
|     cpu: [ia32] | ||||
|     os: [win32] | ||||
|  | ||||
|   '@esbuild/win32-x64@0.25.10': | ||||
|     resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} | ||||
|   '@esbuild/win32-x64@0.25.11': | ||||
|     resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} | ||||
|     engines: {node: '>=18'} | ||||
|     cpu: [x64] | ||||
|     os: [win32] | ||||
| @@ -749,8 +749,8 @@ packages: | ||||
|     resolution: {integrity: sha512-o2/jvNsdLC8SRdH1kQ7JjNOQNu9el0FpJ/QOW3mgiC5C9reuTp18iU4kijsVVLgvw4KZv6Z289SoKPh3HPsS0g==} | ||||
|     hasBin: true | ||||
|  | ||||
|   '@git.zone/tsrun@1.3.4': | ||||
|     resolution: {integrity: sha512-bAhlV5ORVyahl6ew1SC379qf48Gnc0HB5CVZbQZrqoVtOgXvUo9A47WynCyLOw+7sCeYMLYp3/yfwlgmuRoFPw==} | ||||
|   '@git.zone/tsrun@1.6.2': | ||||
|     resolution: {integrity: sha512-SOHbQqBg3/769/jPQcdpPCmugdEtIJINiG0O6aWx+su91GvGhheha5dAhccsCutJYErr+aJcBqBYuUYfhOfkFQ==} | ||||
|     hasBin: true | ||||
|  | ||||
|   '@happy-dom/global-registrator@15.11.7': | ||||
| @@ -1047,8 +1047,8 @@ packages: | ||||
|   '@push.rocks/smartrequest@2.1.0': | ||||
|     resolution: {integrity: sha512-3eHLTRInHA+u+W98TqJwgTES7rRimBAsJC4JxVNQC3UUezmblAhM5/TIQsEBQTsbjAY8SeQKy6NHzW6iTiaD8w==} | ||||
|  | ||||
|   '@push.rocks/smartrequest@4.3.1': | ||||
|     resolution: {integrity: sha512-H5FQSfFEbSJCHpE2A+SasQQcxM5FlxhHIUEzhUsSLjtlCTEu9T7Xb1WzVLFYvdWfyP5VIrg+XM4AMOols8cG+Q==} | ||||
|   '@push.rocks/smartrequest@4.3.2': | ||||
|     resolution: {integrity: sha512-Alms3xnC9gpXg8XOgsczwJxnXEjAEf6SDUk0/Ykw9OJJVkZIstWkoytGwJS/hByx70WtPENvq30zBqxI1Gl9rQ==} | ||||
|  | ||||
|   '@push.rocks/smartrouter@1.3.3': | ||||
|     resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==} | ||||
| @@ -2242,8 +2242,8 @@ packages: | ||||
|     resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} | ||||
|     engines: {node: '>= 0.4'} | ||||
|  | ||||
|   esbuild@0.25.10: | ||||
|     resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} | ||||
|   esbuild@0.25.11: | ||||
|     resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} | ||||
|     engines: {node: '>=18'} | ||||
|     hasBin: true | ||||
|  | ||||
| @@ -3996,7 +3996,7 @@ snapshots: | ||||
|       '@push.rocks/smartopen': 2.0.0 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
|       '@push.rocks/smartpromise': 4.2.3 | ||||
|       '@push.rocks/smartrequest': 4.3.1 | ||||
|       '@push.rocks/smartrequest': 4.3.2 | ||||
|       '@push.rocks/smartrx': 3.0.10 | ||||
|       '@push.rocks/smartsitemap': 2.0.3 | ||||
|       '@push.rocks/smartstream': 3.2.5 | ||||
| @@ -5002,157 +5002,157 @@ snapshots: | ||||
|       tslib: 2.8.1 | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/aix-ppc64@0.25.10': | ||||
|   '@esbuild/aix-ppc64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/aix-ppc64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-arm64@0.25.10': | ||||
|   '@esbuild/android-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-arm@0.25.10': | ||||
|   '@esbuild/android-arm@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-arm@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-x64@0.25.10': | ||||
|   '@esbuild/android-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/android-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/darwin-arm64@0.25.10': | ||||
|   '@esbuild/darwin-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/darwin-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/darwin-x64@0.25.10': | ||||
|   '@esbuild/darwin-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/darwin-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/freebsd-arm64@0.25.10': | ||||
|   '@esbuild/freebsd-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/freebsd-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/freebsd-x64@0.25.10': | ||||
|   '@esbuild/freebsd-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/freebsd-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-arm64@0.25.10': | ||||
|   '@esbuild/linux-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-arm@0.25.10': | ||||
|   '@esbuild/linux-arm@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-arm@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-ia32@0.25.10': | ||||
|   '@esbuild/linux-ia32@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-ia32@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-loong64@0.25.10': | ||||
|   '@esbuild/linux-loong64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-loong64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-mips64el@0.25.10': | ||||
|   '@esbuild/linux-mips64el@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-mips64el@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-ppc64@0.25.10': | ||||
|   '@esbuild/linux-ppc64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-ppc64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-riscv64@0.25.10': | ||||
|   '@esbuild/linux-riscv64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-riscv64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-s390x@0.25.10': | ||||
|   '@esbuild/linux-s390x@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-s390x@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-x64@0.25.10': | ||||
|   '@esbuild/linux-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/linux-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/netbsd-arm64@0.25.10': | ||||
|   '@esbuild/netbsd-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/netbsd-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/netbsd-x64@0.25.10': | ||||
|   '@esbuild/netbsd-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/netbsd-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openbsd-arm64@0.25.10': | ||||
|   '@esbuild/openbsd-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openbsd-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openbsd-x64@0.25.10': | ||||
|   '@esbuild/openbsd-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openbsd-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openharmony-arm64@0.25.10': | ||||
|   '@esbuild/openharmony-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/openharmony-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/sunos-x64@0.25.10': | ||||
|   '@esbuild/sunos-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/sunos-x64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-arm64@0.25.10': | ||||
|   '@esbuild/win32-arm64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-arm64@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-ia32@0.25.10': | ||||
|   '@esbuild/win32-ia32@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-ia32@0.25.9': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-x64@0.25.10': | ||||
|   '@esbuild/win32-x64@0.25.11': | ||||
|     optional: true | ||||
|  | ||||
|   '@esbuild/win32-x64@0.25.9': | ||||
| @@ -5203,13 +5203,13 @@ snapshots: | ||||
|       '@push.rocks/smartlog': 3.1.10 | ||||
|       '@push.rocks/smartnpm': 2.0.6 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
|       '@push.rocks/smartrequest': 4.3.1 | ||||
|       '@push.rocks/smartrequest': 4.3.2 | ||||
|       '@push.rocks/smartshell': 3.3.0 | ||||
|     transitivePeerDependencies: | ||||
|       - aws-crt | ||||
|       - supports-color | ||||
|  | ||||
|   '@git.zone/tsrun@1.3.4': | ||||
|   '@git.zone/tsrun@1.6.2': | ||||
|     dependencies: | ||||
|       '@push.rocks/smartfile': 11.2.7 | ||||
|       '@push.rocks/smartshell': 3.3.0 | ||||
| @@ -5511,7 +5511,7 @@ snapshots: | ||||
|       '@push.rocks/smartfile': 11.2.7 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
|       '@push.rocks/smartpromise': 4.2.3 | ||||
|       '@push.rocks/smartrequest': 4.3.1 | ||||
|       '@push.rocks/smartrequest': 4.3.2 | ||||
|       '@push.rocks/smartrx': 3.0.10 | ||||
|       '@push.rocks/smartstream': 3.2.5 | ||||
|       '@push.rocks/smartunique': 3.0.9 | ||||
| @@ -5693,7 +5693,7 @@ snapshots: | ||||
|       '@push.rocks/smartmime': 2.0.4 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
|       '@push.rocks/smartpromise': 4.2.3 | ||||
|       '@push.rocks/smartrequest': 4.3.1 | ||||
|       '@push.rocks/smartrequest': 4.3.2 | ||||
|       '@push.rocks/smartstream': 3.2.5 | ||||
|       '@types/fs-extra': 11.0.4 | ||||
|       '@types/js-yaml': 4.0.9 | ||||
| @@ -5820,7 +5820,7 @@ snapshots: | ||||
|       '@push.rocks/smartfile': 11.2.7 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
|       '@push.rocks/smartpromise': 4.2.3 | ||||
|       '@push.rocks/smartrequest': 4.3.1 | ||||
|       '@push.rocks/smartrequest': 4.3.2 | ||||
|       '@push.rocks/smarttime': 4.1.1 | ||||
|       '@push.rocks/smartversion': 3.0.5 | ||||
|       package-json: 8.1.1 | ||||
| @@ -5902,7 +5902,7 @@ snapshots: | ||||
|       agentkeepalive: 4.6.0 | ||||
|       form-data: 4.0.4 | ||||
|  | ||||
|   '@push.rocks/smartrequest@4.3.1': | ||||
|   '@push.rocks/smartrequest@4.3.2': | ||||
|     dependencies: | ||||
|       '@push.rocks/smartenv': 5.0.13 | ||||
|       '@push.rocks/smartpath': 6.0.0 | ||||
| @@ -7377,34 +7377,34 @@ snapshots: | ||||
|       has-tostringtag: 1.0.2 | ||||
|       hasown: 2.0.2 | ||||
|  | ||||
|   esbuild@0.25.10: | ||||
|   esbuild@0.25.11: | ||||
|     optionalDependencies: | ||||
|       '@esbuild/aix-ppc64': 0.25.10 | ||||
|       '@esbuild/android-arm': 0.25.10 | ||||
|       '@esbuild/android-arm64': 0.25.10 | ||||
|       '@esbuild/android-x64': 0.25.10 | ||||
|       '@esbuild/darwin-arm64': 0.25.10 | ||||
|       '@esbuild/darwin-x64': 0.25.10 | ||||
|       '@esbuild/freebsd-arm64': 0.25.10 | ||||
|       '@esbuild/freebsd-x64': 0.25.10 | ||||
|       '@esbuild/linux-arm': 0.25.10 | ||||
|       '@esbuild/linux-arm64': 0.25.10 | ||||
|       '@esbuild/linux-ia32': 0.25.10 | ||||
|       '@esbuild/linux-loong64': 0.25.10 | ||||
|       '@esbuild/linux-mips64el': 0.25.10 | ||||
|       '@esbuild/linux-ppc64': 0.25.10 | ||||
|       '@esbuild/linux-riscv64': 0.25.10 | ||||
|       '@esbuild/linux-s390x': 0.25.10 | ||||
|       '@esbuild/linux-x64': 0.25.10 | ||||
|       '@esbuild/netbsd-arm64': 0.25.10 | ||||
|       '@esbuild/netbsd-x64': 0.25.10 | ||||
|       '@esbuild/openbsd-arm64': 0.25.10 | ||||
|       '@esbuild/openbsd-x64': 0.25.10 | ||||
|       '@esbuild/openharmony-arm64': 0.25.10 | ||||
|       '@esbuild/sunos-x64': 0.25.10 | ||||
|       '@esbuild/win32-arm64': 0.25.10 | ||||
|       '@esbuild/win32-ia32': 0.25.10 | ||||
|       '@esbuild/win32-x64': 0.25.10 | ||||
|       '@esbuild/aix-ppc64': 0.25.11 | ||||
|       '@esbuild/android-arm': 0.25.11 | ||||
|       '@esbuild/android-arm64': 0.25.11 | ||||
|       '@esbuild/android-x64': 0.25.11 | ||||
|       '@esbuild/darwin-arm64': 0.25.11 | ||||
|       '@esbuild/darwin-x64': 0.25.11 | ||||
|       '@esbuild/freebsd-arm64': 0.25.11 | ||||
|       '@esbuild/freebsd-x64': 0.25.11 | ||||
|       '@esbuild/linux-arm': 0.25.11 | ||||
|       '@esbuild/linux-arm64': 0.25.11 | ||||
|       '@esbuild/linux-ia32': 0.25.11 | ||||
|       '@esbuild/linux-loong64': 0.25.11 | ||||
|       '@esbuild/linux-mips64el': 0.25.11 | ||||
|       '@esbuild/linux-ppc64': 0.25.11 | ||||
|       '@esbuild/linux-riscv64': 0.25.11 | ||||
|       '@esbuild/linux-s390x': 0.25.11 | ||||
|       '@esbuild/linux-x64': 0.25.11 | ||||
|       '@esbuild/netbsd-arm64': 0.25.11 | ||||
|       '@esbuild/netbsd-x64': 0.25.11 | ||||
|       '@esbuild/openbsd-arm64': 0.25.11 | ||||
|       '@esbuild/openbsd-x64': 0.25.11 | ||||
|       '@esbuild/openharmony-arm64': 0.25.11 | ||||
|       '@esbuild/sunos-x64': 0.25.11 | ||||
|       '@esbuild/win32-arm64': 0.25.11 | ||||
|       '@esbuild/win32-ia32': 0.25.11 | ||||
|       '@esbuild/win32-x64': 0.25.11 | ||||
|  | ||||
|   esbuild@0.25.9: | ||||
|     optionalDependencies: | ||||
| @@ -9268,7 +9268,7 @@ snapshots: | ||||
|  | ||||
|   tsx@4.20.6: | ||||
|     dependencies: | ||||
|       esbuild: 0.25.10 | ||||
|       esbuild: 0.25.11 | ||||
|       get-tsconfig: 4.12.0 | ||||
|     optionalDependencies: | ||||
|       fsevents: 2.3.3 | ||||
|   | ||||
							
								
								
									
										123
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								readme.md
									
									
									
									
									
								
							| @@ -319,6 +319,129 @@ tstest provides multiple exports for different use cases: | ||||
| - `@git.zone/tstest` - Main CLI and test runner functionality | ||||
| - `@git.zone/tstest/tapbundle` - Browser-compatible test framework | ||||
| - `@git.zone/tstest/tapbundle_node` - Node.js-specific test utilities | ||||
| - `@git.zone/tstest/tapbundle_protocol` - Protocol V2 emitter and parser for TAP extensions | ||||
|  | ||||
| ## tapbundle Protocol V2 | ||||
|  | ||||
| tstest includes an enhanced TAP protocol (Protocol V2) that extends standard TAP 13 with additional metadata while maintaining backwards compatibility. | ||||
|  | ||||
| ### Overview | ||||
|  | ||||
| Protocol V2 adds structured metadata to TAP output using Unicode markers (`⟦TSTEST:...⟧`) that standard TAP parsers safely ignore. This allows for: | ||||
|  | ||||
| - **Timing information** - Test execution duration in milliseconds | ||||
| - **Structured errors** - Stack traces, diffs, and detailed error data | ||||
| - **Test events** - Real-time progress and lifecycle events | ||||
| - **Snapshots** - Snapshot testing data exchange | ||||
| - **Custom metadata** - Tags, retry counts, file locations | ||||
|  | ||||
| ### Using the Protocol | ||||
|  | ||||
| ```typescript | ||||
| import { | ||||
|   ProtocolEmitter, | ||||
|   ProtocolParser, | ||||
|   PROTOCOL_MARKERS, | ||||
|   PROTOCOL_VERSION | ||||
| } from '@git.zone/tstest/tapbundle_protocol'; | ||||
|  | ||||
| // Create an emitter | ||||
| const emitter = new ProtocolEmitter(); | ||||
|  | ||||
| // Emit protocol header | ||||
| console.log(emitter.emitProtocolHeader()); | ||||
| // Output: ⟦TSTEST:PROTOCOL:2.0.0⟧ | ||||
|  | ||||
| // Emit TAP version | ||||
| console.log(emitter.emitTapVersion(13)); | ||||
| // Output: TAP version 13 | ||||
|  | ||||
| // Emit a test result with metadata | ||||
| const testResult = { | ||||
|   ok: true, | ||||
|   testNumber: 1, | ||||
|   description: 'user authentication works', | ||||
|   metadata: { | ||||
|     time: 123, | ||||
|     tags: ['auth', 'unit'] | ||||
|   } | ||||
| }; | ||||
| console.log(emitter.emitTest(testResult).join('\n')); | ||||
| // Output: ok 1 - user authentication works ⟦TSTEST:time:123⟧ | ||||
| //         ⟦TSTEST:META:{"tags":["auth","unit"]}⟧ | ||||
| ``` | ||||
|  | ||||
| ### Protocol Markers | ||||
|  | ||||
| ```typescript | ||||
| PROTOCOL_MARKERS = { | ||||
|   START: '⟦TSTEST:', | ||||
|   END: '⟧', | ||||
|   META_PREFIX: 'META:', | ||||
|   ERROR_PREFIX: 'ERROR', | ||||
|   SNAPSHOT_PREFIX: 'SNAPSHOT:', | ||||
|   SKIP_PREFIX: 'SKIP:', | ||||
|   TODO_PREFIX: 'TODO:', | ||||
|   EVENT_PREFIX: 'EVENT:' | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Use Cases | ||||
|  | ||||
| #### Creating Custom Test Runners | ||||
|  | ||||
| ```typescript | ||||
| import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol'; | ||||
|  | ||||
| const emitter = new ProtocolEmitter(); | ||||
|  | ||||
| // Emit header and version | ||||
| console.log(emitter.emitProtocolHeader()); | ||||
| console.log(emitter.emitTapVersion(13)); | ||||
| console.log(emitter.emitPlan({ start: 1, end: 2 })); | ||||
|  | ||||
| // Run your tests and emit results | ||||
| const startTime = Date.now(); | ||||
| // ... run test ... | ||||
| const duration = Date.now() - startTime; | ||||
|  | ||||
| console.log(emitter.emitTest({ | ||||
|   ok: true, | ||||
|   testNumber: 1, | ||||
|   description: 'my custom test', | ||||
|   metadata: { time: duration } | ||||
| }).join('\n')); | ||||
| ``` | ||||
|  | ||||
| #### Parsing tapbundle Output | ||||
|  | ||||
| ```typescript | ||||
| import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol'; | ||||
|  | ||||
| const parser = new ProtocolParser(); | ||||
|  | ||||
| // Parse TAP output line by line | ||||
| parser.parseLine('⟦TSTEST:PROTOCOL:2.0.0⟧'); | ||||
| parser.parseLine('TAP version 13'); | ||||
| parser.parseLine('1..1'); | ||||
| parser.parseLine('ok 1 - test name ⟦TSTEST:time:123⟧'); | ||||
|  | ||||
| // Get parsed results | ||||
| const results = parser.getResults(); | ||||
| console.log(results); | ||||
| ``` | ||||
|  | ||||
| ### Backwards Compatibility | ||||
|  | ||||
| Protocol V2 is fully backwards compatible with standard TAP 13. The Unicode markers are treated as comments by standard TAP parsers, so Protocol V2 output can be consumed by any TAP-compliant tool: | ||||
|  | ||||
| ``` | ||||
| ⟦TSTEST:PROTOCOL:2.0.0⟧    ← Ignored by standard TAP parsers | ||||
| TAP version 13               ← Standard TAP | ||||
| 1..2                         ← Standard TAP | ||||
| ok 1 - test ⟦TSTEST:time:45⟧ ← TAP parsers see: "ok 1 - test" | ||||
| ok 2 - another test          ← Standard TAP | ||||
| ``` | ||||
|  | ||||
| ## tapbundle Test Framework | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								test/test.example.latest.docker.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								test/test.example.latest.docker.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #!/bin/bash | ||||
| # Sample Docker test file | ||||
| # This file demonstrates the naming pattern: test.{baseName}.{variant}.docker.sh | ||||
| # The variant "latest" maps to the Dockerfile in the project root | ||||
|  | ||||
| echo "TAP version 13" | ||||
| echo "1..2" | ||||
| echo "ok 1 - Sample Docker test passes" | ||||
| echo "ok 2 - Docker environment is working" | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tstest', | ||||
|   version: '2.5.1', | ||||
|   version: '2.7.0', | ||||
|   description: 'a test utility to run tests that match test/**/*.ts' | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ export class BunRuntimeAdapter extends RuntimeAdapter { | ||||
|    */ | ||||
|   async checkAvailable(): Promise<RuntimeAvailability> { | ||||
|     try { | ||||
|       const result = await this.smartshellInstance.exec('bun --version', { | ||||
|       const result = await this.smartshellInstance.execSilent('bun --version', { | ||||
|         cwd: process.cwd(), | ||||
|         onError: () => { | ||||
|           // Ignore error | ||||
| @@ -47,11 +47,11 @@ export class BunRuntimeAdapter extends RuntimeAdapter { | ||||
|       } | ||||
|  | ||||
|       // Bun version is just the version number | ||||
|       const version = result.stdout.trim(); | ||||
|       const version = `v${result.stdout.trim()}`; | ||||
|  | ||||
|       return { | ||||
|         available: true, | ||||
|         version: `Bun ${version}`, | ||||
|         version: version, | ||||
|       }; | ||||
|     } catch (error) { | ||||
|       return { | ||||
|   | ||||
| @@ -37,7 +37,7 @@ export class ChromiumRuntimeAdapter extends RuntimeAdapter { | ||||
|       // The browser binary is usually handled by @push.rocks/smartbrowser | ||||
|       return { | ||||
|         available: true, | ||||
|         version: 'Chromium (via smartbrowser)', | ||||
|         version: 'via smartbrowser', | ||||
|       }; | ||||
|     } catch (error) { | ||||
|       return { | ||||
|   | ||||
| @@ -51,7 +51,7 @@ export class DenoRuntimeAdapter extends RuntimeAdapter { | ||||
|    */ | ||||
|   async checkAvailable(): Promise<RuntimeAvailability> { | ||||
|     try { | ||||
|       const result = await this.smartshellInstance.exec('deno --version', { | ||||
|       const result = await this.smartshellInstance.execSilent('deno --version', { | ||||
|         cwd: process.cwd(), | ||||
|         onError: () => { | ||||
|           // Ignore error | ||||
| @@ -67,11 +67,11 @@ export class DenoRuntimeAdapter extends RuntimeAdapter { | ||||
|  | ||||
|       // Parse Deno version from output (first line is "deno X.Y.Z") | ||||
|       const versionMatch = result.stdout.match(/deno (\d+\.\d+\.\d+)/); | ||||
|       const version = versionMatch ? versionMatch[1] : 'unknown'; | ||||
|       const version = versionMatch ? `v${versionMatch[1]}` : 'unknown'; | ||||
|  | ||||
|       return { | ||||
|         available: true, | ||||
|         version: `Deno ${version}`, | ||||
|         version: version, | ||||
|       }; | ||||
|     } catch (error) { | ||||
|       return { | ||||
|   | ||||
							
								
								
									
										251
									
								
								ts/tstest.classes.runtime.docker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								ts/tstest.classes.runtime.docker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | ||||
| import * as plugins from './tstest.plugins.js'; | ||||
| import { coloredString as cs } from '@push.rocks/consolecolor'; | ||||
| import { | ||||
|   RuntimeAdapter, | ||||
|   type RuntimeOptions, | ||||
|   type RuntimeCommand, | ||||
|   type RuntimeAvailability, | ||||
| } from './tstest.classes.runtime.adapter.js'; | ||||
| import { TapParser } from './tstest.classes.tap.parser.js'; | ||||
| import { TsTestLogger } from './tstest.logging.js'; | ||||
| import type { Runtime } from './tstest.classes.runtime.parser.js'; | ||||
| import { | ||||
|   parseDockerTestFilename, | ||||
|   mapVariantToDockerfile, | ||||
|   isDockerTestFile | ||||
| } from './tstest.classes.runtime.parser.js'; | ||||
|  | ||||
| /** | ||||
|  * Docker runtime adapter | ||||
|  * Executes shell test files inside Docker containers | ||||
|  * Pattern: test.{variant}.docker.sh | ||||
|  * Variants map to Dockerfiles: latest -> Dockerfile, others -> Dockerfile_{variant} | ||||
|  */ | ||||
| export class DockerRuntimeAdapter extends RuntimeAdapter { | ||||
|   readonly id: Runtime = 'node'; // Using 'node' temporarily as Runtime type doesn't include 'docker' | ||||
|   readonly displayName: string = 'Docker'; | ||||
|  | ||||
|   private builtImages: Set<string> = new Set(); // Track built images to avoid rebuilding | ||||
|  | ||||
|   constructor( | ||||
|     private logger: TsTestLogger, | ||||
|     private smartshellInstance: any, // SmartShell instance from @push.rocks/smartshell | ||||
|     private timeoutSeconds: number | null, | ||||
|     private cwd: string | ||||
|   ) { | ||||
|     super(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Check if Docker CLI is available | ||||
|    */ | ||||
|   async checkAvailable(): Promise<RuntimeAvailability> { | ||||
|     try { | ||||
|       const result = await this.smartshellInstance.exec('docker --version'); | ||||
|  | ||||
|       if (result.exitCode !== 0) { | ||||
|         return { | ||||
|           available: false, | ||||
|           error: 'Docker command failed', | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       // Extract version from output like "Docker version 24.0.5, build ced0996" | ||||
|       const versionMatch = result.stdout.match(/Docker version ([^,]+)/); | ||||
|       const version = versionMatch ? versionMatch[1] : 'unknown'; | ||||
|  | ||||
|       return { | ||||
|         available: true, | ||||
|         version, | ||||
|       }; | ||||
|     } catch (error) { | ||||
|       return { | ||||
|         available: false, | ||||
|         error: `Docker not found: ${error.message}`, | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create command configuration for Docker test execution | ||||
|    * This is used for informational purposes | ||||
|    */ | ||||
|   createCommand(testFile: string, options?: RuntimeOptions): RuntimeCommand { | ||||
|     const parsed = parseDockerTestFilename(testFile); | ||||
|     const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd); | ||||
|     const imageName = `tstest-${parsed.variant}`; | ||||
|  | ||||
|     return { | ||||
|       command: 'docker', | ||||
|       args: [ | ||||
|         'run', | ||||
|         '--rm', | ||||
|         '-v', | ||||
|         `${this.cwd}/test:/test`, | ||||
|         imageName, | ||||
|         'taprun', | ||||
|         `/test/${plugins.path.basename(testFile)}` | ||||
|       ], | ||||
|       env: {}, | ||||
|       cwd: this.cwd, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Build a Docker image from the specified Dockerfile | ||||
|    */ | ||||
|   private async buildDockerImage(dockerfilePath: string, imageName: string): Promise<void> { | ||||
|     // Check if image is already built | ||||
|     if (this.builtImages.has(imageName)) { | ||||
|       this.logger.tapOutput(`Using cached Docker image: ${imageName}`); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // Check if Dockerfile exists | ||||
|     if (!await plugins.smartfile.fs.fileExists(dockerfilePath)) { | ||||
|       throw new Error( | ||||
|         `Dockerfile not found: ${dockerfilePath}\n` + | ||||
|         `Expected Dockerfile for Docker test variant.` | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     this.logger.tapOutput(`Building Docker image: ${imageName} from ${dockerfilePath}`); | ||||
|  | ||||
|     try { | ||||
|       const buildResult = await this.smartshellInstance.exec( | ||||
|         `docker build -f ${dockerfilePath} -t ${imageName} ${this.cwd}`, | ||||
|         { | ||||
|           cwd: this.cwd, | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       if (buildResult.exitCode !== 0) { | ||||
|         throw new Error(`Docker build failed:\n${buildResult.stderr}`); | ||||
|       } | ||||
|  | ||||
|       this.builtImages.add(imageName); | ||||
|       this.logger.tapOutput(`✅ Docker image built successfully: ${imageName}`); | ||||
|     } catch (error) { | ||||
|       throw new Error(`Failed to build Docker image: ${error.message}`); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Execute a Docker test file | ||||
|    */ | ||||
|   async run( | ||||
|     testFile: string, | ||||
|     index: number, | ||||
|     total: number, | ||||
|     options?: RuntimeOptions | ||||
|   ): Promise<TapParser> { | ||||
|     this.logger.testFileStart(testFile, this.displayName, index, total); | ||||
|  | ||||
|     // Parse the Docker test filename | ||||
|     const parsed = parseDockerTestFilename(testFile); | ||||
|     const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd); | ||||
|     const imageName = `tstest-${parsed.variant}`; | ||||
|  | ||||
|     // Build the Docker image | ||||
|     await this.buildDockerImage(dockerfilePath, imageName); | ||||
|  | ||||
|     // Prepare the test file path relative to the mounted directory | ||||
|     // We need to get the path relative to cwd | ||||
|     const absoluteTestPath = plugins.path.isAbsolute(testFile) | ||||
|       ? testFile | ||||
|       : plugins.path.join(this.cwd, testFile); | ||||
|  | ||||
|     const relativeTestPath = plugins.path.relative(this.cwd, absoluteTestPath); | ||||
|  | ||||
|     // Create TAP parser | ||||
|     const tapParser = new TapParser(testFile + ':docker', this.logger); | ||||
|  | ||||
|     try { | ||||
|       // Build docker run command | ||||
|       const dockerArgs = [ | ||||
|         'run', | ||||
|         '--rm', | ||||
|         '-v', | ||||
|         `${this.cwd}/test:/test`, | ||||
|         imageName, | ||||
|         'taprun', | ||||
|         `/test/${plugins.path.basename(testFile)}` | ||||
|       ]; | ||||
|  | ||||
|       this.logger.tapOutput(`Executing: docker ${dockerArgs.join(' ')}`); | ||||
|  | ||||
|       // Execute the Docker container | ||||
|       const execPromise = this.smartshellInstance.execStreaming( | ||||
|         `docker ${dockerArgs.join(' ')}`, | ||||
|         { | ||||
|           cwd: this.cwd, | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       // Set up timeout if configured | ||||
|       let timeoutHandle: NodeJS.Timeout | null = null; | ||||
|       if (this.timeoutSeconds) { | ||||
|         timeoutHandle = setTimeout(() => { | ||||
|           this.logger.tapOutput(`⏱️  Test timeout (${this.timeoutSeconds}s) - killing container`); | ||||
|           // Try to kill any running containers with this image | ||||
|           this.smartshellInstance.exec(`docker ps -q --filter ancestor=${imageName} | xargs -r docker kill`); | ||||
|         }, this.timeoutSeconds * 1000); | ||||
|       } | ||||
|  | ||||
|       // Stream output to TAP parser line by line | ||||
|       execPromise.childProcess.stdout.on('data', (data: Buffer) => { | ||||
|         const output = data.toString(); | ||||
|         const lines = output.split('\n'); | ||||
|         for (const line of lines) { | ||||
|           if (line.trim()) { | ||||
|             tapParser.handleTapLog(line); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       execPromise.childProcess.stderr.on('data', (data: Buffer) => { | ||||
|         const output = data.toString(); | ||||
|         this.logger.tapOutput(cs(`[stderr] ${output}`, 'orange')); | ||||
|       }); | ||||
|  | ||||
|       // Wait for completion | ||||
|       const result = await execPromise; | ||||
|  | ||||
|       // Clear timeout | ||||
|       if (timeoutHandle) { | ||||
|         clearTimeout(timeoutHandle); | ||||
|       } | ||||
|  | ||||
|       if (result.exitCode !== 0) { | ||||
|         this.logger.tapOutput(cs(`❌ Docker test failed with exit code ${result.exitCode}`, 'red')); | ||||
|       } | ||||
|  | ||||
|       // Evaluate final result | ||||
|       await tapParser.evaluateFinalResult(); | ||||
|  | ||||
|     } catch (error) { | ||||
|       this.logger.tapOutput(cs(`❌ Error running Docker test: ${error.message}`, 'red')); | ||||
|       // Add a failing test result to the parser | ||||
|       tapParser.handleTapLog('not ok 1 - Docker test execution failed'); | ||||
|       await tapParser.evaluateFinalResult(); | ||||
|     } | ||||
|  | ||||
|     return tapParser; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Clean up built Docker images (optional, can be called at end of test suite) | ||||
|    */ | ||||
|   async cleanup(): Promise<void> { | ||||
|     for (const imageName of this.builtImages) { | ||||
|       try { | ||||
|         this.logger.tapOutput(`Removing Docker image: ${imageName}`); | ||||
|         await this.smartshellInstance.exec(`docker rmi ${imageName}`); | ||||
|       } catch (error) { | ||||
|         // Ignore cleanup errors | ||||
|         this.logger.tapOutput(cs(`Warning: Failed to remove image ${imageName}: ${error.message}`, 'orange')); | ||||
|       } | ||||
|     } | ||||
|     this.builtImages.clear(); | ||||
|   } | ||||
| } | ||||
| @@ -35,18 +35,11 @@ export class NodeRuntimeAdapter extends RuntimeAdapter { | ||||
|       // Check Node.js version | ||||
|       const nodeVersion = process.version; | ||||
|  | ||||
|       // Check if tsrun is available | ||||
|       const result = await this.smartshellInstance.exec('tsrun --version', { | ||||
|         cwd: process.cwd(), | ||||
|         onError: () => { | ||||
|           // Ignore error | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       if (result.exitCode !== 0) { | ||||
|       // Check if tsrun module is available (imported as dependency) | ||||
|       if (!plugins.tsrun || !plugins.tsrun.spawnPath) { | ||||
|         return { | ||||
|           available: false, | ||||
|           error: 'tsrun not found. Install with: pnpm install --save-dev @git.zone/tsrun', | ||||
|           error: 'tsrun module not found or outdated (requires version 1.6.0+)', | ||||
|         }; | ||||
|       } | ||||
|  | ||||
| @@ -96,7 +89,7 @@ export class NodeRuntimeAdapter extends RuntimeAdapter { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Execute a test file in Node.js | ||||
|    * Execute a test file in Node.js using tsrun's spawnPath API | ||||
|    */ | ||||
|   async run( | ||||
|     testFile: string, | ||||
| @@ -109,28 +102,35 @@ export class NodeRuntimeAdapter extends RuntimeAdapter { | ||||
|  | ||||
|     const mergedOptions = this.mergeOptions(options); | ||||
|  | ||||
|     // Build tsrun command | ||||
|     let tsrunOptions = ''; | ||||
|     // Build spawn options | ||||
|     const spawnOptions: any = { | ||||
|       cwd: mergedOptions.cwd || process.cwd(), | ||||
|       env: { ...mergedOptions.env }, | ||||
|       args: [] as string[], | ||||
|       stdio: 'pipe' as const, | ||||
|     }; | ||||
|  | ||||
|     // Add --web flag if needed | ||||
|     if (process.argv.includes('--web')) { | ||||
|       tsrunOptions += ' --web'; | ||||
|       spawnOptions.args.push('--web'); | ||||
|     } | ||||
|  | ||||
|     // Set filter tags as environment variable | ||||
|     if (this.filterTags.length > 0) { | ||||
|       process.env.TSTEST_FILTER_TAGS = this.filterTags.join(','); | ||||
|       spawnOptions.env.TSTEST_FILTER_TAGS = this.filterTags.join(','); | ||||
|     } | ||||
|  | ||||
|     // Check for 00init.ts file in test directory | ||||
|     const testDir = plugins.path.dirname(testFile); | ||||
|     const initFile = plugins.path.join(testDir, '00init.ts'); | ||||
|     let runCommand = `tsrun ${testFile}${tsrunOptions}`; | ||||
|  | ||||
|     const initFileExists = await plugins.smartfile.fs.fileExists(initFile); | ||||
|  | ||||
|     // If 00init.ts exists, run it first | ||||
|     // Determine which file to run | ||||
|     let fileToRun = testFile; | ||||
|     let loaderPath: string | null = null; | ||||
|  | ||||
|     // If 00init.ts exists, create a loader file | ||||
|     if (initFileExists) { | ||||
|       // Create a temporary loader file that imports both 00init.ts and the test file | ||||
|       const absoluteInitFile = plugins.path.resolve(initFile); | ||||
|       const absoluteTestFile = plugins.path.resolve(testFile); | ||||
|       const loaderContent = ` | ||||
| @@ -139,10 +139,12 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | ||||
| `; | ||||
|       loaderPath = plugins.path.join(testDir, `.loader_${plugins.path.basename(testFile)}`); | ||||
|       await plugins.smartfile.memory.toFs(loaderContent, loaderPath); | ||||
|       runCommand = `tsrun ${loaderPath}${tsrunOptions}`; | ||||
|       fileToRun = loaderPath; | ||||
|     } | ||||
|  | ||||
|     const execResultStreaming = await this.smartshellInstance.execStreamingSilent(runCommand); | ||||
|     // Spawn the test process using tsrun's spawnPath API | ||||
|     // Pass undefined for fromFileUrl since fileToRun is already an absolute path | ||||
|     const tsrunProcess = plugins.tsrun.spawnPath(fileToRun, undefined, spawnOptions); | ||||
|  | ||||
|     // If we created a loader file, clean it up after test execution | ||||
|     if (loaderPath) { | ||||
| @@ -156,8 +158,8 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | ||||
|         } | ||||
|       }; | ||||
|  | ||||
|       execResultStreaming.childProcess.on('exit', cleanup); | ||||
|       execResultStreaming.childProcess.on('error', cleanup); | ||||
|       tsrunProcess.childProcess.on('exit', cleanup); | ||||
|       tsrunProcess.childProcess.on('error', cleanup); | ||||
|     } | ||||
|  | ||||
|     // Start warning timer if no timeout was specified | ||||
| @@ -180,15 +182,15 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | ||||
|  | ||||
|       const timeoutPromise = new Promise<void>((_resolve, reject) => { | ||||
|         timeoutId = setTimeout(async () => { | ||||
|           // Use smartshell's terminate() to kill entire process tree | ||||
|           await execResultStreaming.terminate(); | ||||
|           // Use tsrun's terminate() to gracefully kill the process | ||||
|           await tsrunProcess.terminate(); | ||||
|           reject(new Error(`Test file timed out after ${this.timeoutSeconds} seconds`)); | ||||
|         }, timeoutMs); | ||||
|       }); | ||||
|  | ||||
|       try { | ||||
|         await Promise.race([ | ||||
|           tapParser.handleTapProcess(execResultStreaming.childProcess), | ||||
|           tapParser.handleTapProcess(tsrunProcess.childProcess), | ||||
|           timeoutPromise | ||||
|         ]); | ||||
|         // Clear timeout if test completed successfully | ||||
| @@ -200,16 +202,16 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | ||||
|         } | ||||
|         // Handle timeout error | ||||
|         tapParser.handleTimeout(this.timeoutSeconds); | ||||
|         // Ensure entire process tree is killed if still running | ||||
|         // Ensure process is killed if still running | ||||
|         try { | ||||
|           await execResultStreaming.kill(); // This kills the entire process tree with SIGKILL | ||||
|           tsrunProcess.kill('SIGKILL'); | ||||
|         } catch (killError) { | ||||
|           // Process tree might already be dead | ||||
|           // Process might already be dead | ||||
|         } | ||||
|         await tapParser.evaluateFinalResult(); | ||||
|       } | ||||
|     } else { | ||||
|       await tapParser.handleTapProcess(execResultStreaming.childProcess); | ||||
|       await tapParser.handleTapProcess(tsrunProcess.childProcess); | ||||
|     } | ||||
|  | ||||
|     // Clear warning timer if it was set | ||||
|   | ||||
| @@ -29,7 +29,7 @@ export interface ParserConfig { | ||||
|  | ||||
| const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']); | ||||
| const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']); | ||||
| const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts']); | ||||
| const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts', 'sh']); | ||||
| const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun']; | ||||
|  | ||||
| // Legacy mappings for backwards compatibility | ||||
| @@ -228,3 +228,81 @@ export function getLegacyMigrationTarget(fileName: string): string | null { | ||||
|  | ||||
|   return parts.join('.'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Docker test file information | ||||
|  */ | ||||
| export interface DockerTestFileInfo { | ||||
|   baseName: string; | ||||
|   variant: string; | ||||
|   isDockerTest: true; | ||||
|   original: string; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Check if a filename matches the Docker test pattern: *.{variant}.docker.sh | ||||
|  * Examples: test.latest.docker.sh, test.integration.npmci.docker.sh | ||||
|  */ | ||||
| export function isDockerTestFile(fileName: string): boolean { | ||||
|   // Must end with .docker.sh | ||||
|   if (!fileName.endsWith('.docker.sh')) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Extract filename from path if needed | ||||
|   const name = fileName.split('/').pop() || fileName; | ||||
|  | ||||
|   // Must have at least 3 parts: [baseName, variant, docker, sh] | ||||
|   const parts = name.split('.'); | ||||
|   return parts.length >= 4 && parts[parts.length - 2] === 'docker' && parts[parts.length - 1] === 'sh'; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Parse a Docker test filename to extract variant and base name | ||||
|  * Pattern: test.{baseName}.{variant}.docker.sh | ||||
|  * Examples: | ||||
|  *   - test.latest.docker.sh -> { baseName: 'test', variant: 'latest' } | ||||
|  *   - test.integration.npmci.docker.sh -> { baseName: 'test.integration', variant: 'npmci' } | ||||
|  */ | ||||
| export function parseDockerTestFilename(filePath: string): DockerTestFileInfo { | ||||
|   // Extract just the filename from the path | ||||
|   const fileName = filePath.split('/').pop() || filePath; | ||||
|   const original = fileName; | ||||
|  | ||||
|   if (!isDockerTestFile(fileName)) { | ||||
|     throw new Error(`Not a valid Docker test file: "${fileName}". Expected pattern: *.{variant}.docker.sh`); | ||||
|   } | ||||
|  | ||||
|   // Remove .docker.sh suffix | ||||
|   const withoutSuffix = fileName.slice(0, -10); // Remove '.docker.sh' | ||||
|   const tokens = withoutSuffix.split('.'); | ||||
|  | ||||
|   if (tokens.length === 0) { | ||||
|     throw new Error(`Invalid Docker test file: empty basename in "${fileName}"`); | ||||
|   } | ||||
|  | ||||
|   // Last token before .docker.sh is the variant | ||||
|   const variant = tokens[tokens.length - 1]; | ||||
|  | ||||
|   // Everything else is the base name | ||||
|   const baseName = tokens.slice(0, -1).join('.'); | ||||
|  | ||||
|   return { | ||||
|     baseName: baseName || 'test', | ||||
|     variant, | ||||
|     isDockerTest: true, | ||||
|     original, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Map a Docker variant to its corresponding Dockerfile path | ||||
|  * "latest" -> "Dockerfile" | ||||
|  * Other variants -> "Dockerfile_{variant}" | ||||
|  */ | ||||
| export function mapVariantToDockerfile(variant: string, baseDir: string): string { | ||||
|   if (variant === 'latest') { | ||||
|     return `${baseDir}/Dockerfile`; | ||||
|   } | ||||
|   return `${baseDir}/Dockerfile_${variant}`; | ||||
| } | ||||
|   | ||||
| @@ -74,12 +74,20 @@ export class TestDirectory { | ||||
|       case TestExecutionMode.DIRECTORY: | ||||
|         // Directory mode - now recursive with ** pattern | ||||
|         const dirPath = plugins.path.join(this.cwd, this.testPath); | ||||
|         const testPattern = '**/test*.ts'; | ||||
|  | ||||
|         const testFiles = await plugins.smartfile.fs.listFileTree(dirPath, testPattern); | ||||
|         // Search for both TypeScript test files and Docker shell test files | ||||
|         const tsPattern = '**/test*.ts'; | ||||
|         const dockerPattern = '**/*.docker.sh'; | ||||
|  | ||||
|         const [tsFiles, dockerFiles] = await Promise.all([ | ||||
|           plugins.smartfile.fs.listFileTree(dirPath, tsPattern), | ||||
|           plugins.smartfile.fs.listFileTree(dirPath, dockerPattern), | ||||
|         ]); | ||||
|  | ||||
|         const allTestFiles = [...tsFiles, ...dockerFiles]; | ||||
|  | ||||
|         this.testfileArray = await Promise.all( | ||||
|           testFiles.map(async (filePath) => { | ||||
|           allTestFiles.map(async (filePath) => { | ||||
|             const absolutePath = plugins.path.isAbsolute(filePath) | ||||
|               ? filePath | ||||
|               : plugins.path.join(dirPath, filePath); | ||||
|   | ||||
| @@ -11,12 +11,13 @@ import { TsTestLogger } from './tstest.logging.js'; | ||||
| import type { LogOptions } from './tstest.logging.js'; | ||||
|  | ||||
| // Runtime adapters | ||||
| import { parseTestFilename } from './tstest.classes.runtime.parser.js'; | ||||
| import { parseTestFilename, isDockerTestFile, parseDockerTestFilename } from './tstest.classes.runtime.parser.js'; | ||||
| import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js'; | ||||
| import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js'; | ||||
| import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js'; | ||||
| import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js'; | ||||
| import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js'; | ||||
| import { DockerRuntimeAdapter } from './tstest.classes.runtime.docker.js'; | ||||
|  | ||||
| export class TsTest { | ||||
|   public testDir: TestDirectory; | ||||
| @@ -37,6 +38,7 @@ export class TsTest { | ||||
|   public tsbundleInstance = new plugins.tsbundle.TsBundle(); | ||||
|  | ||||
|   public runtimeRegistry = new RuntimeAdapterRegistry(); | ||||
|   public dockerAdapter: DockerRuntimeAdapter | null = null; | ||||
|  | ||||
|   constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) { | ||||
|     this.executionMode = executionModeArg; | ||||
| @@ -60,9 +62,29 @@ export class TsTest { | ||||
|     this.runtimeRegistry.register( | ||||
|       new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags) | ||||
|     ); | ||||
|  | ||||
|     // Initialize Docker adapter | ||||
|     this.dockerAdapter = new DockerRuntimeAdapter( | ||||
|       this.logger, | ||||
|       this.smartshellInstance, | ||||
|       this.timeoutSeconds, | ||||
|       cwdArg | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Check and display available runtimes | ||||
|    */ | ||||
|   private async checkEnvironment() { | ||||
|     const availability = await this.runtimeRegistry.checkAvailability(); | ||||
|     this.logger.environmentCheck(availability); | ||||
|     return availability; | ||||
|   } | ||||
|  | ||||
|   async run() { | ||||
|     // Check and display environment | ||||
|     await this.checkEnvironment(); | ||||
|      | ||||
|     // Move previous log files if --logfile option is used | ||||
|     if (this.logger.options.logFile) { | ||||
|       await this.movePreviousLogFiles(); | ||||
| @@ -199,8 +221,14 @@ export class TsTest { | ||||
|   } | ||||
|    | ||||
|   private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) { | ||||
|     // Parse the filename to determine runtimes and modifiers | ||||
|     const fileName = plugins.path.basename(fileNameArg); | ||||
|  | ||||
|     // Check if this is a Docker test file | ||||
|     if (isDockerTestFile(fileName)) { | ||||
|       return await this.runDockerTest(fileNameArg, fileIndex, totalFiles, tapCombinator); | ||||
|     } | ||||
|  | ||||
|     // Parse the filename to determine runtimes and modifiers (for TypeScript tests) | ||||
|     const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false }); | ||||
|  | ||||
|     // Check for nonci modifier in CI environment | ||||
| @@ -246,6 +274,28 @@ export class TsTest { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Execute a Docker test file | ||||
|    */ | ||||
|   private async runDockerTest( | ||||
|     fileNameArg: string, | ||||
|     fileIndex: number, | ||||
|     totalFiles: number, | ||||
|     tapCombinator: TapCombinator | ||||
|   ): Promise<void> { | ||||
|     if (!this.dockerAdapter) { | ||||
|       this.logger.tapOutput(cs('❌ Docker adapter not initialized', 'red')); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       const tapParser = await this.dockerAdapter.run(fileNameArg, fileIndex, totalFiles); | ||||
|       tapCombinator.addTapParser(tapParser); | ||||
|     } catch (error) { | ||||
|       this.logger.tapOutput(cs(`❌ Docker test failed: ${error.message}`, 'red')); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> { | ||||
|     this.logger.testFileStart(fileNameArg, 'node.js', index, total); | ||||
|     const tapParser = new TapParser(fileNameArg + ':node', this.logger); | ||||
|   | ||||
| @@ -138,6 +138,43 @@ export class TsTestLogger { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Environment check - display available runtimes | ||||
|   environmentCheck(availability: Map<string, { available: boolean; version?: string; error?: string }>) { | ||||
|     if (this.options.json) { | ||||
|       const runtimes: any = {}; | ||||
|       for (const [runtime, info] of availability) { | ||||
|         runtimes[runtime] = info; | ||||
|       } | ||||
|       this.logJson({ event: 'environmentCheck', runtimes }); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     if (this.options.quiet) return; | ||||
|      | ||||
|     this.log(this.format('\n🌍 Test Environment', 'bold')); | ||||
|      | ||||
|     // Define runtime display names | ||||
|     const runtimeNames: Record<string, string> = { | ||||
|       node: 'Node.js', | ||||
|       deno: 'Deno', | ||||
|       bun: 'Bun', | ||||
|       chromium: 'Chrome/Chromium' | ||||
|     }; | ||||
|      | ||||
|     // Display each runtime | ||||
|     for (const [runtime, info] of availability) { | ||||
|       const displayName = runtimeNames[runtime] || runtime; | ||||
|        | ||||
|       if (info.available) { | ||||
|         const versionStr = info.version ? ` ${info.version}` : ''; | ||||
|         this.log(this.format(`   ✓ ${displayName}${versionStr}`, 'green')); | ||||
|       } else { | ||||
|         const errorStr = info.error ? ` (${info.error})` : ''; | ||||
|         this.log(this.format(`   ✗ ${displayName}${errorStr}`, 'dim')); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Test execution | ||||
|   testFileStart(filename: string, runtime: string, index: number, total: number) { | ||||
|     this.currentFileResult = { | ||||
|   | ||||
| @@ -37,8 +37,9 @@ export { | ||||
|  | ||||
| // @git.zone scope | ||||
| import * as tsbundle from '@git.zone/tsbundle'; | ||||
| import * as tsrun from '@git.zone/tsrun'; | ||||
|  | ||||
| export { tsbundle }; | ||||
| export { tsbundle, tsrun }; | ||||
|  | ||||
| // sindresorhus | ||||
| import figures from 'figures'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user