diff --git a/package-lock.json b/package-lock.json index be559ff..525bfbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "better-commits", - "version": "1.24.0", + "version": "1.26.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "better-commits", - "version": "1.24.0", + "version": "1.26.0", "license": "MIT", "dependencies": { "@bomb.sh/args": "^0.3.1", @@ -27,7 +27,7 @@ "knip": "^6.15.0", "semantic-release": "^25.0.3", "tsdown": "^0.21.10", - "tsx": "^3.12.3", + "tsx": "^4.23.12", "typescript": "^5.9.3", "valibot": "1.3.1", "vitest": "^3.2.4" @@ -240,46 +240,27 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild-kit/cjs-loader": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.4.tgz", - "integrity": "sha512-NfsJX4PdzhwSkfJukczyUiZGc7zNNWZcEAyqeISpDnn0PTfzMJR1aR8xAIPskBejIxBJbIgCCMzbaYa9SXepIg==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.2.3", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "deprecated": "Merged into tsx: https://tsx.is", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "deprecated": "Merged into tsx: https://tsx.is", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -290,13 +271,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -307,13 +288,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -324,13 +305,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -341,13 +322,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -358,13 +339,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -375,13 +356,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -392,13 +373,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -409,13 +390,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -426,13 +407,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -443,13 +424,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -460,13 +441,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -477,13 +458,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -494,13 +475,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -511,13 +492,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -528,13 +509,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -545,13 +526,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -562,13 +560,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -579,13 +594,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -596,13 +628,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -613,13 +645,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -630,13 +662,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -647,7 +679,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { @@ -2901,13 +2933,6 @@ "node": ">=8" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3682,9 +3707,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3692,31 +3717,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -8218,17 +8247,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -8425,6 +8443,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -8748,21 +8767,22 @@ "optional": true }, "node_modules/tsx": { - "version": "3.12.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.3.tgz", - "integrity": "sha512-Wc5BFH1xccYTXaQob+lEcimkcb/Pq+0en2s+ruiX0VEIC80nV7/0s7XRahx8NnsoCnpCVUPz8wrqVSPi760LkA==", + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", "dev": true, "license": "MIT", "dependencies": { - "@esbuild-kit/cjs-loader": "^2.4.2", - "@esbuild-kit/core-utils": "^3.0.0", - "@esbuild-kit/esm-loader": "^2.5.5" + "esbuild": "~0.28.0" }, "bin": { - "tsx": "dist/cli.js" + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" } }, "node_modules/tunnel": { @@ -8791,7 +8811,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10663,187 +10683,185 @@ "tslib": "^2.4.0" } }, - "@esbuild-kit/cjs-loader": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.4.tgz", - "integrity": "sha512-NfsJX4PdzhwSkfJukczyUiZGc7zNNWZcEAyqeISpDnn0PTfzMJR1aR8xAIPskBejIxBJbIgCCMzbaYa9SXepIg==", + "@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "dev": true, - "requires": { - "@esbuild-kit/core-utils": "^3.2.3", - "get-tsconfig": "^4.7.0" - } - }, - "@esbuild-kit/core-utils": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", - "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", - "dev": true, - "requires": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "@esbuild-kit/esm-loader": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", - "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", - "dev": true, - "requires": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } + "optional": true }, "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "dev": true, + "optional": true + }, + "@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "dev": true, "optional": true }, @@ -12152,12 +12170,6 @@ "fill-range": "^7.1.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, "cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -12659,33 +12671,37 @@ "dev": true }, "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "escalade": { @@ -15694,16 +15710,6 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -15861,7 +15867,8 @@ "tagged-tag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true }, "temp-dir": { "version": "2.0.0", @@ -16050,15 +16057,13 @@ "optional": true }, "tsx": { - "version": "3.12.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.3.tgz", - "integrity": "sha512-Wc5BFH1xccYTXaQob+lEcimkcb/Pq+0en2s+ruiX0VEIC80nV7/0s7XRahx8NnsoCnpCVUPz8wrqVSPi760LkA==", + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", "dev": true, "requires": { - "@esbuild-kit/cjs-loader": "^2.4.2", - "@esbuild-kit/core-utils": "^3.0.0", - "@esbuild-kit/esm-loader": "^2.5.5", - "fsevents": "~2.3.2" + "esbuild": "~0.28.0", + "fsevents": "~2.3.3" } }, "tunnel": { @@ -16077,7 +16082,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true + "dev": true }, "uglify-js": { "version": "3.19.3", diff --git a/package.json b/package.json index 346c9bd..997f465 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "knip": "^6.15.0", "semantic-release": "^25.0.3", "tsdown": "^0.21.10", - "tsx": "^3.12.3", + "tsx": "^4.23.12", "typescript": "^5.9.3", "valibot": "1.3.1", "vitest": "^3.2.4" diff --git a/src/branch-args.test.ts b/src/branch-args.test.ts index 5ca26cd..89fcb4c 100644 --- a/src/branch-args.test.ts +++ b/src/branch-args.test.ts @@ -82,5 +82,23 @@ describe("parse_branch_runtime_flags", () => { expect(parsed.git_args).toBe( "--git-dir=/tmp/repo/.git --work-tree=/tmp/repo", ); + expect(parsed.git_options).toEqual([ + "--git-dir=/tmp/repo/.git", + "--work-tree=/tmp/repo", + ]); + }); + + it("preserves spaces in git-dir and work-tree options", () => { + const parsed = parse_branch_runtime_flags([ + "--git-dir", + "/tmp/my repo/.git", + "--work-tree", + "/tmp/my repo", + ]); + + expect(parsed.git_options).toEqual([ + "--git-dir=/tmp/my repo/.git", + "--work-tree=/tmp/my repo", + ]); }); }); diff --git a/src/branch-args.ts b/src/branch-args.ts index 5b9a9a4..4d50771 100644 --- a/src/branch-args.ts +++ b/src/branch-args.ts @@ -8,6 +8,7 @@ type ParsedRuntimeFlags = { help: boolean; version: boolean; git_args: string; + git_options: string[]; no_interactive: boolean; dry_run: boolean; branch_state: Partial; @@ -59,6 +60,10 @@ class BranchFlags { return this.#runtime.git_args; } + get git_options(): string[] { + return this.#runtime.git_options; + } + get branch_state(): Partial { return this.#runtime.branch_state; } @@ -91,19 +96,28 @@ export function parse_branch_runtime_flags(argv: string[]): ParsedRuntimeFlags { } }); + const git_options = get_git_options( + parsed["git-dir"], + parsed["work-tree"], + ); + return { help: parsed["help"] === true, version: parsed["version"] === true, - git_args: get_git_args(parsed["git-dir"], parsed["work-tree"]), + git_args: git_options.join(" "), + git_options, no_interactive: parsed["interactive"] === false, dry_run: parsed["dry-run"] === true, branch_state: branch_state, }; } -function get_git_args( +function get_git_options( git_dir: string | undefined, work_tree: string | undefined, -): string { - return `${git_dir ? `--git-dir=${git_dir}` : ""} ${work_tree ? `--work-tree=${work_tree}` : ""}`.trim(); +): string[] { + return [ + ...(git_dir ? [`--git-dir=${git_dir}`] : []), + ...(work_tree ? [`--work-tree=${work_tree}`] : []), + ]; } diff --git a/src/branch-help.ts b/src/branch-help.ts index 4f46f1e..884ff3a 100644 --- a/src/branch-help.ts +++ b/src/branch-help.ts @@ -1,4 +1,4 @@ -import { execSync } from "child_process"; +import { execFileSync } from "child_process"; import { InferOutput } from "valibot"; import { branch_flags } from "./branch-args"; import { get_package_version } from "./utils"; @@ -56,17 +56,17 @@ export function print_help_text( let branch = "(none)"; try { branch = - execSync(`git ${branch_flags.git_args} branch --show-current`, { - stdio: "pipe", - }) + execFileSync( + "git", + [...branch_flags.git_options, "branch", "--show-current"], + { stdio: "pipe" }, + ) .toString() .trim() || "(none)"; - } catch { - // noop - } + } catch {} const inferred_type = - infer_type_from_git(config.commit_type.options, branch_flags.git_args) || + infer_type_from_git(config.commit_type.options, branch_flags.git_options) || "Unknown"; const inferred_ticket = config.check_ticket.infer_ticket ? infer_ticket_from_git( @@ -74,13 +74,13 @@ export function print_help_text( append_hashtag: config.check_ticket.append_hashtag, prepend_hashtag: config.check_ticket.prepend_hashtag, }, - branch_flags.git_args, + branch_flags.git_options, ) || "Unknown" : "Infer Disabled"; const inferred_scope = config.commit_scope.infer_scope_from_branch ? infer_scope_from_git( config.commit_scope.options, - branch_flags.git_args, + branch_flags.git_options, ) || "Unknown" : "Infer Disabled"; diff --git a/src/branch.ts b/src/branch.ts index b9c50b2..8a585ca 100644 --- a/src/branch.ts +++ b/src/branch.ts @@ -44,7 +44,7 @@ const promptCtors: PromptCtor[] = [ const { config, config_source } = load_setup( " better-branch ", - branch_flags.git_args, + branch_flags.git_options, ); main(config, config_source); @@ -53,7 +53,7 @@ async function main( config: InferOutput, config_source: ConfigSource, ) { - chdir(get_git_root(branch_flags.git_args)); + chdir(get_git_root(branch_flags.git_options)); if (branch_flags.version) { const version = get_package_version(); @@ -77,7 +77,7 @@ async function main( } else { p.log.error(`Failed to validate branch input: ${err}`); } - process.exit(0); + process.exit(1); } } diff --git a/src/prompts/branch-confirm.prompt.test.ts b/src/prompts/branch-confirm.prompt.test.ts index 598540d..8817ac4 100644 --- a/src/prompts/branch-confirm.prompt.test.ts +++ b/src/prompts/branch-confirm.prompt.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { parse } from "valibot"; import { BranchState, Config } from "../valibot-state"; @@ -12,13 +12,18 @@ const sq = (value: string): string => `'${value.replaceAll("'", `'\\''`)}'`; const mocked = vi.hoisted(() => ({ dry_run: true, + git_options: [] as string[], + execFileSync: vi.fn(), execSync: vi.fn(), + spawnSync: vi.fn(), info: vi.fn(), error: vi.fn(), })); vi.mock("child_process", () => ({ + execFileSync: mocked.execFileSync, execSync: mocked.execSync, + spawnSync: mocked.spawnSync, })); vi.mock("@clack/prompts", () => ({ @@ -41,6 +46,9 @@ vi.mock("picocolors", () => ({ vi.mock("../branch-args", () => ({ branch_flags: { git_args: "", + get git_options() { + return mocked.git_options; + }, get dry_run() { return mocked.dry_run; }, @@ -56,9 +64,25 @@ vi.mock("../utils", async () => { }); describe("BranchConfirmPrompt", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + beforeEach(() => { mocked.dry_run = true; + mocked.git_options = []; + mocked.execFileSync.mockReset(); + mocked.execFileSync.mockImplementation((_file, args: string[]) => { + if (args.includes("show-ref")) throw new Error("branch missing"); + return Buffer.from(""); + }); mocked.execSync.mockReset(); + mocked.spawnSync.mockReset(); + mocked.spawnSync.mockImplementation((_file, args: string[]) => ({ + status: 0, + stdout: args.at(-1), + stderr: "", + })); mocked.info.mockReset(); mocked.error.mockReset(); }); @@ -85,15 +109,19 @@ describe("BranchConfirmPrompt", () => { await new BranchConfirmPrompt(config, branch_state, {} as never).run(); - expect(mocked.execSync).toHaveBeenCalledTimes(1); - expect(mocked.execSync).toHaveBeenCalledWith( - "git show-ref feat/TAC-123-cli-add-parser", - { - encoding: "utf-8", - }, + expect(mocked.execFileSync).toHaveBeenCalledTimes(1); + expect(mocked.execFileSync).toHaveBeenCalledWith( + "git", + [ + "show-ref", + "--verify", + "--quiet", + "refs/heads/feat/TAC-123-cli-add-parser", + ], + { encoding: "utf-8" }, ); expect(mocked.info).toHaveBeenCalledWith( - "Dry run: git worktree add ../worktrees/repo-TAC-123-add-parser -b feat/TAC-123-cli-add-parser", + "Dry run: git worktree add -b feat/TAC-123-cli-add-parser ../worktrees/repo-TAC-123-add-parser", ); }); @@ -124,12 +152,12 @@ describe("BranchConfirmPrompt", () => { await new BranchConfirmPrompt(config, branch_state, {} as never).run(); // Only the show-ref check runs a real command; everything else is dry-run logged. - expect(mocked.execSync).toHaveBeenCalledTimes(1); + expect(mocked.execFileSync).toHaveBeenCalledTimes(1); expect(mocked.info).toHaveBeenCalledWith( `Dry run: echo pre ${sq("TAC-123")}`, ); expect(mocked.info).toHaveBeenCalledWith( - "Dry run: git checkout -b feat/TAC-123-cli-add-parser", + "Dry run: git checkout -b feat/TAC-123-cli-add-parser", ); expect(mocked.info).toHaveBeenCalledWith( `Dry run: echo post ${sq("feat")}/${sq("TAC-123")}`, @@ -327,4 +355,122 @@ describe("BranchConfirmPrompt", () => { { stdio: "inherit" }, ); }); + + it("passes git-dir and work-tree as literal arguments", async () => { + mocked.dry_run = false; + mocked.git_options = [ + "--git-dir=/tmp/my repo/.git", + "--work-tree=/tmp/my repo", + ]; + const { BranchConfirmPrompt } = await import("./branch-confirm.prompt"); + const config = parse(Config, {}); + const branch_state = parse(BranchState, { + type: "feat", + description: "safe-branch", + checkout: "branch", + }); + + await new BranchConfirmPrompt(config, branch_state, {} as never).run(); + + expect(mocked.execFileSync).toHaveBeenNthCalledWith( + 2, + "git", + [ + "--git-dir=/tmp/my repo/.git", + "--work-tree=/tmp/my repo", + "checkout", + "-b", + "feat/safe-branch", + ], + { stdio: "inherit" }, + ); + }); + + it("exits non-zero when branch validation fails", async () => { + mocked.spawnSync.mockReturnValue({ + status: 128, + stdout: "", + stderr: "fatal: 'feat/a..b' is not a valid branch name", + }); + const exit = vi + .spyOn(process, "exit") + .mockImplementation((code): never => { + throw new Error(`exit ${code}`); + }); + const { BranchConfirmPrompt } = await import("./branch-confirm.prompt"); + const config = parse(Config, {}); + const branch_state = parse(BranchState, { + type: "feat", + description: "a..b", + checkout: "branch", + }); + + await expect( + new BranchConfirmPrompt(config, branch_state, {} as never).run(), + ).rejects.toThrow("exit 1"); + + expect(exit).toHaveBeenCalledWith(1); + expect(mocked.execFileSync).not.toHaveBeenCalled(); + exit.mockRestore(); + }); + + it("exits non-zero when checkout fails", async () => { + mocked.dry_run = false; + mocked.execFileSync.mockImplementation(() => { + throw new Error("checkout failed"); + }); + const exit = vi + .spyOn(process, "exit") + .mockImplementation((code): never => { + throw new Error(`exit ${code}`); + }); + const { BranchConfirmPrompt } = await import("./branch-confirm.prompt"); + const config = parse(Config, {}); + const branch_state = parse(BranchState, { + type: "feat", + description: "safe-branch", + checkout: "branch", + }); + + await expect( + new BranchConfirmPrompt(config, branch_state, {} as never).run(), + ).rejects.toThrow("exit 1"); + + expect(exit).toHaveBeenCalledWith(1); + expect(mocked.error).toHaveBeenCalledWith( + "Failed to checkout branch 'feat/safe-branch'", + ); + exit.mockRestore(); + }); + + it("exits non-zero when worktree creation fails", async () => { + mocked.dry_run = false; + mocked.execFileSync.mockImplementation(() => { + throw new Error("worktree failed"); + }); + const exit = vi + .spyOn(process, "exit") + .mockImplementation((code): never => { + throw new Error(`exit ${code}`); + }); + const { BranchConfirmPrompt } = await import("./branch-confirm.prompt"); + const config = parse(Config, { + worktrees: { base_path: "../worktrees" }, + }); + const branch_state = parse(BranchState, { + type: "feat", + description: "safe-worktree", + checkout: "worktree", + }); + + await expect( + new BranchConfirmPrompt(config, branch_state, {} as never).run(), + ).rejects.toThrow("exit 1"); + + expect(exit).toHaveBeenCalledWith(1); + expect(mocked.error).toHaveBeenCalledWith( + "Failed to create worktree '../worktrees/repo-safe-worktree'", + ); + exit.mockRestore(); + }); }); diff --git a/src/prompts/branch-confirm.prompt.ts b/src/prompts/branch-confirm.prompt.ts index c1549e9..12646d1 100644 --- a/src/prompts/branch-confirm.prompt.ts +++ b/src/prompts/branch-confirm.prompt.ts @@ -1,19 +1,27 @@ import * as p from "@clack/prompts"; -import { execSync } from "child_process"; +import { execFileSync } from "child_process"; import color from "picocolors"; import { chdir } from "process"; import { branch_flags } from "../branch-args"; import { BranchHooks } from "../branch-hooks"; import { get_git_root } from "../utils"; import { build_branch, build_worktree_path } from "../utils/build-branch"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { BranchRunnable } from "./branch-runnable"; export class BranchConfirmPrompt extends BranchRunnable { async run(): Promise { + const branch_name = this.#branch_name; + const validation_error = validate_branch_name(branch_name); + if (validation_error) { + p.log.error(validation_error); + process.exit(1); + } + const hooks = new BranchHooks(this.config, this.branch_state); hooks.run_pre(); - this.#run_checkout(); + this.#run_checkout(branch_name); hooks.run_post(); } @@ -25,19 +33,22 @@ export class BranchConfirmPrompt extends BranchRunnable { return build_branch(this.branch_state, this.config); } - #run_checkout(): void { - const branch_name = this.#branch_name; + #run_checkout(branch_name: string): void { const branch_flag = this.#verify_branch_name(branch_name); if (!this.#is_worktree) { - const command = `git ${branch_flags.git_args} checkout ${branch_flag} ${branch_name}`; + const args = [ + "checkout", + ...(branch_flag ? [branch_flag] : []), + branch_name, + ]; if (branch_flags.dry_run) { - this.#log_dry_run_command(command); + this.#log_dry_run_command(args); return; } try { - execSync(command, { + execFileSync("git", [...branch_flags.git_options, ...args], { stdio: "inherit", }); p.log.info( @@ -45,26 +56,29 @@ export class BranchConfirmPrompt extends BranchRunnable { " " + color.black(branch_name) + " ", )}'`, ); - } catch (err) { - process.exit(0); + } catch { + p.log.error(`Failed to checkout branch '${branch_name}'`); + process.exit(1); } return; } - try { - const worktree_name = build_worktree_path( - this.branch_state, - this.config, - get_git_root(branch_flags.git_args), - ); - const command = `git ${branch_flags.git_args} worktree add ${worktree_name} ${branch_flag} ${branch_name}`; - if (branch_flags.dry_run) { - this.#log_dry_run_command(command); - return; - } + const worktree_name = build_worktree_path( + this.branch_state, + this.config, + get_git_root(branch_flags.git_options), + ); + const args = branch_flag + ? ["worktree", "add", branch_flag, branch_name, worktree_name] + : ["worktree", "add", worktree_name, branch_name]; + if (branch_flags.dry_run) { + this.#log_dry_run_command(args); + return; + } - execSync(command, { + try { + execFileSync("git", [...branch_flags.git_options, ...args], { stdio: "inherit", }); p.log.info( @@ -79,32 +93,38 @@ export class BranchConfirmPrompt extends BranchRunnable { " to navigate to your new worktree", ); chdir(worktree_name); - } catch (err) { - process.exit(0); + } catch { + p.log.error(`Failed to create worktree '${worktree_name}'`); + process.exit(1); } } - #log_dry_run_command(command: string): void { + #log_dry_run_command(args: string[]): void { + const command = ["git", ...branch_flags.git_options, ...args].join(" "); p.log.info(`Dry run: ${command}`); } #verify_branch_name(branch_name: string): string { - // TODO: There has to be a better way 🤦 - let branch_flag = ""; try { - execSync(`git ${branch_flags.git_args} show-ref ${branch_name}`, { - encoding: "utf-8", - }); + execFileSync( + "git", + [ + ...branch_flags.git_options, + "show-ref", + "--verify", + "--quiet", + `refs/heads/${branch_name}`, + ], + { encoding: "utf-8" }, + ); p.log.warning( color.yellow( `${branch_name} already exists! Checking out existing branch.`, ), ); - } catch (err) { - // Branch does not exist - branch_flag = "-b"; + return ""; + } catch { + return "-b"; } - - return branch_flag; } } diff --git a/src/prompts/branch-description.prompt.ts b/src/prompts/branch-description.prompt.ts index e98f95a..ce2f855 100644 --- a/src/prompts/branch-description.prompt.ts +++ b/src/prompts/branch-description.prompt.ts @@ -1,4 +1,5 @@ import * as p from "@clack/prompts"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { BranchRunnable } from "./branch-runnable"; export class BranchDescriptionPrompt extends BranchRunnable { @@ -27,6 +28,7 @@ export class BranchDescriptionPrompt extends BranchRunnable { if (value.length > this.#max_length) { return `Exceeded max length. Description max [${this.#max_length}]`; } + return validate_branch_name(value.replace(/\s+/g, "-").toLowerCase()); } #run_post_effects(prompt_result: string): void { diff --git a/src/prompts/branch-scope.prompt.ts b/src/prompts/branch-scope.prompt.ts index a7145f5..460e09e 100644 --- a/src/prompts/branch-scope.prompt.ts +++ b/src/prompts/branch-scope.prompt.ts @@ -1,4 +1,5 @@ import * as p from "@clack/prompts"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { CUSTOM_SCOPE_KEY } from "../valibot-consts"; import { BranchRunnable } from "./branch-runnable"; @@ -49,6 +50,7 @@ export class BranchScopePrompt extends BranchRunnable { const branch_scope = await p.text({ message: "Write a custom scope", placeholder: "", + validate: validate_branch_name, }); if (p.isCancel(branch_scope)) process.exit(0); branch_scope_value = branch_scope ?? ""; diff --git a/src/prompts/branch-ticket.prompt.ts b/src/prompts/branch-ticket.prompt.ts index 76492c7..f385169 100644 --- a/src/prompts/branch-ticket.prompt.ts +++ b/src/prompts/branch-ticket.prompt.ts @@ -1,5 +1,6 @@ import * as p from "@clack/prompts"; import { optional_message } from "../utils/messages"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { BranchRunnable } from "./branch-runnable"; export class BranchTicketPrompt extends BranchRunnable { @@ -33,6 +34,7 @@ export class BranchTicketPrompt extends BranchRunnable { #validate(value: string | undefined): string | undefined { if (this.#is_required && !value) return "Please enter a ticket / issue"; + return validate_branch_name(value); } #run_post_effects(prompt_result: string): void { diff --git a/src/prompts/branch-user.prompt.ts b/src/prompts/branch-user.prompt.ts index a132117..4bc3f78 100644 --- a/src/prompts/branch-user.prompt.ts +++ b/src/prompts/branch-user.prompt.ts @@ -1,6 +1,7 @@ import * as p from "@clack/prompts"; import { get_value_from_cache, set_value_cache } from "../utils"; import { optional_message } from "../utils/messages"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { BranchRunnable } from "./branch-runnable"; export class BranchUserPrompt extends BranchRunnable { @@ -41,6 +42,7 @@ export class BranchUserPrompt extends BranchRunnable { #validate(value: string | undefined): string | undefined { if (this.#is_required && !value) return "Please enter a username"; + return validate_branch_name(value?.replace(/\s+/g, "-").toLowerCase()); } #run_post_effects(prompt_result: string): void { diff --git a/src/prompts/branch-version.prompt.ts b/src/prompts/branch-version.prompt.ts index 07e50ad..0f8ec40 100644 --- a/src/prompts/branch-version.prompt.ts +++ b/src/prompts/branch-version.prompt.ts @@ -1,5 +1,6 @@ import * as p from "@clack/prompts"; import { optional_message } from "../utils/messages"; +import { validate_branch_name } from "../utils/validate-branch-name"; import { BranchRunnable } from "./branch-runnable"; export class BranchVersionPrompt extends BranchRunnable { @@ -33,6 +34,7 @@ export class BranchVersionPrompt extends BranchRunnable { #validate(value: string | undefined): string | undefined { if (this.#is_required && !value) return "Please enter a version"; + return validate_branch_name(value); } #run_post_effects(prompt_result: string): void { diff --git a/src/utils.ts b/src/utils.ts index b334d88..741ecd8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import * as p from "@clack/prompts"; -import { execSync } from "child_process"; +import { execFileSync, execSync } from "child_process"; import fs from "fs"; import { homedir } from "os"; import color from "picocolors"; @@ -60,7 +60,7 @@ export type LoadedSetup = { /* LOAD */ export function load_setup( cli_name = " better-commits ", - git_args = flags.git_args, + git_args: string | string[] = flags.git_args, ): LoadedSetup { console.clear(); p.intro(`${color.bgCyan(color.black(cli_name))}`); @@ -154,12 +154,15 @@ function validate_config(config: unknown): InferOutput { /* rev-parse will fail in a --bare repository root */ -export function get_git_root(git_args = flags.git_args): string { +export function get_git_root( + git_args: string | string[] = flags.git_args, +): string { let path = "."; try { - path = execSync(`git ${git_args} rev-parse --show-toplevel`) - .toString() - .trim(); + const output = Array.isArray(git_args) + ? execFileSync("git", [...git_args, "rev-parse", "--show-toplevel"]) + : execSync(`git ${git_args} rev-parse --show-toplevel`); + path = output.toString().trim(); } catch (err) { p.log.warn( "Could not find git root. If in a --bare repository, ignore this warning.", diff --git a/src/utils/infer.ts b/src/utils/infer.ts index 17e75df..5414b37 100644 --- a/src/utils/infer.ts +++ b/src/utils/infer.ts @@ -1,9 +1,10 @@ -import { execSync } from "child_process"; +import { execFileSync, execSync } from "child_process"; import { flags } from "../args"; import { InferOutput } from "valibot"; import { Config } from "../valibot-state"; import { CUSTOM_SCOPE_KEY } from "../valibot-consts"; +type GitArgs = string | string[]; type PrependHashtag = "Never" | "Always" | "Prompt"; const REGEX_SLASH_TAG = /\/(\w+-\d+)/; @@ -52,7 +53,7 @@ export function infer_not_interactive(config: InferOutput) { export function infer_type_from_git( options: { value: string }[], - git_args: string, + git_args: GitArgs, ): string { const branch = get_current_branch(git_args); if (!branch) return ""; @@ -65,7 +66,7 @@ export function infer_type_from_git( export function infer_ticket_from_git( options: { append_hashtag: boolean; prepend_hashtag: PrependHashtag }, - git_args: string, + git_args: GitArgs, ): string { const branch = get_current_branch(git_args); if (!branch) return ""; @@ -75,7 +76,7 @@ export function infer_ticket_from_git( export function infer_scope_from_git( options: { value: string }[], - git_args: string, + git_args: GitArgs, ): string { const branch = get_current_branch(git_args); if (!branch) return ""; @@ -147,13 +148,16 @@ function infer_scope_from_branch( return found ?? ""; } -function get_current_branch(git_args: string): string { +function get_current_branch(git_args: GitArgs): string { try { - return execSync(`git ${git_args} branch --show-current`, { - stdio: "pipe", - }) - .toString() - .trim(); + const branch = Array.isArray(git_args) + ? execFileSync("git", [...git_args, "branch", "--show-current"], { + stdio: "pipe", + }) + : execSync(`git ${git_args} branch --show-current`, { + stdio: "pipe", + }); + return branch.toString().trim(); } catch { return ""; } diff --git a/src/utils/validate-branch-name.test.ts b/src/utils/validate-branch-name.test.ts new file mode 100644 index 0000000..341d6d6 --- /dev/null +++ b/src/utils/validate-branch-name.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { validate_branch_name } from "./validate-branch-name"; + +describe("validate_branch_name", () => { + it("accepts valid branch names", () => { + expect(validate_branch_name("feat/TAC-123-safe-branch")).toBeUndefined(); + }); + + it.each(["feat/a..b", "feat/done.", "feat/x:y"])( + "rejects %s", + (branch_name) => { + expect(validate_branch_name(branch_name)).toContain( + "is not a valid branch name", + ); + }, + ); + + it("rejects branch shorthand", () => { + expect(validate_branch_name("@{-1}")).toBeDefined(); + }); +}); diff --git a/src/utils/validate-branch-name.ts b/src/utils/validate-branch-name.ts new file mode 100644 index 0000000..c025426 --- /dev/null +++ b/src/utils/validate-branch-name.ts @@ -0,0 +1,22 @@ +import { spawnSync } from "child_process"; + +export function validate_branch_name( + branch_name: string | undefined, +): string | undefined { + if (!branch_name) return; + + const result = spawnSync( + "git", + ["check-ref-format", "--branch", branch_name], + { encoding: "utf8" }, + ); + + if (result.status === 0 && result.stdout?.trim() === branch_name) return; + + const error = + result.stderr?.trim().replace(/^fatal:\s*/, "") || + result.error?.message || + `'${branch_name}' is not a valid branch name`; + + return `${error} [git-check-ref-format]`; +}