├── .commitlintrc.yml ├── .czrc ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.yml ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .mocharc.yml ├── .nycrc.yml ├── .prettierignore ├── .prettierrc.yml ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-interactive-tools.cjs └── releases │ └── yarn-3.2.2.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── package.json ├── src ├── constants.ts ├── helpers.ts ├── index.ts ├── type-extensions.ts └── types.ts ├── test ├── fixture-project │ ├── contracts │ │ ├── A.sol │ │ ├── B.sol │ │ ├── lib │ │ │ └── C.sol │ │ └── test │ │ │ └── foo │ │ │ └── D.sol │ └── hardhat.config.ts ├── helpers.ts └── packager.test.ts ├── tsconfig.json ├── tsconfig.prod.json └── yarn.lock /.commitlintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - "@commitlint/config-conventional" 3 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "cz-conventional-changelog" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # All files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | COVERALLS_REPO_TOKEN="secret repo token" 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | .yarn/ 3 | **/.nyc_output 4 | **/artifacts 5 | **/build 6 | **/cache 7 | **/coverage 8 | **/dist 9 | **/node_modules 10 | **/typechain 11 | 12 | # files 13 | *.env 14 | *.log 15 | *.tsbuildinfo 16 | .pnp.* 17 | coverage.json 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - "eslint:recommended" 3 | - "plugin:@typescript-eslint/eslint-recommended" 4 | - "plugin:@typescript-eslint/recommended" 5 | - "prettier" 6 | parser: "@typescript-eslint/parser" 7 | parserOptions: 8 | project: "tsconfig.json" 9 | plugins: 10 | - "@typescript-eslint" 11 | root: true 12 | rules: 13 | "@typescript-eslint/no-explicit-any": "off" 14 | "@typescript-eslint/no-floating-promises": 15 | - error 16 | - ignoreIIFE: true 17 | ignoreVoid: true 18 | "@typescript-eslint/no-inferrable-types": "off" 19 | "@typescript-eslint/no-unused-vars": 20 | - error 21 | - argsIgnorePattern: _ 22 | varsIgnorePattern: _ 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: "https://3cities.xyz/#/pay?c=CAESFAKY9DMuOFdjE4Wzl2YyUFipPiSfIgICATICCAJaFURvbmF0aW9uIHRvIFBhdWwgQmVyZw" 2 | github: "PaulRBerg" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | env: 4 | COVERAGE_GIT_BRANCH: "main" 5 | COVERAGE_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 6 | COVERAGE_SERVICE_NAME: "github-actions-ci" 7 | 8 | on: 9 | pull_request: 10 | branches: 11 | - "main" 12 | push: 13 | branches: 14 | - "main" 15 | 16 | jobs: 17 | ci: 18 | runs-on: "ubuntu-latest" 19 | steps: 20 | - name: "Check out the repo" 21 | uses: "actions/checkout@v3" 22 | 23 | - name: "Install Node.js" 24 | uses: "actions/setup-node@v3" 25 | with: 26 | cache: "yarn" 27 | node-version: "16" 28 | 29 | - name: "Install the dependencies" 30 | run: "yarn install --immutable" 31 | 32 | - name: "Lint the code" 33 | run: "yarn lint" 34 | 35 | - name: "Run the tests and generate coverage report" 36 | run: "yarn coverage" 37 | 38 | - name: "Upload the coverage report to Coveralls" 39 | uses: "coverallsapp/github-action@master" 40 | with: 41 | github-token: ${{ secrets.GITHUB_TOKEN }} 42 | path-to-lcov: "./coverage/lcov.info" 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folders 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/releases 5 | !.yarn/plugins 6 | !.yarn/sdks 7 | !.yarn/versions 8 | **/.nyc_output 9 | **/artifacts 10 | **/build 11 | **/cache 12 | **/coverage 13 | **/dist 14 | **/node_modules 15 | **/typechain 16 | 17 | # files 18 | *.env 19 | *.log 20 | *.tsbuildinfo 21 | .pnp.* 22 | coverage.json 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn dlx commitlint --edit $1 5 | 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn dlx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,json,md,ts,yml}": [ 3 | "prettier --config ./.prettierrc.yml --write" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | extension: "ts" 2 | recursive: true 3 | require: 4 | - "earljs/mocha" 5 | - "ts-node/register/transpile-only" 6 | - "source-map-support/register" 7 | spec: "test/**/*.test.ts" 8 | timeout: 30000 9 | watchExtension: "ts" 10 | -------------------------------------------------------------------------------- /.nycrc.yml: -------------------------------------------------------------------------------- 1 | check-coverage: false 2 | extends: "@istanbuljs/nyc-config-typescript" 3 | include: "src/**/*" 4 | report-dir: "coverage" 5 | reporter: 6 | - "html" 7 | - "lcov" 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # folders 2 | .yarn/ 3 | **/.nyc_output 4 | **/artifacts 5 | **/build 6 | **/cache 7 | **/coverage 8 | **/dist 9 | **/node_modules 10 | **/typechain 11 | 12 | # files 13 | *.env 14 | *.log 15 | *.tsbuildinfo 16 | .pnp.* 17 | coverage.json 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | arrowParens: avoid 2 | bracketSpacing: true 3 | endOfLine: auto 4 | importOrder: ["", "^[./]"] 5 | importOrderParserPlugins: ["typescript"] 6 | importOrderSeparation: true 7 | importOrderSortSpecifiers: true 8 | printWidth: 120 9 | singleQuote: false 10 | tabWidth: 2 11 | trailingComma: all 12 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: "node-modules" 2 | 3 | plugins: 4 | - path: ".yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs" 5 | spec: "@yarnpkg/plugin-interactive-tools" 6 | 7 | yarnPath: ".yarn/releases/yarn-3.2.2.cjs" 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Common Changelog](https://common-changelog.org/), and this project adheres to [Semantic 6 | Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | [1.4.2]: https://github.com/paulrberg/hardhat-packager/compare/v1.4.1...v1.4.2 9 | [1.4.1]: https://github.com/paulrberg/hardhat-packager/compare/v1.4.0...v1.4.1 10 | [1.4.0]: https://github.com/paulrberg/hardhat-packager/compare/v1.3.0...v1.4.0 11 | [1.3.0]: https://github.com/paulrberg/hardhat-packager/compare/v1.2.1...v1.3.0 12 | [1.2.1]: https://github.com/paulrberg/hardhat-packager/compare/v1.2.0...v1.2.1 13 | [1.2.0]: https://github.com/paulrberg/hardhat-packager/compare/v1.1.0...v1.2.0 14 | [1.1.0]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.5...v1.1.0 15 | [1.0.5]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.4...v1.0.5 16 | [1.0.4]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.3...v1.0.4 17 | [1.0.3]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.2...v1.0.3 18 | [1.0.2]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.1...v1.0.2 19 | [1.0.1]: https://github.com/paulrberg/hardhat-packager/compare/v1.0.0...v1.0.1 20 | [1.0.0]: https://github.com/paulrberg/hardhat-packager/releases/tag/v1.0.0 21 | 22 | ## [1.4.2] - 2022-07-26 23 | 24 | ### Changed 25 | 26 | - Adhere to Common Changelog in `CHANGELOG.md` (@paulrberg) 27 | - Change license from "Unlicense" to "MIT" (@paulrberg) 28 | 29 | ## [1.4.1] - 2022-03-29 30 | 31 | ### Fixed 32 | 33 | - Handle empty directories two or more levels deep (@paulrberg) 34 | 35 | ## [1.4.0] - 2022-03-29 36 | 37 | ### Changed 38 | 39 | - Bump version of `@typechain/hardhat`, `hardhat` and `typechain` peer dependencies (@paulrberg) 40 | - Change directory tree in generated types now reflects the directory tree in the contracts inputs (@paulrberg) 41 | - Upgrade to `@typechain/hardhat` v6.0.0 (@paulrberg) 42 | - Upgrade to `hardhat` v2.9.2 (@paulrberg) 43 | - Upgrade to `typechain` v8.0.0 (@paulrberg) 44 | 45 | ## [1.3.0] - 2022-02-17 46 | 47 | ### Changed 48 | 49 | - Bump version of `@typechain/hardhat`, `hardhat` and `typechain` peer dependencies ([#12](https://github.com/paulrberg/hardhat-packager/issues/12)) (@paulrberg) 50 | - Upgrade to `@typechain/hardhat` v4.0.0 (@paulrberg) 51 | - Upgrade to `hardhat` v2.8.4 (@paulrberg) 52 | 53 | ## [1.2.1] - 2021-09-23 54 | 55 | ### Fixed 56 | 57 | - Exclude the "common" file in TypeChain subtask ([#11](https://github.com/paulrberg/hardhat-packager/issues/11)) (@paulrberg) 58 | 59 | ## [1.2.0] - 2021-09-18 60 | 61 | ### Changed 62 | 63 | - Affix version of `tempy` to v1.0.1 (@paulrberg) 64 | - Change license from "WTFPL" to "Unlicense" (@paulrberg) 65 | - Change the `TASK_` prefix to `SUBTASK_` for subtask constant names (@paulrberg) 66 | - Polish README (@paulrberg) 67 | - Set the `includeFactories` setting to `false` by default (@paulrberg) 68 | - Upgrade to `@typechain/hardhat` v2.3.0 (@paulrberg) 69 | - Upgrade to `hardhat` v2.6.4 (@paulrberg) 70 | 71 | ## [1.1.0] - 2021-08-09 72 | 73 | ### Added 74 | 75 | - Allow users to include the TypeChain factories in the output (@paulrberg) 76 | 77 | ### Changed 78 | 79 | - Rename `SUBTASK_PREPARE_PACKAGE_TYPECHAIN` task (@paulrberg) 80 | - Separate the TypeChain subtask in core bindings and factories subtasks (@paulrberg) 81 | 82 | ## [1.0.5] - 2021-08-04 83 | 84 | ### Added 85 | 86 | - Include the `CHANGELOG.md` file in package (@paulrberg) 87 | 88 | ### Fixed 89 | 90 | - Bump version of `hardhat` and `typechain` peer dependencies (@paulrberg) 91 | 92 | ## [1.0.4] - 2021-08-03 93 | 94 | ### Fixed 95 | 96 | - Fix typos in README (@paulrberg) 97 | 98 | ### Fixed 99 | 100 | - Prevent the TypeChain `common.ts` file from being deleted (@paulrberg) 101 | 102 | ## [1.0.3] - 2021-08-02 103 | 104 | ### Changed 105 | 106 | - Change the order of messages logged in the console (@paulrberg) 107 | 108 | ## [1.0.2] - 2021-08-02 109 | 110 | ### Added 111 | 112 | - Add new fields in `package.json`: bugs, keywords, homepage and repository (@paulrberg) 113 | - Add peer dependencies sa required by `@typechain/ethers-v5` (@paulrberg) 114 | 115 | ### Fixed 116 | 117 | - Fix name of task in `README.md` (@paulrberg) 118 | - Fix path to types in `package.json` (@paulrberg) 119 | 120 | ## [1.0.1] - 2021-08-02 121 | 122 | _This release was unpublished from npm_ 123 | 124 | ## [1.0.0] - 2021-08-02 125 | 126 | ### Added 127 | 128 | - First release of the plugin (@paulrberg) 129 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Paul Razvan Berg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hardhat Packager [![GitHub Actions][gha-badge]][gha] [![Coverage Status][coveralls-badge]][coveralls] [![Styled with Prettier][prettier-badge]][prettier] [![License: MIT][license-badge]][license] 2 | 3 | [gha]: https://github.com/paulrberg/hardhat-packager/actions 4 | [gha-badge]: https://github.com/paulrberg/hardhat-packager/actions/workflows/ci.yml/badge.svg 5 | [coveralls]: https://coveralls.io/github/paulrberg/hardhat-packager 6 | [coveralls-badge]: https://coveralls.io/repos/github/paulrberg/hardhat-packager/badge.svg?branch=main 7 | [prettier]: https://prettier.io 8 | [prettier-badge]: https://img.shields.io/badge/Code_Style-Prettier-ff69b4.svg 9 | [license]: https://opensource.org/licenses/MIT 10 | [license-badge]: https://img.shields.io/badge/License-MIT-blue.svg 11 | 12 | Hardhat plugin for preparing the contract artifacts and the TypeChain bindings for registry deployment. 13 | 14 | ## Description 15 | 16 | This plugin builds on top the [TypeChain plugin](https://github.com/ethereum-ts/TypeChain/tree/master/packages/hardhat) 17 | to prepare the contract artifacts and TypeChain bindings for being deployed to a package registry (e.g. 18 | [npmjs.org](https://npmjs.org)). More specifically, it deletes all artifacts and bindings that are not in an allowlist of 19 | contracts, minifying the directory structure in the process. 20 | 21 | ## Installation 22 | 23 | First, install the plugin and its peer dependencies. If you are using Ethers or Waffle, run: 24 | 25 | ```sh 26 | yarn add --dev hardhat-packager typechain @typechain/hardhat @typechain/ethers-v5 27 | ``` 28 | 29 | Or if you are using Truffle, run: 30 | 31 | ```sh 32 | yarn add --dev hardhat-packager typechain @typechain/hardhat @typechain/truffle-v5 33 | ``` 34 | 35 | Second, import the plugin in your `hardhat.config.js`: 36 | 37 | ```javascript 38 | require("@typechain/hardhat"); 39 | require("hardhat-packager"); 40 | ``` 41 | 42 | Or, if you are using TypeScript, in your `hardhat.config.ts`: 43 | 44 | ```typescript 45 | import "@typechain/hardhat"; 46 | import "hardhat-packager"; 47 | ``` 48 | 49 | ## Required plugins 50 | 51 | - [@typechain/hardhat](https://github.com/ethereum-ts/TypeChain/tree/master/packages/hardhat) 52 | 53 | ## Tasks 54 | 55 | This plugin adds the _prepare-package_ task to Hardhat: 56 | 57 | ```text 58 | Prepares the contract artifacts and the TypeChain bindings for registry deployment 59 | ``` 60 | 61 | ## Environment Extensions 62 | 63 | This plugin does not extend the Hardhat Runtime Environment. 64 | 65 | ## Configuration 66 | 67 | This plugin extends the `HardhatUserConfig` object with an optional `packager` object. This object contains one field, 68 | `contracts`. This is an array of strings that represent the names of the smart contracts in your project. The plugin 69 | uses this array as an allowlist for the artifacts and the bindings that should be kept for registry deployment. 70 | 71 | An example for how to set it: 72 | 73 | ```javascript 74 | module.exports = { 75 | packager: { 76 | // What contracts to keep the artifacts and the bindings for. 77 | contracts: ["MyToken", "ERC20"], 78 | // Whether to include the TypeChain factories or not. 79 | // If this is enabled, you need to compile the TypeChain files with the TypeScript compiler before shipping to the registry. 80 | includeFactories: true, 81 | }, 82 | }; 83 | ``` 84 | 85 | ## Usage 86 | 87 | To use this plugin you need to decide which contracts you would like to be part of the package deployed to the registry. 88 | Refer to the [configuration](./README.md#configuration) section above. 89 | 90 | Then run this: 91 | 92 | ```sh 93 | yarn hardhat prepare-package 94 | ``` 95 | 96 | And go look what you got in the `artifacts` and the `typechain` directory. 97 | 98 | ### Tips 99 | 100 | - You may want to add the `/artifacts`, `/contracts` and `/typechain` globs to the 101 | [files](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files) field in your `package.json` file. 102 | - You may want to blocklist some files, such as test contracts. You can do this via an 103 | [.npmignore](https://docs.npmjs.com/cli/v7/using-npm/developers#keeping-files-out-of-your-package) file. 104 | - See how the plugin is integrated in [@hifi/protocol](https://github.com/hifi-finance/hifi/tree/main/packages/protocol), and how the artifacts and 105 | the bindings are used in [@hifi/deployers](https://github.com/hifi-finance/hifi/tree/main/packages/deployers). 106 | 107 | ## License 108 | 109 | [MIT](./LICENSE.md) © Paul Razvan Berg 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-packager", 3 | "description": "Hardhat plugin for preparing the contract artifacts and the TypeChain bindings for registry deployment", 4 | "version": "1.4.2", 5 | "author": { 6 | "name": "Paul Razvan Berg", 7 | "url": "https://github.com/paulrberg" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/paulrberg/hardhat-packager/issues" 11 | }, 12 | "dependencies": { 13 | "@typechain/hardhat": "^6.0.0", 14 | "fs-extra": "^10.0.1", 15 | "hardhat": "^2.9.2", 16 | "tempy": "1.0.1", 17 | "typechain": "^8.0.0" 18 | }, 19 | "devDependencies": { 20 | "@commitlint/cli": "^16.2.3", 21 | "@commitlint/config-conventional": "^16.2.1", 22 | "@ethersproject/abi": "^5.6.0", 23 | "@ethersproject/bytes": "^5.6.1", 24 | "@ethersproject/providers": "^5.6.2", 25 | "@istanbuljs/nyc-config-typescript": "^1.0.2", 26 | "@trivago/prettier-plugin-sort-imports": "^3.2.0", 27 | "@typechain/ethers-v5": "^10.0.0", 28 | "@types/fs-extra": "^9.0.13", 29 | "@types/mocha": "^9.1.0", 30 | "@types/node": "^17.0.23", 31 | "@typescript-eslint/eslint-plugin": "^5.17.0", 32 | "@typescript-eslint/parser": "^5.17.0", 33 | "commitizen": "^4.2.4", 34 | "cz-conventional-changelog": "^3.3.0", 35 | "earljs": "^0.2.3", 36 | "eslint": "^8.12.0", 37 | "eslint-config-prettier": "^8.5.0", 38 | "ethers": "^5.6.2", 39 | "husky": "^7.0.4", 40 | "lint-staged": "^12.3.7", 41 | "lodash": "^4.17.21", 42 | "mocha": "^9.2.2", 43 | "nyc": "^15.1.0", 44 | "pinst": "^3.0.0", 45 | "prettier": "^2.6.1", 46 | "shx": "^0.3.4", 47 | "source-map-support": "^0.5.21", 48 | "ts-node": "^10.7.0", 49 | "typescript": "^4.6.3" 50 | }, 51 | "files": [ 52 | "/dist/**/*.d.ts", 53 | "/dist/**/*.d.ts.map", 54 | "/dist/**/*.js", 55 | "/dist/**/*.js.map", 56 | "CHANGELOG.md" 57 | ], 58 | "homepage": "https://github.com/paulrberg/hardhat-packager#readme", 59 | "keywords": [ 60 | "bindings", 61 | "blockchain", 62 | "ethereum", 63 | "hardhat", 64 | "hardhat-plugin", 65 | "smart-contracts", 66 | "typechain", 67 | "typescript" 68 | ], 69 | "license": "MIT", 70 | "main": "./dist/index.js", 71 | "packageManager": "yarn@3.2.2", 72 | "peerDependencies": { 73 | "@typechain/hardhat": "6.x", 74 | "hardhat": "2.x", 75 | "lodash": "4.x", 76 | "typechain": "8.x" 77 | }, 78 | "publishConfig": { 79 | "access": "public" 80 | }, 81 | "repository": { 82 | "type": "git", 83 | "url": "https://github.com/paulrberg/hardhat-packager" 84 | }, 85 | "scripts": { 86 | "build": "tsc --project \"./tsconfig.prod.json\"", 87 | "clean": "shx rm -rf ./.nyc_output ./coverage ./dist ./coverage.json ./tsconfig.prod.tsbuildinfo", 88 | "coverage": "yarn nyc --nycrc-path ./.nycrc.yml mocha", 89 | "lint": "yarn lint:ts && yarn prettier:check && yarn typecheck", 90 | "lint:ts": "eslint --config \"./.eslintrc.yml\" --ignore-path \"./.eslintignore\" --ext .js,.ts .", 91 | "postinstall": "husky install", 92 | "postpublish": "pinst --enable", 93 | "prepack": "yarn build", 94 | "prepublishOnly": "pinst --disable", 95 | "prettier": "prettier --config \"./.prettierrc.yml\" --ignore-path \"./.prettierignore\" --write \"**/*.{js,json,md,ts,yml}\"", 96 | "prettier:check": "prettier --check --config \"./.prettierrc.yml\" --ignore-path \"./.prettierignore\" \"**/*.{js,json,md,ts,yml}\"", 97 | "test": "mocha --config \"./.mocharc.yml\"", 98 | "typecheck": "tsc --incremental false --noEmit" 99 | }, 100 | "types": "./dist/index.d.ts" 101 | } 102 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const PLUGIN_NAME = "hardhat-packager"; 2 | export const SUBTASK_PREPARE_PACKAGE_ARTIFACTS: string = "prepare-package:artifacts"; 3 | export const SUBTASK_PREPARE_PACKAGE_TYPECHAIN: string = "prepare-package:typechain"; 4 | export const SUBTASK_PREPARE_PACKAGE_TYPECHAIN_FACTORIES: string = "prepare-package:typechain:factories"; 5 | export const TASK_PREPARE_PACKAGE: string = "prepare-package"; 6 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import fsExtra from "fs-extra"; 2 | import { resolve } from "path"; 3 | 4 | /** 5 | * Recurses over all the bindings factories generated by TypeChain on a depth-first basis. 6 | * Re-evaluates the the status of the directories after performing the search to check whether 7 | * the directory has become empty in the meantime. 8 | */ 9 | export async function* getEmptyDirectoriesRecursively(directory: string): AsyncIterable { 10 | const initialEntries = await fsExtra.readdir(directory, { withFileTypes: true }); 11 | for (const initialEntry of initialEntries) { 12 | const result = resolve(directory, initialEntry.name); 13 | if (initialEntry.isDirectory()) { 14 | yield* getEmptyDirectoriesRecursively(result); 15 | } 16 | } 17 | // Needed to cover the case when a file is nested in two or more directories. 18 | const updatedEntries = await fsExtra.readdir(directory, { withFileTypes: true }); 19 | if (updatedEntries.length == 0) { 20 | yield directory; 21 | } 22 | } 23 | 24 | /** 25 | * Recurses over all the bindings generated by TypeChain, but excludes the "factories" directory. 26 | */ 27 | export async function* getFilesRecursively(directory: string): AsyncIterable { 28 | const entries = await fsExtra.readdir(directory, { withFileTypes: true }); 29 | for (const entry of entries) { 30 | const result = resolve(directory, entry.name); 31 | if (entry.isDirectory()) { 32 | // Do not recurse into the "factories" directory 33 | if (entry.name === "factories") { 34 | continue; 35 | } 36 | yield* getFilesRecursively(result); 37 | } else { 38 | yield result; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { TASK_TYPECHAIN } from "@typechain/hardhat/dist/constants"; 2 | import fsExtra from "fs-extra"; 3 | import { extendConfig, subtask, task } from "hardhat/config"; 4 | import { HardhatPluginError } from "hardhat/plugins"; 5 | import { Artifact, HardhatConfig, HardhatUserConfig, TaskArguments } from "hardhat/types"; 6 | import { basename, extname, join, parse } from "path"; 7 | import tempy from "tempy"; 8 | 9 | import { 10 | PLUGIN_NAME, 11 | SUBTASK_PREPARE_PACKAGE_ARTIFACTS, 12 | SUBTASK_PREPARE_PACKAGE_TYPECHAIN, 13 | SUBTASK_PREPARE_PACKAGE_TYPECHAIN_FACTORIES, 14 | TASK_PREPARE_PACKAGE, 15 | } from "./constants"; 16 | import { getEmptyDirectoriesRecursively, getFilesRecursively } from "./helpers"; 17 | import "./type-extensions"; 18 | import type { PackagerConfig } from "./types"; 19 | 20 | extendConfig(function (config: HardhatConfig, userConfig: Readonly) { 21 | const defaultPackagerConfig: PackagerConfig = { 22 | contracts: [], 23 | includeFactories: false, 24 | }; 25 | 26 | config.packager = { 27 | ...defaultPackagerConfig, 28 | ...userConfig.packager, 29 | }; 30 | }); 31 | 32 | subtask(SUBTASK_PREPARE_PACKAGE_ARTIFACTS).setAction(async function (_taskArgs: TaskArguments, { artifacts, config }) { 33 | if (!fsExtra.existsSync(config.paths.artifacts)) { 34 | throw new HardhatPluginError(PLUGIN_NAME, "Please generate the contract artifacts before running this plugin"); 35 | } 36 | 37 | // Aggregate the chosen contracts in a temporary directory. 38 | const temporaryPathToArtifacts: string = tempy.directory(); 39 | for (const contract of config.packager.contracts) { 40 | const artifact: Artifact = await artifacts.readArtifact(contract); 41 | await fsExtra.writeJson(join(temporaryPathToArtifacts, contract + ".json"), artifact, { spaces: 2 }); 42 | } 43 | 44 | // Replace the previous artifacts directory. 45 | await fsExtra.remove(config.paths.artifacts); 46 | await fsExtra.move(temporaryPathToArtifacts, config.paths.artifacts); 47 | }); 48 | 49 | subtask(SUBTASK_PREPARE_PACKAGE_TYPECHAIN).setAction(async function (_taskArgs: TaskArguments, { config }) { 50 | const pathToBindings: string = join(config.paths.root, config.typechain.outDir); 51 | if (!fsExtra.existsSync(pathToBindings)) { 52 | throw new HardhatPluginError(PLUGIN_NAME, "Please generate the TypeChain bindings before running this plugin"); 53 | } 54 | 55 | // TypeChain generates some files that are shared across all bindings. 56 | const excludedFiles: string[] = ["common"]; 57 | 58 | // Delete all bindings that have not been allowlisted. 59 | for await (const pathToBinding of getFilesRecursively(pathToBindings)) { 60 | const fileName: string = basename(pathToBinding, extname(pathToBinding)); 61 | if (excludedFiles.includes(fileName)) { 62 | continue; 63 | } 64 | if (!config.packager.contracts.includes(fileName)) { 65 | await fsExtra.remove(pathToBinding); 66 | } 67 | } 68 | 69 | // Delete any remaining empty directories. 70 | for await (const pathToEmptyBindingDirectory of getEmptyDirectoriesRecursively(pathToBindings)) { 71 | await fsExtra.remove(pathToEmptyBindingDirectory); 72 | } 73 | }); 74 | 75 | subtask(SUBTASK_PREPARE_PACKAGE_TYPECHAIN_FACTORIES).setAction(async function (_taskArgs: TaskArguments, { config }) { 76 | const pathToBindingsFactories: string = join(config.paths.root, config.typechain.outDir, "factories"); 77 | if (!fsExtra.existsSync(pathToBindingsFactories)) { 78 | throw new HardhatPluginError( 79 | PLUGIN_NAME, 80 | "Please generate the TypeChain bindings factories before running this plugin", 81 | ); 82 | } 83 | 84 | for await (const pathToBindingFactory of getFilesRecursively(pathToBindingsFactories)) { 85 | const contract: string = parse(pathToBindingFactory).name.replace("__factory", ""); 86 | if (!config.packager.contracts.includes(contract)) { 87 | await fsExtra.remove(pathToBindingFactory); 88 | } 89 | } 90 | 91 | // Delete any remaining empty directories. 92 | for await (const pathToEmptyBindingFactoryDirectory of getEmptyDirectoriesRecursively(pathToBindingsFactories)) { 93 | await fsExtra.remove(pathToEmptyBindingFactoryDirectory); 94 | } 95 | }); 96 | 97 | task( 98 | TASK_PREPARE_PACKAGE, 99 | "Prepares the contract artifacts and the TypeChain bindings for registry deployment", 100 | ).setAction(async function (_taskArgs: TaskArguments, { config, run }): Promise { 101 | if (config.packager.contracts.length === 0) { 102 | console.log(`No contracts to prepare. List them in the "packager" field of your Hardhat config file.`); 103 | return; 104 | } 105 | 106 | // Run the TypeChain task first. This runs the "compile" task internally, so the contract artifacts are generated too. 107 | await run(TASK_TYPECHAIN); 108 | 109 | // Let the user know that the package is being prepared. 110 | console.log(`Preparing ${config.packager.contracts.length} contracts ...`); 111 | 112 | // Prepare the contract artifacts. 113 | await run(SUBTASK_PREPARE_PACKAGE_ARTIFACTS); 114 | 115 | // Prepare the TypeChain bindings factories if the user decided to include them. 116 | // This task is run first so we don't have to parse the factories twice. 117 | if (config.packager.includeFactories) { 118 | await run(SUBTASK_PREPARE_PACKAGE_TYPECHAIN_FACTORIES); 119 | } 120 | // Or otherwise remove them. 121 | else { 122 | const pathToBindingsFactories: string = join(config.paths.root, config.typechain.outDir, "factories"); 123 | await fsExtra.remove(pathToBindingsFactories); 124 | } 125 | 126 | // Prepare the TypeChain bindings. 127 | await run(SUBTASK_PREPARE_PACKAGE_TYPECHAIN); 128 | 129 | // Let the user know that the package has been prepared successfully. 130 | console.log(`Successfully prepared ${config.packager.contracts.length} contracts for registry deployment!`); 131 | }); 132 | -------------------------------------------------------------------------------- /src/type-extensions.ts: -------------------------------------------------------------------------------- 1 | import "@typechain/hardhat"; 2 | import "hardhat/types/config"; 3 | 4 | import { PackagerConfig, PackagerUserConfig } from "./types"; 5 | 6 | declare module "hardhat/types/config" { 7 | interface HardhatUserConfig { 8 | packager?: PackagerUserConfig; 9 | } 10 | 11 | interface HardhatConfig { 12 | packager: PackagerConfig; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface PackagerUserConfig { 2 | contracts?: string[]; 3 | includeFactories?: boolean; 4 | } 5 | 6 | export interface PackagerConfig { 7 | contracts: string[]; 8 | includeFactories: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture-project/contracts/A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.0; 3 | 4 | import "./B.sol"; 5 | 6 | contract A is B { 7 | function a() external pure returns (uint256) { 8 | return 1; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixture-project/contracts/B.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.0; 3 | 4 | import "./lib/C.sol"; 5 | 6 | contract B is C { 7 | function b() external pure returns (uint256) { 8 | return 1; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixture-project/contracts/lib/C.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.0; 3 | 4 | contract C { 5 | function c() external pure returns (uint256) { 6 | return 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixture-project/contracts/test/foo/D.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.8.0; 3 | 4 | contract D { 5 | function d() external pure returns (uint256) { 6 | return 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixture-project/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | // We load the plugin here. 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | 4 | import "../../src"; 5 | 6 | const config: HardhatUserConfig = { 7 | defaultNetwork: "hardhat", 8 | solidity: "0.8.13", 9 | typechain: { 10 | outDir: "typechain", 11 | target: "ethers-v5", 12 | }, 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | import { resetHardhatContext } from "hardhat/plugins-testing"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import path from "path"; 4 | 5 | declare module "mocha" { 6 | interface Context { 7 | hre: HardhatRuntimeEnvironment; 8 | } 9 | } 10 | 11 | export function useHardhatEnvironment(): void { 12 | beforeEach("Loading Hardhat environment", async function () { 13 | process.chdir(path.join(__dirname, "fixture-project")); 14 | this.hre = await import("hardhat"); 15 | }); 16 | 17 | afterEach("Resetting Hardhat", function () { 18 | resetHardhatContext(); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/packager.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function, no-empty-pattern */ 2 | import { TASK_TYPECHAIN } from "@typechain/hardhat/dist/constants"; 3 | import { Mock, expect, mockFn } from "earljs"; 4 | import fsExtra from "fs-extra"; 5 | import { TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 6 | import { task } from "hardhat/config"; 7 | import { TaskArguments } from "hardhat/types"; 8 | import path from "path"; 9 | 10 | import { TASK_PREPARE_PACKAGE } from "../src/constants"; 11 | import { useHardhatEnvironment } from "./helpers"; 12 | 13 | const pathToArtifacts: string = path.join(__dirname, "fixture-project/artifacts"); 14 | const pathToBindings: string = path.join(__dirname, "fixture-project/typechain"); 15 | const pathToBindingsFactories: string = path.join(__dirname, "fixture-project/typechain/factories"); 16 | const pathToCommonFile: string = path.join(__dirname, "fixture-project/typechain/common.ts"); 17 | 18 | describe("Hardhat Packager", function () { 19 | useHardhatEnvironment(); 20 | 21 | let originalConsoleLog: any; 22 | let consoleLogMock: Mock; 23 | 24 | beforeEach(async function () { 25 | await this.hre.run("clean"); 26 | consoleLogMock = mockFn().returns(undefined); 27 | originalConsoleLog = console.log; 28 | console.log = consoleLogMock; 29 | }); 30 | 31 | context("when the list of contracts to prepare is empty", function () { 32 | beforeEach(function () { 33 | this.hre.config.packager.contracts = []; 34 | }); 35 | 36 | it("does nothing", async function () { 37 | await this.hre.run(TASK_PREPARE_PACKAGE); 38 | expect(consoleLogMock).toHaveBeenCalledWith([ 39 | 'No contracts to prepare. List them in the "packager" field of your Hardhat config file.', 40 | ]); 41 | }); 42 | }); 43 | 44 | context("when the list of contracts to prepare is not empty", function () { 45 | beforeEach(function () { 46 | this.hre.config.packager.contracts = ["A", "C"]; 47 | }); 48 | 49 | context("when the contract artifacts do not exist", function () { 50 | beforeEach(async function () { 51 | task(TASK_TYPECHAIN, "Disable the TypeChain task").setAction(async function (): Promise {}); 52 | }); 53 | 54 | it("throws an error", async function () { 55 | await expect(this.hre.run(TASK_PREPARE_PACKAGE)).toBeRejected( 56 | "Please generate the contract artifacts before running this plugin", 57 | ); 58 | }); 59 | }); 60 | 61 | context("when the contract artifacts exist", function () { 62 | context("when the TypeChain bindings do not exist", function () { 63 | beforeEach(async function () { 64 | task(TASK_COMPILE, "Override the Hardhat compile subtask").setAction(async function ( 65 | taskArguments: TaskArguments, 66 | {}, 67 | runSuper, 68 | ) { 69 | await runSuper({ ...taskArguments, noTypechain: true }); 70 | }); 71 | }); 72 | 73 | it("throws an error", async function () { 74 | await expect(this.hre.run(TASK_PREPARE_PACKAGE)).toBeRejected( 75 | "Please generate the TypeChain bindings before running this plugin", 76 | ); 77 | }); 78 | }); 79 | 80 | context("when the TypeChain bindings exist", function () { 81 | context("when the user decided to not include the TypeChain bindings factories", async function () { 82 | beforeEach(async function () { 83 | this.hre.config.packager.includeFactories = false; 84 | }); 85 | 86 | it("prepares the contracts artifacts and the TypeChain bindings for registry deployment", async function () { 87 | await this.hre.run(TASK_PREPARE_PACKAGE); 88 | 89 | expect(fsExtra.existsSync(pathToArtifacts)).toEqual(true); 90 | expect(fsExtra.existsSync(pathToCommonFile)).toEqual(true); 91 | 92 | // Bindings 93 | expect(fsExtra.existsSync(pathToBindings)).toEqual(true); 94 | expect(fsExtra.existsSync(path.join(pathToBindings, "A.ts"))).toEqual(true); 95 | expect(fsExtra.existsSync(path.join(pathToBindings, "lib", "C.ts"))).toEqual(true); 96 | expect(fsExtra.existsSync(path.join(pathToBindings, "test"))).toEqual(false); 97 | 98 | // Factories 99 | expect(fsExtra.existsSync(pathToBindingsFactories)).toEqual(false); 100 | 101 | expect(consoleLogMock).toHaveBeenCalledWith(["Preparing 2 contracts ..."]); 102 | expect(consoleLogMock).toHaveBeenCalledWith(["Successfully prepared 2 contracts for registry deployment!"]); 103 | }); 104 | }); 105 | 106 | context("when the user decided to include the TypeChain bindings factories", async function () { 107 | beforeEach(async function () { 108 | this.hre.config.packager.includeFactories = true; 109 | }); 110 | 111 | context("when the TypeChain bindings factories do not exist", function () { 112 | beforeEach(async function () { 113 | task(TASK_COMPILE, "Override the Hardhat compile subtask").setAction(async function ( 114 | taskArguments: TaskArguments, 115 | {}, 116 | runSuper, 117 | ) { 118 | await runSuper({ ...taskArguments, noTypechain: true }); 119 | }); 120 | }); 121 | 122 | it("throws an error", async function () { 123 | await fsExtra.ensureDir(pathToBindings); 124 | await expect(this.hre.run(TASK_PREPARE_PACKAGE)).toBeRejected( 125 | "Please generate the TypeChain bindings factories before running this plugin", 126 | ); 127 | }); 128 | }); 129 | 130 | context("when the TypeChain bindings factories exist", function () { 131 | it("prepares the contracts artifacts and the TypeChain bindings for registry deployment", async function () { 132 | await this.hre.run(TASK_PREPARE_PACKAGE); 133 | 134 | expect(fsExtra.existsSync(pathToArtifacts)).toEqual(true); 135 | expect(fsExtra.existsSync(pathToCommonFile)).toEqual(true); 136 | 137 | // Bindings 138 | expect(fsExtra.existsSync(pathToBindings)).toEqual(true); 139 | expect(fsExtra.existsSync(path.join(pathToBindings, "A.ts"))).toEqual(true); 140 | expect(fsExtra.existsSync(path.join(pathToBindings, "lib", "C.ts"))).toEqual(true); 141 | expect(fsExtra.existsSync(path.join(pathToBindings, "test"))).toEqual(false); 142 | 143 | // Factories 144 | expect(fsExtra.existsSync(pathToBindingsFactories)).toEqual(true); 145 | expect(fsExtra.existsSync(path.join(pathToBindingsFactories, "A__factory.ts"))).toEqual(true); 146 | expect(fsExtra.existsSync(path.join(pathToBindingsFactories, "lib", "C__factory.ts"))).toEqual(true); 147 | expect(fsExtra.existsSync(path.join(pathToBindingsFactories, "test"))).toEqual(false); 148 | 149 | expect(consoleLogMock).toHaveBeenCalledWith(["Preparing 2 contracts ..."]); 150 | expect(consoleLogMock).toHaveBeenCalledWith([ 151 | "Successfully prepared 2 contracts for registry deployment!", 152 | ]); 153 | }); 154 | }); 155 | }); 156 | }); 157 | }); 158 | }); 159 | 160 | afterEach(() => { 161 | console.log = originalConsoleLog; 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["es6"], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "skipLibCheck": false, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es5" 17 | }, 18 | "exclude": ["**/node_modules", "**/typechain"], 19 | "include": ["src/**/*", "test/**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*"] 4 | } 5 | --------------------------------------------------------------------------------