From 6e506a023940116e26c2e44831e4d7adb6557c67 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Mon, 19 Jan 2026 14:06:51 +0100 Subject: [PATCH 01/11] feat: add colored logging with pino MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add pino@10.2.0 and pino-pretty@13.1.3 for CLI-friendly colored log output - Create centralized logger module (src/logger.ts) - Replace all console.log/error/warn calls with logger.info/error/warn - Update tests to mock the new logger Pino is the de facto standard for high-performance Node.js logging with: - Structured JSON output (production) - Pretty colored output (development via pino-pretty) - Configurable log levels via LOG_LEVEL env var diff --git c/package.json i/package.json index e5a2f56..984399f 100644 --- c/package.json +++ i/package.json @@ -4,7 +4,13 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48", - "files": ["dist", "src", "README.md", "LICENSE", "package.json"], + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], "engines": { "node": ">=22.0.0" }, @@ -46,6 +52,8 @@ "openapi-fetch": "0.15.0", "ora": "9.0.0", "otplib": "13.1.1", + "pino": "10.2.0", + "pino-pretty": "13.1.3", "shell-quote": "1.8.3", "simple-git": "3.30.0", "tabtab": "3.0.2", diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml index 43fc574..266eae6 100644 --- c/pnpm-lock.yaml +++ i/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: otplib: specifier: 13.1.1 version: 13.1.1 + pino: + specifier: 10.2.0 + version: 10.2.0 + pino-pretty: + specifier: 13.1.3 + version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -553,6 +559,9 @@ packages: '@otplib/uri@13.1.1': resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.57.0': resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} @@ -874,6 +883,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -937,6 +950,9 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -955,6 +971,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -997,6 +1016,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1044,6 +1066,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1051,6 +1076,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1140,6 +1168,9 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1240,6 +1271,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1344,6 +1379,13 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1429,6 +1471,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.2.0: + resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==} + hasBin: true + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -1458,6 +1514,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1465,6 +1527,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1472,6 +1537,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + remeda@2.33.4: resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} @@ -1517,9 +1586,16 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1549,10 +1625,17 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1598,6 +1681,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1609,6 +1696,10 @@ packages: tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -1820,6 +1911,9 @@ packages: engines: {node: '>=8'} hasBin: true + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2209,6 +2303,8 @@ snapshots: dependencies: '@otplib/core': 13.1.1 + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.57.0': dependencies: playwright: 1.57.0 @@ -2484,6 +2580,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + balanced-match@1.0.2: {} bluebird@3.7.2: {} @@ -2538,6 +2636,8 @@ snapshots: colorette@1.4.0: {} + colorette@2.0.20: {} + commander@14.0.2: {} commander@7.2.0: {} @@ -2552,6 +2652,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + dateformat@4.6.3: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2581,6 +2683,10 @@ snapshots: dependencies: jake: 10.9.4 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2681,6 +2787,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2691,6 +2799,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -2780,6 +2890,8 @@ snapshots: has-flag@3.0.0: {} + help-me@5.0.0: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2861,6 +2973,8 @@ snapshots: jiti@2.6.1: {} + joycon@3.1.1: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -2953,6 +3067,12 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3079,6 +3199,42 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.2.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 4.0.0 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -3103,10 +3259,19 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -3119,6 +3284,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + remeda@2.33.4: {} require-from-string@2.0.2: {} @@ -3182,8 +3349,12 @@ snapshots: safe-buffer@5.1.2: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + secure-json-parse@4.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3208,8 +3379,14 @@ snapshots: slash@5.1.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -3250,6 +3427,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-json-comments@5.0.3: {} + supports-color@10.2.2: {} supports-color@5.5.0: @@ -3267,6 +3446,10 @@ snapshots: transitivePeerDependencies: - supports-color + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tinybench@2.9.0: {} @@ -3430,6 +3613,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrappy@1.0.2: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git c/src/completion.ts i/src/completion.ts index f5a0c77..3e0b0ac 100644 --- c/src/completion.ts +++ i/src/completion.ts @@ -3,6 +3,7 @@ import { parse } from "shell-quote"; import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; +import { logger } from "./logger"; import { loadConfig } from "./config"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; @@ -193,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => console.error("INSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "INSTALL ERROR")); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => console.error("UNINSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "UNINSTALL ERROR")); }; diff --git c/src/config.ts i/src/config.ts index 9268bd7..bf42479 100644 --- c/src/config.ts +++ i/src/config.ts @@ -3,6 +3,8 @@ import fs from "fs/promises"; import { homedir } from "os"; import { join } from "path"; +import { logger } from "./logger"; + const CONFIG_DIR = ".config"; export async function getConfigPath(ensureDir?: boolean): Promise { @@ -45,9 +47,9 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - console.error( - "❌ Error parsing configuration:", - (error as Error).message, + logger.error( + { err: error as Error }, + "❌ Error parsing configuration", ); process.exit(1); } @@ -59,10 +61,10 @@ export async function saveConfig(newConfig: Config): Promise { try { const configPath = await getConfigPath(true); await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8"); - console.log(`✅ Configuration saved to ${configPath}`); + logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - console.error("❌ Error saving configuration:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error saving configuration"); process.exit(1); } } diff --git c/src/debugtopus/index.ts i/src/debugtopus/index.ts index 05ad042..32e7e63 100644 --- c/src/debugtopus/index.ts +++ i/src/debugtopus/index.ts @@ -16,6 +16,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../helpers"; +import { logger } from "../logger"; import { getEnvironments, getPlaywrightCode, @@ -91,7 +92,7 @@ const prepareDirectories = async ( const appDir = dirname(nodeModule.filename); packageRootDir = getPackageRootLevel(appDir); } else { - console.warn("package was not installed as valid nodeJS module"); + logger.warn("package was not installed as valid nodeJS module"); packageRootDir = process.cwd(); } } @@ -169,17 +170,17 @@ const runTests = async ({ command += " --ui"; } - console.log(`executing command : '${command}'`); + logger.info(`executing command : '${command}'`); const { stderr } = await promisify(exec)(command, { cwd: packageRootDir, }); if (stderr) { - console.error(stderr); + logger.error(stderr); process.exit(1); } else { - console.log(`success, you can find your artifacts at ${outputDir}`); + logger.info(`success, you can find your artifacts at ${outputDir}`); } }; diff --git c/src/debugtopus/installation.ts i/src/debugtopus/installation.ts index 6582abd..080346a 100644 --- c/src/debugtopus/installation.ts +++ i/src/debugtopus/installation.ts @@ -5,6 +5,8 @@ import { promisify } from "util"; import { chromium } from "@playwright/test"; +import { logger } from "../logger"; + export const ensureChromiumIsInstalled = async ( packageRootDir: string, ): Promise => { @@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async ( reject(error); return; } - console.log( + logger.info( "Couldn't find any chromium binary, executing 'npx playwright install chromium'", ); @@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async ( ); playwrightInstallExecution.child?.stdout?.on("data", (data) => - console.log(data), + logger.info(data), ); await playwrightInstallExecution; diff --git c/src/logger.ts i/src/logger.ts new file mode 100644 index 0000000..946ed95 --- /dev/null +++ i/src/logger.ts @@ -0,0 +1,15 @@ +import pino from "pino"; + +const isProduction = process.env.NODE_ENV === "production"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || "info", + transport: isProduction + ? undefined + : { + target: "pino-pretty", + options: { + colorize: true, + }, + }, +}); diff --git c/src/plw/index.ts i/src/plw/index.ts index b26e846..ea22afc 100644 --- c/src/plw/index.ts +++ i/src/plw/index.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import { createInterface } from "readline"; import { loadConfig } from "../config"; +import { logger } from "../logger"; interface StreamResult { lines: string[]; @@ -39,7 +40,7 @@ const spawnAndStreamLines = async ( rl.on("line", (line: string) => { lines.push(line); - console.log(`-- ${line}`); + logger.info(`-- ${line}`); lineCount++; if (lineCount >= maxLines) { @@ -58,7 +59,7 @@ const spawnAndStreamLines = async ( child.on("exit", (code, signal) => { if (lineCount < maxLines) { - console.log(`Process exited with code ${code}, signal ${signal}`); + logger.info(`Process exited with code ${code}, signal ${signal}`); } resolve({ lines, detached: false, code, signal }); }); @@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: { }); if (!(await checkDockerDaemon())) { - console.error( + logger.error( "Docker daemon is not running. Please start Docker and try again.", ); return; } - console.log( + logger.info( `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`, ); const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); - console.log(`Captured ${result.lines.length} lines`); - console.log(`Process detached: ${result.detached}`); + logger.info(`Captured ${result.lines.length} lines`); + logger.info(`Process detached: ${result.detached}`); }; export const stopPLW = async (): Promise => { @@ -131,9 +132,9 @@ export const stopPLW = async (): Promise => { const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); if (result.code === 0) { - console.log("Private Location Worker stopped successfully."); + logger.info("Private Location Worker stopped successfully."); } else { - console.error( + logger.error( `Failed to stop Private Location Worker. Exit code: ${result.code}`, ); } diff --git c/src/tools/client.ts i/src/tools/client.ts index 979aa2a..6e5146c 100644 --- c/src/tools/client.ts +++ i/src/tools/client.ts @@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch"; import type { components, paths } from "../api"; // generated by openapi-typescript import { loadConfig } from "../config"; +import { logger } from "../logger"; import { version } from "../version"; export const BASE_URL = @@ -65,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - console.error(error); + logger.error(error); process.exit(1); }, }; @@ -77,9 +78,9 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - console.error(error); + logger.error(error); if (typeof error === "string" && error.startsWith("403")) { - console.error( + logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", ); } @@ -91,5 +92,5 @@ export type ListOptions = { json?: boolean; }; export const logJson = (result: unknown): void => { - console.log(JSON.stringify(result, null, 2)); + logger.info(JSON.stringify(result, null, 2)); }; diff --git c/src/tools/discoveries.ts i/src/tools/discoveries.ts index a8f2449..7aa3d14 100644 --- c/src/tools/discoveries.ts +++ i/src/tools/discoveries.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -47,9 +48,9 @@ export const batchGeneration = async ( throw new Error("Batch generation ID is missing"); } - console.log("Batch generation created successfully!"); - console.log(`Batch generation ID: ${response.batchGenerationId}`); - console.log( + logger.info("Batch generation created successfully!"); + logger.info(`Batch generation ID: ${response.batchGenerationId}`); + logger.info( `Batch generation URL: ${await getUrl({ batchGenerationId: response.batchGenerationId, entityType: "batch-generation", @@ -95,13 +96,13 @@ export const createDiscovery = async ( return; } - console.log("Discovery created successfully!"); - console.log( + logger.info("Discovery created successfully!"); + logger.info( `${await getUrl({ testCaseId: response.testCaseId ?? "", entityType: "discovery", })}`, ); - console.log(`Discovery ID: ${response.discoveryId}`); - console.log(`Test Case ID: ${response.testCaseId}`); + logger.info(`Discovery ID: ${response.discoveryId}`); + logger.info(`Test Case ID: ${response.testCaseId}`); }; diff --git c/src/tools/environments.ts i/src/tools/environments.ts index 6712707..c38925f 100644 --- c/src/tools/environments.ts +++ i/src/tools/environments.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type GetEnvironmentsOptions = @@ -26,12 +27,12 @@ export const listEnvironments = async ( return; } - console.log("Environments:"); + logger.info("Environments:"); environments.forEach((environment) => { - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }); }; @@ -99,11 +100,11 @@ export const createEnvironment = async ( return; } - console.log("Environment created successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment created successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const updateEnvironment = async ( @@ -152,11 +153,11 @@ export const updateEnvironment = async ( return; } - console.log("Environment updated successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment updated successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const getEnvironment = async ( @@ -176,11 +177,11 @@ export const getEnvironment = async ( return; } - console.log("Environment:"); - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info("Environment:"); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }; export const deleteEnvironment = async ( @@ -205,5 +206,5 @@ export const deleteEnvironment = async ( return; } - console.log("Environment deleted successfully!"); + logger.info("Environment deleted successfully!"); }; diff --git c/src/tools/init.ts i/src/tools/init.ts index a815fd2..57ad8b2 100644 --- c/src/tools/init.ts +++ i/src/tools/init.ts @@ -1,5 +1,6 @@ import { Config, loadConfig, saveConfig } from "../config"; import { promptUser } from "../helpers"; +import { logger } from "../logger"; import { getTestTargets, listTestTargets } from "./test-targets"; const selectTestTarget = async (): Promise => { @@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise => { await listTestTargets({}); if (testTargets.length === 1) { - console.log( + logger.info( `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`, ); return testTargets[0].id; @@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise => { testTargetIndexAsInt < 1 || testTargetIndexAsInt > testTargets.length ) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } const testTargetId = testTargets[testTargetIndexAsInt - 1].id; if (!testTargetId) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } @@ -43,7 +44,7 @@ export const switchTestTarget = async () => { testTargetId, }; await saveConfig(newConfig); - console.log(`✨ Switched to test target: ${testTargetId}`); + logger.info(`✨ Switched to test target: ${testTargetId}`); }; export const init = async (options: { @@ -52,12 +53,12 @@ export const init = async (options: { force?: boolean; }) => { try { - console.log("🚀 Initializing configuration...\n"); + logger.info("🚀 Initializing configuration...\n"); const existingConfig = await loadConfig(options.force); if (existingConfig.apiKey && !options.force) { - console.log("⚠️ Configuration already exists."); + logger.info("⚠️ Configuration already exists."); const overwrite = await promptUser( "Do you want to overwrite it? (y/N): ", ); @@ -66,7 +67,7 @@ export const init = async (options: { overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes" ) { - console.log("Configuration unchanged."); + logger.info("Configuration unchanged."); return; } } @@ -76,7 +77,7 @@ export const init = async (options: { "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ", ); if (!options.apiKey) { - console.log("❌ API key is required."); + logger.info("❌ API key is required."); process.exit(1); } } @@ -99,9 +100,9 @@ export const init = async (options: { await saveConfig(newConfig); - console.log("\n✨ Initialization complete!"); + logger.info("\n✨ Initialization complete!"); } catch (error) { - console.error("❌ Error during initialization:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error during initialization"); process.exit(1); } }; diff --git c/src/tools/notifications.ts i/src/tools/notifications.ts index a1b9339..93f9319 100644 --- c/src/tools/notifications.ts +++ i/src/tools/notifications.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -27,28 +28,28 @@ export const listNotifications = async ( return; } - console.log("Notifications:"); + logger.info("Notifications:"); for (const notification of response) { - console.log(`\nID: ${notification.id}`); - console.log(`Type: ${notification.type}`); - console.log(`Created At: ${notification.createdAt}`); + logger.info(`\nID: ${notification.id}`); + logger.info(`Type: ${notification.type}`); + logger.info(`Created At: ${notification.createdAt}`); if (notification.payload?.testReportId) { - console.log(`Test Report ID: ${notification.payload.testReportId}`); - console.log( + logger.info(`Test Report ID: ${notification.payload.testReportId}`); + logger.info( `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`, ); } if (notification.payload?.testCaseId) { - console.log(`Test Case ID: ${notification.payload.testCaseId}`); - console.log( + logger.info(`Test Case ID: ${notification.payload.testCaseId}`); + logger.info( `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`, ); } if (notification.payload?.failed !== undefined) { - console.log(`Failed: ${notification.payload.failed}`); + logger.info(`Failed: ${notification.payload.failed}`); } if (notification.ack) { - console.log(`Acknowledged: ${notification.ack}`); + logger.info(`Acknowledged: ${notification.ack}`); } } }; diff --git c/src/tools/private-locations.ts i/src/tools/private-locations.ts index b91a414..7c0ba62 100644 --- c/src/tools/private-locations.ts +++ i/src/tools/private-locations.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type SuccessResponse = components["schemas"]["SuccessResponse"]; @@ -35,7 +36,7 @@ export const registerLocation = async ( return; } - console.log("Registration result:", response.success ? "Success" : "Failed"); + logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`); }; export const unregisterLocation = async ( @@ -60,9 +61,8 @@ export const unregisterLocation = async ( return; } - console.log( - "Unregistration result:", - response.success ? "Success" : "Failed", + logger.info( + `Unregistration result: ${response.success ? "Success" : "Failed"}`, ); }; @@ -79,10 +79,10 @@ export const listPrivateLocations = async ( return; } - console.log("Private Locations:"); + logger.info("Private Locations:"); response.forEach((location) => { - console.log(`- Name: ${location.name}`); - console.log(` Status: ${location.status}`); - console.log(` Address: ${location.address}`); + logger.info(`- Name: ${location.name}`); + logger.info(` Status: ${location.status}`); + logger.info(` Address: ${location.address}`); }); }; diff --git c/src/tools/sync/git.ts i/src/tools/sync/git.ts index 765b3bd..3a7fc1f 100644 --- c/src/tools/sync/git.ts +++ i/src/tools/sync/git.ts @@ -2,6 +2,7 @@ import path from "path"; import { simpleGit } from "simple-git"; +import { logger } from "../../logger"; import { ExecutionContext } from "./types"; const FALLBACK_DEFAULT_BRANCH = "refs/heads/main"; @@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - console.error(error); + logger.error({ err: error }, "Failed to parse git remote"); return {}; } }; @@ -59,9 +60,9 @@ export const getDefaultBranch = async ( return symbolicRefBranch; } } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify symbolic ref, falling back to origin parsing", - e, ); } } @@ -69,7 +70,7 @@ export const getDefaultBranch = async ( try { const origin = await simpleGit().remote(["show", "origin"]); if (!origin) { - console.warn("could not identify default branch, falling back to 'main'"); + logger.warn("could not identify default branch, falling back to 'main'"); return FALLBACK_DEFAULT_BRANCH; } @@ -79,9 +80,9 @@ export const getDefaultBranch = async ( ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify default branch, falling back to 'main'", - e, ); } @@ -108,9 +109,9 @@ export const getGitContext = async (): Promise => { }; return ctx; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify git context, falling back to undefined", - e, ); return undefined; } diff --git c/src/tools/sync/yaml.ts i/src/tools/sync/yaml.ts index 7451476..edf2d28 100644 --- c/src/tools/sync/yaml.ts +++ i/src/tools/sync/yaml.ts @@ -4,6 +4,7 @@ import path from "path"; import yaml from "yaml"; +import { logger } from "../../logger"; import { PushTestTargetBody } from "../../schemas/octomindExternalAPI"; import { SyncTestCase, TestTargetSyncData } from "./types"; @@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => { if (result.success) { testCases.push(result.data); } else { - console.warn( + logger.warn( `Failed to read test case from ${file}: ${result.error.message}`, ); } } catch { - console.error(`Failed to read test case from ${file}`); + logger.error(`Failed to read test case from ${file}`); } } diff --git c/src/tools/test-cases.ts i/src/tools/test-cases.ts index 742fb06..54a3831 100644 --- c/src/tools/test-cases.ts +++ i/src/tools/test-cases.ts @@ -3,6 +3,7 @@ import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; @@ -37,7 +38,7 @@ export const deleteTestCase = async ( if (options.json) { logJson(data); } - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); return; } @@ -46,7 +47,7 @@ export const deleteTestCase = async ( const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log( + logger.info( `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, ); return; @@ -54,7 +55,7 @@ export const deleteTestCase = async ( const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); }; export const listTestCase = async ( @@ -81,35 +82,35 @@ export const listTestCase = async ( return; } - console.log("Test Case Details:"); - console.log(`ID: ${response.id}`); - console.log(`Description: ${response.description}`); - console.log(`Status: ${response.status}`); - console.log(`Run Status: ${response.runStatus}`); - console.log(`Created At: ${response.createdAt}`); - console.log(`Updated At: ${response.updatedAt}`); + logger.info("Test Case Details:"); + logger.info(`ID: ${response.id}`); + logger.info(`Description: ${response.description}`); + logger.info(`Status: ${response.status}`); + logger.info(`Run Status: ${response.runStatus}`); + logger.info(`Created At: ${response.createdAt}`); + logger.info(`Updated At: ${response.updatedAt}`); if (response.elements && response.elements.length > 0) { - console.log("\nElements:"); + logger.info("\nElements:"); response.elements.forEach((element, index) => { - console.log(`\nElement ${index + 1}:`); + logger.info(`\nElement ${index + 1}:`); if (element.interaction) { - console.log(` Action: ${element.interaction.action}`); + logger.info(` Action: ${element.interaction.action}`); if (element.interaction.calledWith) { - console.log(` Called With: ${element.interaction.calledWith}`); + logger.info(` Called With: ${element.interaction.calledWith}`); } } if (element.assertion) { - console.log(` Expectation: ${element.assertion.expectation}`); + logger.info(` Expectation: ${element.assertion.expectation}`); if (element.assertion.calledWith) { - console.log(` Called With: ${element.assertion.calledWith}`); + logger.info(` Called With: ${element.assertion.calledWith}`); } } - console.log(" Selectors:"); + logger.info(" Selectors:"); element.selectors?.forEach((selector) => { - console.log(` - ${selector.selectorType}: ${selector.selector}`); + logger.info(` - ${selector.selectorType}: ${selector.selector}`); if (selector.options?.name) { - console.log(` Name: ${selector.options.name}`); + logger.info(` Name: ${selector.options.name}`); } }); }); @@ -193,7 +194,7 @@ export const getTestCaseCode = async ( if (!data) { throw new Error("no test code found"); } - console.log(data.testCode); + logger.info(data.testCode); }; export const listTestCases = async ( @@ -209,16 +210,16 @@ export const listTestCases = async ( return; } - console.log("Test Cases:"); + logger.info("Test Cases:"); for (let idx = 0; idx < testCases.length; idx++) { const testCase = testCases[idx]; const idxString = `${idx + 1}. `.padEnd( testCases.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}Description: ${testCase.description}`); - console.log(`${paddingString}ID: ${testCase.id}`); - console.log( + logger.info(`${idxString}Description: ${testCase.description}`); + logger.info(`${paddingString}ID: ${testCase.id}`); + logger.info( `${paddingString}${await getUrl({ testCaseId: testCase.id, entityType: "test-case", diff --git c/src/tools/test-reports.ts i/src/tools/test-reports.ts index 1e5e509..2571e8e 100644 --- c/src/tools/test-reports.ts +++ i/src/tools/test-reports.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -39,19 +40,19 @@ export const executeTests = async ( } if (data) { - console.log("Test execution started successfully!"); - console.log("Test Report URL:", data.testReportUrl); - console.log("Report Status:", data.testReport?.status); + logger.info("Test execution started successfully!"); + logger.info(`Test Report URL: ${data.testReportUrl}`); + logger.info(`Report Status: ${data.testReport?.status}`); const numberOfTestResults = data.testReport?.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } }); } @@ -100,15 +101,15 @@ export const listTestReport = async ( } const response = data as TestReport; - console.log("Test Report Details:"); - console.log("Status:", response.status); - console.log("Execution URL:", response.executionUrl); + logger.info("Test Report Details:"); + logger.info(`Status: ${response.status}`); + logger.info(`Execution URL: ${response.executionUrl}`); const numberOfTestResults = response.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); - console.log( + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info( ` ${await getUrl({ testReportId: options.testReportId, testResultId: result.id ?? "", @@ -116,10 +117,10 @@ export const listTestReport = async ( })}`, ); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } } } diff --git c/src/tools/test-targets.ts i/src/tools/test-targets.ts index cf39a33..2cb64e4 100644 --- c/src/tools/test-targets.ts +++ i/src/tools/test-targets.ts @@ -4,6 +4,7 @@ import ora from "ora"; import { OCTOMIND_FOLDER_NAME } from "../constants"; import { confirmAction, findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; @@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { return; } - console.log("Test Targets:"); + logger.info("Test Targets:"); for (let idx = 0; idx < testTargets.length; idx++) { const testTarget = testTargets[idx]; @@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise => { testTargets.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}ID: ${testTarget.id}`); - console.log(`${paddingString}App: ${testTarget.app}`); - console.log( + logger.info(`${idxString}ID: ${testTarget.id}`); + logger.info(`${paddingString}App: ${testTarget.app}`); + logger.info( `${paddingString}${await getUrl({ testTargetId: testTarget.id, entityType: "test-target", @@ -98,7 +99,7 @@ export const pullTestTarget = async ( path.join(process.cwd(), OCTOMIND_FOLDER_NAME); await writeYaml(data, destination); - console.log("Test Target pulled successfully"); + logger.info("Test Target pulled successfully"); }; export const pushTestTarget = async ( @@ -124,7 +125,7 @@ export const pushTestTarget = async ( `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`, ); if (!confirmed) { - console.log("Push cancelled."); + logger.info("Push cancelled."); return; } } diff --git c/src/tools/update.ts i/src/tools/update.ts index 2091739..6b89dc1 100644 --- c/src/tools/update.ts +++ i/src/tools/update.ts @@ -5,6 +5,8 @@ import path from "path"; import which from "which"; +import { logger } from "../logger"; + export const update = async (): Promise => { const pathToRoot = path.posix.normalize( path.join(homedir(), "/.local/packages"), @@ -12,20 +14,20 @@ export const update = async (): Promise => { const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { - console.error( + logger.error( "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind", ); process.exit(1); } if (!(await which("npm"))) { - console.error( + logger.error( `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); } - console.log(`updating package.json at ${pathToPackageJson}`); + logger.info(`updating package.json at ${pathToPackageJson}`); const result = child_process.execSync( "npm install @octomind/octomind@latest", @@ -34,6 +36,6 @@ export const update = async (): Promise => { }, ); - console.log(`${result}`); - console.log("\n✔ update complete"); + logger.info(`${result}`); + logger.info("\n✔ update complete"); }; diff --git c/src/tools/yamlMutations/create.ts i/src/tools/yamlMutations/create.ts index d819b50..946d268 100644 --- c/src/tools/yamlMutations/create.ts +++ i/src/tools/yamlMutations/create.ts @@ -9,6 +9,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -75,7 +76,7 @@ const openBrowser = async ({ await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); }; @@ -88,7 +89,7 @@ const writeOutput = async ({ createResult: SyncTestCase | "cancelled"; }): Promise => { if (createResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -101,8 +102,8 @@ const writeOutput = async ({ yaml.stringify(createResult), ); - console.log(`Created test case successfully`); - console.log(diff); + logger.info(`Created test case successfully`); + logger.info(diff); }; export const create = async (options: CreateOptions): Promise => { diff --git c/src/tools/yamlMutations/edit.ts i/src/tools/yamlMutations/edit.ts index b8fb58e..fe8a00c 100644 --- c/src/tools/yamlMutations/edit.ts +++ i/src/tools/yamlMutations/edit.ts @@ -7,6 +7,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise => { await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); @@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise => { ); if (editResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise => { yaml.stringify(editResult), ); - console.log(`Edited test case successfully`); - console.log(diff); + logger.info(`Edited test case successfully`); + logger.info(diff); }; diff --git c/tests/config.spec.ts i/tests/config.spec.ts index c1c9b9c..0856939 100644 --- c/tests/config.spec.ts +++ i/tests/config.spec.ts @@ -5,13 +5,14 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; +import { mockLogger } from "./mocks"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - console.error = vi.fn(); + mockLogger.error.mockClear(); resetConfig(); }); @@ -68,7 +69,7 @@ describe("Config", () => { const result = await loadConfig(); expect(result).toEqual({}); - expect(console.error).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); }); it("should error and exit when config file does not exist and force is true", async () => { @@ -89,7 +90,7 @@ describe("Config", () => { await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow( "Process exit with code: 1", ); - expect(console.error).toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); diff --git c/tests/mocks.ts i/tests/mocks.ts index 42342b8..02e672c 100644 --- c/tests/mocks.ts +++ i/tests/mocks.ts @@ -1,3 +1,4 @@ +import { vi } from "vitest"; import { EnvironmentResponse, TestCaseResponse, @@ -5,6 +6,19 @@ import { } from "../src/tools"; import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types"; +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, +})); + export const createMockSyncTestCase = ( overrides?: Partial, ): SyncTestCase => ({ diff --git c/tests/setup.ts i/tests/setup.ts index cd22023..aa85cd5 100644 --- c/tests/setup.ts +++ i/tests/setup.ts @@ -1,5 +1,7 @@ import { beforeEach, vi } from "vitest"; +import { mockLogger } from "./mocks"; + vi.mock("ora", () => ({ default: vi.fn(() => ({ start: vi.fn().mockReturnThis(), @@ -9,7 +11,10 @@ vi.mock("ora", () => ({ })); beforeEach(() => { - console.log = vi.fn(); - console.warn = vi.fn(); - console.error = vi.fn(); + mockLogger.info.mockClear(); + mockLogger.warn.mockClear(); + mockLogger.error.mockClear(); + mockLogger.debug.mockClear(); + mockLogger.log.mockClear(); + mockLogger.success.mockClear(); }); diff --git c/tests/tools/init.spec.ts i/tests/tools/init.spec.ts index a29a295..fc1b5b3 100644 --- c/tests/tools/init.spec.ts +++ i/tests/tools/init.spec.ts @@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; +import { mockLogger } from "../mocks"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -15,7 +16,7 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); @@ -36,10 +37,10 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Only one test target found, using it: testTargetApp (testTargetId)", ); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 3, "\n✨ Initialization complete!", ); @@ -56,7 +57,7 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId1", }); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 2, "\n✨ Initialization complete!", ); diff --git c/tests/tools/sync/git.spec.ts i/tests/tools/sync/git.spec.ts index 4dc35df..4b2a10c 100644 --- c/tests/tools/sync/git.spec.ts +++ i/tests/tools/sync/git.spec.ts @@ -7,6 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; +import { mockLogger } from "../../mocks"; vi.mock("simple-git"); @@ -17,30 +18,29 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - console.error = vi.fn(); + mockLogger.error.mockClear(); + mockLogger.warn.mockClear(); }); describe("getDefaultBranch", () => { it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockResolvedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockRejectedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); }); diff --git c/tests/tools/test-cases.spec.ts i/tests/tools/test-cases.spec.ts index 0731b48..696d4bf 100644 --- c/tests/tools/test-cases.spec.ts +++ i/tests/tools/test-cases.spec.ts @@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; +import { mockLogger } from "../mocks"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -18,7 +19,7 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); describe("deleteTestCase via API", () => { @@ -36,7 +37,7 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); }); @@ -84,7 +85,7 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); await expect(fsPromises.access(filePath)).rejects.toThrow(); @@ -98,7 +99,7 @@ describe("test-cases", () => { testCaseId: "non-existent-id", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( `No test case with id non-existent-id found in folder ${tmpDir}`, ); expect(clientDELETE).not.toHaveBeenCalled(); diff --git c/tests/tools/test-targets.spec.ts i/tests/tools/test-targets.spec.ts index 92e0bcb..37dadc4 100644 --- c/tests/tools/test-targets.spec.ts +++ i/tests/tools/test-targets.spec.ts @@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; +import { mockLogger } from "../mocks"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -35,7 +36,7 @@ describe("push", () => { error: undefined, response: mock(), }); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); it("pushes to main if on default branch", async () => { @@ -114,7 +115,7 @@ describe("push", () => { }); expect(client.POST).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Push cancelled."); + expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled."); }); }); }); diff --git c/tests/tools/yamlMutations/create.spec.ts i/tests/tools/yamlMutations/create.spec.ts index eadbc73..b1e46dd 100644 --- c/tests/tools/yamlMutations/create.spec.ts +++ i/tests/tools/yamlMutations/create.spec.ts @@ -18,6 +18,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -32,7 +33,7 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); @@ -110,7 +111,7 @@ describe("create", () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -118,7 +119,7 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully"); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git c/tests/tools/yamlMutations/edit.spec.ts i/tests/tools/yamlMutations/edit.spec.ts index dcb928c..4799668 100644 --- c/tests/tools/yamlMutations/edit.spec.ts +++ i/tests/tools/yamlMutations/edit.spec.ts @@ -15,6 +15,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -29,7 +30,7 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( @@ -96,7 +97,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -108,7 +109,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully"); }); it.each([ diff --git c/package.json i/package.json index 12b46dd..3eb85d8 100644 --- c/package.json +++ i/package.json @@ -4,7 +4,13 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48", - "files": ["dist", "src", "README.md", "LICENSE", "package.json"], + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], "engines": { "node": ">=22.0.0" }, @@ -46,6 +52,8 @@ "openapi-fetch": "0.15.0", "ora": "9.0.0", "otplib": "13.1.1", + "pino": "10.2.0", + "pino-pretty": "13.1.3", "shell-quote": "1.8.3", "simple-git": "3.30.0", "tabtab": "3.0.2", diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml index 43fc574..266eae6 100644 --- c/pnpm-lock.yaml +++ i/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: otplib: specifier: 13.1.1 version: 13.1.1 + pino: + specifier: 10.2.0 + version: 10.2.0 + pino-pretty: + specifier: 13.1.3 + version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -553,6 +559,9 @@ packages: '@otplib/uri@13.1.1': resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.57.0': resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} @@ -874,6 +883,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -937,6 +950,9 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -955,6 +971,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -997,6 +1016,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1044,6 +1066,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1051,6 +1076,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1140,6 +1168,9 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1240,6 +1271,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1344,6 +1379,13 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1429,6 +1471,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.2.0: + resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==} + hasBin: true + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -1458,6 +1514,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1465,6 +1527,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1472,6 +1537,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + remeda@2.33.4: resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} @@ -1517,9 +1586,16 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1549,10 +1625,17 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1598,6 +1681,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1609,6 +1696,10 @@ packages: tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -1820,6 +1911,9 @@ packages: engines: {node: '>=8'} hasBin: true + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2209,6 +2303,8 @@ snapshots: dependencies: '@otplib/core': 13.1.1 + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.57.0': dependencies: playwright: 1.57.0 @@ -2484,6 +2580,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + balanced-match@1.0.2: {} bluebird@3.7.2: {} @@ -2538,6 +2636,8 @@ snapshots: colorette@1.4.0: {} + colorette@2.0.20: {} + commander@14.0.2: {} commander@7.2.0: {} @@ -2552,6 +2652,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + dateformat@4.6.3: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2581,6 +2683,10 @@ snapshots: dependencies: jake: 10.9.4 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2681,6 +2787,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2691,6 +2799,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -2780,6 +2890,8 @@ snapshots: has-flag@3.0.0: {} + help-me@5.0.0: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2861,6 +2973,8 @@ snapshots: jiti@2.6.1: {} + joycon@3.1.1: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -2953,6 +3067,12 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3079,6 +3199,42 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.2.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 4.0.0 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -3103,10 +3259,19 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -3119,6 +3284,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + remeda@2.33.4: {} require-from-string@2.0.2: {} @@ -3182,8 +3349,12 @@ snapshots: safe-buffer@5.1.2: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + secure-json-parse@4.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3208,8 +3379,14 @@ snapshots: slash@5.1.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -3250,6 +3427,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-json-comments@5.0.3: {} + supports-color@10.2.2: {} supports-color@5.5.0: @@ -3267,6 +3446,10 @@ snapshots: transitivePeerDependencies: - supports-color + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tinybench@2.9.0: {} @@ -3430,6 +3613,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrappy@1.0.2: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git c/src/completion.ts i/src/completion.ts index f5a0c77..3e0b0ac 100644 --- c/src/completion.ts +++ i/src/completion.ts @@ -3,6 +3,7 @@ import { parse } from "shell-quote"; import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; +import { logger } from "./logger"; import { loadConfig } from "./config"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; @@ -193,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => console.error("INSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "INSTALL ERROR")); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => console.error("UNINSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "UNINSTALL ERROR")); }; diff --git c/src/config.ts i/src/config.ts index 9268bd7..bf42479 100644 --- c/src/config.ts +++ i/src/config.ts @@ -3,6 +3,8 @@ import fs from "fs/promises"; import { homedir } from "os"; import { join } from "path"; +import { logger } from "./logger"; + const CONFIG_DIR = ".config"; export async function getConfigPath(ensureDir?: boolean): Promise { @@ -45,9 +47,9 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - console.error( - "❌ Error parsing configuration:", - (error as Error).message, + logger.error( + { err: error as Error }, + "❌ Error parsing configuration", ); process.exit(1); } @@ -59,10 +61,10 @@ export async function saveConfig(newConfig: Config): Promise { try { const configPath = await getConfigPath(true); await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8"); - console.log(`✅ Configuration saved to ${configPath}`); + logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - console.error("❌ Error saving configuration:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error saving configuration"); process.exit(1); } } diff --git c/src/debugtopus/index.ts i/src/debugtopus/index.ts index 05ad042..32e7e63 100644 --- c/src/debugtopus/index.ts +++ i/src/debugtopus/index.ts @@ -16,6 +16,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../helpers"; +import { logger } from "../logger"; import { getEnvironments, getPlaywrightCode, @@ -91,7 +92,7 @@ const prepareDirectories = async ( const appDir = dirname(nodeModule.filename); packageRootDir = getPackageRootLevel(appDir); } else { - console.warn("package was not installed as valid nodeJS module"); + logger.warn("package was not installed as valid nodeJS module"); packageRootDir = process.cwd(); } } @@ -169,17 +170,17 @@ const runTests = async ({ command += " --ui"; } - console.log(`executing command : '${command}'`); + logger.info(`executing command : '${command}'`); const { stderr } = await promisify(exec)(command, { cwd: packageRootDir, }); if (stderr) { - console.error(stderr); + logger.error(stderr); process.exit(1); } else { - console.log(`success, you can find your artifacts at ${outputDir}`); + logger.info(`success, you can find your artifacts at ${outputDir}`); } }; diff --git c/src/debugtopus/installation.ts i/src/debugtopus/installation.ts index 6582abd..080346a 100644 --- c/src/debugtopus/installation.ts +++ i/src/debugtopus/installation.ts @@ -5,6 +5,8 @@ import { promisify } from "util"; import { chromium } from "@playwright/test"; +import { logger } from "../logger"; + export const ensureChromiumIsInstalled = async ( packageRootDir: string, ): Promise => { @@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async ( reject(error); return; } - console.log( + logger.info( "Couldn't find any chromium binary, executing 'npx playwright install chromium'", ); @@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async ( ); playwrightInstallExecution.child?.stdout?.on("data", (data) => - console.log(data), + logger.info(data), ); await playwrightInstallExecution; diff --git c/src/logger.ts i/src/logger.ts new file mode 100644 index 0000000..946ed95 --- /dev/null +++ i/src/logger.ts @@ -0,0 +1,15 @@ +import pino from "pino"; + +const isProduction = process.env.NODE_ENV === "production"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || "info", + transport: isProduction + ? undefined + : { + target: "pino-pretty", + options: { + colorize: true, + }, + }, +}); diff --git c/src/plw/index.ts i/src/plw/index.ts index b26e846..ea22afc 100644 --- c/src/plw/index.ts +++ i/src/plw/index.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import { createInterface } from "readline"; import { loadConfig } from "../config"; +import { logger } from "../logger"; interface StreamResult { lines: string[]; @@ -39,7 +40,7 @@ const spawnAndStreamLines = async ( rl.on("line", (line: string) => { lines.push(line); - console.log(`-- ${line}`); + logger.info(`-- ${line}`); lineCount++; if (lineCount >= maxLines) { @@ -58,7 +59,7 @@ const spawnAndStreamLines = async ( child.on("exit", (code, signal) => { if (lineCount < maxLines) { - console.log(`Process exited with code ${code}, signal ${signal}`); + logger.info(`Process exited with code ${code}, signal ${signal}`); } resolve({ lines, detached: false, code, signal }); }); @@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: { }); if (!(await checkDockerDaemon())) { - console.error( + logger.error( "Docker daemon is not running. Please start Docker and try again.", ); return; } - console.log( + logger.info( `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`, ); const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); - console.log(`Captured ${result.lines.length} lines`); - console.log(`Process detached: ${result.detached}`); + logger.info(`Captured ${result.lines.length} lines`); + logger.info(`Process detached: ${result.detached}`); }; export const stopPLW = async (): Promise => { @@ -131,9 +132,9 @@ export const stopPLW = async (): Promise => { const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); if (result.code === 0) { - console.log("Private Location Worker stopped successfully."); + logger.info("Private Location Worker stopped successfully."); } else { - console.error( + logger.error( `Failed to stop Private Location Worker. Exit code: ${result.code}`, ); } diff --git c/src/tools/client.ts i/src/tools/client.ts index 979aa2a..6e5146c 100644 --- c/src/tools/client.ts +++ i/src/tools/client.ts @@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch"; import type { components, paths } from "../api"; // generated by openapi-typescript import { loadConfig } from "../config"; +import { logger } from "../logger"; import { version } from "../version"; export const BASE_URL = @@ -65,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - console.error(error); + logger.error(error); process.exit(1); }, }; @@ -77,9 +78,9 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - console.error(error); + logger.error(error); if (typeof error === "string" && error.startsWith("403")) { - console.error( + logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", ); } @@ -91,5 +92,5 @@ export type ListOptions = { json?: boolean; }; export const logJson = (result: unknown): void => { - console.log(JSON.stringify(result, null, 2)); + logger.info(JSON.stringify(result, null, 2)); }; diff --git c/src/tools/discoveries.ts i/src/tools/discoveries.ts index a8f2449..7aa3d14 100644 --- c/src/tools/discoveries.ts +++ i/src/tools/discoveries.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -47,9 +48,9 @@ export const batchGeneration = async ( throw new Error("Batch generation ID is missing"); } - console.log("Batch generation created successfully!"); - console.log(`Batch generation ID: ${response.batchGenerationId}`); - console.log( + logger.info("Batch generation created successfully!"); + logger.info(`Batch generation ID: ${response.batchGenerationId}`); + logger.info( `Batch generation URL: ${await getUrl({ batchGenerationId: response.batchGenerationId, entityType: "batch-generation", @@ -95,13 +96,13 @@ export const createDiscovery = async ( return; } - console.log("Discovery created successfully!"); - console.log( + logger.info("Discovery created successfully!"); + logger.info( `${await getUrl({ testCaseId: response.testCaseId ?? "", entityType: "discovery", })}`, ); - console.log(`Discovery ID: ${response.discoveryId}`); - console.log(`Test Case ID: ${response.testCaseId}`); + logger.info(`Discovery ID: ${response.discoveryId}`); + logger.info(`Test Case ID: ${response.testCaseId}`); }; diff --git c/src/tools/environments.ts i/src/tools/environments.ts index 6712707..c38925f 100644 --- c/src/tools/environments.ts +++ i/src/tools/environments.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type GetEnvironmentsOptions = @@ -26,12 +27,12 @@ export const listEnvironments = async ( return; } - console.log("Environments:"); + logger.info("Environments:"); environments.forEach((environment) => { - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }); }; @@ -99,11 +100,11 @@ export const createEnvironment = async ( return; } - console.log("Environment created successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment created successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const updateEnvironment = async ( @@ -152,11 +153,11 @@ export const updateEnvironment = async ( return; } - console.log("Environment updated successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment updated successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const getEnvironment = async ( @@ -176,11 +177,11 @@ export const getEnvironment = async ( return; } - console.log("Environment:"); - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info("Environment:"); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }; export const deleteEnvironment = async ( @@ -205,5 +206,5 @@ export const deleteEnvironment = async ( return; } - console.log("Environment deleted successfully!"); + logger.info("Environment deleted successfully!"); }; diff --git c/src/tools/init.ts i/src/tools/init.ts index a815fd2..57ad8b2 100644 --- c/src/tools/init.ts +++ i/src/tools/init.ts @@ -1,5 +1,6 @@ import { Config, loadConfig, saveConfig } from "../config"; import { promptUser } from "../helpers"; +import { logger } from "../logger"; import { getTestTargets, listTestTargets } from "./test-targets"; const selectTestTarget = async (): Promise => { @@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise => { await listTestTargets({}); if (testTargets.length === 1) { - console.log( + logger.info( `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`, ); return testTargets[0].id; @@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise => { testTargetIndexAsInt < 1 || testTargetIndexAsInt > testTargets.length ) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } const testTargetId = testTargets[testTargetIndexAsInt - 1].id; if (!testTargetId) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } @@ -43,7 +44,7 @@ export const switchTestTarget = async () => { testTargetId, }; await saveConfig(newConfig); - console.log(`✨ Switched to test target: ${testTargetId}`); + logger.info(`✨ Switched to test target: ${testTargetId}`); }; export const init = async (options: { @@ -52,12 +53,12 @@ export const init = async (options: { force?: boolean; }) => { try { - console.log("🚀 Initializing configuration...\n"); + logger.info("🚀 Initializing configuration...\n"); const existingConfig = await loadConfig(options.force); if (existingConfig.apiKey && !options.force) { - console.log("⚠️ Configuration already exists."); + logger.info("⚠️ Configuration already exists."); const overwrite = await promptUser( "Do you want to overwrite it? (y/N): ", ); @@ -66,7 +67,7 @@ export const init = async (options: { overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes" ) { - console.log("Configuration unchanged."); + logger.info("Configuration unchanged."); return; } } @@ -76,7 +77,7 @@ export const init = async (options: { "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ", ); if (!options.apiKey) { - console.log("❌ API key is required."); + logger.info("❌ API key is required."); process.exit(1); } } @@ -99,9 +100,9 @@ export const init = async (options: { await saveConfig(newConfig); - console.log("\n✨ Initialization complete!"); + logger.info("\n✨ Initialization complete!"); } catch (error) { - console.error("❌ Error during initialization:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error during initialization"); process.exit(1); } }; diff --git c/src/tools/notifications.ts i/src/tools/notifications.ts index a1b9339..93f9319 100644 --- c/src/tools/notifications.ts +++ i/src/tools/notifications.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -27,28 +28,28 @@ export const listNotifications = async ( return; } - console.log("Notifications:"); + logger.info("Notifications:"); for (const notification of response) { - console.log(`\nID: ${notification.id}`); - console.log(`Type: ${notification.type}`); - console.log(`Created At: ${notification.createdAt}`); + logger.info(`\nID: ${notification.id}`); + logger.info(`Type: ${notification.type}`); + logger.info(`Created At: ${notification.createdAt}`); if (notification.payload?.testReportId) { - console.log(`Test Report ID: ${notification.payload.testReportId}`); - console.log( + logger.info(`Test Report ID: ${notification.payload.testReportId}`); + logger.info( `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`, ); } if (notification.payload?.testCaseId) { - console.log(`Test Case ID: ${notification.payload.testCaseId}`); - console.log( + logger.info(`Test Case ID: ${notification.payload.testCaseId}`); + logger.info( `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`, ); } if (notification.payload?.failed !== undefined) { - console.log(`Failed: ${notification.payload.failed}`); + logger.info(`Failed: ${notification.payload.failed}`); } if (notification.ack) { - console.log(`Acknowledged: ${notification.ack}`); + logger.info(`Acknowledged: ${notification.ack}`); } } }; diff --git c/src/tools/private-locations.ts i/src/tools/private-locations.ts index b91a414..7c0ba62 100644 --- c/src/tools/private-locations.ts +++ i/src/tools/private-locations.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type SuccessResponse = components["schemas"]["SuccessResponse"]; @@ -35,7 +36,7 @@ export const registerLocation = async ( return; } - console.log("Registration result:", response.success ? "Success" : "Failed"); + logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`); }; export const unregisterLocation = async ( @@ -60,9 +61,8 @@ export const unregisterLocation = async ( return; } - console.log( - "Unregistration result:", - response.success ? "Success" : "Failed", + logger.info( + `Unregistration result: ${response.success ? "Success" : "Failed"}`, ); }; @@ -79,10 +79,10 @@ export const listPrivateLocations = async ( return; } - console.log("Private Locations:"); + logger.info("Private Locations:"); response.forEach((location) => { - console.log(`- Name: ${location.name}`); - console.log(` Status: ${location.status}`); - console.log(` Address: ${location.address}`); + logger.info(`- Name: ${location.name}`); + logger.info(` Status: ${location.status}`); + logger.info(` Address: ${location.address}`); }); }; diff --git c/src/tools/sync/git.ts i/src/tools/sync/git.ts index 765b3bd..3a7fc1f 100644 --- c/src/tools/sync/git.ts +++ i/src/tools/sync/git.ts @@ -2,6 +2,7 @@ import path from "path"; import { simpleGit } from "simple-git"; +import { logger } from "../../logger"; import { ExecutionContext } from "./types"; const FALLBACK_DEFAULT_BRANCH = "refs/heads/main"; @@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - console.error(error); + logger.error({ err: error }, "Failed to parse git remote"); return {}; } }; @@ -59,9 +60,9 @@ export const getDefaultBranch = async ( return symbolicRefBranch; } } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify symbolic ref, falling back to origin parsing", - e, ); } } @@ -69,7 +70,7 @@ export const getDefaultBranch = async ( try { const origin = await simpleGit().remote(["show", "origin"]); if (!origin) { - console.warn("could not identify default branch, falling back to 'main'"); + logger.warn("could not identify default branch, falling back to 'main'"); return FALLBACK_DEFAULT_BRANCH; } @@ -79,9 +80,9 @@ export const getDefaultBranch = async ( ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify default branch, falling back to 'main'", - e, ); } @@ -108,9 +109,9 @@ export const getGitContext = async (): Promise => { }; return ctx; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify git context, falling back to undefined", - e, ); return undefined; } diff --git c/src/tools/sync/yaml.ts i/src/tools/sync/yaml.ts index 7451476..edf2d28 100644 --- c/src/tools/sync/yaml.ts +++ i/src/tools/sync/yaml.ts @@ -4,6 +4,7 @@ import path from "path"; import yaml from "yaml"; +import { logger } from "../../logger"; import { PushTestTargetBody } from "../../schemas/octomindExternalAPI"; import { SyncTestCase, TestTargetSyncData } from "./types"; @@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => { if (result.success) { testCases.push(result.data); } else { - console.warn( + logger.warn( `Failed to read test case from ${file}: ${result.error.message}`, ); } } catch { - console.error(`Failed to read test case from ${file}`); + logger.error(`Failed to read test case from ${file}`); } } diff --git c/src/tools/test-cases.ts i/src/tools/test-cases.ts index 742fb06..54a3831 100644 --- c/src/tools/test-cases.ts +++ i/src/tools/test-cases.ts @@ -3,6 +3,7 @@ import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; @@ -37,7 +38,7 @@ export const deleteTestCase = async ( if (options.json) { logJson(data); } - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); return; } @@ -46,7 +47,7 @@ export const deleteTestCase = async ( const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log( + logger.info( `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, ); return; @@ -54,7 +55,7 @@ export const deleteTestCase = async ( const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); }; export const listTestCase = async ( @@ -81,35 +82,35 @@ export const listTestCase = async ( return; } - console.log("Test Case Details:"); - console.log(`ID: ${response.id}`); - console.log(`Description: ${response.description}`); - console.log(`Status: ${response.status}`); - console.log(`Run Status: ${response.runStatus}`); - console.log(`Created At: ${response.createdAt}`); - console.log(`Updated At: ${response.updatedAt}`); + logger.info("Test Case Details:"); + logger.info(`ID: ${response.id}`); + logger.info(`Description: ${response.description}`); + logger.info(`Status: ${response.status}`); + logger.info(`Run Status: ${response.runStatus}`); + logger.info(`Created At: ${response.createdAt}`); + logger.info(`Updated At: ${response.updatedAt}`); if (response.elements && response.elements.length > 0) { - console.log("\nElements:"); + logger.info("\nElements:"); response.elements.forEach((element, index) => { - console.log(`\nElement ${index + 1}:`); + logger.info(`\nElement ${index + 1}:`); if (element.interaction) { - console.log(` Action: ${element.interaction.action}`); + logger.info(` Action: ${element.interaction.action}`); if (element.interaction.calledWith) { - console.log(` Called With: ${element.interaction.calledWith}`); + logger.info(` Called With: ${element.interaction.calledWith}`); } } if (element.assertion) { - console.log(` Expectation: ${element.assertion.expectation}`); + logger.info(` Expectation: ${element.assertion.expectation}`); if (element.assertion.calledWith) { - console.log(` Called With: ${element.assertion.calledWith}`); + logger.info(` Called With: ${element.assertion.calledWith}`); } } - console.log(" Selectors:"); + logger.info(" Selectors:"); element.selectors?.forEach((selector) => { - console.log(` - ${selector.selectorType}: ${selector.selector}`); + logger.info(` - ${selector.selectorType}: ${selector.selector}`); if (selector.options?.name) { - console.log(` Name: ${selector.options.name}`); + logger.info(` Name: ${selector.options.name}`); } }); }); @@ -193,7 +194,7 @@ export const getTestCaseCode = async ( if (!data) { throw new Error("no test code found"); } - console.log(data.testCode); + logger.info(data.testCode); }; export const listTestCases = async ( @@ -209,16 +210,16 @@ export const listTestCases = async ( return; } - console.log("Test Cases:"); + logger.info("Test Cases:"); for (let idx = 0; idx < testCases.length; idx++) { const testCase = testCases[idx]; const idxString = `${idx + 1}. `.padEnd( testCases.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}Description: ${testCase.description}`); - console.log(`${paddingString}ID: ${testCase.id}`); - console.log( + logger.info(`${idxString}Description: ${testCase.description}`); + logger.info(`${paddingString}ID: ${testCase.id}`); + logger.info( `${paddingString}${await getUrl({ testCaseId: testCase.id, entityType: "test-case", diff --git c/src/tools/test-reports.ts i/src/tools/test-reports.ts index 1e5e509..2571e8e 100644 --- c/src/tools/test-reports.ts +++ i/src/tools/test-reports.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -39,19 +40,19 @@ export const executeTests = async ( } if (data) { - console.log("Test execution started successfully!"); - console.log("Test Report URL:", data.testReportUrl); - console.log("Report Status:", data.testReport?.status); + logger.info("Test execution started successfully!"); + logger.info(`Test Report URL: ${data.testReportUrl}`); + logger.info(`Report Status: ${data.testReport?.status}`); const numberOfTestResults = data.testReport?.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } }); } @@ -100,15 +101,15 @@ export const listTestReport = async ( } const response = data as TestReport; - console.log("Test Report Details:"); - console.log("Status:", response.status); - console.log("Execution URL:", response.executionUrl); + logger.info("Test Report Details:"); + logger.info(`Status: ${response.status}`); + logger.info(`Execution URL: ${response.executionUrl}`); const numberOfTestResults = response.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); - console.log( + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info( ` ${await getUrl({ testReportId: options.testReportId, testResultId: result.id ?? "", @@ -116,10 +117,10 @@ export const listTestReport = async ( })}`, ); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } } } diff --git c/src/tools/test-targets.ts i/src/tools/test-targets.ts index cf39a33..2cb64e4 100644 --- c/src/tools/test-targets.ts +++ i/src/tools/test-targets.ts @@ -4,6 +4,7 @@ import ora from "ora"; import { OCTOMIND_FOLDER_NAME } from "../constants"; import { confirmAction, findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; @@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { return; } - console.log("Test Targets:"); + logger.info("Test Targets:"); for (let idx = 0; idx < testTargets.length; idx++) { const testTarget = testTargets[idx]; @@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise => { testTargets.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}ID: ${testTarget.id}`); - console.log(`${paddingString}App: ${testTarget.app}`); - console.log( + logger.info(`${idxString}ID: ${testTarget.id}`); + logger.info(`${paddingString}App: ${testTarget.app}`); + logger.info( `${paddingString}${await getUrl({ testTargetId: testTarget.id, entityType: "test-target", @@ -98,7 +99,7 @@ export const pullTestTarget = async ( path.join(process.cwd(), OCTOMIND_FOLDER_NAME); await writeYaml(data, destination); - console.log("Test Target pulled successfully"); + logger.info("Test Target pulled successfully"); }; export const pushTestTarget = async ( @@ -124,7 +125,7 @@ export const pushTestTarget = async ( `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`, ); if (!confirmed) { - console.log("Push cancelled."); + logger.info("Push cancelled."); return; } } diff --git c/src/tools/update.ts i/src/tools/update.ts index 3aff6dd..f63daa9 100644 --- c/src/tools/update.ts +++ i/src/tools/update.ts @@ -5,6 +5,8 @@ import path from "path"; import which from "which"; +import { logger } from "../logger"; + export const update = async (): Promise => { const pathToRoot = path.posix.normalize( path.join(homedir(), ".local", "packages"), @@ -12,14 +14,14 @@ export const update = async (): Promise => { const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { - console.error( + logger.error( "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind", ); process.exit(1); } if (!(await which("npm"))) { - console.error( + logger.error( `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); @@ -30,7 +32,7 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`version before update: ${oldVersion}`); + logger.info(`version before update: ${oldVersion}`); child_process.execSync("npm install @octomind/octomind@latest", { cwd: pathToRoot, @@ -42,5 +44,5 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`✔ update complete: ${newVersion}`); + logger.info(`✔ update complete: ${newVersion}`); }; diff --git c/src/tools/yamlMutations/create.ts i/src/tools/yamlMutations/create.ts index d819b50..946d268 100644 --- c/src/tools/yamlMutations/create.ts +++ i/src/tools/yamlMutations/create.ts @@ -9,6 +9,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -75,7 +76,7 @@ const openBrowser = async ({ await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); }; @@ -88,7 +89,7 @@ const writeOutput = async ({ createResult: SyncTestCase | "cancelled"; }): Promise => { if (createResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -101,8 +102,8 @@ const writeOutput = async ({ yaml.stringify(createResult), ); - console.log(`Created test case successfully`); - console.log(diff); + logger.info(`Created test case successfully`); + logger.info(diff); }; export const create = async (options: CreateOptions): Promise => { diff --git c/src/tools/yamlMutations/edit.ts i/src/tools/yamlMutations/edit.ts index b8fb58e..fe8a00c 100644 --- c/src/tools/yamlMutations/edit.ts +++ i/src/tools/yamlMutations/edit.ts @@ -7,6 +7,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise => { await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); @@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise => { ); if (editResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise => { yaml.stringify(editResult), ); - console.log(`Edited test case successfully`); - console.log(diff); + logger.info(`Edited test case successfully`); + logger.info(diff); }; diff --git c/tests/config.spec.ts i/tests/config.spec.ts index c1c9b9c..0856939 100644 --- c/tests/config.spec.ts +++ i/tests/config.spec.ts @@ -5,13 +5,14 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; +import { mockLogger } from "./mocks"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - console.error = vi.fn(); + mockLogger.error.mockClear(); resetConfig(); }); @@ -68,7 +69,7 @@ describe("Config", () => { const result = await loadConfig(); expect(result).toEqual({}); - expect(console.error).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); }); it("should error and exit when config file does not exist and force is true", async () => { @@ -89,7 +90,7 @@ describe("Config", () => { await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow( "Process exit with code: 1", ); - expect(console.error).toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); diff --git c/tests/mocks.ts i/tests/mocks.ts index 42342b8..02e672c 100644 --- c/tests/mocks.ts +++ i/tests/mocks.ts @@ -1,3 +1,4 @@ +import { vi } from "vitest"; import { EnvironmentResponse, TestCaseResponse, @@ -5,6 +6,19 @@ import { } from "../src/tools"; import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types"; +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, +})); + export const createMockSyncTestCase = ( overrides?: Partial, ): SyncTestCase => ({ diff --git c/tests/setup.ts i/tests/setup.ts index cd22023..aa85cd5 100644 --- c/tests/setup.ts +++ i/tests/setup.ts @@ -1,5 +1,7 @@ import { beforeEach, vi } from "vitest"; +import { mockLogger } from "./mocks"; + vi.mock("ora", () => ({ default: vi.fn(() => ({ start: vi.fn().mockReturnThis(), @@ -9,7 +11,10 @@ vi.mock("ora", () => ({ })); beforeEach(() => { - console.log = vi.fn(); - console.warn = vi.fn(); - console.error = vi.fn(); + mockLogger.info.mockClear(); + mockLogger.warn.mockClear(); + mockLogger.error.mockClear(); + mockLogger.debug.mockClear(); + mockLogger.log.mockClear(); + mockLogger.success.mockClear(); }); diff --git c/tests/tools/init.spec.ts i/tests/tools/init.spec.ts index a29a295..fc1b5b3 100644 --- c/tests/tools/init.spec.ts +++ i/tests/tools/init.spec.ts @@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; +import { mockLogger } from "../mocks"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -15,7 +16,7 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); @@ -36,10 +37,10 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Only one test target found, using it: testTargetApp (testTargetId)", ); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 3, "\n✨ Initialization complete!", ); @@ -56,7 +57,7 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId1", }); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 2, "\n✨ Initialization complete!", ); diff --git c/tests/tools/sync/git.spec.ts i/tests/tools/sync/git.spec.ts index 4dc35df..4b2a10c 100644 --- c/tests/tools/sync/git.spec.ts +++ i/tests/tools/sync/git.spec.ts @@ -7,6 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; +import { mockLogger } from "../../mocks"; vi.mock("simple-git"); @@ -17,30 +18,29 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - console.error = vi.fn(); + mockLogger.error.mockClear(); + mockLogger.warn.mockClear(); }); describe("getDefaultBranch", () => { it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockResolvedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockRejectedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); }); diff --git c/tests/tools/test-cases.spec.ts i/tests/tools/test-cases.spec.ts index 0731b48..696d4bf 100644 --- c/tests/tools/test-cases.spec.ts +++ i/tests/tools/test-cases.spec.ts @@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; +import { mockLogger } from "../mocks"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -18,7 +19,7 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); describe("deleteTestCase via API", () => { @@ -36,7 +37,7 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); }); @@ -84,7 +85,7 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); await expect(fsPromises.access(filePath)).rejects.toThrow(); @@ -98,7 +99,7 @@ describe("test-cases", () => { testCaseId: "non-existent-id", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( `No test case with id non-existent-id found in folder ${tmpDir}`, ); expect(clientDELETE).not.toHaveBeenCalled(); diff --git c/tests/tools/test-targets.spec.ts i/tests/tools/test-targets.spec.ts index 92e0bcb..37dadc4 100644 --- c/tests/tools/test-targets.spec.ts +++ i/tests/tools/test-targets.spec.ts @@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; +import { mockLogger } from "../mocks"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -35,7 +36,7 @@ describe("push", () => { error: undefined, response: mock(), }); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); it("pushes to main if on default branch", async () => { @@ -114,7 +115,7 @@ describe("push", () => { }); expect(client.POST).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Push cancelled."); + expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled."); }); }); }); diff --git c/tests/tools/yamlMutations/create.spec.ts i/tests/tools/yamlMutations/create.spec.ts index eadbc73..b1e46dd 100644 --- c/tests/tools/yamlMutations/create.spec.ts +++ i/tests/tools/yamlMutations/create.spec.ts @@ -18,6 +18,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -32,7 +33,7 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); @@ -110,7 +111,7 @@ describe("create", () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -118,7 +119,7 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully"); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git c/tests/tools/yamlMutations/edit.spec.ts i/tests/tools/yamlMutations/edit.spec.ts index dcb928c..4799668 100644 --- c/tests/tools/yamlMutations/edit.spec.ts +++ i/tests/tools/yamlMutations/edit.spec.ts @@ -15,6 +15,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -29,7 +30,7 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( @@ -96,7 +97,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -108,7 +109,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully"); }); it.each([ diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml index bd5a2a4..619d022 100644 --- c/pnpm-lock.yaml +++ i/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: otplib: specifier: 13.1.1 version: 13.1.1 + pino: + specifier: 10.2.0 + version: 10.2.0 + pino-pretty: + specifier: 13.1.3 + version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -553,6 +559,9 @@ packages: '@otplib/uri@13.1.1': resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.57.0': resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} @@ -874,6 +883,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -937,6 +950,9 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -955,6 +971,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -997,6 +1016,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1044,6 +1066,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1051,6 +1076,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1140,6 +1168,9 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1240,6 +1271,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1344,6 +1379,13 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1429,6 +1471,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.2.0: + resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==} + hasBin: true + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -1458,6 +1514,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1465,6 +1527,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1472,6 +1537,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + remeda@2.33.4: resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} @@ -1517,9 +1586,16 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1549,10 +1625,17 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1598,6 +1681,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1609,6 +1696,10 @@ packages: tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -1820,6 +1911,9 @@ packages: engines: {node: '>=8'} hasBin: true + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2209,6 +2303,8 @@ snapshots: dependencies: '@otplib/core': 13.1.1 + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.57.0': dependencies: playwright: 1.57.0 @@ -2484,6 +2580,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + balanced-match@1.0.2: {} bluebird@3.7.2: {} @@ -2538,6 +2636,8 @@ snapshots: colorette@1.4.0: {} + colorette@2.0.20: {} + commander@14.0.2: {} commander@7.2.0: {} @@ -2552,6 +2652,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + dateformat@4.6.3: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2581,6 +2683,10 @@ snapshots: dependencies: jake: 10.9.4 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2681,6 +2787,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2691,6 +2799,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -2780,6 +2890,8 @@ snapshots: has-flag@3.0.0: {} + help-me@5.0.0: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2861,6 +2973,8 @@ snapshots: jiti@2.6.1: {} + joycon@3.1.1: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -2953,6 +3067,12 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3078,6 +3198,42 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.2.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 4.0.0 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -3102,10 +3258,19 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -3118,6 +3283,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + remeda@2.33.4: {} require-from-string@2.0.2: {} @@ -3181,8 +3348,12 @@ snapshots: safe-buffer@5.1.2: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + secure-json-parse@4.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3207,8 +3378,14 @@ snapshots: slash@5.1.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -3249,6 +3426,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-json-comments@5.0.3: {} + supports-color@10.2.2: {} supports-color@5.5.0: @@ -3266,6 +3445,10 @@ snapshots: transitivePeerDependencies: - supports-color + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tinybench@2.9.0: {} @@ -3429,6 +3612,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrappy@1.0.2: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git c/src/completion.ts i/src/completion.ts index f5a0c77..3e0b0ac 100644 --- c/src/completion.ts +++ i/src/completion.ts @@ -3,6 +3,7 @@ import { parse } from "shell-quote"; import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; +import { logger } from "./logger"; import { loadConfig } from "./config"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; @@ -193,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => console.error("INSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "INSTALL ERROR")); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => console.error("UNINSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "UNINSTALL ERROR")); }; diff --git c/src/config.ts i/src/config.ts index 9268bd7..bf42479 100644 --- c/src/config.ts +++ i/src/config.ts @@ -3,6 +3,8 @@ import fs from "fs/promises"; import { homedir } from "os"; import { join } from "path"; +import { logger } from "./logger"; + const CONFIG_DIR = ".config"; export async function getConfigPath(ensureDir?: boolean): Promise { @@ -45,9 +47,9 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - console.error( - "❌ Error parsing configuration:", - (error as Error).message, + logger.error( + { err: error as Error }, + "❌ Error parsing configuration", ); process.exit(1); } @@ -59,10 +61,10 @@ export async function saveConfig(newConfig: Config): Promise { try { const configPath = await getConfigPath(true); await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8"); - console.log(`✅ Configuration saved to ${configPath}`); + logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - console.error("❌ Error saving configuration:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error saving configuration"); process.exit(1); } } diff --git c/src/debugtopus/index.ts i/src/debugtopus/index.ts index 05ad042..32e7e63 100644 --- c/src/debugtopus/index.ts +++ i/src/debugtopus/index.ts @@ -16,6 +16,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../helpers"; +import { logger } from "../logger"; import { getEnvironments, getPlaywrightCode, @@ -91,7 +92,7 @@ const prepareDirectories = async ( const appDir = dirname(nodeModule.filename); packageRootDir = getPackageRootLevel(appDir); } else { - console.warn("package was not installed as valid nodeJS module"); + logger.warn("package was not installed as valid nodeJS module"); packageRootDir = process.cwd(); } } @@ -169,17 +170,17 @@ const runTests = async ({ command += " --ui"; } - console.log(`executing command : '${command}'`); + logger.info(`executing command : '${command}'`); const { stderr } = await promisify(exec)(command, { cwd: packageRootDir, }); if (stderr) { - console.error(stderr); + logger.error(stderr); process.exit(1); } else { - console.log(`success, you can find your artifacts at ${outputDir}`); + logger.info(`success, you can find your artifacts at ${outputDir}`); } }; diff --git c/src/debugtopus/installation.ts i/src/debugtopus/installation.ts index 6582abd..080346a 100644 --- c/src/debugtopus/installation.ts +++ i/src/debugtopus/installation.ts @@ -5,6 +5,8 @@ import { promisify } from "util"; import { chromium } from "@playwright/test"; +import { logger } from "../logger"; + export const ensureChromiumIsInstalled = async ( packageRootDir: string, ): Promise => { @@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async ( reject(error); return; } - console.log( + logger.info( "Couldn't find any chromium binary, executing 'npx playwright install chromium'", ); @@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async ( ); playwrightInstallExecution.child?.stdout?.on("data", (data) => - console.log(data), + logger.info(data), ); await playwrightInstallExecution; diff --git c/src/logger.ts i/src/logger.ts new file mode 100644 index 0000000..946ed95 --- /dev/null +++ i/src/logger.ts @@ -0,0 +1,15 @@ +import pino from "pino"; + +const isProduction = process.env.NODE_ENV === "production"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || "info", + transport: isProduction + ? undefined + : { + target: "pino-pretty", + options: { + colorize: true, + }, + }, +}); diff --git c/src/plw/index.ts i/src/plw/index.ts index b26e846..ea22afc 100644 --- c/src/plw/index.ts +++ i/src/plw/index.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import { createInterface } from "readline"; import { loadConfig } from "../config"; +import { logger } from "../logger"; interface StreamResult { lines: string[]; @@ -39,7 +40,7 @@ const spawnAndStreamLines = async ( rl.on("line", (line: string) => { lines.push(line); - console.log(`-- ${line}`); + logger.info(`-- ${line}`); lineCount++; if (lineCount >= maxLines) { @@ -58,7 +59,7 @@ const spawnAndStreamLines = async ( child.on("exit", (code, signal) => { if (lineCount < maxLines) { - console.log(`Process exited with code ${code}, signal ${signal}`); + logger.info(`Process exited with code ${code}, signal ${signal}`); } resolve({ lines, detached: false, code, signal }); }); @@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: { }); if (!(await checkDockerDaemon())) { - console.error( + logger.error( "Docker daemon is not running. Please start Docker and try again.", ); return; } - console.log( + logger.info( `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`, ); const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); - console.log(`Captured ${result.lines.length} lines`); - console.log(`Process detached: ${result.detached}`); + logger.info(`Captured ${result.lines.length} lines`); + logger.info(`Process detached: ${result.detached}`); }; export const stopPLW = async (): Promise => { @@ -131,9 +132,9 @@ export const stopPLW = async (): Promise => { const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); if (result.code === 0) { - console.log("Private Location Worker stopped successfully."); + logger.info("Private Location Worker stopped successfully."); } else { - console.error( + logger.error( `Failed to stop Private Location Worker. Exit code: ${result.code}`, ); } diff --git c/src/tools/client.ts i/src/tools/client.ts index 979aa2a..6e5146c 100644 --- c/src/tools/client.ts +++ i/src/tools/client.ts @@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch"; import type { components, paths } from "../api"; // generated by openapi-typescript import { loadConfig } from "../config"; +import { logger } from "../logger"; import { version } from "../version"; export const BASE_URL = @@ -65,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - console.error(error); + logger.error(error); process.exit(1); }, }; @@ -77,9 +78,9 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - console.error(error); + logger.error(error); if (typeof error === "string" && error.startsWith("403")) { - console.error( + logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", ); } @@ -91,5 +92,5 @@ export type ListOptions = { json?: boolean; }; export const logJson = (result: unknown): void => { - console.log(JSON.stringify(result, null, 2)); + logger.info(JSON.stringify(result, null, 2)); }; diff --git c/src/tools/discoveries.ts i/src/tools/discoveries.ts index a8f2449..7aa3d14 100644 --- c/src/tools/discoveries.ts +++ i/src/tools/discoveries.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -47,9 +48,9 @@ export const batchGeneration = async ( throw new Error("Batch generation ID is missing"); } - console.log("Batch generation created successfully!"); - console.log(`Batch generation ID: ${response.batchGenerationId}`); - console.log( + logger.info("Batch generation created successfully!"); + logger.info(`Batch generation ID: ${response.batchGenerationId}`); + logger.info( `Batch generation URL: ${await getUrl({ batchGenerationId: response.batchGenerationId, entityType: "batch-generation", @@ -95,13 +96,13 @@ export const createDiscovery = async ( return; } - console.log("Discovery created successfully!"); - console.log( + logger.info("Discovery created successfully!"); + logger.info( `${await getUrl({ testCaseId: response.testCaseId ?? "", entityType: "discovery", })}`, ); - console.log(`Discovery ID: ${response.discoveryId}`); - console.log(`Test Case ID: ${response.testCaseId}`); + logger.info(`Discovery ID: ${response.discoveryId}`); + logger.info(`Test Case ID: ${response.testCaseId}`); }; diff --git c/src/tools/environments.ts i/src/tools/environments.ts index 6712707..c38925f 100644 --- c/src/tools/environments.ts +++ i/src/tools/environments.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type GetEnvironmentsOptions = @@ -26,12 +27,12 @@ export const listEnvironments = async ( return; } - console.log("Environments:"); + logger.info("Environments:"); environments.forEach((environment) => { - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }); }; @@ -99,11 +100,11 @@ export const createEnvironment = async ( return; } - console.log("Environment created successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment created successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const updateEnvironment = async ( @@ -152,11 +153,11 @@ export const updateEnvironment = async ( return; } - console.log("Environment updated successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment updated successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const getEnvironment = async ( @@ -176,11 +177,11 @@ export const getEnvironment = async ( return; } - console.log("Environment:"); - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info("Environment:"); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }; export const deleteEnvironment = async ( @@ -205,5 +206,5 @@ export const deleteEnvironment = async ( return; } - console.log("Environment deleted successfully!"); + logger.info("Environment deleted successfully!"); }; diff --git c/src/tools/init.ts i/src/tools/init.ts index a815fd2..57ad8b2 100644 --- c/src/tools/init.ts +++ i/src/tools/init.ts @@ -1,5 +1,6 @@ import { Config, loadConfig, saveConfig } from "../config"; import { promptUser } from "../helpers"; +import { logger } from "../logger"; import { getTestTargets, listTestTargets } from "./test-targets"; const selectTestTarget = async (): Promise => { @@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise => { await listTestTargets({}); if (testTargets.length === 1) { - console.log( + logger.info( `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`, ); return testTargets[0].id; @@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise => { testTargetIndexAsInt < 1 || testTargetIndexAsInt > testTargets.length ) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } const testTargetId = testTargets[testTargetIndexAsInt - 1].id; if (!testTargetId) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } @@ -43,7 +44,7 @@ export const switchTestTarget = async () => { testTargetId, }; await saveConfig(newConfig); - console.log(`✨ Switched to test target: ${testTargetId}`); + logger.info(`✨ Switched to test target: ${testTargetId}`); }; export const init = async (options: { @@ -52,12 +53,12 @@ export const init = async (options: { force?: boolean; }) => { try { - console.log("🚀 Initializing configuration...\n"); + logger.info("🚀 Initializing configuration...\n"); const existingConfig = await loadConfig(options.force); if (existingConfig.apiKey && !options.force) { - console.log("⚠️ Configuration already exists."); + logger.info("⚠️ Configuration already exists."); const overwrite = await promptUser( "Do you want to overwrite it? (y/N): ", ); @@ -66,7 +67,7 @@ export const init = async (options: { overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes" ) { - console.log("Configuration unchanged."); + logger.info("Configuration unchanged."); return; } } @@ -76,7 +77,7 @@ export const init = async (options: { "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ", ); if (!options.apiKey) { - console.log("❌ API key is required."); + logger.info("❌ API key is required."); process.exit(1); } } @@ -99,9 +100,9 @@ export const init = async (options: { await saveConfig(newConfig); - console.log("\n✨ Initialization complete!"); + logger.info("\n✨ Initialization complete!"); } catch (error) { - console.error("❌ Error during initialization:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error during initialization"); process.exit(1); } }; diff --git c/src/tools/notifications.ts i/src/tools/notifications.ts index a1b9339..93f9319 100644 --- c/src/tools/notifications.ts +++ i/src/tools/notifications.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -27,28 +28,28 @@ export const listNotifications = async ( return; } - console.log("Notifications:"); + logger.info("Notifications:"); for (const notification of response) { - console.log(`\nID: ${notification.id}`); - console.log(`Type: ${notification.type}`); - console.log(`Created At: ${notification.createdAt}`); + logger.info(`\nID: ${notification.id}`); + logger.info(`Type: ${notification.type}`); + logger.info(`Created At: ${notification.createdAt}`); if (notification.payload?.testReportId) { - console.log(`Test Report ID: ${notification.payload.testReportId}`); - console.log( + logger.info(`Test Report ID: ${notification.payload.testReportId}`); + logger.info( `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`, ); } if (notification.payload?.testCaseId) { - console.log(`Test Case ID: ${notification.payload.testCaseId}`); - console.log( + logger.info(`Test Case ID: ${notification.payload.testCaseId}`); + logger.info( `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`, ); } if (notification.payload?.failed !== undefined) { - console.log(`Failed: ${notification.payload.failed}`); + logger.info(`Failed: ${notification.payload.failed}`); } if (notification.ack) { - console.log(`Acknowledged: ${notification.ack}`); + logger.info(`Acknowledged: ${notification.ack}`); } } }; diff --git c/src/tools/private-locations.ts i/src/tools/private-locations.ts index b91a414..7c0ba62 100644 --- c/src/tools/private-locations.ts +++ i/src/tools/private-locations.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type SuccessResponse = components["schemas"]["SuccessResponse"]; @@ -35,7 +36,7 @@ export const registerLocation = async ( return; } - console.log("Registration result:", response.success ? "Success" : "Failed"); + logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`); }; export const unregisterLocation = async ( @@ -60,9 +61,8 @@ export const unregisterLocation = async ( return; } - console.log( - "Unregistration result:", - response.success ? "Success" : "Failed", + logger.info( + `Unregistration result: ${response.success ? "Success" : "Failed"}`, ); }; @@ -79,10 +79,10 @@ export const listPrivateLocations = async ( return; } - console.log("Private Locations:"); + logger.info("Private Locations:"); response.forEach((location) => { - console.log(`- Name: ${location.name}`); - console.log(` Status: ${location.status}`); - console.log(` Address: ${location.address}`); + logger.info(`- Name: ${location.name}`); + logger.info(` Status: ${location.status}`); + logger.info(` Address: ${location.address}`); }); }; diff --git c/src/tools/sync/git.ts i/src/tools/sync/git.ts index 765b3bd..3a7fc1f 100644 --- c/src/tools/sync/git.ts +++ i/src/tools/sync/git.ts @@ -2,6 +2,7 @@ import path from "path"; import { simpleGit } from "simple-git"; +import { logger } from "../../logger"; import { ExecutionContext } from "./types"; const FALLBACK_DEFAULT_BRANCH = "refs/heads/main"; @@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - console.error(error); + logger.error({ err: error }, "Failed to parse git remote"); return {}; } }; @@ -59,9 +60,9 @@ export const getDefaultBranch = async ( return symbolicRefBranch; } } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify symbolic ref, falling back to origin parsing", - e, ); } } @@ -69,7 +70,7 @@ export const getDefaultBranch = async ( try { const origin = await simpleGit().remote(["show", "origin"]); if (!origin) { - console.warn("could not identify default branch, falling back to 'main'"); + logger.warn("could not identify default branch, falling back to 'main'"); return FALLBACK_DEFAULT_BRANCH; } @@ -79,9 +80,9 @@ export const getDefaultBranch = async ( ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify default branch, falling back to 'main'", - e, ); } @@ -108,9 +109,9 @@ export const getGitContext = async (): Promise => { }; return ctx; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify git context, falling back to undefined", - e, ); return undefined; } diff --git c/src/tools/sync/yaml.ts i/src/tools/sync/yaml.ts index 7451476..edf2d28 100644 --- c/src/tools/sync/yaml.ts +++ i/src/tools/sync/yaml.ts @@ -4,6 +4,7 @@ import path from "path"; import yaml from "yaml"; +import { logger } from "../../logger"; import { PushTestTargetBody } from "../../schemas/octomindExternalAPI"; import { SyncTestCase, TestTargetSyncData } from "./types"; @@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => { if (result.success) { testCases.push(result.data); } else { - console.warn( + logger.warn( `Failed to read test case from ${file}: ${result.error.message}`, ); } } catch { - console.error(`Failed to read test case from ${file}`); + logger.error(`Failed to read test case from ${file}`); } } diff --git c/src/tools/test-cases.ts i/src/tools/test-cases.ts index 742fb06..54a3831 100644 --- c/src/tools/test-cases.ts +++ i/src/tools/test-cases.ts @@ -3,6 +3,7 @@ import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; @@ -37,7 +38,7 @@ export const deleteTestCase = async ( if (options.json) { logJson(data); } - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); return; } @@ -46,7 +47,7 @@ export const deleteTestCase = async ( const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log( + logger.info( `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, ); return; @@ -54,7 +55,7 @@ export const deleteTestCase = async ( const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); }; export const listTestCase = async ( @@ -81,35 +82,35 @@ export const listTestCase = async ( return; } - console.log("Test Case Details:"); - console.log(`ID: ${response.id}`); - console.log(`Description: ${response.description}`); - console.log(`Status: ${response.status}`); - console.log(`Run Status: ${response.runStatus}`); - console.log(`Created At: ${response.createdAt}`); - console.log(`Updated At: ${response.updatedAt}`); + logger.info("Test Case Details:"); + logger.info(`ID: ${response.id}`); + logger.info(`Description: ${response.description}`); + logger.info(`Status: ${response.status}`); + logger.info(`Run Status: ${response.runStatus}`); + logger.info(`Created At: ${response.createdAt}`); + logger.info(`Updated At: ${response.updatedAt}`); if (response.elements && response.elements.length > 0) { - console.log("\nElements:"); + logger.info("\nElements:"); response.elements.forEach((element, index) => { - console.log(`\nElement ${index + 1}:`); + logger.info(`\nElement ${index + 1}:`); if (element.interaction) { - console.log(` Action: ${element.interaction.action}`); + logger.info(` Action: ${element.interaction.action}`); if (element.interaction.calledWith) { - console.log(` Called With: ${element.interaction.calledWith}`); + logger.info(` Called With: ${element.interaction.calledWith}`); } } if (element.assertion) { - console.log(` Expectation: ${element.assertion.expectation}`); + logger.info(` Expectation: ${element.assertion.expectation}`); if (element.assertion.calledWith) { - console.log(` Called With: ${element.assertion.calledWith}`); + logger.info(` Called With: ${element.assertion.calledWith}`); } } - console.log(" Selectors:"); + logger.info(" Selectors:"); element.selectors?.forEach((selector) => { - console.log(` - ${selector.selectorType}: ${selector.selector}`); + logger.info(` - ${selector.selectorType}: ${selector.selector}`); if (selector.options?.name) { - console.log(` Name: ${selector.options.name}`); + logger.info(` Name: ${selector.options.name}`); } }); }); @@ -193,7 +194,7 @@ export const getTestCaseCode = async ( if (!data) { throw new Error("no test code found"); } - console.log(data.testCode); + logger.info(data.testCode); }; export const listTestCases = async ( @@ -209,16 +210,16 @@ export const listTestCases = async ( return; } - console.log("Test Cases:"); + logger.info("Test Cases:"); for (let idx = 0; idx < testCases.length; idx++) { const testCase = testCases[idx]; const idxString = `${idx + 1}. `.padEnd( testCases.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}Description: ${testCase.description}`); - console.log(`${paddingString}ID: ${testCase.id}`); - console.log( + logger.info(`${idxString}Description: ${testCase.description}`); + logger.info(`${paddingString}ID: ${testCase.id}`); + logger.info( `${paddingString}${await getUrl({ testCaseId: testCase.id, entityType: "test-case", diff --git c/src/tools/test-reports.ts i/src/tools/test-reports.ts index 1e5e509..2571e8e 100644 --- c/src/tools/test-reports.ts +++ i/src/tools/test-reports.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -39,19 +40,19 @@ export const executeTests = async ( } if (data) { - console.log("Test execution started successfully!"); - console.log("Test Report URL:", data.testReportUrl); - console.log("Report Status:", data.testReport?.status); + logger.info("Test execution started successfully!"); + logger.info(`Test Report URL: ${data.testReportUrl}`); + logger.info(`Report Status: ${data.testReport?.status}`); const numberOfTestResults = data.testReport?.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } }); } @@ -100,15 +101,15 @@ export const listTestReport = async ( } const response = data as TestReport; - console.log("Test Report Details:"); - console.log("Status:", response.status); - console.log("Execution URL:", response.executionUrl); + logger.info("Test Report Details:"); + logger.info(`Status: ${response.status}`); + logger.info(`Execution URL: ${response.executionUrl}`); const numberOfTestResults = response.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); - console.log( + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info( ` ${await getUrl({ testReportId: options.testReportId, testResultId: result.id ?? "", @@ -116,10 +117,10 @@ export const listTestReport = async ( })}`, ); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } } } diff --git c/src/tools/test-targets.ts i/src/tools/test-targets.ts index cf39a33..2cb64e4 100644 --- c/src/tools/test-targets.ts +++ i/src/tools/test-targets.ts @@ -4,6 +4,7 @@ import ora from "ora"; import { OCTOMIND_FOLDER_NAME } from "../constants"; import { confirmAction, findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; @@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { return; } - console.log("Test Targets:"); + logger.info("Test Targets:"); for (let idx = 0; idx < testTargets.length; idx++) { const testTarget = testTargets[idx]; @@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise => { testTargets.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}ID: ${testTarget.id}`); - console.log(`${paddingString}App: ${testTarget.app}`); - console.log( + logger.info(`${idxString}ID: ${testTarget.id}`); + logger.info(`${paddingString}App: ${testTarget.app}`); + logger.info( `${paddingString}${await getUrl({ testTargetId: testTarget.id, entityType: "test-target", @@ -98,7 +99,7 @@ export const pullTestTarget = async ( path.join(process.cwd(), OCTOMIND_FOLDER_NAME); await writeYaml(data, destination); - console.log("Test Target pulled successfully"); + logger.info("Test Target pulled successfully"); }; export const pushTestTarget = async ( @@ -124,7 +125,7 @@ export const pushTestTarget = async ( `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`, ); if (!confirmed) { - console.log("Push cancelled."); + logger.info("Push cancelled."); return; } } diff --git c/src/tools/update.ts i/src/tools/update.ts index 3aff6dd..f63daa9 100644 --- c/src/tools/update.ts +++ i/src/tools/update.ts @@ -5,6 +5,8 @@ import path from "path"; import which from "which"; +import { logger } from "../logger"; + export const update = async (): Promise => { const pathToRoot = path.posix.normalize( path.join(homedir(), ".local", "packages"), @@ -12,14 +14,14 @@ export const update = async (): Promise => { const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { - console.error( + logger.error( "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind", ); process.exit(1); } if (!(await which("npm"))) { - console.error( + logger.error( `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); @@ -30,7 +32,7 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`version before update: ${oldVersion}`); + logger.info(`version before update: ${oldVersion}`); child_process.execSync("npm install @octomind/octomind@latest", { cwd: pathToRoot, @@ -42,5 +44,5 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`✔ update complete: ${newVersion}`); + logger.info(`✔ update complete: ${newVersion}`); }; diff --git c/src/tools/yamlMutations/create.ts i/src/tools/yamlMutations/create.ts index d819b50..946d268 100644 --- c/src/tools/yamlMutations/create.ts +++ i/src/tools/yamlMutations/create.ts @@ -9,6 +9,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -75,7 +76,7 @@ const openBrowser = async ({ await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); }; @@ -88,7 +89,7 @@ const writeOutput = async ({ createResult: SyncTestCase | "cancelled"; }): Promise => { if (createResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -101,8 +102,8 @@ const writeOutput = async ({ yaml.stringify(createResult), ); - console.log(`Created test case successfully`); - console.log(diff); + logger.info(`Created test case successfully`); + logger.info(diff); }; export const create = async (options: CreateOptions): Promise => { diff --git c/src/tools/yamlMutations/edit.ts i/src/tools/yamlMutations/edit.ts index b8fb58e..fe8a00c 100644 --- c/src/tools/yamlMutations/edit.ts +++ i/src/tools/yamlMutations/edit.ts @@ -7,6 +7,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise => { await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); @@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise => { ); if (editResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise => { yaml.stringify(editResult), ); - console.log(`Edited test case successfully`); - console.log(diff); + logger.info(`Edited test case successfully`); + logger.info(diff); }; diff --git c/tests/config.spec.ts i/tests/config.spec.ts index c1c9b9c..0856939 100644 --- c/tests/config.spec.ts +++ i/tests/config.spec.ts @@ -5,13 +5,14 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; +import { mockLogger } from "./mocks"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - console.error = vi.fn(); + mockLogger.error.mockClear(); resetConfig(); }); @@ -68,7 +69,7 @@ describe("Config", () => { const result = await loadConfig(); expect(result).toEqual({}); - expect(console.error).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); }); it("should error and exit when config file does not exist and force is true", async () => { @@ -89,7 +90,7 @@ describe("Config", () => { await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow( "Process exit with code: 1", ); - expect(console.error).toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); diff --git c/tests/mocks.ts i/tests/mocks.ts index 42342b8..02e672c 100644 --- c/tests/mocks.ts +++ i/tests/mocks.ts @@ -1,3 +1,4 @@ +import { vi } from "vitest"; import { EnvironmentResponse, TestCaseResponse, @@ -5,6 +6,19 @@ import { } from "../src/tools"; import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types"; +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, +})); + export const createMockSyncTestCase = ( overrides?: Partial, ): SyncTestCase => ({ diff --git c/tests/setup.ts i/tests/setup.ts index cd22023..aa85cd5 100644 --- c/tests/setup.ts +++ i/tests/setup.ts @@ -1,5 +1,7 @@ import { beforeEach, vi } from "vitest"; +import { mockLogger } from "./mocks"; + vi.mock("ora", () => ({ default: vi.fn(() => ({ start: vi.fn().mockReturnThis(), @@ -9,7 +11,10 @@ vi.mock("ora", () => ({ })); beforeEach(() => { - console.log = vi.fn(); - console.warn = vi.fn(); - console.error = vi.fn(); + mockLogger.info.mockClear(); + mockLogger.warn.mockClear(); + mockLogger.error.mockClear(); + mockLogger.debug.mockClear(); + mockLogger.log.mockClear(); + mockLogger.success.mockClear(); }); diff --git c/tests/tools/init.spec.ts i/tests/tools/init.spec.ts index a29a295..fc1b5b3 100644 --- c/tests/tools/init.spec.ts +++ i/tests/tools/init.spec.ts @@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; +import { mockLogger } from "../mocks"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -15,7 +16,7 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); @@ -36,10 +37,10 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Only one test target found, using it: testTargetApp (testTargetId)", ); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 3, "\n✨ Initialization complete!", ); @@ -56,7 +57,7 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId1", }); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 2, "\n✨ Initialization complete!", ); diff --git c/tests/tools/sync/git.spec.ts i/tests/tools/sync/git.spec.ts index 4dc35df..4b2a10c 100644 --- c/tests/tools/sync/git.spec.ts +++ i/tests/tools/sync/git.spec.ts @@ -7,6 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; +import { mockLogger } from "../../mocks"; vi.mock("simple-git"); @@ -17,30 +18,29 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - console.error = vi.fn(); + mockLogger.error.mockClear(); + mockLogger.warn.mockClear(); }); describe("getDefaultBranch", () => { it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockResolvedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockRejectedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); }); diff --git c/tests/tools/test-cases.spec.ts i/tests/tools/test-cases.spec.ts index 0731b48..696d4bf 100644 --- c/tests/tools/test-cases.spec.ts +++ i/tests/tools/test-cases.spec.ts @@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; +import { mockLogger } from "../mocks"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -18,7 +19,7 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); describe("deleteTestCase via API", () => { @@ -36,7 +37,7 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); }); @@ -84,7 +85,7 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); await expect(fsPromises.access(filePath)).rejects.toThrow(); @@ -98,7 +99,7 @@ describe("test-cases", () => { testCaseId: "non-existent-id", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( `No test case with id non-existent-id found in folder ${tmpDir}`, ); expect(clientDELETE).not.toHaveBeenCalled(); diff --git c/tests/tools/test-targets.spec.ts i/tests/tools/test-targets.spec.ts index 92e0bcb..37dadc4 100644 --- c/tests/tools/test-targets.spec.ts +++ i/tests/tools/test-targets.spec.ts @@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; +import { mockLogger } from "../mocks"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -35,7 +36,7 @@ describe("push", () => { error: undefined, response: mock(), }); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); it("pushes to main if on default branch", async () => { @@ -114,7 +115,7 @@ describe("push", () => { }); expect(client.POST).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Push cancelled."); + expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled."); }); }); }); diff --git c/tests/tools/yamlMutations/create.spec.ts i/tests/tools/yamlMutations/create.spec.ts index eadbc73..b1e46dd 100644 --- c/tests/tools/yamlMutations/create.spec.ts +++ i/tests/tools/yamlMutations/create.spec.ts @@ -18,6 +18,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -32,7 +33,7 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); @@ -110,7 +111,7 @@ describe("create", () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -118,7 +119,7 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully"); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git c/tests/tools/yamlMutations/edit.spec.ts i/tests/tools/yamlMutations/edit.spec.ts index dcb928c..4799668 100644 --- c/tests/tools/yamlMutations/edit.spec.ts +++ i/tests/tools/yamlMutations/edit.spec.ts @@ -15,6 +15,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -29,7 +30,7 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( @@ -96,7 +97,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -108,7 +109,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully"); }); it.each([ --- pnpm-lock.yaml | 185 +++++++++++++++++++++++ src/completion.ts | 5 +- src/config.ts | 12 +- src/debugtopus/index.ts | 9 +- src/debugtopus/installation.ts | 6 +- src/logger.ts | 15 ++ src/plw/index.ts | 17 ++- src/tools/client.ts | 9 +- src/tools/discoveries.ts | 15 +- src/tools/environments.ts | 43 +++--- src/tools/init.ts | 21 +-- src/tools/notifications.ts | 21 +-- src/tools/private-locations.ts | 16 +- src/tools/sync/git.ts | 17 ++- src/tools/sync/yaml.ts | 5 +- src/tools/test-cases.ts | 49 +++--- src/tools/test-reports.ts | 31 ++-- src/tools/test-targets.ts | 13 +- src/tools/update.ts | 10 +- src/tools/yamlMutations/create.ts | 9 +- src/tools/yamlMutations/edit.ts | 9 +- tests/config.spec.ts | 7 +- tests/mocks.ts | 14 ++ tests/setup.ts | 11 +- tests/tools/init.spec.ts | 9 +- tests/tools/sync/git.spec.ts | 10 +- tests/tools/test-cases.spec.ts | 9 +- tests/tools/test-targets.spec.ts | 5 +- tests/tools/yamlMutations/create.spec.ts | 7 +- tests/tools/yamlMutations/edit.spec.ts | 7 +- 30 files changed, 421 insertions(+), 175 deletions(-) create mode 100644 src/logger.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30d64cb..d4318b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,12 @@ importers: otplib: specifier: 13.1.1 version: 13.1.1 + pino: + specifier: 10.2.0 + version: 10.2.0 + pino-pretty: + specifier: 13.1.3 + version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -553,6 +559,9 @@ packages: '@otplib/uri@13.1.1': resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.57.0': resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} @@ -874,6 +883,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -937,6 +950,9 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -955,6 +971,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -997,6 +1016,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1044,6 +1066,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1051,6 +1076,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1140,6 +1168,9 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1240,6 +1271,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1344,6 +1379,13 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1429,6 +1471,20 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.2.0: + resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==} + hasBin: true + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -1458,6 +1514,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1465,6 +1527,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1472,6 +1537,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + remeda@2.33.4: resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} @@ -1517,9 +1586,16 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1549,10 +1625,17 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1598,6 +1681,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1609,6 +1696,10 @@ packages: tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -1820,6 +1911,9 @@ packages: engines: {node: '>=8'} hasBin: true + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2209,6 +2303,8 @@ snapshots: dependencies: '@otplib/core': 13.1.1 + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.57.0': dependencies: playwright: 1.57.0 @@ -2484,6 +2580,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + balanced-match@1.0.2: {} bluebird@3.7.2: {} @@ -2538,6 +2636,8 @@ snapshots: colorette@1.4.0: {} + colorette@2.0.20: {} + commander@14.0.2: {} commander@7.2.0: {} @@ -2552,6 +2652,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + dateformat@4.6.3: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -2581,6 +2683,10 @@ snapshots: dependencies: jake: 10.9.4 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2681,6 +2787,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2691,6 +2799,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -2780,6 +2890,8 @@ snapshots: has-flag@3.0.0: {} + help-me@5.0.0: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2861,6 +2973,8 @@ snapshots: jiti@2.6.1: {} + joycon@3.1.1: {} + js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -2953,6 +3067,12 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3078,6 +3198,42 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.2.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 4.0.0 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -3102,10 +3258,19 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -3118,6 +3283,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + remeda@2.33.4: {} require-from-string@2.0.2: {} @@ -3181,8 +3348,12 @@ snapshots: safe-buffer@5.1.2: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + secure-json-parse@4.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3207,8 +3378,14 @@ snapshots: slash@5.1.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -3249,6 +3426,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-json-comments@5.0.3: {} + supports-color@10.2.2: {} supports-color@5.5.0: @@ -3266,6 +3445,10 @@ snapshots: transitivePeerDependencies: - supports-color + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tinybench@2.9.0: {} @@ -3429,6 +3612,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrappy@1.0.2: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git a/src/completion.ts b/src/completion.ts index f5a0c77..3e0b0ac 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -3,6 +3,7 @@ import { parse } from "shell-quote"; import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; +import { logger } from "./logger"; import { loadConfig } from "./config"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; @@ -193,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => console.error("INSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "INSTALL ERROR")); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => console.error("UNINSTALL ERROR", err)); + }).catch((err) => logger.error({ err }, "UNINSTALL ERROR")); }; diff --git a/src/config.ts b/src/config.ts index 9268bd7..bf42479 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,6 +3,8 @@ import fs from "fs/promises"; import { homedir } from "os"; import { join } from "path"; +import { logger } from "./logger"; + const CONFIG_DIR = ".config"; export async function getConfigPath(ensureDir?: boolean): Promise { @@ -45,9 +47,9 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - console.error( - "❌ Error parsing configuration:", - (error as Error).message, + logger.error( + { err: error as Error }, + "❌ Error parsing configuration", ); process.exit(1); } @@ -59,10 +61,10 @@ export async function saveConfig(newConfig: Config): Promise { try { const configPath = await getConfigPath(true); await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8"); - console.log(`✅ Configuration saved to ${configPath}`); + logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - console.error("❌ Error saving configuration:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error saving configuration"); process.exit(1); } } diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 05ad042..32e7e63 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -16,6 +16,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../helpers"; +import { logger } from "../logger"; import { getEnvironments, getPlaywrightCode, @@ -91,7 +92,7 @@ const prepareDirectories = async ( const appDir = dirname(nodeModule.filename); packageRootDir = getPackageRootLevel(appDir); } else { - console.warn("package was not installed as valid nodeJS module"); + logger.warn("package was not installed as valid nodeJS module"); packageRootDir = process.cwd(); } } @@ -169,17 +170,17 @@ const runTests = async ({ command += " --ui"; } - console.log(`executing command : '${command}'`); + logger.info(`executing command : '${command}'`); const { stderr } = await promisify(exec)(command, { cwd: packageRootDir, }); if (stderr) { - console.error(stderr); + logger.error(stderr); process.exit(1); } else { - console.log(`success, you can find your artifacts at ${outputDir}`); + logger.info(`success, you can find your artifacts at ${outputDir}`); } }; diff --git a/src/debugtopus/installation.ts b/src/debugtopus/installation.ts index 6582abd..080346a 100644 --- a/src/debugtopus/installation.ts +++ b/src/debugtopus/installation.ts @@ -5,6 +5,8 @@ import { promisify } from "util"; import { chromium } from "@playwright/test"; +import { logger } from "../logger"; + export const ensureChromiumIsInstalled = async ( packageRootDir: string, ): Promise => { @@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async ( reject(error); return; } - console.log( + logger.info( "Couldn't find any chromium binary, executing 'npx playwright install chromium'", ); @@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async ( ); playwrightInstallExecution.child?.stdout?.on("data", (data) => - console.log(data), + logger.info(data), ); await playwrightInstallExecution; diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..946ed95 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,15 @@ +import pino from "pino"; + +const isProduction = process.env.NODE_ENV === "production"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || "info", + transport: isProduction + ? undefined + : { + target: "pino-pretty", + options: { + colorize: true, + }, + }, +}); diff --git a/src/plw/index.ts b/src/plw/index.ts index b26e846..ea22afc 100644 --- a/src/plw/index.ts +++ b/src/plw/index.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import { createInterface } from "readline"; import { loadConfig } from "../config"; +import { logger } from "../logger"; interface StreamResult { lines: string[]; @@ -39,7 +40,7 @@ const spawnAndStreamLines = async ( rl.on("line", (line: string) => { lines.push(line); - console.log(`-- ${line}`); + logger.info(`-- ${line}`); lineCount++; if (lineCount >= maxLines) { @@ -58,7 +59,7 @@ const spawnAndStreamLines = async ( child.on("exit", (code, signal) => { if (lineCount < maxLines) { - console.log(`Process exited with code ${code}, signal ${signal}`); + logger.info(`Process exited with code ${code}, signal ${signal}`); } resolve({ lines, detached: false, code, signal }); }); @@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: { }); if (!(await checkDockerDaemon())) { - console.error( + logger.error( "Docker daemon is not running. Please start Docker and try again.", ); return; } - console.log( + logger.info( `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`, ); const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); - console.log(`Captured ${result.lines.length} lines`); - console.log(`Process detached: ${result.detached}`); + logger.info(`Captured ${result.lines.length} lines`); + logger.info(`Process detached: ${result.detached}`); }; export const stopPLW = async (): Promise => { @@ -131,9 +132,9 @@ export const stopPLW = async (): Promise => { const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); if (result.code === 0) { - console.log("Private Location Worker stopped successfully."); + logger.info("Private Location Worker stopped successfully."); } else { - console.error( + logger.error( `Failed to stop Private Location Worker. Exit code: ${result.code}`, ); } diff --git a/src/tools/client.ts b/src/tools/client.ts index 979aa2a..6e5146c 100644 --- a/src/tools/client.ts +++ b/src/tools/client.ts @@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch"; import type { components, paths } from "../api"; // generated by openapi-typescript import { loadConfig } from "../config"; +import { logger } from "../logger"; import { version } from "../version"; export const BASE_URL = @@ -65,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - console.error(error); + logger.error(error); process.exit(1); }, }; @@ -77,9 +78,9 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - console.error(error); + logger.error(error); if (typeof error === "string" && error.startsWith("403")) { - console.error( + logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", ); } @@ -91,5 +92,5 @@ export type ListOptions = { json?: boolean; }; export const logJson = (result: unknown): void => { - console.log(JSON.stringify(result, null, 2)); + logger.info(JSON.stringify(result, null, 2)); }; diff --git a/src/tools/discoveries.ts b/src/tools/discoveries.ts index a8f2449..7aa3d14 100644 --- a/src/tools/discoveries.ts +++ b/src/tools/discoveries.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -47,9 +48,9 @@ export const batchGeneration = async ( throw new Error("Batch generation ID is missing"); } - console.log("Batch generation created successfully!"); - console.log(`Batch generation ID: ${response.batchGenerationId}`); - console.log( + logger.info("Batch generation created successfully!"); + logger.info(`Batch generation ID: ${response.batchGenerationId}`); + logger.info( `Batch generation URL: ${await getUrl({ batchGenerationId: response.batchGenerationId, entityType: "batch-generation", @@ -95,13 +96,13 @@ export const createDiscovery = async ( return; } - console.log("Discovery created successfully!"); - console.log( + logger.info("Discovery created successfully!"); + logger.info( `${await getUrl({ testCaseId: response.testCaseId ?? "", entityType: "discovery", })}`, ); - console.log(`Discovery ID: ${response.discoveryId}`); - console.log(`Test Case ID: ${response.testCaseId}`); + logger.info(`Discovery ID: ${response.discoveryId}`); + logger.info(`Test Case ID: ${response.testCaseId}`); }; diff --git a/src/tools/environments.ts b/src/tools/environments.ts index 6712707..c38925f 100644 --- a/src/tools/environments.ts +++ b/src/tools/environments.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type GetEnvironmentsOptions = @@ -26,12 +27,12 @@ export const listEnvironments = async ( return; } - console.log("Environments:"); + logger.info("Environments:"); environments.forEach((environment) => { - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }); }; @@ -99,11 +100,11 @@ export const createEnvironment = async ( return; } - console.log("Environment created successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment created successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const updateEnvironment = async ( @@ -152,11 +153,11 @@ export const updateEnvironment = async ( return; } - console.log("Environment updated successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment updated successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const getEnvironment = async ( @@ -176,11 +177,11 @@ export const getEnvironment = async ( return; } - console.log("Environment:"); - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info("Environment:"); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }; export const deleteEnvironment = async ( @@ -205,5 +206,5 @@ export const deleteEnvironment = async ( return; } - console.log("Environment deleted successfully!"); + logger.info("Environment deleted successfully!"); }; diff --git a/src/tools/init.ts b/src/tools/init.ts index a815fd2..57ad8b2 100644 --- a/src/tools/init.ts +++ b/src/tools/init.ts @@ -1,5 +1,6 @@ import { Config, loadConfig, saveConfig } from "../config"; import { promptUser } from "../helpers"; +import { logger } from "../logger"; import { getTestTargets, listTestTargets } from "./test-targets"; const selectTestTarget = async (): Promise => { @@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise => { await listTestTargets({}); if (testTargets.length === 1) { - console.log( + logger.info( `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`, ); return testTargets[0].id; @@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise => { testTargetIndexAsInt < 1 || testTargetIndexAsInt > testTargets.length ) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } const testTargetId = testTargets[testTargetIndexAsInt - 1].id; if (!testTargetId) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } @@ -43,7 +44,7 @@ export const switchTestTarget = async () => { testTargetId, }; await saveConfig(newConfig); - console.log(`✨ Switched to test target: ${testTargetId}`); + logger.info(`✨ Switched to test target: ${testTargetId}`); }; export const init = async (options: { @@ -52,12 +53,12 @@ export const init = async (options: { force?: boolean; }) => { try { - console.log("🚀 Initializing configuration...\n"); + logger.info("🚀 Initializing configuration...\n"); const existingConfig = await loadConfig(options.force); if (existingConfig.apiKey && !options.force) { - console.log("⚠️ Configuration already exists."); + logger.info("⚠️ Configuration already exists."); const overwrite = await promptUser( "Do you want to overwrite it? (y/N): ", ); @@ -66,7 +67,7 @@ export const init = async (options: { overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes" ) { - console.log("Configuration unchanged."); + logger.info("Configuration unchanged."); return; } } @@ -76,7 +77,7 @@ export const init = async (options: { "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ", ); if (!options.apiKey) { - console.log("❌ API key is required."); + logger.info("❌ API key is required."); process.exit(1); } } @@ -99,9 +100,9 @@ export const init = async (options: { await saveConfig(newConfig); - console.log("\n✨ Initialization complete!"); + logger.info("\n✨ Initialization complete!"); } catch (error) { - console.error("❌ Error during initialization:", (error as Error).message); + logger.error({ err: error as Error }, "❌ Error during initialization"); process.exit(1); } }; diff --git a/src/tools/notifications.ts b/src/tools/notifications.ts index a1b9339..93f9319 100644 --- a/src/tools/notifications.ts +++ b/src/tools/notifications.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -27,28 +28,28 @@ export const listNotifications = async ( return; } - console.log("Notifications:"); + logger.info("Notifications:"); for (const notification of response) { - console.log(`\nID: ${notification.id}`); - console.log(`Type: ${notification.type}`); - console.log(`Created At: ${notification.createdAt}`); + logger.info(`\nID: ${notification.id}`); + logger.info(`Type: ${notification.type}`); + logger.info(`Created At: ${notification.createdAt}`); if (notification.payload?.testReportId) { - console.log(`Test Report ID: ${notification.payload.testReportId}`); - console.log( + logger.info(`Test Report ID: ${notification.payload.testReportId}`); + logger.info( `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`, ); } if (notification.payload?.testCaseId) { - console.log(`Test Case ID: ${notification.payload.testCaseId}`); - console.log( + logger.info(`Test Case ID: ${notification.payload.testCaseId}`); + logger.info( `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`, ); } if (notification.payload?.failed !== undefined) { - console.log(`Failed: ${notification.payload.failed}`); + logger.info(`Failed: ${notification.payload.failed}`); } if (notification.ack) { - console.log(`Acknowledged: ${notification.ack}`); + logger.info(`Acknowledged: ${notification.ack}`); } } }; diff --git a/src/tools/private-locations.ts b/src/tools/private-locations.ts index b91a414..7c0ba62 100644 --- a/src/tools/private-locations.ts +++ b/src/tools/private-locations.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type SuccessResponse = components["schemas"]["SuccessResponse"]; @@ -35,7 +36,7 @@ export const registerLocation = async ( return; } - console.log("Registration result:", response.success ? "Success" : "Failed"); + logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`); }; export const unregisterLocation = async ( @@ -60,9 +61,8 @@ export const unregisterLocation = async ( return; } - console.log( - "Unregistration result:", - response.success ? "Success" : "Failed", + logger.info( + `Unregistration result: ${response.success ? "Success" : "Failed"}`, ); }; @@ -79,10 +79,10 @@ export const listPrivateLocations = async ( return; } - console.log("Private Locations:"); + logger.info("Private Locations:"); response.forEach((location) => { - console.log(`- Name: ${location.name}`); - console.log(` Status: ${location.status}`); - console.log(` Address: ${location.address}`); + logger.info(`- Name: ${location.name}`); + logger.info(` Status: ${location.status}`); + logger.info(` Address: ${location.address}`); }); }; diff --git a/src/tools/sync/git.ts b/src/tools/sync/git.ts index 765b3bd..3a7fc1f 100644 --- a/src/tools/sync/git.ts +++ b/src/tools/sync/git.ts @@ -2,6 +2,7 @@ import path from "path"; import { simpleGit } from "simple-git"; +import { logger } from "../../logger"; import { ExecutionContext } from "./types"; const FALLBACK_DEFAULT_BRANCH = "refs/heads/main"; @@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - console.error(error); + logger.error({ err: error }, "Failed to parse git remote"); return {}; } }; @@ -59,9 +60,9 @@ export const getDefaultBranch = async ( return symbolicRefBranch; } } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify symbolic ref, falling back to origin parsing", - e, ); } } @@ -69,7 +70,7 @@ export const getDefaultBranch = async ( try { const origin = await simpleGit().remote(["show", "origin"]); if (!origin) { - console.warn("could not identify default branch, falling back to 'main'"); + logger.warn("could not identify default branch, falling back to 'main'"); return FALLBACK_DEFAULT_BRANCH; } @@ -79,9 +80,9 @@ export const getDefaultBranch = async ( ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify default branch, falling back to 'main'", - e, ); } @@ -108,9 +109,9 @@ export const getGitContext = async (): Promise => { }; return ctx; } catch (e) { - console.warn( + logger.warn( + { err: e }, "could not identify git context, falling back to undefined", - e, ); return undefined; } diff --git a/src/tools/sync/yaml.ts b/src/tools/sync/yaml.ts index 7451476..edf2d28 100644 --- a/src/tools/sync/yaml.ts +++ b/src/tools/sync/yaml.ts @@ -4,6 +4,7 @@ import path from "path"; import yaml from "yaml"; +import { logger } from "../../logger"; import { PushTestTargetBody } from "../../schemas/octomindExternalAPI"; import { SyncTestCase, TestTargetSyncData } from "./types"; @@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => { if (result.success) { testCases.push(result.data); } else { - console.warn( + logger.warn( `Failed to read test case from ${file}: ${result.error.message}`, ); } } catch { - console.error(`Failed to read test case from ${file}`); + logger.error(`Failed to read test case from ${file}`); } } diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts index 742fb06..54a3831 100644 --- a/src/tools/test-cases.ts +++ b/src/tools/test-cases.ts @@ -3,6 +3,7 @@ import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; @@ -37,7 +38,7 @@ export const deleteTestCase = async ( if (options.json) { logJson(data); } - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); return; } @@ -46,7 +47,7 @@ export const deleteTestCase = async ( const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log( + logger.info( `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, ); return; @@ -54,7 +55,7 @@ export const deleteTestCase = async ( const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); }; export const listTestCase = async ( @@ -81,35 +82,35 @@ export const listTestCase = async ( return; } - console.log("Test Case Details:"); - console.log(`ID: ${response.id}`); - console.log(`Description: ${response.description}`); - console.log(`Status: ${response.status}`); - console.log(`Run Status: ${response.runStatus}`); - console.log(`Created At: ${response.createdAt}`); - console.log(`Updated At: ${response.updatedAt}`); + logger.info("Test Case Details:"); + logger.info(`ID: ${response.id}`); + logger.info(`Description: ${response.description}`); + logger.info(`Status: ${response.status}`); + logger.info(`Run Status: ${response.runStatus}`); + logger.info(`Created At: ${response.createdAt}`); + logger.info(`Updated At: ${response.updatedAt}`); if (response.elements && response.elements.length > 0) { - console.log("\nElements:"); + logger.info("\nElements:"); response.elements.forEach((element, index) => { - console.log(`\nElement ${index + 1}:`); + logger.info(`\nElement ${index + 1}:`); if (element.interaction) { - console.log(` Action: ${element.interaction.action}`); + logger.info(` Action: ${element.interaction.action}`); if (element.interaction.calledWith) { - console.log(` Called With: ${element.interaction.calledWith}`); + logger.info(` Called With: ${element.interaction.calledWith}`); } } if (element.assertion) { - console.log(` Expectation: ${element.assertion.expectation}`); + logger.info(` Expectation: ${element.assertion.expectation}`); if (element.assertion.calledWith) { - console.log(` Called With: ${element.assertion.calledWith}`); + logger.info(` Called With: ${element.assertion.calledWith}`); } } - console.log(" Selectors:"); + logger.info(" Selectors:"); element.selectors?.forEach((selector) => { - console.log(` - ${selector.selectorType}: ${selector.selector}`); + logger.info(` - ${selector.selectorType}: ${selector.selector}`); if (selector.options?.name) { - console.log(` Name: ${selector.options.name}`); + logger.info(` Name: ${selector.options.name}`); } }); }); @@ -193,7 +194,7 @@ export const getTestCaseCode = async ( if (!data) { throw new Error("no test code found"); } - console.log(data.testCode); + logger.info(data.testCode); }; export const listTestCases = async ( @@ -209,16 +210,16 @@ export const listTestCases = async ( return; } - console.log("Test Cases:"); + logger.info("Test Cases:"); for (let idx = 0; idx < testCases.length; idx++) { const testCase = testCases[idx]; const idxString = `${idx + 1}. `.padEnd( testCases.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}Description: ${testCase.description}`); - console.log(`${paddingString}ID: ${testCase.id}`); - console.log( + logger.info(`${idxString}Description: ${testCase.description}`); + logger.info(`${paddingString}ID: ${testCase.id}`); + logger.info( `${paddingString}${await getUrl({ testCaseId: testCase.id, entityType: "test-case", diff --git a/src/tools/test-reports.ts b/src/tools/test-reports.ts index 1e5e509..2571e8e 100644 --- a/src/tools/test-reports.ts +++ b/src/tools/test-reports.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -39,19 +40,19 @@ export const executeTests = async ( } if (data) { - console.log("Test execution started successfully!"); - console.log("Test Report URL:", data.testReportUrl); - console.log("Report Status:", data.testReport?.status); + logger.info("Test execution started successfully!"); + logger.info(`Test Report URL: ${data.testReportUrl}`); + logger.info(`Report Status: ${data.testReport?.status}`); const numberOfTestResults = data.testReport?.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } }); } @@ -100,15 +101,15 @@ export const listTestReport = async ( } const response = data as TestReport; - console.log("Test Report Details:"); - console.log("Status:", response.status); - console.log("Execution URL:", response.executionUrl); + logger.info("Test Report Details:"); + logger.info(`Status: ${response.status}`); + logger.info(`Execution URL: ${response.executionUrl}`); const numberOfTestResults = response.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); - console.log( + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info( ` ${await getUrl({ testReportId: options.testReportId, testResultId: result.id ?? "", @@ -116,10 +117,10 @@ export const listTestReport = async ( })}`, ); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } } } diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index cf39a33..2cb64e4 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -4,6 +4,7 @@ import ora from "ora"; import { OCTOMIND_FOLDER_NAME } from "../constants"; import { confirmAction, findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; @@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { return; } - console.log("Test Targets:"); + logger.info("Test Targets:"); for (let idx = 0; idx < testTargets.length; idx++) { const testTarget = testTargets[idx]; @@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise => { testTargets.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}ID: ${testTarget.id}`); - console.log(`${paddingString}App: ${testTarget.app}`); - console.log( + logger.info(`${idxString}ID: ${testTarget.id}`); + logger.info(`${paddingString}App: ${testTarget.app}`); + logger.info( `${paddingString}${await getUrl({ testTargetId: testTarget.id, entityType: "test-target", @@ -98,7 +99,7 @@ export const pullTestTarget = async ( path.join(process.cwd(), OCTOMIND_FOLDER_NAME); await writeYaml(data, destination); - console.log("Test Target pulled successfully"); + logger.info("Test Target pulled successfully"); }; export const pushTestTarget = async ( @@ -124,7 +125,7 @@ export const pushTestTarget = async ( `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`, ); if (!confirmed) { - console.log("Push cancelled."); + logger.info("Push cancelled."); return; } } diff --git a/src/tools/update.ts b/src/tools/update.ts index 3aff6dd..f63daa9 100644 --- a/src/tools/update.ts +++ b/src/tools/update.ts @@ -5,6 +5,8 @@ import path from "path"; import which from "which"; +import { logger } from "../logger"; + export const update = async (): Promise => { const pathToRoot = path.posix.normalize( path.join(homedir(), ".local", "packages"), @@ -12,14 +14,14 @@ export const update = async (): Promise => { const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { - console.error( + logger.error( "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind", ); process.exit(1); } if (!(await which("npm"))) { - console.error( + logger.error( `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); @@ -30,7 +32,7 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`version before update: ${oldVersion}`); + logger.info(`version before update: ${oldVersion}`); child_process.execSync("npm install @octomind/octomind@latest", { cwd: pathToRoot, @@ -42,5 +44,5 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`✔ update complete: ${newVersion}`); + logger.info(`✔ update complete: ${newVersion}`); }; diff --git a/src/tools/yamlMutations/create.ts b/src/tools/yamlMutations/create.ts index d819b50..946d268 100644 --- a/src/tools/yamlMutations/create.ts +++ b/src/tools/yamlMutations/create.ts @@ -9,6 +9,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -75,7 +76,7 @@ const openBrowser = async ({ await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); }; @@ -88,7 +89,7 @@ const writeOutput = async ({ createResult: SyncTestCase | "cancelled"; }): Promise => { if (createResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -101,8 +102,8 @@ const writeOutput = async ({ yaml.stringify(createResult), ); - console.log(`Created test case successfully`); - console.log(diff); + logger.info(`Created test case successfully`); + logger.info(diff); }; export const create = async (options: CreateOptions): Promise => { diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b8fb58e..fe8a00c 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -7,6 +7,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise => { await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); @@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise => { ); if (editResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise => { yaml.stringify(editResult), ); - console.log(`Edited test case successfully`); - console.log(diff); + logger.info(`Edited test case successfully`); + logger.info(diff); }; diff --git a/tests/config.spec.ts b/tests/config.spec.ts index c1c9b9c..0856939 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -5,13 +5,14 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; +import { mockLogger } from "./mocks"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - console.error = vi.fn(); + mockLogger.error.mockClear(); resetConfig(); }); @@ -68,7 +69,7 @@ describe("Config", () => { const result = await loadConfig(); expect(result).toEqual({}); - expect(console.error).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); }); it("should error and exit when config file does not exist and force is true", async () => { @@ -89,7 +90,7 @@ describe("Config", () => { await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow( "Process exit with code: 1", ); - expect(console.error).toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); diff --git a/tests/mocks.ts b/tests/mocks.ts index 42342b8..02e672c 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,3 +1,4 @@ +import { vi } from "vitest"; import { EnvironmentResponse, TestCaseResponse, @@ -5,6 +6,19 @@ import { } from "../src/tools"; import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types"; +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, +})); + export const createMockSyncTestCase = ( overrides?: Partial, ): SyncTestCase => ({ diff --git a/tests/setup.ts b/tests/setup.ts index cd22023..aa85cd5 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,5 +1,7 @@ import { beforeEach, vi } from "vitest"; +import { mockLogger } from "./mocks"; + vi.mock("ora", () => ({ default: vi.fn(() => ({ start: vi.fn().mockReturnThis(), @@ -9,7 +11,10 @@ vi.mock("ora", () => ({ })); beforeEach(() => { - console.log = vi.fn(); - console.warn = vi.fn(); - console.error = vi.fn(); + mockLogger.info.mockClear(); + mockLogger.warn.mockClear(); + mockLogger.error.mockClear(); + mockLogger.debug.mockClear(); + mockLogger.log.mockClear(); + mockLogger.success.mockClear(); }); diff --git a/tests/tools/init.spec.ts b/tests/tools/init.spec.ts index a29a295..fc1b5b3 100644 --- a/tests/tools/init.spec.ts +++ b/tests/tools/init.spec.ts @@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; +import { mockLogger } from "../mocks"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -15,7 +16,7 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); @@ -36,10 +37,10 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Only one test target found, using it: testTargetApp (testTargetId)", ); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 3, "\n✨ Initialization complete!", ); @@ -56,7 +57,7 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId1", }); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 2, "\n✨ Initialization complete!", ); diff --git a/tests/tools/sync/git.spec.ts b/tests/tools/sync/git.spec.ts index 4dc35df..4b2a10c 100644 --- a/tests/tools/sync/git.spec.ts +++ b/tests/tools/sync/git.spec.ts @@ -7,6 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; +import { mockLogger } from "../../mocks"; vi.mock("simple-git"); @@ -17,30 +18,29 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - console.error = vi.fn(); + mockLogger.error.mockClear(); + mockLogger.warn.mockClear(); }); describe("getDefaultBranch", () => { it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockResolvedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockRejectedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); }); diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 0731b48..696d4bf 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; +import { mockLogger } from "../mocks"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -18,7 +19,7 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); describe("deleteTestCase via API", () => { @@ -36,7 +37,7 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); }); @@ -84,7 +85,7 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); await expect(fsPromises.access(filePath)).rejects.toThrow(); @@ -98,7 +99,7 @@ describe("test-cases", () => { testCaseId: "non-existent-id", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( `No test case with id non-existent-id found in folder ${tmpDir}`, ); expect(clientDELETE).not.toHaveBeenCalled(); diff --git a/tests/tools/test-targets.spec.ts b/tests/tools/test-targets.spec.ts index 92e0bcb..37dadc4 100644 --- a/tests/tools/test-targets.spec.ts +++ b/tests/tools/test-targets.spec.ts @@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; +import { mockLogger } from "../mocks"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -35,7 +36,7 @@ describe("push", () => { error: undefined, response: mock(), }); - console.log = vi.fn(); + mockLogger.info.mockClear(); }); it("pushes to main if on default branch", async () => { @@ -114,7 +115,7 @@ describe("push", () => { }); expect(client.POST).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Push cancelled."); + expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled."); }); }); }); diff --git a/tests/tools/yamlMutations/create.spec.ts b/tests/tools/yamlMutations/create.spec.ts index eadbc73..b1e46dd 100644 --- a/tests/tools/yamlMutations/create.spec.ts +++ b/tests/tools/yamlMutations/create.spec.ts @@ -18,6 +18,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -32,7 +33,7 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); @@ -110,7 +111,7 @@ describe("create", () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -118,7 +119,7 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully"); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index dcb928c..4799668 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -15,6 +15,7 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, + mockLogger, } from "../../mocks"; vi.mock("open"); @@ -29,7 +30,7 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - console.log = vi.fn(); + mockLogger.info.mockClear(); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( @@ -96,7 +97,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -108,7 +109,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully"); }); it.each([ From 3f5214c6f51092c72774670e318a17b378276b06 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Mon, 19 Jan 2026 14:59:02 +0100 Subject: [PATCH 02/11] pino-pretty is not production-ready diff --git c/pnpm-lock.yaml i/pnpm-lock.yaml index 619d022..31c08e7 100644 --- c/pnpm-lock.yaml +++ i/pnpm-lock.yaml @@ -35,9 +35,6 @@ importers: pino: specifier: 10.2.0 version: 10.2.0 - pino-pretty: - specifier: 13.1.3 - version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -950,9 +947,6 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -971,9 +965,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -1016,9 +1007,6 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1066,9 +1054,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - fast-copy@4.0.2: - resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1076,9 +1061,6 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1168,9 +1150,6 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1271,10 +1250,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1383,9 +1358,6 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1474,10 +1446,6 @@ packages: pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - pino-std-serializers@7.1.0: resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} @@ -1517,9 +1485,6 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1593,9 +1558,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1681,10 +1643,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@5.0.3: - resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} - engines: {node: '>=14.16'} - supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1911,9 +1869,6 @@ packages: engines: {node: '>=8'} hasBin: true - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2636,8 +2591,6 @@ snapshots: colorette@1.4.0: {} - colorette@2.0.20: {} - commander@14.0.2: {} commander@7.2.0: {} @@ -2652,8 +2605,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - dateformat@4.6.3: {} - debug@4.4.1: dependencies: ms: 2.1.3 @@ -2683,10 +2634,6 @@ snapshots: dependencies: jake: 10.9.4 - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2787,8 +2734,6 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - fast-copy@4.0.2: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2799,8 +2744,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} fastq@1.20.1: @@ -2890,8 +2833,6 @@ snapshots: has-flag@3.0.0: {} - help-me@5.0.0: {} - https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2973,8 +2914,6 @@ snapshots: jiti@2.6.1: {} - joycon@3.1.1: {} - js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -3069,10 +3008,6 @@ snapshots: on-exit-leak-free@2.1.2: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3202,22 +3137,6 @@ snapshots: dependencies: split2: 4.2.0 - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.2 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.3 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.0 - strip-json-comments: 5.0.3 - pino-std-serializers@7.1.0: {} pino@10.2.0: @@ -3260,11 +3179,6 @@ snapshots: process-warning@5.0.0: {} - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode.js@2.3.1: {} queue-microtask@1.2.3: {} @@ -3352,8 +3266,6 @@ snapshots: safer-buffer@2.1.2: {} - secure-json-parse@4.1.0: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3426,8 +3338,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@5.0.3: {} - supports-color@10.2.2: {} supports-color@5.5.0: @@ -3612,8 +3522,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wrappy@1.0.2: {} - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git c/src/logger.ts i/src/logger.ts index 946ed95..3181857 100644 --- c/src/logger.ts +++ i/src/logger.ts @@ -1,15 +1,5 @@ import pino from "pino"; -const isProduction = process.env.NODE_ENV === "production"; - export const logger = pino({ level: process.env.LOG_LEVEL || "info", - transport: isProduction - ? undefined - : { - target: "pino-pretty", - options: { - colorize: true, - }, - }, }); --- pnpm-lock.yaml | 92 -------------------------------------------------- src/logger.ts | 10 ------ 2 files changed, 102 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4318b3..4806f9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,6 @@ importers: pino: specifier: 10.2.0 version: 10.2.0 - pino-pretty: - specifier: 13.1.3 - version: 13.1.3 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -950,9 +947,6 @@ packages: colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} @@ -971,9 +965,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -1016,9 +1007,6 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1066,9 +1054,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - fast-copy@4.0.2: - resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1076,9 +1061,6 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1168,9 +1150,6 @@ packages: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1271,10 +1250,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -1383,9 +1358,6 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1474,10 +1446,6 @@ packages: pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - pino-std-serializers@7.1.0: resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} @@ -1517,9 +1485,6 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1593,9 +1558,6 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1681,10 +1643,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@5.0.3: - resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} - engines: {node: '>=14.16'} - supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1911,9 +1869,6 @@ packages: engines: {node: '>=8'} hasBin: true - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2636,8 +2591,6 @@ snapshots: colorette@1.4.0: {} - colorette@2.0.20: {} - commander@14.0.2: {} commander@7.2.0: {} @@ -2652,8 +2605,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - dateformat@4.6.3: {} - debug@4.4.1: dependencies: ms: 2.1.3 @@ -2683,10 +2634,6 @@ snapshots: dependencies: jake: 10.9.4 - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -2787,8 +2734,6 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - fast-copy@4.0.2: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -2799,8 +2744,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} fastq@1.20.1: @@ -2890,8 +2833,6 @@ snapshots: has-flag@3.0.0: {} - help-me@5.0.0: {} - https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -2973,8 +2914,6 @@ snapshots: jiti@2.6.1: {} - joycon@3.1.1: {} - js-levenshtein@1.1.6: {} js-tokens@4.0.0: {} @@ -3069,10 +3008,6 @@ snapshots: on-exit-leak-free@2.1.2: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3202,22 +3137,6 @@ snapshots: dependencies: split2: 4.2.0 - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.2 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.3 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.0 - strip-json-comments: 5.0.3 - pino-std-serializers@7.1.0: {} pino@10.2.0: @@ -3260,11 +3179,6 @@ snapshots: process-warning@5.0.0: {} - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode.js@2.3.1: {} queue-microtask@1.2.3: {} @@ -3352,8 +3266,6 @@ snapshots: safer-buffer@2.1.2: {} - secure-json-parse@4.1.0: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3426,8 +3338,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@5.0.3: {} - supports-color@10.2.2: {} supports-color@5.5.0: @@ -3612,8 +3522,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wrappy@1.0.2: {} - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git a/src/logger.ts b/src/logger.ts index 946ed95..3181857 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,15 +1,5 @@ import pino from "pino"; -const isProduction = process.env.NODE_ENV === "production"; - export const logger = pino({ level: process.env.LOG_LEVEL || "info", - transport: isProduction - ? undefined - : { - target: "pino-pretty", - options: { - colorize: true, - }, - }, }); From 2d2d41e75ffc014dfa0348d0645564fe08b3c4b6 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Mon, 19 Jan 2026 18:32:59 +0100 Subject: [PATCH 03/11] logtape? --- package.json | 2 + pnpm-lock.yaml | 113 +++++++++---------------------------------------- src/cli.ts | 24 ++++++++++- src/index.ts | 5 ++- src/logger.ts | 6 +-- 5 files changed, 51 insertions(+), 99 deletions(-) diff --git a/package.json b/package.json index 35813e4..3368597 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "author": "octomind", "license": "MIT", "dependencies": { + "@logtape/logtape": "2.0.0", + "@logtape/pretty": "2.0.0", "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4806f9e..22d5b5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@logtape/logtape': + specifier: 2.0.0 + version: 2.0.0 + '@logtape/pretty': + specifier: 2.0.0 + version: 2.0.0(@logtape/logtape@2.0.0) '@playwright/test': specifier: 1.57.0 version: 1.57.0 @@ -32,9 +38,6 @@ importers: otplib: specifier: 13.1.1 version: 13.1.1 - pino: - specifier: 10.2.0 - version: 10.2.0 shell-quote: specifier: 1.8.3 version: 1.8.3 @@ -489,6 +492,14 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@logtape/logtape@2.0.0': + resolution: {integrity: sha512-z9Hp44mIRXAzgxSyQfFQiRuJ78EMnZa6g43UCxyGOO3RgHjn/7q+5OhdbhypkeHjiJRPxv6RmRsyF0S+OOYWnA==} + + '@logtape/pretty@2.0.0': + resolution: {integrity: sha512-S92TWceFNA5ThnTAFsTeXPg0xwlEK6Ysro7L2hFm6L+ihmxLH7eqZb++ahRZoErCSHIAz4ZeBDW04hqdRQZ10w==} + peerDependencies: + '@logtape/logtape': ^2.0.0 + '@noble/hashes@2.0.1': resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} @@ -556,9 +567,6 @@ packages: '@otplib/uri@13.1.1': resolution: {integrity: sha512-UMdz/41JIKPLw6/VeExc3MJaCHZYEZXlF5WVyhtWT5nfzQKAcgp6HI8Tar0lDN+lXXKCjbGwlyCKOWCMAISNXg==} - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - '@playwright/test@1.57.0': resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} engines: {node: '>=18'} @@ -880,10 +888,6 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1354,10 +1358,6 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - onetime@2.0.1: resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} engines: {node: '>=4'} @@ -1443,16 +1443,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pino-abstract-transport@3.0.0: - resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@10.2.0: - resolution: {integrity: sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==} - hasBin: true - playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -1482,9 +1472,6 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -1492,9 +1479,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -1502,10 +1486,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - remeda@2.33.4: resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} @@ -1551,10 +1531,6 @@ packages: safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1587,17 +1563,10 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1654,10 +1623,6 @@ packages: tabtab@3.0.2: resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==} - thread-stream@4.0.0: - resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} - engines: {node: '>=20'} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -2119,6 +2084,12 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@logtape/logtape@2.0.0': {} + + '@logtape/pretty@2.0.0(@logtape/logtape@2.0.0)': + dependencies: + '@logtape/logtape': 2.0.0 + '@noble/hashes@2.0.1': {} '@nodelib/fs.scandir@2.1.5': @@ -2258,8 +2229,6 @@ snapshots: dependencies: '@otplib/core': 13.1.1 - '@pinojs/redact@0.4.0': {} - '@playwright/test@1.57.0': dependencies: playwright: 1.57.0 @@ -2535,8 +2504,6 @@ snapshots: async@3.2.6: {} - atomic-sleep@1.0.0: {} - balanced-match@1.0.2: {} bluebird@3.7.2: {} @@ -3006,8 +2973,6 @@ snapshots: obug@2.1.1: {} - on-exit-leak-free@2.1.2: {} - onetime@2.0.1: dependencies: mimic-fn: 1.2.0 @@ -3133,26 +3098,6 @@ snapshots: picomatch@4.0.3: {} - pino-abstract-transport@3.0.0: - dependencies: - split2: 4.2.0 - - pino-std-serializers@7.1.0: {} - - pino@10.2.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 4.0.0 - playwright-core@1.57.0: {} playwright@1.57.0: @@ -3177,14 +3122,10 @@ snapshots: process-nextick-args@2.0.1: {} - process-warning@5.0.0: {} - punycode.js@2.3.1: {} queue-microtask@1.2.3: {} - quick-format-unescaped@4.0.4: {} - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -3197,8 +3138,6 @@ snapshots: readdirp@5.0.0: {} - real-require@0.2.0: {} - remeda@2.33.4: {} require-from-string@2.0.2: {} @@ -3262,8 +3201,6 @@ snapshots: safe-buffer@5.1.2: {} - safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: {} shebang-command@2.0.0: @@ -3290,14 +3227,8 @@ snapshots: slash@5.1.0: {} - sonic-boom@4.2.0: - dependencies: - atomic-sleep: 1.0.0 - source-map-js@1.2.1: {} - split2@4.2.0: {} - stackback@0.0.2: {} std-env@3.10.0: {} @@ -3355,10 +3286,6 @@ snapshots: transitivePeerDependencies: - supports-color - thread-stream@4.0.0: - dependencies: - real-require: 0.2.0 - through@2.3.8: {} tinybench@2.9.0: {} diff --git a/src/cli.ts b/src/cli.ts index 1e8b77d..8e615ac 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,3 +1,5 @@ +import { configure, getConsoleSink } from "@logtape/logtape"; +import { getPrettyFormatter } from "@logtape/pretty"; import { Option } from "commander"; import { @@ -77,7 +79,27 @@ const createCommandWithCommonOptions = ( .option("-j, --json", "Output raw JSON response") as CompletableCommand; }; -export const buildCmd = (): CompletableCommand => { +export const buildCmd = async (): Promise => { + await configure({ + sinks: { + console: getConsoleSink({ + formatter: getPrettyFormatter({ + timestamp: "none", + categoryStyle: "dim", + messageColor: "white", + }), + }), + }, + loggers: [ + { + category: ["logtape", "meta"], + sinks: ["console"], + lowestLevel: "warning", + }, + { category: "octomind", lowestLevel: "debug", sinks: ["console"] }, + ], + }); + const program = new CompletableCommand(); program diff --git a/src/index.ts b/src/index.ts index 05df6f4..fa169c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ #!/usr/bin/env node import { buildCmd } from "./cli"; +import { logger } from "./logger"; -buildCmd().parse(); +void buildCmd() + .then((res) => res.parse()) + .catch(logger.error); diff --git a/src/logger.ts b/src/logger.ts index 3181857..ebd4dcf 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,3 @@ -import pino from "pino"; +import { getLogger } from "@logtape/logtape"; -export const logger = pino({ - level: process.env.LOG_LEVEL || "info", -}); +export const logger = getLogger("octomind"); From 1333c13f6e9434f2b2fa1a1bcde3672d358e0ffd Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 09:54:16 +0100 Subject: [PATCH 04/11] fix tests --- tests/cli.spec.ts | 4 ++-- tests/config.spec.ts | 3 +-- tests/mocks.ts | 14 +------------- tests/setup.ts | 24 ++++++++++++++---------- tests/tools/init.spec.ts | 4 +--- tests/tools/sync/git.spec.ts | 4 +--- tests/tools/test-cases.spec.ts | 3 +-- tests/tools/test-targets.spec.ts | 3 +-- tests/tools/yamlMutations/create.spec.ts | 8 ++++---- tests/tools/yamlMutations/edit.spec.ts | 8 ++++---- 10 files changed, 30 insertions(+), 45 deletions(-) diff --git a/tests/cli.spec.ts b/tests/cli.spec.ts index 80d01f3..1007942 100644 --- a/tests/cli.spec.ts +++ b/tests/cli.spec.ts @@ -16,8 +16,8 @@ vi.mock("../src/config", () => ({ describe("CLI", () => { let program: Command; - beforeEach(() => { - program = buildCmd(); + beforeEach(async () => { + program = await buildCmd(); program.exitOverride((err) => { throw err; }); diff --git a/tests/config.spec.ts b/tests/config.spec.ts index 0856939..82181cf 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -5,14 +5,13 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; -import { mockLogger } from "./mocks"; +import { mockLogger } from "./setup"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - mockLogger.error.mockClear(); resetConfig(); }); diff --git a/tests/mocks.ts b/tests/mocks.ts index 02e672c..756383c 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,4 +1,5 @@ import { vi } from "vitest"; + import { EnvironmentResponse, TestCaseResponse, @@ -6,19 +7,6 @@ import { } from "../src/tools"; import { SyncDataByStableId, SyncTestCase } from "../src/tools/sync/types"; -export const mockLogger = { - info: vi.fn(), - error: vi.fn(), - warn: vi.fn(), - debug: vi.fn(), - log: vi.fn(), - success: vi.fn(), -}; - -vi.mock("../src/logger", () => ({ - logger: mockLogger, -})); - export const createMockSyncTestCase = ( overrides?: Partial, ): SyncTestCase => ({ diff --git a/tests/setup.ts b/tests/setup.ts index aa85cd5..5645c57 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,7 +1,5 @@ import { beforeEach, vi } from "vitest"; -import { mockLogger } from "./mocks"; - vi.mock("ora", () => ({ default: vi.fn(() => ({ start: vi.fn().mockReturnThis(), @@ -10,11 +8,17 @@ vi.mock("ora", () => ({ })), })); -beforeEach(() => { - mockLogger.info.mockClear(); - mockLogger.warn.mockClear(); - mockLogger.error.mockClear(); - mockLogger.debug.mockClear(); - mockLogger.log.mockClear(); - mockLogger.success.mockClear(); -}); +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, +})); + +vi.mock("@logtape/logtape"); diff --git a/tests/tools/init.spec.ts b/tests/tools/init.spec.ts index fc1b5b3..5da0f4a 100644 --- a/tests/tools/init.spec.ts +++ b/tests/tools/init.spec.ts @@ -4,7 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; -import { mockLogger } from "../mocks"; +import { mockLogger } from "../setup"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -16,8 +16,6 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - mockLogger.info.mockClear(); - loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); diff --git a/tests/tools/sync/git.spec.ts b/tests/tools/sync/git.spec.ts index 4b2a10c..d4a6080 100644 --- a/tests/tools/sync/git.spec.ts +++ b/tests/tools/sync/git.spec.ts @@ -7,7 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; -import { mockLogger } from "../../mocks"; +import { mockLogger } from "../../setup"; vi.mock("simple-git"); @@ -18,8 +18,6 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - mockLogger.error.mockClear(); - mockLogger.warn.mockClear(); }); describe("getDefaultBranch", () => { diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 696d4bf..98d4bc0 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -8,7 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; -import { mockLogger } from "../mocks"; +import { mockLogger } from "../setup"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -19,7 +19,6 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - mockLogger.info.mockClear(); }); describe("deleteTestCase via API", () => { diff --git a/tests/tools/test-targets.spec.ts b/tests/tools/test-targets.spec.ts index 37dadc4..07770b6 100644 --- a/tests/tools/test-targets.spec.ts +++ b/tests/tools/test-targets.spec.ts @@ -6,7 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; -import { mockLogger } from "../mocks"; +import { mockLogger } from "../setup"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -36,7 +36,6 @@ describe("push", () => { error: undefined, response: mock(), }); - mockLogger.info.mockClear(); }); it("pushes to main if on default branch", async () => { diff --git a/tests/tools/yamlMutations/create.spec.ts b/tests/tools/yamlMutations/create.spec.ts index b1e46dd..f9fcc03 100644 --- a/tests/tools/yamlMutations/create.spec.ts +++ b/tests/tools/yamlMutations/create.spec.ts @@ -18,8 +18,8 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, - mockLogger, } from "../../mocks"; +import { mockLogger } from "../../setup"; vi.mock("open"); vi.mock("../../../src/helpers"); @@ -33,8 +33,6 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - mockLogger.info.mockClear(); - vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(draftPush).mockResolvedValue( @@ -119,7 +117,9 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(mockLogger.info).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith( + "Created test case successfully", + ); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index 4799668..a70d9d5 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -15,8 +15,8 @@ import { waitForLocalChangesToBeFinished } from "../../../src/tools/yamlMutation import { createMockDraftPushResponse, createMockSyncTestCase, - mockLogger, } from "../../mocks"; +import { mockLogger } from "../../setup"; vi.mock("open"); vi.mock("../../../src/helpers"); @@ -30,8 +30,6 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - mockLogger.info.mockClear(); - vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( "/mock/.octomind/test.yaml", @@ -109,7 +107,9 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(mockLogger.info).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith( + "Edited test case successfully", + ); }); it.each([ From b790900744b7d84d38e25233b56382a5eda77cae Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:07:33 +0100 Subject: [PATCH 05/11] finish it --- src/cli.ts | 27 ++++++-------------- src/completion.ts | 6 ++--- src/config.ts | 7 ++---- src/logger.ts | 58 ++++++++++++++++++++++++++++++++++++++++++- src/tools/client.ts | 4 +-- src/tools/init.ts | 2 +- src/tools/sync/git.ts | 24 ++++++++---------- 7 files changed, 83 insertions(+), 45 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 8e615ac..83f7533 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,4 +1,8 @@ -import { configure, getConsoleSink } from "@logtape/logtape"; +import { + configure, + getAnsiColorFormatter, + getConsoleSink, +} from "@logtape/logtape"; import { getPrettyFormatter } from "@logtape/pretty"; import { Option } from "commander"; @@ -15,6 +19,7 @@ import { } from "./completion"; import { executeLocalTestCases, runDebugtopus } from "./debugtopus"; import { resolveTestTargetId } from "./helpers"; +import { configureLogger } from "./logger"; import { startPrivateLocationWorker, stopPLW } from "./plw"; import { batchGeneration, @@ -80,25 +85,7 @@ const createCommandWithCommonOptions = ( }; export const buildCmd = async (): Promise => { - await configure({ - sinks: { - console: getConsoleSink({ - formatter: getPrettyFormatter({ - timestamp: "none", - categoryStyle: "dim", - messageColor: "white", - }), - }), - }, - loggers: [ - { - category: ["logtape", "meta"], - sinks: ["console"], - lowestLevel: "warning", - }, - { category: "octomind", lowestLevel: "debug", sinks: ["console"] }, - ], - }); + await configureLogger(); const program = new CompletableCommand(); diff --git a/src/completion.ts b/src/completion.ts index 3e0b0ac..e0375bc 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -3,8 +3,8 @@ import { parse } from "shell-quote"; import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; -import { logger } from "./logger"; import { loadConfig } from "./config"; +import { logger } from "./logger"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; import { getTestCases } from "./tools/test-cases"; @@ -194,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => logger.error({ err }, "INSTALL ERROR")); + }).catch((err) => logger.error("INSTALL ERROR", { err })); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => logger.error({ err }, "UNINSTALL ERROR")); + }).catch((err) => logger.error("UNINSTALL ERROR", { err })); }; diff --git a/src/config.ts b/src/config.ts index bf42479..79ad407 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,10 +47,7 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - logger.error( - { err: error as Error }, - "❌ Error parsing configuration", - ); + logger.error("❌ Error parsing configuration", { error }); process.exit(1); } return {}; @@ -64,7 +61,7 @@ export async function saveConfig(newConfig: Config): Promise { logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - logger.error({ err: error as Error }, "❌ Error saving configuration"); + logger.error("❌ Error saving configuration", { error }); process.exit(1); } } diff --git a/src/logger.ts b/src/logger.ts index ebd4dcf..b81c3d4 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,3 +1,59 @@ -import { getLogger } from "@logtape/logtape"; +import { + configure, + FormattedValues, + getAnsiColorFormatter, + getConsoleSink, + getLogger, + getTextFormatter, + LogLevel, +} from "@logtape/logtape"; +import { getPrettyFormatter } from "@logtape/pretty"; export const logger = getLogger("octomind"); + +const ansiColors = { + black: "\x1B[30m", + red: "\x1B[31m", + green: "\x1B[32m", + yellow: "\x1B[33m", + blue: "\x1B[34m", + magenta: "\x1B[35m", + cyan: "\x1B[36m", + white: "\x1B[37m", +}; + +const levelColors: Record< + LogLevel | string, + (typeof ansiColors)[keyof typeof ansiColors] +> = { + debug: ansiColors.white, + info: ansiColors.white, + warning: ansiColors.yellow, + error: ansiColors.red, + trace: ansiColors.white, + fatal: ansiColors.red, +}; + +export const configureLogger = async (): Promise => + configure({ + sinks: { + console: getConsoleSink({ + formatter: getTextFormatter({ + timestamp: "disabled", + level: "full", + + format(values: FormattedValues): string { + return levelColors[values.level] + values.message; + }, + }), + }), + }, + loggers: [ + { + category: ["logtape", "meta"], + sinks: ["console"], + lowestLevel: "warning", + }, + { category: "octomind", lowestLevel: "debug", sinks: ["console"] }, + ], + }); diff --git a/src/tools/client.ts b/src/tools/client.ts index 6e5146c..2c2ad33 100644 --- a/src/tools/client.ts +++ b/src/tools/client.ts @@ -66,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - logger.error(error); + logger.error("error", { error }); process.exit(1); }, }; @@ -78,7 +78,7 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - logger.error(error); + logger.error("error", { error }); if (typeof error === "string" && error.startsWith("403")) { logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", diff --git a/src/tools/init.ts b/src/tools/init.ts index 57ad8b2..6470e7c 100644 --- a/src/tools/init.ts +++ b/src/tools/init.ts @@ -102,7 +102,7 @@ export const init = async (options: { logger.info("\n✨ Initialization complete!"); } catch (error) { - logger.error({ err: error as Error }, "❌ Error during initialization"); + logger.error("❌ Error during initialization", { error }); process.exit(1); } }; diff --git a/src/tools/sync/git.ts b/src/tools/sync/git.ts index 3a7fc1f..e18dd43 100644 --- a/src/tools/sync/git.ts +++ b/src/tools/sync/git.ts @@ -34,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - logger.error({ err: error }, "Failed to parse git remote"); + logger.error("Failed to parse git remote", { error }); return {}; } }; @@ -59,10 +59,10 @@ export const getDefaultBranch = async ( if (symbolicRefBranch) { return symbolicRefBranch; } - } catch (e) { + } catch (error) { logger.warn( - { err: e }, "could not identify symbolic ref, falling back to origin parsing", + { error }, ); } } @@ -79,11 +79,10 @@ export const getDefaultBranch = async ( return originDefaultBranch?.groups?.["branchName"] ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; - } catch (e) { - logger.warn( - { err: e }, - "could not identify default branch, falling back to 'main'", - ); + } catch (error) { + logger.warn("could not identify default branch, falling back to 'main'", { + error, + }); } return FALLBACK_DEFAULT_BRANCH; @@ -108,11 +107,10 @@ export const getGitContext = async (): Promise => { defaultBranch, }; return ctx; - } catch (e) { - logger.warn( - { err: e }, - "could not identify git context, falling back to undefined", - ); + } catch (error) { + logger.warn("could not identify git context, falling back to undefined", { + error, + }); return undefined; } }; From 5e9eb9b719c4dbe3caa4badee94300dbbc792d6b Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:11:38 +0100 Subject: [PATCH 06/11] colors for now --- src/logger.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index b81c3d4..b154539 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,13 +1,11 @@ import { configure, FormattedValues, - getAnsiColorFormatter, getConsoleSink, getLogger, getTextFormatter, LogLevel, } from "@logtape/logtape"; -import { getPrettyFormatter } from "@logtape/pretty"; export const logger = getLogger("octomind"); @@ -20,17 +18,18 @@ const ansiColors = { magenta: "\x1B[35m", cyan: "\x1B[36m", white: "\x1B[37m", + none: "", }; const levelColors: Record< LogLevel | string, (typeof ansiColors)[keyof typeof ansiColors] > = { - debug: ansiColors.white, - info: ansiColors.white, + trace: ansiColors.none, + debug: ansiColors.none, + info: ansiColors.none, warning: ansiColors.yellow, error: ansiColors.red, - trace: ansiColors.white, fatal: ansiColors.red, }; From 8aa77724f3bbc7e103fb6d8457aed994439eee7b Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:14:37 +0100 Subject: [PATCH 07/11] review --- src/logger.ts | 3 +-- tests/mocks.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index b154539..07f1d91 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -10,14 +10,13 @@ import { export const logger = getLogger("octomind"); const ansiColors = { - black: "\x1B[30m", red: "\x1B[31m", green: "\x1B[32m", yellow: "\x1B[33m", blue: "\x1B[34m", magenta: "\x1B[35m", cyan: "\x1B[36m", - white: "\x1B[37m", + // use native terminal colors none: "", }; diff --git a/tests/mocks.ts b/tests/mocks.ts index 756383c..42342b8 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,5 +1,3 @@ -import { vi } from "vitest"; - import { EnvironmentResponse, TestCaseResponse, From 15552c4161cb5355911ffcbb388b0537de01488c Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:17:22 +0100 Subject: [PATCH 08/11] syntax --- src/logger.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 07f1d91..c2baaba 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -39,8 +39,7 @@ export const configureLogger = async (): Promise => formatter: getTextFormatter({ timestamp: "disabled", level: "full", - - format(values: FormattedValues): string { + format: (values: FormattedValues): string => { return levelColors[values.level] + values.message; }, }), From 05300445dce2794593189ffb1de5362c40395e51 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:19:14 +0100 Subject: [PATCH 09/11] - pretty, format --- package.json | 9 +++++++-- pnpm-lock.yaml | 12 ------------ src/tools/private-locations.ts | 4 +++- src/tools/update.ts | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 3368597..cfb82cc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,13 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316", - "files": ["dist", "src", "README.md", "LICENSE", "package.json"], + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], "engines": { "node": ">=22.0.0" }, @@ -39,7 +45,6 @@ "license": "MIT", "dependencies": { "@logtape/logtape": "2.0.0", - "@logtape/pretty": "2.0.0", "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22d5b5a..ae57003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@logtape/logtape': specifier: 2.0.0 version: 2.0.0 - '@logtape/pretty': - specifier: 2.0.0 - version: 2.0.0(@logtape/logtape@2.0.0) '@playwright/test': specifier: 1.57.0 version: 1.57.0 @@ -495,11 +492,6 @@ packages: '@logtape/logtape@2.0.0': resolution: {integrity: sha512-z9Hp44mIRXAzgxSyQfFQiRuJ78EMnZa6g43UCxyGOO3RgHjn/7q+5OhdbhypkeHjiJRPxv6RmRsyF0S+OOYWnA==} - '@logtape/pretty@2.0.0': - resolution: {integrity: sha512-S92TWceFNA5ThnTAFsTeXPg0xwlEK6Ysro7L2hFm6L+ihmxLH7eqZb++ahRZoErCSHIAz4ZeBDW04hqdRQZ10w==} - peerDependencies: - '@logtape/logtape': ^2.0.0 - '@noble/hashes@2.0.1': resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} @@ -2086,10 +2078,6 @@ snapshots: '@logtape/logtape@2.0.0': {} - '@logtape/pretty@2.0.0(@logtape/logtape@2.0.0)': - dependencies: - '@logtape/logtape': 2.0.0 - '@noble/hashes@2.0.1': {} '@nodelib/fs.scandir@2.1.5': diff --git a/src/tools/private-locations.ts b/src/tools/private-locations.ts index 7c0ba62..a399778 100644 --- a/src/tools/private-locations.ts +++ b/src/tools/private-locations.ts @@ -36,7 +36,9 @@ export const registerLocation = async ( return; } - logger.info(`Registration result: ${response.success ? "Success" : "Failed"}`); + logger.info( + `Registration result: ${response.success ? "Success" : "Failed"}`, + ); }; export const unregisterLocation = async ( diff --git a/src/tools/update.ts b/src/tools/update.ts index f63daa9..557bc7a 100644 --- a/src/tools/update.ts +++ b/src/tools/update.ts @@ -44,5 +44,5 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - logger.info(`✔ update complete: ${newVersion}`); + logger.info(`✔ update complete: ${newVersion}`); }; From 0f14072723fa8441aac432d4297f0d0d91afecc3 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:21:15 +0100 Subject: [PATCH 10/11] + await --- package.json | 8 +------- scripts/generate-docs.ts | 2 +- src/cli.ts | 6 ------ 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index cfb82cc..ebe33d6 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,7 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316", - "files": [ - "dist", - "src", - "README.md", - "LICENSE", - "package.json" - ], + "files": ["dist", "src", "README.md", "LICENSE", "package.json"], "engines": { "node": ">=22.0.0" }, diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index c859a91..6ac4c9a 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -105,7 +105,7 @@ export function generateCommandDocs(command: Command, level = 1): string { */ async function main() { try { - const program = buildCmd(); + const program = await buildCmd(); const markdown = generateCommandDocs(program); diff --git a/src/cli.ts b/src/cli.ts index 83f7533..2577f10 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,9 +1,3 @@ -import { - configure, - getAnsiColorFormatter, - getConsoleSink, -} from "@logtape/logtape"; -import { getPrettyFormatter } from "@logtape/pretty"; import { Option } from "commander"; import { From 711db0c1f954a7ab226cdc87cc5c36f42ff6315b Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 21 Jan 2026 13:22:00 +0100 Subject: [PATCH 11/11] fix --- tests/setup.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/setup.ts b/tests/setup.ts index 5645c57..64947ad 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -19,6 +19,7 @@ export const mockLogger = { vi.mock("../src/logger", () => ({ logger: mockLogger, + configureLogger: vi.fn(), })); vi.mock("@logtape/logtape");