diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 9b59918..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm run autoformat:staged diff --git a/packages/cli/README.md b/README.md similarity index 100% rename from packages/cli/README.md rename to README.md diff --git a/commands b/commands new file mode 100644 index 0000000..51480dd --- /dev/null +++ b/commands @@ -0,0 +1 @@ +./node_modules/react-native-engine/packages/cli/bin/dev build:upload diff --git a/packages/cli/package.json b/packages/cli/package.json index fe79237..beb61b3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -22,6 +22,8 @@ "/uber-apk-signer.jar" ], "dependencies": { + "@azure/identity": "^3.3.1", + "@azure/storage-blob": "^12.16.0", "@firebase/auth": "^1.1.0", "@oclif/core": "^2", "@oclif/plugin-help": "^5", diff --git a/packages/cli/pnpm-lock.yaml b/packages/cli/pnpm-lock.yaml index 53d1605..02098de 100644 --- a/packages/cli/pnpm-lock.yaml +++ b/packages/cli/pnpm-lock.yaml @@ -1,6 +1,8 @@ lockfileVersion: 5.4 specifiers: + '@azure/identity': ^3.3.1 + '@azure/storage-blob': ^12.16.0 '@firebase/auth': ^1.1.0 '@oclif/core': ^2 '@oclif/plugin-help': ^5 @@ -49,6 +51,8 @@ specifiers: typescript: ^4.9.5 dependencies: + '@azure/identity': 3.3.1 + '@azure/storage-blob': 12.16.0 '@firebase/auth': 1.1.0 '@oclif/core': 2.4.0 '@oclif/plugin-help': 5.2.7 @@ -108,6 +112,184 @@ packages: '@jridgewell/trace-mapping': 0.3.17 dev: true + /@azure/abort-controller/1.1.0: + resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==} + engines: {node: '>=12.0.0'} + dependencies: + tslib: 2.5.0 + dev: false + + /@azure/core-auth/1.5.0: + resolution: {integrity: sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-util': 1.5.0 + tslib: 2.5.0 + dev: false + + /@azure/core-client/1.7.3: + resolution: {integrity: sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.5.0 + '@azure/core-rest-pipeline': 1.12.1 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.5.0 + '@azure/logger': 1.0.4 + tslib: 2.5.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@azure/core-http/3.0.3: + resolution: {integrity: sha512-QMib3wXotJMFhHgmJBPUF9YsyErw34H0XDFQd9CauH7TPB+RGcyl9Ayy7iURtJB04ngXhE6YwrQsWDXlSLrilg==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.5.0 + '@azure/core-tracing': 1.0.0-preview.13 + '@azure/core-util': 1.5.0 + '@azure/logger': 1.0.4 + '@types/node-fetch': 2.6.6 + '@types/tunnel': 0.0.3 + form-data: 4.0.0 + node-fetch: 2.6.9 + process: 0.11.10 + tslib: 2.5.0 + tunnel: 0.0.6 + uuid: 8.3.2 + xml2js: 0.5.0 + transitivePeerDependencies: + - encoding + dev: false + + /@azure/core-lro/2.5.4: + resolution: {integrity: sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-util': 1.5.0 + '@azure/logger': 1.0.4 + tslib: 2.5.0 + dev: false + + /@azure/core-paging/1.5.0: + resolution: {integrity: sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.5.0 + dev: false + + /@azure/core-rest-pipeline/1.12.1: + resolution: {integrity: sha512-SsyWQ+T5MFQRX+M8H/66AlaI6HyCbQStGfFngx2fuiW+vKI2DkhtOvbYodPyf9fOe/ARLWWc3ohX54lQ5Kmaog==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.5.0 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.5.0 + '@azure/logger': 1.0.4 + form-data: 4.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + tslib: 2.5.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@azure/core-tracing/1.0.0-preview.13: + resolution: {integrity: sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==} + engines: {node: '>=12.0.0'} + dependencies: + '@opentelemetry/api': 1.6.0 + tslib: 2.5.0 + dev: false + + /@azure/core-tracing/1.0.1: + resolution: {integrity: sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==} + engines: {node: '>=12.0.0'} + dependencies: + tslib: 2.5.0 + dev: false + + /@azure/core-util/1.5.0: + resolution: {integrity: sha512-GZBpVFDtQ/15hW1OgBcRdT4Bl7AEpcEZqLfbAvOtm1CQUncKWiYapFHVD588hmlV27NbOOtSm3cnLF3lvoHi4g==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + tslib: 2.5.0 + dev: false + + /@azure/identity/3.3.1: + resolution: {integrity: sha512-96im0LrJt0kzsMqA8XjWxqbd2pPuEZHDlyLM4zdMv6nowLV/ul3dOW5X55OuLoFX+h22tYnMcGmQb3tlkdt/UA==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.5.0 + '@azure/core-client': 1.7.3 + '@azure/core-rest-pipeline': 1.12.1 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.5.0 + '@azure/logger': 1.0.4 + '@azure/msal-browser': 2.38.2 + '@azure/msal-common': 13.3.0 + '@azure/msal-node': 1.18.3 + events: 3.3.0 + jws: 4.0.0 + open: 8.4.2 + stoppable: 1.1.0 + tslib: 2.5.0 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@azure/logger/1.0.4: + resolution: {integrity: sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.5.0 + dev: false + + /@azure/msal-browser/2.38.2: + resolution: {integrity: sha512-71BeIn2we6LIgMplwCSaMq5zAwmalyJR3jFcVOZxNVfQ1saBRwOD+P77nLs5vrRCedVKTq8RMFhIOdpMLNno0A==} + engines: {node: '>=0.8.0'} + dependencies: + '@azure/msal-common': 13.3.0 + dev: false + + /@azure/msal-common/13.3.0: + resolution: {integrity: sha512-/VFWTicjcJbrGp3yQP7A24xU95NiDMe23vxIU1U6qdRPFsprMDNUohMudclnd+WSHE4/McqkZs/nUU3sAKkVjg==} + engines: {node: '>=0.8.0'} + dev: false + + /@azure/msal-node/1.18.3: + resolution: {integrity: sha512-lI1OsxNbS/gxRD4548Wyj22Dk8kS7eGMwD9GlBZvQmFV8FJUXoXySL1BiNzDsHUE96/DS/DHmA+F73p1Dkcktg==} + engines: {node: 10 || 12 || 14 || 16 || 18} + dependencies: + '@azure/msal-common': 13.3.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + dev: false + + /@azure/storage-blob/12.16.0: + resolution: {integrity: sha512-jz33rUSUGUB65FgYrTRgRDjG6hdPHwfvHe+g/UrwVG8MsyLqSxg9TaW7Yuhjxu1v1OZ5xam2NU6+IpCN0xJO8Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-http': 3.0.3 + '@azure/core-lro': 2.5.4 + '@azure/core-paging': 1.5.0 + '@azure/core-tracing': 1.0.0-preview.13 + '@azure/logger': 1.0.4 + events: 3.3.0 + tslib: 2.5.0 + transitivePeerDependencies: + - encoding + dev: false + /@babel/code-frame/7.12.11: resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} dependencies: @@ -2002,6 +2184,11 @@ packages: '@octokit/openapi-types': 12.11.0 dev: true + /@opentelemetry/api/1.6.0: + resolution: {integrity: sha512-OWlrQAnWn9577PhVgqjUvMr1pg57Bc4jv0iL4w0PRuOSRvq67rvHW9Ie/dZVMvCzhSCB+UxhcY/PmCmFj33Q+g==} + engines: {node: '>=8.0.0'} + dev: false + /@protobufjs/aspromise/1.1.2: resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} dev: false @@ -2345,7 +2532,6 @@ packages: /@tootallnate/once/2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} - dev: true /@tsconfig/node10/1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -2440,6 +2626,13 @@ packages: resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} dev: true + /@types/node-fetch/2.6.6: + resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==} + dependencies: + '@types/node': 16.18.14 + form-data: 4.0.0 + dev: false + /@types/node/15.14.9: resolution: {integrity: sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==} dev: true @@ -2478,6 +2671,12 @@ packages: resolution: {integrity: sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==} dev: true + /@types/tunnel/0.0.3: + resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==} + dependencies: + '@types/node': 16.18.14 + dev: false + /@types/validator/13.11.1: resolution: {integrity: sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==} dev: false @@ -2670,7 +2869,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /agentkeepalive/4.2.1: resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} @@ -3059,6 +3257,10 @@ packages: node-int64: 0.4.0 dev: true + /buffer-equal-constant-time/1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + /buffer-from/1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -3823,6 +4025,12 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: false + /ecdsa-sig-formatter/1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /ee-first/1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true @@ -4258,6 +4466,11 @@ packages: engines: {node: '>=0.4.x'} dev: true + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: false + /execa/1.0.0: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} @@ -4885,7 +5098,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /http2-wrapper/1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} @@ -4903,7 +5115,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: true /human-signals/2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} @@ -5463,6 +5674,22 @@ packages: engines: {'0': node >= 0.2.0} dev: true + /jsonwebtoken/9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.5.4 + dev: false + /just-diff-apply/5.5.0: resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==} dev: true @@ -5471,6 +5698,36 @@ packages: resolution: {integrity: sha512-6ufhP9SHjb7jibNFrNxyFZ6od3g+An6Ai9mhGRvcYe8UJlH0prseN64M+6ZBBUoKYHZsitDP42gAJ8+eVWr3lw==} dev: true + /jwa/1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jwa/2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws/3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + + /jws/4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + dev: false + /keyv/4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: @@ -5598,10 +5855,38 @@ packages: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true + /lodash.includes/4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + + /lodash.isboolean/3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger/4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber/3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject/4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isstring/4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.once/4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + /lodash.throttle/4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} dev: true @@ -6334,7 +6619,6 @@ packages: /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true /multimap/1.1.0: resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} @@ -7500,7 +7784,6 @@ packages: /sax/1.2.1: resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} - dev: true /scoped-regex/2.1.0: resolution: {integrity: sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==} @@ -7522,6 +7805,14 @@ packages: dependencies: lru-cache: 6.0.0 + /semver/7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + /send/0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -7787,6 +8078,11 @@ packages: - supports-color dev: true + /stoppable/1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + dev: false + /string-argv/0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -8131,6 +8427,11 @@ packages: dependencies: safe-buffer: 5.2.1 + /tunnel/0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + dev: false + /type-check/0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -8308,6 +8609,11 @@ packages: hasBin: true dev: true + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -8517,6 +8823,19 @@ packages: xmlbuilder: 9.0.7 dev: true + /xml2js/0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + dependencies: + sax: 1.2.1 + xmlbuilder: 11.0.1 + dev: false + + /xmlbuilder/11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + dev: false + /xmlbuilder/9.0.7: resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==} engines: {node: '>=4.0'} diff --git a/packages/cli/src/_projectAwareCommand.ts b/packages/cli/src/_projectAwareCommand.ts index f057761..7e9bf11 100644 --- a/packages/cli/src/_projectAwareCommand.ts +++ b/packages/cli/src/_projectAwareCommand.ts @@ -1,13 +1,16 @@ import { Command } from '@oclif/core'; import { checkAuth } from './application/cloud/auth'; -import { checkProject, ProjectData, ProjectFileSchema } from './application/cloud/projectsManagement'; +import { + checkProject, + ProjectConfiguration, + ProjectData, + ProjectFileSchema, + RemoteAdapterConfig, +} from './application/cloud/projectsManagement'; -export default abstract class ProjectAwareCommand extends Command { - currentProject: ProjectFileSchema = { - get id(): string { - throw new Error('select a project'); - }, - get name(): string { +export default abstract class RemoteAwareCommand extends Command { + currentProject: ProjectConfiguration = { + get remoteAdapter(): RemoteAdapterConfig { throw new Error('select a project'); }, get currentBuildId(): string | null { @@ -17,7 +20,7 @@ export default abstract class ProjectAwareCommand extends Command { protected init(): Promise { return Promise.all([ - checkAuth(), + // checkAuth(), checkProject().then(project => { this.currentProject = project; }), diff --git a/packages/cli/src/commands/addUserToProject.ts b/packages/cli/src/_unused-commands/addUserToProject.ts similarity index 100% rename from packages/cli/src/commands/addUserToProject.ts rename to packages/cli/src/_unused-commands/addUserToProject.ts diff --git a/packages/cli/src/commands/createProject.ts b/packages/cli/src/_unused-commands/createProject.ts similarity index 100% rename from packages/cli/src/commands/createProject.ts rename to packages/cli/src/_unused-commands/createProject.ts diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/_unused-commands/login.ts similarity index 100% rename from packages/cli/src/commands/login.ts rename to packages/cli/src/_unused-commands/login.ts diff --git a/packages/cli/src/commands/selectProject.ts b/packages/cli/src/_unused-commands/selectProject.ts similarity index 100% rename from packages/cli/src/commands/selectProject.ts rename to packages/cli/src/_unused-commands/selectProject.ts diff --git a/packages/cli/src/adapters/adapter.ts b/packages/cli/src/adapters/adapter.ts new file mode 100644 index 0000000..1c86c0f --- /dev/null +++ b/packages/cli/src/adapters/adapter.ts @@ -0,0 +1,47 @@ +// create a random build id (alfanumeric string) +function createBuildId(): string { + return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); +} + +export type Build = { + device: "all" | "iphonesimulator"; + flavor: string; + release: boolean; + debug: boolean; + type: "release" | "debug"; + path: string; +}; + +export type ProjectBuildDoc = { + id: string; + projectId: string; + androidBuilds: Build[]; + iosBuilds: Build[]; + createdAt: string; + createdBy: string; + version: string; +}; + +export abstract class HubAdapter { + createBuildId(): string { + return createBuildId(); + } + + abstract setLastBuild(buildId: string): Promise ; + + abstract getLastBuild(): Promise; + + abstract saveBuildInfo(buildId: string, info: { + androidBuilds: Build[]; + iosBuilds: Build[]; + }): Promise; + + + abstract getBuildInfo(buildId: string): Promise<{ + androidBuilds: Build[]; + iosBuilds: Build[]; + }>; + + abstract upload(buildId: string, buffer: Buffer, fileName: string): Promise; + abstract download(path: string): Promise; +} diff --git a/packages/cli/src/adapters/azure.ts b/packages/cli/src/adapters/azure.ts new file mode 100644 index 0000000..e732f30 --- /dev/null +++ b/packages/cli/src/adapters/azure.ts @@ -0,0 +1,129 @@ +import { BlobServiceClient, BlockBlobClient, ContainerClient, StorageSharedKeyCredential } from "@azure/storage-blob"; +import { DefaultAzureCredential } from "@azure/identity"; + +import { Build, HubAdapter } from "./adapter"; +import path from "path"; +import { getProjectRootDir } from "../application/utils"; + +const ENV_CONNECTION_STRING = "NXCACHE_AZURE_CONNECTION_STRING"; +const ENV_ACCOUNT_KEY = "NXCACHE_AZURE_ACCOUNT_KEY"; +const ENV_ACCOUNT_NAME = "NXCACHE_AZURE_ACCOUNT_NAME"; +const ENV_CONTAINER = "NXCACHE_AZURE_CONTAINER"; +const ENV_AZURE_URL = "NXCACHE_AZURE_URL"; +const ENV_SAS_URL = "NXCACHE_AZURE_SAS_URL"; +const ENV_AZURE_AD_AUTH = "NXCACHE_AZURE_AD_AUTH"; + +const getEnv = (key: string) => process.env[key]; + +function getBlockBlobClient(filename: string, options: AzureBlobRunnerOptions) { + const connectionString = getEnv(ENV_CONNECTION_STRING) ?? options.connectionString; + const accountKey = getEnv(ENV_ACCOUNT_KEY) ?? options.accountKey; + const accountName = getEnv(ENV_ACCOUNT_NAME) ?? options.accountName; + const container = getEnv(ENV_CONTAINER) ?? options.container; + const sasUrl = getEnv(ENV_SAS_URL) ?? options.sasUrl; + const adAuth = (getEnv(ENV_AZURE_AD_AUTH) ?? String(options.adAuth)) === "true"; + + if (sasUrl) { + return new ContainerClient(sasUrl).getBlockBlobClient(filename); + } + + if (!container) { + throw Error("Did not pass valid container. Supply the container either via env or nx.json."); + } + + if (connectionString) { + return new BlockBlobClient(connectionString, container, filename); + } + + if (accountName) { + const defaultUrl = `https://${accountName}.blob.core.windows.net`; + const basePath = getEnv(ENV_AZURE_URL) ?? options.azureUrl ?? defaultUrl; + + if (accountKey) { + const fullUrl = `${basePath}/${container}/${filename}`; + const credential = new StorageSharedKeyCredential(accountName, accountKey); + return new BlockBlobClient(fullUrl, credential); + } else if (adAuth) { + return new BlobServiceClient(basePath, new DefaultAzureCredential()) + .getContainerClient(container) + .getBlockBlobClient(filename); + } + } + + throw Error(`Did not pass valid credentials. Supply them either via env or nx.json.`); +} + +interface AzureBlobRunnerOptions { + connectionString?: string; + accountKey?: string; + accountName?: string; + container?: string; + azureUrl?: string; + sasUrl?: string; + adAuth?: boolean; +} + +/* +export default createCustomRunner(async (options) => { + initEnv(options); + + return { + name: "Azure Blob Storage", + fileExists: (filename) => blob(filename).exists(), + retrieveFile: async (filename) => + (await blob(filename).download()).readableStreamBody!, + storeFile: (filename, stream) => blob(filename).uploadStream(stream), + }; +}); */ + +export class AzureHubAdapter extends HubAdapter { + private blob: (filename: string) => BlockBlobClient; + + // todo validate config + constructor(config: object) { + super(); + require("dotenv").config({ path: path.join(getProjectRootDir(), ".env") }); + this.blob = (filename: string) => getBlockBlobClient(filename, config); + } + + async upload(buildId: string, buffer: Buffer, fileName: string): Promise { + const buildpath = this.getBuildPath(buildId); + await this.blob(`${buildpath}/${fileName}`).uploadData(buffer); + return `${buildpath}/${fileName}`; + } + + async setLastBuild(buildId: string): Promise { + const fileContent = JSON.stringify({ lastBuild: buildId }); + await this.blob(`config.json`).uploadData(Buffer.from(fileContent, "utf-8")); + } + + async getLastBuild(): Promise { + const fileContent = await this.blob(`config.json`).downloadToBuffer(); + return JSON.parse(fileContent.toString()).lastBuild; + } + + private getBuildPath(buildId: string) { + return `builds/${buildId}`; + } + + async saveBuildInfo(buildId: string, info: { + androidBuilds: Build[]; + iosBuilds: Build[]; + }): Promise { + const buildpath = this.getBuildPath(buildId); + await this.blob(`${buildpath}/info.json`).uploadData(Buffer.from(JSON.stringify(info), "utf-8")) + } + + async getBuildInfo(buildId: string): Promise<{ + androidBuilds: Build[]; + iosBuilds: Build[]; + }>{ + const buildpath = this.getBuildPath(buildId); + const fileContent = await this.blob(`${buildpath}/info.json`).downloadToBuffer(); + return JSON.parse(fileContent.toString()); + } + + download(path: string): Promise { + return this.blob(path).downloadToBuffer(); + } +} diff --git a/packages/cli/src/application/cloud/buildsManagement.ts b/packages/cli/src/application/cloud/buildsManagement.ts index a9d29b8..9a0c972 100644 --- a/packages/cli/src/application/cloud/buildsManagement.ts +++ b/packages/cli/src/application/cloud/buildsManagement.ts @@ -1,5 +1,5 @@ -import fs from 'node:fs'; -import path from 'node:path'; +import fs from "node:fs"; +import path from "node:path"; import { addDoc, @@ -13,33 +13,16 @@ import { query, serverTimestamp, setDoc, - where, -} from 'firebase/firestore'; -import { getBytes, getStorage, ref, StorageReference, uploadBytesResumable } from 'firebase/storage'; -import { getAuth } from 'firebase/auth'; -import AdmZip from 'adm-zip'; -import { capitalize, getBuildFolderByBuildId, getRootDestinationFolder } from '../utils'; -import logger from '../logger'; -import { iosBuildPlatforms } from '../iosUtils'; - -export type Build = { - device: 'all' | 'iphonesimulator'; - flavor: string; - release: boolean; - debug: boolean; - type: 'release' | 'debug'; - path: string; -}; - -export type ProjectBuildDoc = { - id: string; - projectId: string; - androidBuilds: Build[]; - iosBuilds: Build[]; - createdAt: string; - createdBy: string; - version: string; -}; + where +} from "firebase/firestore"; +import { getBytes, getStorage, ref, StorageReference, uploadBytesResumable } from "firebase/storage"; +import AdmZip from "adm-zip"; +import { capitalize, getAdapter, getBuildFolderByBuildId, getRootDestinationFolder } from "../utils"; +import logger from "../logger"; +import { iosBuildPlatforms } from "../iosUtils"; +import { ProjectConfiguration } from "./projectsManagement"; +import { Build, HubAdapter } from "../../adapters/adapter"; + async function zipFolder(folderPath: string, outputZipPath: string) { if (!fs.existsSync(folderPath)) { @@ -59,10 +42,10 @@ function clearLastLine() { async function upload(fileRef: StorageReference, buffer: Buffer, filename: string) { return new Promise((resolve, reject) => { const uploadTask = uploadBytesResumable(fileRef, buffer); - process.stdout.write('\n'); + process.stdout.write("\n"); uploadTask.on( - 'state_changed', + "state_changed", snapshot => { // Observe state change events such as progress, pause, and resume // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded @@ -70,12 +53,12 @@ async function upload(fileRef: StorageReference, buffer: Buffer, filename: strin clearLastLine(); logger.info(`Upload of ${filename} is ${progress.toFixed(0)}% done`); switch (snapshot.state) { - case 'paused': - logger.debug('Upload is paused'); - break; - case 'running': - logger.debug('Upload is running'); - break; + case "paused": + logger.debug("Upload is paused"); + break; + case "running": + logger.debug("Upload is running"); + break; } }, error => { @@ -84,60 +67,73 @@ async function upload(fileRef: StorageReference, buffer: Buffer, filename: strin }, () => { resolve(uploadTask.snapshot.ref); - }, + } ); }); } -export async function uploadBuilds(androidBuilds: Build[], iosBuilds: Build[], projectId: string) { +export async function uploadBuilds(androidBuilds: Build[], iosBuilds: Build[], config: ProjectConfiguration) { // for each build zip the folder and upload to firebase storage than create e build document in the builds collection - const buildRef = await addDoc(collection(getFirestore(), 'builds'), { - projectId: projectId, - createdAt: serverTimestamp(), - createdBy: getAuth().currentUser!.uid, - version: '1.0.0', // todo, - }); - const buildId = buildRef.id; - const tempBuildsFolder = path.join(getRootDestinationFolder(), 'temp_builds', buildId); + /* + create a build folder in firestore + const buildRef = await addDoc(collection(getFirestore(), "builds"), { + projectId: projectId, + createdAt: serverTimestamp(), + createdBy: getAuth().currentUser!.uid, + version: "1.0.0" // todo, + const buildId = buildRef.id; + }); */ + const adapter = getAdapter(config); + const buildId = adapter.createBuildId(); + const tempBuildsFolder = path.join(getRootDestinationFolder(), "temp_builds", buildId); fs.mkdirSync(tempBuildsFolder, { recursive: true }); - const rootBuildsFolder = `projects/${projectId}/builds/${buildId}`; + logger.info(`Found ${androidBuilds.length} android builds and ${iosBuilds.length} ios builds`); // todo improve parallelism for (const buildInfo of [...androidBuilds, ...iosBuilds]) { const fileName = `${buildInfo.device}-${buildInfo.flavor}-${buildInfo.type}.zip`; - const fileRef = ref(getStorage(), `${rootBuildsFolder}/${fileName}`); + /* const rootBuildsFolder = `projects/${projectId}/builds/${buildId}`; + const fileRef = ref(getStorage(), `${rootBuildsFolder}/${fileName}`); */ const tempZipPath = path.join(tempBuildsFolder, fileName); logger.debug(`Zipping ${buildInfo.path} to ${tempZipPath}`); await zipFolder(buildInfo.path, tempZipPath); - logger.debug(`Uploading ${tempZipPath} to ${fileRef.fullPath}`); - await upload(fileRef, new AdmZip(tempZipPath).toBuffer(), fileName); - logger.debug(`Uploaded ${tempZipPath} to ${fileRef.fullPath}`); - buildInfo.path = fileRef.fullPath; + logger.debug(`Uploading ${tempZipPath}`); + const fullPath = await adapter.upload(buildId, new AdmZip(tempZipPath).toBuffer(), fileName); + // await upload(fileRef, new AdmZip(tempZipPath).toBuffer(), fileName); + logger.debug(`Uploaded ${tempZipPath}`); + buildInfo.path = fullPath; } - await setDoc( - buildRef, - { - androidBuilds: androidBuilds, - iosBuilds: iosBuilds, - }, - { merge: true }, - ); + // await setDoc( + // buildRef, + // { + // androidBuilds: androidBuilds, + // iosBuilds: iosBuilds, + // }, + // { merge: true }, + // ); + await adapter.setLastBuild(buildId); + await adapter.saveBuildInfo(buildId, { + androidBuilds: androidBuilds, + iosBuilds: iosBuilds + }); logger.info(`Builds created successfully. Build id: ${buildId}`); fs.rmSync(tempBuildsFolder, { recursive: true }); return buildId; } -export async function getLastBuild(projectId: string): Promise { - const lastBuild = await getDocs( - query( - collection(getFirestore(), 'builds'), - where('projectId', '==', projectId), - orderBy('createdAt', 'desc'), - limit(1), - ), - ); - return { ...(lastBuild.docs[0].data() as ProjectBuildDoc), id: lastBuild.docs[0].id } as ProjectBuildDoc; +export async function getLastBuild(config: ProjectConfiguration): Promise { + // const lastBuild = await getDocs( + // query( + // collection(getFirestore(), 'builds'), + // where('projectId', '==', projectId), + // orderBy('createdAt', 'desc'), + // limit(1), + // ), + // ); + // return { ...(lastBuild.docs[0].data() as ProjectBuildDoc), id: lastBuild.docs[0].id } as ProjectBuildDoc; + const adapter = getAdapter(config); + return adapter.getLastBuild(); } function unzipFile(zipFilePath: string, destinationFolder: string): void { @@ -153,37 +149,40 @@ function unzipFile(zipFilePath: string, destinationFolder: string): void { console.log(`File extracted to ${destinationFolder}`); } -async function downloadZipBuild(buildInfo: Build, buildId: string) { - const fileRef = ref(getStorage(), buildInfo.path); - const tempZipPath = path.join(getRootDestinationFolder(), 'temp_builds', buildId, path.basename(buildInfo.path)); - logger.debug(`Downloading ${fileRef.fullPath} to ${tempZipPath}`); - const fileBuffer = await getBytes(fileRef); +async function downloadZipBuild(buildInfo: Build, buildId: string, adapter: HubAdapter) { + // const fileRef = ref(getStorage(), buildInfo.path); + const tempZipPath = path.join(getRootDestinationFolder(), "temp_builds", buildId, path.basename(buildInfo.path)); + logger.debug(`Downloading ${buildInfo.path} to ${tempZipPath}`); + //const fileBuffer = await getBytes(fileRef); + const buffer = await adapter.download(buildInfo.path); fs.mkdirSync(path.dirname(tempZipPath), { recursive: true }); - fs.writeFileSync(tempZipPath, Buffer.from(fileBuffer)); - logger.debug(`Downloaded ${fileRef.fullPath} to ${tempZipPath}`); + fs.writeFileSync(tempZipPath, buffer); + logger.debug(`Downloaded ${buildInfo.path} to ${tempZipPath}`); return tempZipPath; } -export async function downloadBuild(buildId: string): Promise { - const buildInfoDoc = await getDoc(doc(getFirestore(), 'builds', buildId)); - const build = buildInfoDoc.data() as ProjectBuildDoc; +export async function downloadBuild(buildId: string, projectConfig: ProjectConfiguration): Promise { + // const buildInfoDoc = await getDoc(doc(getFirestore(), "builds", buildId)); + // const build = buildInfoDoc.data() as ProjectBuildDoc; + const adapter = getAdapter(projectConfig); + const build = await adapter.getBuildInfo(buildId); logger.info(`Found ${build.androidBuilds.length} android builds and ${build.iosBuilds.length} ios builds`); // todo improve parallelism for (const buildInfo of build.androidBuilds) { - const tempZipPath = await downloadZipBuild(buildInfo, buildId); - const destinationFolder = path.join(getBuildFolderByBuildId(buildId), 'android', buildInfo.flavor, buildInfo.type); + const tempZipPath = await downloadZipBuild(buildInfo, buildId, adapter); + const destinationFolder = path.join(getBuildFolderByBuildId(buildId), "android", buildInfo.flavor, buildInfo.type); await unzipFile(tempZipPath, destinationFolder); fs.rmSync(tempZipPath); } for (const buildInfo of build.iosBuilds) { - const tempZipPath = await downloadZipBuild(buildInfo, buildId); + const tempZipPath = await downloadZipBuild(buildInfo, buildId, adapter); const destinationFolder = path.join( getRootDestinationFolder(), - 'builds', + "builds", buildId, - 'ios', + "ios", `${buildInfo.device}-${buildInfo.flavor}`, - capitalize(buildInfo.type), + capitalize(buildInfo.type) ); await unzipFile(tempZipPath, destinationFolder); @@ -192,11 +191,11 @@ export async function downloadBuild(buildId: string): Promise { } export async function makeCurrentBuild(buildId: string) { - const androidPath = path.join(getRootDestinationFolder(), 'android'); + const androidPath = path.join(getRootDestinationFolder(), "android"); if (fs.existsSync(androidPath)) { fs.rmSync(androidPath, { recursive: true }); } - const iosPath = path.join(getRootDestinationFolder(), 'ios'); + const iosPath = path.join(getRootDestinationFolder(), "ios"); if (fs.existsSync(iosPath)) { fs.rmSync(iosPath, { recursive: true }); } @@ -205,38 +204,38 @@ export async function makeCurrentBuild(buildId: string) { export function getAvailableCurrentBuilds() { const androidBuilds: Build[] = []; - const androidFolder = path.join(getRootDestinationFolder(), 'android'); + const androidFolder = path.join(getRootDestinationFolder(), "android"); if (fs.existsSync(androidFolder)) { const flavorsFolders = fs.readdirSync(androidFolder); for (const flavorFolder of flavorsFolders) { - const debugBuildPath = path.join(androidFolder, flavorFolder, 'debug'); + const debugBuildPath = path.join(androidFolder, flavorFolder, "debug"); if (fs.existsSync(debugBuildPath)) { androidBuilds.push({ - device: 'all', + device: "all", flavor: flavorFolder, release: false, debug: true, - type: 'debug', - path: debugBuildPath, + type: "debug", + path: debugBuildPath }); } } } const iosBuilds: Build[] = []; - const iosFolder = path.join(getRootDestinationFolder(), 'ios'); + const iosFolder = path.join(getRootDestinationFolder(), "ios"); if (fs.existsSync(iosFolder)) { const flavorsFolders = fs.readdirSync(iosFolder); for (const flavorFolder of flavorsFolders) { - const [deviceType, ...flavor] = flavorFolder.split('-'); - const debugBuildPath = path.join(iosFolder, flavorFolder, 'Debug'); + const [deviceType, ...flavor] = flavorFolder.split("-"); + const debugBuildPath = path.join(iosFolder, flavorFolder, "Debug"); if (fs.existsSync(debugBuildPath) && deviceType === iosBuildPlatforms.simulator.name) { iosBuilds.push({ - device: 'iphonesimulator', - flavor: flavor.join('-'), + device: "iphonesimulator", + flavor: flavor.join("-"), release: false, debug: true, - type: 'debug', - path: debugBuildPath, + type: "debug", + path: debugBuildPath }); } } diff --git a/packages/cli/src/application/cloud/projectsManagement.ts b/packages/cli/src/application/cloud/projectsManagement.ts index ead8bae..4214141 100644 --- a/packages/cli/src/application/cloud/projectsManagement.ts +++ b/packages/cli/src/application/cloud/projectsManagement.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { getDoc, doc, collection, addDoc, getFirestore, serverTimestamp, writeBatch } from 'firebase/firestore'; import { getAuth } from 'firebase/auth'; -import { getRootDestinationFolder } from '../utils'; +import { getConfigFile, getRootDestinationFolder } from '../utils'; import logger from '../logger'; export type ProjectFileSchema = { @@ -11,6 +11,19 @@ export type ProjectFileSchema = { currentBuildId: string | null; }; +type RemoteAdapterName = 'azure' | (string & {}); + +export type RemoteAdapterConfig = { + name: RemoteAdapterName; + config: object; +}; + +export type ProjectConfiguration = { + remoteAdapter: RemoteAdapterConfig; + // this should not stay at project level because it indicates the build that the user has locally + currentBuildId: string | null; +}; + export async function createProject(name: string): Promise { const db = getFirestore(); const batch = writeBatch(db); @@ -42,11 +55,12 @@ export async function updateCurrentBuildInFile(buildId: string | null) { fs.writeFileSync(path.join(getRootDestinationFolder(), 'project.json'), JSON.stringify(projectData, null, 2)); } -export async function checkProject(): Promise { - if (!fs.existsSync(path.join(getRootDestinationFolder(), 'project.json'))) { +export async function checkProject(): Promise { + if (!fs.existsSync(getConfigFile())) { throw new Error('No project found. Please select or create a project first'); } else { - const projectData = JSON.parse(fs.readFileSync(path.join(getRootDestinationFolder(), 'project.json'), 'utf-8')); + // todo validate with zod or similar + const projectData = JSON.parse(fs.readFileSync(getConfigFile(), 'utf-8')); return projectData; } } diff --git a/packages/cli/src/application/utils.ts b/packages/cli/src/application/utils.ts index c352592..bf0bc01 100644 --- a/packages/cli/src/application/utils.ts +++ b/packages/cli/src/application/utils.ts @@ -2,6 +2,8 @@ import process from 'process'; import fs from 'fs'; import path from 'path'; import { execSync, ExecSyncOptions, ExecSyncOptionsWithBufferEncoding } from 'child_process'; +import { ProjectConfiguration } from './cloud/projectsManagement'; +import { AzureHubAdapter } from '../adapters/azure'; const util = require('util'); const execAsync = util.promisify(require('child_process').exec); @@ -19,10 +21,15 @@ export function executeCommandAsync(command: string, options?: ExecSyncOptionsWi } export function getRootDestinationFolder() { - return path.join(getProjectRootDir(), '.rn-incremental'); + return path.join(getProjectRootDir(), '.rn-build-hub'); +} + +export function getConfigFile() { + return path.join(getProjectRootDir(), '.rn-build-hub.json'); } export function getProjectRootDir() { + // todo improve this return process.cwd(); } @@ -57,3 +64,8 @@ export function getBuildFolderByBuildId(buildId: string) { export function capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1); } + +export function getAdapter(config: ProjectConfiguration) { + // todo select from configuration and import dynamically + return new AzureHubAdapter(config.remoteAdapter.config); +} diff --git a/packages/cli/src/commands/downloadBuild.ts b/packages/cli/src/commands/downloadBuild.ts index e7be967..ab49c4c 100644 --- a/packages/cli/src/commands/downloadBuild.ts +++ b/packages/cli/src/commands/downloadBuild.ts @@ -1,9 +1,9 @@ -import ProjectAwareCommand from '../_projectAwareCommand'; +import RemoteAwareCommand from '../_projectAwareCommand'; import { downloadBuild, getLastBuild } from '../application/cloud/buildsManagement'; import { Flags } from '@oclif/core'; import logger from '../application/logger'; -export default class DownloadBuild extends ProjectAwareCommand { +export default class DownloadBuild extends RemoteAwareCommand { static description = 'Download all the binary of the given build'; static examples = ['<%= config.bin %> <%= command.id %>']; @@ -19,13 +19,13 @@ export default class DownloadBuild extends ProjectAwareCommand { const buildId = flags.buildId; let buildIdToDownload: string; if (buildId === 'last') { - const build = await getLastBuild(this.currentProject.id); - buildIdToDownload = build.id; + const buildId = await getLastBuild(this.currentProject); + buildIdToDownload = buildId; } else { buildIdToDownload = buildId; } logger.info(`Downloading build ${buildIdToDownload}`); - await downloadBuild(buildIdToDownload); + await downloadBuild(buildIdToDownload, this.currentProject); this.exit(0); } } diff --git a/packages/cli/src/commands/makeBuildCurrent.ts b/packages/cli/src/commands/makeBuildCurrent.ts index e3581df..96481a7 100644 --- a/packages/cli/src/commands/makeBuildCurrent.ts +++ b/packages/cli/src/commands/makeBuildCurrent.ts @@ -1,12 +1,33 @@ import fs from 'node:fs'; -import ProjectAwareCommand from '../_projectAwareCommand'; +import RemoteAwareCommand from '../_projectAwareCommand'; import { downloadBuild, getLastBuild, makeCurrentBuild } from '../application/cloud/buildsManagement'; import { Flags } from '@oclif/core'; import logger from '../application/logger'; import { getBuildFolderByBuildId } from '../application/utils'; -import { updateCurrentBuildInFile } from '../application/cloud/projectsManagement'; +import { ProjectConfiguration, updateCurrentBuildInFile } from "../application/cloud/projectsManagement"; -export default class MakeBuildCurrent extends ProjectAwareCommand { +export async function updateCurrentBuild(buildId: string, config: ProjectConfiguration) { + let buildIdToDownload: string; + + if (buildId === "last") { + const buildId = await getLastBuild(config); + buildIdToDownload = buildId; + } else { + buildIdToDownload = buildId; + } + logger.info(`Downloading build ${buildIdToDownload}`); + if (fs.existsSync(getBuildFolderByBuildId(buildIdToDownload))) { + logger.info(`Build ${buildIdToDownload} already downloaded`); + } else { + await downloadBuild(buildIdToDownload, config); + } + + await makeCurrentBuild(buildIdToDownload); + + await updateCurrentBuildInFile(buildIdToDownload); +} + +export default class MakeBuildCurrent extends RemoteAwareCommand { static description = 'Make a give build the current one, if not present it will be downloaded'; static examples = ['<%= config.bin %> <%= command.id %>']; @@ -20,23 +41,9 @@ export default class MakeBuildCurrent extends ProjectAwareCommand { public async run(): Promise { const { flags } = await this.parse(MakeBuildCurrent); const buildId = flags.buildId; - let buildIdToDownload: string; - if (buildId === 'last') { - const build = await getLastBuild(this.currentProject.id); - buildIdToDownload = build.id; - } else { - buildIdToDownload = buildId; - } - logger.info(`Downloading build ${buildIdToDownload}`); - if (fs.existsSync(getBuildFolderByBuildId(buildIdToDownload))) { - logger.info(`Build ${buildIdToDownload} already downloaded`); - } else { - await downloadBuild(buildIdToDownload); - } - - await makeCurrentBuild(buildIdToDownload); - - await updateCurrentBuildInFile(buildIdToDownload); + + const config = this.currentProject; + await updateCurrentBuild(buildId, config); this.exit(0); } diff --git a/packages/cli/src/commands/run.ts b/packages/cli/src/commands/run.ts index 5e179dd..8cfcbe3 100644 --- a/packages/cli/src/commands/run.ts +++ b/packages/cli/src/commands/run.ts @@ -4,8 +4,10 @@ import { runApp as runIos } from '../application/runIos'; import { startMetro, checkIsMetroRunning } from '../application/metroManager'; import logger from '../application/logger'; import { iosBuildPlatforms } from '../application/iosUtils'; +import { updateCurrentBuild } from "./makeBuildCurrent"; +import RemoteAwareCommand from "../_projectAwareCommand"; -export default class Run extends Command { +export default class Run extends RemoteAwareCommand { static description = 'Run the native app'; static examples = ['<%= config.bin %> <%= command.id %>']; @@ -36,6 +38,13 @@ export default class Run extends Command { const start = performance.now(); logger.info('Checking if metro is running...'); + if(buildId !== 'local') { + logger.info(`Requested to run specific id ${buildId}`); + // todo remote command only if needed? + // do we have to copy to local? can't we just run from build folder? + await updateCurrentBuild(buildId, this.currentProject); + } + const isMetroRunning = await checkIsMetroRunning(); if (!isMetroRunning) { logger.info('Metro is not running. Starting Metro'); diff --git a/packages/cli/src/commands/uploadBuild.ts b/packages/cli/src/commands/uploadBuild.ts index 2165498..b45f9a8 100644 --- a/packages/cli/src/commands/uploadBuild.ts +++ b/packages/cli/src/commands/uploadBuild.ts @@ -1,8 +1,8 @@ -import ProjectAwareCommand from '../_projectAwareCommand'; +import RemoteAwareCommand from '../_projectAwareCommand'; import { getAvailableCurrentBuilds, uploadBuilds } from '../application/cloud/buildsManagement'; import { updateCurrentBuildInFile } from '../application/cloud/projectsManagement'; -export default class UploadBuild extends ProjectAwareCommand { +export default class UploadBuild extends RemoteAwareCommand { static description = 'Save current android and ios builds to the cloud'; static examples = ['<%= config.bin %> <%= command.id %>']; @@ -12,7 +12,7 @@ export default class UploadBuild extends ProjectAwareCommand { public async run(): Promise { const { androidBuilds, iosBuilds } = getAvailableCurrentBuilds(); - const buildId = await uploadBuilds(androidBuilds, iosBuilds, this.currentProject.id); + const buildId = await uploadBuilds(androidBuilds, iosBuilds, this.currentProject); await updateCurrentBuildInFile(buildId); this.exit(0); }