From 4bde7c56b1a0bd28e59f5c2f2bdbbcc31f76be81 Mon Sep 17 00:00:00 2001 From: Debajyati Date: Sat, 9 Nov 2024 22:17:08 +0530 Subject: [PATCH 01/82] updated .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index c6bba59..afcfe6b 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,7 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# local files I'm using for development +api-urls.json +thank.js From 25b8edb3896d04e524881175d86379fbd6fc728a Mon Sep 17 00:00:00 2001 From: Debajyati Date: Sat, 9 Nov 2024 22:44:02 +0530 Subject: [PATCH 02/82] feat:stylish header for the CLI --- src/utils/headerText.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/utils/headerText.js diff --git a/src/utils/headerText.js b/src/utils/headerText.js new file mode 100644 index 0000000..2d4e731 --- /dev/null +++ b/src/utils/headerText.js @@ -0,0 +1,29 @@ +import cfonts from "cfonts"; + +function getRandomIntInclusive(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +const fontStyles = ['chrome','slick','grid','shade','tiny','pallet']; +const randomFontStyle = fontStyles[getRandomIntInclusive(0, fontStyles.length - 1)]; +const options = { + font: randomFontStyle, // define the font face + align: 'center', // define text alignment + colors: ['greenBright', 'magentaBright', 'redBright'], // define all colors + background: 'transparent', // define the background color, you can also use `backgroundColor` here as key + letterSpacing: 1, // define letter spacing + lineHeight: 1, // define the line height + space: true, // define if the output text should have empty lines on top and on the bottom + maxLength: '0', // define how many character can be on one line + gradient: true, // define your two gradient colors + independentGradient: false, // define if you want to recalculate the gradient for each new line + transitionGradient: false, // define if this is a transition between colors directly + rawMode: false, // define if the line breaks should be CRLF (`\r\n`) over the default LF (`\n`) + env: 'node' // define the environment cfonts is being executed in +}; + +const sexy = cfonts.render('GITFM',options); + +const headerText = sexy.string; + +export { headerText }; From d71f3a55b5ded4488015d268665a4f396a7c311e Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 13 Nov 2024 21:16:45 +0530 Subject: [PATCH 03/82] update dependencies --- package-lock.json | 1344 ++++++++++++++++++++++++++++++--------------- package.json | 10 +- 2 files changed, 900 insertions(+), 454 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ce1f76..d6830c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,16 @@ "version": "1.1.0", "license": "ISC", "dependencies": { + "@inquirer/prompts": "^7.0.1", + "@inquirer/search": "^3.0.1", + "@octokit/oauth-methods": "^5.1.2", + "@octokit/rest": "^21.0.2", "axios": "^1.7.2", + "boxen": "^8.0.1", + "cfonts": "^3.3.0", "chalk": "^5.3.0", - "inquirer": "^9.2.23" + "commander": "^12.1.0", + "open": "^10.1.0" }, "bin": { "gitfm": "index.js" @@ -19,7 +26,6 @@ "devDependencies": { "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@types/inquirer": "^9.0.7", "globals": "^15.10.0" } }, @@ -70,53 +76,429 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@inquirer/checkbox": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.1.tgz", + "integrity": "sha512-ehJjmNPdguajc1hStvjN7DJNVjwG5LC1mgGMGFjCmdkn2fxB2GtULftMnlaqNmvMdPpqdaSoOFpl86VkLtG4pQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.1.tgz", + "integrity": "sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.0.1.tgz", + "integrity": "sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.0.1.tgz", + "integrity": "sha512-qAHHJ6hs343eNtCKgV2wV5CImFxYG8J1pl/YCeI5w9VoW7QpulRUU26+4NsMhjR6zDRjKBsH/rRjCIcaAOHsrg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.1.tgz", + "integrity": "sha512-9anjpdc802YInXekwePsa5LWySzVMHbhVS6v6n5IJxrl8w09mODOeP69wZ1d0WrOvot2buQSmYp4lW/pq8y+zQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@inquirer/figures": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.3.tgz", - "integrity": "sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.7.tgz", + "integrity": "sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==", + "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "node_modules/@inquirer/input": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.0.1.tgz", + "integrity": "sha512-m+SliZ2m43cDRIpAdQxfv5QOeAQCuhS8TGLvtzEP1An4IH1kBES4RLMRgE/fC+z29aN8qYG8Tq/eXQQKTYwqAg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" } }, - "node_modules/@types/inquirer": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.7.tgz", - "integrity": "sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==", - "dev": true, + "node_modules/@inquirer/number": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.1.tgz", + "integrity": "sha512-gF3erqfm0snpwBjbyKXUUe17QJ7ebm49btXApajrM0rgCCoYX0o9W5NCuYNae87iPxaIJVjtuoQ42DX32IdbMA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.1.tgz", + "integrity": "sha512-D7zUuX4l4ZpL3D7/SWu9ibijP09jigwHi/gfUHLx5GMS5oXzuMfPV2xPMG1tskco4enTx70HA0VtMXecerpvbg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.0.1.tgz", + "integrity": "sha512-cu2CpGC2hz7WTt2VBvdkzahDvYice6vYA/8Dm7Fy3tRNzKuQTF2EY3CV4H2GamveWE6tA2XzyXtbWX8+t4WMQg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.0.1", + "@inquirer/confirm": "^5.0.1", + "@inquirer/editor": "^4.0.1", + "@inquirer/expand": "^4.0.1", + "@inquirer/input": "^4.0.1", + "@inquirer/number": "^3.0.1", + "@inquirer/password": "^4.0.1", + "@inquirer/rawlist": "^4.0.1", + "@inquirer/search": "^3.0.1", + "@inquirer/select": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.1.tgz", + "integrity": "sha512-0LuMOgaWs7W8JNcbiKkoFwyWFDEeCmLqDCygF0hidQUVa6J5grFVRZxrpompiWDFM49Km2rf7WoZwRo1uf1yWQ==", + "license": "MIT", "dependencies": { - "@types/through": "*", - "rxjs": "^7.2.0" + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.1.tgz", + "integrity": "sha512-ehMqjiO0pAf+KtdONKeCLVy4i3fy3feyRRhDrvzWhiwB8JccgKn7eHFr39l+Nx/FaZAhr0YxIJvkK5NuNvG+Ww==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.1.tgz", + "integrity": "sha512-tVRatFRGU49bxFCKi/3P+C0E13KZduNFbWuHWRx0L2+jbiyKRpXgHp9qiRHWRk/KarhYBXzH/di6w3VQ5aJd5w==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.0.tgz", + "integrity": "sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", + "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", + "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", + "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz", + "integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/request": "^9.1.0", + "@octokit/request-error": "^6.1.0", + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.5.tgz", + "integrity": "sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz", + "integrity": "sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", + "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz", + "integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.0.2.tgz", + "integrity": "sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.1.2", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.6.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz", + "integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" } }, "node_modules/@types/node": { "version": "20.14.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", "integrity": "sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==", - "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } }, - "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, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -157,10 +539,20 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -175,6 +567,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -183,6 +576,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -223,33 +617,123 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/brace-expansion": { @@ -263,45 +747,19 @@ "concat-map": "0.0.1" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "run-applescript": "^7.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/callsites": { @@ -314,6 +772,49 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cfonts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cfonts/-/cfonts-3.3.0.tgz", + "integrity": "sha512-RlVxeEw2FXWI5Bs9LD0/Ef3bsQIc9m6lK/DINN20HIW0Y0YHUO2jjy88cot9YKZITiRTCdWzTfLmTyx47HeSLA==", + "license": "GPL-3.0-or-later", + "dependencies": { + "supports-color": "^8", + "window-size": "^1" + }, + "bin": { + "cfonts": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cfonts/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -328,25 +829,16 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "license": "MIT" }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -356,22 +848,16 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", "engines": { "node": ">= 12" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -382,7 +868,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -395,6 +882,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -420,31 +916,56 @@ } } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", "dependencies": { - "clone": "^1.0.2" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/delayed-stream": { @@ -458,26 +979,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/eslint-visitor-keys": { "version": "4.1.0", @@ -514,6 +1017,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -577,22 +1081,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globals": { @@ -608,17 +1106,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -627,39 +1114,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -675,6 +1129,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -682,25 +1137,6 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -728,58 +1164,113 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" }, - "node_modules/inquirer": { - "version": "9.2.23", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.23.tgz", - "integrity": "sha512-kod5s+FBPIDM2xiy9fu+6wdU/SkK5le5GS9lh4FEBjBHqiMgD9lLFbCbuqFNAjNL2ZOy9Wd9F694IOzN9pZHBA==", + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.3", - "@ljharb/through": "^2.3.13", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" + "hasown": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-interactive": { + "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, "engines": { - "node": ">=10" + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -805,39 +1296,16 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "is-buffer": "^1.1.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.10.0" } }, "node_modules/mime-db": { @@ -859,14 +1327,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -888,68 +1348,37 @@ "license": "MIT" }, "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -982,19 +1411,6 @@ "node": ">=6" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1005,91 +1421,41 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1103,6 +1469,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1123,21 +1490,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -1145,15 +1502,11 @@ "node": ">=0.6.0" } }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -1165,7 +1518,13 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "peer": true + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "license": "ISC" }, "node_modules/uri-js": { "version": "4.4.1", @@ -1177,23 +1536,92 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", "dependencies": { - "defaults": "^1.0.3" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/window-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", + "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", + "license": "MIT", + "dependencies": { + "define-property": "^1.0.0", + "is-number": "^3.0.0" + }, + "bin": { + "window-size": "cli.js" + }, + "engines": { + "node": ">= 0.10.0" } }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -1202,6 +1630,18 @@ "engines": { "node": ">=8" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index dcb2303..9d37da5 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,20 @@ "author": "Debajyati Dey", "license": "ISC", "dependencies": { + "@inquirer/prompts": "^7.0.1", + "@inquirer/search": "^3.0.1", + "@octokit/oauth-methods": "^5.1.2", + "@octokit/rest": "^21.0.2", "axios": "^1.7.2", + "boxen": "^8.0.1", + "cfonts": "^3.3.0", "chalk": "^5.3.0", - "inquirer": "^9.2.23" + "commander": "^12.1.0", + "open": "^10.1.0" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@types/inquirer": "^9.0.7", "globals": "^15.10.0" } } From a30ca99a2c5ffb6d21f30dbd85657f92e62fb6a4 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 13 Nov 2024 21:17:34 +0530 Subject: [PATCH 04/82] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index afcfe6b..d5b1264 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,4 @@ dist # local files I'm using for development api-urls.json thank.js +sample_output.js From a14d9fab8346ecfd423079c8a98976ca847837da Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Thu, 14 Nov 2024 13:20:00 +0530 Subject: [PATCH 05/82] Codes are distributed between many files for modular approach. Here will be only the argparsing --- index.js | 215 ++----------------------------------------------------- 1 file changed, 4 insertions(+), 211 deletions(-) diff --git a/index.js b/index.js index 1bfd8e2..edea98e 100644 --- a/index.js +++ b/index.js @@ -1,213 +1,6 @@ #!/usr/bin/env node -import axios from "axios"; -import inquirer from "inquirer"; -import chalk from "chalk"; -import { exec } from "node:child_process"; +import { Command } from 'commander'; +const program = new Command(); -// Fetch repositories based on the search term -async function fetchRepos (searchTerm) { - try { - const response = await axios.get( - `https://api.github.com/search/repositories?q=${searchTerm}`, - ); - return { - entries: response.data.items, - length: response.data.total_count, - }; - } catch (error) { - console.log( - chalk.bgBlueBright("Unexpected Error occured while fetching repos..."), - ); - process.exit(1); - } -} - -// Render a single repository -function renderRepo(repo) { - const { name, description, html_url } = repo; - console.log( - chalk.bgYellow(chalk.black("repo name :")), - "\t", - chalk.cyanBright(name), - "\n", - ); - console.log( - chalk.bgBlueBright(chalk.black("Description :")), - "\t", - chalk.bold(description), - "\n", - ); - console.log( - chalk.bgGreen(chalk.black("URL :")), - "\t", - chalk.yellow(chalk.underline(html_url)), - "\n", - ); -} - -// Prompt the user to select a repository from the list -async function promptRepoSelection(repos) { - const choices = repos.map((repo) => ({ name: repo.full_name, value: repo })); - return await inquirer.prompt([ - { - type: "rawlist", - name: "selectedRepo", - message: "Select a repository:", - loop: false, - choices, - }, - ]); -} - -// Fetch the contents of the selected repository as an object -// When response statusText is 'OK', the content object will be `fetchRepoContentsResponse().data` -// If response status code is >= 400, the `fetchRepoContentsResponse().data` will be error.response.data -async function fetchRepoContentsResponse(repoFullName) { - try { - const response = await axios.get( - `https://api.github.com/repos/${repoFullName}/contents`, - ); - if (response.statusText === "OK") { - return { - data: response.data, - status: Number(response.status), - }; - } - } catch (error) { - console.log( - chalk.bgRedBright(chalk.black(`${error.response.data.message}\n`)), - ); - return { - data: error.response.data, - status: Number(error.response.status), - }; - } -} - -// Render the repository contents as a folder structure -function renderRepoContents(contents, indent = " ") { - try { - contents.forEach((item) => { - if (item.type === "dir") { - console.log( - `${indent}`, - chalk.bgCyanBright(chalk.black(`${item.name}`)), - chalk.blueBright("/"), - ); - /* axios.get(item.url).then((response) => { - renderRepoContents(response.data, indent + " "); - }); */ - } else { - console.log(`${indent}${item.name}`); - } - }); - } catch (error) { - console.error( - chalk.bgBlue("Unexpected Error occured while rendering contents"), - ); - } -} - -const main = async () => { - // Prompt the user for a search term - const promptUser = await inquirer.prompt([ - { - type: "input", - name: "searchTerm", - message: chalk.cyan("Enter the search term:"), - }, - ]); - - const userInput = promptUser.searchTerm; - - const repos = await fetchRepos(userInput); - - if (repos.length < 1) { - console.log(`${chalk.red(`${repos.length} Repositories found!`)} `); - console.log("Nothing to show"); - process.exit(1); - } - const { selectedRepo } = await promptRepoSelection(repos.entries); - renderRepo(selectedRepo); - - const askUser = await inquirer.prompt([ - { - type: "input", - name: "want", - message: `${chalk.cyan(`view the repository contents?`)} (${chalk.green('y')}/${chalk.red('n')}) [default=${chalk.red('n')}] `, - }, - ]); - if (askUser.want === "yes" || askUser.want === "y" || askUser.want === "Y") { - const repoContentsResponse = await fetchRepoContentsResponse( - selectedRepo.full_name, - ); - if (repoContentsResponse.status < 400) { - console.log(chalk.green(`Contents of ${selectedRepo.full_name}:\n`)); - renderRepoContents(repoContentsResponse.data); - } else { - console.log( - `request returned ${repoContentsResponse.status} status response.\n`, - ); - } - } - const askAgain = await inquirer.prompt([ - { - type: "input", - name: "want", - message: `${chalk.cyan(`clone the repository?`)} (${chalk.green('y')}/${chalk.red('n')}) [default=${chalk.red('n')}] `, - }, - ]); - if ( - askAgain.want === "yes" || - askAgain.want === "y" || - askAgain.want === "Y" - ) { - const lastQuestion = await inquirer.prompt([ - { - type: "input", - name: "choice", - message: `${chalk.cyan('clone into a specific directory?')} (${chalk.green('y')}/${chalk.red('n')}) [default=${chalk.red('n')}] `, - }, - ]); - if ( - lastQuestion.choice === "yes" || - lastQuestion.choice === "Y" || - lastQuestion.choice === "y" - ) { - const directoryName = await inquirer.prompt([ - { - type: "input", - name: "input", - message: - chalk.cyan(`Enter the directory for cloning ${chalk.yellow('(WARNING: directory must NOT ALREADY exist!)')} -> `), - }, - ]); - console.log(chalk.bgMagentaBright(chalk.black("Cloning Initaited!\n"))); - exec( - `git clone https://github.com/${selectedRepo.full_name}.git ${directoryName.input}`, - (error, stdout, stderr) => { - if (error) { - console.log(chalk.red(error)); - process.exit(1); - } - console.log(stdout); - }, - ); - } else { - console.log(chalk.bgMagentaBright(chalk.black("Cloning Initaited!\n"))); - exec( - `git clone https://github.com/${selectedRepo.full_name}.git`, - (error, stdout, stderr) => { - if (error) { - console.log(chalk.red(error)); - process.exit(1); - } else { - console.log(stdout); - } - }, - ); - } - } -}; - -main(); +program + .parse() From fececac270a33bc2cd721a6c2ceaeb8117c10529 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Thu, 14 Nov 2024 13:23:23 +0530 Subject: [PATCH 06/82] util functions of the interactive repo search and clone flow for the unauthenticated user --- src/utils/unauthenticated/repo.js | 167 ++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/utils/unauthenticated/repo.js diff --git a/src/utils/unauthenticated/repo.js b/src/utils/unauthenticated/repo.js new file mode 100644 index 0000000..b42d48f --- /dev/null +++ b/src/utils/unauthenticated/repo.js @@ -0,0 +1,167 @@ +import axios from "axios"; +import search from "@inquirer/search"; +import chalk from "chalk"; + +async function getCurrentRateLimitsData() { + const rateLimits = await axios.get("https://api.github.com/rate_limit", { + headers: { "X-GitHub-Api-Version": "2022-11-28" }, + }); + + return rateLimits.data.resources; +} + +// Fetch repositories based on the search term +async function fetchRepos(searchTerm) { + try { + const rateLimitsData = await getCurrentRateLimitsData(); + const searchRequestsRemaining = Number(rateLimitsData.search.remaining); + if (searchRequestsRemaining > 0) { + const response = await axios.get( + `https://api.github.com/search/repositories?q=${searchTerm}`, { + headers: { "X-GitHub-Api-Version": "2022-11-28" }, + }); + return { + entries: response.data.items, + length: response.data.total_count, + }; + } else { + console.log( + chalk.yellow( + "\nYou ran out of search queries!\nTry again after some minutes!!\n", + ), + ); + console.log( + `Visit - https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api to learn more about GitHub API rate limits`, + ); + process.exit(1); + } + } catch (err) { + console.log("\n", err.message, "\n"); + process.exit(1); + } +} + +// Show Info of a single repository +function repoInfo(repo) { + const { name, description, html_url } = repo; + console.log(''); + console.log( + chalk.bgGreenBright(chalk.black("repo name :")), + "\t", + chalk.bold(name), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("Description :")), + "\t", + chalk.bold(description), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("URL :")), + "\t", + chalk.bold(chalk.underline(html_url)), + "\n", + ); +} + +function mapRepos(repos) { + return repos.map((repo) => ({ + name: repo.full_name, + value: repo, + description: repo.description, + })); +} + +// Prompt the user to select a repository from the list with Autocomplete +async function promptRepoSelection(repos) { + const choices = mapRepos(repos); + try { + return await search({ + message: `${chalk.greenBright("Select a repository: ")}${chalk.yellow("(Autocomplete Available)")}`, + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + if (!input) { + return choices; + } else { + const filteredChoices = choices.filter((choice) => + choice.name.includes(input), + ); + return filteredChoices; + } + }, + }); + } catch (_) { + console.log(chalk.yellow("Aborted! Exiting Gracefully...")); + process.exit(1); + } +} + +// Fetch the contents of the selected repository as an object +// When response statusText is 'OK', the content object will be `fetchRepoContentsResponse().data` +// If response status code is >= 400, the `fetchRepoContentsResponse().data` will be error.response.data +async function fetchRepoContentsResponse(repoFullName) { + try { + const rateLimits = await getCurrentRateLimitsData(); + const coreRequestsRemaining = Number(rateLimits.core.remaining); + + if (coreRequestsRemaining > 0) { + const response = await axios.get( + `https://api.github.com/repos/${repoFullName}/contents`, + ); + if (response.statusText === "OK") { + return { + data: response.data, + status: Number(response.status), + }; + } + } else { + console.log( + chalk.yellow( + `\nYou ran out of request limits!\nTry again after some minutes!!\n`, + ), + ); + } + } catch (error) { + console.error(error.response.data.message,'\n'); + return { + data: error.response.data, + status: Number(error.response.status), + }; + } +} + +// Render the repository contents as a folder structure +function renderRepoContents(contents, indent = " ") { + try { + contents.forEach((item) => { + if (item.type === "dir") { + console.log( + `${indent}`, + chalk.bgCyanBright(chalk.black(`${item.name}`)), + chalk.blueBright("/"), + ); + /* axios.get(item.url).then((response) => { + renderRepoContents(response.data, indent + " "); + }); */ + } else { + console.log(`${indent}${item.name}`); + } + }); + } catch (error) { + console.error( + chalk.bgBlue("Unexpected Error occured while rendering contents"), + ); + } +} + +export { + fetchRepos, + fetchRepoContentsResponse, + repoInfo, + promptRepoSelection, + renderRepoContents, +}; From e16e3313bc991f5e4b027f8632283e11982253b1 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Thu, 14 Nov 2024 13:24:03 +0530 Subject: [PATCH 07/82] interactive search and clone flow for the unauthenticated user --- src/utils/unauthenticated/interactiveFlow.js | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/utils/unauthenticated/interactiveFlow.js diff --git a/src/utils/unauthenticated/interactiveFlow.js b/src/utils/unauthenticated/interactiveFlow.js new file mode 100644 index 0000000..73936ed --- /dev/null +++ b/src/utils/unauthenticated/interactiveFlow.js @@ -0,0 +1,85 @@ +import input from "../input.js"; +import chalk from "chalk"; +import { exec } from "node:child_process"; +import { headerText } from "../headerText.js"; +import { + fetchRepos, + fetchRepoContentsResponse, + renderRepoContents, + repoInfo, + promptRepoSelection, +} from "./repo.js"; + +async function unAuthenticatedInteractiveFlow() { + console.log(headerText); + + // Prompt the user for a search term + const searchTerm = await input(chalk.greenBright("Enter the term to search repositories: ")); + const repos = await fetchRepos(searchTerm); + + if (repos.length < 1) { + console.log(`${chalk.red(`${repos.length} Repositories found!`)} `); + console.log("Nothing to show"); + process.exit(1); + } + const selectedRepo = await promptRepoSelection(repos.entries); + repoInfo(selectedRepo); + + let userInput = await input(`${chalk.greenBright(`view the repository contents?`)} (${chalk.green("y")}/${chalk.red("n")}) [default=${chalk.red("n")}] `); + + if (userInput === "yes" || userInput === "y" || userInput === "Y") { + const repoContentsResponse = await fetchRepoContentsResponse( + selectedRepo.full_name, + ); + if (repoContentsResponse.status < 400) { + console.log(chalk.greenBright(`Contents of ${selectedRepo.full_name}:\n`)); + renderRepoContents(repoContentsResponse.data); + } else { + console.log( + `request returned ${repoContentsResponse.status} status response.\n`, + ); + } + } + userInput = await input(`${chalk.greenBright(`clone the repository?`)} (${chalk.green("y")}/${chalk.red("n")}) [default=${chalk.red("n")}] `); + + if (userInput === "yes" || userInput === "y" || userInput === "Y") { + userInput = await input(`${chalk.greenBright("clone into a specific directory?")} (${chalk.green("y")}/${chalk.red("n")}) [default=${chalk.red("n")}] `); + + if (userInput === "yes" || userInput === "Y" || userInput === "y") { + const directoryName = await input( + chalk.greenBright(`Enter the directory for cloning${chalk.yellow("(WARNING: directory must NOT ALREADY exist!)")}->`), + ); + + console.log(chalk.bgMagentaBright(chalk.black("Cloning Initaited!\n"))); + + exec( + `git clone https://github.com/${selectedRepo.full_name}.git ${directoryName}`, + (error, stdout, stderr) => { + if (error) { + console.log(chalk.red(error)); + console.log(chalk.red(stderr)); + process.exit(1); + } + console.log(`${stdout}\n`); + console.log("Cloning Completed Succesfully!!!"); + }, + ); + } else { + console.log(chalk.bgMagentaBright(chalk.black("Cloning Initaited!\n"))); + exec( + `git clone https://github.com/${selectedRepo.full_name}.git`, + (error, stdout, stderr) => { + if (error) { + console.log(chalk.red(error)); + console.log(chalk.red(stderr)); + process.exit(1); + } else { + console.log(stdout); + } + }, + ); + } + } +} + +export { unAuthenticatedInteractiveFlow }; From cde14f24130dac673e0dda6ec42718ecc6dd0673 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Thu, 14 Nov 2024 13:25:58 +0530 Subject: [PATCH 08/82] util function for managing all single line console inputs --- src/utils/input.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/utils/input.js diff --git a/src/utils/input.js b/src/utils/input.js new file mode 100644 index 0000000..06eb4c3 --- /dev/null +++ b/src/utils/input.js @@ -0,0 +1,13 @@ +import { input as inputPrompt } from "@inquirer/prompts"; +import chalk from "chalk"; + +async function input(message) { + try { + return await inputPrompt({ message }); + } catch (err) { + console.log(chalk.yellow("Aborted! Exiting Gracefully...")); + process.exit(1); + } +} + +export default input; From fc75c5c365690783b57f86cbabebf1c65293896e Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Thu, 14 Nov 2024 14:22:55 +0530 Subject: [PATCH 09/82] added the Accept header to the GET requests --- src/utils/unauthenticated/repo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/unauthenticated/repo.js b/src/utils/unauthenticated/repo.js index b42d48f..f1dcfdc 100644 --- a/src/utils/unauthenticated/repo.js +++ b/src/utils/unauthenticated/repo.js @@ -4,7 +4,7 @@ import chalk from "chalk"; async function getCurrentRateLimitsData() { const rateLimits = await axios.get("https://api.github.com/rate_limit", { - headers: { "X-GitHub-Api-Version": "2022-11-28" }, + headers: { "X-GitHub-Api-Version": "2022-11-28", "Accept": "application/vnd.github+json" }, }); return rateLimits.data.resources; @@ -18,7 +18,7 @@ async function fetchRepos(searchTerm) { if (searchRequestsRemaining > 0) { const response = await axios.get( `https://api.github.com/search/repositories?q=${searchTerm}`, { - headers: { "X-GitHub-Api-Version": "2022-11-28" }, + headers: { "X-GitHub-Api-Version": "2022-11-28", "Accept": "application/vnd.github+json" }, }); return { entries: response.data.items, From e30f71a1a46e09b6ecdb7d573272a3aad6333c64 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:20:43 +0530 Subject: [PATCH 10/82] feat:initial version of CLI with only 2 commands --- index.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index edea98e..ef24245 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,61 @@ #!/usr/bin/env node import { Command } from 'commander'; +import { clearToken, getStoredAuthType, getStoredToken } from './src/auth/tokenHelpers.js'; +import config from "./src/auth/config.js"; +import { userProfile, login } from "./src/utils/authenticated/requests.js"; const program = new Command(); +program.addHelpText('beforeAll', await ( async () => { + const { headerText } = await import("./src/utils/headerText.js"); + return headerText; +})()); + program - .parse() + .command('auth') + .description("authorize or unauthorize gitfm with your GitHub") + .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token") + .option('--logout', "logout from the CLI and delete your token") + .action(async (options) => { + + if (options.logout) { + const { Octokit } = await import("@octokit/rest"); + const storedToken = getStoredToken(config.TOKEN_FILE); + + if (storedToken === null) { + console.error("Error: Token not found"); + console.error("You are not authenticated"); + + process.exit(1); + } + + const octokit = new Octokit({ auth: storedToken}); + + if (getStoredAuthType(config.TOKEN_FILE) === "oauth") { + octokit.rest.apps.deleteToken({ + client_id: config.CLIENT_ID, + access_token: storedToken, + }); + } + + clearToken(config.TOKEN_FILE); + + } else { + if (options.login === "token") { + await login("token"); + } else if (options.login === "web") { + await login("oauth"); + } else { + await login(); + } + } + }); + +program + .command('profile') + .description("fetch a minimal overview of your GitHub profile") + .action(async () => { + const octokit = await login(); + await userProfile(octokit); + }); + +program.parse(); From b2db237810096f5253113d4ebbb37bdf6a65afeb Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:22:01 +0530 Subject: [PATCH 11/82] util file with currently two request functions - profile fetching and login/logout --- src/utils/authenticated/requests.js | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/utils/authenticated/requests.js diff --git a/src/utils/authenticated/requests.js b/src/utils/authenticated/requests.js new file mode 100644 index 0000000..77a8ecb --- /dev/null +++ b/src/utils/authenticated/requests.js @@ -0,0 +1,66 @@ +const userProfile = async (octokit) => { + const { default: boxen } = await import("boxen"); + const { default: chalk } = await import("chalk"); + const response = await octokit.rest.users.getAuthenticated(); + const { + data: { + login: userName, + followers, + following, + bio, + owned_private_repos: privateRepos, + public_repos: publicRepos, + }, + } = response; + + const profileInfo = ` + Hello, - ${userName}! + ${bio} + You have ${followers} followers & you follow ${following} + + You currently have ${publicRepos} public repos and ${privateRepos} private repos. + `; + + console.log(boxen(profileInfo, + {title: `${chalk.bgGreenBright('profile')}`, titleAlignment: 'center'} + )); +}; + +const login = async (authType) => { // authType (optional) = "oauth" | "token" + const config = await import("../../auth/config.js"); + const { getStoredToken } = await import("../../auth/tokenHelpers.js"); + const { checkTokenValidity } = await import("../../auth/index.js"); + const storedToken = getStoredToken(config.default.TOKEN_FILE); + + console.log("checking if already authorized!"); + + if (storedToken !== null) { + if (await checkTokenValidity(storedToken)) { + console.log("\nUser is already authorized\n"); + const { Octokit } = await import("@octokit/rest"); + const octokit = new Octokit({ auth:storedToken }); + return octokit; + } else { + console.error("\nToken Expired! Initiating Authorization\n"); + const { clearToken } = await import("../../auth/tokenHelpers.js"); + clearToken(); + return await login(authType); + } + } else { + if (authType === "oauth") { + const { authLogin } = await import("../../auth/index.js"); + const octokit = await authLogin("oauth"); + return octokit; + } else if (authType === "token") { + const { authLogin } = await import("../../auth/index.js"); + const octokit = await authLogin("token"); + return octokit; + } else { + const { authLoginInteractive } = await import("../../auth/index.js"); + const octokit = await authLoginInteractive(); + return octokit; + } + } +} + +export { login, userProfile }; From bc336028818ccacb77f9f732389862d7d7c32075 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:23:31 +0530 Subject: [PATCH 12/82] object for handling unauthentication and authentication error related issues --- src/auth/unauthentication.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/auth/unauthentication.js diff --git a/src/auth/unauthentication.js b/src/auth/unauthentication.js new file mode 100644 index 0000000..8fb9c02 --- /dev/null +++ b/src/auth/unauthentication.js @@ -0,0 +1,18 @@ +/* + * for handling unauthentication and authentication error related issues + * */ + +export default { + type: "unauthenticated", + reason: (errorCode) => { + if (errorCode === "access-denied") { + return "User has denied the request. The authorization process has been canceled."; + } else if (errorCode === "incorrect_device_code") { + return "The device code provided is not valid."; + } else if (errorCode === "expired_token") { + return "The device code has expired. Please start the process again."; + } else { + return "The app's access has been revoked by GitHub!\n\t Or Unknown Error : ("; + } + } +} From f8c2e7f1170135d15e3207669fbdd1798c13d52c Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:24:08 +0530 Subject: [PATCH 13/82] functions for storing, accessing and deleting token from configuration file --- src/auth/tokenHelpers.js | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/auth/tokenHelpers.js diff --git a/src/auth/tokenHelpers.js b/src/auth/tokenHelpers.js new file mode 100644 index 0000000..9511266 --- /dev/null +++ b/src/auth/tokenHelpers.js @@ -0,0 +1,44 @@ +import { existsSync, readFileSync, writeFileSync } from "fs"; + +// Check if a stored token exists and is valid +function getStoredToken(TOKEN_FILE) { + if (existsSync(TOKEN_FILE)) { + const tokenData = JSON.parse(readFileSync(TOKEN_FILE, "utf-8")); + if (tokenData && tokenData.token) { + return tokenData.token; + } + } else { + return null; + } +} + +function getStoredAuthType(TOKEN_FILE) { + if (existsSync(TOKEN_FILE)) { + const tokenData = JSON.parse(readFileSync(TOKEN_FILE, "utf-8")); + if (tokenData && tokenData.type) { + return tokenData.type; + } + } else { + return null; + } +} + + +// Save token and the authType to a file with type key +function saveToken(tokenData, TOKEN_FILE) { + writeFileSync(TOKEN_FILE, JSON.stringify(tokenData, null, 2)); +} + +// Clear token from storage and set type to unauthenticated +function clearToken(TOKEN_FILE) { + if (existsSync(TOKEN_FILE)) { + const dataToSave = { + token: null, + type: "unauthenticated" + }; + writeFileSync(TOKEN_FILE, JSON.stringify(dataToSave, null, 2)); + } +} + +export { getStoredToken, getStoredAuthType, saveToken, clearToken }; + From 08aef87b7b034bb71e33d27de2444b3e055009ae Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:24:41 +0530 Subject: [PATCH 14/82] functions for implementaing OAuth 2.0 authentication via device flow --- src/auth/oauth.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/auth/oauth.js diff --git a/src/auth/oauth.js b/src/auth/oauth.js new file mode 100644 index 0000000..8598a65 --- /dev/null +++ b/src/auth/oauth.js @@ -0,0 +1,71 @@ +/* OAuth Authentication with Device Flow */ +import open, { apps } from "open"; +import { createDeviceCode, exchangeDeviceCode } from "@octokit/oauth-methods"; +import config from "./config.js"; +import { promisify } from "node:util"; +import { type as osType } from "os"; +const sleep = promisify(setTimeout); + +async function getOAuthenticationObject() { + const { + data: { device_code, user_code, verification_uri, interval }, + } = await createDeviceCode({ + clientType: "oauth-app", + clientId: config.CLIENT_ID, + scopes: ["repo", "user", "notifications", "gist"], // oauth scopes + }); + + console.log(`\nYour OAuth User Code is - \n\t${user_code}\n`); + await sleep( + 1500, + console.log("Opening the Browser Window to Enter the User Code"), + ); + console.log( + "Waiting for the user to grant access through the browser and then close the window ...", + ); + if (osType() === "Windows_NT") { + await open(verification_uri, { wait: true, app: { name: apps.browser } }); + } else { + await open(verification_uri, { wait: true }); + } + + let currentInterval = interval; + while (true) { + try { + const { authentication } = await sleep(currentInterval*1000, await exchangeDeviceCode({ + clientType: "oauth-app", + clientId: config.CLIENT_ID, + code: device_code, + })); + return authentication; // Exit loop and return authentication object + } catch (error) { + + if (error.status === 400) { + const errorCode = error.response.data.error; + + if (errorCode === "authorization_pending") { + console.log("Authorization still pending... waiting before retrying"); + await sleep(currentInterval * 1000); + } else if (errorCode === "slow_down") { + console.log("Received slow_down response, increasing interval"); + currentInterval += 5; // Increase interval as per GitHub's requirement + await sleep(currentInterval * 1000); + } else if (errorCode === "expired_token") { + return { error: errorCode }; // Exit loop as a new device code is needed + } else if (errorCode === "incorrect_device_code") { + return { error: errorCode }; // Exit loop as there is a fundamental error + } else if (errorCode === "access_denied") { + return { error: errorCode }; // Exit loop as the process cannot continue + } else { + console.error(`Unexpected error: ${errorCode}`); + return {error: `Unexpected 400 status error: ${errorCode}`}; // 400 status unknown errors + } + } else { + console.error("An unexpected error occurred:", error.message); + throw error; // Re-throw non-400 status unknown errors + } + } + } +} + +export { getOAuthenticationObject }; From 09f880659be24261ac036b5e6185f3c0276609ed Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:26:11 +0530 Subject: [PATCH 15/82] config object that contains client id and config file path --- src/auth/config.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/auth/config.js diff --git a/src/auth/config.js b/src/auth/config.js new file mode 100644 index 0000000..61822ac --- /dev/null +++ b/src/auth/config.js @@ -0,0 +1,9 @@ +import path from "node:path"; +import os from "os"; + +const config = { + CLIENT_ID: "Ov23li5gcNYDdMUWPeCt", + TOKEN_FILE: path.join(os.homedir(), '.gitfmrc.json'), +} + +export default config; From 31c7143dacbb3f185acfc34afd5180083689c76d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:26:55 +0530 Subject: [PATCH 16/82] custom authentication object for convenience --- src/auth/authObject.js | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/auth/authObject.js diff --git a/src/auth/authObject.js b/src/auth/authObject.js new file mode 100644 index 0000000..29139a2 --- /dev/null +++ b/src/auth/authObject.js @@ -0,0 +1,46 @@ +import unauthentication from "./unauthentication.js"; + +async function getAuthenticationObject(authType = "token") { + try { + if (authType === "oauth") { + const { getOAuthenticationObject } = await import("./oauth.js"); + const oAuthObject = await getOAuthenticationObject(); + if (oAuthObject.hasOwnProperty('error')) { + return { + authStatus: unauthentication.type, + reason: unauthentication.reason(oAuthObject.error), + token: "NA", + } + } else { + return { + authStatus: "authenticated", + reason: "user authorized via Device Flow in Browser", + token: oAuthObject.token, + } + } + } else { + const { default:input } = await import("../utils/input.js"); + const { promisify } = await import("node:util"); + const sleep = promisify(setTimeout); + + console.log('Create a new personal access token from your GitHub Developer Settings'); + console.log(`Check only these scopes from the checkboxes when creating a new token - \n\t${['repo', 'user', 'notifications', 'gist']} `) + + const personalAcessToken = await sleep(2000, await input("Enter the personal access token ")); + + return { + authStatus: "toBeAuthenticated", + reason: "user provided personal access token", + token: personalAcessToken, + }; + } + } catch (err) { + return { + authStatus: unauthentication.type, + reason: err.message, + token: "NA", + }; + } +} + +export { getAuthenticationObject }; From f933f947fbcdc964debd607a42ee3617269c0d28 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:31:21 +0530 Subject: [PATCH 17/82] functions for returning and saving token data only after successful authentication status and responses and verification (only for personal access token) --- src/auth/getAuthenticated.js | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/auth/getAuthenticated.js diff --git a/src/auth/getAuthenticated.js b/src/auth/getAuthenticated.js new file mode 100644 index 0000000..f81c0d6 --- /dev/null +++ b/src/auth/getAuthenticated.js @@ -0,0 +1,78 @@ +import { getAuthenticationObject } from "./authObject.js"; +import { Octokit } from "@octokit/rest"; +import chalk from "chalk"; + +const getLogin = async (token) => { + const octokit = new Octokit({auth:token}); + const response = await octokit.rest.users.getAuthenticated(); + return response.data.login; +} + +async function checkTokenValidity(token) { + try { + const userName = await getLogin(token); // If this works without errors, the token is valid + if (userName.length > 0) { + return true; + } else { + return false; + } + } catch (error) { + if (error.status === 401) { + return false; + } else { + throw new Error("Error checking token:", error.message); + } + } +} + +async function verifyAuthStatus(token, authStatus, reason) { + try { + if (token !== "NA") { // if token is available + if (authStatus === "toBeAuthenticated") { // condition for personal access token + const tokenIsValid = await checkTokenValidity(token); + if (tokenIsValid) { + console.log(chalk.greenBright(reason)); + console.log('provided token is valid.\n\tInitiating Authentication...'); + return "readyToBeAuthenticated"; + } else { // Invalid access token provided by user + throw new Error("Invalid token: Access denied"); + } + } else { // condition for oauth + console.log(chalk.greenBright(reason)); + return authStatus; + } + } else { // condition for unsuccessful oauth + console.error(reason); + return authStatus; + } + } catch (err) { // condition for unknown errors + console.error(err.message); + process.exit(1); + } +} + +async function getVerifiedAuthToken(authType = "token") { + if (authType === "oauth") { + const { token, authStatus, reason } = await getAuthenticationObject("oauth"); + const currentAuthStatus = await verifyAuthStatus(token, authStatus, reason); + if (currentAuthStatus === "unauthenticated") { + process.exit(1); + } else { + const tokenData = { token:token, type:"oauth" }; + return tokenData; + } + } else { + const {token, authStatus, reason } = await getAuthenticationObject("token"); + const currentAuthStatus = await verifyAuthStatus(token, authStatus, reason); + if (currentAuthStatus === "readyToBeAuthenticated") { + const tokenData = { token:token, type:"token" }; + return tokenData; + } else { + process.exit(1); + } + } +} + +export { + getVerifiedAuthToken, checkTokenValidity, +} From a01792a21349a3dbe3b6fdedb082c6fef63e3e33 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 02:33:55 +0530 Subject: [PATCH 18/82] final index of the auth module providing 3 functions for CLI commands - authLogin, authLoginInteractive and checkTokenValidity --- src/auth/index.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/auth/index.js diff --git a/src/auth/index.js b/src/auth/index.js new file mode 100644 index 0000000..cc05c60 --- /dev/null +++ b/src/auth/index.js @@ -0,0 +1,42 @@ +const { getVerifiedAuthToken, checkTokenValidity } = await import("./getAuthenticated.js"); +const { Octokit } = await import("@octokit/rest"); +const { saveToken } = await import("./tokenHelpers.js"); +import config from "./config.js"; + +const authLoginInteractive = async () => { + console.log("Choose your preferred way to authenticate\n\t 1. Browser Login with passcode, 2. personal access token"); + console.log("Enter 1 to begin Browser Login, otherwise token authentication will be initiated"); + + const { default:input } = await import("../utils/input.js"); + const choice = await input("Enter your choice ->"); + + try { + const numberChoice = Number(choice); + + if (numberChoice === 1) { + return await authLogin("oauth"); + } else if (isNaN(numberChoice)) { + console.error("Invalid Input: Auth aborted!"); + process.exit(1); + } else { + return await authLogin("token"); + } + + } catch (error) { + console.error(error.message); + process.exit(1); + } + +} + +const authLogin = async (authType) => { + const tokenData = + authType === "oauth" + ? await getVerifiedAuthToken("oauth") + : await getVerifiedAuthToken(); + saveToken(tokenData, config.TOKEN_FILE); + const octokit = new Octokit({ auth: tokenData.token }); + return octokit; +}; + +export { authLoginInteractive, authLogin, checkTokenValidity }; From 325351c69b943dbc6ff27c36fe272c16f1504f98 Mon Sep 17 00:00:00 2001 From: Debajyati Dey <127122455+Debajyati@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:03:32 +0530 Subject: [PATCH 19/82] Update README.md --- README.md | 44 +------------------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/README.md b/README.md index b0d8e86..af8c63e 100644 --- a/README.md +++ b/README.md @@ -8,53 +8,11 @@ First, make sure you have installed Node.js. Then install gitFM globally by runn ```bash npm install -g gitfm -gitfm ``` -or, run it without locally installing it - - -```bash -npx gitfm -``` - -## How to use - -gitFM has a simple command line interface that guides you through the process of searching, selecting, and operating the repository. - -### Search Repositories - -When you run gitFM, it will prompt you to enter a search keyword. For example, if you want to search for a repository with the name "express", you can enter: - -```bash -express -``` - - in the search input. - -### Select Repository - -gitFM returns a list of search results. You can select one from the Repository list. - -**NOTE:** There's no reverse motion support for now. Means once you enter in the list of repos after the search input you can't go back to the search input. - -### View Repositories contents - -After selecting a Repo, gitFM will display detailed information about the Repo, including the Repositories name, description, and GitHub link. You can continue viewing the contents of . - -### Clone Repositories - -If you select a Repository, gitFM will prompt you to agree cloning the Repository locally. For example, if you want to create a repository in the "my-repo" directory, you can specify: - -```bash -my-repo -``` -in the next input prompt where it will ask you if want to clone into a specific directory. - -## Summary: -gitFM is a convenient command-line tool that can be used to search, view and clone GitHub Repositories. Through this tool, you can easily browse and explore the code repository on GitHub from you fav commandline. ## Feedback and contributions -If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue or pull request in the GitHub repository. +If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the GitHub repository. GitHub repository: https://github.com/Debajyati/gitFM From 0cfca9773c8fe86821f85e1acec758f92c17e099 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 13:12:04 +0530 Subject: [PATCH 20/82] restructured project filetree --- src/{ => gh}/auth/authObject.js | 0 src/{ => gh}/auth/config.js | 0 src/{ => gh}/auth/getAuthenticated.js | 0 src/{ => gh}/auth/index.js | 0 src/{ => gh}/auth/oauth.js | 2 +- src/{ => gh}/auth/tokenHelpers.js | 0 src/{ => gh}/auth/unauthentication.js | 0 src/{ => gh}/utils/authenticated/requests.js | 4 ++-- src/{ => gh}/utils/headerText.js | 0 src/{ => gh}/utils/input.js | 0 src/{ => gh}/utils/unauthenticated/interactiveFlow.js | 0 src/{ => gh}/utils/unauthenticated/repo.js | 0 12 files changed, 3 insertions(+), 3 deletions(-) rename src/{ => gh}/auth/authObject.js (100%) rename src/{ => gh}/auth/config.js (100%) rename src/{ => gh}/auth/getAuthenticated.js (100%) rename src/{ => gh}/auth/index.js (100%) rename src/{ => gh}/auth/oauth.js (94%) rename src/{ => gh}/auth/tokenHelpers.js (100%) rename src/{ => gh}/auth/unauthentication.js (100%) rename src/{ => gh}/utils/authenticated/requests.js (95%) rename src/{ => gh}/utils/headerText.js (100%) rename src/{ => gh}/utils/input.js (100%) rename src/{ => gh}/utils/unauthenticated/interactiveFlow.js (100%) rename src/{ => gh}/utils/unauthenticated/repo.js (100%) diff --git a/src/auth/authObject.js b/src/gh/auth/authObject.js similarity index 100% rename from src/auth/authObject.js rename to src/gh/auth/authObject.js diff --git a/src/auth/config.js b/src/gh/auth/config.js similarity index 100% rename from src/auth/config.js rename to src/gh/auth/config.js diff --git a/src/auth/getAuthenticated.js b/src/gh/auth/getAuthenticated.js similarity index 100% rename from src/auth/getAuthenticated.js rename to src/gh/auth/getAuthenticated.js diff --git a/src/auth/index.js b/src/gh/auth/index.js similarity index 100% rename from src/auth/index.js rename to src/gh/auth/index.js diff --git a/src/auth/oauth.js b/src/gh/auth/oauth.js similarity index 94% rename from src/auth/oauth.js rename to src/gh/auth/oauth.js index 8598a65..84f3e03 100644 --- a/src/auth/oauth.js +++ b/src/gh/auth/oauth.js @@ -21,7 +21,7 @@ async function getOAuthenticationObject() { console.log("Opening the Browser Window to Enter the User Code"), ); console.log( - "Waiting for the user to grant access through the browser and then close the window ...", + "Waiting for the user to grant access through the browser ...", ); if (osType() === "Windows_NT") { await open(verification_uri, { wait: true, app: { name: apps.browser } }); diff --git a/src/auth/tokenHelpers.js b/src/gh/auth/tokenHelpers.js similarity index 100% rename from src/auth/tokenHelpers.js rename to src/gh/auth/tokenHelpers.js diff --git a/src/auth/unauthentication.js b/src/gh/auth/unauthentication.js similarity index 100% rename from src/auth/unauthentication.js rename to src/gh/auth/unauthentication.js diff --git a/src/utils/authenticated/requests.js b/src/gh/utils/authenticated/requests.js similarity index 95% rename from src/utils/authenticated/requests.js rename to src/gh/utils/authenticated/requests.js index 77a8ecb..74b7e59 100644 --- a/src/utils/authenticated/requests.js +++ b/src/gh/utils/authenticated/requests.js @@ -32,11 +32,11 @@ const login = async (authType) => { // authType (optional) = "oauth" | "token" const { checkTokenValidity } = await import("../../auth/index.js"); const storedToken = getStoredToken(config.default.TOKEN_FILE); - console.log("checking if already authorized!"); + console.log("checking if already authorized..."); if (storedToken !== null) { if (await checkTokenValidity(storedToken)) { - console.log("\nUser is already authorized\n"); + console.log("\nUser is already authorized!\n"); const { Octokit } = await import("@octokit/rest"); const octokit = new Octokit({ auth:storedToken }); return octokit; diff --git a/src/utils/headerText.js b/src/gh/utils/headerText.js similarity index 100% rename from src/utils/headerText.js rename to src/gh/utils/headerText.js diff --git a/src/utils/input.js b/src/gh/utils/input.js similarity index 100% rename from src/utils/input.js rename to src/gh/utils/input.js diff --git a/src/utils/unauthenticated/interactiveFlow.js b/src/gh/utils/unauthenticated/interactiveFlow.js similarity index 100% rename from src/utils/unauthenticated/interactiveFlow.js rename to src/gh/utils/unauthenticated/interactiveFlow.js diff --git a/src/utils/unauthenticated/repo.js b/src/gh/utils/unauthenticated/repo.js similarity index 100% rename from src/utils/unauthenticated/repo.js rename to src/gh/utils/unauthenticated/repo.js From 2a671ef862e2d02a1c8a4233f3ecce1c68dfc4a4 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 13:14:01 +0530 Subject: [PATCH 21/82] feat:organised imports; set cli name,description & version directly from package.json; small changes to auth command --- index.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index ef24245..90931f9 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,31 @@ #!/usr/bin/env node import { Command } from 'commander'; -import { clearToken, getStoredAuthType, getStoredToken } from './src/auth/tokenHelpers.js'; -import config from "./src/auth/config.js"; -import { userProfile, login } from "./src/utils/authenticated/requests.js"; +import { clearToken, getStoredAuthType, getStoredToken } from './src/gh/auth/tokenHelpers.js'; +import config from "./src/gh/auth/config.js"; +import { userProfile, login } from "./src/gh/utils/authenticated/requests.js"; + +const packageJson = await (async ()=>{ + const { createRequire } = await import("node:module"); + const require = createRequire(import.meta.url); + return require("./package.json"); +})(); const program = new Command(); program.addHelpText('beforeAll', await ( async () => { - const { headerText } = await import("./src/utils/headerText.js"); + const { headerText } = await import("./src/gh/utils/headerText.js"); return headerText; })()); program - .command('auth') + .name(`${packageJson.name}`) + .description(`${packageJson.description}`) + .version(`${packageJson.version}`); + +program + .command('ghauth') .description("authorize or unauthorize gitfm with your GitHub") .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token") - .option('--logout', "logout from the CLI and delete your token") + .option('--logout', "logout from the CLI and delete your GitHub token") .action(async (options) => { if (options.logout) { From 6b9eb9278af88a0dc0ef6184902a09f34d47b2ae Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 15 Nov 2024 13:14:33 +0530 Subject: [PATCH 22/82] update version number and project description --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9d37da5..edf0fe8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gitfm", - "version": "1.1.0", - "description": "A TUI App for searching github repos, fetching as folder structure and cloning", + "version": "2.0.0", + "description": "A CLI App for searching GitHub/GitLab repos, fetching as folder structure and cloning", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From 6ac6193ea03b1957957447ab302875614459cf70 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sat, 16 Nov 2024 13:07:10 +0530 Subject: [PATCH 23/82] new version --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6830c4..4122cec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gitfm", - "version": "1.1.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitfm", - "version": "1.1.0", + "version": "2.0.0", "license": "ISC", "dependencies": { "@inquirer/prompts": "^7.0.1", From c956a8f27cdadc9e0f649ad1bf7cb7fe40f2e9fc Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sat, 16 Nov 2024 13:07:44 +0530 Subject: [PATCH 24/82] feat: internal JSON file to store the token --- src/gl/config.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/gl/config.json diff --git a/src/gl/config.json b/src/gl/config.json new file mode 100644 index 0000000..13b71c3 --- /dev/null +++ b/src/gl/config.json @@ -0,0 +1,3 @@ +{ + "token": "NA" +} From c03e246fb59b345542ae21e37e1105cf0c0919ab Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sat, 16 Nov 2024 13:08:38 +0530 Subject: [PATCH 25/82] feat: gitlab user authentication using personal access token --- src/gl/auth.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/gl/auth.js diff --git a/src/gl/auth.js b/src/gl/auth.js new file mode 100644 index 0000000..403f25d --- /dev/null +++ b/src/gl/auth.js @@ -0,0 +1,35 @@ +import input from '../gh/utils/input.js'; + +const baseURL = "https://https://gitlab.com/api/v4"; + +const tokenAuthenticate = async () => { + console.log('Create a new personal access token from your GitLab profile'); + console.log('Choose only \'api\' or both \'read_api\' and \'read-user\' from the checkboxes.'); + const token = await input('Enter the token here'); + + const url = `${baseURL}/personal_access_tokens`; + + try { + const response = await fetch(url,{ + method: "GET", + headers: { + "PRIVATE-TOKEN": token, + } + }); + + const data = await response.json(); + + if (data.message === "401 Unauthorized") { + console.error("invalid access token: Authorization aborted!"); + process.exit(1); + } else { + console.log(data); + return token; + } + } catch (error) { + console.error(error.message); + process.exit(1); + } +} + +export { tokenAuthenticate }; From 77865f5346401660069390b0f59794f5d3e20447 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sat, 16 Nov 2024 13:10:05 +0530 Subject: [PATCH 26/82] feat: command for gitlab authentication --- index.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/index.js b/index.js index 90931f9..20db66b 100644 --- a/index.js +++ b/index.js @@ -69,4 +69,18 @@ program await userProfile(octokit); }); +program + .command('glauth') + .description("authorize or unauthorize gitfm with your GitLab") + .option('--login', "login with a personal access token") + .option('--logout', "logout and revoke the token") + .action(async (options) => { + if (options.logout) { + // TODO: + } else { + const { tokenAuthenticate } = await import("./src/gl/auth.js"); + return await tokenAuthenticate(); + } + }) + program.parse(); From 192a2dd85be4a15dac9bff4675ac9303192f9c5a Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 02:10:14 +0530 Subject: [PATCH 27/82] refactored gitlab auth --- src/gh/auth/tokenHelpers.js | 2 +- src/gl/auth.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gh/auth/tokenHelpers.js b/src/gh/auth/tokenHelpers.js index 9511266..2c2c087 100644 --- a/src/gh/auth/tokenHelpers.js +++ b/src/gh/auth/tokenHelpers.js @@ -1,6 +1,6 @@ import { existsSync, readFileSync, writeFileSync } from "fs"; -// Check if a stored token exists and is valid +// Check if a stored token exists function getStoredToken(TOKEN_FILE) { if (existsSync(TOKEN_FILE)) { const tokenData = JSON.parse(readFileSync(TOKEN_FILE, "utf-8")); diff --git a/src/gl/auth.js b/src/gl/auth.js index 403f25d..e6440a0 100644 --- a/src/gl/auth.js +++ b/src/gl/auth.js @@ -1,10 +1,10 @@ import input from '../gh/utils/input.js'; -const baseURL = "https://https://gitlab.com/api/v4"; +const baseURL = "https://gitlab.com/api/v4"; const tokenAuthenticate = async () => { console.log('Create a new personal access token from your GitLab profile'); - console.log('Choose only \'api\' or both \'read_api\' and \'read-user\' from the checkboxes.'); + console.log('Choose both \'read_api\' and \'read-user\' scopes from the checkboxes.'); const token = await input('Enter the token here'); const url = `${baseURL}/personal_access_tokens`; @@ -20,10 +20,10 @@ const tokenAuthenticate = async () => { const data = await response.json(); if (data.message === "401 Unauthorized") { - console.error("invalid access token: Authorization aborted!"); + console.error("invalid access token: Authentication aborted!"); process.exit(1); } else { - console.log(data); + console.log("Authentication Successful"); return token; } } catch (error) { From 6c187363aaab8eab2cfb33321dfd16e6fe991584 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 02:10:59 +0530 Subject: [PATCH 28/82] change gitlab token config default value --- src/gl/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/config.json b/src/gl/config.json index 13b71c3..9ccfc6b 100644 --- a/src/gl/config.json +++ b/src/gl/config.json @@ -1,3 +1,3 @@ { - "token": "NA" + "token": null } From f4ad1b99a795aeefc17642284e1f9eff96504321 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 02:11:53 +0530 Subject: [PATCH 29/82] requests module for all GitLab REST API requests --- src/gl/requests.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/gl/requests.js diff --git a/src/gl/requests.js b/src/gl/requests.js new file mode 100644 index 0000000..9fbb2d1 --- /dev/null +++ b/src/gl/requests.js @@ -0,0 +1,98 @@ +import path from "node:path"; + +// JSON import of the config file containing the token +const configJson = await (async ()=>{ + const { createRequire } = await import("node:module"); + const require = createRequire(import.meta.url); + return require("./config.json"); +})(); + +const CONFIG_FILE = path.join(__dirname, "config.json"); // file path + +const baseURL = "https://gitlab.com/api/v4"; + +async function fetchErrorHandlerCallback(error) { + if (typeof error.json === "function") { + try { + const jsonError = await error.json(); + console.error("Json error from API"); + console.log(jsonError); + } catch (genericError) { + console.error("Generic error from API"); + console.log(genericError.statusText); + } + } else { + console.error("Fetch error"); + console.log(error); + } +} + +async function revokeToken(token) { + const url = `${baseURL}/personal_access_tokens/self`; + try { + const response = await fetch(`${url}?${new URLSearchParams({ "expires_at" : expirationDate }).toString()}`, { + method: "DELETE", + headers: { + "PRIVATE-TOKEN": token, + } + }); + const data = await response.json(); + + if (data.message === "204: No Content") { + return "successful"; + } else if (data.message === "400: Bad Request") { + return "unsuccessful"; + } else if (data.message === "401: Unauthorized") { + return "invalid"; + } + } catch (error) { + await fetchErrorHandlerCallback(error); + } +} + +async function rotateToken(token, expirationDate) { + const url = `${baseURL}/personal_access_tokens/self/rotate`; + + try { + const response = await fetch( + `${url}?${new URLSearchParams({ expires_at: expirationDate }).toString()}`, + { + method: "POST", + headers: { + "PRIVATE-TOKEN": token, + } + } + ); + if (response.ok) { + const data = await response.json(); + console.log("Token Rotated Successfully!"); + return data.token; + } else { + if (response.status === 400) { + console.error("Not Rotated Successfully"); + process.exit(1); + } else if (response.status === 401) { + console.error("Either The Token Has Expired Or Has Been Revoked Already"); + process.exit(1); + } else if (response.status === 403) { + console.error("The Token Is Not Allowed To Rotate Itself"); + process.exit(1); + } else if (response.status === 405) { + console.error("The Token Is Not A Personal Access Token"); + process.exit(1); + } else { + console.error(`Unknown Status ${response.status} Error Occurred!`); + process.exit(1); + } + } + } catch (error) { + await fetchErrorHandlerCallback(error); + } +} + +export { + CONFIG_FILE, + configJson, + revokeToken, + rotateToken, +} From 530c5ec5360c51c49a04e8881f765588cdfa218f Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 02:12:20 +0530 Subject: [PATCH 30/82] module for managing gitlab token --- src/gl/tokenhelpers.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/gl/tokenhelpers.js diff --git a/src/gl/tokenhelpers.js b/src/gl/tokenhelpers.js new file mode 100644 index 0000000..bbb60c6 --- /dev/null +++ b/src/gl/tokenhelpers.js @@ -0,0 +1,19 @@ +import { existsSync, writeFileSync } from "fs"; + +// Save token and the authType to a file with type key +function saveToken(tokenData, CONFIG_FILE) { + writeFileSync(CONFIG_FILE, JSON.stringify(tokenData, null, 2)); +} + +// Clear token from storage +function clearToken(CONFIG_FILE) { + if (existsSync(CONFIG_FILE)) { + const dataToSave = { + token: null, + }; + writeFileSync(CONFIG_FILE, JSON.stringify(dataToSave, null, 2)); + } +} + +export { saveToken, clearToken }; + From 7d22132d3b4a3dcf710db65b0f15f7a0f7312de6 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 02:12:58 +0530 Subject: [PATCH 31/82] gitlab auth commands --- index.js | 65 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 20db66b..8693867 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ import { Command } from 'commander'; import { clearToken, getStoredAuthType, getStoredToken } from './src/gh/auth/tokenHelpers.js'; import config from "./src/gh/auth/config.js"; import { userProfile, login } from "./src/gh/utils/authenticated/requests.js"; +import { CONFIG_FILE as GITLAB_CONFIG_FILE } from './src/gl/requests.js'; const packageJson = await (async ()=>{ const { createRequire } = await import("node:module"); @@ -61,26 +62,64 @@ program } }); -program - .command('profile') - .description("fetch a minimal overview of your GitHub profile") - .action(async () => { - const octokit = await login(); - await userProfile(octokit); - }); - program .command('glauth') .description("authorize or unauthorize gitfm with your GitLab") .option('--login', "login with a personal access token") .option('--logout', "logout and revoke the token") + .option('--rotate ', "rotate the personal access token with an Expiry Date (NOTE: Expiry Date has to be in YYYY-MM-DD format!)") .action(async (options) => { - if (options.logout) { - // TODO: + try { + if (options.logout) { + const { revokeToken, configJson } = await import("./src/gl/requests.js"); + const token = configJson.token; + if (token !== null) { + const resStatus = await revokeToken(token); + if (resStatus === "successful") { + console.log("Token successfully revoked!"); + } else if (resStatus === "unsuccessful") { + console.error("Bad Request: Couldn't revoke successfully!"); + process.exit(1); + } else if (resStatus === "invalid") { + console.error("The access token is invalid!"); + process.exit(1); + } + } else { + console.error("Can't Logout: Not currently Authenticated!"); + process.exit(1); + } + } else if (options.login) { + const { tokenAuthenticate } = await import("./src/gl/auth.js"); + const token = await tokenAuthenticate(); + const { saveToken } = await import("./src/gl/tokenhelpers.js"); + saveToken({token: token}, GITLAB_CONFIG_FILE); + } else { + const { rotateToken, configJson } = await import("./src/gl/requests.js"); + const token = configJson.token; + const { saveToken } = await import("./src/gl/tokenhelpers.js"); + const expiryDate = options.rotate; + + const newToken = await rotateToken(token, expiryDate); + saveToken({token: newToken}, GITLAB_CONFIG_FILE); + } + } catch (error) { + console.error(error); + process.exit(1); + } + }); + +program + .command('profile') + .description("get a minimal overview of your profile") + .option('--gl', "GitLab profile") + .option('--gh', "GitHub profile") + .action(async (options) => { + if (options.gh) { + const octokit = await login(); + await userProfile(octokit); } else { - const { tokenAuthenticate } = await import("./src/gl/auth.js"); - return await tokenAuthenticate(); + // TODO: } - }) + }); program.parse(); From 5369f36ace93aebe341bc616d3b7987692015dfa Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 12:45:42 +0530 Subject: [PATCH 32/82] fix:config file path --- src/gl/requests.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gl/requests.js b/src/gl/requests.js index 9fbb2d1..1387811 100644 --- a/src/gl/requests.js +++ b/src/gl/requests.js @@ -1,5 +1,3 @@ -import path from "node:path"; - // JSON import of the config file containing the token const configJson = await (async ()=>{ const { createRequire } = await import("node:module"); @@ -7,7 +5,11 @@ const configJson = await (async ()=>{ return require("./config.json"); })(); -const CONFIG_FILE = path.join(__dirname, "config.json"); // file path +const CONFIG_FILE = await (async () => { + const {default:path} = await import("node:path"); + const dirname = import.meta.dirname; + path.join(dirname, "config.json") +})(); // file path const baseURL = "https://gitlab.com/api/v4"; From 6edfdf2bcd2fa763b79e31d7ef56c6d19e019827 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 17 Nov 2024 16:19:37 +0530 Subject: [PATCH 33/82] fix: remove unneeded sleepwait and keep the oauth process for a finite attempt of requests --- src/gh/auth/oauth.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gh/auth/oauth.js b/src/gh/auth/oauth.js index 84f3e03..ddcde81 100644 --- a/src/gh/auth/oauth.js +++ b/src/gh/auth/oauth.js @@ -30,13 +30,19 @@ async function getOAuthenticationObject() { } let currentInterval = interval; + let remainingAttempts = 25; while (true) { + remainingAttempts -= 1; + if (remainingAttempts < 0) { + console.error("User took too long to respond"); + return { error: "Request Timeout, try again"}; + } try { - const { authentication } = await sleep(currentInterval*1000, await exchangeDeviceCode({ + const { authentication } = await exchangeDeviceCode({ clientType: "oauth-app", clientId: config.CLIENT_ID, code: device_code, - })); + }); return authentication; // Exit loop and return authentication object } catch (error) { From 8bfbfbfc236c15a338647e3c286d3e8e1e144d47 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 18 Nov 2024 01:00:58 +0530 Subject: [PATCH 34/82] migrated all existing axios code to fetch API, axios removed from dependency --- src/gh/utils/unauthenticated/repo.js | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/gh/utils/unauthenticated/repo.js b/src/gh/utils/unauthenticated/repo.js index f1dcfdc..2d865ce 100644 --- a/src/gh/utils/unauthenticated/repo.js +++ b/src/gh/utils/unauthenticated/repo.js @@ -1,13 +1,14 @@ -import axios from "axios"; import search from "@inquirer/search"; import chalk from "chalk"; async function getCurrentRateLimitsData() { - const rateLimits = await axios.get("https://api.github.com/rate_limit", { + const response = await fetch("https://api.github.com/rate_limit", { + method: "GET", headers: { "X-GitHub-Api-Version": "2022-11-28", "Accept": "application/vnd.github+json" }, }); - - return rateLimits.data.resources; + + const rateLimits = await response.json(); + return rateLimits.resources; } // Fetch repositories based on the search term @@ -16,13 +17,14 @@ async function fetchRepos(searchTerm) { const rateLimitsData = await getCurrentRateLimitsData(); const searchRequestsRemaining = Number(rateLimitsData.search.remaining); if (searchRequestsRemaining > 0) { - const response = await axios.get( + const response = await fetch( `https://api.github.com/search/repositories?q=${searchTerm}`, { headers: { "X-GitHub-Api-Version": "2022-11-28", "Accept": "application/vnd.github+json" }, }); + const data = await response.json(); return { - entries: response.data.items, - length: response.data.total_count, + entries: data.items, + length: data.total_count, }; } else { console.log( @@ -109,15 +111,14 @@ async function fetchRepoContentsResponse(repoFullName) { const coreRequestsRemaining = Number(rateLimits.core.remaining); if (coreRequestsRemaining > 0) { - const response = await axios.get( + const response = await fetch( `https://api.github.com/repos/${repoFullName}/contents`, ); - if (response.statusText === "OK") { - return { - data: response.data, - status: Number(response.status), - }; - } + const responseData = await response.json(); + return { + data: responseData, + status: Number(response.status), + }; } else { console.log( chalk.yellow( @@ -126,11 +127,7 @@ async function fetchRepoContentsResponse(repoFullName) { ); } } catch (error) { - console.error(error.response.data.message,'\n'); - return { - data: error.response.data, - status: Number(error.response.status), - }; + console.error(error.message,'\n', "Fetch Error"); } } From dbfe62669e0fa1b382005f92913fc57b9d17ddaf Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 18 Nov 2024 01:25:21 +0530 Subject: [PATCH 35/82] remove axios from dependencies --- package-lock.json | 92 ----------------------------------------------- package.json | 1 - src/gl/auth.js | 19 +++++----- 3 files changed, 10 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4122cec..9c8d6f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@inquirer/search": "^3.0.1", "@octokit/oauth-methods": "^5.1.2", "@octokit/rest": "^21.0.2", - "axios": "^1.7.2", "boxen": "^8.0.1", "cfonts": "^3.3.0", "chalk": "^5.3.0", @@ -594,22 +593,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -871,17 +854,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -968,14 +940,6 @@ "node": ">=0.10.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1041,38 +1005,6 @@ "dev": true, "license": "MIT" }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1308,25 +1240,6 @@ "node": ">=0.10.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1396,11 +1309,6 @@ "node": ">=6" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index edf0fe8..d162f1f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@inquirer/search": "^3.0.1", "@octokit/oauth-methods": "^5.1.2", "@octokit/rest": "^21.0.2", - "axios": "^1.7.2", "boxen": "^8.0.1", "cfonts": "^3.3.0", "chalk": "^5.3.0", diff --git a/src/gl/auth.js b/src/gl/auth.js index e6440a0..762bb72 100644 --- a/src/gl/auth.js +++ b/src/gl/auth.js @@ -2,11 +2,7 @@ import input from '../gh/utils/input.js'; const baseURL = "https://gitlab.com/api/v4"; -const tokenAuthenticate = async () => { - console.log('Create a new personal access token from your GitLab profile'); - console.log('Choose both \'read_api\' and \'read-user\' scopes from the checkboxes.'); - const token = await input('Enter the token here'); - +const checkTokenIsValid = async (token) => { const url = `${baseURL}/personal_access_tokens`; try { @@ -17,10 +13,8 @@ const tokenAuthenticate = async () => { } }); - const data = await response.json(); - - if (data.message === "401 Unauthorized") { - console.error("invalid access token: Authentication aborted!"); + if (response.status === 401) { + console.error("invalid access token: Aborted!"); process.exit(1); } else { console.log("Authentication Successful"); @@ -32,4 +26,11 @@ const tokenAuthenticate = async () => { } } +const tokenAuthenticate = async () => { + console.log('Create a new personal access token from your GitLab profile'); + console.log('Choose only the \'api\' scope from the checkboxes.'); + const token = await input('Enter the token here'); + return await checkTokenIsValid(token); +} + export { tokenAuthenticate }; From 01d3bd3e62a049d9027e1b7bf552aee3991a44c2 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 18 Nov 2024 01:43:51 +0530 Subject: [PATCH 36/82] add gitlab profile command refactor gitlab logout command --- index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 8693867..434a4d4 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ import { Command } from 'commander'; import { clearToken, getStoredAuthType, getStoredToken } from './src/gh/auth/tokenHelpers.js'; import config from "./src/gh/auth/config.js"; import { userProfile, login } from "./src/gh/utils/authenticated/requests.js"; -import { CONFIG_FILE as GITLAB_CONFIG_FILE } from './src/gl/requests.js'; +const { CONFIG_FILE: GITLAB_CONFIG_FILE } = await import('./src/gl/requests.js'); const packageJson = await (async ()=>{ const { createRequire } = await import("node:module"); @@ -77,6 +77,8 @@ program const resStatus = await revokeToken(token); if (resStatus === "successful") { console.log("Token successfully revoked!"); + const { clearToken } = await import("./src/gl/tokenhelpers.js"); + clearToken(GITLAB_CONFIG_FILE); } else if (resStatus === "unsuccessful") { console.error("Bad Request: Couldn't revoke successfully!"); process.exit(1); @@ -89,6 +91,10 @@ program process.exit(1); } } else if (options.login) { + const { configJson } = await import("./src/gl/requests.js"); + if (configJson.token !== null) { + console.log("A token already in use. Checking validity..."); + } const { tokenAuthenticate } = await import("./src/gl/auth.js"); const token = await tokenAuthenticate(); const { saveToken } = await import("./src/gl/tokenhelpers.js"); @@ -118,7 +124,8 @@ program const octokit = await login(); await userProfile(octokit); } else { - // TODO: + const { userProfile, configJson } = await import("./src/gl/requests.js"); + await userProfile(configJson.token); } }); From 2812c2ab16fa64a9bd43463dcdc4eda35c04d284 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 18 Nov 2024 01:45:01 +0530 Subject: [PATCH 37/82] fix gitlab config file and add two more request functions --- src/gl/requests.js | 104 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/src/gl/requests.js b/src/gl/requests.js index 1387811..773fac0 100644 --- a/src/gl/requests.js +++ b/src/gl/requests.js @@ -7,48 +7,33 @@ const configJson = await (async ()=>{ const CONFIG_FILE = await (async () => { const {default:path} = await import("node:path"); - const dirname = import.meta.dirname; - path.join(dirname, "config.json") + const { fileURLToPath } = await import("node:url"); + const dirname = path.dirname(fileURLToPath(import.meta.url)); + return path.join(dirname, "config.json"); })(); // file path const baseURL = "https://gitlab.com/api/v4"; -async function fetchErrorHandlerCallback(error) { - if (typeof error.json === "function") { - try { - const jsonError = await error.json(); - console.error("Json error from API"); - console.log(jsonError); - } catch (genericError) { - console.error("Generic error from API"); - console.log(genericError.statusText); - } - } else { - console.error("Fetch error"); - console.log(error); - } -} - async function revokeToken(token) { const url = `${baseURL}/personal_access_tokens/self`; try { - const response = await fetch(`${url}?${new URLSearchParams({ "expires_at" : expirationDate }).toString()}`, { + const response = await fetch(url, { method: "DELETE", headers: { "PRIVATE-TOKEN": token, } }); - const data = await response.json(); - if (data.message === "204: No Content") { + if (response.status === 204) { return "successful"; - } else if (data.message === "400: Bad Request") { + } else if (response.status === 400) { return "unsuccessful"; - } else if (data.message === "401: Unauthorized") { + } else if (response.status === 401) { return "invalid"; } } catch (error) { - await fetchErrorHandlerCallback(error); + console.error("Fetch Error"); + console.log(error); } } @@ -88,7 +73,74 @@ async function rotateToken(token, expirationDate) { } } } catch (error) { - await fetchErrorHandlerCallback(error); + console.error("Fetch Error"); + console.log(error); + } +} + +async function userProfile(token) { + const { default: boxen } = await import("boxen"); + const { default: chalk } = await import("chalk"); + const url = `${baseURL}/user`; + + try { + const response = await fetch(url, { + method: "GET", + headers: { + "PRIVATE-TOKEN": token, + }, + }); + + if (response.ok) { + const { name, bio } = await response.json(); + const profileInfo = ` + Hello, - ${name}! + ${bio} + `; + console.log(boxen(profileInfo, + {title: `${chalk.bgGreenBright('profile')}`, titleAlignment: 'center'} + )); + } else { + console.log(`Request Returned Status ${response.status} Error`); + process.exit(1); + } + + } catch (error) { + console.error("Fetch Error"); + console.log(error); + } +} + +async function getProjects(token, searchTerm) { + const url = `${baseURL}/search?scope=projects&search=${searchTerm}`; + + try { + const response = await fetch(url, { + method: "GET", + headers: { + "PRIVATE-TOKEN": token, + } + }); + + if (response.ok) { + const data = await response.json(); + + const projectsList = data.map((project) => ({ + name: project.path_with_namespace, + description: project.descriptions, + url: project.http_url_to_repo, + projectID: project.id, + })); + + return projectsList; + } else { + console.error(response.statusText); + console.error(`Request Returned Status ${response.status} Error`); + process.exit(1); + } + } catch (error) { + console.error(error); + process.exit(1); } } @@ -97,4 +149,6 @@ export { configJson, revokeToken, rotateToken, + userProfile, + getProjects, } From 5cca674410f34215fd42e8f741a96ac154e215c4 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 25 Nov 2024 00:32:26 +0530 Subject: [PATCH 38/82] utility functions for cloning --- cloning.js | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 cloning.js diff --git a/cloning.js b/cloning.js new file mode 100644 index 0000000..b06e782 --- /dev/null +++ b/cloning.js @@ -0,0 +1,106 @@ +import { execFile } from 'child_process'; +import { promisify } from 'util'; + +const execFileAsync = promisify(execFile); + +async function executeCommand(command, args = []) { + try { + const { stdout, stderr } = await execFileAsync(command, args); + if (stderr) { + const { default: chalk } = await import('chalk'); + console.log(chalk.yellowBright(stderr)); + } + // console.log(`stdout: ${stdout}`); + return stdout; + } catch (error) { + console.error(`Error: ${error.message}`); + throw error; + } +} + +async function runShallowClone(repoUrl, branchName='', dirName='') { + try { + if (!branchName) { + await executeCommand('git', ['clone', '--depth', '1', repoUrl, dirName]); + } else { + await executeCommand('git', ['clone', '--depth', '1', '-b', branchName, repoUrl, dirName]); + } + console.log('Shallow cloning completed successfully!'); + } catch (error) { + console.error(`Error during shallow clone process: ${error.message}`); + process.exit(1); + } +} + +async function runBloblessClone(repoUrl, branchName='', dirName='') { + try { + if (!branchName) { + await executeCommand('git', ['clone', '--filter=blob:none', repoUrl, dirName]); + } else { + await executeCommand('git', ['clone', '--filter=blob:none', '-b', branchName, repoUrl, dirName]); + } + console.log('Blobless cloning completed successfully!'); + } catch (error) { + console.error(`Error during blobless clone process: ${error.message}`); + process.exit(1); + } +} + +async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirectory) { + try { + // Validate inputs + if (!pathToDirectory) { + throw new Error('Path to directory for sparse checkout cannot be empty.'); + } + + // Derive default directory name if dirName is not provided + if (!dirName) { + dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; + } + + // Determine git clone command based on inputs + const cloneArgs = ['clone', '--no-checkout', '--filter=blob:none']; + if (branch) cloneArgs.push('-b', branch); + cloneArgs.push(repoUrl, dirName); + + // Execute git clone + await executeCommand('git', cloneArgs); + + // Change to cloned directory + process.chdir(dirName); + + // Initialize sparse-checkout + await executeCommand('git', ['sparse-checkout', 'set', '--no-cone']); + await executeCommand('git', ['sparse-checkout', 'add', '!/*', pathToDirectory]); + + // Determine default branch if not provided + const branchList = (await executeCommand('git', ['ls-remote', '--heads', 'origin'])) + .split('\n') + .map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); + const defaultLocalBranch = branch || branchList[0] || 'main'; + + // Checkout branch + await executeCommand('git', ['checkout', defaultLocalBranch]); + console.log('Cloning specific directory completed successfully!'); + } catch (error) { + console.error('Error during sparse checkout process:', error.message || error); + process.exit(1); + } +} + +async function normalClone(repoUrl, dirName='') { + try { + await executeCommand('git', ['clone', repoUrl, dirName]); + console.log('Cloning completed successfully!'); + } catch (error) { + console.error(`Error during cloning process: ${error.message}`); + process.exit(1); + } +} + +export { + runSparseCheckout, + normalClone, + runShallowClone, + runBloblessClone, +} From 2f7423f8f5ead63819ec6ac68f6a0a2ef1e1dd4d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 25 Nov 2024 00:32:52 +0530 Subject: [PATCH 39/82] interactive cloning flow for gitlab projects --- src/gl/interactiveFlow.js | 115 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/gl/interactiveFlow.js diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js new file mode 100644 index 0000000..f8e05d5 --- /dev/null +++ b/src/gl/interactiveFlow.js @@ -0,0 +1,115 @@ +import chalk from "chalk"; + +const cloningOptions = [ + { + name: "Normal Cloning", + value: "normal", + description: "Clones the entire repository, including all branches and history.", + }, + { + name: "Partial Cloning", + value: "partial", + description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", + } +]; + +const partialCloningOptions = [ + { + name: "Shallow Cloning", + value: "shallow", + description: "shallow clone only the latest commit", + }, + { + name: "Sparse Cloning", + value: "sparse", + description: "clone only a specific folder", + }, + { + name: "Blobless Cloning", + value: "blobless", + description: "clones the repository without downloading the actual file contents (blobs). You only need the repository history", + } +]; + +export async function interactiveClone(token) { + const { + getProjects, + getSingleProject, + promptProjectSelection, + projectInfo, + } = await import("./requests.js"); + const { runShallowClone, runSparseCheckout, runBloblessClone, normalClone } = await import("../../cloning.js"); + const { default:search } = await import("@inquirer/search"); + const { default:input } = await import("../gh/utils/input.js"); + + const searchTerm = await input(chalk.greenBright("Search a GitLab Project ->")); + const projects = await getProjects(token, searchTerm); + + const selectedProject = await promptProjectSelection(projects); + projectInfo(selectedProject); + + const cloningPreference = await search({ + message: "Choose your prefered way to clone the repository", + source: async (_input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + return cloningOptions; + }, + }); + + if (cloningPreference === "partial") { + const partialCloningPreference = await search({ + message: "Choose your preferred way to partially clone the repository", + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + if (!input) { + return partialCloningOptions; + } else { + const filteredChoices = partialCloningOptions.filter((choice) => choice.name.includes(input)); + return filteredChoices; + } + }, + }); + + if (partialCloningPreference === "shallow") { + await runShallowClone(selectedProject.url); + } else if (partialCloningPreference === "sparse") { + const selectedProjectContents = await (async () => { + const selectedProjectAllContents = await getSingleProject(token, selectedProject.projectID, true); + return selectedProjectAllContents.filter(content => content.type === "tree"); + })(); + const directoriesFromProjectContents = selectedProjectContents.map((content) => ({ + name: content.path, + value: content.path, + })); + + let pathToDirectory; + try { + pathToDirectory = await search({ + message:"Enter the directory you want to clone ->", + source: async (_input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + return directoriesFromProjectContents; + }, + }); + pathToDirectory = pathToDirectory; + await runSparseCheckout(selectedProject.url, '', '', pathToDirectory); + } catch (error) { + console.error(error.message); + process.exit(1); + } + } else { + await runBloblessClone(selectedProject.url); + } + } else { + await normalClone(selectedProject.url); + } +} From ba71ec2eb8db1bb2fb73d878f942dd5696156e0c Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 25 Nov 2024 00:36:19 +0530 Subject: [PATCH 40/82] feat: add all other necessary gitlab repo related utility functions and remove the gitlab profile fetching function in exports --- src/gl/requests.js | 153 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 27 deletions(-) diff --git a/src/gl/requests.js b/src/gl/requests.js index 773fac0..8dbc37f 100644 --- a/src/gl/requests.js +++ b/src/gl/requests.js @@ -1,3 +1,5 @@ +import chalk from "chalk"; + // JSON import of the config file containing the token const configJson = await (async ()=>{ const { createRequire } = await import("node:module"); @@ -78,41 +80,66 @@ async function rotateToken(token, expirationDate) { } } -async function userProfile(token) { - const { default: boxen } = await import("boxen"); - const { default: chalk } = await import("chalk"); - const url = `${baseURL}/user`; +async function getProjects(token, searchTerm) { + const url = `${baseURL}/search?scope=projects&search=${searchTerm}`; try { const response = await fetch(url, { method: "GET", headers: { "PRIVATE-TOKEN": token, - }, + } }); if (response.ok) { - const { name, bio } = await response.json(); - const profileInfo = ` - Hello, - ${name}! - ${bio} - `; - console.log(boxen(profileInfo, - {title: `${chalk.bgGreenBright('profile')}`, titleAlignment: 'center'} - )); + const data = await response.json(); + + const projectsList = data.map((project) => ({ + name: project.path_with_namespace, + description: project.description, + url: project.http_url_to_repo, + projectID: project.id, + })); + + return projectsList; } else { - console.log(`Request Returned Status ${response.status} Error`); + console.error(response.statusText); + console.error(`Request Returned Status ${response.status} Error`); process.exit(1); } + } catch (error) { + console.error(error); + process.exit(1); + } +} + +async function getSingleProject(token, id, recursive=false) { + const url = `${baseURL}/projects/${id}/repository/tree?recursive=${recursive}`; + try { + const response = await fetch(url, { + method: "GET", + headers: { + "PRIVATE-TOKEN": token, + } + }); + + if (response.ok) { + const data = await response.json(); + return data; + } else { + console.error(response.statusText); + console.error(`Request Returned Status ${response.status} Error`); + process.exit(1); + } } catch (error) { - console.error("Fetch Error"); + console.error("Fetch Error!"); console.log(error); } } -async function getProjects(token, searchTerm) { - const url = `${baseURL}/search?scope=projects&search=${searchTerm}`; +async function listRepoBranches(token, id) { + const url = `${baseURL}/projects/${id}/repository/branches`; try { const response = await fetch(url, { @@ -125,30 +152,102 @@ async function getProjects(token, searchTerm) { if (response.ok) { const data = await response.json(); - const projectsList = data.map((project) => ({ - name: project.path_with_namespace, - description: project.descriptions, - url: project.http_url_to_repo, - projectID: project.id, - })); - - return projectsList; + return data.map(item => item.name); } else { console.error(response.statusText); console.error(`Request Returned Status ${response.status} Error`); process.exit(1); } } catch (error) { - console.error(error); + console.error("Fetch Error!"); + console.log(error); + } +} + +function projectInfo(project) { + const { name, description, url} = project; + console.log(''); + console.log( + chalk.bgGreenBright(chalk.black("repo name :")), + "\t", + chalk.bold(name), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("Description :")), + "\t", + chalk.bold(description), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("URL :")), + "\t", + chalk.bold(chalk.underline(url)), + "\n", + ); +} + +async function promptProjectSelection(projects) { + const choices = projects.map((project) => ({ + name: project.name, + value: project, + description: project.description, + })); + + try { + const { default: search } = await import("@inquirer/search"); + return await search({ + message: `${chalk.greenBright("Select a repository: ")}${chalk.yellow("(Autocomplete Available)")}`, + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + if (!input) { + return choices; + } else { + const filteredChoices = choices.filter((choice) => + choice.name.includes(input), + ); + return filteredChoices; + } + }, + }); + } catch (_) { + console.log(chalk.yellow("Aborted! Exiting Gracefully...")); process.exit(1); } } +// Render the repository contents as a folder structure +function renderProjectContents(contents, indent = " ") { + try { + contents.forEach((item) => { + if (item.type === "tree") { + console.log( + `${indent}`, + chalk.bgCyanBright(chalk.black(`${item.name}`)), + chalk.blueBright("/"), + ); + } else { + console.log(`${indent}${item.name}`); + } + }); + } catch (error) { + console.error("Unexpected Error occured while rendering contents"); + console.log(error.message); + } +} + export { CONFIG_FILE, configJson, revokeToken, rotateToken, - userProfile, getProjects, + getSingleProject, + listRepoBranches, + projectInfo, + promptProjectSelection, + renderProjectContents, } From 67c9f7a214008d5eb24ebd14d45ec5c66ae6f9db Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 25 Nov 2024 00:40:37 +0530 Subject: [PATCH 41/82] feat: add gitlab repo interactive cloning command and remove useless gitlab profile reaching command --- index.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 434a4d4..222f730 100644 --- a/index.js +++ b/index.js @@ -116,17 +116,26 @@ program program .command('profile') - .description("get a minimal overview of your profile") - .option('--gl', "GitLab profile") - .option('--gh', "GitHub profile") - .action(async (options) => { - if (options.gh) { - const octokit = await login(); - await userProfile(octokit); - } else { - const { userProfile, configJson } = await import("./src/gl/requests.js"); - await userProfile(configJson.token); - } + .description("get a minimal overview of your GitHub profile") + .action(async () => { + const octokit = await login(); + await userProfile(octokit); }); +program + .command('glclone') + .description("clone a GitLab repository") + .action( + async () => { + const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); + const { configJson } = await import("./src/gl/requests.js"); + await interactiveClone(configJson.token); + process.exit(0); + } + ); + +/* program + .command('stars') + .description("get a list of your starred repositories") */ + program.parse(); From 2e8744acc10730662fbc5ca5416a5f5e137ddf56 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:10:06 +0530 Subject: [PATCH 42/82] changed product description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d162f1f..ebe4323 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gitfm", "version": "2.0.0", - "description": "A CLI App for searching GitHub/GitLab repos, fetching as folder structure and cloning", + "description": "A CLI App for searching GitHub/GitLab repos, fetching as filesystem and cloning", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From 401ad19841a26349974c589d6e3a3e7085a06d38 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:13:46 +0530 Subject: [PATCH 43/82] feat: added github repo interactive cloning command, commands for revoking and refreshing github token --- index.js | 56 ++-- src/gh/auth/index.js | 100 ++++++- src/gh/utils/authenticated/interactiveFlow.js | 163 +++++++++++ src/gh/utils/authenticated/requests.js | 260 +++++++++++++++++- 4 files changed, 544 insertions(+), 35 deletions(-) create mode 100644 src/gh/utils/authenticated/interactiveFlow.js diff --git a/index.js b/index.js index 222f730..4da327b 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,5 @@ #!/usr/bin/env node import { Command } from 'commander'; -import { clearToken, getStoredAuthType, getStoredToken } from './src/gh/auth/tokenHelpers.js'; -import config from "./src/gh/auth/config.js"; import { userProfile, login } from "./src/gh/utils/authenticated/requests.js"; const { CONFIG_FILE: GITLAB_CONFIG_FILE } = await import('./src/gl/requests.js'); @@ -26,31 +24,16 @@ program .command('ghauth') .description("authorize or unauthorize gitfm with your GitHub") .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token") - .option('--logout', "logout from the CLI and delete your GitHub token") + .option('--logout', "logout from the CLI and delete(revoke) your GitHub token") + .option('--refresh', "refresh your GitHub token") .action(async (options) => { if (options.logout) { - const { Octokit } = await import("@octokit/rest"); - const storedToken = getStoredToken(config.TOKEN_FILE); - - if (storedToken === null) { - console.error("Error: Token not found"); - console.error("You are not authenticated"); - - process.exit(1); - } - - const octokit = new Octokit({ auth: storedToken}); - - if (getStoredAuthType(config.TOKEN_FILE) === "oauth") { - octokit.rest.apps.deleteToken({ - client_id: config.CLIENT_ID, - access_token: storedToken, - }); - } - - clearToken(config.TOKEN_FILE); - + const { revokeToken } = await import("./src/gh/auth/index.js"); + await revokeToken(); + } else if (options.refresh) { + const { refreshToken } = await import("./src/gh/auth/index.js"); + await refreshToken(); } else { if (options.login === "token") { await login("token"); @@ -115,13 +98,31 @@ program }); program - .command('profile') + .command('ghprofile') .description("get a minimal overview of your GitHub profile") .action(async () => { const octokit = await login(); await userProfile(octokit); }); +program + .command('ghclone') + .description("clone a GitHub repository") + .option('-u, --unauthenticated', 'legacy version of this command (without authentication and partial cloning support)') + .action( + async (options) => { + if (options.unauthenticated || options.u) { + const { unAuthenticatedInteractiveClone } = await import("./src/gh/utils/unauthenticated/interactiveFlow.js"); + await unAuthenticatedInteractiveClone(); + } else { + const { interactiveClone } = await import("./src/gh/utils/authenticated/interactiveFlow.js"); + const { login } = await import("./src/gh/utils/authenticated/requests.js"); + const octokit = await login(); + await interactiveClone(octokit); + } + } + ); + program .command('glclone') .description("clone a GitLab repository") @@ -130,12 +131,7 @@ program const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); const { configJson } = await import("./src/gl/requests.js"); await interactiveClone(configJson.token); - process.exit(0); } ); -/* program - .command('stars') - .description("get a list of your starred repositories") */ - program.parse(); diff --git a/src/gh/auth/index.js b/src/gh/auth/index.js index cc05c60..6cfcfe4 100644 --- a/src/gh/auth/index.js +++ b/src/gh/auth/index.js @@ -1,6 +1,6 @@ const { getVerifiedAuthToken, checkTokenValidity } = await import("./getAuthenticated.js"); const { Octokit } = await import("@octokit/rest"); -const { saveToken } = await import("./tokenHelpers.js"); +const { saveToken, getStoredToken, getStoredAuthType, clearToken } = await import("./tokenHelpers.js"); import config from "./config.js"; const authLoginInteractive = async () => { @@ -39,4 +39,100 @@ const authLogin = async (authType) => { return octokit; }; -export { authLoginInteractive, authLogin, checkTokenValidity }; +const revokeToken = async () => { + try { + const { default:input } = await import("../utils/input.js"); + const { default:chalk } = await import("chalk"); + const yesOrNo = await input(chalk.red("Are you sure you want to revoke the token? [y/N]")); + if (yesOrNo.toLowerCase() !== "y") { + console.log("Token revocation aborted."); + return; + } + const { Octokit } = await import("@octokit/rest"); + const tokenFilePath = config.TOKEN_FILE; + const storedToken = getStoredToken(tokenFilePath); + + if (!storedToken) { + console.error("Error: Token not found."); + console.error("You are not authenticated."); + return; + } + + const isValid = await checkTokenValidity(storedToken); + if (!isValid) { + console.log("Token is already expired. No need to revoke."); + clearToken(tokenFilePath); + return; + } + + const authType = getStoredAuthType(tokenFilePath); + if (authType !== "oauth") { + console.log("Token type is not OAuth. Skipping revocation."); + return; + } + + const octokit = new Octokit({ auth: storedToken }); + + // Attempt to revoke the token + await octokit.rest.apps.deleteToken({ + client_id: config.CLIENT_ID, + access_token: storedToken, + }); + console.log("Token revoked successfully!"); + + clearToken(tokenFilePath); + } catch (error) { + console.error("An error occurred while revoking the token:", error.message); + process.exit(1); + } +}; + +const refreshToken = async () => { + try { + const { Octokit } = await import("@octokit/rest"); + const tokenFilePath = config.TOKEN_FILE; + const storedToken = getStoredToken(tokenFilePath); + if (!storedToken) { + console.error("Error: Token not found."); + console.error("You are not authenticated."); + return; + } + + const isValid = await checkTokenValidity(storedToken); + if (!isValid) { + console.log("Token is already expired/invalid. Can't refresh."); + return; + } + + const authType = getStoredAuthType(tokenFilePath); + if (authType !== "oauth") { + console.log("Token type is not OAuth. Skipping refresh."); + return; + } + + const octokit = new Octokit({ auth: storedToken }); + + // Attempt to refresh the token + const { token: newToken } = await octokit.request( + `PATCH /applications/${config.CLIENT_ID}/token`, + { + client_id: config.CLIENT_ID, + access_token: storedToken, + headers: { + accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + }, + ); + console.log("Token refreshed successfully!"); + saveToken({ token: newToken, type: "oauth" }, tokenFilePath); + } catch (error) { + console.error( + "An error occurred while refreshing the token:", + error.message, + ); + process.exit(1); + } +}; + +export { authLoginInteractive, authLogin, checkTokenValidity, revokeToken, refreshToken }; diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js new file mode 100644 index 0000000..ceec87a --- /dev/null +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -0,0 +1,163 @@ +const { promisify } = await import("node:util"); +const sleep = promisify(setTimeout); +const cloningOptions = [ + { + name: "Normal Cloning", + value: "normal", + description: "Clones the entire repository, including all branches and history.", + }, + { + name: "Partial Cloning", + value: "partial", + description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", + } +]; + +const partialCloningOptions = [ + { + name: "Shallow Cloning", + value: "shallow", + description: "shallow clone only the latest commit", + }, + { + name: "Sparse Cloning", + value: "sparse", + description: "clone only a specific folder", + }, + { + name: "Blobless Cloning", + value: "blobless", + description: "clones the repository without downloading the actual file contents (blobs). You only need the repository history", + } +]; + +import { + runShallowClone, + runSparseCheckout, + runBloblessClone, + normalClone, +} from "../../../../cloning.js"; + +import { + fetchRepos, + promptRepoSelection, + promptFolderSelectionFromSubFolder, + promptFolderSelectionFromRoot, + getCurrentRateLimits, + repoInfo, +} from "./requests.js"; +import input from "../input.js"; +import search from "@inquirer/search"; +import chalk from "chalk"; +import { headerText } from "../headerText.js"; + +const interactiveClone = async (octokit) => { + console.log(headerText); + + const searchTerm = await input(chalk.greenBright("Enter the term to search repositories: ")); + const repos = (await getCurrentRateLimits(octokit)).search.remaining > 0 ? await fetchRepos(octokit, searchTerm) : ''; + + if ( typeof(repos) === 'string' ) { + console.log( + chalk.yellow( + "\nYou ran out of search queries!\nTry again after some minutes!!\n", + ), + ); + console.log( + `Visit - https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api to learn more about GitHub API rate limits`, + ); + process.exit(1); + } + + if (repos.length < 1) { + console.log(`${chalk.red(`${repos.length} Repositories found!`)} `); + console.log("Nothing to show"); + process.exit(1); + } + const selectedRepo = await promptRepoSelection(repos.items); + await repoInfo(selectedRepo); + + const cloningPreference = await search({ + message: "Choose your prefered way to clone the repository", + source: async (_input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + return cloningOptions; + }, + }); + + if (cloningPreference === "partial") { + const partialCloningPreference = await search({ + message: "Choose your preferred way to partially clone the repository", + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + if (!input) { + return partialCloningOptions; + } else { + const filteredChoices = partialCloningOptions.filter((choice) => choice.name.includes(input)); + return filteredChoices; + } + }, + }); + + if (partialCloningPreference === "shallow") { + await runShallowClone(selectedRepo.html_url); + } else if (partialCloningPreference === "sparse") { + let selectedFolder = await promptFolderSelectionFromRoot(octokit, selectedRepo.full_name); // yes I've changed this to let from const + if (selectedFolder === null) { + console.log(`${chalk.red(`${selectedFolder.length} Folders found!`)} `); + console.log("Exiting..."); + process.exit(0); + } + + const yesOrNo = await input(chalk.greenBright("Partially clone this selected folder? [Y/N]")); + if (yesOrNo.toLowerCase() === "y") { + await runSparseCheckout(selectedRepo.html_url, '', '', selectedFolder); + } else { + await sleep(1000); + console.log('Entering into this directory'); + let pathToNextFolder; + while (pathToNextFolder !== "" || pathToNextFolder !== null) { + try { + await sleep(2000); + pathToNextFolder = (await promptFolderSelectionFromSubFolder(octokit, selectedRepo.full_name, selectedFolder)) || ""; + if (pathToNextFolder) { + console.log('path to selected Folder is -> ' + pathToNextFolder); + const yesOrNo = await input(chalk.greenBright("Partially clone this selected folder? [Y/N]")); + if (yesOrNo.toLowerCase() === "y") { + await runSparseCheckout(selectedRepo.html_url, '', '', pathToNextFolder); + break; + } else { + selectedFolder = pathToNextFolder; + } + } else { + console.log('No folders found further down'); + const yesOrNo = await input(chalk.greenBright("Still don't want to partially clone last selected folder? [Y/N]")); + if (yesOrNo.toLowerCase() === "y") { + await runSparseCheckout(selectedRepo.html_url, '', '', selectedFolder); + break; + } else { + console.log('Exiting...'); + process.exit(0); + } + } + } catch (error) { + console.error(error.message); + process.exit(1); + } + } + } + } else { + await runBloblessClone(selectedRepo.html_url); + } + } else { + await normalClone(selectedRepo.html_url); + } +} + +export { interactiveClone }; diff --git a/src/gh/utils/authenticated/requests.js b/src/gh/utils/authenticated/requests.js index 74b7e59..74fc353 100644 --- a/src/gh/utils/authenticated/requests.js +++ b/src/gh/utils/authenticated/requests.js @@ -41,10 +41,15 @@ const login = async (authType) => { // authType (optional) = "oauth" | "token" const octokit = new Octokit({ auth:storedToken }); return octokit; } else { - console.error("\nToken Expired! Initiating Authorization\n"); + console.error("\nLooks Like Your Token Expired! Initiating Authorization\n"); const { clearToken } = await import("../../auth/tokenHelpers.js"); clearToken(); - return await login(authType); + const { authLogin, authLoginInteractive } = await import("../../auth/index.js"); + if (authType) { + return await authLogin(authType); + } else { + return await authLoginInteractive(); + } } } else { if (authType === "oauth") { @@ -63,4 +68,253 @@ const login = async (authType) => { // authType (optional) = "oauth" | "token" } } -export { login, userProfile }; +const getCurrentRateLimits = async (octokit) => { + const response = await octokit.rest.rateLimit.get(); + return response.data.resources; +}; + +const fetchRepos = async (octokit, searchTerm) => { + const response = await octokit.rest.search.repos({ + q: searchTerm, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchReposByUserName = async (octokit, userName) => { + const response = await octokit.rest.search.repos({ + q: `user:${userName}`, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchReposBySearchingInReadme = async (octokit, searchTerm) => { + const response = await octokit.rest.search.repos({ + q: `q=${encodeURIComponent(`${searchTerm} in:readme`)}`, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchReposBySearchingInTopics = async (octokit, searchTerm) => { + const response = await octokit.rest.search.repos({ + q: `q=${encodeURIComponent(`${searchTerm} in:topics`)}`, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchReposBySearchingInLanguages = async (octokit, searchTerm, language) => { + const response = await octokit.rest.search.repos({ + q: `q=${encodeURIComponent(`${searchTerm} language:${language}`)}`, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchYourPrivateRepos = async (octokit) => { + const response = await octokit.rest.search.repos({ + q: `is:private`, + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const fetchStarredRepos = async (octokit) => { + const response = await octokit.rest.activity.listReposStarredByAuthenticatedUser({ + per_page: 100, + }); + return { + items: response.data.items, + length: response.data.total_count, + }; +}; + +const repoInfo = async (repo) => { + const { default:chalk } = await import("chalk"); + const { name, description, html_url } = repo; + console.log(''); + console.log( + chalk.bgGreenBright(chalk.black("repo name :")), + "\t", + chalk.bold(name), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("Description :")), + "\t", + chalk.bold(description), + "\n", + ); + console.log( + chalk.bgGreenBright(chalk.black("URL :")), + "\t", + chalk.bold(chalk.underline(html_url)), + "\n", + ); +} + +function mapRepos(repos) { + return repos.map((repo) => ({ + name: repo.full_name, + value: repo, + description: repo.description, + })); +} + +function filterMapRepoContents(contents) { + return contents.filter((item) => item.type === "dir").map((item) => ({ + name: item.name, + value: item.path, + })); +} + +// Prompt the user to select a repository from the list with Autocomplete +const promptRepoSelection = async (repos) => { + const choices = mapRepos(repos); + const { default:chalk } = await import("chalk"); + try { + const {default:search} = await import("@inquirer/search"); + return await search({ + message: `${chalk.greenBright("Select a repository: ")}${chalk.yellow("(Autocomplete Available)")}`, + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + if (!input) { + return choices; + } else { + const filteredChoices = choices.filter((choice) => + choice.name.includes(input), + ); + return filteredChoices; + } + }, + }); + } catch (_) { + console.log(chalk.yellow("Aborted! Exiting Gracefully...")); + process.exit(1); + } +} + +const getRepoContent = async (octokit, repoFullName) => { + const response = await octokit.rest.repos.getContent({ + owner: repoFullName.split("/")[0], + repo: repoFullName.split("/")[1], + path: "", + }); + return response.data; +} + +const getRepoFolderContents = async (octokit, repoFullName, folderPath) => { + const response = await octokit.rest.repos.getContent({ + owner: repoFullName.split("/")[0], + repo: repoFullName.split("/")[1], + path: folderPath, + }); + return response.data; +} + +const promptFolderSelectionFromRoot = async (octokit, repoFullName) => { + const repoContent = await getRepoContent(octokit, repoFullName); + const repoParentDirs = filterMapRepoContents(repoContent); + + if (repoParentDirs.length < 1) { + return null; + } else { + + const { default:search } = await import("@inquirer/search"); + + return await search({ + message: "Select a folder from repository root: ", + source: async (input, { signal }) => { + if (signal.aborted) { + const { default:chalk } = await import("chalk"); + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + + if (!input) { + return repoParentDirs; + } else { + const filteredChoices = repoParentDirs.filter((choice) => + choice.name.includes(input), + ); + return filteredChoices; + } + } + }); + } +} + +const promptFolderSelectionFromSubFolder = async (octokit, repoFullName, folderPath) => { + const dirContents = (await getRepoFolderContents(octokit, repoFullName, folderPath)).filter((item) => item.type === "dir"); + + if (dirContents.length < 1) { + return null; + } else { + const dirChoices = dirContents.map((item) => ({ + name: item.name, + value: item.path, + })); + const { default:search } = await import("@inquirer/search"); + return await search({ + message: "Select a folder", + source: async (input, { signal }) => { + if (signal.aborted) { + console.log(chalk.yellow("Aborted!")); + process.exit(1); + } + + if (!input) { + return dirChoices; + } else { + const filteredChoices = dirChoices.filter((choice) => + choice.name.includes(input), + ); + return filteredChoices; + } + } + }); + } +} + +export { + login, + userProfile, + fetchReposByUserName, + fetchReposBySearchingInReadme, + fetchReposBySearchingInTopics, + fetchReposBySearchingInLanguages, + fetchYourPrivateRepos, + fetchStarredRepos, + fetchRepos, + repoInfo, + promptRepoSelection, + getRepoContent, + getRepoFolderContents, + promptFolderSelectionFromRoot, + promptFolderSelectionFromSubFolder, + filterMapRepoContents, + getCurrentRateLimits, +}; From 7af2f99f6550a3919ee29ec4652a2730bed78383 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:14:24 +0530 Subject: [PATCH 44/82] update to new client ID --- src/gh/auth/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gh/auth/config.js b/src/gh/auth/config.js index 61822ac..f12b9f8 100644 --- a/src/gh/auth/config.js +++ b/src/gh/auth/config.js @@ -2,7 +2,7 @@ import path from "node:path"; import os from "os"; const config = { - CLIENT_ID: "Ov23li5gcNYDdMUWPeCt", + CLIENT_ID: "Ov23liEZlvbyKNsSPB1n", TOKEN_FILE: path.join(os.homedir(), '.gitfmrc.json'), } From 5d126a6de529fd93eae8b40f79d9bfcd71a5d189 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:15:05 +0530 Subject: [PATCH 45/82] refactor: refined and reasonable oauth scopes --- src/gh/auth/authObject.js | 2 +- src/gh/auth/oauth.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gh/auth/authObject.js b/src/gh/auth/authObject.js index 29139a2..72a0dad 100644 --- a/src/gh/auth/authObject.js +++ b/src/gh/auth/authObject.js @@ -24,7 +24,7 @@ async function getAuthenticationObject(authType = "token") { const sleep = promisify(setTimeout); console.log('Create a new personal access token from your GitHub Developer Settings'); - console.log(`Check only these scopes from the checkboxes when creating a new token - \n\t${['repo', 'user', 'notifications', 'gist']} `) + console.log(`Check only these scopes from the checkboxes when creating the token - \n\t${['repo', 'user']} `); const personalAcessToken = await sleep(2000, await input("Enter the personal access token ")); diff --git a/src/gh/auth/oauth.js b/src/gh/auth/oauth.js index ddcde81..2ff95d1 100644 --- a/src/gh/auth/oauth.js +++ b/src/gh/auth/oauth.js @@ -12,7 +12,7 @@ async function getOAuthenticationObject() { } = await createDeviceCode({ clientType: "oauth-app", clientId: config.CLIENT_ID, - scopes: ["repo", "user", "notifications", "gist"], // oauth scopes + scopes: ["repo", "user"], // oauth scopes }); console.log(`\nYour OAuth User Code is - \n\t${user_code}\n`); From e92b33782a5c58c549254e902216ced605e62e77 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:15:59 +0530 Subject: [PATCH 46/82] update the legacy version of github repo cloning command --- src/gh/utils/unauthenticated/interactiveFlow.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gh/utils/unauthenticated/interactiveFlow.js b/src/gh/utils/unauthenticated/interactiveFlow.js index 73936ed..de535d2 100644 --- a/src/gh/utils/unauthenticated/interactiveFlow.js +++ b/src/gh/utils/unauthenticated/interactiveFlow.js @@ -10,7 +10,7 @@ import { promptRepoSelection, } from "./repo.js"; -async function unAuthenticatedInteractiveFlow() { +async function unAuthenticatedInteractiveClone() { console.log(headerText); // Prompt the user for a search term @@ -82,4 +82,4 @@ async function unAuthenticatedInteractiveFlow() { } } -export { unAuthenticatedInteractiveFlow }; +export { unAuthenticatedInteractiveClone }; From fe7324779f2b380d5cc919734964d35b89656f61 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 13:16:58 +0530 Subject: [PATCH 47/82] feat: headerText on top of GitLab Repo cloning command line interface at interactive mode --- src/gl/interactiveFlow.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index f8e05d5..410ab76 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -32,6 +32,8 @@ const partialCloningOptions = [ ]; export async function interactiveClone(token) { + const { headerText } = await import("../gh/utils/headerText.js"); + console.log(headerText); const { getProjects, getSingleProject, @@ -100,7 +102,6 @@ export async function interactiveClone(token) { return directoriesFromProjectContents; }, }); - pathToDirectory = pathToDirectory; await runSparseCheckout(selectedProject.url, '', '', pathToDirectory); } catch (error) { console.error(error.message); From 0af9903c44c3e416717e7b04965ffc72b6fd4b62 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 17:29:02 +0530 Subject: [PATCH 48/82] fix: fixed the unhandled empty directory name case in shallow and blobless clone --- cloning.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cloning.js b/cloning.js index b06e782..002b910 100644 --- a/cloning.js +++ b/cloning.js @@ -20,6 +20,9 @@ async function executeCommand(command, args = []) { async function runShallowClone(repoUrl, branchName='', dirName='') { try { + if (!dirName) { + dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; + } if (!branchName) { await executeCommand('git', ['clone', '--depth', '1', repoUrl, dirName]); } else { @@ -34,6 +37,9 @@ async function runShallowClone(repoUrl, branchName='', dirName='') { async function runBloblessClone(repoUrl, branchName='', dirName='') { try { + if (!dirName) { + dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; + } if (!branchName) { await executeCommand('git', ['clone', '--filter=blob:none', repoUrl, dirName]); } else { @@ -74,7 +80,7 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec await executeCommand('git', ['sparse-checkout', 'add', '!/*', pathToDirectory]); // Determine default branch if not provided - const branchList = (await executeCommand('git', ['ls-remote', '--heads', 'origin'])) + const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) .split('\n') .map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); const defaultLocalBranch = branch || branchList[0] || 'main'; @@ -90,7 +96,9 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec async function normalClone(repoUrl, dirName='') { try { - await executeCommand('git', ['clone', repoUrl, dirName]); + const cloneArgs = ['clone', repoUrl]; + if (dirName) cloneArgs.push(dirName); + await executeCommand('git', cloneArgs); console.log('Cloning completed successfully!'); } catch (error) { console.error(`Error during cloning process: ${error.message}`); From 711b59eaa1527b5bd285722754657af49ea482db Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 19:00:05 +0530 Subject: [PATCH 49/82] feat: added treeless cloning feature in interactive cloning --- cloning.js | 22 +++++++++++++++++-- src/gh/utils/authenticated/interactiveFlow.js | 7 +++++- src/gl/interactiveFlow.js | 22 ++++++++++++++++--- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/cloning.js b/cloning.js index 002b910..4690b31 100644 --- a/cloning.js +++ b/cloning.js @@ -26,7 +26,7 @@ async function runShallowClone(repoUrl, branchName='', dirName='') { if (!branchName) { await executeCommand('git', ['clone', '--depth', '1', repoUrl, dirName]); } else { - await executeCommand('git', ['clone', '--depth', '1', '-b', branchName, repoUrl, dirName]); + await executeCommand('git', ['clone', '--depth', '1', '--single-branch', '-b', branchName, repoUrl, dirName]); } console.log('Shallow cloning completed successfully!'); } catch (error) { @@ -43,7 +43,7 @@ async function runBloblessClone(repoUrl, branchName='', dirName='') { if (!branchName) { await executeCommand('git', ['clone', '--filter=blob:none', repoUrl, dirName]); } else { - await executeCommand('git', ['clone', '--filter=blob:none', '-b', branchName, repoUrl, dirName]); + await executeCommand('git', ['clone', '--filter=blob:none', '--single-branch', '-b', branchName, repoUrl, dirName]); } console.log('Blobless cloning completed successfully!'); } catch (error) { @@ -52,6 +52,23 @@ async function runBloblessClone(repoUrl, branchName='', dirName='') { } } +async function runTreelessClone(repoUrl, dirName = '', branch = '') { + try { + if (!dirName) { + dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; + } + if (!branch) { + await executeCommand('git', ['clone', '--no-checkout', '--filter=tree:0', repoUrl, dirName]); + } else { + await executeCommand('git', ['clone', '--no-checkout', '--filter=tree:0', '--single-branch', '-b', branch, repoUrl, dirName]); + } + console.log('Treeless cloning completed successfully!'); + } catch (error) { + console.error(`Error during treeless clone process: ${error.message}`); + process.exit(1); + } +} + async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirectory) { try { // Validate inputs @@ -111,4 +128,5 @@ export { normalClone, runShallowClone, runBloblessClone, + runTreelessClone, } diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js index ceec87a..33f409e 100644 --- a/src/gh/utils/authenticated/interactiveFlow.js +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -27,7 +27,9 @@ const partialCloningOptions = [ { name: "Blobless Cloning", value: "blobless", - description: "clones the repository without downloading the actual file contents (blobs). You only need the repository history", + description: ` + Clones the repository by fetching only the repository metadata and history. + File contents (blobs) fetched on-demand when accessed.`, } ]; @@ -35,6 +37,7 @@ import { runShallowClone, runSparseCheckout, runBloblessClone, + runTreelessClone, normalClone, } from "../../../../cloning.js"; @@ -107,6 +110,8 @@ const interactiveClone = async (octokit) => { if (partialCloningPreference === "shallow") { await runShallowClone(selectedRepo.html_url); + } else if (partialCloningPreference === "treeless") { + await runTreelessClone(selectedProject.url); } else if (partialCloningPreference === "sparse") { let selectedFolder = await promptFolderSelectionFromRoot(octokit, selectedRepo.full_name); // yes I've changed this to let from const if (selectedFolder === null) { diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index 410ab76..431ac04 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -27,8 +27,16 @@ const partialCloningOptions = [ { name: "Blobless Cloning", value: "blobless", - description: "clones the repository without downloading the actual file contents (blobs). You only need the repository history", - } + description: ` + Clones the repository by fetching only the repository metadata and history. + file contents (blobs) fetched on-demand when accessed.`, + }, + { + name: "Treeless Cloning", + value: "treeless", + description: + "Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", + }, ]; export async function interactiveClone(token) { @@ -40,7 +48,13 @@ export async function interactiveClone(token) { promptProjectSelection, projectInfo, } = await import("./requests.js"); - const { runShallowClone, runSparseCheckout, runBloblessClone, normalClone } = await import("../../cloning.js"); + const { + runShallowClone, + runSparseCheckout, + runBloblessClone, + runTreelessClone, + normalClone, + } = await import("../../cloning.js"); const { default:search } = await import("@inquirer/search"); const { default:input } = await import("../gh/utils/input.js"); @@ -80,6 +94,8 @@ export async function interactiveClone(token) { if (partialCloningPreference === "shallow") { await runShallowClone(selectedProject.url); + } else if (partialCloningPreference === "treeless") { + await runTreelessClone(selectedProject.url); } else if (partialCloningPreference === "sparse") { const selectedProjectContents = await (async () => { const selectedProjectAllContents = await getSingleProject(token, selectedProject.projectID, true); From 1ab9932c7d0a7ff4c2a72cab6dd8adf2f865de9e Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 19:04:47 +0530 Subject: [PATCH 50/82] update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5b1264..d7148c6 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ dist api-urls.json thank.js sample_output.js +src/gl/config.json.bak From 8b439deb418d4afb10a663e8513dac44fcf54c74 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 19:45:51 +0530 Subject: [PATCH 51/82] refactor: properly handle unauthenticated case when cloning a gitlab repo --- src/gl/interactiveFlow.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index 431ac04..640ae67 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -58,6 +58,10 @@ export async function interactiveClone(token) { const { default:search } = await import("@inquirer/search"); const { default:input } = await import("../gh/utils/input.js"); + if (!token) { + console.log(chalk.yellow("No Token Found! Exiting Gracefully...")); + process.exit(1); + } const searchTerm = await input(chalk.greenBright("Search a GitLab Project ->")); const projects = await getProjects(token, searchTerm); From 1f5c8ad0c13baa64accb83e5ca9ceb6e7b08f4d0 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 19:46:25 +0530 Subject: [PATCH 52/82] refactor: improve the GitHub user profile output --- src/gh/utils/authenticated/requests.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gh/utils/authenticated/requests.js b/src/gh/utils/authenticated/requests.js index 74fc353..b035b4e 100644 --- a/src/gh/utils/authenticated/requests.js +++ b/src/gh/utils/authenticated/requests.js @@ -13,12 +13,16 @@ const userProfile = async (octokit) => { }, } = response; + const bioToShow = !(bio) ? chalk.yellow("Looks Like You Don't Have a Bio Yet :(") : bio; + const followersToShow = !(followers) ? chalk.yellow("no") : chalk.yellow(followers); + const followingToShow = !(following) ? chalk.yellow("no one") : chalk.yellow(following); + const profileInfo = ` Hello, - ${userName}! - ${bio} - You have ${followers} followers & you follow ${following} + ${bioToShow} + You have ${followersToShow} followers & you follow ${followingToShow} - You currently have ${publicRepos} public repos and ${privateRepos} private repos. + You currently have ${chalk.yellow(publicRepos)} public repo(s) and ${chalk.yellow(privateRepos)} private repo(s) `; console.log(boxen(profileInfo, From 8c06f4a760d6341fa54086cc819c0ad10dc0bb6d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 19:51:38 +0530 Subject: [PATCH 53/82] chore: added more relevant keywords in package.json --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index ebe4323..02cf0f6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,16 @@ "keywords": [ "git", "github", + "gitlab", + "search", + "fetch", + "clone", + "oauth", + "sparse-checkout", + "sparse", + "shallow", + "blobless", + "treeless", "cloning", "cli", "filesystem", From a4ffe797eddeb9ceb98c104515547cbc982c90f1 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 20:00:28 +0530 Subject: [PATCH 54/82] refactor: treeless cloning feature for Github repos also --- src/gh/utils/authenticated/interactiveFlow.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js index 33f409e..f14bc9e 100644 --- a/src/gh/utils/authenticated/interactiveFlow.js +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -30,7 +30,13 @@ const partialCloningOptions = [ description: ` Clones the repository by fetching only the repository metadata and history. File contents (blobs) fetched on-demand when accessed.`, - } + }, + { + name: "Treeless Cloning", + value: "treeless", + description: + "Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", + }, ]; import { From 614615620d62487590980c7fa8e71d882a2a7a37 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 21:44:19 +0530 Subject: [PATCH 55/82] fix: handled cases related to repos with no subdirectory while sparse cloning --- src/gh/utils/authenticated/interactiveFlow.js | 4 ++-- src/gl/interactiveFlow.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js index f14bc9e..84dfdf5 100644 --- a/src/gh/utils/authenticated/interactiveFlow.js +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -121,9 +121,9 @@ const interactiveClone = async (octokit) => { } else if (partialCloningPreference === "sparse") { let selectedFolder = await promptFolderSelectionFromRoot(octokit, selectedRepo.full_name); // yes I've changed this to let from const if (selectedFolder === null) { - console.log(`${chalk.red(`${selectedFolder.length} Folders found!`)} `); + console.log(`${chalk.red(`No Folders found!`)} `); console.log("Exiting..."); - process.exit(0); + process.exit(1); } const yesOrNo = await input(chalk.greenBright("Partially clone this selected folder? [Y/N]")); diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index 640ae67..f0a4dd5 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -105,6 +105,12 @@ export async function interactiveClone(token) { const selectedProjectAllContents = await getSingleProject(token, selectedProject.projectID, true); return selectedProjectAllContents.filter(content => content.type === "tree"); })(); + + if (!selectedProjectContents.length) { + console.log(chalk.yellow("No folders found in the project")); + process.exit(1); + } + const directoriesFromProjectContents = selectedProjectContents.map((content) => ({ name: content.path, value: content.path, From 3cbb9ffe3cbfca868dfff1e63e234d8f2b122402 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 1 Dec 2024 22:28:48 +0530 Subject: [PATCH 56/82] fix: handling the case of impossible browser autostart at browser login In that case user will have to manually open the shown URL in the Browser --- src/gh/auth/oauth.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/gh/auth/oauth.js b/src/gh/auth/oauth.js index 2ff95d1..ba6afbc 100644 --- a/src/gh/auth/oauth.js +++ b/src/gh/auth/oauth.js @@ -23,10 +23,17 @@ async function getOAuthenticationObject() { console.log( "Waiting for the user to grant access through the browser ...", ); - if (osType() === "Windows_NT") { - await open(verification_uri, { wait: true, app: { name: apps.browser } }); - } else { - await open(verification_uri, { wait: true }); + try { + if (osType() === "Windows_NT") { + await open(verification_uri, { wait: true, app: { name: apps.browser } }); + } else { + await open(verification_uri, { wait: true }); + } + } catch (error) { + console.error("Error opening browser:", error.message); + console.log("Please manually open the following URL in your browser:"); + console.log(verification_uri); + sleep(3000); } let currentInterval = interval; From b8525b1a53b8f20673a4fea8f1ff086e332cc327 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 2 Dec 2024 23:24:54 +0530 Subject: [PATCH 57/82] refactor:keeping the order and name of arguments overall same across all cloning functions --- cloning.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cloning.js b/cloning.js index 4690b31..c1035cc 100644 --- a/cloning.js +++ b/cloning.js @@ -18,15 +18,15 @@ async function executeCommand(command, args = []) { } } -async function runShallowClone(repoUrl, branchName='', dirName='') { +async function runShallowClone(repoUrl, dirName='', branch='') { try { if (!dirName) { dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; } - if (!branchName) { + if (!branch) { await executeCommand('git', ['clone', '--depth', '1', repoUrl, dirName]); } else { - await executeCommand('git', ['clone', '--depth', '1', '--single-branch', '-b', branchName, repoUrl, dirName]); + await executeCommand('git', ['clone', '--depth', '1', '--single-branch', '-b', branch, repoUrl, dirName]); } console.log('Shallow cloning completed successfully!'); } catch (error) { @@ -35,15 +35,15 @@ async function runShallowClone(repoUrl, branchName='', dirName='') { } } -async function runBloblessClone(repoUrl, branchName='', dirName='') { +async function runBloblessClone(repoUrl, dirName='', branch='') { try { if (!dirName) { dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; } - if (!branchName) { + if (!branch) { await executeCommand('git', ['clone', '--filter=blob:none', repoUrl, dirName]); } else { - await executeCommand('git', ['clone', '--filter=blob:none', '--single-branch', '-b', branchName, repoUrl, dirName]); + await executeCommand('git', ['clone', '--filter=blob:none', '--single-branch', '-b', branch, repoUrl, dirName]); } console.log('Blobless cloning completed successfully!'); } catch (error) { @@ -76,17 +76,14 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec throw new Error('Path to directory for sparse checkout cannot be empty.'); } - // Derive default directory name if dirName is not provided if (!dirName) { dirName = repoUrl.split('/').pop().replace(/\.git$/, '') || 'default-repo'; } - // Determine git clone command based on inputs const cloneArgs = ['clone', '--no-checkout', '--filter=blob:none']; if (branch) cloneArgs.push('-b', branch); cloneArgs.push(repoUrl, dirName); - // Execute git clone await executeCommand('git', cloneArgs); // Change to cloned directory @@ -94,7 +91,12 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec // Initialize sparse-checkout await executeCommand('git', ['sparse-checkout', 'set', '--no-cone']); - await executeCommand('git', ['sparse-checkout', 'add', '!/*', pathToDirectory]); + + if (pathToDirectory.constructor === Array) { + await executeCommand('git', ['sparse-checkout', 'add', '!/*', ...pathToDirectory]); + } else { + await executeCommand('git', ['sparse-checkout', 'add', '!/*', pathToDirectory]); + } // Determine default branch if not provided const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) @@ -111,9 +113,10 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec } } -async function normalClone(repoUrl, dirName='') { +async function normalClone(repoUrl, dirName='', branch='') { try { const cloneArgs = ['clone', repoUrl]; + if (branch) cloneArgs.push('--single-branch', '--branch', branch); if (dirName) cloneArgs.push(dirName); await executeCommand('git', cloneArgs); console.log('Cloning completed successfully!'); @@ -124,6 +127,7 @@ async function normalClone(repoUrl, dirName='') { } export { + executeCommand, runSparseCheckout, normalClone, runShallowClone, From 471135d13cef2452550499757851fffa1bc04813 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 2 Dec 2024 23:26:25 +0530 Subject: [PATCH 58/82] fix: error at runTreelessClone function usage at interactiveClone function due to invalid variable name --- src/gh/utils/authenticated/interactiveFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js index 84dfdf5..7cf000f 100644 --- a/src/gh/utils/authenticated/interactiveFlow.js +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -117,7 +117,7 @@ const interactiveClone = async (octokit) => { if (partialCloningPreference === "shallow") { await runShallowClone(selectedRepo.html_url); } else if (partialCloningPreference === "treeless") { - await runTreelessClone(selectedProject.url); + await runTreelessClone(selectedRepo.html_url); } else if (partialCloningPreference === "sparse") { let selectedFolder = await promptFolderSelectionFromRoot(octokit, selectedRepo.full_name); // yes I've changed this to let from const if (selectedFolder === null) { From 9fdf9481ac83bcca0a3e0f141d4b571f5989e063 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 2 Dec 2024 23:27:40 +0530 Subject: [PATCH 59/82] changing commands. keeping all interactive cloning options in a single command (icl) and manual cloning commands in another single command (clone) --- index.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 4da327b..4a2af64 100644 --- a/index.js +++ b/index.js @@ -106,32 +106,78 @@ program }); program - .command('ghclone') - .description("clone a GitHub repository") - .option('-u, --unauthenticated', 'legacy version of this command (without authentication and partial cloning support)') + .command('icl') + .description("interactively clone a GitHub or GitLab repository") + .option('-u, --unauthenticated', 'legacy version of the GitHub repo interactive clone command (without authentication and partial cloning support)') + .option('--gh', 'clone a GitHub repository') + .option('--gl', 'clone a GitLab repository') .action( async (options) => { if (options.unauthenticated || options.u) { const { unAuthenticatedInteractiveClone } = await import("./src/gh/utils/unauthenticated/interactiveFlow.js"); await unAuthenticatedInteractiveClone(); - } else { + } else if (options.gh) { const { interactiveClone } = await import("./src/gh/utils/authenticated/interactiveFlow.js"); const { login } = await import("./src/gh/utils/authenticated/requests.js"); const octokit = await login(); await interactiveClone(octokit); + } else if (options.gl) { + const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); + const { configJson } = await import("./src/gl/requests.js"); + await interactiveClone(configJson.token); } } ); program - .command('glclone') - .description("clone a GitLab repository") - .action( - async () => { - const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); - const { configJson } = await import("./src/gl/requests.js"); - await interactiveClone(configJson.token); + .command('clone [DIRNAME] [BRANCHNAME]') + .usage(' [DIRNAME] [BRANCHNAME] [options]') + .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') + .description('clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch. Exceptionally, in case of sparse cloning [BRANCHNAME] is the branch which will be switched to after cloning is completed. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit from the default one after sparse cloning.') + .option('--sparse ', 'clone only the specified directory or directories of the repository(sparse checkout)') + .option('--shallow', 'shallow clone only the latest commit of the repository') + .option('--blobless', 'run a blobless clone of the repository') + .option('--treeless', 'run a treeless clone of the repository') + .action(async (REPO_URL, DIRNAME, BRANCHNAME, options) => { + const { runSparseCheckout, normalClone, runShallowClone, runBloblessClone, runTreelessClone } = await import("./cloning.js"); + + const optionalArgs = []; + const urlPattern = new RegExp('^(https?|ssh):\\/\\/([\\w.-]+(:[\\w.-]+)?@)?([\\w.-]+)(:\\d+)?(\\/[\\w.-]+)*\\/?$'); + const isValidUrl = (url) => { + return urlPattern.test(url); } - ); + if (!isValidUrl(REPO_URL)) { + console.error(`Invalid URL: ${REPO_URL}`); + process.exit(1); + } + + if (DIRNAME) { + optionalArgs.push(DIRNAME); + } else { + optionalArgs.push(''); + } + if (BRANCHNAME) { + optionalArgs.push(BRANCHNAME); + } else { + optionalArgs.push(''); + } + + try { + if (options.sparse) { + await runSparseCheckout(REPO_URL, ...optionalArgs, options.sparse); + } else if (options.shallow) { + await runShallowClone(REPO_URL, ...optionalArgs); + } else if (options.blobless) { + await runBloblessClone(REPO_URL, ...optionalArgs); + } else if (options.treeless) { + await runTreelessClone(REPO_URL, ...optionalArgs); + } else { + await normalClone(REPO_URL, ...optionalArgs); + } + } catch (error) { + console.error(`error during ${'`gitfm clone`'}: ${error.message}`); + process.exit(1); + } + }); -program.parse(); +program.parseAsync(); From d9a7c68a899fb0f347ceede23095f32a0b8400d6 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Tue, 3 Dec 2024 00:30:37 +0530 Subject: [PATCH 60/82] detailed helptexts about the github authorization through browser login --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 4a2af64..cfc3335 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ program program .command('ghauth') .description("authorize or unauthorize gitfm with your GitHub") - .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token") + .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token. \nNote: In case of login with browser, browser will auto open the verification URL only if your default browser is anything between chrome, edge or firefox. Otherwise, you will be prompted to manually open the URL in your browser.") .option('--logout', "logout from the CLI and delete(revoke) your GitHub token") .option('--refresh', "refresh your GitHub token") .action(async (options) => { From e44598777574154df2edad4da36bc21de4f4494a Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Tue, 3 Dec 2024 00:37:39 +0530 Subject: [PATCH 61/82] chore: better formatted description for `gitfm clone` --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index cfc3335..a7af22c 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ program program .command('ghauth') .description("authorize or unauthorize gitfm with your GitHub") - .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token. \nNote: In case of login with browser, browser will auto open the verification URL only if your default browser is anything between chrome, edge or firefox. Otherwise, you will be prompted to manually open the URL in your browser.") + .option('--login [TYPE]', "Choose your prefered way to log in. If wrong/no argument is provided, interactive login will take place. Valid arguments - web, token. \n\nNote: In case of login with browser, browser will auto open the verification URL only if your default browser is anything between chrome, edge or firefox. Otherwise, you will be prompted to manually open the URL in your browser.") .option('--logout', "logout from the CLI and delete(revoke) your GitHub token") .option('--refresh', "refresh your GitHub token") .action(async (options) => { @@ -133,7 +133,7 @@ program .command('clone [DIRNAME] [BRANCHNAME]') .usage(' [DIRNAME] [BRANCHNAME] [options]') .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') - .description('clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch. Exceptionally, in case of sparse cloning [BRANCHNAME] is the branch which will be switched to after cloning is completed. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit from the default one after sparse cloning.') + .description('clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\nExceptionally, in case of sparse cloning [BRANCHNAME] is the branch which will be switched to after cloning is completed. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit from the default one after sparse cloning.') .option('--sparse ', 'clone only the specified directory or directories of the repository(sparse checkout)') .option('--shallow', 'shallow clone only the latest commit of the repository') .option('--blobless', 'run a blobless clone of the repository') From baf1da90109629728ae030e519d26a64135826f3 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 4 Dec 2024 01:27:14 +0530 Subject: [PATCH 62/82] update no of attempts of polling in oauth and remove unnecessary console.error at catch block --- src/gh/auth/oauth.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gh/auth/oauth.js b/src/gh/auth/oauth.js index ba6afbc..e7366e0 100644 --- a/src/gh/auth/oauth.js +++ b/src/gh/auth/oauth.js @@ -37,7 +37,7 @@ async function getOAuthenticationObject() { } let currentInterval = interval; - let remainingAttempts = 25; + let remainingAttempts = 150; while (true) { remainingAttempts -= 1; if (remainingAttempts < 0) { @@ -70,7 +70,6 @@ async function getOAuthenticationObject() { } else if (errorCode === "access_denied") { return { error: errorCode }; // Exit loop as the process cannot continue } else { - console.error(`Unexpected error: ${errorCode}`); return {error: `Unexpected 400 status error: ${errorCode}`}; // 400 status unknown errors } } else { From 8f69602121924b483a3cf52179de8d318ef6bab0 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 4 Dec 2024 01:27:58 +0530 Subject: [PATCH 63/82] keeping all cloning and partial cloning options in one file for modular approach --- cloneOpts.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 cloneOpts.js diff --git a/cloneOpts.js b/cloneOpts.js new file mode 100644 index 0000000..66d8fad --- /dev/null +++ b/cloneOpts.js @@ -0,0 +1,40 @@ +const cloningOptions = [ + { + name: "Normal Cloning", + value: "normal", + description: "Clones the entire repository, including all branches and history.", + }, + { + name: "Partial Cloning", + value: "partial", + description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", + } +]; + +const partialCloningOptions = [ + { + name: "Shallow Cloning", + value: "shallow", + description: "shallow clone only the latest commit", + }, + { + name: "Sparse Cloning", + value: "sparse", + description: "clone only a specific folder", + }, + { + name: "Blobless Cloning", + value: "blobless", + description: ` + Clones the repository by fetching only the repository metadata and history. + file contents (blobs) fetched on-demand when accessed.`, + }, + { + name: "Treeless Cloning", + value: "treeless", + description: + "Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", + }, +]; + +export { cloningOptions, partialCloningOptions }; From f00423dda4f82ea2ec307ba5c510b518251c5f77 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 4 Dec 2024 01:29:28 +0530 Subject: [PATCH 64/82] added option to choose cone or no-cone mode in runSparseCheckout() function and refactored the handling of branch argument to the function --- cloning.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cloning.js b/cloning.js index c1035cc..d1f2056 100644 --- a/cloning.js +++ b/cloning.js @@ -69,7 +69,7 @@ async function runTreelessClone(repoUrl, dirName = '', branch = '') { } } -async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirectory) { +async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirectory, noCone=true) { try { // Validate inputs if (!pathToDirectory) { @@ -90,22 +90,30 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec process.chdir(dirName); // Initialize sparse-checkout - await executeCommand('git', ['sparse-checkout', 'set', '--no-cone']); + const sparseAddDirArgs = ['sparse-checkout', 'add']; + if (noCone) { + await executeCommand('git', ['sparse-checkout', 'set', '--no-cone']); + sparseAddDirArgs.push('!/*'); + } else { + await executeCommand('git', ['sparse-checkout', 'set', '--cone']); + } if (pathToDirectory.constructor === Array) { - await executeCommand('git', ['sparse-checkout', 'add', '!/*', ...pathToDirectory]); + await executeCommand('git', [...sparseAddDirArgs, ...pathToDirectory]); } else { - await executeCommand('git', ['sparse-checkout', 'add', '!/*', pathToDirectory]); + await executeCommand('git', [...sparseAddDirArgs, pathToDirectory]); } - // Determine default branch if not provided - const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) + if (!branch) { + // Determine default branch if not provided + const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) .split('\n') .map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); - const defaultLocalBranch = branch || branchList[0] || 'main'; + const defaultLocalBranch = branchList[0] || 'main'; - // Checkout branch - await executeCommand('git', ['checkout', defaultLocalBranch]); + // Checkout branch + await executeCommand('git', ['checkout', defaultLocalBranch]); + } console.log('Cloning specific directory completed successfully!'); } catch (error) { console.error('Error during sparse checkout process:', error.message || error); From e56606032b17679c72b0a7ad0543cba3c8a90005 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 4 Dec 2024 01:30:14 +0530 Subject: [PATCH 65/82] using the cloning and partial cloning options from the cloneOpts file --- src/gh/utils/authenticated/interactiveFlow.js | 43 +++--------------- src/gl/interactiveFlow.js | 44 +++---------------- 2 files changed, 10 insertions(+), 77 deletions(-) diff --git a/src/gh/utils/authenticated/interactiveFlow.js b/src/gh/utils/authenticated/interactiveFlow.js index 7cf000f..31b9b94 100644 --- a/src/gh/utils/authenticated/interactiveFlow.js +++ b/src/gh/utils/authenticated/interactiveFlow.js @@ -1,43 +1,10 @@ const { promisify } = await import("node:util"); const sleep = promisify(setTimeout); -const cloningOptions = [ - { - name: "Normal Cloning", - value: "normal", - description: "Clones the entire repository, including all branches and history.", - }, - { - name: "Partial Cloning", - value: "partial", - description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", - } -]; -const partialCloningOptions = [ - { - name: "Shallow Cloning", - value: "shallow", - description: "shallow clone only the latest commit", - }, - { - name: "Sparse Cloning", - value: "sparse", - description: "clone only a specific folder", - }, - { - name: "Blobless Cloning", - value: "blobless", - description: ` - Clones the repository by fetching only the repository metadata and history. - File contents (blobs) fetched on-demand when accessed.`, - }, - { - name: "Treeless Cloning", - value: "treeless", - description: - "Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", - }, -]; +import { + cloningOptions, + partialCloningOptions, +} from "../../../../cloneOpts.js"; import { runShallowClone, @@ -128,7 +95,7 @@ const interactiveClone = async (octokit) => { const yesOrNo = await input(chalk.greenBright("Partially clone this selected folder? [Y/N]")); if (yesOrNo.toLowerCase() === "y") { - await runSparseCheckout(selectedRepo.html_url, '', '', selectedFolder); + await runSparseCheckout(selectedRepo.html_url, '', '', selectedFolder, true); } else { await sleep(1000); console.log('Entering into this directory'); diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index f0a4dd5..7420a05 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -1,43 +1,9 @@ import chalk from "chalk"; -const cloningOptions = [ - { - name: "Normal Cloning", - value: "normal", - description: "Clones the entire repository, including all branches and history.", - }, - { - name: "Partial Cloning", - value: "partial", - description: "Clones only specific parts of the repository, such as a single branch or a subset of files.", - } -]; - -const partialCloningOptions = [ - { - name: "Shallow Cloning", - value: "shallow", - description: "shallow clone only the latest commit", - }, - { - name: "Sparse Cloning", - value: "sparse", - description: "clone only a specific folder", - }, - { - name: "Blobless Cloning", - value: "blobless", - description: ` - Clones the repository by fetching only the repository metadata and history. - file contents (blobs) fetched on-demand when accessed.`, - }, - { - name: "Treeless Cloning", - value: "treeless", - description: - "Clones the repository without checking out the working directory tree,\n including only the repository metadata and history.", - }, -]; +import { + cloningOptions, + partialCloningOptions, +} from "../../cloneOpts.js"; export async function interactiveClone(token) { const { headerText } = await import("../gh/utils/headerText.js"); @@ -128,7 +94,7 @@ export async function interactiveClone(token) { return directoriesFromProjectContents; }, }); - await runSparseCheckout(selectedProject.url, '', '', pathToDirectory); + await runSparseCheckout(selectedProject.url, '', '', pathToDirectory, true); } catch (error) { console.error(error.message); process.exit(1); From e9309ca08bc8e1223ea02332a89f9ce15db8bb79 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Wed, 4 Dec 2024 01:31:44 +0530 Subject: [PATCH 66/82] added cone/no-cone mode logic as an argument in the clone command of the CLI and refactored and fixed the clone command description --- index.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index a7af22c..8d6dbeb 100644 --- a/index.js +++ b/index.js @@ -130,15 +130,15 @@ program ); program - .command('clone [DIRNAME] [BRANCHNAME]') - .usage(' [DIRNAME] [BRANCHNAME] [options]') + .command('clone [DIRNAME] [BRANCHNAME] [CONE_MODE]') + .usage(' [DIRNAME] [BRANCHNAME] [CONE_MODE] [options]') .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') - .description('clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\nExceptionally, in case of sparse cloning [BRANCHNAME] is the branch which will be switched to after cloning is completed. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit from the default one after sparse cloning.') + .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter regex pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter regex patterns. Also you will get rid of the files at the repo root.') .option('--sparse ', 'clone only the specified directory or directories of the repository(sparse checkout)') .option('--shallow', 'shallow clone only the latest commit of the repository') .option('--blobless', 'run a blobless clone of the repository') .option('--treeless', 'run a treeless clone of the repository') - .action(async (REPO_URL, DIRNAME, BRANCHNAME, options) => { + .action(async (REPO_URL, DIRNAME, BRANCHNAME, CONE_MODE, options) => { const { runSparseCheckout, normalClone, runShallowClone, runBloblessClone, runTreelessClone } = await import("./cloning.js"); const optionalArgs = []; @@ -164,7 +164,18 @@ program try { if (options.sparse) { - await runSparseCheckout(REPO_URL, ...optionalArgs, options.sparse); + let noCone = true; + if (CONE_MODE) { + if (CONE_MODE === "nocone") { + noCone = true; + } else if (CONE_MODE === "cone") { + noCone = false; + } else { + console.error(`Invalid value for argument [CONE_MODE]: ${CONE_MODE}`); + process.exit(1); + } + } + await runSparseCheckout(REPO_URL, ...optionalArgs, options.sparse, noCone); } else if (options.shallow) { await runShallowClone(REPO_URL, ...optionalArgs); } else if (options.blobless) { @@ -175,7 +186,7 @@ program await normalClone(REPO_URL, ...optionalArgs); } } catch (error) { - console.error(`error during ${'`gitfm clone`'}: ${error.message}`); + console.error(`error during command ${'`gitfm clone`'}: ${error.message}`); process.exit(1); } }); From 3dc609a2172e728acb640272a6bd42bd3192113d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 6 Dec 2024 21:13:33 +0530 Subject: [PATCH 67/82] fix: reverting back to the old way of selecting the branch of the repo in a sparse checkout cloning --- cloning.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cloning.js b/cloning.js index d1f2056..57c0be0 100644 --- a/cloning.js +++ b/cloning.js @@ -81,7 +81,7 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec } const cloneArgs = ['clone', '--no-checkout', '--filter=blob:none']; - if (branch) cloneArgs.push('-b', branch); + // if (branch) cloneArgs.push('-b', branch); cloneArgs.push(repoUrl, dirName); await executeCommand('git', cloneArgs); @@ -104,16 +104,14 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec await executeCommand('git', [...sparseAddDirArgs, pathToDirectory]); } - if (!branch) { - // Determine default branch if not provided - const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) - .split('\n') - .map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); - const defaultLocalBranch = branchList[0] || 'main'; - - // Checkout branch - await executeCommand('git', ['checkout', defaultLocalBranch]); - } + // Determine default branch if not provided + const branchList = (await executeCommand('git', ['ls-remote', '--sort=-committerdate', '--heads', 'origin'])) + .split('\n') + .map(line => line.split('\t').pop().replace('refs/heads/', '').trim()); + const defaultLocalBranch = branch || branchList[0] || 'main'; + + // Checkout branch + await executeCommand('git', ['checkout', defaultLocalBranch]); console.log('Cloning specific directory completed successfully!'); } catch (error) { console.error('Error during sparse checkout process:', error.message || error); From 6b3d25534a5f3ae3f821e7015dab1578a04802d3 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 6 Dec 2024 21:37:41 +0530 Subject: [PATCH 68/82] fix: correction in stdout message after successful execution of the sparse checkout --- cloning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloning.js b/cloning.js index 57c0be0..8ce6cd5 100644 --- a/cloning.js +++ b/cloning.js @@ -112,7 +112,7 @@ async function runSparseCheckout(repoUrl, dirName = '', branch = '', pathToDirec // Checkout branch await executeCommand('git', ['checkout', defaultLocalBranch]); - console.log('Cloning specific directory completed successfully!'); + console.log('Cloning portion of the repo completed successfully!'); } catch (error) { console.error('Error during sparse checkout process:', error.message || error); process.exit(1); From 96dd98d09a01aeb668709506ca17dfa8c380b52f Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Fri, 6 Dec 2024 21:39:32 +0530 Subject: [PATCH 69/82] fix: correction of the sparse option description in the clone command --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 8d6dbeb..1ec4f33 100644 --- a/index.js +++ b/index.js @@ -134,7 +134,7 @@ program .usage(' [DIRNAME] [BRANCHNAME] [CONE_MODE] [options]') .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter regex pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter regex patterns. Also you will get rid of the files at the repo root.') - .option('--sparse ', 'clone only the specified directory or directories of the repository(sparse checkout)') + .option('--sparse ', 'clone only the specified director(y|ies) or file(s) of the repository(sparse checkout)') .option('--shallow', 'shallow clone only the latest commit of the repository') .option('--blobless', 'run a blobless clone of the repository') .option('--treeless', 'run a treeless clone of the repository') From 7407019a34ebf2910bdd6f9758abf40d2e79caea Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 8 Dec 2024 22:32:47 +0530 Subject: [PATCH 70/82] chore: add screenshots of command helptexts --- assets/img/clone-helptext.png | Bin 0 -> 100310 bytes assets/img/helptext.png | Bin 0 -> 54646 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/img/clone-helptext.png create mode 100644 assets/img/helptext.png diff --git a/assets/img/clone-helptext.png b/assets/img/clone-helptext.png new file mode 100644 index 0000000000000000000000000000000000000000..aecc01c8555c3d69c646d77d505af13d3b525a7f GIT binary patch literal 100310 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%0D@X~cj>6A?V;U^IT14I2C+? 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 literal 0 HcmV?d00001 diff --git a/assets/img/helptext.png b/assets/img/helptext.png new file mode 100644 index 0000000000000000000000000000000000000000..65ecc53f7aae867c43a034996188f294d82ce437 GIT binary patch literal 54646 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ΜUlg^jXS=tvoa+%3x;9cXLhXa)=@MY+(`@|faY`_1>dNhZ#eHvZK z;$FWu2aR}T=GATr5b)8!H4uzOdn7OO$E>F2$ShgcP9P zXZ)v7_X8V&4HnimGn=I*`xq55!-|9xgzzC)65&cB>&F)~AjZCUzcD;)1T(_P>VT`# z{l9XvUtQM=FvtQh+zizKJ*Itj8;%DLJV6SuvAlaTBZ*}D6`c~hw2e4P=G}Oc!2H17Qv@cjLIV-Ow>H8MKo+R3 zQw^TzMSE+|z1>tCc7@;TI8b*igI`HIwP?t$(@5cR+k_hrEG7e+#8~f{IhyzCt7-sas7?_);8aM#^Y{_fWd(uOloA*tK zsers(HFo)QVHN@bv=Ol`57zCFTtD`?q^=1@ueAsA-3>pG_4ta9sc4fg&wn4ePkVS4 zlhCI}K1m9B&Wb%8-U5+uUgtZx{YUAc(z{24>IU?gB=KKdJIc2UFJcjEBP3n9#(b_O z^>MR#Iw{pBO__v5H1h8s9ceJUk*$$j1k?>z*!;Np9xMm+fTvw9vXqAzY0dqf0id`3JaN1FW1N62OJUeM-|GwbDnGv zuR}E-`aUa2p&Fw3dp&iPs`PHjo!?k(E6LFifG}0kA-(gSoANHfVvt&ubL-?V6*X1{&+++VnZoZTXt!DSpP(b~1R==@RS*HOaF(~*8=<4m&8ESj z;%0rxgjl7FNn`Zo6BN9LI_tGqgzP-?f za37lXfQG*pFnu7Fv=)N?E`m|_H|V-j#O@*X7f4-tEm8uz;D561KKMWOL9IDWf_(lQ z9bGuJAZDz%+E?zn62GVq1MU>y*p^@IKs?nVC5Q={w5(!0jW8M5`cx+=tIXVv}M` ztV$zZ(e`aQ9znM|2upAjO&(f#gl4Fzlx?3KLnoFl|4fpDU*q2pc4})}VS9X^@%)W> z9uTXG5h(Z&UBi0=i}8_Z;w%V$t(xywvHGXFy1MF=iUu#Lmh?Ljc#={ur%CU3>`YVm zy+En#ZZX;SvI|l)nMJ#xYhw0$cDHE=^LN{AA)jl!aNc!P8;k69>Vh2)!t1&5Jw9~8 zHTct)<^3jkEXaUx|Fqd4sv!S0&|@+@gh$yse>dJu!Pkcfbd*TmQ!n%`4@)+MMM+#X ztL-Zw+*&4h|7jDgzbS2SW(wR_$E_WH7f^EUBMQ0yyg2BNw)|rO6G1+&S`^JBoBImJ zZ!ms*gJt*>PPtSH|4G=%8o>Ykmmt3?kcWlspPc7^*!_Hz_bb~dNK7|%>_(>fisa=- zOEH3+-i5J`9h(<-m&s!9n_>Uwyt4%J)L{r=>344z0rPafyX>gprsnLbg$? z*M|t9KoecV94fVteErCKI!Y_*1F5%(ndOR;#dW8rK#HUlEq)?-BBMp6586FB#?vt zB&p={W-Lb9!{{1Qd2wxFp0tyg#=>B%c{yAVx&B-vtRT!=-Sy{D%L*hyTjUUl3h?DPcZK@ z$``i%#X5)mrSr9(g!VA?{6}5ZK4MyVflQ(BLVVHgyRDc&DKq>?)3T*)UwNT`%v^~y zhUZ$+N2wwK-NLN4=bx5d4y_l5uP{FjJ1;$e5B*V1NdDhX#tD@j^x%?Cv%e1MGB`y2 OaaBS6O3`IA|NjCJQ(;B` literal 0 HcmV?d00001 From a7bfcf6e7879e8754cb6092cc31717e4a211fae8 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Sun, 8 Dec 2024 23:56:32 +0530 Subject: [PATCH 71/82] docs: Keeping the documentation in the README file (incomplete, WIP) --- README.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index af8c63e..546bd6c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,40 @@ # gitFM -gitFM is a command-line tool based on Node.js that searches the GitHub repository, retrieves repository file structures, and creates repository files. +gitFM is a command-line tool that can search GitHub/GitLab repositories, retrieve repository file contents and clone them normally or partially depending on your choice directly from the terminal(never leave the terminal). Your best friend for fetching repository as filesystem and easy cloning. ## Installation -First, make sure you have installed Node.js. Then install gitFM globally by running the following command: +First, make sure you have Node.js and git pre-installed in your system. Then install gitFM globally by running the following command: ```bash npm install -g gitfm ``` +## Usage -## Feedback and contributions +### Helptexts -If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the GitHub repository. +Run `gitfm --help` or `gitfm -h` or `gitfm help` to know get the helptext. It will look like this - +![screenshot of the helptext](./assets/img/helptext.png) -GitHub repository: https://github.com/Debajyati/gitFM +To get more info about any individual command of gitfm, run - `gitfm help `. For example, - if you want to know about the `clone` command in detail, you would run - `gitfm help clone`. + +![screenshot of the clone command helptext](./assets/img/clone-helptext.png) + +> [!NOTE] +> In the helptexts, - an argument enclosed within `[]` signify that the arg is optional and within `<>` signify that the arg is mandatory. + +### Descriptions of the Individual Commands + +1. `gitfm clone [options]` :- This command has four options, - `--blobless`, `--treeless`, `--shallow` and `--sparse`. When none of them are provided, it proceeds to perform a normal(cloning the whole repository as usual) git clone of the provided repository URL. This command doesn't require any authentication. This is going to be your most helpful/used command of this application (most likely). `--shallow` option is going to be used when you want to clone only the latest commit of the repository. `--blobless` and `--treeless` options are going to be used when you want to clone a repository in a blobless or treeless format. The `--sparse` option is going to be used when you want to clone only the specified files or directories of the repository. For more info about the options, run `gitfm clone --help` or `gitfm help clone`. +2. `gitfm ghauth [options]` :- This command has three options, - `--login`, `--logout` and `--refresh`. When none of them are provided, it will first check if you are already authenticated with GitHub. If you are, it will say so and exit. If you are not, it will ask you to login with your GitHub account(basically, the same as `gitfm ghauth --login`). If you want to logout, run `gitfm ghauth --logout`. If you want to refresh your token, run `gitfm ghauth --refresh`. For more info about the options, run `gitfm ghauth --help` or `gitfm help ghauth`. +3. `gitfm ghprofile` :- +4. `gitfm glauth [options]` :- +5. `gitfm icl [options]` :- + +## Feedback + +If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the [GitHub repository](https://github.com/Debajyati/gitFM). + +## Star This repo +Show some love to this project by starring it. It will help in increasing the visibility of this project and also in motivating me to work on this project more often. From f57fcfa3ceb80bfa9b9dd3659952d4a246f5c2eb Mon Sep 17 00:00:00 2001 From: Debajyati Date: Mon, 9 Dec 2024 00:21:04 +0530 Subject: [PATCH 72/82] chore: edit helptext.png --- assets/img/helptext.png | Bin 54646 -> 48341 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/img/helptext.png b/assets/img/helptext.png index 65ecc53f7aae867c43a034996188f294d82ce437..cef2c03404efca08efd334360931ffffa53cf088 100644 GIT binary patch literal 48341 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 c`o3STsHs1FpV#>l(*05(**R@5vGFL1bg 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ΜUlg^jXS=tvoa+%3x;9cXLhXa)=@MY+(`@|faY`_1>dNhZ#eHvZK z;$FWu2aR}T=GATr5b)8!H4uzOdn7OO$E>F2$ShgcP9P zXZ)v7_X8V&4HnimGn=I*`xq55!-|9xgzzC)65&cB>&F)~AjZCUzcD;)1T(_P>VT`# z{l9XvUtQM=FvtQh+zizKJ*Itj8;%DLJV6SuvAlaTBZ*}D6`c~hw2e4P=G}Oc!2H17Qv@cjLIV-Ow>H8MKo+R3 zQw^TzMSE+|z1>tCc7@;TI8b*igI`HIwP?t$(@5cR+k_hrEG7e+#8~f{IhyzCt7-sas7?_);8aM#^Y{_fWd(uOloA*tK zsers(HFo)QVHN@bv=Ol`57zCFTtD`?q^=1@ueAsA-3>pG_4ta9sc4fg&wn4ePkVS4 zlhCI}K1m9B&Wb%8-U5+uUgtZx{YUAc(z{24>IU?gB=KKdJIc2UFJcjEBP3n9#(b_O z^>MR#Iw{pBO__v5H1h8s9ceJUk*$$j1k?>z*!;Np9xMm+fTvw9vXqAzY0dqf0id`3JaN1FW1N62OJUeM-|GwbDnGv zuR}E-`aUa2p&Fw3dp&iPs`PHjo!?k(E6LFifG}0kA-(gSoANHfVvt&ubL-?V6*X1{&+++VnZoZTXt!DSpP(b~1R==@RS*HOaF(~*8=<4m&8ESj z;%0rxgjl7FNn`Zo6BN9LI_tGqgzP-?f za37lXfQG*pFnu7Fv=)N?E`m|_H|V-j#O@*X7f4-tEm8uz;D561KKMWOL9IDWf_(lQ z9bGuJAZDz%+E?zn62GVq1MU>y*p^@IKs?nVC5Q={w5(!0jW8M5`cx+=tIXVv}M` ztV$zZ(e`aQ9znM|2upAjO&(f#gl4Fzlx?3KLnoFl|4fpDU*q2pc4})}VS9X^@%)W> z9uTXG5h(Z&UBi0=i}8_Z;w%V$t(xywvHGXFy1MF=iUu#Lmh?Ljc#={ur%CU3>`YVm zy+En#ZZX;SvI|l)nMJ#xYhw0$cDHE=^LN{AA)jl!aNc!P8;k69>Vh2)!t1&5Jw9~8 zHTct)<^3jkEXaUx|Fqd4sv!S0&|@+@gh$yse>dJu!Pkcfbd*TmQ!n%`4@)+MMM+#X ztL-Zw+*&4h|7jDgzbS2SW(wR_$E_WH7f^EUBMQ0yyg2BNw)|rO6G1+&S`^JBoBImJ zZ!ms*gJt*>PPtSH|4G=%8o>Ykmmt3?kcWlspPc7^*!_Hz_bb~dNK7|%>_(>fisa=- zOEH3+-i5J`9h(<-m&s!9n_>Uwyt4%J)L{r=>344z0rPafyX>gprsnLbg$? z*M|t9KoecV94fVteErCKI!Y_*1F5%(ndOR;#dW8rK#HUlEq)?-BBMp6586FB#?vt zB&p={W-Lb9!{{1Qd2wxFp0tyg#=>B%c{yAVx&B-vtRT!=-Sy{D%L*hyTjUUl3h?DPcZK@ z$``i%#X5)mrSr9(g!VA?{6}5ZK4MyVflQ(BLVVHgyRDc&DKq>?)3T*)UwNT`%v^~y zhUZ$+N2wwK-NLN4=bx5d4y_l5uP{FjJ1;$e5B*V1NdDhX#tD@j^x%?Cv%e1MGB`y2 OaaBS6O3`IA|NjCJQ(;B` From 0af5695a842bbce5844ee6608e8b5b5d43cc89c7 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 01:33:32 +0530 Subject: [PATCH 73/82] changed the location of the gitlab token file and so all the code related to it --- index.js | 29 +++++++++++++++++++++-------- src/gl/auth.js | 29 ++--------------------------- src/gl/config.js | 8 ++++++++ src/gl/config.json | 3 --- src/gl/requests.js | 40 ++++++++++++++++++++++++++-------------- src/gl/tokenhelpers.js | 23 +++++++++++++++++------ 6 files changed, 74 insertions(+), 58 deletions(-) create mode 100644 src/gl/config.js delete mode 100644 src/gl/config.json diff --git a/index.js b/index.js index 1ec4f33..a89f2b7 100644 --- a/index.js +++ b/index.js @@ -53,9 +53,10 @@ program .option('--rotate ', "rotate the personal access token with an Expiry Date (NOTE: Expiry Date has to be in YYYY-MM-DD format!)") .action(async (options) => { try { + const { getStoredToken } = await import("./src/gl/tokenhelpers.js"); if (options.logout) { - const { revokeToken, configJson } = await import("./src/gl/requests.js"); - const token = configJson.token; + const { revokeToken } = await import("./src/gl/requests.js"); + const token = getStoredToken(GITLAB_CONFIG_FILE); if (token !== null) { const resStatus = await revokeToken(token); if (resStatus === "successful") { @@ -74,14 +75,26 @@ program process.exit(1); } } else if (options.login) { - const { configJson } = await import("./src/gl/requests.js"); - if (configJson.token !== null) { + const storedtoken = getStoredToken(GITLAB_CONFIG_FILE); + const authenticate = async () => { + const { tokenAuthenticate } = await import("./src/gl/auth.js"); + const token = await tokenAuthenticate(); + const { saveToken } = await import("./src/gl/tokenhelpers.js"); + saveToken({token: token}, GITLAB_CONFIG_FILE); + } + if (storedtoken !== null) { console.log("A token already in use. Checking validity..."); + const { checkTokenIsValid } = await import("./src/gl/requests.js"); + const storedTokenIsValid = await checkTokenIsValid(storedtoken); + if (storedTokenIsValid) { + console.log("Token is valid!"); + } else { + console.error("Token is invalid or expired!"); + await authenticate(); + } + } else { + await authenticate(); } - const { tokenAuthenticate } = await import("./src/gl/auth.js"); - const token = await tokenAuthenticate(); - const { saveToken } = await import("./src/gl/tokenhelpers.js"); - saveToken({token: token}, GITLAB_CONFIG_FILE); } else { const { rotateToken, configJson } = await import("./src/gl/requests.js"); const token = configJson.token; diff --git a/src/gl/auth.js b/src/gl/auth.js index 762bb72..356ed2c 100644 --- a/src/gl/auth.js +++ b/src/gl/auth.js @@ -1,30 +1,5 @@ -import input from '../gh/utils/input.js'; - -const baseURL = "https://gitlab.com/api/v4"; - -const checkTokenIsValid = async (token) => { - const url = `${baseURL}/personal_access_tokens`; - - try { - const response = await fetch(url,{ - method: "GET", - headers: { - "PRIVATE-TOKEN": token, - } - }); - - if (response.status === 401) { - console.error("invalid access token: Aborted!"); - process.exit(1); - } else { - console.log("Authentication Successful"); - return token; - } - } catch (error) { - console.error(error.message); - process.exit(1); - } -} +const { default:input} = await import('../gh/utils/input.js'); +const { checkTokenIsValid } = await import('./requests.js'); const tokenAuthenticate = async () => { console.log('Create a new personal access token from your GitLab profile'); diff --git a/src/gl/config.js b/src/gl/config.js new file mode 100644 index 0000000..270287a --- /dev/null +++ b/src/gl/config.js @@ -0,0 +1,8 @@ +import path from "node:path"; +import os from "os"; + +const config = { + TOKEN_FILE: path.join(os.homedir(), '.gl.gitfmrc.json'), +}; + +export { config }; diff --git a/src/gl/config.json b/src/gl/config.json deleted file mode 100644 index 9ccfc6b..0000000 --- a/src/gl/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "token": null -} diff --git a/src/gl/requests.js b/src/gl/requests.js index 8dbc37f..39171e4 100644 --- a/src/gl/requests.js +++ b/src/gl/requests.js @@ -1,20 +1,32 @@ import chalk from "chalk"; +import { config } from "./config.js"; +const { TOKEN_FILE:CONFIG_FILE } = config; -// JSON import of the config file containing the token -const configJson = await (async ()=>{ - const { createRequire } = await import("node:module"); - const require = createRequire(import.meta.url); - return require("./config.json"); -})(); +const baseURL = "https://gitlab.com/api/v4"; -const CONFIG_FILE = await (async () => { - const {default:path} = await import("node:path"); - const { fileURLToPath } = await import("node:url"); - const dirname = path.dirname(fileURLToPath(import.meta.url)); - return path.join(dirname, "config.json"); -})(); // file path +const checkTokenIsValid = async (token) => { + const url = `${baseURL}/personal_access_tokens`; -const baseURL = "https://gitlab.com/api/v4"; + try { + const response = await fetch(url,{ + method: "GET", + headers: { + "PRIVATE-TOKEN": token, + } + }); + + if (response.status === 401) { + console.error("invalid access token: Aborted!"); + process.exit(1); + } else { + console.log("Authentication Successful"); + return token; + } + } catch (error) { + console.error(error.message); + process.exit(1); + } +} async function revokeToken(token) { const url = `${baseURL}/personal_access_tokens/self`; @@ -241,7 +253,7 @@ function renderProjectContents(contents, indent = " ") { export { CONFIG_FILE, - configJson, + checkTokenIsValid, revokeToken, rotateToken, getProjects, diff --git a/src/gl/tokenhelpers.js b/src/gl/tokenhelpers.js index bbb60c6..3cf34ea 100644 --- a/src/gl/tokenhelpers.js +++ b/src/gl/tokenhelpers.js @@ -1,19 +1,30 @@ import { existsSync, writeFileSync } from "fs"; +// Check if a stored token exists +function getStoredToken(TOKEN_FILE) { + if (existsSync(TOKEN_FILE)) { + const tokenData = JSON.parse(readFileSync(TOKEN_FILE, "utf-8")); + if (tokenData && tokenData.token) { + return tokenData.token; + } + } else { + return null; + } +} // Save token and the authType to a file with type key -function saveToken(tokenData, CONFIG_FILE) { - writeFileSync(CONFIG_FILE, JSON.stringify(tokenData, null, 2)); +function saveToken(tokenData, TOKEN_FILE) { + writeFileSync(TOKEN_FILE, JSON.stringify(tokenData, null, 2)); } // Clear token from storage -function clearToken(CONFIG_FILE) { - if (existsSync(CONFIG_FILE)) { +function clearToken(TOKEN_FILE) { + if (existsSync(TOKEN_FILE)) { const dataToSave = { token: null, }; - writeFileSync(CONFIG_FILE, JSON.stringify(dataToSave, null, 2)); + writeFileSync(TOKEN_FILE, JSON.stringify(dataToSave, null, 2)); } } -export { saveToken, clearToken }; +export { getStoredToken, saveToken, clearToken }; From 3e2d6f3fa3b5d0788fe0bba47bb2c987806e1e2d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 01:37:46 +0530 Subject: [PATCH 74/82] further adjustments --- index.js | 5 +++-- src/gl/interactiveFlow.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index a89f2b7..54c5893 100644 --- a/index.js +++ b/index.js @@ -136,8 +136,9 @@ program await interactiveClone(octokit); } else if (options.gl) { const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); - const { configJson } = await import("./src/gl/requests.js"); - await interactiveClone(configJson.token); + const { storedToken } = await import("./src/gl/tokenhelpers.js"); + const token = storedToken(GITLAB_CONFIG_FILE); + await interactiveClone(token); } } ); diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index 7420a05..9c0b50f 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -25,7 +25,7 @@ export async function interactiveClone(token) { const { default:input } = await import("../gh/utils/input.js"); if (!token) { - console.log(chalk.yellow("No Token Found! Exiting Gracefully...")); + console.log(chalk.yellow("No Token Found or Invalid Token! Exiting Gracefully...")); process.exit(1); } const searchTerm = await input(chalk.greenBright("Search a GitLab Project ->")); From 7e9fe6f8114bdedbce73d797a46f4f26b19d8e04 Mon Sep 17 00:00:00 2001 From: Debajyati Dey <127122455+Debajyati@users.noreply.github.com> Date: Mon, 9 Dec 2024 01:39:52 +0530 Subject: [PATCH 75/82] docs: update README.md (WIP) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 546bd6c..6945f31 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ To get more info about any individual command of gitfm, run - `gitfm help Date: Mon, 9 Dec 2024 11:15:03 +0530 Subject: [PATCH 76/82] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6945f31..c13ae85 100644 --- a/README.md +++ b/README.md @@ -36,5 +36,5 @@ To get more info about any individual command of gitfm, run - `gitfm help Date: Mon, 9 Dec 2024 12:24:46 +0530 Subject: [PATCH 77/82] docs: adding descriptions of more commands --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c13ae85..8d94f34 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,15 @@ To get more info about any individual command of gitfm, run - `gitfm help [options]`). For more info about the options, run `gitfm clone --help` or `gitfm help clone`. +- **`gitfm ghauth [options]`** :- This command has three options, - `--login`, `--logout` and `--refresh`. When none of them are provided, it will first check if you are already authenticated with GitHub. If you are, it will say so and exit. If you are not, it will ask you to login with your GitHub account(basically, the same as `gitfm ghauth --login`). If you want to logout, run `gitfm ghauth --logout`. If you want to refresh your token, run `gitfm ghauth --refresh`. In case of browser login, a browser window/tab will be automatically opened with the verification URL if and only if your default browser is anything between **chrome**, **edge** or **firefox**. If your default browser is something else, the verification URL will be shown in the console so that you can manually open it in the browser to log in. After a successful login, the token is stored in the `$HOME` directory of your system in a config file named `.gitfmrc.json`. Your token is revoked by gitfm while logging out only if it is an ***oauth token** (you authenticated using Browser with passcodes). If it is a **PAT**, gitfm will just reset the config file. **TOKEN WON'T BE DELETED**. To delete your token, you should directly do that from your GitHub developer settings. For more info about the options, run `gitfm ghauth --help` or `gitfm help ghauth`. +- **`gitfm ghprofile`** :- Shows a minimal view of your GitHub profile. This command takes no arguments. Requires you to be authenticated. If not, it will autostart interactive authentication mode. When authentication process will complete, it will show your profile info. +- **`gitfm glauth [options]`** :- This command also contains 3 options - `--login`, `--logout`, `--rotate`. You can authenticate gitfm with your Gitlab account only using a personal access token. Browser login is not supported and won't be implemented in future. The token is stored in a JSON file(`.gl.gitfmrc.json`) inside the `$HOME` directory of your system. For more info, run - `gitfm glauth -h` or `gitfm help glauth`. +- **`gitfm icl [options]`** :- ## Feedback If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the [GitHub repository](https://github.com/Debajyati/gitFM). -## Star๐ŸŒŸ This repo +## Star ๐ŸŒŸ This repo Show some love to this project by starring it. It will help in increasing the visibility of this project and also in motivating me to work on this project more often. From 93912ea6dca0a8376a1c14e07b1e6999c4169ac4 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 12:38:32 +0530 Subject: [PATCH 78/82] docs: almost complete README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8d94f34..6e2d3e0 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,17 @@ To get more info about any individual command of gitfm, run - `gitfm help [options]`). For more info about the options, run `gitfm clone --help` or `gitfm help clone`. -- **`gitfm ghauth [options]`** :- This command has three options, - `--login`, `--logout` and `--refresh`. When none of them are provided, it will first check if you are already authenticated with GitHub. If you are, it will say so and exit. If you are not, it will ask you to login with your GitHub account(basically, the same as `gitfm ghauth --login`). If you want to logout, run `gitfm ghauth --logout`. If you want to refresh your token, run `gitfm ghauth --refresh`. In case of browser login, a browser window/tab will be automatically opened with the verification URL if and only if your default browser is anything between **chrome**, **edge** or **firefox**. If your default browser is something else, the verification URL will be shown in the console so that you can manually open it in the browser to log in. After a successful login, the token is stored in the `$HOME` directory of your system in a config file named `.gitfmrc.json`. Your token is revoked by gitfm while logging out only if it is an ***oauth token** (you authenticated using Browser with passcodes). If it is a **PAT**, gitfm will just reset the config file. **TOKEN WON'T BE DELETED**. To delete your token, you should directly do that from your GitHub developer settings. For more info about the options, run `gitfm ghauth --help` or `gitfm help ghauth`. +- **`gitfm ghauth [options]`** :- This command has three options, - `--login`, `--logout` and `--refresh`. When none of them are provided, it will first check if you are already authenticated with GitHub. If you are, it will say so and exit. If you are not, it will ask you to login with your GitHub account(basically, the same as `gitfm ghauth --login`). If you want to logout, run `gitfm ghauth --logout`. If you want to refresh your token, run `gitfm ghauth --refresh`. In case of browser login, a browser window/tab will be automatically opened with the verification URL if and only if your default browser is anything between **chrome**, **edge** or **firefox**. If your default browser is something else, the verification URL will be shown in the console so that you can manually open it in the browser to log in. After a successful login, the token is stored in the `$HOME` directory of your system in a config file named `.gitfmrc.json`. Your token is revoked by gitfm while logging out only if it is an **oauth token** (you authenticated using Browser with passcodes). If it is a **PAT**, gitfm will just reset the config file. **TOKEN WON'T BE DELETED**. To delete your token, you should directly do that from your GitHub developer settings. For more info about the options, run `gitfm ghauth --help` or `gitfm help ghauth`. - **`gitfm ghprofile`** :- Shows a minimal view of your GitHub profile. This command takes no arguments. Requires you to be authenticated. If not, it will autostart interactive authentication mode. When authentication process will complete, it will show your profile info. - **`gitfm glauth [options]`** :- This command also contains 3 options - `--login`, `--logout`, `--rotate`. You can authenticate gitfm with your Gitlab account only using a personal access token. Browser login is not supported and won't be implemented in future. The token is stored in a JSON file(`.gl.gitfmrc.json`) inside the `$HOME` directory of your system. For more info, run - `gitfm glauth -h` or `gitfm help glauth`. -- **`gitfm icl [options]`** :- +- **`gitfm icl [options]`** :- This command has three options - `--gh`, `--gl` and `--unauthenticated`. When none of them are provided, it will do nothing.. If you want to interactively search and clone a GitHub repository, run `gitfm icl --gh`. If you want to interactively search and clone a GitLab repository, run `gitfm icl --gl`. If you want to run the legacy version of the GitHub repo interactive clone command(without partial cloning support but doesn't require authentication), run `gitfm icl --unauthenticated`. For more info about the options, run `gitfm icl --help` or `gitfm help icl`. ## Feedback If you encounter any problems or have any suggestions when using gitFM, you are welcome to submit an issue in the [GitHub repository](https://github.com/Debajyati/gitFM). ## Star ๐ŸŒŸ This repo -Show some love to this project by starring it. It will help in increasing the visibility of this project and also in motivating me to work on this project more often. +Show some love to this project by starring it. It will help in increasing the visibility of this project and also in motivating me to work on this project more often. More features will be added to this project in the future. :) + +## License +Internet Standard Consortium License (ISC) From bdd7ab7a3002dce88ec8a0077f78dffa12383430 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 13:07:28 +0530 Subject: [PATCH 79/82] fix: resolve bugs related to gitlab config file migration --- index.js | 4 ++-- src/gl/tokenhelpers.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 54c5893..fb2998d 100644 --- a/index.js +++ b/index.js @@ -136,8 +136,8 @@ program await interactiveClone(octokit); } else if (options.gl) { const { interactiveClone } = await import("./src/gl/interactiveFlow.js"); - const { storedToken } = await import("./src/gl/tokenhelpers.js"); - const token = storedToken(GITLAB_CONFIG_FILE); + const { getStoredToken } = await import("./src/gl/tokenhelpers.js"); + const token = getStoredToken(GITLAB_CONFIG_FILE); await interactiveClone(token); } } diff --git a/src/gl/tokenhelpers.js b/src/gl/tokenhelpers.js index 3cf34ea..2397e14 100644 --- a/src/gl/tokenhelpers.js +++ b/src/gl/tokenhelpers.js @@ -1,4 +1,4 @@ -import { existsSync, writeFileSync } from "fs"; +import { readFileSync, existsSync, writeFileSync } from "fs"; // Check if a stored token exists function getStoredToken(TOKEN_FILE) { From 08395cede84db4282acafd432da71f1e1805ba4a Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 14:32:31 +0530 Subject: [PATCH 80/82] update clone command description --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index fb2998d..2bbed2c 100644 --- a/index.js +++ b/index.js @@ -147,7 +147,7 @@ program .command('clone [DIRNAME] [BRANCHNAME] [CONE_MODE]') .usage(' [DIRNAME] [BRANCHNAME] [CONE_MODE] [options]') .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') - .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter regex pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter regex patterns. Also you will get rid of the files at the repo root.') + .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter patterns. Also you won\'t get of the files at the repo root by default. But you can change that with two preceeding arguments(pattern to include and pattern to exclude).') .option('--sparse ', 'clone only the specified director(y|ies) or file(s) of the repository(sparse checkout)') .option('--shallow', 'shallow clone only the latest commit of the repository') .option('--blobless', 'run a blobless clone of the repository') From 4169ec94f1375ec597e3351a450424997c8620c1 Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 14:41:21 +0530 Subject: [PATCH 81/82] fix: resolve unbounded wait at repo selection when no repos are found by searching gitlab (icl command) --- src/gl/interactiveFlow.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gl/interactiveFlow.js b/src/gl/interactiveFlow.js index 9c0b50f..e6805ed 100644 --- a/src/gl/interactiveFlow.js +++ b/src/gl/interactiveFlow.js @@ -30,6 +30,10 @@ export async function interactiveClone(token) { } const searchTerm = await input(chalk.greenBright("Search a GitLab Project ->")); const projects = await getProjects(token, searchTerm); + if (!projects.length) { + console.log(chalk.yellow("No Projects Found! Exiting Gracefully...")); + process.exit(1); + } const selectedProject = await promptProjectSelection(projects); projectInfo(selectedProject); From 4250f7fac405e463f624d7afddcaa8f7265b997d Mon Sep 17 00:00:00 2001 From: Debajyati Dey Date: Mon, 9 Dec 2024 14:42:16 +0530 Subject: [PATCH 82/82] fix typo --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2bbed2c..d13f0e1 100644 --- a/index.js +++ b/index.js @@ -147,7 +147,7 @@ program .command('clone [DIRNAME] [BRANCHNAME] [CONE_MODE]') .usage(' [DIRNAME] [BRANCHNAME] [CONE_MODE] [options]') .summary('clone any remote repository using the url. Run `gitfm clone --help` to know how to use this command.') - .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter patterns. Also you won\'t get of the files at the repo root by default. But you can change that with two preceeding arguments(pattern to include and pattern to exclude).') + .description(' clone any remote git repository using the url. is mandatory argument. It is the url of the repository to be cloned. [DIRNAME] and [BRANCHNAME] are optional arguments. [DIRNAME] is the directory name where the repository will be cloned. [BRANCHNAME] is one of all the branches of the remote repository. Only specify if you want to clone a specific branch. If [DIRNAME] is not provided, the repository will be cloned in a directory with the same name as the repository. If [BRANCHNAME] is not provided, the repository will be cloned in the default branch.\n\n Exceptionally, in case of sparse cloning, [BRANCHNAME] is the branch which will be checked out instead of HEAD(you still have access to all the remote branches inside your local clone), at the time of cloning. By default(when no [BRANCHNAME] is provided), the algorithm will switch to the branch with the latest commit, from the default one, after sparse cloning. \n\n [CONE_MODE] is an optional argument meant to be used with only --sparse option. If provided, valid values - cone or nocone. If not provided, default value is nocone. In cone mode you can\'t enter pattern(s) to clone all the file(s) and/or director(y|ies) that match the pattern. You have to manually specify whole path of the director(y|ies) and/or file(s) to clone. Also all the files at the repo root will be cloned and you can\'t change it. In nocone mode, you can enter patterns. Also you won\'t get the files at the repo root by default. But you can change that with two preceeding arguments(pattern to include and pattern to exclude).') .option('--sparse ', 'clone only the specified director(y|ies) or file(s) of the repository(sparse checkout)') .option('--shallow', 'shallow clone only the latest commit of the repository') .option('--blobless', 'run a blobless clone of the repository')