diff --git a/bun.lock b/bun.lock index edd9156..bcefb74 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "workspaces": { "": { "dependencies": { - "@badisi/latest-version": "^7.0.14", + "@colors/colors": "^1.6.0", "bplist-parser": "^0.3.2", "bytebuffer": "^5.0.1", "cgbi-to-png": "^1.0.7", @@ -14,6 +14,7 @@ "filesize-parser": "^1.5.1", "form-data": "^4.0.2", "fs-extra": "8", + "global-dirs": "^4.0.0", "gradle-to-js": "^2.0.1", "i18next": "^24.2.3", "isomorphic-git": "^1.30.1", @@ -23,6 +24,7 @@ "progress": "^2.0.3", "properties": "^1.2.1", "read": "^4.1.0", + "registry-auth-token": "^5.1.0", "semver": "^7.7.1", "tcp-ping": "^0.1.1", "tty-table": "4.2", @@ -50,14 +52,9 @@ "@biomejs/biome", "@swc/core", ], - "patchedDependencies": { - "@badisi/latest-version@7.0.14": "patches/@badisi%2Flatest-version@7.0.14.patch", - }, "packages": { "@babel/runtime": ["@babel/runtime@7.26.10", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw=="], - "@badisi/latest-version": ["@badisi/latest-version@7.0.14", "", { "dependencies": { "@colors/colors": "^1.6.0", "global-dirs": "3.0.1", "ora": "^8.2.0", "registry-auth-token": "^5.1.0", "semver": "^7.7.1" }, "bin": { "latest-version": "bin/latest-version", "lv": "bin/latest-version" } }, "sha512-/p+0uggyIiv8PkNIcRgUvm/CWWnclJHRSSeYry4iHzXF3m1IXRS6hXKR9dtEgpF3XsvNYEvlGGF/0MBvBACgQA=="], - "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], @@ -270,10 +267,6 @@ "cli-arguments": ["cli-arguments@0.2.1", "", {}, "sha512-vaoTjiREjxKlpTNMiaJUkQnYRhgui8r+huhB6mMHcGQyz5F7Hd1o1jsW9C/wRKjlNYQ6fTvODLtZe7DxfEIz8g=="], - "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], - - "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], @@ -330,7 +323,7 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "es-abstract": ["es-abstract@1.23.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", "data-view-buffer": "^1.0.1", "data-view-byte-length": "^1.0.1", "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", "get-intrinsic": "^1.2.4", "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "hasown": "^2.0.2", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-data-view": "^1.0.1", "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.2", "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.9", "string.prototype.trimend": "^1.0.8", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", "which-typed-array": "^1.1.15" } }, "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A=="], @@ -392,8 +385,6 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="], - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -404,7 +395,7 @@ "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "global-dirs": ["global-dirs@3.0.1", "", { "dependencies": { "ini": "2.0.0" } }, "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA=="], + "global-dirs": ["global-dirs@4.0.0", "", { "dependencies": { "ini": "2.0.0" } }, "sha512-PJ0OjGf/kVuu9gh5IPgAyssfJne5PsU9+ICxfWiRYDUnYq8ob+Y2nSWAEUNEHRj+gowyzI+wg5/nWkvcjcyLwg=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -470,8 +461,6 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], - "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], @@ -492,8 +481,6 @@ "is-typed-array": ["is-typed-array@1.1.13", "", { "dependencies": { "which-typed-array": "^1.1.14" } }, "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw=="], - "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "is-weakref": ["is-weakref@1.0.2", "", { "dependencies": { "call-bind": "^1.0.2" } }, "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ=="], "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -518,8 +505,6 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], - "long": ["long@3.2.0", "", {}, "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg=="], "lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], @@ -540,8 +525,6 @@ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -570,8 +553,6 @@ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], - "p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], @@ -632,8 +613,6 @@ "responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], - "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], - "reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -682,8 +661,6 @@ "source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], - "stream-to": ["stream-to@0.2.2", "", {}, "sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw=="], "stream-to-buffer": ["stream-to-buffer@0.1.0", "", { "dependencies": { "stream-to": "~0.2.0" } }, "sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw=="], @@ -694,7 +671,7 @@ "streamx": ["streamx@2.20.2", "", { "dependencies": { "fast-fifo": "^1.3.2", "queue-tick": "^1.0.1", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string.prototype.trim": ["string.prototype.trim@1.2.9", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.0", "es-object-atoms": "^1.0.0" } }, "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw=="], @@ -812,8 +789,6 @@ "call-bind/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "define-data-property/es-define-property": ["es-define-property@1.0.0", "", { "dependencies": { "get-intrinsic": "^1.2.4" } }, "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ=="], @@ -848,22 +823,10 @@ "isomorphic-unzip/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], - "log-symbols/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], - - "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - "make-dir/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], "object.assign/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], - "ora/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], - - "ora/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], - - "restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "safe-array-concat/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], "safe-array-concat/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], @@ -880,8 +843,6 @@ "smartwrap/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], - "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - "string.prototype.trim/es-object-atoms": ["es-object-atoms@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw=="], "string.prototype.trimend/es-object-atoms": ["es-object-atoms@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw=="], @@ -898,10 +859,6 @@ "which-typed-array/gopd": ["gopd@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.1.3" } }, "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA=="], - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "yazl/buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="], "@types/fs-extra/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], @@ -920,8 +877,6 @@ "call-bind/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "define-data-property/es-define-property/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], "define-data-property/gopd/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], @@ -936,22 +891,16 @@ "is-array-buffer/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], - "ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "set-function-length/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], "side-channel/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], "smartwrap/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], - "smartwrap/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "smartwrap/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], "smartwrap/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "typed-array-byte-length/gopd/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], "typed-array-byte-offset/gopd/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], @@ -960,10 +909,6 @@ "which-typed-array/gopd/get-intrinsic": ["get-intrinsic@1.2.4", "", { "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" } }, "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ=="], - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "define-data-property/es-define-property/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], "define-data-property/gopd/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], @@ -974,8 +919,6 @@ "smartwrap/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "smartwrap/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "typed-array-byte-length/gopd/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], "typed-array-byte-offset/gopd/get-intrinsic/has-symbols": ["has-symbols@1.0.3", "", {}, "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="], diff --git a/package.json b/package.json index 97452f3..51235d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-update-cli", - "version": "1.44.3", + "version": "1.44.5", "description": "command line tool for react-native-update (remote updates for react native)", "main": "index.js", "bin": { @@ -35,7 +35,7 @@ }, "homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli", "dependencies": { - "@badisi/latest-version": "^7.0.14", + "@colors/colors": "^1.6.0", "bplist-parser": "^0.3.2", "bytebuffer": "^5.0.1", "cgbi-to-png": "^1.0.7", @@ -46,6 +46,7 @@ "filesize-parser": "^1.5.1", "form-data": "^4.0.2", "fs-extra": "8", + "global-dirs": "^4.0.0", "gradle-to-js": "^2.0.1", "i18next": "^24.2.3", "isomorphic-git": "^1.30.1", @@ -55,6 +56,7 @@ "progress": "^2.0.3", "properties": "^1.2.1", "read": "^4.1.0", + "registry-auth-token": "^5.1.0", "semver": "^7.7.1", "tcp-ping": "^0.1.1", "tty-table": "4.2", @@ -82,8 +84,5 @@ "trustedDependencies": [ "@biomejs/biome", "@swc/core" - ], - "patchedDependencies": { - "@badisi/latest-version@7.0.14": "patches/@badisi%2Flatest-version@7.0.14.patch" - } + ] } diff --git a/patches/@badisi%2Flatest-version@7.0.14.patch b/patches/@badisi%2Flatest-version@7.0.14.patch deleted file mode 100644 index 23d8316..0000000 --- a/patches/@badisi%2Flatest-version@7.0.14.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/index.js b/index.js -index a7af7990160fa54a549cbb6245017d55e6b150f5..74b3372c53b9c693af5b571722bafd97e448a34b 100644 ---- a/index.js -+++ b/index.js -@@ -100,7 +100,7 @@ var downloadMetadata = (pkgName, options) => { - } - }); - const abort = (error) => { -- request.removeAllListeners(); -+ // request.removeAllListeners(); - request.destroy(); - reject(error); - }; -@@ -110,6 +110,9 @@ var downloadMetadata = (pkgName, options) => { - request.once("error", (err) => { - abort(err); - }); -+ request.on("close", () => { -+ request.removeAllListeners(); -+ }); - }); - }; - var getCacheDir = (name = "@badisi/latest-version") => { diff --git a/src/utils/index.ts b/src/utils/index.ts index 3558910..f8c7cb4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,7 +5,7 @@ import pkg from '../../package.json'; import AppInfoParser from './app-info-parser'; import semverSatisfies from 'semver/functions/satisfies'; import chalk from 'chalk'; -import latestVersion from '@badisi/latest-version'; +import latestVersion from '../utils/latest-version'; import { checkPlugins } from './check-plugin'; import { read } from 'read'; diff --git a/src/utils/latest-version/cli.ts b/src/utils/latest-version/cli.ts new file mode 100644 index 0000000..fd4c532 --- /dev/null +++ b/src/utils/latest-version/cli.ts @@ -0,0 +1,443 @@ +import { + blue, + bold, + cyan, + gray, + green, + italic, + magenta, + red, + reset, + strip, + underline, + yellow, +} from '@colors/colors/safe'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname } from 'node:path'; +import latestVersion, { + type Package, + type PackageJson, + type LatestVersionPackage, + type LatestVersionOptions, +} from '.'; +import semverMajor from 'semver/functions/major'; +import semverDiff from 'semver/functions/diff'; + +interface TableColumn { + label: string; + attrName: keyof TableRow; + align: 'left' | 'center' | 'right'; + maxLength: number; + items: string[]; +} + +type TableRowGroup = + | 'patch' + | 'minor' + | 'major' + | 'majorVersionZero' + | 'unknown'; + +interface TableRow { + name: string; + location: string; + installed: string; + tagOrRange: string; + separator: string; + wanted: string; + latest: string; + group: TableRowGroup; +} + +const colorizeDiff = (from: string, to: string): string => { + const toParts = to.split('.'); + + const diffIndex = from.split('.').findIndex((part, i) => part !== toParts[i]); + if (diffIndex !== -1) { + let color = magenta; + if (toParts[0] !== '0') { + color = diffIndex === 0 ? red : diffIndex === 1 ? cyan : green; + } + const start = toParts.slice(0, diffIndex).join('.'); + const mid = diffIndex === 0 ? '' : '.'; + const end = color(toParts.slice(diffIndex).join('.')); + return `${start}${mid}${end}`; + } + return to; +}; + +const columnCellRenderer = (column: TableColumn, row: TableRow): string => { + let text = row[column.attrName]; + const gap = + text.length < column.maxLength + ? ' '.repeat(column.maxLength - text.length) + : ''; + + switch (column.attrName) { + case 'name': + text = yellow(text); + break; + case 'installed': + case 'separator': + text = blue(text); + break; + case 'location': + case 'tagOrRange': + text = gray(text); + break; + case 'wanted': + text = colorizeDiff(row.installed, text); + break; + case 'latest': + if (text !== row.wanted) { + text = colorizeDiff(row.installed, text); + } + break; + default: + break; + } + + return column.align === 'right' ? `${gap}${text}` : `${text}${gap}`; +}; + +const columnHeaderRenderer = (column: TableColumn): string => { + const text = column.label; + const gap = + text.length < column.maxLength + ? ' '.repeat(column.maxLength - text.length) + : ''; + return column.align === 'right' + ? `${gap}${underline(text)}` + : `${underline(text)}${gap}`; +}; + +const drawBox = ( + lines: string[], + color = yellow, + horizontalPadding = 3, +): void => { + const maxLineWidth = lines.reduce( + (max, row) => Math.max(max, strip(row).length), + 0, + ); + + console.log(color(`┌${'─'.repeat(maxLineWidth + horizontalPadding * 2)}┐`)); + lines.forEach((row) => { + const padding = ' '.repeat(horizontalPadding); + const fullRow = `${row}${' '.repeat(maxLineWidth - strip(row).length)}`; + console.log( + `${color('│')}${padding}${reset(fullRow)}${padding}${color('│')}`, + ); + }); + console.log(color(`└${'─'.repeat(maxLineWidth + horizontalPadding * 2)}┘`)); +}; + +const getTableColumns = (rows: TableRow[]): TableColumn[] => { + const columns: TableColumn[] = [ + { + label: 'Package', + attrName: 'name', + align: 'left', + maxLength: 0, + items: [], + }, + { + label: 'Location', + attrName: 'location', + align: 'left', + maxLength: 0, + items: [], + }, + { + label: 'Installed', + attrName: 'installed', + align: 'right', + maxLength: 0, + items: [], + }, + { + label: '', + attrName: 'separator', + align: 'center', + maxLength: 0, + items: [], + }, + { + label: 'Range', + attrName: 'tagOrRange', + align: 'right', + maxLength: 0, + items: [], + }, + { + label: '', + attrName: 'separator', + align: 'center', + maxLength: 0, + items: [], + }, + { + label: 'Wanted', + attrName: 'wanted', + align: 'right', + maxLength: 0, + items: [], + }, + { + label: 'Latest', + attrName: 'latest', + align: 'right', + maxLength: 0, + items: [], + }, + ]; + rows.forEach((row) => { + columns.forEach((column) => { + column.maxLength = Math.max( + column.label.length, + column.maxLength, + row[column.attrName].length || 0, + ); + }); + }); + return columns; +}; + +const getTableRows = (updates: LatestVersionPackage[]): TableRow[] => { + return updates.reduce((all, pkg) => { + const { + name, + latest, + local, + globalNpm, + globalYarn, + wantedTagOrRange, + updatesAvailable, + } = pkg; + const getGroup = (a?: string, b?: string): TableRowGroup => { + if (b && semverMajor(b) === 0) { + return 'majorVersionZero'; + } else if (a && b) { + const releaseType = semverDiff(a, b) ?? ''; + if (['major', 'premajor', 'prerelease'].includes(releaseType)) { + return 'major'; + } else if (['minor', 'preminor'].includes(releaseType)) { + return 'minor'; + } else if (['patch', 'prepatch'].includes(releaseType)) { + return 'patch'; + } + } + return 'unknown'; + }; + const add = ( + group: TableRowGroup, + location: string, + installed?: string, + wanted?: string, + ) => + all.push({ + name: ' ' + name, + location, + installed: installed ?? 'unknown', + latest: latest ?? 'unknown', + tagOrRange: wantedTagOrRange ?? 'unknown', + separator: '→', + wanted: wanted ?? 'unknown', + group, + }); + if (updatesAvailable) { + if (updatesAvailable.local) { + add( + getGroup(local, updatesAvailable.local), + 'local', + local, + updatesAvailable.local, + ); + } + if (updatesAvailable.globalNpm) { + add( + getGroup(globalNpm, updatesAvailable.globalNpm), + 'NPM global', + globalNpm, + updatesAvailable.globalNpm, + ); + } + if (updatesAvailable.globalYarn) { + add( + getGroup(globalYarn, updatesAvailable.globalYarn), + 'YARN global', + globalYarn, + updatesAvailable.globalYarn, + ); + } + } else { + if (local && local !== latest) { + add(getGroup(local, latest), 'local', local, pkg.wanted); + } + if (globalNpm && globalNpm !== latest) { + add(getGroup(globalNpm, latest), 'NPM global', globalNpm, pkg.wanted); + } + if (globalYarn && globalYarn !== latest) { + add( + getGroup(globalYarn, latest), + 'YARN global', + globalYarn, + pkg.wanted, + ); + } + if (!local && !globalNpm && !globalYarn) { + add('unknown', 'unknown', 'unknown', pkg.wanted); + } + } + return all; + }, []); +}; + +const displayTable = (latestVersionPackages: LatestVersionPackage[]): void => { + const updates = latestVersionPackages.filter((pkg) => pkg.updatesAvailable); + if (updates.length) { + const rows = getTableRows(updates); + const hasUpdates = rows.some((row) => row.installed !== 'unknown'); + const columns = getTableColumns(rows); + const columnGap = 2; + + const getGroupLines = ( + groupType: TableRowGroup, + color: (str: string) => string, + title: string, + description?: string, + ): string[] => { + const items = rows + .filter((row) => row.group === groupType) + .sort((a, b) => (a.name > b.name ? 1 : -1)); + return !items.length + ? [] + : [ + '', + color(`${bold(title)} ${italic(`(${description})`)}`), + ...items.map((row) => + columns + .map((column) => columnCellRenderer(column, row)) + .join(' '.repeat(columnGap)), + ), + ]; + }; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + drawBox( + [ + '', + hasUpdates ? yellow('Important updates are available.') : undefined, + hasUpdates ? '' : undefined, + columns.map(columnHeaderRenderer).join(' '.repeat(columnGap)), + ...getGroupLines( + 'patch', + green, + 'Patch', + 'backwards-compatible bug fixes', + ), + ...getGroupLines( + 'minor', + cyan, + 'Minor', + 'backwards-compatible features', + ), + ...getGroupLines( + 'major', + red, + 'Major', + 'potentially breaking API changes', + ), + ...getGroupLines( + 'majorVersionZero', + magenta, + 'Major version zero', + 'not stable, anything may change', + ), + ...getGroupLines('unknown', blue, 'Missing', 'not installed'), + '', + ].filter((line) => line !== undefined) as string[], + ); + } else { + console.log(green('🎉 Packages are up-to-date')); + } +}; + +const checkVersions = async ( + packages: Package | Package[] | PackageJson, + skipMissing: boolean, + options: LatestVersionOptions = { useCache: true }, +): Promise => { + const ora = (await import('ora')).default; + const spinner = ora({ text: cyan('Checking versions...') }); + spinner.start(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + let latestVersionPackages: LatestVersionPackage[] = await latestVersion( + packages, + options, + ); + if (skipMissing) { + latestVersionPackages = latestVersionPackages.filter( + (pkg) => pkg.local ?? pkg.globalNpm ?? pkg.globalYarn, + ); + } + spinner.stop(); + displayTable(latestVersionPackages); +}; + +void (async () => { + let args = process.argv.slice(2); + + const skipMissing = args.includes('--skip-missing'); + + // Remove any options from the arguments + args = args.filter((arg) => !arg.startsWith('-')); + + // If argument is a package.json file + if (args.length === 1 && args[0].endsWith('package.json')) { + if (existsSync(args[0])) { + process.chdir(dirname(args[0])); + await checkVersions( + JSON.parse(readFileSync(args[0]).toString()) as PackageJson, + skipMissing, + ); + } else { + console.log(cyan('No package.json file were found')); + } + } + // else.. + else { + // Check if a local package.json file exists + let localPkgJson: PackageJson | undefined; + if (existsSync('package.json')) { + localPkgJson = JSON.parse(readFileSync('package.json').toString()); + } + + // Check given arguments + if (args.length) { + // Map arguments with any range that could be found in local package.json + args = args.map((arg) => { + if (localPkgJson?.dependencies?.[arg]) { + return `${arg}@${localPkgJson.dependencies?.[arg]}`; + } + if (localPkgJson?.devDependencies?.[arg]) { + return `${arg}@${localPkgJson.devDependencies?.[arg]}`; + } + if (localPkgJson?.peerDependencies?.[arg]) { + return `${arg}@${localPkgJson.peerDependencies?.[arg]}`; + } + return arg; + }); + await checkVersions(args, skipMissing); + } + // ...else check the local package.json if any + else if (localPkgJson) { + await checkVersions(localPkgJson, skipMissing); + } + // ...else do nothing + else { + console.log(cyan('No packages were found')); + } + } +})(); diff --git a/src/utils/latest-version/index.ts b/src/utils/latest-version/index.ts new file mode 100644 index 0000000..6052ca4 --- /dev/null +++ b/src/utils/latest-version/index.ts @@ -0,0 +1,497 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import type { + RequestOptions as HttpRequestOptions, + Agent, + IncomingMessage, +} from 'node:http'; +import type { RequestOptions as HttpsRequestOptions } from 'node:https'; +import { join, dirname, resolve as pathResolve, parse } from 'node:path'; +import { npm, yarn } from 'global-dirs'; +import { homedir } from 'node:os'; +import { URL } from 'node:url'; + +import getRegistryUrl from 'registry-auth-token/registry-url'; +import registryAuthToken from 'registry-auth-token'; +import maxSatisfying from 'semver/ranges/max-satisfying'; +import gt from 'semver/functions/gt'; + +interface RegistryVersions { + /** + * The latest version of the package found on the registry (if found). + */ + latest?: string; + /** + * The next version of the package found on the registry (if found). + */ + next?: string; + /** + * The latest version of the package found on the registry and satisfied by the wanted tag or version range. + */ + wanted?: string; +} + +interface InstalledVersions { + /** + * The current local installed version of the package (if installed). + */ + local?: string; + /** + * The current npm global installed version of the package (if installed). + */ + globalNpm?: string; + /** + * The current yarn global installed version of the package (if installed). + */ + globalYarn?: string; +} + +interface LatestVersionPackage extends InstalledVersions, RegistryVersions { + /** + * The name of the package. + */ + name: string; + /** + * The tag or version range that was provided (if provided). + * @default "latest" + */ + wantedTagOrRange?: string; + /** + * Whether the local or global installed versions (if any) could be upgraded or not, based on the wanted version. + */ + updatesAvailable: + | { + local: string | false; + globalNpm: string | false; + globalYarn: string | false; + } + | false; + /** + * Any error that might have occurred during the process. + */ + error?: Error; +} + +interface RequestOptions { + readonly ca?: string | Buffer | Array; + readonly rejectUnauthorized?: boolean; + readonly agent?: Agent | boolean; + readonly timeout?: number; +} + +interface LatestVersionOptions { + /** + * Awaiting the api to return might take time, depending on the network, and might impact your package loading performance. + * You can use the cache mechanism to improve load performance and reduce unnecessary network requests. + * If `useCache` is not supplied, the api will always check for updates and wait for every requests to return before returning itself. + * If `useCache` is used, the api will always returned immediately, with either (for each provided packages): + * 1) a latest/next version available if a cache was found + * 2) no latest/next version available if no cache was found - in such case updates will be fetched in the background and a cache will + * be created for each provided packages and made available for the next call to the api. + * @default false + */ + readonly useCache?: boolean; + + /** + * How long the cache for the provided packages should be used before being refreshed (in milliseconds). + * If `useCache` is not supplied, this option has no effect. + * If `0` is used, this will force the cache to refresh immediately: + * 1) The api will returned immediately (without any latest nor next version available for the provided packages) + * 2) New updates will be fetched in the background + * 3) The cache for each provided packages will be refreshed and made available for the next call to the api + * @default ONE_DAY + */ + readonly cacheMaxAge?: number; + + /** + * A JavaScript package registry url that implements the CommonJS Package Registry specification. + * @default "Looks at any registry urls in the .npmrc file or fallback to the default npm registry instead" + * @example .npmrc + * registry = 'https://custom-registry.com/' + * @pkgscope:registry = 'https://custom-registry.com/' + */ + readonly registryUrl?: string; + + /** + * Set of options to be passed down to Node.js http/https request. + * @example Behind a proxy with self-signed certificate + * { ca: [ fs.readFileSync('proxy-cert.pem') ] } + * @example Bypassing certificate validation + * { rejectUnauthorized: false } + */ + readonly requestOptions?: RequestOptions; +} + +interface LatestVersion { + /** + * Get latest versions of packages from of a package json like object. + * @param {PackageJson} item - A package json like object (with dependencies, devDependencies and peerDependencies attributes). + * @example { dependencies: { 'npm': 'latest' }, devDependencies: { 'npm': '1.3.2' }, peerDependencies: { '@scope/name': '^5.0.2' } } + * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options. + * If `useCache` is not supplied, the default of `false` is used. + * If `cacheMaxAge` is not supplied, the default of `one day` is used. + * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the `npm registry url` instead. + * @returns {Promise} + */ + (item: PackageJson, options?: LatestVersionOptions): Promise< + LatestVersionPackage[] + >; + + /** + * Get latest version of a single package. + * @param {Package} item - A single package object (represented by a string that should match the following format: `${'@' | ''}${string}@${string}`) + * @example 'npm', 'npm@1.3.2', '@scope/name@^5.0.2' + * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options. + * If `useCache` is not supplied, the default of `false` is used. + * If `cacheMaxAge` is not supplied, the default of `one day` is used. + * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the npm registry url instead. + * @returns {Promise} + */ + ( + item: Package, + options?: LatestVersionOptions, + ): Promise; + + /** + * Get latest versions of a collection of packages. + * @param {Package[]} items - A collection of package object (represented by a string that should match the following format: `${'@' | ''}${string}@${string}`) + * @example ['npm', 'npm@1.3.2', '@scope/name@^5.0.2'] + * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options. + * If `useCache` is not supplied, the default of `false` is used. + * If `cacheMaxAge` is not supplied, the default of `one day` is used. + * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the npm registry url instead. + * @returns {Promise} + */ + (items: Package[], options?: LatestVersionOptions): Promise< + LatestVersionPackage[] + >; // eslint-disable-line @typescript-eslint/unified-signatures +} +type PackageRange = `${'@' | ''}${string}@${string}`; +type Package = PackageRange | string; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +type PackageJsonDependencies = Record; +type PackageJson = Record & + ( + | { + dependencies: PackageJsonDependencies; + } + | { + devDependencies: PackageJsonDependencies; + } + | { + peerDependencies: PackageJsonDependencies; + } + ); + +/** + * @internal + */ +interface PackageMetadata { + name: string; + lastUpdateDate: number; + versions: string[]; + distTags: Record; +} + +export const ONE_DAY = 1000 * 60 * 60 * 24; // eslint-disable-line @typescript-eslint/naming-convention + +const isPackageJson = (obj: any): obj is PackageJson => { + return ( + (obj as PackageJson).dependencies !== undefined || + (obj as PackageJson).devDependencies !== undefined || + (obj as PackageJson).peerDependencies !== undefined + ); +}; + +const downloadMetadata = ( + pkgName: string, + options?: LatestVersionOptions, +): Promise => { + return new Promise((resolve, reject) => { + const i = pkgName.indexOf('/'); + const pkgScope = i !== -1 ? pkgName.slice(0, i) : ''; + const registryUrl = options?.registryUrl ?? getRegistryUrl(pkgScope); + const pkgUrl = new URL( + encodeURIComponent(pkgName).replace(/^%40/, '@'), + registryUrl, + ); + + let requestOptions: HttpRequestOptions | HttpsRequestOptions = { + headers: { + accept: + 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*', + }, + host: pkgUrl.hostname, + path: pkgUrl.pathname, + port: pkgUrl.port, + }; + const authInfo = registryAuthToken(pkgUrl.toString(), { recursive: true }); + if (authInfo && requestOptions.headers) { + requestOptions.headers.authorization = `${authInfo.type} ${authInfo.token}`; + } + if (options?.requestOptions) { + requestOptions = { ...requestOptions, ...options.requestOptions }; + } + + const { get } = require(pkgUrl.protocol === 'https:' ? 'https' : 'http'); + const request = get(requestOptions, (res: IncomingMessage) => { + if (res.statusCode === 200) { + let rawData = ''; + res.setEncoding('utf8'); + res.on('data', (chunk: string) => (rawData += chunk)); + res.once('error', (err) => { + res.removeAllListeners(); + reject(`Request error (${err.message}): ${pkgUrl}`); + }); + res.once('end', () => { + res.removeAllListeners(); + try { + const pkgMetadata = JSON.parse(rawData); + resolve({ + name: pkgName, + lastUpdateDate: Date.now(), + versions: Object.keys(pkgMetadata.versions as string[]), + distTags: pkgMetadata['dist-tags'], + }); + return; + } catch (err) { + reject(err); + return; + } + }); + } else { + res.removeAllListeners(); + res.resume(); // consume response data to free up memory + reject(`Request error (${res.statusCode}): ${pkgUrl}`); + return; + } + }); + const abort = (error: Error | string): void => { + request.destroy(); + reject(error); + }; + request.once('timeout', () => { + abort(`Request timed out: ${pkgUrl}`); + }); + request.once('error', (err: Error) => { + abort(err); + }); + request.on('close', () => { + request.removeAllListeners(); + }); + }); +}; + +const getCacheDir = (name = '@badisi/latest-version'): string => { + const homeDir = homedir(); + switch (process.platform) { + case 'darwin': + return join(homeDir, 'Library', 'Caches', name); + case 'win32': + return join( + process.env.LOCALAPPDATA ?? join(homeDir, 'AppData', 'Local'), + name, + 'Cache', + ); + default: + return join(process.env.XDG_CACHE_HOME ?? join(homeDir, '.cache'), name); + } +}; + +const saveMetadataToCache = (pkg: PackageMetadata): void => { + const filePath = join(getCacheDir(), `${pkg.name}.json`); + if (!existsSync(dirname(filePath))) { + mkdirSync(dirname(filePath), { recursive: true }); + } + writeFileSync(filePath, JSON.stringify(pkg)); +}; + +const getMetadataFromCache = ( + pkgName: string, + options?: LatestVersionOptions, +): PackageMetadata | undefined => { + const maxAge = options?.cacheMaxAge ?? ONE_DAY; + if (maxAge !== 0) { + const pkgCacheFilePath = join(getCacheDir(), `${pkgName}.json`); + if (existsSync(pkgCacheFilePath)) { + const pkg = JSON.parse( + readFileSync(pkgCacheFilePath).toString(), + ) as PackageMetadata; + if (Date.now() - pkg.lastUpdateDate < maxAge) { + return pkg; + } + } + } + return undefined; // invalidates cache +}; + +const getRegistryVersions = async ( + pkgName: string, + tagOrRange?: string, + options?: LatestVersionOptions, +): Promise => { + let pkgMetadata: PackageMetadata | undefined; + if (pkgName.length && options?.useCache) { + pkgMetadata = getMetadataFromCache(pkgName, options); + if (!pkgMetadata) { + pkgMetadata = await downloadMetadata(pkgName, options); + saveMetadataToCache(pkgMetadata); + } + } else if (pkgName.length) { + pkgMetadata = await downloadMetadata(pkgName, options); + } + + const versions: RegistryVersions = { + latest: pkgMetadata?.distTags.latest, + next: pkgMetadata?.distTags.next, + }; + if (tagOrRange && pkgMetadata?.distTags[tagOrRange]) { + versions.wanted = pkgMetadata.distTags[tagOrRange]; + } else if (tagOrRange && pkgMetadata?.versions.length) { + versions.wanted = + maxSatisfying(pkgMetadata.versions, tagOrRange) ?? undefined; + } + return versions; +}; + +const getInstalledVersion = ( + pkgName: string, + location: keyof InstalledVersions = 'local', +): string | undefined => { + try { + if (location === 'globalNpm') { + return require(join(npm.packages, pkgName, 'package.json')) + ?.version as string; + } else if (location === 'globalYarn') { + // Make sure package is globally installed by Yarn + const yarnGlobalPkg = require(pathResolve( + yarn.packages, + '..', + 'package.json', + )); + if (!yarnGlobalPkg?.dependencies?.[pkgName]) { + return undefined; + } + return require(join(yarn.packages, pkgName, 'package.json')) + ?.version as string; + } else { + /** + * Compute the local paths manually as require.resolve() and require.resolve.paths() + * cannot be trusted anymore. + * @see https://github.com/nodejs/node/issues/33460 + * @see https://github.com/nodejs/loaders/issues/26 + */ + const { root } = parse(process.cwd()); + let path = process.cwd(); + const localPaths = [join(path, 'node_modules')]; + while (path !== root) { + path = dirname(path); + localPaths.push(join(path, 'node_modules')); + } + for (const localPath of localPaths) { + const pkgPath = join(localPath, pkgName, 'package.json'); + if (existsSync(pkgPath)) { + return require(pkgPath)?.version as string; + } + } + } + return undefined; + } catch { + return undefined; + } +}; + +const getInfo = async ( + pkg: Package, + options?: LatestVersionOptions, +): Promise => { + const i = pkg.lastIndexOf('@'); + let pkgInfo: LatestVersionPackage = { + name: i > 1 ? pkg.slice(0, i) : pkg, + wantedTagOrRange: i > 1 ? pkg.slice(i + 1) : 'latest', + updatesAvailable: false, + }; + + try { + pkgInfo = { + ...pkgInfo, + local: getInstalledVersion(pkgInfo.name, 'local'), + globalNpm: getInstalledVersion(pkgInfo.name, 'globalNpm'), + globalYarn: getInstalledVersion(pkgInfo.name, 'globalYarn'), + ...(await getRegistryVersions( + pkgInfo.name, + pkgInfo.wantedTagOrRange, + options, + )), + }; + const local = + pkgInfo.local && pkgInfo.wanted + ? gt(pkgInfo.wanted, pkgInfo.local) + ? pkgInfo.wanted + : false + : false; + const globalNpm = + pkgInfo.globalNpm && pkgInfo.wanted + ? gt(pkgInfo.wanted, pkgInfo.globalNpm) + ? pkgInfo.wanted + : false + : false; + const globalYarn = + pkgInfo.globalYarn && pkgInfo.wanted + ? gt(pkgInfo.wanted, pkgInfo.globalYarn) + ? pkgInfo.wanted + : false + : false; + pkgInfo.updatesAvailable = + local || globalNpm || globalYarn + ? { local, globalNpm, globalYarn } + : false; + } catch (err: any) { + pkgInfo.error = err?.message ?? err; + } + + return pkgInfo; +}; + +const latestVersion: LatestVersion = async ( + arg: Package | Package[] | PackageJson, + options?: LatestVersionOptions, +): Promise => { + const pkgs: Package[] = []; + if (typeof arg === 'string') { + pkgs.push(arg); + } else if (Array.isArray(arg)) { + pkgs.push(...arg); + } else if (isPackageJson(arg)) { + const addDeps = (deps?: PackageJsonDependencies): void => { + if (deps) { + pkgs.push( + ...Object.keys(deps).map((key: string) => `${key}@${deps[key]}`), + ); + } + }; + addDeps(arg.dependencies as PackageJsonDependencies | undefined); + addDeps(arg.devDependencies as PackageJsonDependencies | undefined); + addDeps(arg.peerDependencies as PackageJsonDependencies | undefined); + } + + const jobs = await Promise.allSettled( + pkgs.map((pkg) => getInfo(pkg, options)), + ); + const results = jobs.map( + (jobResult: PromiseSettledResult) => + (jobResult as PromiseFulfilledResult).value, + ); + return typeof arg === 'string' ? results[0] : results; +}; + +export type { + LatestVersion, + Package, + PackageRange, + PackageJson, + PackageJsonDependencies, + RegistryVersions, + LatestVersionPackage, + RequestOptions, + LatestVersionOptions, +}; +export default latestVersion;