diff --git a/package-lock.json b/package-lock.json index af28478..6b2884b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "gitbun", - "version": "1.12.1", + "version": "1.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitbun", - "version": "1.12.1", + "version": "1.15.0", "license": "MIT", "dependencies": { "chalk": "^5.6.2", "commander": "^14.0.3", "cosmiconfig": "^9.0.0", - "inquirer": "^13.2.4", + "inquirer": "^13.4.3", "node-fetch": "^3.3.2", "ora": "^9.3.0", "simple-git": "^3.31.1", @@ -31,6 +31,7 @@ "@semantic-release/github": "^12.0.6", "@semantic-release/npm": "^13.1.4", "@semantic-release/release-notes-generator": "^14.1.0", + "@types/inquirer": "^9.0.10", "@types/node": "^25.9.0", "@typescript-eslint/eslint-plugin": "^8.56.1", "@typescript-eslint/parser": "^8.56.1", @@ -1055,27 +1056,27 @@ } }, "node_modules/@inquirer/ansi": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", - "integrity": "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/checkbox": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.6.tgz", - "integrity": "sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.3", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1087,16 +1088,16 @@ } }, "node_modules/@inquirer/confirm": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.6.tgz", - "integrity": "sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1108,21 +1109,21 @@ } }, "node_modules/@inquirer/core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.3.tgz", - "integrity": "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3", + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1134,17 +1135,17 @@ } }, "node_modules/@inquirer/editor": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.6.tgz", - "integrity": "sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/external-editor": "^2.0.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1156,16 +1157,16 @@ } }, "node_modules/@inquirer/expand": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.6.tgz", - "integrity": "sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1177,16 +1178,16 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.3.tgz", - "integrity": "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", "license": "MIT", "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1198,25 +1199,25 @@ } }, "node_modules/@inquirer/figures": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", - "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/input": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.6.tgz", - "integrity": "sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1228,16 +1229,16 @@ } }, "node_modules/@inquirer/number": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.6.tgz", - "integrity": "sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1249,17 +1250,17 @@ } }, "node_modules/@inquirer/password": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.6.tgz", - "integrity": "sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1271,24 +1272,24 @@ } }, "node_modules/@inquirer/prompts": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.2.1.tgz", - "integrity": "sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.0.5", - "@inquirer/confirm": "^6.0.5", - "@inquirer/editor": "^5.0.5", - "@inquirer/expand": "^5.0.5", - "@inquirer/input": "^5.0.5", - "@inquirer/number": "^4.0.5", - "@inquirer/password": "^5.0.5", - "@inquirer/rawlist": "^5.2.1", - "@inquirer/search": "^4.1.1", - "@inquirer/select": "^5.0.5" + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1300,16 +1301,16 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.2.tgz", - "integrity": "sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1321,17 +1322,17 @@ } }, "node_modules/@inquirer/search": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.2.tgz", - "integrity": "sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.3", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1343,18 +1344,18 @@ } }, "node_modules/@inquirer/select": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.6.tgz", - "integrity": "sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.3", - "@inquirer/figures": "^2.0.3", - "@inquirer/type": "^4.0.3" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -1366,12 +1367,12 @@ } }, "node_modules/@inquirer/type": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.3.tgz", - "integrity": "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2511,6 +2512,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/inquirer": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.10.tgz", + "integrity": "sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2522,7 +2534,7 @@ "version": "25.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -2535,6 +2547,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", @@ -3131,9 +3153,9 @@ } }, "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "license": "MIT" }, "node_modules/clean-stack": { @@ -4321,9 +4343,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-wrap-ansi": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", - "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", "license": "MIT", "dependencies": { "fast-string-width": "^3.0.2" @@ -4795,9 +4817,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4912,15 +4934,15 @@ } }, "node_modules/inquirer": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.2.4.tgz", - "integrity": "sha512-7JJ8+lGhGtJOeVnrH4IqP7mQgOqvHkKS6DNLTkMHEI3iHKzZUaidOivU9q8wrlSRvT0ISCBMweMK7TWYzr5BhA==", + "version": "13.4.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.4.3.tgz", + "integrity": "sha512-EPd3IqieHSavSOXh+LZhrIkdQcOELWeRblLT6kslQr+cF9XTh/HxZdSt1YkHH1iq4dvqBnV42uwg2YlorgOy6g==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.3", - "@inquirer/core": "^11.1.3", - "@inquirer/prompts": "^8.2.1", - "@inquirer/type": "^4.0.3", + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.10", + "@inquirer/prompts": "^8.4.3", + "@inquirer/type": "^4.0.5", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" @@ -9696,7 +9718,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", @@ -9758,7 +9780,7 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { diff --git a/package.json b/package.json index c1bd43b..77fc3cb 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "chalk": "^5.6.2", "commander": "^14.0.3", "cosmiconfig": "^9.0.0", - "inquirer": "^13.2.4", + "inquirer": "^13.4.3", "node-fetch": "^3.3.2", "ora": "^9.3.0", "simple-git": "^3.31.1", @@ -52,6 +52,7 @@ "@semantic-release/github": "^12.0.6", "@semantic-release/npm": "^13.1.4", "@semantic-release/release-notes-generator": "^14.1.0", + "@types/inquirer": "^9.0.10", "@types/node": "^25.9.0", "@typescript-eslint/eslint-plugin": "^8.56.1", "@typescript-eslint/parser": "^8.56.1", diff --git a/src/commands/init.ts b/src/commands/init.ts new file mode 100644 index 0000000..2e3d2a1 --- /dev/null +++ b/src/commands/init.ts @@ -0,0 +1,94 @@ +import inquirer from 'inquirer'; +import { execSync } from 'child_process'; +import { writeFileSync, existsSync, readFileSync } from 'fs'; +import chalk from 'chalk'; + +async function detectOllamaModels(): Promise { + try { + const output = execSync('ollama list --format json', { + stdio: ['pipe', 'pipe', 'ignore'], + }).toString(); + return JSON.parse(output).map((m: { name: string }) => m.name); + } catch { + return []; + } +} + +function detectProjectType(): string { + if (existsSync('Cargo.toml')) return 'rust'; + if (existsSync('pyproject.toml') || existsSync('requirements.txt')) return 'python'; + if (existsSync('go.mod')) return 'go'; + if (existsSync('package.json')) { + const pkg = JSON.parse(readFileSync('package.json', 'utf-8')); + if (pkg.dependencies?.next || pkg.devDependencies?.next) return 'nextjs'; + if (pkg.dependencies?.react || pkg.devDependencies?.react) return 'react'; + return 'nodejs'; + } + return 'generic'; +} + +export async function runInitWizard(opts: { yes?: boolean } = {}) { + console.log(chalk.cyan("\nšŸ‡ Welcome to Gitbun! Let's set up your config.\n")); + + const configPath = '.gitbunrc'; + + // Ask before overwriting + if (existsSync(configPath) && !opts.yes) { + const { overwrite } = await inquirer.prompt([{ + type: 'confirm', + name: 'overwrite', + message: `${configPath} already exists. Overwrite?`, + default: false, + }]); + if (!overwrite) { + console.log(chalk.yellow('Aborted.')); + return; + } + } + + const projectType = detectProjectType(); + const ollamaModels = await detectOllamaModels(); + const hasOllama = ollamaModels.length > 0; + + console.log(chalk.gray(`Detected project type: ${projectType}`)); + console.log(chalk.gray(`Ollama: ${hasOllama ? `āœ“ (${ollamaModels.length} models)` : 'āœ— not detected'}\n`)); + + const answers = opts.yes + ? { provider: hasOllama ? 'ollama' : 'none', model: ollamaModels[0], interactive: true, conventionalCommits: true } + : await inquirer.prompt([ + { + type: 'list', + name: 'provider', + message: 'Which AI provider?', + choices: [ + ...(hasOllama ? [{ name: 'Ollama (local, private)', value: 'ollama' }] : []), + { name: 'OpenAI (requires OPENAI_API_KEY)', value: 'openai' }, + { name: 'Anthropic Claude (requires ANTHROPIC_API_KEY)', value: 'anthropic' }, + { name: 'Rule-based only (no AI)', value: 'none' }, + ], + }, + { + type: 'list', + name: 'model', + message: 'Which Ollama model?', + choices: ollamaModels, + when: (a) => a.provider === 'ollama' && hasOllama, + }, + { type: 'confirm', name: 'interactive', message: 'Always preview before committing?', default: true }, + { type: 'confirm', name: 'conventionalCommits', message: 'Enforce Conventional Commits?', default: true }, + ]); + + const config: Record = { + ...(answers.provider !== 'none' && { provider: answers.provider }), + ...(answers.model && { model: answers.model }), + ai: answers.provider !== 'none', + interactive: answers.interactive, + }; + + console.log(chalk.cyan('\nConfig preview:')); + console.log(JSON.stringify(config, null, 2)); + + writeFileSync(configPath, JSON.stringify(config, null, 2)); + console.log(chalk.green(`\nāœ“ Config written to ${configPath}`)); + console.log(chalk.cyan('Run `gitbun` in any repo to generate your first commit message.\n')); +} \ No newline at end of file diff --git a/src/index.staging.test.ts b/src/index.staging.test.ts index 0dc6795..74ae2a0 100644 --- a/src/index.staging.test.ts +++ b/src/index.staging.test.ts @@ -1,5 +1,7 @@ +// src/index.staging.test.ts import { beforeEach, describe, expect, it, vi } from "vitest"; +// Hoist mocks so they are available before module imports const { execFileSyncMock, promptMock, @@ -40,10 +42,13 @@ const { generateCommitMessageMock: vi.fn(), })); +// Single mock for node:child_process – includes both execFileSync and execFile vi.mock("node:child_process", async (importOriginal) => { - const actual = await importOriginal() as Record; return { + const actual = await importOriginal() as Record; + return { ...actual, execFileSync: execFileSyncMock, + execFile: vi.fn(), // needed for other modules (e.g., semanticAnalyzer) }; }); @@ -53,14 +58,6 @@ vi.mock("inquirer", () => ({ }, })); -vi.mock("node:child_process", async (importOriginal) => { - const actual = await importOriginal() as Record; - return { - ...actual, - execFileSync: execFileSyncMock, - }; -}); - vi.mock("ora", () => ({ default: () => ({ start: vi.fn().mockReturnThis(), @@ -130,12 +127,14 @@ vi.mock("./llm/checkOllama", () => ({ getBestModel: getBestModelMock, })); +// Spy on process.exit – throws an error so we can catch it const exitSpy = vi .spyOn(process, "exit") .mockImplementation(((code?: number) => { - throw new Error(`process.exit:${code ?? 0}`); + throw new Error(`process.exit called with ${code ?? 0}`); }) as never); +// Silence console logs during tests const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined); const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); @@ -145,6 +144,7 @@ describe("interactive staging UI", () => { beforeEach(() => { vi.clearAllMocks(); + // Default mocks for all dependencies isGitRepoMock.mockResolvedValue(true); getStagedFilesMock.mockResolvedValue([]); getDiffStatsMock.mockResolvedValue({ additions: 1, deletions: 0 }); @@ -161,74 +161,103 @@ describe("interactive staging UI", () => { confirmCommitMock.mockResolvedValue("feat(core): add staging ui"); commitMock.mockResolvedValue("committed"); enhanceCommitMock.mockResolvedValue("feat(core): add staging ui"); + + // Ensure test environment is set + process.env.NODE_ENV = "test"; }); it("exits cleanly when no unstaged files exist", async () => { + // No modified or untracked files execFileSyncMock - .mockReturnValueOnce(Buffer.from("")) - .mockReturnValueOnce(Buffer.from("")); + .mockReturnValueOnce(Buffer.from("")) // git ls-files --modified + .mockReturnValueOnce(Buffer.from("")); // git ls-files --others - await expect(run({ auto: true })).rejects.toThrow("process.exit:1"); + // The function should exit with 0 + await expect(run({ auto: true })).rejects.toThrow("process.exit called with 0"); - expect(exitSpy).toHaveBeenCalledWith(0); + // Verify that we didn't prompt the user expect(promptMock).not.toHaveBeenCalled(); + // Verify no error was logged (catch block not entered) + expect(errorSpy).not.toHaveBeenCalled(); }); it("exits cleanly when user selects nothing in the prompt", async () => { + // There are unstaged files execFileSyncMock - .mockReturnValueOnce(Buffer.from("src/index.ts\n")) - .mockReturnValueOnce(Buffer.from("")); + .mockReturnValueOnce(Buffer.from("src/index.ts\n")) // modified + .mockReturnValueOnce(Buffer.from("")); // untracked + + // User selects nothing promptMock.mockResolvedValue({ filesToStage: [] }); - await expect(run({ auto: true })).rejects.toThrow("process.exit:1"); + // Should exit with 0 + await expect(run({ auto: true })).rejects.toThrow("process.exit called with 0"); expect(promptMock).toHaveBeenCalledOnce(); - expect(exitSpy).toHaveBeenCalledWith(0); + expect(errorSpy).not.toHaveBeenCalled(); }); it("calls git add with correct files when user selects files", async () => { + // Setup: staged files initially empty, then after staging we have files getStagedFilesMock - .mockResolvedValueOnce([]) + .mockResolvedValueOnce([]) // first call in run .mockResolvedValueOnce([ { path: "src/index.ts", status: "M" }, { path: "README.md", status: "A" }, ]); + + // Mock git commands in order of execution: + // 1. ls-files --modified + // 2. ls-files --others + // 3. git add (success) + // 4. git diff --cached --name-only (verification success) execFileSyncMock - .mockReturnValueOnce(Buffer.from("src/index.ts\n")) - .mockReturnValueOnce(Buffer.from("README.md\n")) - .mockReturnValueOnce(Buffer.from("")) - .mockReturnValueOnce(Buffer.from("src/index.ts\nREADME.md\n")); + .mockReturnValueOnce(Buffer.from("src/index.ts\n")) // modified + .mockReturnValueOnce(Buffer.from("README.md\n")) // others + .mockReturnValueOnce(Buffer.from("")) // git add + .mockReturnValueOnce(Buffer.from("src/index.ts\nREADME.md\n")); // verification + promptMock.mockResolvedValue({ filesToStage: ["src/index.ts", "README.md"], }); + // Mock diff stats for the staged files (called during commit generation) + getDiffStatsMock + .mockResolvedValueOnce({ additions: 10, deletions: 2 }) + .mockResolvedValueOnce({ additions: 5, deletions: 1 }); + + // The commit generation will proceed; we don't expect an exit here await run({ auto: true }); + // Verify git add was called with the correct files expect(execFileSyncMock).toHaveBeenCalledWith( "git", ["add", "src/index.ts", "README.md"], { stdio: "inherit" } ); expect(commitMock).toHaveBeenCalledOnce(); + expect(errorSpy).not.toHaveBeenCalled(); }); it("exits with error when staging verification fails", async () => { + // There are unstaged files execFileSyncMock - .mockReturnValueOnce(Buffer.from("src/index.ts\n")) - .mockReturnValueOnce(Buffer.from("")) - .mockReturnValueOnce(Buffer.from("")) - .mockReturnValueOnce(Buffer.from("")); + .mockReturnValueOnce(Buffer.from("src/index.ts\n")) // modified + .mockReturnValueOnce(Buffer.from("")) // others + .mockReturnValueOnce(Buffer.from("")) // git add + .mockReturnValueOnce(Buffer.from("")); // verification fails (empty) + promptMock.mockResolvedValue({ filesToStage: ["src/index.ts"] }); - await expect(run({ auto: true })).rejects.toThrow("process.exit:1"); + // Should exit with 1 + await expect(run({ auto: true })).rejects.toThrow("process.exit called with 1"); expect(execFileSyncMock).toHaveBeenCalledWith( "git", ["add", "src/index.ts"], { stdio: "inherit" } ); - expect(logSpy).toHaveBeenCalledWith("Staging failed. No files were staged."); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Staging failed")); expect(errorSpy).toHaveBeenCalled(); - expect(exitSpy).toHaveBeenCalledWith(1); }); -}); +}); \ No newline at end of file diff --git a/src/index.test.ts b/src/index.test.ts index 5d47053..a2eeaad 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,7 +1,14 @@ -import { describe, it, expect } from 'vitest'; +// src/index.test.ts +import { describe, it, expect, vi } from 'vitest'; import { detectScope } from './analyzer/scopeDetector'; import { ValidationError, CancellationError } from './utils/errors'; +// Mock any external dependencies if needed +vi.mock('node:child_process', () => ({ + execFileSync: vi.fn(), + execFile: vi.fn(), +})); + describe('detectScope', () => { it('should detect scope from src directory', () => { const files = ['src/analyzer/scopeDetector.ts', 'src/analyzer/typeClassifier.ts']; @@ -31,4 +38,4 @@ describe('Errors', () => { expect(error.name).toBe('CancellationError'); expect(error.message).toBe('Commit cancelled.'); }); -}); +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e46764d..6391fa9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,8 @@ import chalk from "chalk"; import ora from "ora"; import inquirer from "inquirer"; import { execFileSync } from "node:child_process"; - +import { program } from "commander"; +import { runInitWizard } from './commands/init'; import { isGitRepo } from "./git/checkRepo"; import { getStagedFiles } from "./git/getStagedFiles"; import { getDiffStats } from "./git/getDiffStats"; @@ -37,6 +38,14 @@ interface CliOptions { [key: string]: unknown; } +// Helper to exit safely - can be mocked in tests +function safeExit(code: number): never { + if (process.env.NODE_ENV === 'test') { + throw new Error(`process.exit called with ${code}`); + } + process.exit(code); +} + async function launchStagingUI(options: CliOptions) { try { const modifiedFiles = execFileSync("git", ["ls-files", "--modified"]) @@ -55,7 +64,7 @@ async function launchStagingUI(options: CliOptions) { if (unstagedFiles.length === 0) { console.log("No changes detected to commit."); - process.exit(0); + safeExit(0); } const { filesToStage } = await inquirer.prompt([{ @@ -67,7 +76,7 @@ async function launchStagingUI(options: CliOptions) { if (filesToStage.length === 0) { console.log("Nothing selected. Exiting."); - process.exit(0); + safeExit(0); } execFileSync("git", ["add", ...filesToStage], { stdio: "inherit" }); @@ -78,7 +87,7 @@ async function launchStagingUI(options: CliOptions) { if (!verified) { console.log(chalk.red("Staging failed. No files were staged.")); - process.exit(1); + safeExit(1); } await run(options); @@ -87,7 +96,7 @@ async function launchStagingUI(options: CliOptions) { throw error; } console.error(chalk.red("Failed to launch staging UI:"), error); - process.exit(1); + safeExit(1); } } @@ -215,7 +224,7 @@ export async function run(options: CliOptions) { } catch (error) { spinner.fail("Failed during analysis or generation."); console.error(error); - process.exit(1); + safeExit(1); } // Hook mode: print message to stdout and exit without committing @@ -227,7 +236,7 @@ export async function run(options: CliOptions) { // Dry run if (options.dryRun) { console.log("\n" + colorizeCommitMessage(commitMessage) + "\n"); - process.exit(0); + safeExit(0); } // Confirmation @@ -245,4 +254,94 @@ export async function run(options: CliOptions) { // Perform commit const output = await commit(finalMessage); console.log("\n" + output); + safeExit(0); +} + +// ============================================ +// CLI Command Registration +// ============================================ + +// Set up the CLI program +program + .name('git-commit-assistant') + .description('AI-powered commit message generator with semantic analysis') + .version('1.0.0'); + +// Main commit command (existing functionality) +program + .command('commit') + .description('Generate and commit with AI assistance') + .option('--ai', 'Use AI enhancement with Ollama') + .option('--model ', 'Specify the AI model to use') + .option('--auto', 'Skip confirmation and commit automatically') + .option('--generate-only', 'Only generate the commit message without committing') + .option('--verbose', 'Show verbose output during processing') + .option('--dry-run', 'Show the commit message without committing') + .action(async (options) => { + try { + await run(options); + } catch (error) { + if (error instanceof CancellationError) { + console.log(chalk.yellow('\nCommit cancelled.')); + safeExit(0); + } + console.error(chalk.red('\nError:'), error); + safeExit(1); + } + }); + +// NEW: Init command for interactive setup wizard +program + .command('init') + .description('Interactive setup wizard for configuring the commit assistant') + .option('--yes', 'Accept all defaults (non-interactive mode)') + .action(async (opts) => { + try { + await runInitWizard({ yes: opts.yes }); + safeExit(0); + } catch (error) { + console.error(chalk.red('\nInit failed:'), error); + safeExit(1); + } + }); + +// Default command: if no command is specified, run the commit command +program + .command('*', { isDefault: true }) + .description('Default command - run commit assistant') + .action(() => { + // Parse the original arguments to check for options + const args = process.argv.slice(2); + const options: CliOptions = {}; + + // Simple parsing for common options + if (args.includes('--ai')) options.ai = true; + if (args.includes('--auto')) options.auto = true; + if (args.includes('--generate-only')) options.generateOnly = true; + if (args.includes('--verbose')) options.verbose = true; + if (args.includes('--dry-run')) options.dryRun = true; + + // Parse --model value + const modelIndex = args.indexOf('--model'); + if (modelIndex !== -1 && args[modelIndex + 1]) { + options.model = args[modelIndex + 1]; + } + + // Run the commit command + run(options).catch((error) => { + if (error instanceof CancellationError) { + console.log(chalk.yellow('\nCommit cancelled.')); + safeExit(0); + } + console.error(chalk.red('\nError:'), error); + safeExit(1); + }); + }); + +// Only parse if this is the main module (not being imported for tests) +if (require.main === module) { + program.parse(); } + +// Export for testing +export { program, safeExit }; \ No newline at end of file