diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf8ab8f..aa416a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - NodeJS minimum version changed in order to reflect the `o1js` requirements. [#641](https://github.com/o1-labs/zkapp-cli/pull/641) +### Changed + +- Improved `SmartContract` classes inheritance lookup used during the zkApps deployment procedure. [#640](https://github.com/o1-labs/zkapp-cli/pull/640) + ## [0.20.2](https://github.com/o1-labs/zkapp-cli/compare/0.20.1...0.20.2) - 2024-05-07 ### Fixed diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..935f7910 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +export default { + testMatch: ['/src/**/*.test.js'], +}; diff --git a/package-lock.json b/package-lock.json index cf6c0636..177afdee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,22 +9,24 @@ "version": "0.20.3", "license": "Apache-2.0", "dependencies": { + "acorn": "^8.11.3", + "acorn-walk": "^8.3.2", "chalk": "^5.3.0", "debug": "^4.3.4", "decompress": "^4.2.1", "enquirer": "^2.4.1", - "envinfo": "^7.11.1", + "envinfo": "^7.13.0", "fast-glob": "^3.3.2", "find-npm-prefix": "^1.0.2", "fs-extra": "^11.2.0", "gittar": "^0.1.1", - "mina-signer": "^3.0.4", + "mina-signer": "^3.0.7", "o1js": "^1.*", "opener": "^1.5.2", "ora": "^8.0.1", - "semver": "^7.6.0", + "semver": "^7.6.2", "shelljs": "^0.8.5", - "table": "^6.8.1", + "table": "^6.8.2", "yargs": "^17.7.2" }, "bin": { @@ -33,25 +35,24 @@ "zkapp-cli": "src/bin/index.js" }, "devDependencies": { - "@playwright/test": "^1.42.1", + "@playwright/test": "^1.44.0", "@shimkiv/cli-testing-library": "^0.1.7", "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.12", - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-plugin-o1js": "^0.4.0", - "execa": "^8.0.1", "husky": "^9.0.11", "jest": "^29.7.0", "lint-staged": "^15.2.2", "portfinder": "^1.0.32", "prettier": "^3.2.5", - "rimraf": "^5.0.5", + "rimraf": "^5.0.6", "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "zx": "^7.2.3" + "typescript": "^5.4.5", + "zx": "^8.0.2" }, "engines": { "node": ">=18.14.0" @@ -1616,12 +1617,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.0.tgz", - "integrity": "sha512-Ebw0+MCqoYflop7wVKj711ccbNlrwTBCtjY5rlbiY9kHL2bCYxq+qltK6uPsVBGGAOb033H2VO0YobcQVxoW7Q==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.0.tgz", + "integrity": "sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==", "dev": true, "dependencies": { - "playwright": "1.43.0" + "playwright": "1.44.0" }, "bin": { "playwright": "cli.js" @@ -1796,12 +1797,6 @@ "@types/node": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true - }, "node_modules/@types/node": { "version": "20.12.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz", @@ -1811,12 +1806,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/ps-tree": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@types/ps-tree/-/ps-tree-1.1.6.tgz", - "integrity": "sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -1829,12 +1818,6 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "node_modules/@types/which": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz", - "integrity": "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==", - "dev": true - }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -1851,16 +1834,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz", - "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/type-utils": "7.6.0", - "@typescript-eslint/utils": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.3.1", @@ -1886,15 +1869,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz", - "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/typescript-estree": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4" }, "engines": { @@ -1914,13 +1897,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz", - "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0" + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1931,13 +1914,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz", - "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.6.0", - "@typescript-eslint/utils": "7.6.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1958,9 +1941,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz", - "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1971,13 +1954,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz", - "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/visitor-keys": "7.6.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1999,17 +1982,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz", - "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.15", "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.6.0", - "@typescript-eslint/types": "7.6.0", - "@typescript-eslint/typescript-estree": "7.6.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", "semver": "^7.6.0" }, "engines": { @@ -2024,12 +2007,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz", - "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.6.0", + "@typescript-eslint/types": "7.8.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2050,7 +2033,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -2071,7 +2053,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -2906,15 +2887,6 @@ "node": ">= 8" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3100,12 +3072,6 @@ "node": ">=6.0.0" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3156,9 +3122,9 @@ } }, "node_modules/envinfo": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz", - "integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", "bin": { "envinfo": "dist/cli.js" }, @@ -3427,21 +3393,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -3588,29 +3539,6 @@ "pend": "~1.2.0" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3714,24 +3642,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3785,15 +3695,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fx": { - "version": "34.0.0", - "resolved": "https://registry.npmjs.org/fx/-/fx-34.0.0.tgz", - "integrity": "sha512-/fZih3/WLsrtlaj2mahjWxAmyuikmcl3D5kKPqLtFmEilLsy9wp0+/vEmfvYXXhwJc+ajtCFDCf+yttXmPMHSQ==", - "dev": true, - "bin": { - "fx": "index.js" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6080,12 +5981,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6125,9 +6020,9 @@ } }, "node_modules/mina-signer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/mina-signer/-/mina-signer-3.0.5.tgz", - "integrity": "sha512-H/Dqp8UZom0U9CXqB68hmH2GZDz1PyGL9eWV8FkJ8zxLNwdmtk9/TJ2RKR/R7OfmfvY5W+F7wKJ8WgzeFjt+Eg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/mina-signer/-/mina-signer-3.0.7.tgz", + "integrity": "sha512-7eYp/6WWj2VzJjvfC8dNeGMud/brdBrzkUsCdysFFXnfV2/FVpVhAGCMfaS6hs0HJtS4+eplmiD2hXfshQS8CQ==", "dependencies": { "blakejs": "^1.2.1", "js-sha256": "^0.9.0" @@ -6195,43 +6090,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6545,15 +6403,6 @@ "node": ">=8" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dev": true, - "dependencies": { - "through": "~2.3" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -6689,12 +6538,12 @@ } }, "node_modules/playwright": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.0.tgz", - "integrity": "sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.0.tgz", + "integrity": "sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==", "dev": true, "dependencies": { - "playwright-core": "1.43.0" + "playwright-core": "1.44.0" }, "bin": { "playwright": "cli.js" @@ -6707,9 +6556,9 @@ } }, "node_modules/playwright-core": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.0.tgz", - "integrity": "sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.0.tgz", + "integrity": "sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -6811,21 +6660,6 @@ "node": ">= 6" } }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7039,9 +6873,9 @@ "dev": true }, "node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.6.tgz", + "integrity": "sha512-X72SgyOf+1lFnGM6gYcmZ4+jMOwuT4E4SajKQzUIlI7EoR5eFHMhS/wf8Ll0mN+w2bxcIVldrJQ6xT7HFQywjg==", "dev": true, "dependencies": { "glob": "^10.3.7" @@ -7146,12 +6980,9 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -7159,22 +6990,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7286,18 +7101,6 @@ "source-map": "^0.6.0" } }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7336,15 +7139,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -7857,9 +7651,9 @@ } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -7964,29 +7758,11 @@ "makeerror": "1.0.12" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/webpod": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/webpod/-/webpod-0.0.2.tgz", - "integrity": "sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==", - "dev": true, - "bin": { - "webpod": "dist/index.js" - } - }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", @@ -8288,87 +8064,19 @@ } }, "node_modules/zx": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/zx/-/zx-7.2.3.tgz", - "integrity": "sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==", - "dev": true, - "dependencies": { - "@types/fs-extra": "^11.0.1", - "@types/minimist": "^1.2.2", - "@types/node": "^18.16.3", - "@types/ps-tree": "^1.1.2", - "@types/which": "^3.0.0", - "chalk": "^5.2.0", - "fs-extra": "^11.1.1", - "fx": "*", - "globby": "^13.1.4", - "minimist": "^1.2.8", - "node-fetch": "3.3.1", - "ps-tree": "^1.2.0", - "webpod": "^0", - "which": "^3.0.0", - "yaml": "^2.2.2" - }, + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/zx/-/zx-8.0.2.tgz", + "integrity": "sha512-3g+ePtPYmyrjRuASlJiUhkje1je4a47woML/fzTKBb9PA5BzRQbSswwyJ8nlFWJjA1ORRi6TMyAdhuz/jK+Gaw==", + "dev": true, "bin": { "zx": "build/cli.js" }, "engines": { "node": ">= 16.0.0" - } - }, - "node_modules/zx/node_modules/@types/node": { - "version": "18.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", - "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/zx/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zx/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zx/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "optionalDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": ">=20.12.5" } } } diff --git a/package.json b/package.json index 0ead3cf2..72badfd8 100644 --- a/package.json +++ b/package.json @@ -49,50 +49,46 @@ "prettier --write --ignore-unknown" ] }, - "jest": { - "testMatch": [ - "/src/**/*.test.js" - ] - }, "dependencies": { + "acorn": "^8.11.3", + "acorn-walk": "^8.3.2", "chalk": "^5.3.0", "debug": "^4.3.4", "decompress": "^4.2.1", "enquirer": "^2.4.1", - "envinfo": "^7.11.1", + "envinfo": "^7.13.0", "fast-glob": "^3.3.2", "find-npm-prefix": "^1.0.2", "fs-extra": "^11.2.0", "gittar": "^0.1.1", - "mina-signer": "^3.0.4", + "mina-signer": "^3.0.7", "o1js": "^1.*", "opener": "^1.5.2", "ora": "^8.0.1", - "semver": "^7.6.0", + "semver": "^7.6.2", "shelljs": "^0.8.5", - "table": "^6.8.1", + "table": "^6.8.2", "yargs": "^17.7.2" }, "devDependencies": { - "@playwright/test": "^1.42.1", + "@playwright/test": "^1.44.0", "@shimkiv/cli-testing-library": "^0.1.7", "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.12", - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "cross-env": "^7.0.3", "eslint": "^8.57.0", "eslint-plugin-o1js": "^0.4.0", - "execa": "^8.0.1", "husky": "^9.0.11", "jest": "^29.7.0", "lint-staged": "^15.2.2", "portfinder": "^1.0.32", "prettier": "^3.2.5", - "rimraf": "^5.0.5", + "rimraf": "^5.0.6", "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "zx": "^7.2.3" + "typescript": "^5.4.5", + "zx": "^8.0.2" }, "engines": { "node": ">=18.14.0" diff --git a/src/lib/deploy.js b/src/lib/deploy.js index 21aa6e5c..acc7b9d6 100644 --- a/src/lib/deploy.js +++ b/src/lib/deploy.js @@ -8,7 +8,9 @@ import path from 'path'; import { getBorderCharacters, table } from 'table'; import util from 'util'; import { readDeployAliasesConfig } from './config.js'; -import step from './helpers.js'; +import step, { + findIfClassExtendsOrImplementsSmartContract, +} from './helpers.js'; const log = console.log; const DEFAULT_NETWORK_ID = 'testnet'; @@ -194,12 +196,9 @@ export async function deploy({ alias, yes }) { } // Find the users file to import the smart contract from - let smartContractFile = await findSmartContractToDeploy( - `${projectRoot}/build/**/*.js`, - contractName - ); - - let smartContractImportPath = `${projectRoot}/build/src/${smartContractFile}`; + let smartContractImportPath = build.smartContracts.find( + (contract) => contract.className === contractName + ).filePath; if (process.platform === 'win32') { smartContractImportPath = 'file://' + smartContractImportPath; } @@ -454,7 +453,7 @@ async function getContractName(config, build, alias) { const contractNameResponse = await enquirer.prompt({ type: 'select', name: 'contractName', - choices: build.smartContracts, + choices: build.smartContracts.map((contract) => contract.className), message: (state) => { // Makes the step text green upon success, else uses reset. const style = @@ -485,7 +484,7 @@ async function getContractName(config, build, alias) { ); } else { log( - ` Only one smart contract exists in the project: ${build.smartContracts[0]}` + ` Only one smart contract exists in the project: ${build.smartContracts[0].className}` ); } } @@ -662,7 +661,7 @@ function hasBreakingChanges(installedVersion, latestVersion) { * Find the user-specified class names for every instance of `SmartContract` * in the build dir. * @param {string} path The glob pattern--e.g. `build/**\/*.js` - * @returns {Promise} The user-specified class names--e.g. ['Foo', 'Bar'] + * @returns {Promise} The user-specified names of the classes that extend or implement o1js `SmartContract`, e.g. ['Foo', 'Bar'] */ export async function findSmartContracts(path) { @@ -670,17 +669,15 @@ export async function findSmartContracts(path) { path = path.replaceAll('\\', '/'); } const files = await glob(path); - let smartContracts = []; + const smartContracts = []; for (const file of files) { - const str = fs.readFileSync(file, 'utf-8'); - // TODO: Implement better SmartContract classes lookup. - // https://github.com/o1-labs/zkapp-cli/issues/636 - let results = str.matchAll(/class (\w+) (extends|implements) (\w+)/gi); - results = Array.from(results) ?? []; // prevent error if no results - results = results.map((result) => result[1]); // only keep first capture group, the class name - smartContracts.push(...results); + const result = findIfClassExtendsOrImplementsSmartContract(file); + if (result) { + smartContracts.push(...result); + } } + return smartContracts; } @@ -699,7 +696,7 @@ export function chooseSmartContract(config, deploy, deployAliasName) { // If only one smart contract exists in the build, use it. if (deploy.smartContracts.length === 1) { - return deploy.smartContracts[0]; + return deploy.smartContracts[0].className; } // If 2+ smartContract classes exist in build.json, return falsy. @@ -782,25 +779,6 @@ async function getZkProgram(projectRoot, zkProgramNameArg) { return zkProgram; } -/** - * Find the file name of the smart contract to be deployed. - * @param {string} buildPath The glob pattern--e.g. `build/**\/*.js` - * @param {string} contractName The user-specified contract name to deploy. - * @returns {Promise} The file name of the user-specified smart contract. - */ -async function findSmartContractToDeploy(buildPath, contractName) { - if (process.platform === 'win32') { - buildPath = buildPath.replaceAll('\\', '/'); - } - const files = await glob(buildPath); - const re = new RegExp(`class ${contractName} extends SmartContract`, 'gi'); - for (const file of files) { - const contract = fs.readFileSync(file, 'utf-8'); - if (re.test(contract)) { - return path.basename(file); - } - } -} async function sendGraphQL(graphQLUrl, query) { const controller = new AbortController(); diff --git a/src/lib/deploy.test.js b/src/lib/deploy.test.js index c2717636..47856ebd 100644 --- a/src/lib/deploy.test.js +++ b/src/lib/deploy.test.js @@ -1,9 +1,61 @@ +import { jest } from '@jest/globals'; +import fs from 'node:fs'; import { chooseSmartContract } from './deploy.js'; +import { findIfClassExtendsOrImplementsSmartContract } from './helpers.js'; -describe('deploy.js', () => { - describe('findSmartContracts()', () => { - it.skip('should be tested', () => {}); +beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(fs, 'readFileSync').mockImplementation((path) => { + if (path.includes('package.json')) { + return JSON.stringify({ main: 'index.js' }); + } else if (path.endsWith('TestZkApp.js')) { + return ` + import { SmartContract } from 'o1js'; + export class TestZkApp extends SmartContract {} + `; + } else if (path.endsWith('MultipleInheritance.js')) { + return ` + import { SmartContract } from 'o1js'; + export class TestZkApp extends SmartContract {} + export class ZkAppWithSecondInheritanceLevel extends TestZkApp {} + export class ZkAppWithThirdInheritanceLevel extends ZkAppWithSecondInheritanceLevel {} + `; + } else if (path.endsWith('ZkAppWithSecondInheritanceLevel.js')) { + return ` + import { TestZkApp } from './TestZkApp.js'; + export class ZkAppWithSecondInheritanceLevel extends TestZkApp {} + `; + } else if (path.endsWith('ZkAppWithThirdInheritanceLevel.js')) { + return ` + import { ZkAppWithSecondInheritanceLevel } from './ZkAppWithSecondInheritanceLevel.js'; + export class ZkAppWithThirdInheritanceLevel extends ZkAppWithSecondInheritanceLevel {} + `; + } else if (path.endsWith('SomeLibOnFileSystem.js')) { + return ` + export class SmartContract {} + `; + } else if (path.endsWith('NotO1jsSmartContractLib.js')) { + return ` + import { SmartContract } from 'whatever'; + export class NotO1jsSmartContract extends SmartContract {} + `; + } else if (path.endsWith('NotO1jsSmartContractFs.js')) { + return ` + import { SmartContract } from './SomeLibOnFileSystem.js'; + export class NotO1jsSmartContract extends SmartContract {} + `; + } + return ''; }); + jest.spyOn(fs, 'existsSync').mockImplementation(() => true); + jest.spyOn(fs, 'readdirSync').mockImplementation(() => []); +}); + +afterAll(() => { + jest.restoreAllMocks(); +}); + +describe('deploy.js', () => { describe('chooseSmartContract()', () => { describe('if the network in config.json has a smartContract specified', () => { it('should select that smart contract', () => { @@ -22,6 +74,7 @@ describe('deploy.js', () => { expect(result).toEqual('Foo'); }); }); + describe('if the network in config.json does NOT have a smartContract specified', () => { const config = { version: 1, @@ -29,12 +82,11 @@ describe('deploy.js', () => { mainnet: {}, }, }; - const deployAliasName = 'mainnet'; describe('if only one smart contract exists in the build (deploy.json)', () => { it('should select that smart contract', () => { const deploy = { - smartContracts: ['Bar'], + smartContracts: [{ className: 'Bar', filePath: './Bar.js' }], }; const result = chooseSmartContract(config, deploy, deployAliasName); expect(result).toEqual('Bar'); @@ -43,7 +95,10 @@ describe('deploy.js', () => { describe('if 2+ smart contract exists in the build (deploy.json)', () => { it('should select that smart contract', () => { const deploy = { - smartContracts: ['Foo', 'Bar'], + smartContracts: [ + { className: 'Foo', filePath: './Foo.js' }, + { className: 'Baz', filePath: './Baz.js' }, + ], }; const deployAliasName = 'mainnet'; const result = chooseSmartContract(config, deploy, deployAliasName); @@ -52,4 +107,91 @@ describe('deploy.js', () => { }); }); }); + + describe('SmartContract inheritance detection', () => { + it('should identify classes extending SmartContract from o1js directly', async () => { + fs.readdirSync.mockReturnValue(['TestZkApp.js']); + const result = findIfClassExtendsOrImplementsSmartContract( + './build/src/TestZkApp.js' + ); + expect(result).toEqual([ + { className: 'TestZkApp', filePath: './build/src/TestZkApp.js' }, + ]); + }); + + it('should identify classes extending another class that extends SmartContract from o1js within the same file', async () => { + fs.readdirSync.mockReturnValue(['MultipleInheritance.js']); + const result = findIfClassExtendsOrImplementsSmartContract( + './build/src/MultipleInheritance.js' + ); + expect(result).toEqual([ + { + className: 'TestZkApp', + filePath: './build/src/MultipleInheritance.js', + }, + { + className: 'ZkAppWithSecondInheritanceLevel', + filePath: './build/src/MultipleInheritance.js', + }, + { + className: 'ZkAppWithThirdInheritanceLevel', + filePath: './build/src/MultipleInheritance.js', + }, + ]); + }); + + it('should identify classes extending another class that extends SmartContract from o1js with imports across files', async () => { + fs.readdirSync.mockReturnValue([ + 'TestZkApp.js', + 'ZkAppWithSecondInheritanceLevel.js', + ]); + const resultForTestZkApp = findIfClassExtendsOrImplementsSmartContract( + './build/src/TestZkApp.js' + ); + expect(resultForTestZkApp).toEqual([ + { className: 'TestZkApp', filePath: './build/src/TestZkApp.js' }, + ]); + + const resultForZkAppWithSecondInheritanceLevel = + findIfClassExtendsOrImplementsSmartContract( + './build/src/ZkAppWithSecondInheritanceLevel.js' + ); + expect(resultForZkAppWithSecondInheritanceLevel).toEqual([ + { + className: 'ZkAppWithSecondInheritanceLevel', + filePath: './build/src/ZkAppWithSecondInheritanceLevel.js', + }, + ]); + + const resultForZkAppWithThirdInheritanceLevel = + findIfClassExtendsOrImplementsSmartContract( + './build/src/ZkAppWithThirdInheritanceLevel.js' + ); + expect(resultForZkAppWithThirdInheritanceLevel).toEqual([ + { + className: 'ZkAppWithThirdInheritanceLevel', + filePath: './build/src/ZkAppWithThirdInheritanceLevel.js', + }, + ]); + }); + + it('should skip classes extending SmartContract not from o1js', async () => { + fs.readdirSync.mockReturnValue(['NotO1jsSmartContractLib.js']); + const result = findIfClassExtendsOrImplementsSmartContract( + './build/src/NotO1jsSmartContractLib.js' + ); + expect(result).toEqual([]); + }); + + it('should skip classes extending SmartContract not from o1js with imports across files', async () => { + fs.readdirSync.mockReturnValue([ + 'SomeLibOnFileSystem.js', + 'NotO1jsSmartContractFs.js', + ]); + const result = findIfClassExtendsOrImplementsSmartContract( + './build/src/NotO1jsSmartContractFs.js' + ); + expect(result).toEqual([]); + }); + }); }); diff --git a/src/lib/helpers.js b/src/lib/helpers.js index f837eacc..7958b796 100644 --- a/src/lib/helpers.js +++ b/src/lib/helpers.js @@ -1,7 +1,18 @@ +import { parse as acornParse } from 'acorn'; +import { simple as simpleAcornWalk } from 'acorn-walk'; import chalk from 'chalk'; import net from 'net'; +import fs from 'node:fs'; +import { builtinModules } from 'node:module'; +import path from 'node:path'; import ora from 'ora'; +const acornOptions = { + ecmaVersion: 2020, + sourceType: 'module', + allowAwaitOutsideFunction: true, +}; + /** * Helper for any steps for a consistent UX. * @template T @@ -85,4 +96,245 @@ export async function checkLocalPortsAvailability(ports) { } } +/** + * Finds all classes that extend or implement the 'SmartContract' class from 'o1js'. + * + * @param {string} entryFilePath - The path of the entry file. + * @returns {Array} - An array of objects containing the class name and file path of the smart contract classes found. + */ +export function findIfClassExtendsOrImplementsSmartContract(entryFilePath) { + const classesMap = buildClassHierarchy(entryFilePath); + const importMappings = resolveImports(entryFilePath); + const smartContractClasses = []; + + // Check each class in the class map for inheritance from the o1js `SmartContract` + for (let className of Object.keys(classesMap)) { + const result = checkClassInheritance( + className, + 'SmartContract', + classesMap, + new Set(), + importMappings + ); + if (result) { + smartContractClasses.push({ + className, + filePath: classesMap[className].filePath, + }); + } + } + + return smartContractClasses; +} + +/** + * Builds a class hierarchy map based on the provided file path. + * + * @param {string} filePath - The path to the file containing the class declarations. + * @returns {Object} - The class hierarchy map, where keys are class names and values are objects containing class information. + */ +function buildClassHierarchy(filePath) { + const source = fs.readFileSync(filePath, 'utf-8'); + const ast = acornParse(source, acornOptions); + const classesMap = {}; + const importSet = new Set(); + + // Traverse the AST to find class declarations and imports + simpleAcornWalk(ast, { + ClassDeclaration(node) { + const currentClass = node.id.name; + const parentClass = node.superClass ? node.superClass.name : null; + const implementedInterfaces = node.implements + ? node.implements.map((iface) => iface.id.name) + : []; + classesMap[currentClass] = { + extends: parentClass, + implements: implementedInterfaces, + filePath, + inheritsFromO1jsSmartContract: false, + }; + }, + ImportDeclaration(node) { + if (node.source.value === 'o1js') { + node.specifiers.forEach((specifier) => { + importSet.add(specifier.local.name); + }); + } + }, + }); + + // Mark classes that extend `SmartContract` from `o1js` in the same file + Object.values(classesMap).forEach((classInfo) => { + if (importSet.has(classInfo.extends)) { + classInfo.inheritsFromO1jsSmartContract = true; + } + }); + + return classesMap; +} + +/** + * Resolves the imports in the given file path and returns the import mappings. + * + * @param {string} filePath - The path of the file to resolve imports for. + * @returns {Object} - The import mappings where the keys are the local names and the values are objects with the resolved paths and module names. + */ +function resolveImports(filePath) { + const source = fs.readFileSync(filePath, 'utf-8'); + const ast = acornParse(source, acornOptions); + const importMappings = {}; + + // Traverse the AST to find import declarations + simpleAcornWalk(ast, { + ImportDeclaration(node) { + const sourcePath = node.source.value; + node.specifiers.forEach((specifier) => { + const resolvedPath = resolveModulePath( + sourcePath, + path.dirname(filePath) + ); + importMappings[specifier.local.name] = { + resolvedPath, + moduleName: sourcePath, + }; + }); + }, + }); + + return importMappings; +} + +/** + * Resolves the path of a module based on the provided module name and base path. + * + * @param {string} moduleName - The name of the module to resolve. + * @param {string} basePath - The base path to resolve the module path relative to. + * @returns {string|null} - The resolved module path, or null if the module is not found or is a built-in module. + */ +function resolveModulePath(moduleName, basePath) { + // Check if the module is a Node.js built-in module + if (builtinModules.includes(moduleName)) { + return null; + } + + // Resolve relative or absolute paths based on the current file's directory + if (path.isAbsolute(moduleName) || moduleName.startsWith('.')) { + return path.resolve(basePath, moduleName); + } else { + // Module is a node_modules dependency + const packagePath = path.join('node_modules', moduleName); + const packageJsonPath = path.join(packagePath, 'package.json'); + + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + // Try to resolve the main file using the package.json + let mainFile = + packageJson.main || packageJson?.exports?.node?.import || 'index.js'; + return path.join(packagePath, mainFile); + } else { + console.error( + `Module '${moduleName}' not found in the './node_modules' directory.` + ); + return null; + } + } +} + +/** + * Checks if a class inherits from a target class by traversing the class hierarchy. + * + * @param {string} className - The name of the class to check. + * @param {string} targetClass - The name of the target class to check inheritance against. + * @param {Object} classesMap - A map of class names to class information. + * @param {Set} visitedClasses - A set of visited class names to avoid infinite loops. + * @param {Object} importMappings - A map of class names to resolved file paths and module names. + * @returns {boolean} - Returns true if the class inherits from the target class from 'o1js', false otherwise. + */ +function checkClassInheritance( + className, + targetClass, + classesMap, + visitedClasses, + importMappings +) { + // Avoid infinite loops by tracking visited classes + if (visitedClasses.has(className)) return false; + visitedClasses.add(className); + + // If the class is not found in the current classesMap, build its hierarchy from imports + if (!classesMap[className]) { + let importMapping = importMappings[className]; + if (!importMapping) return false; + + Object.assign(classesMap, buildClassHierarchy(importMapping.resolvedPath)); + importMappings = Object.assign( + importMappings, + resolveImports(importMapping.resolvedPath) + ); + } + + const classInfo = classesMap[className]; + if (!classInfo) return false; + + // Ensure that the parent class hierarchy is processed + if (classInfo.extends && !classesMap[classInfo.extends]) { + const parentMapping = importMappings[classInfo.extends]; + if (parentMapping) { + Object.assign( + classesMap, + buildClassHierarchy(parentMapping.resolvedPath) + ); + importMappings = Object.assign( + importMappings, + resolveImports(parentMapping.resolvedPath) + ); + } + } + + // Check if the class extends the target class from 'o1js' + if ( + classInfo.extends === targetClass && + classInfo.inheritsFromO1jsSmartContract + ) { + return true; + } + + // Check each implemented interface + for (const iface of classInfo.implements) { + if ( + (iface === targetClass && classInfo.inheritsFromO1jsSmartContract) || + checkClassInheritance( + iface, + targetClass, + classesMap, + visitedClasses, + importMappings + ) + ) { + classInfo.inheritsFromO1jsSmartContract = true; + return true; + } + } + + // If there is no parent class, return false + if (!classInfo.extends) return false; + + // Recursively check the parent class + const parentClassResult = checkClassInheritance( + classInfo.extends, + targetClass, + classesMap, + visitedClasses, + importMappings + ); + + // Propagate the inheritsFromO1jsSmartContract flag + if (parentClassResult) { + classInfo.inheritsFromO1jsSmartContract = true; + return true; + } + + return false; +} + export default step; diff --git a/src/lib/project.test.js b/src/lib/project.test.js index abc84088..3458587a 100644 --- a/src/lib/project.test.js +++ b/src/lib/project.test.js @@ -17,29 +17,66 @@ describe('project.js', () => { }); describe('setProjectName()', () => { + const DIR = 'temp-fixture-proj'; + const NAME = 'my-cool-zkapp'; + const TEMPLATE_DIR = 'templates/project-ts'; + + beforeAll(() => { + fs.copyFileSync( + path.join(TEMPLATE_DIR, 'README.md'), + path.join(TEMPLATE_DIR, 'README.md.bak') + ); + fs.copyFileSync( + path.join(TEMPLATE_DIR, 'package.json'), + path.join(TEMPLATE_DIR, 'package.json.bak') + ); + }); + + afterAll(() => { + fs.copyFileSync( + path.join(TEMPLATE_DIR, 'README.md.bak'), + path.join(TEMPLATE_DIR, 'README.md') + ); + fs.copyFileSync( + path.join(TEMPLATE_DIR, 'package.json.bak'), + path.join(TEMPLATE_DIR, 'package.json') + ); + fs.unlinkSync(path.join(TEMPLATE_DIR, 'README.md.bak')); + fs.unlinkSync(path.join(TEMPLATE_DIR, 'package.json.bak')); + }); + + afterEach(() => { + if (fs.existsSync(DIR)) { + fs.rmSync(DIR, { recursive: true }); + } + }); + it('README.md contains target text to replace', () => { - const readmeTs = fs.readFileSync( - path.join('templates', 'project-ts', 'README.md') + const readme = fs.readFileSync( + path.join(TEMPLATE_DIR, 'README.md'), + 'utf8' ); - expect(readmeTs.includes('Mina zkApp: PROJECT_NAME')).toBeTruthy(); + expect(readme.includes('Mina zkApp: PROJECT_NAME')).toBeTruthy(); }); it('package.json contains target text to replace', () => { - const readmeTs = fs.readFileSync( - path.join('templates', 'project-ts', 'package.json') + const packageJson = fs.readFileSync( + path.join(TEMPLATE_DIR, 'package.json'), + 'utf8' ); - expect(readmeTs.includes('package-name')).toBeTruthy(); + expect(packageJson.includes('package-name')).toBeTruthy(); }); it('should replace text in README.md & package.json', () => { - const DIR = 'temp-fixture-proj'; - const NAME = 'my-cool-zkapp'; - - const README = '# Mina zkApp: PROJECT_NAME\n more stuff\n and more'; - const PKG = `{"name": "package-name","version": "0.1.0"}`; - fs.mkdirSync('temp-fixture-proj', { recursive: true }); - fs.writeFileSync(DIR + '/README.md', README); - fs.writeFileSync(DIR + '/package.json', PKG); + fs.mkdirSync(DIR, { recursive: true }); + fs.writeFileSync( + DIR + '/README.md', + '# Mina zkApp: PROJECT_NAME\n more stuff\n and more' + ); + fs.writeFileSync( + DIR + '/package.json', + `{"name": "package-name","version": "0.1.0"}` + ); setProjectName(DIR, NAME); @@ -52,20 +89,23 @@ describe('project.js', () => { ); expect(packageAfter.includes('my-cool-zkapp')).toBeTruthy(); expect(packageAfter.includes('package-name')).toBeFalsy(); - fs.rmSync(DIR, { recursive: true }); }); }); describe('replaceInFile()', () => { + const file = 'tmp-fixture-file'; + + afterEach(() => { + fs.unlinkSync(file); + }); + it('should replace target content in a file', () => { - const file = 'tmp-fixture-file'; const str = '# Mina zkApp: PROJECT_NAME\n more stuff'; fs.writeFileSync(file, str); replaceInFile(file, 'PROJECT_NAME', 'Foo Bar'); const result = fs.readFileSync(file, 'utf8'); expect(result.includes('Foo Bar')).toBeTruthy(); expect(result.includes('PROJECT_NAME')).toBeFalsy(); - fs.unlinkSync(file); }); }); diff --git a/templates/project-ts/src/Add.test.ts b/templates/project-ts/src/Add.test.ts index 7b6b47f9..5fee2df6 100644 --- a/templates/project-ts/src/Add.test.ts +++ b/templates/project-ts/src/Add.test.ts @@ -1,5 +1,5 @@ +import { AccountUpdate, Field, Mina, PrivateKey, PublicKey } from 'o1js'; import { Add } from './Add'; -import { Field, Mina, PrivateKey, PublicKey, AccountUpdate } from 'o1js'; /* * This file specifies how to test the `Add` example smart contract. It is safe to delete this file and replace diff --git a/tests/utils/deploy-utils.ts b/tests/utils/deploy-utils.ts index 00ddc771..bc941ecd 100644 --- a/tests/utils/deploy-utils.ts +++ b/tests/utils/deploy-utils.ts @@ -58,12 +58,6 @@ export async function zkDeploy( try { if ((Constants.exampleTypes as string[]).includes(projectType)) { - // TODO: Get rid of this workaround once the issue is fixed - // https://github.com/o1-labs/zkapp-cli/issues/636 - interactiveDialog = { - ...interactiveDialog, - 'Choose smart contract to deploy': ['arrowDown', 'enter'], - }; workDir = `./${projectType}`; await zkExample(projectType as ExampleType, true, processHandler); } else {