diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6822bff..40407f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1,91 @@ -Please refer to https://github.com/pawanpaudel93/contribute +# Contributing to ao-deploy + +Thank you for considering contributing to ao-deploy! We welcome contributions from everyone. To ensure a smooth process, please follow the guidelines below. + +## How to Contribute + +### Reporting Issues + +1. **Search for existing issues**: Before opening a new issue, make sure to check if the issue has already been reported. +2. **Provide detailed information**: Include a clear and concise description of the issue, steps to reproduce it, and any relevant screenshots or logs. +3. **Open an issue**: Report issues on our [issue tracker](https://github.com/pawanpaudel93/ao-deploy/issues). + +### Contributing Code + +1. **Fork the repository**: Create a personal copy of the repository on GitHub by forking it. +2. **Create a branch**: Make sure to create a new branch for your changes. Use a descriptive name for your branch (e.g., `fix-typo`, `add-new-feature`). +3. **Install dependencies**: Use `pnpm` to install dependencies. + + ```bash + pnpm install + ``` + +4. **Make changes**: Implement your changes in your branch. +5. **Test your changes**: Ensure that your changes do not break any existing functionality. + +6. **Lint your code**: Ensure your code follows the project's coding standards. + + ```bash + pnpm lint + ``` + +7. **Submit a pull request**: Push your changes to your forked repository and open a pull request to the main repository. Provide a clear description of the changes and why they are needed. + +### Development Guidelines + +1. **Code style**: + - Follow the coding style and guidelines as configured in our ESLint setup. We use `@antfu/eslint-config` for ESLint configuration. + - Ensure code formatting using ESLint. We have `lint-staged` configured to run `eslint --fix` on staged files before committing. + +2. **Documentation**: Update documentation as necessary to reflect your changes. +3. **Commit messages**: Write clear and concise commit messages that explain the purpose of the changes. + +### Scripts + +- **Build**: Build the project using `unbuild`. + + ```bash + pnpm build + ``` + +- **Dev**: Start the development server. + + ```bash + pnpm dev + ``` + +- **Lint**: Lint the project using ESLint. + + ```bash + pnpm lint + ``` + +- **Test**: Run tests using Vitest. + + ```bash + pnpm test + ``` + +- **Typecheck**: Check TypeScript types. + + ```bash + pnpm typecheck + ``` + +- **Release**: Bump version and publish. + + ```bash + pnpm release + ``` + +- **Prepare**: Prepare git hooks using `simple-git-hooks`. + + ```bash + pnpm prepare + ``` + +## Need Help? + +If you need assistance or have questions, feel free to reach out to us through our [issue tracker](https://github.com/pawanpaudel93/ao-deploy/issues). + +Thank you for contributing! diff --git a/README.md b/README.md index 1ae981e..258484f 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,20 @@ [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] -[![bundle][bundle-src]][bundle-href] [![JSDocs][jsdocs-src]][jsdocs-href] [![License][license-src]][license-href] A package for deploying AO contracts. +## Features + +- Build only or deploy AO contracts with ease. +- Custom LUA_PATH support. +- Support LuaRocks packages. +- Support for deployment configuration. +- Flexible concurrency and retry options for reliable deployments. +- CLI and API interfaces for versatile usage. + ## Installation ### Using npm @@ -52,24 +60,27 @@ Options: -w, --wallet [wallet] Path to the wallet JWK file. -l, --lua-path [luaPath] Specify the Lua modules path seperated by semicolon. -d, --deploy [deploy] List of deployment configuration names, separated by commas. + -b, --build [build] List of deployment configuration names, separated by commas. -s, --scheduler [scheduler] Scheduler to be used for the process. (default: "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA") -m, --module [module] Module source for spawning the process. -c, --cron [interval] Cron interval for the process (e.g. 1-minute, 5-minutes). -t, --tags [tags...] Additional tags for spawning the process. -p, --process-id [processId] Specify process Id of existing process. + --build-only Bundle the contract into a single file and store it in the process-dist directory. + --out-dir [outDir] Used with --build-only to output the single bundle contract file to a specified directory. --concurrency [limit] Concurrency limit for deploying multiple processes. (default: "5") --retry-count [count] Number of retries for deploying contract. (default: "10") --retry-delay [delay] Delay between retries in milliseconds. (default: "3000") -h, --help display help for command ``` -#### CLI Examples +#### Example: Deploy contract ```sh ao-deploy process.lua -n tictactoe -w wallet.json --tags name1:value1 name2:value2 ``` -##### Deployment with configuration +#### Example: Deploy contracts with configuration Here is an example using a deployment configuration: @@ -116,6 +127,71 @@ Deploy specific contracts: ao-deploy aod.config.ts --deploy=contract_1,contract_3 ``` +#### Example: Build Contract + +To Build contracts and produce single bundle lua file, take a look at below provided commands + +Build contract and save to default(`process-dist`) directory: + +```sh +aod src/process.lua -n my-process --build-only +``` + +Build contract and save to specific directory: + +```sh +aod src/process.lua -n my-process --build-only --out-dir +aod src/process.lua -n my-process --build-only --out-dir ./dist +``` + +#### Example: Build Contracts using Configuration + +To Build contracts using config, take a look at below provided example + +Here is an example using a deployment configuration: + +```ts +// aod.config.ts +import { defineConfig } from 'ao-deploy' + +const luaPath = './?.lua;./src/?.lua' + +const config = defineConfig({ + contract_1: { + luaPath, + name: `contract-1`, + contractPath: 'contract-1.lua', + outDir: './dist', + }, + contract_2: { + luaPath, + name: `contract-2`, + contractPath: 'contract-2.lua', + outDir: './dist', + }, + contract_3: { + luaPath, + name: `contract-3`, + contractPath: 'contract-3.lua', + outDir: './dist', + } +}) + +export default config +``` + +Build all specified contracts: + +```sh +ao-deploy aod.config.ts --build-only +``` + +Build specific contracts: + +```sh +ao-deploy aod.config.ts --build=contract_1,contract_3 --build-only +``` + > [!Note] A wallet is generated and saved if not passed. @@ -251,8 +327,6 @@ Copyright © 2024 [Pawan Paudel](https://github.com/pawanpaudel93). [npm-version-href]: https://npmjs.com/package/ao-deploy [npm-downloads-src]: https://img.shields.io/npm/dm/ao-deploy?style=flat&colorA=080f12&colorB=1fa669 [npm-downloads-href]: https://npmjs.com/package/ao-deploy -[bundle-src]: https://img.shields.io/bundlephobia/minzip/ao-deploy?style=flat&colorA=080f12&colorB=1fa669&label=minzip -[bundle-href]: https://bundlephobia.com/result?p=ao-deploy [license-src]: https://img.shields.io/github/license/pawanpaudel93/ao-deploy.svg?style=flat&colorA=080f12&colorB=1fa669 [license-href]: https://github.com/pawanpaudel93/ao-deploy/blob/main/LICENSE [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669 diff --git a/package.json b/package.json index 17cc59b..a0d62c6 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "prepare": "simple-git-hooks" }, "dependencies": { - "@permaweb/aoconnect": "^0.0.53", + "@permaweb/aoconnect": "^0.0.57", "ardb": "^1.1.10", "arweave": "^1.15.1", "chalk": "^5.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index faa01f6..1899371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@permaweb/aoconnect': - specifier: ^0.0.53 - version: 0.0.53 + specifier: ^0.0.57 + version: 0.0.57 ardb: specifier: ^1.1.10 version: 1.1.10 @@ -35,7 +35,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^2.16.1 - version: 2.16.1(@vue/compiler-sfc@3.4.26)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0) + version: 2.16.1(@vue/compiler-sfc@3.4.34)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0) '@antfu/ni': specifier: ^0.21.12 version: 0.21.12 @@ -89,7 +89,7 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true - /@antfu/eslint-config@2.16.1(@vue/compiler-sfc@3.4.26)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0): + /@antfu/eslint-config@2.16.1(@vue/compiler-sfc@3.4.34)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0): resolution: {integrity: sha512-7oHCor9ZgVb8FguStNZMOZLRdyYdr1/t6EhhWVSXZjuq7086OFdlksdav6jcflOzazo0doRlP12urzoYq+r1cg==} hasBin: true peerDependencies: @@ -157,7 +157,7 @@ packages: eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0)(typescript@5.4.5)(vitest@1.6.0) eslint-plugin-vue: 9.25.0(eslint@8.57.0) eslint-plugin-yml: 1.14.0(eslint@8.57.0) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.26)(eslint@8.57.0) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.34)(eslint@8.57.0) globals: 15.1.0 jsonc-eslint-parser: 2.4.0 local-pkg: 0.5.0 @@ -198,6 +198,14 @@ packages: picocolors: 1.0.0 dev: true + /@babel/code-frame@7.24.7: + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.0 + dev: true + /@babel/compat-data@7.24.4: resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} @@ -307,11 +315,21 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-string-parser@7.24.8: + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-identifier@7.24.5: resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -339,6 +357,17 @@ packages: picocolors: 1.0.0 dev: true + /@babel/highlight@7.24.7: + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + requiresBuild: true + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.0 + dev: true + /@babel/parser@7.24.5: resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} engines: {node: '>=6.0.0'} @@ -347,6 +376,14 @@ packages: '@babel/types': 7.24.5 dev: true + /@babel/parser@7.25.0: + resolution: {integrity: sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.25.0 + dev: true + /@babel/standalone@7.24.5: resolution: {integrity: sha512-Sl8oN9bGfRlNUA2jzfzoHEZxFBDliBlwi5mPVCAWKSlBNkXXJOHpu7SDOqjF6mRoTa6GNX/1kAWG3Tr+YQ3N7A==} engines: {node: '>=6.9.0'} @@ -388,6 +425,15 @@ packages: to-fast-properties: 2.0.0 dev: true + /@babel/types@7.25.0: + resolution: {integrity: sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + dev: true + /@clack/core@0.3.4: resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} dependencies: @@ -970,27 +1016,27 @@ packages: fastq: 1.17.1 dev: true - /@permaweb/ao-scheduler-utils@0.0.19: - resolution: {integrity: sha512-xwIe9FqQ1UZxEYWvSGJDONz0xr4vDq2Ny1NeRUiO0dKYoonShN+oI1ULgrHocKOjOPNEgRX70vMCKGLe+3x70A==} - engines: {node: '>=18'} + /@permaweb/ao-scheduler-utils@0.0.20: + resolution: {integrity: sha512-bJkcmnQm/rCGqklJt46q5TnHfWkFzSBcSf9Z3uy8ylHRAheS9NyR1BJMAj3EXDjHCpg7JfnLRo6Uc3Xdw1lmOA==} + engines: {node: '>=18', yarn: please-use-npm} dependencies: - lru-cache: 10.2.2 - ramda: 0.30.0 - zod: 3.23.6 + lru-cache: 10.4.3 + ramda: 0.30.1 + zod: 3.23.8 dev: false - /@permaweb/aoconnect@0.0.53: - resolution: {integrity: sha512-AFfuTBU35d82JzwfHlHMJaHTB544LO2laK6lRSEaemD/Q2YDdqA/Nje7EattksB+GNUMwPbRG9WZjsIGS/ZsdA==} - engines: {node: '>=18'} + /@permaweb/aoconnect@0.0.57: + resolution: {integrity: sha512-l1+47cZuQ8pOIMOdRXymcegCmefXjqR8Bc2MY6jIzWv9old/tG6mfCue2W1QviGyhjP3zEVQgr7YofkY2lq35Q==} + engines: {node: '>=18', yarn: please-use-npm} dependencies: - '@permaweb/ao-scheduler-utils': 0.0.19 + '@permaweb/ao-scheduler-utils': 0.0.20 buffer: 6.0.3 - debug: 4.3.4 + debug: 4.3.6 hyper-async: 1.1.2 mnemonist: 0.39.8 - ramda: 0.29.1 + ramda: 0.30.1 warp-arbundles: 1.0.4 - zod: 3.23.6 + zod: 3.23.8 transitivePeerDependencies: - supports-color dev: false @@ -1649,46 +1695,46 @@ packages: pretty-format: 29.7.0 dev: true - /@vue/compiler-core@3.4.26: - resolution: {integrity: sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==} + /@vue/compiler-core@3.4.34: + resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==} dependencies: - '@babel/parser': 7.24.5 - '@vue/shared': 3.4.26 + '@babel/parser': 7.25.0 + '@vue/shared': 3.4.34 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.0 dev: true - /@vue/compiler-dom@3.4.26: - resolution: {integrity: sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==} + /@vue/compiler-dom@3.4.34: + resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==} dependencies: - '@vue/compiler-core': 3.4.26 - '@vue/shared': 3.4.26 + '@vue/compiler-core': 3.4.34 + '@vue/shared': 3.4.34 dev: true - /@vue/compiler-sfc@3.4.26: - resolution: {integrity: sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==} + /@vue/compiler-sfc@3.4.34: + resolution: {integrity: sha512-x6lm0UrM03jjDXTPZgD9Ad8bIVD1ifWNit2EaWQIZB5CULr46+FbLQ5RpK7AXtDHGjx9rmvC7QRCTjsiGkAwRw==} dependencies: - '@babel/parser': 7.24.5 - '@vue/compiler-core': 3.4.26 - '@vue/compiler-dom': 3.4.26 - '@vue/compiler-ssr': 3.4.26 - '@vue/shared': 3.4.26 + '@babel/parser': 7.25.0 + '@vue/compiler-core': 3.4.34 + '@vue/compiler-dom': 3.4.34 + '@vue/compiler-ssr': 3.4.34 + '@vue/shared': 3.4.34 estree-walker: 2.0.2 magic-string: 0.30.10 - postcss: 8.4.38 + postcss: 8.4.40 source-map-js: 1.2.0 dev: true - /@vue/compiler-ssr@3.4.26: - resolution: {integrity: sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==} + /@vue/compiler-ssr@3.4.34: + resolution: {integrity: sha512-8TDBcLaTrFm5rnF+Qm4BlliaopJgqJ28Nsrc80qazynm5aJO+Emu7y0RWw34L8dNnTRdcVBpWzJxhGYzsoVu4g==} dependencies: - '@vue/compiler-dom': 3.4.26 - '@vue/shared': 3.4.26 + '@vue/compiler-dom': 3.4.34 + '@vue/shared': 3.4.34 dev: true - /@vue/shared@3.4.26: - resolution: {integrity: sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==} + /@vue/shared@3.4.34: + resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==} dev: true /@weavery/clarity@0.1.5: @@ -2377,6 +2423,19 @@ packages: optional: true dependencies: ms: 2.1.2 + dev: true + + /debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} @@ -2903,13 +2962,13 @@ packages: - supports-color dev: true - /eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.26)(eslint@8.57.0): + /eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.34)(eslint@8.57.0): resolution: {integrity: sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==} peerDependencies: '@vue/compiler-sfc': ^3.3.0 eslint: ^8.50.0 || ^9.0.0 dependencies: - '@vue/compiler-sfc': 3.4.26 + '@vue/compiler-sfc': 3.4.34 eslint: 8.57.0 dev: true @@ -3803,6 +3862,11 @@ packages: /lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + dev: true + + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: false /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -4196,7 +4260,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.24.2 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4265,6 +4329,10 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + dev: true + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4618,6 +4686,15 @@ packages: source-map-js: 1.2.0 dev: true + /postcss@8.4.40: + resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4658,12 +4735,8 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /ramda@0.29.1: - resolution: {integrity: sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==} - dev: false - - /ramda@0.30.0: - resolution: {integrity: sha512-13Y0iMhIQuAm/wNGBL/9HEqIfRGmNmjKnTPlKWfA9f7dnDkr8d45wQ+S7+ZLh/Pq9PdcGxkqKUEA7ySu1QSd9Q==} + /ramda@0.30.1: + resolution: {integrity: sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==} dev: false /rc9@2.1.2: @@ -4798,7 +4871,7 @@ packages: rollup: 3.29.4 typescript: 5.4.5 optionalDependencies: - '@babel/code-frame': 7.24.2 + '@babel/code-frame': 7.24.7 dev: true /rollup@3.29.4: @@ -5639,6 +5712,6 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - /zod@3.23.6: - resolution: {integrity: sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==} + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false diff --git a/src/cli.ts b/src/cli.ts index 2bac39b..8632bb3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,8 +8,11 @@ import chalk from 'chalk' import { Command } from 'commander' import { deployContract, deployContracts } from './lib/deploy' import { ConfigManager } from './lib/config' -import type { DeployResult, Tag } from './types' +import type { BundleResult, DeployResult, Tag } from './types' import { Logger } from './lib/logger' +import { BuildError, DeployError } from './lib/error' +import { loadAndBundleContracts } from './lib/loader' +import { clearBuildOutDir } from './lib/utils' const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '../') @@ -53,6 +56,20 @@ function logDeploymentDetails(result: DeployResult) { logger.log(`Deployment Message: ${messageUrl}`) } +function logBundleDetails(result: BundleResult) { + const { name, outDir, size, configName } = result + const generated = chalk.green(`${name}.lua has been generated at ${outDir}`) + const bundleSize = chalk.green(`Bundle size is ${size} bytes`) + const logger = Logger.init(configName) + + console.log('') + + logger.log(`Bundling: ${generated}`) + logger.log(`Bundling: ${bundleSize}`) + + logger.log(`Bundling complete! ✨`) +} + const program = new Command() const packageJson = getPackageJson() program @@ -64,11 +81,14 @@ program .option('-w, --wallet [wallet]', 'Path to the wallet JWK file.') .option('-l, --lua-path [luaPath]', 'Specify the Lua modules path seperated by semicolon.') .option('-d, --deploy [deploy]', 'List of deployment configuration names, separated by commas.') + .option('-b, --build [build]', 'List of build configuration names, separated by commas.') .option('-s, --scheduler [scheduler]', 'Scheduler to be used for the process.', '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA') .option('-m, --module [module]', 'Module source for spawning the process.') .option('-c, --cron [interval]', 'Cron interval for the process (e.g. 1-minute, 5-minutes).') .option('-t, --tags [tags...]', 'Additional tags for spawning the process.') .option('-p, --process-id [processId]', 'Specify process Id of an existing process.') + .option('--build-only', 'Bundle the contract into a single file and store it in the process-dist directory.') + .option('--out-dir [outDir]', 'Used with --build-only to output the single bundle contract file to a specified directory.') .option('--concurrency [limit]', 'Concurrency limit for deploying multiple processes.', '5') .option('--retry-count [count]', 'Number of retries for deploying contract.', '10') .option('--retry-delay [delay]', 'Delay between retries in milliseconds.', '3000') @@ -77,11 +97,14 @@ program.parse(process.argv) const options = program.opts() const contractOrConfigPath = program.args[0] +const isContractPath = contractOrConfigPath.endsWith('.lua') +const isBuildOnly = options.buildOnly +const outDir = options.outDir || './process-dist' -;(async () => { +async function deploymentHandler() { try { Logger.log(packageJson.name, 'Deploying...', false, true) - if (contractOrConfigPath.endsWith('.lua')) { + if (isContractPath) { const tags: Tag[] = Array.isArray(options.tags) ? options.tags.reduce((accumulator, tag) => { if (tag && tag.includes(':')) { @@ -133,10 +156,89 @@ const contractOrConfigPath = program.args[0] Logger.log(packageJson.name, `Deployment Status: ${chalk.green(`${successCount}/${totalCount}`)} successful deployments.`, true) } } + catch (error: any) { + throw new DeployError(error?.message ?? 'Failed to deploy contract!') + } +} + +async function buildHandler() { + try { + await clearBuildOutDir(outDir) + Logger.log(packageJson.name, 'Bundling...', false, true) + + const name = options.name || 'bundle' + + if (isContractPath) { + const [result] = await loadAndBundleContracts([{ + contractPath: contractOrConfigPath, + name, + outDir, + luaPath: options.luaPath, + }], 1) + + if (result && result.status === 'fulfilled') { + logBundleDetails(result.value) + } + else { + Logger.error(name, 'Failed to bundle contract!', true) + Logger.error(name, result.reason) + } + } + else { + const configManager = new ConfigManager(contractOrConfigPath) + const deployConfigs = configManager.getDeployConfigs(options.build) + const concurrency = parseToInt(options.concurrency, 5) + + const bundlingConfigs = deployConfigs.map(config => ({ + name: config.name || 'bundle', + contractPath: config.contractPath, + outDir: config.outDir || './process-dist', + luaPath: config.luaPath, + })) + const results = await loadAndBundleContracts(bundlingConfigs, concurrency) + + results.forEach((result, idx) => { + const configName = deployConfigs[idx].configName! + + if (result.status === 'fulfilled') { + logBundleDetails(result.value) + } + else { + Logger.error(configName, 'Failed to bundle contract!', true) + Logger.error(configName, result.reason) + } + }) + + const totalCount = bundlingConfigs.length + const successCount = results.length + Logger.log(packageJson.name, `Build status: ${chalk.green(`${successCount}/${totalCount}`)} successful builds.`, true) + } + } + catch (error: any) { + throw new BuildError(error?.message ?? 'Failed to bundle contract!') + } +} + +;(async () => { + try { + if (isBuildOnly) { + await buildHandler() + } + else { + await deploymentHandler() + } + } catch (error: any) { const logger = Logger.init(packageJson.name) - logger.error(`Deployment failed!`, true) - logger.error(error?.message ?? 'Failed to deploy contract!') + + if (error instanceof DeployError) { + logger.error(`Deployment failed!`, true) + } + if (error instanceof BuildError) { + logger.error(`Build failed!`, true) + } + + logger.error(error?.message) process.exit(1) } })() diff --git a/src/lib/deploy.ts b/src/lib/deploy.ts index 4e089de..b29e385 100644 --- a/src/lib/deploy.ts +++ b/src/lib/deploy.ts @@ -124,6 +124,7 @@ export class DeploymentsManager { const loader = new LuaProjectLoader(configName, luaPath) const contractSrc = await loader.loadContract(contractPath) + logger.log(`Deploying: ${contractPath}`, false, true) // Load contract to process const messageId = await retryWithDelay( async () => diff --git a/src/lib/error.ts b/src/lib/error.ts new file mode 100644 index 0000000..d95fda8 --- /dev/null +++ b/src/lib/error.ts @@ -0,0 +1,2 @@ +export class BuildError extends Error {} +export class DeployError extends Error {} diff --git a/src/lib/loader.ts b/src/lib/loader.ts index 32a88b6..c720504 100644 --- a/src/lib/loader.ts +++ b/src/lib/loader.ts @@ -14,8 +14,10 @@ import util from 'node:util' // @ts-expect-error types missing import createFileTree from 'pretty-file-tree' import chalk from 'chalk' -import type { Module } from '../types' +import pLimit from 'p-limit' +import type { BundleResult, BundlingConfig, Module } from '../types' import { Logger } from './logger' +import { writeFileToProjectDir } from './utils' const execAsync = util.promisify(exec) @@ -158,7 +160,7 @@ export class LuaProjectLoader { throw new Error(chalk.red(`${filePath} file not found.`)) } - this.#logger.log(`Deploying: ${contractPath}`, false, true) + this.#logger.log(`Loading: ${contractPath}`, false, true) let line = await fs.readFile(filePath, 'utf-8') this.#logger.log(`Parsing contract structure...`, false, true) @@ -167,7 +169,7 @@ export class LuaProjectLoader { if (projectStructure.length > 0) { line = `${this.#createExecutableFromProject(projectStructure)}\n\n${line}` - this.#logger.log(chalk.yellow(`The following files will be deployed:`), false, true) + this.#logger.log(chalk.yellow(`The following files will be loaded:`), false, true) console.log(chalk.dim(createFileTree([...projectStructure.map(m => m.path), `${filePath} ${chalk.reset(chalk.bgGreen(' MAIN '))}`]))) console.log('') } @@ -178,4 +180,32 @@ export class LuaProjectLoader { throw new Error(chalk.red('It requires a *.lua file')) } } + + async loadAndBundleContract(config: BundlingConfig): Promise { + try { + const contractSrc = await this.loadContract(config.contractPath) + await writeFileToProjectDir(contractSrc, config.outDir, config.name) + + return { + configName: config.name, + outDir: config.outDir, + size: new TextEncoder().encode(contractSrc).length, + name: config.name, + } + } + catch (error) { + throw new Error(chalk.red(`Failed to load and bundle contract at: ${config.contractPath}`)) + } + } +} + +export async function loadAndBundleContracts(configs: BundlingConfig[], concurrency: number = 5): Promise[]> { + const limit = pLimit(concurrency) + const promises = configs.map(config => limit(() => { + const loader = new LuaProjectLoader(config.name, config.luaPath) + + return loader.loadAndBundleContract(config) + })) + + return await Promise.allSettled(promises) } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 0c49c3e..89a6f0a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,7 @@ +import path from 'node:path' +import process from 'node:process' +import { writeFile } from 'node:fs/promises' +import { existsSync, mkdirSync, rmSync } from 'node:fs' import Arweave from 'arweave' import Ardb from 'ardb' @@ -49,3 +53,33 @@ export async function retryWithDelay( return attempt() } + +export async function writeFileToProjectDir(data: string, outDir: string, fileName: string) { + try { + const fullPath = path.join(process.cwd(), `${outDir}/${fileName}.lua`) + const dirName = path.dirname(fullPath) + if (!existsSync(dirName)) { + mkdirSync(dirName) + } + await writeFile(fullPath, data) + } + catch (error) { + throw new Error(`Failed to write bundle to ${outDir}`) + } +} + +export async function clearBuildOutDir(outDir: string) { + try { + const fullPath = path.join(process.cwd(), `${outDir}`) + const dirName = path.dirname(fullPath) + + if (!existsSync(dirName)) { + return true + } + + rmSync(outDir, { recursive: true, force: true }) + } + catch (error) { + throw new Error(`Failed to clear ${outDir}`) + } +} diff --git a/src/types/index.ts b/src/types/index.ts index 5f8a355..6f5ffc2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -70,6 +70,10 @@ export interface DeployConfig { * Process Id of an existing process */ processId?: string + /** + * Output directory of bundle + */ + outDir?: string } export type Config = Record @@ -82,4 +86,18 @@ export interface DeployResult { isNewProcess: boolean } +export interface BundleResult { + name: string + configName: string + outDir: string + size: number +} + +export interface BundlingConfig { + name: string + contractPath: string + outDir: string + luaPath?: string +} + export interface Module { name: string, path: string, content?: string, dependencies?: Set }