From 179689ab1094a10b77627845fa1b56904f374bd8 Mon Sep 17 00:00:00 2001 From: dalechyn Date: Sat, 9 Nov 2024 16:24:13 +0200 Subject: [PATCH] feat: fhub cli --- biome.json | 1 - package.json | 5 +- pnpm-lock.yaml | 309 ++++++++++++++++++++++++++ src/Cli/Bin.ts | 48 ++++ src/Cli/Commands/Generate.ts | 222 ++++++++++++++++++ src/Cli/Commands/Init.ts | 95 ++++++++ src/Cli/Config.ts | 19 ++ src/Cli/Errors.ts | 57 +++++ src/Cli/Logger.ts | 38 ++++ src/Cli/Utils/findConfig.ts | 33 +++ src/Cli/Utils/format.ts | 17 ++ src/Cli/Utils/getIsUsingTypeScript.ts | 26 +++ src/Cli/Utils/resolveConfig.ts | 21 ++ src/Cli/index.ts | 1 + src/Internal/types.ts | 1 + src/package.json | 31 ++- tsconfig.base.json | 4 +- 17 files changed, 915 insertions(+), 13 deletions(-) create mode 100644 src/Cli/Bin.ts create mode 100644 src/Cli/Commands/Generate.ts create mode 100644 src/Cli/Commands/Init.ts create mode 100644 src/Cli/Config.ts create mode 100644 src/Cli/Errors.ts create mode 100644 src/Cli/Logger.ts create mode 100644 src/Cli/Utils/findConfig.ts create mode 100644 src/Cli/Utils/format.ts create mode 100644 src/Cli/Utils/getIsUsingTypeScript.ts create mode 100644 src/Cli/Utils/resolveConfig.ts create mode 100644 src/Cli/index.ts diff --git a/biome.json b/biome.json index 8c4bb9a..42e0386 100644 --- a/biome.json +++ b/biome.json @@ -21,7 +21,6 @@ "generated.ts", "vectors/*.json", "vectors/**/*.test.ts", - "wagmi", "site/dist" ] }, diff --git a/package.json b/package.json index 4067ba9..e2e222d 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,13 @@ "private": true, "type": "module", "scripts": { - "build": "pnpm clean && pnpm build:cjs && pnpm build:esm && pnpm build:types", - "build:cjs": "tsc --project ./tsconfig.build.json --module commonjs --outDir ./src/_cjs --removeComments --verbatimModuleSyntax false && printf '{\"type\":\"commonjs\"}' > ./src/_cjs/package.json", + "build": "pnpm clean && pnpm build:esm && pnpm build:types", "build:esm": "tsc --project ./tsconfig.build.json --module es2020 --outDir ./src/_esm && printf '{\"type\": \"module\",\"sideEffects\":false}' > ./src/_esm/package.json", "build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./src/_types --emitDeclarationOnly --declaration --declarationMap", "changeset:prepublish": "pnpm version:update && bun scripts/prepublishOnly.ts && pnpm build", "changeset:publish": "pnpm changeset:prepublish && changeset publish", "changeset:version": "changeset version && pnpm install --lockfile-only && pnpm version:update && pnpm format", - "clean": "rm -rf *.tsbuildinfo src/*.tsbuildinfo src/_esm src/_cjs src/_types", + "clean": "rm -rf *.tsbuildinfo src/*.tsbuildinfo src/_esm src/_types", "format": "biome format --write", "lint": "biome check --fix", "lint:repo": "sherif", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f14eb8a..7c0fd60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,12 +81,46 @@ importers: '@noble/hashes': specifier: ^1.5.0 version: 1.5.0 + bundle-require: + specifier: ^5.0.0 + version: 5.0.0(esbuild@0.23.1) + cac: + specifier: ^6.7.14 + version: 6.7.14 + dedent: + specifier: ^1.5.3 + version: 1.5.3 + find-up: + specifier: ^7.0.0 + version: 7.0.0 + fs-extra: + specifier: ^11.2.0 + version: 11.2.0 + ora: + specifier: ^8.1.1 + version: 8.1.1 ox: specifier: 0.0.0-canary-20241006052626 version: 0.0.0-canary-20241006052626(typescript@5.6.2)(zod@3.23.8) + pathe: + specifier: ^1.1.2 + version: 1.1.2 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 + prettier: + specifier: ^3.3.3 + version: 3.3.3 typescript: specifier: '>=5.0.4' version: 5.6.2 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 packages: @@ -486,6 +520,12 @@ packages: '@types/bun@1.1.13': resolution: {integrity: sha512-KmQxSBgVWCl6RSuerlLGZlIWfdxkKqat0nxN61+qu4y1KDn0Ll3j7v1Pl8GnaL3a/U6GGWVTJh75ap62kR1E8Q==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -526,6 +566,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -562,6 +606,20 @@ packages: bun-types@1.1.34: resolution: {integrity: sha512-br5QygTEL/TwB4uQOb96Ky22j4Gq2WxWH/8Oqv20fk5HagwKXo/akB+LiYgSfzexCt6kkcUaVm+bKiPl71xPvw==} + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -573,6 +631,14 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -596,6 +662,14 @@ packages: supports-color: optional: true + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -614,6 +688,9 @@ packages: easy-table@1.2.0: resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} @@ -657,6 +734,14 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -673,6 +758,10 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -726,6 +815,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -734,6 +827,14 @@ packages: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -761,6 +862,9 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + knip@5.33.3: resolution: {integrity: sha512-saUxedVDCa/8p3w445at66vLmYKretzYsX7+elMJ5ROWGzU+1aTRm3EmKELTaho1ue7BlwJB5BxLJROy43+LtQ==} engines: {node: '>=18.6.0'} @@ -769,13 +873,25 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4' + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -787,6 +903,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -829,6 +949,14 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + ora@8.1.1: + resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} + engines: {node: '>=18'} + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -852,10 +980,18 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -879,13 +1015,23 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -903,6 +1049,11 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + pretty-ms@9.1.0: resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} engines: {node: '>=18'} @@ -932,6 +1083,10 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -996,6 +1151,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-git-hooks@2.11.1: resolution: {integrity: sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg==} hasBin: true @@ -1014,10 +1173,22 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -1069,10 +1240,18 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + viem@2.21.40: resolution: {integrity: sha512-no/mE3l7B0mdUTtvO7z/cTLENttQ/M7+ombqFGXJqsQrxv9wrYsTIGpS3za+FA5a447hY+x9D8Wxny84q1zAaA==} peerDependencies: @@ -1115,6 +1294,10 @@ packages: yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} + engines: {node: '>=12.20'} + zod-validation-error@3.4.0: resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} engines: {node: '>=18.0.0'} @@ -1532,6 +1715,15 @@ snapshots: dependencies: bun-types: 1.1.34 + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.7.4 + + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.7.4 + '@types/node@12.20.55': {} '@types/node@20.12.14': @@ -1567,6 +1759,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.1.0: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -1602,12 +1796,27 @@ snapshots: '@types/node': 20.12.14 '@types/ws': 8.5.12 + bundle-require@5.0.0(esbuild@0.23.1): + dependencies: + esbuild: 0.23.1 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chalk@5.3.0: {} + chardet@0.7.0: {} ci-info@3.9.0: {} clean-stack@2.2.0: {} + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + clone@1.0.4: optional: true @@ -1625,6 +1834,8 @@ snapshots: dependencies: ms: 2.1.3 + dedent@1.5.3: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -1644,6 +1855,8 @@ snapshots: optionalDependencies: wcwidth: 1.0.1 + emoji-regex@10.4.0: {} + enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 @@ -1714,6 +1927,18 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -1731,6 +1956,8 @@ snapshots: fsevents@2.3.3: optional: true + get-east-asian-width@1.3.0: {} + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -1785,12 +2012,18 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-interactive@2.0.0: {} + is-number@7.0.0: {} is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + is-windows@1.0.2: {} isexe@2.0.0: {} @@ -1814,6 +2047,12 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + knip@5.33.3(@types/node@22.7.4)(typescript@5.6.2): dependencies: '@nodelib/fs.walk': 1.2.8 @@ -1835,12 +2074,23 @@ snapshots: zod: 3.23.8 zod-validation-error: 3.4.0(zod@3.23.8) + load-tsconfig@0.2.5: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + lodash.startcase@4.4.0: {} + log-symbols@6.0.0: + dependencies: + chalk: 5.3.0 + is-unicode-supported: 1.3.0 + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 @@ -1853,6 +2103,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mimic-function@5.0.1: {} + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -1886,6 +2138,22 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + ora@8.1.1: + dependencies: + chalk: 5.3.0 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + os-tmpdir@1.0.2: {} outdent@0.5.0: {} @@ -1912,10 +2180,18 @@ snapshots: dependencies: p-try: 2.2.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.1.1 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + p-map@2.1.0: {} p-map@4.0.0: @@ -1930,10 +2206,16 @@ snapshots: path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-type@4.0.0: {} + pathe@1.1.2: {} + picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.2: {} @@ -1942,6 +2224,8 @@ snapshots: prettier@2.8.8: {} + prettier@3.3.3: {} + pretty-ms@9.1.0: dependencies: parse-ms: 4.0.0 @@ -1969,6 +2253,11 @@ snapshots: resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.0.4: {} run-parallel@1.2.0: @@ -2018,6 +2307,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + simple-git-hooks@2.11.1: {} slash@3.0.0: {} @@ -2031,10 +2322,22 @@ snapshots: sprintf-js@1.0.3: {} + stdin-discarder@0.2.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + strip-bom@3.0.0: {} strip-json-comments@5.0.1: {} @@ -2070,8 +2373,12 @@ snapshots: undici-types@6.19.8: {} + unicorn-magic@0.1.0: {} + universalify@0.1.2: {} + universalify@2.0.1: {} + viem@2.21.40(typescript@5.6.2)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.11.0 @@ -2117,6 +2424,8 @@ snapshots: yallist@2.1.2: {} + yocto-queue@1.1.1: {} + zod-validation-error@3.4.0(zod@3.23.8): dependencies: zod: 3.23.8 diff --git a/src/Cli/Bin.ts b/src/Cli/Bin.ts new file mode 100644 index 0000000..265a19e --- /dev/null +++ b/src/Cli/Bin.ts @@ -0,0 +1,48 @@ +#!/usr/bin/env node +import { cac } from 'cac' + +import { version } from '../version.js' +import { type Generate, generate } from './Commands/Generate.js' +import { type Init, init } from './Commands/Init.js' +import * as Logger from './Logger.js' + +const cli = cac('fhub') + +cli + .command('generate', 'generate code based on configuration') + .option('-c, --config ', '[string] path to config file') + .option('-r, --root ', '[string] root path to resolve config from') + // .option('-w, --watch', '[boolean] watch for changes') + .example((name) => `${name} generate`) + .action(async (options: Generate) => await generate(options)) + +cli + .command('init', 'create configuration file') + .option('-c, --config ', '[string] path to config file') + .option('-r, --root ', '[string] root path to resolve config from') + .example((name) => `${name} init`) + .action(async (options: Init) => await init(options)) + +cli.help() +cli.version(version) + +void (async () => { + try { + process.title = 'node (fhub)' + } catch {} + + try { + // Parse CLI args without running command + cli.parse(process.argv, { run: false }) + if (!cli.matchedCommand) { + if (cli.args.length === 0) { + if (!cli.options.help && !cli.options.version) cli.outputHelp() + } else throw new Error(`Unknown command: ${cli.args.join(' ')}`) + } + await cli.runMatchedCommand() + process.exit(0) + } catch (error) { + Logger.error(`\n${(error as Error).message}`) + process.exit(1) + } +})() diff --git a/src/Cli/Commands/Generate.ts b/src/Cli/Commands/Generate.ts new file mode 100644 index 0000000..75fbbe6 --- /dev/null +++ b/src/Cli/Commands/Generate.ts @@ -0,0 +1,222 @@ +import { default as dedent } from 'dedent' +import { default as fs } from 'fs-extra' +import { basename, dirname, resolve } from 'pathe' +import pc from 'picocolors' +import { z } from 'zod' + +import { fileURLToPath } from 'node:url' +import { fromZodError } from '../Errors.js' +import * as logger from '../Logger.js' +import { findConfig } from '../Utils/findConfig.js' +import { format } from '../Utils/format.js' +import { getIsUsingTypeScript } from '../Utils/getIsUsingTypeScript.js' +import { resolveConfig } from '../Utils/resolveConfig.js' + +const Generate = z.object({ + /** Path to config file */ + config: z.string().optional(), + /** Directory to search for config file */ + root: z.string().optional(), +}) +export type Generate = z.infer + +export async function generate(options: Generate = {}) { + // Validate command line options + try { + await Generate.parseAsync(options) + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { prefix: 'Invalid option' }) + throw error + } + + // Get cli config file + const configPath = await findConfig(options) + if (!configPath) { + if (options.config) + throw new Error(`Config not found at ${pc.gray(options.config)}`) + throw new Error('Config not found') + } + + const resolvedConfigs = await resolveConfig({ configPath }) + const isTypeScript = await getIsUsingTypeScript() + + const outNames = new Set() + const isArrayConfig = Array.isArray(resolvedConfigs) + const configs = isArrayConfig ? resolvedConfigs : [resolvedConfigs] + for (const config of configs) { + if (isArrayConfig) + logger.log(`Using config ${pc.gray(basename(configPath))}`) + if (!config.out) throw new Error('out is required.') + if (outNames.has(config.out)) + throw new Error(`out "${config.out}" must be unique.`) + outNames.add(config.out) + + const spinner = logger.spinner() + + // Generate client file + spinner.start('Generating client file') + await writeClientFile({ + isTypeScript, + rpcUrl: config.rpcUrl, + out: config.out, + }) + spinner.succeed() + + // Get the filenames of available top level actions + spinner.start('Generating fhub Action Hooks') + const actionsPath = resolve( + dirname(fileURLToPath(import.meta.url)), + '../..', + 'Actions', + ) + const files = await fs.readdir(actionsPath) + + await Promise.all( + files.map(async (file) => { + // Reading function names + const fileContents = await fs.readFile( + resolve(actionsPath, file), + 'utf8', + ) + + // cutting of .ts or .js + const namespaceName = file.slice(0, -3) + + // Idk how to generate server actions that return an async iterator – not sure this is possible + if (namespaceName === 'Watch') return + + const functionNames = (() => { + const matches = [ + ...fileContents.matchAll(/as (?.*) }/gm), + ] + return matches.map((match) => { + const functionName = match.groups?.functionName + if (!functionName) + throw new Error('Unexpected – cant retrieve function name') + return functionName + }) + })() + + // Creating sub directories with server actions + + await Promise.all( + functionNames.map(async (functionName) => { + const hookType = functionName === 'create' ? 'Mutation' : 'Query' + const outputHook = `use${namespaceName}${functionName.slice(0, 1).toUpperCase()}${functionName.slice(1)}${hookType}` + await fs.ensureDir(resolve(config.out, outputHook)) + + // Creating action.ts file + const actionFileContent = await format(dedent` + 'use server' + import { fhubClient } from '../client.${isTypeScript ? 'ts' : 'js'}' + import { Actions } from 'fhub' + + export function action(parameters${isTypeScript ? `: Actions.${namespaceName}.${functionName}.ParametersType` : ''}): ${isTypeScript ? `Promise` : ''} { + return Actions.${namespaceName}.${functionName}(fhubClient, parameters) + } + `) + await fs.writeFile( + resolve( + config.out, + outputHook, + `action.${isTypeScript ? 'ts' : 'js'}`, + ), + actionFileContent, + ) + + // Creating hook file + const hookContents = await (async () => { + if (hookType === 'Mutation') { + return await format(dedent` + import { action } from './action.${isTypeScript ? 'ts' : 'js'}' + ${ + isTypeScript + ? dedent` + import type { Actions } from 'fhub' + import { useMutation, type MutationOptions } from '@tanstack/react-query' + ` + : "import { useMutation } from '@tanstack/react-query'" + } + + export function ${outputHook}${hookType}({mutation = {}}${ + isTypeScript + ? `: { + mutation?: MutationOptions | undefined + }` + : '' + }) { + return useMutation({ ...mutation, mutationKey: ['${namespaceName}.${functionName}'], mutationFn: (args)=> action(args) }) + } + `) + } + + return format(dedent` + import { action } from './action.${isTypeScript ? 'ts' : 'js'}' + ${ + isTypeScript + ? dedent` + import type { Actions } from 'fhub' + import { useQuery, type QueryOptions } from '@tanstack/react-query' + ` + : "import { useQuery } from '@tanstack/react-query'" + } + + ${isTypeScript ? `type QueryKey = ['${namespaceName}.${functionName}', Actions.${namespaceName}.${functionName}.ParametersType | undefined]` : ''} + + function queryKey(parameters${isTypeScript ? `: Actions.${namespaceName}.${functionName}.ParametersType | undefined` : ''})${isTypeScript ? ': QueryKey' : ''} { + return ['${namespaceName}.${functionName}', parameters] as const + } + + export function ${outputHook}${hookType}({query = {}, args}${ + isTypeScript + ? `: { + query?: QueryOptions | undefined + args?: Actions.${namespaceName}.${functionName}.ParametersType | undefined + }` + : '' + }) { + const enabled = Boolean(args && (query.enabled ?? true)) + return useQuery({ ...query, queryKey: queryKey(args), queryFn: ({queryKey:[_, args]})=> action(args), enabled }) + } + `) + })() + + await fs.writeFile( + resolve( + config.out, + outputHook, + `index.${isTypeScript ? 'ts' : 'js'}`, + ), + hookContents, + ) + }), + ) + }), + ) + + spinner.succeed() + spinner.stop() + } +} + +async function writeClientFile({ + isTypeScript, + rpcUrl, + out, +}: { + isTypeScript: boolean + rpcUrl: string + out: string +}) { + // Format and write output + const cwd = process.cwd() + const outPath = resolve(cwd, out, `client.${isTypeScript ? 'ts' : 'js'}`) + await fs.ensureDir(dirname(outPath)) + const formatted = await format(dedent` + import { Client, Transport } from "fhub"; + + export const fhubClient = Client.create(Transport.grpcNode({ baseUrl: '${rpcUrl}', httpVersion: '2' })) + `) + await fs.writeFile(outPath, formatted) +} diff --git a/src/Cli/Commands/Init.ts b/src/Cli/Commands/Init.ts new file mode 100644 index 0000000..627a3e4 --- /dev/null +++ b/src/Cli/Commands/Init.ts @@ -0,0 +1,95 @@ +import dedent from 'dedent' +import { default as fs } from 'fs-extra' +import { relative, resolve } from 'pathe' +import pc from 'picocolors' +import { z } from 'zod' + +import { type Config, defaultConfig } from '../Config.js' +import { fromZodError } from '../Errors.js' +import * as logger from '../Logger.js' +import { findConfig } from '../Utils/findConfig.js' +import { format } from '../Utils/format.js' +import { getIsUsingTypeScript } from '../Utils/getIsUsingTypeScript.js' + +export type Init = { + /** Path to config file */ + config?: string + /** Watch for file system changes to config and plugins */ + content?: Config + /** Directory to init config file */ + root?: string +} + +const Init = z.object({ + config: z.string().optional(), + content: z.object({}).optional(), + root: z.string().optional(), +}) + +export async function init(options: Init = {}) { + // Validate command line options + try { + await Init.parseAsync(options) + } catch (error) { + if (error instanceof z.ZodError) + throw fromZodError(error, { prefix: 'Invalid option' }) + throw error + } + + // Check for existing config file + const configPath = await findConfig(options) + if (configPath) { + logger.info( + `Config already exists at ${pc.gray( + relative(process.cwd(), configPath), + )}`, + ) + return configPath + } + + const spinner = logger.spinner() + spinner.start('Creating config') + // Check if project is using TypeScript + const isUsingTypeScript = await getIsUsingTypeScript() + const rootDir = resolve(options.root || process.cwd()) + let outPath: string + if (options.config) { + outPath = resolve(rootDir, options.config) + } else { + const extension = isUsingTypeScript ? 'ts' : 'js' + outPath = resolve(rootDir, `fhub.config.${extension}`) + } + + let content: string + if (isUsingTypeScript) { + const config = options.content ?? defaultConfig + content = dedent(` + import { defineConfig } from 'fhub/Cli' + + export default defineConfig(${JSON.stringify(config)}) + `) + } else { + const config = options.content ?? { + ...defaultConfig, + out: defaultConfig.out.replace('.ts', '.js'), + } + content = dedent(` + // @ts-check + + /** @type {import('fhub/Cli').Config} */ + export default ${JSON.stringify(config, null, 2).replace( + /"(\d*)":/gm, + '$1:', + )} + `) + } + + const formatted = await format(content) + await fs.writeFile(outPath, formatted) + spinner.succeed() + logger.success( + `Config created at ${pc.gray(relative(process.cwd(), outPath))}`, + ) + + return outPath +} diff --git a/src/Cli/Config.ts b/src/Cli/Config.ts new file mode 100644 index 0000000..041e782 --- /dev/null +++ b/src/Cli/Config.ts @@ -0,0 +1,19 @@ +import type { MaybeArray, MaybePromise } from '../Internal/types.js' + +export type Config = { + /** Hub RPC url */ + rpcUrl: string + /** Output folder path */ + out: string +} + +export function defineConfig( + config: MaybeArray | (() => MaybePromise>), +) { + return config +} + +export const defaultConfig = { + out: 'src/hooks/fhub', + rpcUrl: 'https://hub-grpc.pinata.cloud', +} satisfies Config diff --git a/src/Cli/Errors.ts b/src/Cli/Errors.ts new file mode 100644 index 0000000..6ef3709 --- /dev/null +++ b/src/Cli/Errors.ts @@ -0,0 +1,57 @@ +import type { z } from 'zod' + +class ValidationError extends Error { + details: Zod.ZodIssue[] + + constructor( + message: string, + options: { + details: Zod.ZodIssue[] + }, + ) { + super(message) + this.details = options.details + } +} + +// From https://github.com/causaly/zod-validation-error +export function fromZodError( + zError: z.ZodError, + { + maxIssuesInMessage = 99, + issueSeparator = '\n- ', + prefixSeparator = '\n- ', + prefix = 'Validation Error', + }: { + maxIssuesInMessage?: number + issueSeparator?: string + prefixSeparator?: string + prefix?: string + } = {}, +): ValidationError { + function joinPath(arr: Array): string { + return arr.reduce((acc, value) => { + if (typeof value === 'number') return `${acc}[${value}]` + const separator = acc === '' ? '' : '.' + return acc + separator + value + }, '') + } + + const reason = zError.errors + // limit max number of issues printed in the reason section + .slice(0, maxIssuesInMessage) + // format error message + .map((issue) => { + const { message, path } = issue + if (path.length > 0) return `${message} at \`${joinPath(path)}\`` + return message + }) + // concat as string + .join(issueSeparator) + + const message = reason ? [prefix, reason].join(prefixSeparator) : prefix + + return new ValidationError(message, { + details: zError.errors, + }) +} diff --git a/src/Cli/Logger.ts b/src/Cli/Logger.ts new file mode 100644 index 0000000..9f96151 --- /dev/null +++ b/src/Cli/Logger.ts @@ -0,0 +1,38 @@ +import { format as utilFormat } from 'node:util' +import ora from 'ora' +import pc from 'picocolors' + +function format(args: any[]) { + return utilFormat(...args) + .split('\n') + .join('\n') +} + +export function success(...args: any[]) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(pc.green(format(args))) +} + +export function info(...args: any[]) { + console.info(pc.blue(format(args))) +} + +export function log(...args: any[]) { + // biome-ignore lint/suspicious/noConsoleLog: console.log is used for logging + console.log(pc.white(format(args))) +} + +export function warn(...args: any[]) { + console.warn(pc.yellow(format(args))) +} + +export function error(...args: any[]) { + console.error(pc.red(format(args))) +} + +export function spinner() { + return ora({ + color: 'yellow', + spinner: 'dots', + }) +} diff --git a/src/Cli/Utils/findConfig.ts b/src/Cli/Utils/findConfig.ts new file mode 100644 index 0000000..1ad5065 --- /dev/null +++ b/src/Cli/Utils/findConfig.ts @@ -0,0 +1,33 @@ +import { findUp } from 'find-up' +import * as fs from 'fs-extra' +import { resolve } from 'pathe' + +// Do not reorder +// In order of preference files are checked +const configFiles = [ + 'fhub.config.ts', + 'fhub.config.js', + 'fhub.config.mjs', + 'fhub.config.mts', +] + +type FindConfigParameters = { + /** Config file name */ + config?: string | undefined + /** Config file directory */ + root?: string | undefined +} + +/** + * Resolves path to fhub CLI config file. + */ +export async function findConfig(parameters: FindConfigParameters = {}) { + const { config, root } = parameters + const rootDir = resolve(root || process.cwd()) + if (config) { + const path = resolve(rootDir, config) + if (fs.pathExistsSync(path)) return path + return + } + return findUp(configFiles, { cwd: rootDir }) +} diff --git a/src/Cli/Utils/format.ts b/src/Cli/Utils/format.ts new file mode 100644 index 0000000..880c80f --- /dev/null +++ b/src/Cli/Utils/format.ts @@ -0,0 +1,17 @@ +import * as prettier from 'prettier' + +export async function format(content: string) { + const config = await prettier.resolveConfig(process.cwd()) + return prettier.format(content, { + arrowParens: 'always', + endOfLine: 'lf', + parser: 'typescript', + printWidth: 80, + semi: false, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', + ...config, + plugins: [], + }) +} diff --git a/src/Cli/Utils/getIsUsingTypeScript.ts b/src/Cli/Utils/getIsUsingTypeScript.ts new file mode 100644 index 0000000..757f248 --- /dev/null +++ b/src/Cli/Utils/getIsUsingTypeScript.ts @@ -0,0 +1,26 @@ +import { findUp } from 'find-up' + +export async function getIsUsingTypeScript() { + try { + const cwd = process.cwd() + const tsconfig = await findUp( + [ + 'tsconfig.json', + 'tsconfig.base.json', + 'tsconfig.lib.json', + 'tsconfig.node.json', + ], + { cwd }, + ) + if (tsconfig) return true + + const fhubConfig = await findUp(['fhub.config.ts', 'fhub.config.mts'], { + cwd, + }) + if (fhubConfig) return true + + return false + } catch { + return false + } +} diff --git a/src/Cli/Utils/resolveConfig.ts b/src/Cli/Utils/resolveConfig.ts new file mode 100644 index 0000000..029a232 --- /dev/null +++ b/src/Cli/Utils/resolveConfig.ts @@ -0,0 +1,21 @@ +import { bundleRequire } from 'bundle-require' + +import type { MaybeArray } from '../../Internal/types.js' +import type { Config } from '../Config.js' + +type ResolveConfigParameters = { + /** Path to config file */ + configPath: string +} + +/** Bundles and returns fhub config object from path. */ +export async function resolveConfig( + parameters: ResolveConfigParameters, +): Promise> { + const { configPath } = parameters + const res = await bundleRequire({ filepath: configPath }) + let config = res.mod.default + if (config.default) config = config.default + if (typeof config !== 'function') return config + return await config() +} diff --git a/src/Cli/index.ts b/src/Cli/index.ts new file mode 100644 index 0000000..b331eda --- /dev/null +++ b/src/Cli/index.ts @@ -0,0 +1 @@ +export * from './Config.js' diff --git a/src/Internal/types.ts b/src/Internal/types.ts index bf7c833..3373583 100644 --- a/src/Internal/types.ts +++ b/src/Internal/types.ts @@ -93,6 +93,7 @@ export type Or = T extends readonly [ export type IsUndefined = [undefined] extends [T] ? true : false export type MaybePromise = T | Promise +export type MaybeArray = T | T[] /** * @description Makes attributes on the type T required if required is true. diff --git a/src/package.json b/src/package.json index b03aaf6..9bf5aa1 100644 --- a/src/package.json +++ b/src/package.json @@ -3,22 +3,26 @@ "description": "TypeScript Interface for Farcaster Hubs", "version": "0.0.0", "type": "module", - "main": "./_cjs/index.js", "module": "./_esm/index.js", "types": "./_types/index.d.ts", "typings": "./_types/index.d.ts", "sideEffects": false, "files": ["*", "!**/*.tsbuildinfo", "!tsconfig.build.json", "!jsr.json"], + "bin": { + "fhub": "./_esm/Cli/Bin.js" + }, "exports": { ".": { "types": "./_types/index.d.ts", - "import": "./_esm/index.js", - "default": "./_cjs/index.js" + "import": "./_esm/index.js" }, "./Node": { "types": "./_types/Node/index.d.ts", - "import": "./_esm/Node/index.js", - "default": "./_cjs/Node/index.js" + "import": "./_esm/Node/index.js" + }, + "./Cli": { + "types": "./_types/Cli/index.d.ts", + "import": "./_esm/Cli/index.js" }, "./package.json": "./package.json" }, @@ -38,9 +42,22 @@ "@farcaster/core": "^0.15.6", "@noble/ed25519": "^2.1.0", "@noble/hashes": "^1.5.0", - "ox": "0.0.0-canary-20241006052626" + "bundle-require": "^5.0.0", + "cac": "^6.7.14", + "dedent": "^1.5.3", + "find-up": "^7.0.0", + "fs-extra": "^11.2.0", + "ora": "^8.1.1", + "ox": "0.0.0-canary-20241006052626", + "pathe": "^1.1.2", + "picocolors": "^1.1.1", + "prettier": "^3.3.3", + "zod": "^3.23.8" }, "license": "MIT", "repository": "dalechyn/fhub", - "authors": ["dalechyn.eth"] + "authors": ["dalechyn.eth"], + "devDependencies": { + "@types/fs-extra": "^11.0.4" + } } diff --git a/tsconfig.base.json b/tsconfig.base.json index 8e21b11..6f0f4e2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,8 +24,8 @@ "checkJs": false, // Interop constraints - "esModuleInterop": false, - "allowSyntheticDefaultImports": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "verbatimModuleSyntax": true,