diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..7a3eb02 --- /dev/null +++ b/.nycrc @@ -0,0 +1,5 @@ +{ + "exclude": [ + "src/keys.ts" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index bd9d25f..9c42719 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ $ npm install -g @hyperplay/cli $ hyperplay COMMAND running command... $ hyperplay (--version) -@hyperplay/cli/2.11.4 win32-x64 node-v20.8.0 +@hyperplay/cli/2.13.0 darwin-arm64 node-v20.12.2 $ hyperplay --help [COMMAND] USAGE $ hyperplay COMMAND diff --git a/package-lock.json b/package-lock.json index e433d5a..efa2e02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@hyperplay/cli", - "version": "2.11.9", + "version": "2.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hyperplay/cli", - "version": "2.11.9", + "version": "2.12.0", "license": "MPL-2.0", "dependencies": { "@oclif/core": "^1.8.0", "@oclif/plugin-help": "^5", - "@valist/sdk": "2.9.8", + "@valist/sdk": "^2.9.13", "archiver": "^7.0.0", "axios": "^1.6.7", "axios-cookiejar-support": "^5.0.0", @@ -527,37 +527,6 @@ "node": ">=6.9.0" } }, - "node_modules/@capacitor-community/electron": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@capacitor-community/electron/-/electron-1.4.2.tgz", - "integrity": "sha512-MFNJcyV2smu1icCVaR7kEOdxgFC+5v9fQWDQEN9X4ukagvpak20Rv+xHYoXEEmVkDgrvQMYfonYFUIjqum7NDw==", - "deprecated": "please use latest version", - "license": "MIT", - "dependencies": { - "@capacitor/core": "^2.4.7", - "chalk": "^4.1.1", - "electron-is-dev": "^2.0.0", - "electron-serve": "^1.1.0", - "fs-extra": "~9.1.0", - "mime-types": "~2.1.27", - "ora": "^5.1.0" - } - }, - "node_modules/@capacitor/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-2.5.0.tgz", - "integrity": "sha512-WUkUnqqLtlEYn6tly8t6VR0ABlSVbXdlD/gBbYxx0P+gEqMF9b46uYb2YqyH+8HBDANzTweEySpLfhiSUvYS7w==", - "license": "MIT", - "dependencies": { - "tslib": "^1.9.0" - } - }, - "node_modules/@capacitor/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -688,7 +657,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "dev": true, "funding": [ { "type": "individual", @@ -716,7 +684,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "dev": true, "funding": [ { "type": "individual", @@ -742,7 +709,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "dev": true, "funding": [ { "type": "individual", @@ -766,7 +732,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "dev": true, "funding": [ { "type": "individual", @@ -790,7 +755,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "dev": true, "funding": [ { "type": "individual", @@ -810,7 +774,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "dev": true, "funding": [ { "type": "individual", @@ -832,7 +795,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "dev": true, "funding": [ { "type": "individual", @@ -852,7 +814,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "dev": true, "funding": [ { "type": "individual", @@ -868,11 +829,38 @@ "@ethersproject/bignumber": "^5.7.0" } }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, "node_modules/@ethersproject/hash": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "dev": true, "funding": [ { "type": "individual", @@ -900,7 +888,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "dev": true, "funding": [ { "type": "individual", @@ -921,7 +908,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true, "funding": [ { "type": "individual", @@ -938,7 +924,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "dev": true, "funding": [ { "type": "individual", @@ -958,7 +943,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "dev": true, "funding": [ { "type": "individual", @@ -978,7 +962,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "dev": true, "funding": [ { "type": "individual", @@ -999,7 +982,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "dev": true, "funding": [ { "type": "individual", @@ -1024,7 +1006,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "dev": true, "funding": [ { "type": "individual", @@ -1046,7 +1027,6 @@ "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "dev": true, "funding": [ { "type": "individual", @@ -1074,7 +1054,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "dev": true, "funding": [ { "type": "individual", @@ -3461,17 +3440,16 @@ "license": "ISC" }, "node_modules/@valist/sdk": { - "version": "2.9.8", - "resolved": "https://registry.npmjs.org/@valist/sdk/-/sdk-2.9.8.tgz", - "integrity": "sha512-cQXHfrl5YfE2FiIaeCNh/kw5LQek2/W+M9UPiCER3Wdwpkr2l0nXD+idq2dlJ4GGhsKFmVBxRAFoYMXr+6fHyw==", + "version": "2.9.13", + "resolved": "https://registry.npmjs.org/@valist/sdk/-/sdk-2.9.13.tgz", + "integrity": "sha512-yKFeYdK/63lqNkEN0eWSzukhp5UzvjWVo/Ltitl5Hh4lh5TAmCJxiV2KcWa3dQW7jQ0bkmk95dRpnCJmOT9jgw==", "license": "MPL-2.0", "dependencies": { - "@capacitor-community/electron": "1.4.2", + "@ethersproject/contracts": "^5.7.0", "axios": "^1.4.0", + "axios-retry": "^4.4.1", "ethers": "^6.11.1", - "node-fetch": "^2.6.1", - "proxy-from-env": "^1.1.0", - "ua-parser-js": "1.0.35" + "node-fetch": "^2.6.1" } }, "node_modules/abbrev": { @@ -3966,6 +3944,30 @@ "tough-cookie": ">=4.0.0" } }, + "node_modules/axios-retry": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.4.1.tgz", + "integrity": "sha512-JGzNoglDHtHWIEvvAampB0P7jxQ/sT4COmW0FgSQkVg6o4KqNjNMBI6uFVOq517hkw/OAYYAG08ADtBlV8lvmQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/axios-retry/node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -4156,7 +4158,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true, "license": "MIT" }, "node_modules/boxen": { @@ -4220,7 +4221,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true, "license": "MIT" }, "node_modules/browser-stdout": { @@ -5574,27 +5574,6 @@ "node": ">=0.10.0" } }, - "node_modules/electron-is-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz", - "integrity": "sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/electron-serve": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.3.0.tgz", - "integrity": "sha512-OEC/48ZBJxR6XNSZtCl4cKPyQ1lvsu8yp8GdCplMWwGS1eEyMcEmzML5BRs/io/RLDnpgyf+7rSL+X6ICifRIg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.802", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", @@ -5606,7 +5585,6 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, "license": "MIT", "dependencies": { "bn.js": "^4.11.9", @@ -5622,7 +5600,6 @@ "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, "license": "MIT" }, "node_modules/emoji-regex": { @@ -7915,7 +7892,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -7975,7 +7951,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, "license": "MIT", "dependencies": { "hash.js": "^1.0.3", @@ -8989,7 +8964,6 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true, "license": "MIT" }, "node_modules/js-tokens": { @@ -9783,14 +9757,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, "license": "ISC" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true, "license": "MIT" }, "node_modules/minimatch": { diff --git a/package.json b/package.json index b180ef7..421e1ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hyperplay/cli", - "version": "2.12.0", + "version": "2.13.0", "description": "Hyperplay CLI", "author": "HyperPlay Labs, Inc.", "bin": { @@ -20,7 +20,7 @@ "dependencies": { "@oclif/core": "^1.8.0", "@oclif/plugin-help": "^5", - "@valist/sdk": "2.9.8", + "@valist/sdk": "^2.9.13", "archiver": "^7.0.0", "axios": "^1.6.7", "axios-cookiejar-support": "^5.0.0", diff --git a/src/api.ts b/src/api.ts index 7556d2f..57ea724 100644 --- a/src/api.ts +++ b/src/api.ts @@ -20,23 +20,23 @@ async function logCookiesAndCheckCsrf(cookieJar: CookieJar, baseUrl: string): Pr return csrfToken; } -export async function loginAndPublish(client: AxiosInstance, cookieJar: CookieJar, signer: ethers.Wallet, baseUrl: string, projectID: string, path: string, targetChannel: string) { - await client.get(`${baseUrl}/api/auth/session`); +export async function login(client: AxiosInstance, cookieJar: CookieJar, signer: ethers.Wallet) { + await client.get("/api/auth/session"); - const hasCsrfToken = await logCookiesAndCheckCsrf(cookieJar, baseUrl); + const hasCsrfToken = await logCookiesAndCheckCsrf(cookieJar, client.defaults.baseURL as string); if (!hasCsrfToken) { throw new Error("CSRF token not found in the cookie jar."); } - const csrfResponse = await client.get(`${baseUrl}/api/auth/csrf`); + const csrfResponse = await client.get("/api/auth/csrf"); const csrfToken = csrfResponse.data.csrfToken; - CliUx.ux.action.start('Signing into HyperPlay API with:', signer.address); + CliUx.ux.action.start(`Signing into HyperPlay API with ${signer.address}:`); const siweMessage = new SiweMessage({ - domain: new URL(baseUrl).host, + domain: new URL(client.defaults.baseURL as string).host, address: signer.address, statement: "Sign in with Ethereum to HyperPlay", - uri: baseUrl, + uri: client.defaults.baseURL as string, version: "1", chainId: 137, nonce: csrfToken, @@ -51,24 +51,26 @@ export async function loginAndPublish(client: AxiosInstance, cookieJar: CookieJa redirect: 'false', signature: signature, csrfToken: csrfToken, - callbackUrl: `${baseUrl}/`, + callbackUrl: "/", json: 'true', }); - await client.post(`${baseUrl}/api/auth/callback/ethereum?`, formData, { + await client.post("/api/auth/callback/ethereum?", formData, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }); CliUx.ux.action.stop(); +} +export async function publish(client: AxiosInstance, projectID: string, path: string, targetChannel: string) { CliUx.ux.log('Fetching listing release branches'); - const channels = (await client.get<{ channel_id: number, channel_name: string }[]>(`${baseUrl}/api/v1/channels?project_id=${projectID}`)).data; + const channels = (await client.get<{ channel_id: number, channel_name: string }[]>(`/api/v1/channels?project_id=${projectID}`)).data; const releaseChannel = channels.find((channel) => targetChannel === channel.channel_name); CliUx.ux.log('Submitting release for review'); - await client.post(`${baseUrl}/api/v1/reviews/release`, { + await client.post("/api/v1/reviews/release", { path, channel_id: releaseChannel?.channel_id, }); diff --git a/src/commands/publish.ts b/src/commands/publish.ts index 72142e6..90ed1af 100644 --- a/src/commands/publish.ts +++ b/src/commands/publish.ts @@ -6,7 +6,7 @@ import { select } from '../keys'; import { CookieJar } from 'tough-cookie'; import axios from 'axios'; import { wrapper } from 'axios-cookiejar-support'; -import { loginAndPublish } from '../api'; +import { login, publish } from '../api'; import { uploadRelease } from '../releases'; import { parseYml } from '../yml'; import { FlagOutput } from '@oclif/core/lib/interfaces'; @@ -120,26 +120,29 @@ export default class Publish extends Command { this.error(`release ${config.release} exists`); } - const release = await uploadRelease(valist, config); - CliUx.ux.log(`successfully uploaded files to IPFS: ${release.external_url}`); + const apiURL = 'https://developers.hyperplay.xyz' + const apiClient = wrapper(axios.create({ jar: cookieJar, withCredentials: true, baseURL: apiURL })); + await login( + apiClient, + cookieJar, + wallet + ); - CliUx.ux.action.start('publishing release'); + const release = await uploadRelease(apiClient, config); + CliUx.ux.log(`Successfully uploaded files to HyperPlay: ${release.external_url}`); + + CliUx.ux.action.start('Publishing release'); const tx = await valist.createRelease(projectID, config.release, release); CliUx.ux.action.stop(); - CliUx.ux.action.start(`confirming transaction ${tx.hash}`); + CliUx.ux.action.start(`Confirming transaction ${tx.hash}`); await tx.wait(); CliUx.ux.action.stop(); // Publish to HyperPlay if (!flags['skip_hyperplay_publish']) { - const apiURL = 'https://developers.hyperplay.xyz' - const apiClient = wrapper(axios.create({ jar: cookieJar, withCredentials: true, baseURL: apiURL })); - await loginAndPublish( + await publish( apiClient, - cookieJar, - wallet, - apiURL, projectID, fullReleaseName, flags['channel'] @@ -150,8 +153,6 @@ export default class Publish extends Command { let releaseText = 'view the release at:\n' if (!flags['skip_hyperplay_publish']) releaseText += `https://developers.hyperplay.xyz/${config.account}/${config.project}/settings\n` - releaseText += release.external_url + '\n' - releaseText += `ipfs://${release.external_url.replace('https://gateway.valist.io/ipfs/', '')}\n` CliUx.ux.log(releaseText); this.exit(0); diff --git a/src/releases.ts b/src/releases.ts index cf374ea..590605d 100644 --- a/src/releases.ts +++ b/src/releases.ts @@ -1,11 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { CliUx } from '@oclif/core'; -import { Client, ReleaseMeta } from "@valist/sdk"; -import { PlatformsMetaInterface } from '@valist/sdk/dist/typesShared'; -import fs from 'fs'; -import path from 'path'; +import { ReleaseMeta } from "@valist/sdk"; +import { SupportedPlatform } from '@valist/sdk/dist/typesShared'; import { zipDirectory } from './zip'; import { ReleaseConfig } from './types'; import { getZipName } from './utils/getZipName'; +import { DesktopPlatform, WebPlatform, getSignedUploadUrls, uploadFileS3 } from '@valist/sdk/dist/s3'; +import fs from "fs"; +import { AxiosInstance } from 'axios'; interface PlatformEntry { platform: string @@ -14,59 +16,103 @@ interface PlatformEntry { executable: string } -export async function uploadRelease(valist: Client, config: ReleaseConfig) { +const baseGateWayURL = `https://gateway-b3.valist.io`; + +export async function uploadRelease(client: AxiosInstance, config: ReleaseConfig) { const updatedPlatformEntries: PlatformEntry[] = await Promise.all(Object.entries(config.platforms).map(async ([platform, platformConfig]) => { const installScript = platformConfig.installScript; const executable = platformConfig.executable; if (config && config.platforms[platform] && !config.platforms[platform].zip) { - return {platform, path: platformConfig.path, installScript, executable} + return { platform, path: platformConfig.path, installScript, executable } } const zipPath = getZipName(platformConfig.path); CliUx.ux.action.start(`zipping ${zipPath}`); await zipDirectory(platformConfig.path, zipPath); CliUx.ux.action.stop(); - return {platform, path: zipPath, installScript, executable}; + return { platform, path: zipPath, installScript, executable }; })); + const releasePath = `${config.account}/${config.project}/${config.release}`; const meta: ReleaseMeta = { _metadata_version: "2", - path: `${config.account}/${config.project}/${config.release}`, + path: releasePath, name: config.release, description: config.description || "", - external_url: "", + external_url: `${baseGateWayURL}/${releasePath}`, platforms: {}, }; + CliUx.ux.action.start('uploading files'); - const platformIC = updatedPlatformEntries.map(({platform: platformName, path: zipPath}) => { - const content = fs.createReadStream(zipPath); - return { - path: `${platformName}/${path.basename(zipPath)}`, - content, + const platformsToSign: Partial> = {}; + for (const platformEntry of updatedPlatformEntries) { + const platformKey = platformEntry.platform as SupportedPlatform; + const { path, executable } = platformEntry; + const file = fs.createReadStream(path); + + platformsToSign[platformKey] = { + platform: platformKey, + files: file, + executable, }; - }); + } - CliUx.ux.action.start('uploading files'); - meta.external_url = await valist.writeFolderNode( - platformIC, - true, - (bytes: string | number) => { - CliUx.ux.log(`Uploading ${bytes}`); + CliUx.ux.action.start("Generating presigned urls"); + const urls = await getSignedUploadUrls( + config.account, + config.project, + config.release, + platformsToSign, + { + client, }, ); CliUx.ux.action.stop(); - for (const {platform: platformName, path: zipPath, installScript, executable} of updatedPlatformEntries) { - const stats = await fs.promises.stat(zipPath); - const fileSize = stats.size; + const signedPlatformEntries = Object.entries(platformsToSign); + for (const [name, platform] of signedPlatformEntries) { + const preSignedUrl = urls.find((data) => data.platformKey === name); + if (!preSignedUrl) throw "no pre-signed url found for platform"; - meta.platforms[platformName as keyof PlatformsMetaInterface] = { - name: path.basename(zipPath), - external_url: `${meta.external_url}/${platformName}/${path.basename(zipPath)}`, - downloadSize: fileSize.toString(), - installSize: fileSize.toString(), // Adjust this if necessary - installScript, - executable, + const { uploadId, partUrls, key } = preSignedUrl; + const fileData = platform.files as fs.ReadStream; + + let location: string = ''; + const progressIterator = uploadFileS3( + fileData, + uploadId, + key, + partUrls, + { + client, + } + ); + + for await (const progressUpdate of progressIterator) { + if (typeof progressUpdate === 'number') { + CliUx.ux.log(`Upload progress for ${name}: ${progressUpdate}`); + } else { + location = progressUpdate; + } + } + + if (location === '') throw ('no location returned'); + + const { files, ...rest } = platform as DesktopPlatform; + const updatedPlatform = updatedPlatformEntries.find((item) => item.platform === name); + if (!updatedPlatform) throw ("updated platform path not found"); + + const fileStat = await fs.promises.stat(updatedPlatform.path); + const downloadSize = fileStat.size.toString(); + + meta.platforms[name as SupportedPlatform] = { + ...rest, + name: preSignedUrl.fileName, + external_url: `${baseGateWayURL}${location}`, + downloadSize: downloadSize, + installSize: downloadSize, + installScript: updatedPlatform.installScript, }; } + CliUx.ux.action.stop(); return meta; } diff --git a/test/commands/publish.test.ts b/test/commands/publish.test.ts index 7c8dd6f..9d4be10 100644 --- a/test/commands/publish.test.ts +++ b/test/commands/publish.test.ts @@ -12,7 +12,12 @@ let provider: ethers.JsonRpcProvider; let signer: ethers.JsonRpcSigner; let walletPassedToPublishCommand: ethers.Wallet; -// publishing unzipped folder is not supported only zipped or unzipped files +export type MockPlatform = { + platformKey: string; + fileName: string; + partCount: number; +} + describe('publish CLI command', () => { let valist: Client let members: string[] = [] @@ -68,7 +73,7 @@ describe('publish CLI command', () => { projectID = generateID(accountID, 'cli'); }) - async function runPublishCommandWithMockData(releaseVersion: string, publishArgs: string[]) { + async function runPublishCommandWithMockData(releaseVersion: string, publishArgs: string[], mockPlatforms: MockPlatform[]) { nock(url) .get('/api/auth/session') .reply(200, {}) @@ -88,6 +93,38 @@ describe('publish CLI command', () => { const cookieJar = new CookieJar() cookieJar.setCookie('next-auth.csrf-token=someCookieValue', url) + // Mock S3 signed URL request + const s3BaseURL = 'https://valist-hpstore.s3.us-east-005.backblazeb2.com'; + nock(s3BaseURL) + .persist() + .put(/.*/) + .reply(200, {}, { 'ETag': 'mock-etag' }); + + nock(url) + .post('/api/v1/uploads/releases/presigned-url') + .reply(200, { + uploadDetails: mockPlatforms.map(platform => ({ + platformKey: platform.platformKey, + fileName: platform.fileName, + uploadId: 'mock-upload-id', + partUrls: Array.from({ length: platform.partCount }, (_, i) => ({ + partNumber: i + 1, + url: `${s3BaseURL}/mock-part-url/${i + 1}` + })), + key: `test-ground/test44/0.0.18/${platform.platformKey}/${platform.fileName}` + })) + }); + + mockPlatforms.forEach(platform => { + nock(url) + .put('/api/v1/uploads/complete-multipart-upload', { + uploadId: 'mock-upload-id', + key: `test-ground/test44/0.0.18/${platform.platformKey}/${platform.fileName}`, + parts: [{ PartNumber: 1, ETag: 'mock-etag' }] + }) + .reply(200, { location: `${s3BaseURL}/test-ground/test44/0.0.18/${platform.platformKey}/${platform.fileName}` }); + }); + Publish.cookieJar = cookieJar try { await Publish.run(publishArgs); @@ -107,9 +144,16 @@ describe('publish CLI command', () => { `--private-key=${publisherPrivateKey}`, '--no-meta-tx', '--yml-path=./test/mock_data/hyperplay.yml', - '--network=http://127.0.0.1:8545/' + '--network=http://127.0.0.1:8545/', + '--skip_hyperplay_publish' + ] + const mockPlatforms = [ + { platformKey: 'darwin_amd64', fileName: 'mac_x64.zip', partCount: 1 }, + { platformKey: 'darwin_arm64', fileName: 'mac_arm64.zip', partCount: 1 }, + { platformKey: 'windows_amd64', fileName: 'windows_amd64.zip', partCount: 1 }, + { platformKey: 'web', fileName: 'web.zip', partCount: 1 } ] - const releaseMeta = await runPublishCommandWithMockData('v0.0.2', publishArgs) + const releaseMeta = await runPublishCommandWithMockData('v0.0.2', publishArgs, mockPlatforms) const platformKeys = Object.keys(releaseMeta.platforms) expect(platformKeys.includes('web')).true expect(platformKeys.includes('darwin_amd64')).true @@ -122,10 +166,16 @@ describe('publish CLI command', () => { `--private-key=${publisherPrivateKey}`, '--no-meta-tx', '--yml-path=./test/mock_data/hyperplay_publish.yml', - '--network=http://127.0.0.1:8545/' + '--network=http://127.0.0.1:8545/', + '--skip_hyperplay_publish' + ] + const mockPlatforms = [ + { platformKey: 'HyperPlay-0.12.0-macOS-arm64.dmg', fileName: 'dmg.txt', partCount: 1 }, + { platformKey: 'darwin_arm64_dmg_zip_blockmap', fileName: 'mac_arm64.zip', partCount: 1 }, + { platformKey: 'windows_amd64', fileName: 'windows_amd64.zip', partCount: 1 }, + { platformKey: 'latest_mac_yml', fileName: 'web.zip', partCount: 1 } ] - const releaseMeta = await runPublishCommandWithMockData('v0.0.3', publishArgs) - console.log('release meta ', releaseMeta) + const releaseMeta = await runPublishCommandWithMockData('v0.0.3', publishArgs, mockPlatforms) const platformKeys = Object.keys(releaseMeta.platforms) expect(platformKeys.includes('HyperPlay-0.12.0-macOS-arm64.dmg')).true expect(platformKeys.includes('darwin_arm64_dmg_zip_blockmap')).true