diff --git a/.changeset/tiny-timers-juggle.md b/.changeset/tiny-timers-juggle.md new file mode 100644 index 0000000..8dc5ec8 --- /dev/null +++ b/.changeset/tiny-timers-juggle.md @@ -0,0 +1,6 @@ +--- +"solana": major +--- + +migrate the core of the solana node cli to `mucho` - +https://github.com/solana-developers/mucho/ diff --git a/README.md b/README.md index af9ded0..9a95952 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,7 @@ # Solana Node CLI -This is the `solana`, a command-line tool designed to simplify the development -and testing of Solana blockchain programs. The tool provides an array of -commands to manage Solana Toolkit installations, clone and manage blockchain -fixtures (accounts, programs, etc), and simplifying the experience of running a -local test-validator with all the required state for a consistent development -experience. - -**System Requirements:** - -- NodeJS (version >= 22) +The Solana Node CLI is meant to be a generic utilitarian helper CLI to +accomplish some basic setup and troubleshooting for Solana development. **Usage:** @@ -20,17 +12,14 @@ npx solana --help This tool is not recommended to be installed as a global npm package on your system. If installed globally, unexpected behavior may occur. +**System Requirements:** + +- NodeJS (version >= 22) + ## Commands - [`install`](#install) - Install and manage the Solana Toolkit's local development tooling on your system. -- [`clone`](#clone) - Clone accounts and programs (aka fixtures) from any Solana - cluster desired and declared in the `Solana.toml`. -- [`test-validator`](#test-validator) - Run the Solana test-validator on your - local machine, including loading all the cloned fixtures for your repo. -- [`build`](#build) - Build all or a single Solana program in your workspace. -- [`deploy`](#deploy) - Deploy a Solana program in your workspace. -- [`coverage`](#coverage) - Run code coverage tests on a Solana program. ### install @@ -50,6 +39,9 @@ The Solana Toolkit includes the following tools: - [Agave CLI tool suite](https://solana.com/docs/intro/installation#install-the-solana-cli) - the standard tool suite required to build and deploy Solana programs (formerly known as the "Solana CLI tool suite"). +- [Mucho CLI](https://github.com/solana-developers/mucho) - a superset of + popular developer tools within the Solana ecosystem used to simplify the + development and testing of Solana blockchain programs. - [solana-verify](https://github.com/Ellipsis-Labs/solana-verifiable-build) - A command line tool to build and verify Solana programs. - [Anchor and AVM](https://www.anchor-lang.com/docs/installation#installing-using-anchor-version-manager-avm-recommended) - @@ -60,297 +52,3 @@ The Solana Toolkit includes the following tools: framework for Solana programs to help you ship secure code. - [Zest](https://github.com/LimeChain/zest?tab=readme-ov-file) - Code coverage CLI tool for Solana programs. - -### clone - -Clone all the fixtures (accounts and programs) utilizing your `Solana.toml` -file. - -**Usage:** - -```shell -npx solana clone --help -``` - -The default behavior for fixture cloning is as follows: - -- All cloned fixtures are stored in the `fixtures` directory, unless overriden - by your Solana.toml's `settings.accountDir`. -- All fixtures are cloned from Solana's mainnet cluster or the declared - `settings.cluster` in Solana.toml. -- All fixtures are cloned from the same cluster, unless individually overriden. -- If any fixtures already exist, they are skipped from cloning. -- If a Solana account is cloned, the `owner` program will automatically be - cloned from the same cluster, unless the program is explicitly declared in the - Solana.toml. - -To override the default cloning behavior for any fixture, declare the desired -override setting in your Solana.toml file. Some of the supported overrides -include: - -- `cluster` - Desired cluster to clone the particular fixture from. -- `frequency` - Desired clone frequency when performing the clone operation. -- Each fixture type (account, program, etc) may support other specific - overrides. - -See the Solana.toml's [clone configuration](#clone-configuration) for details -about all options. - -> The cloned fixtures are recommended to be version controlled via git in order -> to facilitate a consistent local Solana ledger (via -> [`text-validator`](#test-validator)) and therefore reproducible and testable -> blockchain state for anyone with access to the repo. - -### build - -Build all or a single Solana program in your workspace. - -**Usage:** - -```shell -npx solana build --help -``` - -### deploy - -Deploy a Solana program in your workspace. - -**Usage:** - -```shell -npx solana deploy --help -``` - -The default behavior for deploying is as follows: - -- The `deploy` command will default to your Solana CLI declared cluster, unless - overriden with the `-u, --url` flag. - -### test-validator - -Run the Solana test-validator on your local machine, including loading all the -cloned fixtures for your repo. - -**Usage:** - -```shell -npx solana test-validator --help -``` - -> Under the hood, the `test-validator` commands wraps the Agave tool suite's -> `solana-test-validator` command but also helps provide additional quality of -> life improvements for Solana developers. To view the generated -> `solana-test-validator` wrapper command, run -> `npx solana test-validator --output-only`. - -The default behavior for running the `test-validator` is as follows: - -- If the ledger does not already exist, or when resetting the ledger via the - `--reset` flag, all the currently cloned fixtures will be loaded into the - validator at genesis. -- All programs declared in your Solana.toml's `programs.localnet` declaration - will be loaded into the validator at genesis. -- All loaded programs will have their upgrade authority set to your local - keypair's address (via `settings.keypair`). - -### coverage - -Run code coverage tests on a Solana program, powered by the -[Zest CLI](https://github.com/LimeChain/zest?tab=readme-ov-file). - -**Usage:** - -```shell -npx solana coverage --help -``` - -To pass additional arguments to the Zest CLI, append them at the end of this -tool's command starting with `--`. For example, to run the Zest CLI's help -command: - -```shell -npx solana coverage -- --help -``` - -## Solana.toml - -The `Solana.toml` file is a framework agnostic manifest file containing metadata -and configuration settings to enable developers to more easily manage their -Solana program development processes. - -The `Solana.toml` file is expected to be stored in the root a repo and committed -to git. - -You can find an [example Solana.toml file](./tests/Solana.toml) here. - -### `settings` configuration - -Declare general defaults and configuration settings for use in various places. - -- `cluster` - Desired cluster to clone all fixtures from (if not individually - overriden per fixture). If not defined, defaults to Solana's mainnet. - - Type: `enum` - - Default: `mainnet` - - Values: `mainnet`, `devnet`, or `testnet` -- `accountDir` - Path to store cloned fixtures (accounts and programs) - - Type: `string` - - Default: `fixtures` -- `keypair` - Path to the default local keypair file to use in various places - (i.e. set as the upgrade authority for all cloned programs when running - `test-validator`) - - Type: `string` - - Default: `~/.config/solana/id.json` (same as the Agave CLI tool suite) - -```toml -[settings] -accountDir = "fixtures" # default="fixtures" -# accountDir = ".cache/accounts" # default="fixtures" - -cluster = "mainnet" # default="mainnet" -# cluster = "devnet" -# cluster = "testnet" - -# keypair = "any/local/path" # default="~/.config/solana/id.json" -``` - -### `programs` configuration - -The addresses of the programs in the workspace. - -```toml -[programs.localnet] -counter = "AgVqLc7bKvnLL6TQnBMsMAkT18LixiW9isEb21q1HWUR" -[programs.devnet] -counter = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" -[programs.testnet] -counter = "BmDHboaj1kBUoinJKKSRqKfMeRKJqQqEbUj1VgzeQe4A" -[programs.mainnet] -counter = "AgVqLc7bKvnLL6TQnBMsMAkT18LixiW9isEb21q1HWUR" -``` - -These addresses will be used to load programs using the `test-validator` at -genesis. - -### `clone` configuration - -The Solana.toml's `clone` configuration settings are used to provide a framework -agnostic, consistent, and declarative way to clone data from the Solana -blockchain for use in local development and testing. Including more fine grain -control and quality of life improvements for Solana developers. - -The cloned data (accounts, programs, etc) are referred to as "fixtures". - -Each fixture type may support specific configuration settings and the following -individual overrides via their Solana.toml declaration: - -- `cluster` - Desired cluster to clone the particular fixture from. If not - declared, defaults to [`settings.cluster`](#settings-configuration). -- `frequency` - Desired clone frequency when performing the clone operation. - - Type: `enum` - - Values: - - `cached` - (default) Only clone the fixture if it does not already exist - locally. - - `always` - Every time a clone operation is performed, this specific - fixture will always be forced cloned from the cluster. - -#### `clone.account` - -To clone any account from a Solana cluster, use the `clone.account.` -declaration. - -Cloned accounts are stored as `json` files in the repo's -[`settings.accountDir`](#settings-configuration) directory. - -When account cloning occurs, the account's `owner` program will be automatically -cloned from the same cluster and with the frequency. This helps ensure the -cloned/stored account fixture will have the same program that knows its data -structure and enables developers to correctly interact with the account while -running their local test validator. - -**Usage:** - -```toml filename="Solana.toml" -[clone.account.wallet] -# this is a random wallet account, owned by system program -address = "GQuioVe2yA6KZfstgmirAvugfUZBcdxSi7sHK7JGk3gk" -# override the cluster for a particular account, if not defined: uses `settings.cluster` -cluster = "devnet" -frequency = "always" - -[clone.account.bonk] -# BONK mint address -address = "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" -cluster = "mainnet" - -[clone.account.tm-rando] -# random metaplex token metadata account -address = "5uZQ4GExZXwwKRNmpxwxTW2ZbHxh8KjDDwKne7Whqm4H" -# this account is owned by: metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s -# and will be auto clone the program without needing to declare it anywhere -``` - -The example above will clone the 3 accounts from the network(s) and the owner -program for the `5uZQ4GExZXwwKRNmpxwxTW2ZbHxh8KjDDwKne7Whqm4H` account (which -happens to be `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s`). - -#### `clone.program` - -To clone any program from a Solana cluster, use the `clone.program.` -declaration. - -Cloned accounts are stored as `so` binary files in the repo's -[`settings.accountDir`](#settings-configuration) directory. - -```toml filename="Solana.toml" -[clone.program.bubblegum] -address = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY" -cluster = "mainnet" -# if you want to always force clone the account -# frequency = "always" - -[clone.program.tm-metadata] -address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -# `cluster` is not defined => fallback to `settings.cluster` -# `frequency` is not defined => will only clone if the program binary is missing locally -``` - -## Anchor Compatibility - -This tool may support similar functionality and configuration enabled via the -`Anchor.toml` file. If an Anchor.toml file is detected, this tool will make a -best attempt to process the Anchor.toml file's configuration for functionality -that this tool supports. - -The `Solana.toml` settings will normally take priority over the `Anchor.toml` -file's configuration. - -The following Anchor.toml configuration settings are supported: - -- `[programs.]` - Anchor's CLI requires the program address to be - declared for each respective cluster. This tool will use the Anchor.toml - declared program ids for each program and cluster defined, unless the same - address is declared in the Solana.toml. -- `[[test.validator.clone]]` - The program `address` listed will be cloned using - the Anchor.toml declared `test.validator.url` value as the cluster, unless the - same address is declared in the Solana.toml. -- `[[test.validator.account]]` - The account `address` listed will be cloned - using the Anchor.toml declared `test.validator.url` value as the cluster, - unless the same address is declared in the Solana.toml. No matter the cluster, - the account's `owner` program will automatically be cloned from that same - cluster. - -Some general difference between how Anchor may handle similar functionality that -this tool supports include: - -- Cloned accounts/programs into the repo - This tool [clones accounts](#clone) - and programs into the repo and expects these fixtures to be committed to git. - Accounts are stored as `.json` files and programs as `.so` binary files. -- Clone cache - Cloned fixtures are cached by default (in the repo), helping to - reduce the load on RPC providers. This also helps developers working on the - same source repository to have a consistent ledger state when performing local - development and testing via [`test-validator`](#test-validator). -- Mix-and-match cloning - This tool allows more - [fine grain control](#cloneaccount) over which cluster any specific - account/program gets cloned from. For example, if you want `account1` to come - from mainnet and `account2` to come from devnet, you can easily accomplish - this via Solana.toml. diff --git a/package.json b/package.json index d64fbdc..2b786ab 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,6 @@ "@commander-js/extra-typings": "^12.1.0", "@iarna/toml": "^2.2.5", "@inquirer/prompts": "^7.0.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/buffer-layout-utils": "^0.2.0", - "@solana/spl-token": "^0.4.9", - "@solana/web3.js": "^1.95.3", "commander": "^12.1.0", "dotenv": "^16.4.5", "inquirer": "^12.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc428eb..5987e02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,18 +17,6 @@ importers: '@inquirer/prompts': specifier: ^7.0.0 version: 7.0.0(@types/node@22.7.6) - '@solana/buffer-layout': - specifier: ^4.0.1 - version: 4.0.1 - '@solana/buffer-layout-utils': - specifier: ^0.2.0 - version: 0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/spl-token': - specifier: ^0.4.9 - version: 0.4.9(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@solana/web3.js': - specifier: ^1.95.3 - version: 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) commander: specifier: ^12.1.0 version: 12.1.0 @@ -519,14 +507,6 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@noble/curves@1.6.0': - resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.5.0': - resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} - engines: {node: ^14.21.3 || >=16} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -686,72 +666,6 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@solana/buffer-layout-utils@0.2.0': - resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} - engines: {node: '>= 10'} - - '@solana/buffer-layout@4.0.1': - resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} - engines: {node: '>=5.10'} - - '@solana/codecs-core@2.0.0-rc.1': - resolution: {integrity: sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==} - peerDependencies: - typescript: '>=5' - - '@solana/codecs-data-structures@2.0.0-rc.1': - resolution: {integrity: sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==} - peerDependencies: - typescript: '>=5' - - '@solana/codecs-numbers@2.0.0-rc.1': - resolution: {integrity: sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==} - peerDependencies: - typescript: '>=5' - - '@solana/codecs-strings@2.0.0-rc.1': - resolution: {integrity: sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==} - peerDependencies: - fastestsmallesttextencoderdecoder: ^1.0.22 - typescript: '>=5' - - '@solana/codecs@2.0.0-rc.1': - resolution: {integrity: sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==} - peerDependencies: - typescript: '>=5' - - '@solana/errors@2.0.0-rc.1': - resolution: {integrity: sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==} - hasBin: true - peerDependencies: - typescript: '>=5' - - '@solana/options@2.0.0-rc.1': - resolution: {integrity: sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==} - peerDependencies: - typescript: '>=5' - - '@solana/spl-token-group@0.0.7': - resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.95.3 - - '@solana/spl-token-metadata@0.1.6': - resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.95.3 - - '@solana/spl-token@0.4.9': - resolution: {integrity: sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.95.3 - - '@solana/web3.js@1.95.3': - resolution: {integrity: sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==} - '@swc/core-darwin-arm64@1.9.3': resolution: {integrity: sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==} engines: {node: '>=10'} @@ -863,9 +777,6 @@ packages: '@types/bn.js@5.1.6': resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==} - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -905,25 +816,12 @@ packages: '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - '@types/uuid@8.3.4': - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} - - '@types/ws@7.4.7': - resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} - - '@types/ws@8.5.12': - resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -933,10 +831,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} - engines: {node: '>= 8.0.0'} - ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1006,32 +900,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base-x@3.0.10: - resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - bigint-buffer@1.1.5: - resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} - engines: {node: '>= 10.0.0'} - - bignumber.js@9.1.2: - resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - - borsh@0.7.0: - resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1051,22 +923,12 @@ packages: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bufferutil@4.0.8: - resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} - engines: {node: '>=6.14.2'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1186,10 +1048,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -1239,12 +1097,6 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - - es6-promisify@5.0.0: - resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1261,9 +1113,6 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1283,10 +1132,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - eyes@0.1.8: - resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} - engines: {node: '> 0.1.90'} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1294,12 +1139,6 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-stable-stringify@1.0.0: - resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} - - fastestsmallesttextencoderdecoder@1.0.22: - resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} - fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -1314,9 +1153,6 @@ packages: picomatch: optional: true - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -1404,16 +1240,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1498,11 +1328,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} - peerDependencies: - ws: '*' - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1532,11 +1357,6 @@ packages: engines: {node: '>=10'} hasBin: true - jayson@4.1.2: - resolution: {integrity: sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==} - engines: {node: '>=8'} - hasBin: true - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1681,9 +1501,6 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1695,10 +1512,6 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1783,19 +1596,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.2: - resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} - hasBin: true - node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -1977,9 +1777,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rpc-websockets@9.0.4: - resolution: {integrity: sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ==} - run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} @@ -2105,10 +1902,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - superstruct@2.0.2: - resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} - engines: {node: '>=14.0.0'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2134,12 +1927,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -2151,9 +1938,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-jest@29.2.5: resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} @@ -2221,14 +2005,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - utf-8-validate@5.0.10: - resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} - engines: {node: '>=6.14.2'} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -2239,12 +2015,6 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -2269,30 +2039,6 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -3014,12 +2760,6 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@noble/curves@1.6.0': - dependencies: - '@noble/hashes': 1.5.0 - - '@noble/hashes@1.5.0': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3143,128 +2883,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - bigint-buffer: 1.1.5 - bignumber.js: 9.1.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@solana/buffer-layout@4.0.1': - dependencies: - buffer: 6.0.3 - - '@solana/codecs-core@2.0.0-rc.1(typescript@5.6.3)': - dependencies: - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 - - '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.6.3)': - dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 - - '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.6.3)': - dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 - - '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': - dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - fastestsmallesttextencoderdecoder: 1.0.22 - typescript: 5.6.3 - - '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': - dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - typescript: 5.6.3 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - '@solana/errors@2.0.0-rc.1(typescript@5.6.3)': - dependencies: - chalk: 5.3.0 - commander: 12.1.0 - typescript: 5.6.3 - - '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': - dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': - dependencies: - '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - typescript - - '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': - dependencies: - '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - typescript - - '@solana/spl-token@0.4.9(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - buffer: 6.0.3 - transitivePeerDependencies: - - bufferutil - - encoding - - fastestsmallesttextencoderdecoder - - typescript - - utf-8-validate - - '@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)': - dependencies: - '@babel/runtime': 7.25.7 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 - '@solana/buffer-layout': 4.0.1 - agentkeepalive: 4.5.0 - bigint-buffer: 1.1.5 - bn.js: 5.2.1 - borsh: 0.7.0 - bs58: 4.0.1 - buffer: 6.0.3 - fast-stable-stringify: 1.0.0 - jayson: 4.1.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) - node-fetch: 2.7.0 - rpc-websockets: 9.0.4 - superstruct: 2.0.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - '@swc/core-darwin-arm64@1.9.3': optional: true @@ -3317,6 +2935,7 @@ snapshots: '@swc/helpers@0.5.13': dependencies: tslib: 2.8.0 + optional: true '@swc/jest@0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13))': dependencies: @@ -3362,10 +2981,6 @@ snapshots: dependencies: '@types/node': 22.7.6 - '@types/connect@3.4.38': - dependencies: - '@types/node': 22.7.6 - '@types/estree@1.0.6': {} '@types/graceful-fs@4.1.9': @@ -3411,37 +3026,18 @@ snapshots: dependencies: '@types/node': 22.7.6 - '@types/uuid@8.3.4': {} - - '@types/ws@7.4.7': - dependencies: - '@types/node': 22.7.6 - - '@types/ws@8.5.12': - dependencies: - '@types/node': 22.7.6 - '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': dependencies: '@types/yargs-parser': 21.0.3 - JSONStream@1.3.5: - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 - acorn-walk@8.3.4: dependencies: acorn: 8.12.1 acorn@8.12.1: {} - agentkeepalive@4.5.0: - dependencies: - humanize-ms: 1.2.1 - ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -3530,34 +3126,10 @@ snapshots: balanced-match@1.0.2: {} - base-x@3.0.10: - dependencies: - safe-buffer: 5.2.1 - - base64-js@1.5.1: {} - better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - bigint-buffer@1.1.5: - dependencies: - bindings: 1.5.0 - - bignumber.js@9.1.2: {} - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bn.js@5.2.1: {} - - borsh@0.7.0: - dependencies: - bn.js: 5.2.1 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3582,26 +3154,12 @@ snapshots: dependencies: fast-json-stable-stringify: 2.1.0 - bs58@4.0.1: - dependencies: - base-x: 3.0.10 - bser@2.1.1: dependencies: node-int64: 0.4.0 buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bufferutil@4.0.8: - dependencies: - node-gyp-build: 4.8.2 - optional: true - callsites@3.1.0: {} camelcase@5.3.1: {} @@ -3696,8 +3254,6 @@ snapshots: deepmerge@4.3.1: {} - delay@5.0.0: {} - detect-indent@6.1.0: {} detect-newline@3.1.0: {} @@ -3733,12 +3289,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es6-promise@4.2.8: {} - - es6-promisify@5.0.0: - dependencies: - es6-promise: 4.2.8 - escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -3747,8 +3297,6 @@ snapshots: estree-walker@2.0.2: {} - eventemitter3@5.0.1: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -3779,8 +3327,6 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - eyes@0.1.8: {} - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3791,10 +3337,6 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-stable-stringify@1.0.0: {} - - fastestsmallesttextencoderdecoder@1.0.22: {} - fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -3807,8 +3349,6 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - file-uri-to-path@1.0.0: {} - filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -3889,16 +3429,10 @@ snapshots: human-signals@2.1.0: {} - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@5.3.2: {} import-local@3.2.0: @@ -3967,10 +3501,6 @@ snapshots: isexe@2.0.0: {} - isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)): - dependencies: - ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -4019,24 +3549,6 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 - jayson@4.1.2(bufferutil@4.0.8)(utf-8-validate@5.0.10): - dependencies: - '@types/connect': 3.4.38 - '@types/node': 12.20.55 - '@types/ws': 7.4.7 - JSONStream: 1.3.5 - commander: 2.20.3 - delay: 5.0.0 - es6-promisify: 5.0.0 - eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)) - json-stringify-safe: 5.0.1 - uuid: 8.3.2 - ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -4357,8 +3869,6 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-stringify-safe@5.0.1: {} - json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -4367,8 +3877,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonparse@1.3.1: {} - kleur@3.0.3: {} leven@3.1.0: {} @@ -4440,13 +3948,6 @@ snapshots: natural-compare@1.4.0: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-gyp-build@4.8.2: - optional: true - node-int64@0.4.0: {} node-releases@2.0.18: {} @@ -4619,19 +4120,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 - rpc-websockets@9.0.4: - dependencies: - '@swc/helpers': 0.5.13 - '@types/uuid': 8.3.4 - '@types/ws': 8.5.12 - buffer: 6.0.3 - eventemitter3: 5.0.1 - uuid: 8.3.2 - ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - run-async@3.0.0: {} run-parallel@1.2.0: @@ -4736,8 +4224,6 @@ snapshots: strip-json-comments@3.1.1: {} - superstruct@2.0.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4763,10 +4249,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - text-encoding-utf-8@1.0.2: {} - - through@2.3.8: {} - tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -4777,8 +4259,6 @@ snapshots: dependencies: is-number: 7.0.0 - tr46@0.0.3: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.7.6)(ts-node@10.9.2(@swc/core@1.9.3(@swc/helpers@0.5.13))(@types/node@22.7.6)(typescript@5.6.3)))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 @@ -4836,13 +4316,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - utf-8-validate@5.0.10: - dependencies: - node-gyp-build: 4.8.2 - optional: true - - uuid@8.3.2: {} - v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -4855,13 +4328,6 @@ snapshots: dependencies: makeerror: 1.0.12 - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which@1.3.1: dependencies: isexe: 2.0.0 @@ -4889,16 +4355,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - - ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - y18n@5.0.8: {} yallist@2.1.2: {} diff --git a/src/commands/build.ts b/src/commands/build.ts deleted file mode 100644 index 91240f8..0000000 --- a/src/commands/build.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { join } from "path"; -import { Command, Option } from "@commander-js/extra-typings"; -import { cliOutputConfig } from "@/lib/cli"; -import { titleMessage, warningOutro, warnMessage } from "@/lib/logs"; -import { - checkCommand, - installedToolVersion, - shellExecInSession, -} from "@/lib/shell"; -import { COMMON_OPTIONS } from "@/const/commands"; -import { autoLocateProgramsInWorkspace, loadCargoToml } from "@/lib/cargo"; -import { buildProgramCommand } from "@/lib/shell/build"; -import { doesFileExist } from "@/lib/utils"; -import { checkVersion } from "@/lib/node"; -import { getPlatformToolsVersions } from "@/lib/solana"; - -/** - * Command: `build` - * - * Build the programs located in the user's repo - */ -export function buildCommand() { - return new Command("build") - .configureOutput(cliOutputConfig) - .description("build your Solana programs") - .usage("[options] [-- ...]") - .addOption( - new Option( - "-- ", - `arguments to pass to the underlying 'cargo build-sbf' command`, - ), - ) - .addOption( - new Option( - "-p --program-name ", - "name of the program to build", - ), - ) - .addOption(COMMON_OPTIONS.manifestPath) - .addOption(COMMON_OPTIONS.config) - .addOption(COMMON_OPTIONS.outputOnly) - .action(async (options, { args: passThroughArgs }) => { - if (!options.outputOnly) { - titleMessage("Build your Solana programs"); - } - - // console.log("options:"); - // console.log(options); - - await checkCommand("cargo build-sbf --help", { - exit: true, - message: - "Unable to detect the 'cargo build-sbf' command. Do you have it installed?", - }); - - let { programs, cargoToml } = autoLocateProgramsInWorkspace( - options.manifestPath, - ); - - // only build a single program - if (options.programName) { - if ( - programs.has(options.programName) && - doesFileExist(programs.get(options.programName)) - ) { - cargoToml = loadCargoToml(programs.get(options.programName)); - } else { - warnMessage( - `Unable to locate program '${options.programName}' in this workspace`, - ); - console.log(`The following programs were located:`); - - programs.forEach((_programPath, programName) => - console.log(" -", programName), - ); - - // todo: should we prompt the user to select a valid program? - process.exit(); - } - } - - if (!cargoToml) { - return warningOutro( - `Unable to locate Cargo.toml file. Operation canceled.`, - ); - } - - let buildCommand: null | string = null; - - let toolsVersion: string | null = null; - const solanaVersion = await installedToolVersion("solana"); - const { platformTools } = await getPlatformToolsVersions(); - - if ( - checkVersion(solanaVersion, "2.0") && - !checkVersion(platformTools, "1.43") - ) { - warnMessage( - `cargo build-sbf versions >=2.X requires building with platform tools version >=1.43`, - ); - toolsVersion = "1.43"; - warnMessage( - `Auto setting platform tools to ${toolsVersion} for this build`, - ); - } - - if (cargoToml.workspace) { - console.log("Building all programs in the workspace"); - buildCommand = buildProgramCommand({ - // no manifest file will attempt to build the whole workspace - manifestPath: cargoToml.configPath, - workspace: true, - toolsVersion, - }); - } else if ( - cargoToml.package && - cargoToml.lib["crate-type"].includes("lib") - ) { - console.log( - `Building program '${ - cargoToml.lib.name || cargoToml.package.name || "[unknown]" - }' only`, - ); - - buildCommand = buildProgramCommand({ - // a single program manifest will build only that one program - manifestPath: cargoToml.configPath, - toolsVersion, - }); - } else { - return warningOutro(`Unable to locate any program's Cargo.toml file`); - } - - if (!buildCommand) { - return warningOutro(`Unable to create build command`); - } - - shellExecInSession({ - command: buildCommand, - args: passThroughArgs, - outputOnly: options.outputOnly, - }); - }); -} - -export default buildCommand; diff --git a/src/commands/clone.ts b/src/commands/clone.ts deleted file mode 100644 index 398cdc1..0000000 --- a/src/commands/clone.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Command, Option } from "@commander-js/extra-typings"; -import { cliOutputConfig, loadConfigToml } from "@/lib/cli"; -import { titleMessage, warnMessage } from "@/lib/logs"; -import { checkCommand } from "@/lib/shell"; -import { - createFolders, - loadFileNamesToMap, - moveFiles, - updateGitignore, -} from "@/lib/utils"; -import { - cloneAccountsFromConfig, - cloneProgramsFromConfig, - cloneTokensFromConfig, - mergeOwnersMapWithConfig, - validateExpectedCloneCounts, -} from "@/lib/shell/clone"; -import { COMMON_OPTIONS } from "@/const/commands"; -import { - DEFAULT_ACCOUNTS_DIR_TEMP, - DEFAULT_CACHE_DIR, - DEFAULT_TEST_LEDGER_DIR, -} from "@/const/solana"; -import { rmSync } from "fs"; -import { deconflictAnchorTomlWithConfig, loadAnchorToml } from "@/lib/anchor"; -import { isGitRepo } from "@/lib/git"; -import { promptToInitGitRepo } from "@/lib/prompts/git"; - -/** - * Command: `clone` - * - * Clone all the fixtures listed in the Solana.toml file - */ -export function cloneCommand() { - return ( - new Command("clone") - .configureOutput(cliOutputConfig) - .description( - "clone all the accounts and programs listed in the Solana.toml file", - ) - .addOption( - new Option("--force", "force clone all fixtures, even if they exist"), - ) - .addOption( - new Option( - "--no-prompt", - "skip the prompts to override any existing fixtures", - ), - ) - .addOption(COMMON_OPTIONS.accountDir) - .addOption(COMMON_OPTIONS.config) - // we intentionally make cloning from mainnet by default (vice the solana cli's config.yml) - .addOption( - new Option( - COMMON_OPTIONS.url.flags, - COMMON_OPTIONS.url.description, - ).default("mainnet"), - ) - .action(async (options) => { - titleMessage("Clone fixtures (accounts and programs)"); - - // console.log("options:"); - // console.log(options); - - await checkCommand("solana account --help", { - exit: true, - message: - "Unable to detect the 'solana account' command. Do you have it installed?", - }); - - let targetGitDir = process.cwd(); - if (!isGitRepo(targetGitDir)) { - warnMessage( - `Cloning fixtures without tracking changes via git is not recommended`, - ); - - await promptToInitGitRepo(targetGitDir); - } - - let config = loadConfigToml( - options.config, - options, - true /* config required */, - ); - - // attempt to load and combine the anchor toml clone settings - const anchorToml = loadAnchorToml(config.configPath); - if (anchorToml) { - config = deconflictAnchorTomlWithConfig(anchorToml, config); - } - - updateGitignore([DEFAULT_CACHE_DIR, DEFAULT_TEST_LEDGER_DIR]); - rmSync(DEFAULT_ACCOUNTS_DIR_TEMP, { - recursive: true, - force: true, - }); - - const currentAccounts = loadFileNamesToMap(config.settings.accountDir); - - /** - * we clone the accounts in the order of: accounts, tokens, then programs - * in order to perform any special processing on them - */ - - const accounts = await cloneAccountsFromConfig( - config, - options, - currentAccounts, - ); - await cloneTokensFromConfig(config, options, currentAccounts); - - let detectedPrograms: ReturnType = {}; - if (accounts) { - detectedPrograms = mergeOwnersMapWithConfig(accounts.owners); - await cloneProgramsFromConfig( - { settings: config.settings, clone: { program: detectedPrograms } }, - { ...options, autoClone: true }, - currentAccounts, - ); - } - - // always clone the config-declared programs last (in order to override the detected ones) - await cloneProgramsFromConfig(config, options, currentAccounts); - - // now that all the files have been deconflicted, we can move them to their final home - createFolders(DEFAULT_ACCOUNTS_DIR_TEMP); - moveFiles(DEFAULT_ACCOUNTS_DIR_TEMP, config.settings.accountDir, true); - - rmSync(DEFAULT_ACCOUNTS_DIR_TEMP, { - recursive: true, - force: true, - }); - - const cloneCounts = validateExpectedCloneCounts( - config.settings.accountDir, - config.clone, - ); - if (cloneCounts.actual === cloneCounts.expected) { - console.log( - `Completed cloning ${cloneCounts.actual} ${ - cloneCounts.actual == 1 ? "account" : "accounts" - }`, - ); - } else { - warnMessage( - `Completed cloning fixtures. Expected ${cloneCounts.expected} fixtures, but only ${cloneCounts.actual} found`, - ); - } - }) - ); -} - -export default cloneCommand; diff --git a/src/commands/coverage.ts b/src/commands/coverage.ts deleted file mode 100644 index 2b27ba2..0000000 --- a/src/commands/coverage.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Command, Option } from "@commander-js/extra-typings"; -import { COMMON_OPTIONS } from "@/const/commands"; -import { cliOutputConfig } from "@/lib/cli"; -import { titleMessage, warnMessage } from "@/lib/logs"; -import { checkCommand, shellExecInSession } from "@/lib/shell"; -import { promptToInstall } from "@/lib/prompts/install"; -import { installZest } from "@/lib/install"; - -const command = `zest coverage`; - -/** - * Command: `coverage` - * - * Run the zest code coverage tool - */ -export function coverageCommand() { - return new Command("coverage") - .configureOutput(cliOutputConfig) - .description("run code coverage on a Solana program") - .usage("[options] [-- ...]") - .addOption( - new Option( - "-- ", - `arguments to pass to the underlying ${command} command`, - ), - ) - .addOption(COMMON_OPTIONS.outputOnly) - .action(async (options, { args: passThroughArgs }) => { - if (!options.outputOnly) { - titleMessage("Zest code coverage"); - } - - await checkCommand("zest --help", { - exit: true, - onError: async () => { - warnMessage("Unable to detect the 'zest' command."); - const shouldInstall = await promptToInstall("zest"); - if (shouldInstall) await installZest(); - }, - doubleCheck: true, - }); - - shellExecInSession({ - command, - args: passThroughArgs, - outputOnly: options.outputOnly, - }); - }); -} - -export default coverageCommand; diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts deleted file mode 100644 index e7c26c5..0000000 --- a/src/commands/deploy.ts +++ /dev/null @@ -1,293 +0,0 @@ -import path from "path"; -import { Command, Option } from "@commander-js/extra-typings"; -import { cliConfig, COMMON_OPTIONS } from "@/const/commands"; -import { cliOutputConfig, loadConfigToml } from "@/lib/cli"; -import { cancelMessage, titleMessage, warnMessage } from "@/lib/logs"; -import { checkCommand, shellExecInSession } from "@/lib/shell"; -import { - buildDeployProgramCommand, - getDeployedProgramInfo, -} from "@/lib/shell/deploy"; -import { autoLocateProgramsInWorkspace } from "@/lib/cargo"; -import { directoryExists, doesFileExist } from "@/lib/utils"; -import { - getSafeClusterMoniker, - loadKeypairFromFile, - parseRpcUrlOrMoniker, -} from "@/lib/solana"; -import { promptToSelectCluster } from "@/lib/prompts/build"; - -/** - * Command: `deploy` - * - * Manage Solana program deployments and upgrades - */ -export function deployCommand() { - return new Command("deploy") - .configureOutput(cliOutputConfig) - .description("deploy a Solana program") - .usage("[options] [-- ...]") - .addOption( - new Option( - "-- ", - `arguments to pass to the underlying 'solana program' command`, - ), - ) - .addOption( - new Option( - "-p --program-name ", - "name of the program to deploy", - ), - ) - .addOption(COMMON_OPTIONS.url) - .addOption(COMMON_OPTIONS.manifestPath) - .addOption(COMMON_OPTIONS.keypair) - .addOption(COMMON_OPTIONS.config) - .addOption(COMMON_OPTIONS.outputOnly) - .action(async (options, { args: passThroughArgs }) => { - if (!options.outputOnly) { - titleMessage("Deploy a Solana program"); - } - - await checkCommand("solana program --help", { - exit: true, - message: "Unable to detect the 'solana program' command", - }); - - const { programs, cargoToml } = autoLocateProgramsInWorkspace( - options.manifestPath, - ); - - // auto select the program name for single program repos - if (!options.programName && programs.size == 1) { - options.programName = programs.entries().next().value[0]; - } - - if (!cargoToml) return warnMessage(`Unable to locate Cargo.toml`); - - // ensure the selected program directory exists in the workspace - if (!programs.has(options.programName) || !options.programName) { - if (!options.programName) { - // todo: we could give the user a prompt - warnMessage(`You must select a program to deploy. See --help.`); - } else if (!programs.has(options.programName)) { - warnMessage( - `Unable to locate program '${options.programName}' in this workspace`, - ); - } - - console.log(`The following programs were located:`); - programs.forEach((_programPath, programName) => - console.log(" -", programName), - ); - - // todo: should we prompt the user to select a valid program? - process.exit(); - } - - if (!options.url) { - const cluster = await promptToSelectCluster( - "Select the cluster to deploy your program on?", - getSafeClusterMoniker(cliConfig?.json_rpc_url) || undefined, - ); - options.url = parseRpcUrlOrMoniker(cluster); - } - - if (!options.url) { - return warnMessage(`You must select cluster to deploy to. See --help`); - } - - let selectedCluster = getSafeClusterMoniker(options.url); - if (!selectedCluster) { - // prompting a second time will allow users to use a custom rpc url - const cluster = await promptToSelectCluster( - "Unable to auto detect the cluster to deploy too. Select a cluster?", - ); - selectedCluster = getSafeClusterMoniker(cluster); - if (!selectedCluster) { - return warnMessage( - `Unable to detect cluster to deploy to. Operation canceled.`, - ); - } - } - - let config = loadConfigToml( - options.config, - options, - false /* config not required */, - ); - - const buildDir = path.join( - path.dirname(cargoToml.configPath), - "target", - "deploy", - ); - - if (!directoryExists(buildDir)) { - warnMessage(`Unable to locate your build dir: ${buildDir}`); - return warnMessage(`Have you built your programs?`); - } - - const binaryPath = path.join(buildDir, `${options.programName}.so`); - if (!doesFileExist(binaryPath)) { - // todo: we should detect if the program is declared and recommend building it - // todo: or we could generate a fresh one? - warnMessage(`Unable to locate program binary:\n${binaryPath}`); - return warnMessage(`Have you built your programs?`); - } - - let programId: string | null = null; - let programIdPath: string | null = path.join( - buildDir, - `${options.programName}-keypair.json`, - ); - const programKeypair = loadKeypairFromFile(programIdPath); - - // process the user's config file if they have one - if (config?.programs) { - // make sure the user has the cluster program declared - if (!getSafeClusterMoniker(selectedCluster, config.programs)) { - warnMessage( - `Unable to locate '${selectedCluster}' programs in your Solana.toml`, - ); - - console.log("The following programs are declared:"); - Object.keys(config.programs).forEach((cl) => { - console.log(` - ${cl}:`); - Object.keys(config.programs[cl]).forEach((name) => { - console.log(` - ${name}`); - }); - }); - - process.exit(); - } - - if ( - !config?.programs?.[selectedCluster] || - !Object.hasOwn(config.programs[selectedCluster], options.programName) - ) { - warnMessage( - `Program '${options.programName}' not found in 'programs.${selectedCluster}'`, - ); - process.exit(); - } - - programId = config.programs[selectedCluster][options.programName]; - } else { - // if the user does not have a config file, we will try to auto detect the program id to use - // todo: this - - if (programKeypair) { - programId = programKeypair.publicKey.toBase58(); - warnMessage(`Auto detected default program keypair file:`); - console.log(` - keypair path: ${programIdPath}`); - console.log(` - program id: ${programId}`); - } else { - warnMessage(`Unable to locate any program id or program keypair.`); - process.exit(); - } - } - - if (!programId) { - return warnMessage( - `Unable to locate program id for '${options.programName}'. Do you have it declared?`, - ); - } - - let programInfo = await getDeployedProgramInfo(programId, options.url); - - /** - * when programInfo exists, we assume the program is already deployed - * (either from the user's current machine or not) - */ - if (!programInfo) { - // not-yet-deployed programs require a keypair to deploy for the first time - // warnMessage( - // `Program ${options.programName} (${programId}) is NOT already deployed on ${selectedCluster}`, - // ); - if (!programKeypair) { - return warnMessage( - `Unable to locate program keypair: ${programIdPath}`, - ); - } - - const programIdFromKeypair = programKeypair.publicKey.toBase58(); - /** - * since the initial deployment requires a keypair: - * if the user has a mismatch between their declared program id - * and the program keypair, we do not explicitly know which address they want - */ - if (programIdFromKeypair !== programId) { - warnMessage( - `The loaded program keypair does NOT match the configured program id`, - ); - console.log(` - program keypair: ${programIdFromKeypair}`); - console.log(` - declared program id: ${programId}`); - warnMessage( - `Unable to perform initial program deployment. Operation cancelled.`, - ); - process.exit(); - // todo: should we prompt the user if they want to proceed - } - programId = programIdFromKeypair; - programInfo = await getDeployedProgramInfo(programId, options.url); - } - - const authorityKeypair = loadKeypairFromFile(config.settings.keypair); - - /** - * todo: assorted pre-deploy checks to add - * + is program already deployed - * + is program frozen - * - do you have the upgrade authority - * - is the upgrade authority a multi sig? - * - do you have enough sol to deploy ? - */ - if (programInfo) { - if (!programInfo.authority) { - return cancelMessage( - `Program ${programInfo.programId} is no longer upgradeable`, - ); - } - - if (programInfo.authority !== authorityKeypair.publicKey.toBase58()) { - return cancelMessage( - `Your keypair (${authorityKeypair.publicKey.toBase58()}) is not the upgrade authority for program ${programId}`, - ); - } - - programId = programInfo.programId; - } else { - // todo: do we need to perform any checks if the program is not already deployed? - } - - const command = buildDeployProgramCommand({ - programPath: binaryPath, - programId: programIdPath || programId, - url: options.url, - keypair: options.keypair, - }); - - // todo: if options.url is localhost, verify the test validator is running - - // todo: if localhost deploy, support feature cloning to match a cluster - - /** - * todo: if deploying to mainnet, we should add some "confirm" prompts - * - this is the program id - * - this is the upgrade authority - * - estimated cost (you have X sol) - * do you want to continue? - */ - - console.log(""); // spacer in the terminal - - shellExecInSession({ - command, - args: passThroughArgs, - outputOnly: options.outputOnly, - }); - }); -} - -export default deployCommand; diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 70593de..44ea0d8 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -10,7 +10,7 @@ import { checkInstalledTools } from "@/lib/setup"; * * Inspect and remedy your local machine for Solana development */ -export default function doctorCommand() { +export function doctorCommand() { return new Command("doctor") .configureOutput(cliOutputConfig) .description("inspect and remedy your local development environment") @@ -22,7 +22,7 @@ export default function doctorCommand() { * * List the current installed versions of Solana development tooling */ -export function doctorListCommand() { +function doctorListCommand() { return new Command("list") .configureOutput(cliOutputConfig) .description("list the current versions of Solana development tooling") diff --git a/src/commands/install.ts b/src/commands/install.ts index 7be3549..5714e7e 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -12,6 +12,7 @@ import { installTrident, installZest, installSolanaVerify, + installMucho, } from "@/lib/install"; import { checkInstalledTools, checkShellPathSource } from "@/lib/setup"; import { PathSourceStatus, TOOL_CONFIG } from "@/const/setup"; @@ -20,6 +21,7 @@ const toolNames: Array = [ "rust", "solana", "avm", + "mucho", "anchor", "trident", "zest", @@ -32,7 +34,7 @@ const toolNames: Array = [ * * Setup your local machine for Solana development */ -export default function installCommand() { +export function installCommand() { return ( new Command("install") .configureOutput(cliOutputConfig) @@ -110,6 +112,14 @@ export default function installCommand() { : true, ); } + + if (!toolName || toolName == "mucho") { + await installMucho({ + os, + version, + }); + } + if (!toolName || toolName == "avm") { // const version = "0.28.0"; //"latest"; // v0.29.0 has the "ahash yanked" issue await installAnchorVersionManager({ diff --git a/src/commands/test-validator.ts b/src/commands/test-validator.ts deleted file mode 100644 index a1fab8e..0000000 --- a/src/commands/test-validator.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Command, Option } from "@commander-js/extra-typings"; -import { cliOutputConfig, loadConfigToml } from "@/lib/cli"; -import { titleMessage, warnMessage } from "@/lib/logs"; -import { checkCommand } from "@/lib/shell"; -import { - deepMerge, - doesFileExist, - loadFileNamesToMap, - updateGitignore, -} from "@/lib/utils"; -import { - buildTestValidatorCommand, - runTestValidator, -} from "@/lib/shell/test-validator"; -import { COMMON_OPTIONS } from "@/const/commands"; -import { loadKeypairFromFile } from "@/lib/solana"; -import { DEFAULT_CACHE_DIR, DEFAULT_TEST_LEDGER_DIR } from "@/const/solana"; -import { deconflictAnchorTomlWithConfig, loadAnchorToml } from "@/lib/anchor"; -import { validateExpectedCloneCounts } from "@/lib/shell/clone"; -import { promptToAutoClone } from "@/lib/prompts/clone"; -import { listLocalPrograms } from "@/lib/programs"; -import cloneCommand from "./clone"; - -/** - * Command: `test-validator` - * - * Run the 'solana-test-validator' on your local machine - */ -export default function testValidatorCommand() { - return ( - new Command("test-validator") - .configureOutput(cliOutputConfig) - .description("run the Solana test-validator on your local machine") - // .addOption( - // new Option("--prompt", "prompt to override any existing cloned accounts"), - // ) - .addOption( - new Option( - "--reset", - "reset the test-validator to genesis, reloading all preloaded fixtures", - ), - ) - .addOption( - new Option( - "--output", - "output the generated test-validator command while executing it", - ), - ) - .addOption(COMMON_OPTIONS.outputOnly) - .addOption(COMMON_OPTIONS.accountDir) - .addOption(COMMON_OPTIONS.config) - .addOption(COMMON_OPTIONS.keypair) - .addOption(COMMON_OPTIONS.url) - .action(async (options) => { - if (!options.outputOnly) { - titleMessage("solana-test-validator"); - } else options.output = options.outputOnly; - - await checkCommand("solana-test-validator --version", { - exit: true, - message: - "Unable to detect the 'solana-test-validator'. Do you have it installed?", - }); - - let config = loadConfigToml(options.config, options); - - updateGitignore([DEFAULT_CACHE_DIR, DEFAULT_TEST_LEDGER_DIR]); - - let authorityAddress: string | null = null; - if (config.settings.keypair) { - if (doesFileExist(config.settings.keypair)) { - authorityAddress = loadKeypairFromFile( - config.settings.keypair, - )?.publicKey.toBase58(); - } else { - warnMessage( - `Unable to locate keypair file: ${config.settings.keypair}`, - ); - warnMessage("Skipping auto creation and setting authorities"); - } - } - - // let localPrograms: SolanaTomlCloneLocalProgram = {}; - let locatedPrograms: ReturnType< - typeof listLocalPrograms - >["locatedPrograms"] = {}; - - // attempt to load and combine the anchor toml clone settings - const anchorToml = loadAnchorToml(config.configPath); - if (anchorToml) { - config = deconflictAnchorTomlWithConfig(anchorToml, config); - - // deep merge the solana and anchor config, taking priority with solana toml - config.programs = deepMerge(config.programs, anchorToml.programs); - } - - Object.assign( - locatedPrograms, - listLocalPrograms({ - configPath: config.configPath, - labels: config.programs, - cluster: "localnet", // todo: handle the user selecting the `cluster` - }).locatedPrograms, - ); - - // todo: check if all the local programs were compiled/found, if not => prompt - // if (!localListing.allFound) { - // // todo: add the ability to prompt the user to build their anchor programs - // warnMessage(`Have you built all your local programs?`); - // } - - // auto run the clone on reset - if (options.reset) { - // run the clone command with default options - // todo: could we pass options in here if we want? - await cloneCommand().parseAsync([]); - } - - // todo: this is flaky and does not seem to detect if some are missing. fix it - const cloneCounts = validateExpectedCloneCounts( - config.settings.accountDir, - config.clone, - ); - if (cloneCounts.actual !== cloneCounts.expected) { - warnMessage( - `Expected ${cloneCounts.expected} fixtures, but only ${cloneCounts.actual} found.`, - ); - - if (!options.outputOnly) { - await promptToAutoClone(); - } - } - - const command = buildTestValidatorCommand({ - verbose: !options.output, - reset: options.reset || false, - accountDir: config.settings.accountDir, - // todo: allow setting the authority from the cli args - authority: authorityAddress, - localPrograms: locatedPrograms, - }); - - if (options.output) console.log(`\n${command}\n`); - // only log the "run validator" command, do not execute it - if (options.outputOnly) process.exit(); - - if (options.reset) { - console.log( - "Loaded", - loadFileNamesToMap(config.settings.accountDir, ".json").size, - "accounts into the local validator", - ); - console.log( - "Loaded", - loadFileNamesToMap(config.settings.accountDir, ".so").size, - "programs into the local validator", - ); - } - - const explorerUrl = new URL( - "https://explorer.solana.com/?cluster=custom", - ); - explorerUrl.searchParams.set("customUrl", "http://localhost:8899"); - console.log("\nSolana Explorer for your local test-validator:"); - console.log( - "(on Brave Browser, you may need to turn Shields down for the Explorer website)", - ); - console.log(explorerUrl.toString()); - - runTestValidator({ - command, - }); - }) - ); -} diff --git a/src/const/commands.ts b/src/const/commands.ts deleted file mode 100644 index afec952..0000000 --- a/src/const/commands.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Option } from "@commander-js/extra-typings"; -import { - DEFAULT_ACCOUNTS_DIR, - DEFAULT_CONFIG_FILE, - DEFAULT_KEYPAIR_PATH, -} from "./solana"; -import { loadSolanaCliConfig } from "@/lib/cli"; -import { join } from "path"; - -export const cliConfig = loadSolanaCliConfig(); - -/** - * Listing of the common and reusable command options - */ -export const COMMON_OPTIONS = { - /** - * path to the local Solana.toml config file - * - * note: this is a different config file than the solana cli's config file - */ - config: new Option( - "-c --config ", - "path to a Solana.toml config file", - ).default(DEFAULT_CONFIG_FILE), - /** - * path to the local authority keypair - */ - keypair: new Option("--keypair ", "path to a keypair file").default( - cliConfig.keypair_path || DEFAULT_KEYPAIR_PATH, - ), - /** - * rpc url or moniker to use - */ - url: new Option( - "-u --url ", - "URL for Solana's JSON RPC or moniker", - ), - //.default(cliConfig.json_rpc_url || "mainnet"), - outputOnly: new Option( - "--output-only", - "only output the generated command, do not execute it", - ), - /** - * local directory path to store and load any cloned accounts - */ - accountDir: new Option( - "--account-dir ", - "local directory path to store any cloned accounts", - ).default(DEFAULT_ACCOUNTS_DIR), - manifestPath: new Option( - "--manifest-path ", - "path to Cargo.toml", - ).default(join(process.cwd(), "Cargo.toml")), -}; diff --git a/src/const/setup.ts b/src/const/setup.ts index 2e96033..cac8067 100644 --- a/src/const/setup.ts +++ b/src/const/setup.ts @@ -21,6 +21,9 @@ export const TOOL_CONFIG: { [key in ToolNames]: ToolCommandConfig } = { yarn: { version: "yarn --version", }, + mucho: { + version: "mucho --version", + }, trident: { dependencies: ["rust"], // the trident cli does not have a version command, so we grab it from cargo diff --git a/src/const/solana.ts b/src/const/solana.ts deleted file mode 100644 index 433e6b1..0000000 --- a/src/const/solana.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const DEFAULT_CONFIG_FILE = "Solana.toml"; - -export const DEFAULT_ACCOUNTS_DIR = "fixtures"; - -export const DEFAULT_CACHE_DIR = ".cache"; - -export const DEFAULT_TEST_LEDGER_DIR = "test-ledger"; - -export const DEFAULT_ACCOUNTS_DIR_TEMP = ".cache/staging/fixtures"; - -export const DEFAULT_ACCOUNTS_DIR_LOADED = ".cache/loaded/fixtures"; - -export const DEFAULT_KEYPAIR_PATH = "~/.config/solana/id.json"; - -export const DEFAULT_CLI_YAML_PATH = "~/.config/solana/cli/config.yml"; - -export const DEFAULT_BUILD_DIR = "target/deploy"; diff --git a/src/index.ts b/src/index.ts index 4fc776b..03af2f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,13 +4,8 @@ import { assertRuntimeVersion } from "@/lib/node"; import { errorMessage } from "@/lib/logs"; import cliProgramRoot from "@/commands"; -import installCommand from "@/commands/install"; -import doctorCommand from "@/commands/doctor"; -import cloneCommand from "@/commands/clone"; -import testValidatorCommand from "@/commands/test-validator"; -import buildCommand from "@/commands/build"; -import coverageCommand from "@/commands/coverage"; -import deployCommand from "@/commands/deploy"; +import { installCommand } from "@/commands/install"; +// import { doctorCommand } from "@/commands/doctor"; // ensure the user running the cli tool is on a supported javascript runtime version assertRuntimeVersion(); @@ -19,14 +14,8 @@ async function main() { try { const program = cliProgramRoot(); - program - .addCommand(installCommand()) - .addCommand(doctorCommand()) - .addCommand(cloneCommand()) - .addCommand(deployCommand()) - .addCommand(buildCommand()) - .addCommand(coverageCommand()) - .addCommand(testValidatorCommand()); + program.addCommand(installCommand()); + // .addCommand(doctorCommand()); // set the default action to `help` without an error if (process.argv.length === 2) { diff --git a/src/lib/anchor.ts b/src/lib/anchor.ts deleted file mode 100644 index f4b227d..0000000 --- a/src/lib/anchor.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { join, dirname } from "path"; -import { AnchorToml, AnchorTomlWithConfigPath } from "@/types/anchor"; -import { - directoryExists, - doesFileExist, - loadFileNamesToMap, - loadTomlFile, -} from "@/lib/utils"; -import { loadConfigToml } from "@/lib/cli"; -import { warningOutro, warnMessage } from "@/lib/logs"; -import { SolanaTomlCloneLocalProgram } from "@/types/config"; - -const ANCHOR_TOML = "Anchor.toml"; - -/** - * Load an Anchor.toml file, normally from the same dir as the Solana.toml - */ -export function loadAnchorToml( - configPath: string, - isConfigRequired: boolean = false, -): AnchorTomlWithConfigPath | false { - // allow the config path to be a full filepath to search that same directory - if (!configPath.endsWith(ANCHOR_TOML)) configPath = dirname(configPath); - - // allow the config path to be a directory, with an Anchor.toml in it - if (directoryExists(configPath)) configPath = join(configPath, ANCHOR_TOML); - - let anchor: AnchorTomlWithConfigPath = { - configPath, - }; - - if (doesFileExist(configPath, true)) { - anchor = loadTomlFile(configPath) || anchor; - } else { - if (isConfigRequired) { - warningOutro(`No Anchor.toml config file found. Operation canceled.`); - } - return false; - // else warnMessage(`No Anchor.toml config file found. Skipping.`); - } - - anchor.configPath = configPath; - return anchor as AnchorTomlWithConfigPath; -} - -/** - * Deconflict and merge Anchor.toml into the Solana.toml config - * - * note: intentionally mutates the `config` - */ -export function deconflictAnchorTomlWithConfig( - anchorToml: AnchorToml, - config: ReturnType, -) { - // todo: use the provided `anchorToml.test.validator.url`? - - // copy anchor cloneable programs - if (anchorToml.test?.validator?.clone) { - for (const cloner in anchorToml.test.validator.clone) { - if ( - Object.prototype.hasOwnProperty.call( - anchorToml.test.validator.clone, - cloner, - ) && - !config.clone.program[anchorToml.test.validator.clone[cloner].address] - ) { - if (anchorToml.test.validator?.url) { - anchorToml.test.validator.clone[cloner].cluster = - anchorToml.test.validator.url; - } - - // looks ugly, but we dont have to allocate anything - config.clone.program[anchorToml.test.validator.clone[cloner].address] = - anchorToml.test.validator.clone[cloner]; - } - } - } - - // todo: for accounts that are owned by the token programs, - // todo: attempt to resolve them as mints and clone them using our mint cloner - - // copy anchor cloneable accounts - if (anchorToml.test?.validator?.account) { - for (const cloner in anchorToml.test.validator.account) { - if ( - Object.prototype.hasOwnProperty.call( - anchorToml.test.validator.account, - cloner, - ) && - !config.clone.account[anchorToml.test.validator.account[cloner].address] - ) { - if (anchorToml.test.validator?.url) { - anchorToml.test.validator.account[cloner].cluster = - anchorToml.test.validator.url; - } - - // looks ugly, but we dont have to allocate anything - config.clone.account[ - anchorToml.test.validator.account[cloner].address - ] = anchorToml.test.validator.account[cloner]; - } - } - } - - // console.log(config.clone.program); - - return config; -} - -export function locateLocalAnchorPrograms( - configPath: string, - programListing: AnchorToml["programs"], -): SolanaTomlCloneLocalProgram { - const buildDir = join(dirname(configPath), "target/deploy"); - - let localPrograms: SolanaTomlCloneLocalProgram = {}; - - if (!directoryExists(buildDir)) return localPrograms; - - const anchorPrograms = loadFileNamesToMap(buildDir, ".so"); - - // todo: handle the user selecting the cluster - const cluster: keyof typeof programListing = "localnet"; - - if (!Object.prototype.hasOwnProperty.call(programListing, cluster)) { - warnMessage(`Unable to locate 'programs.${cluster}' in Anchor.toml`); - return localPrograms; - } - - let missingCounter = 0; - - anchorPrograms.forEach((binaryName, programName) => { - if ( - Object.prototype.hasOwnProperty.call( - programListing[cluster], - programName, - ) && - !Object.hasOwn(localPrograms, programName) - ) { - localPrograms[programName] = { - address: programListing[cluster][programName], - filePath: join(buildDir, binaryName), - }; - } else { - missingCounter++; - warnMessage( - `Unable to locate compiled program '${programName}' in Anchor.toml`, - ); - } - }); - - if (missingCounter > 0) { - // todo: add the ability to prompt the user to build their anchor programs - warnMessage(`Have you built all your local Anchor programs?`); - } - - return localPrograms; -} diff --git a/src/lib/cargo.ts b/src/lib/cargo.ts deleted file mode 100644 index 08e1ff8..0000000 --- a/src/lib/cargo.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Assorted helper functions and wrappers for working in the CLI - */ - -import { CargoTomlWithConfigPath } from "@/types/cargo"; -import { directoryExists, doesFileExist, loadTomlFile } from "@/lib/utils"; -import { readdirSync, statSync } from "fs"; -import { dirname, join, relative } from "path"; - -const DEFAULT_CARGO_TOML_FILE = "Cargo.toml"; - -/** - * Load a Cargo.toml file - */ -export function loadCargoToml( - manifestPath: string = DEFAULT_CARGO_TOML_FILE, - settings: object = {}, - isManifestRequired: boolean = false, -): CargoTomlWithConfigPath | false { - // allow the config path to be a directory, with a Solana.toml in it - if (directoryExists(manifestPath)) { - manifestPath = join(manifestPath, DEFAULT_CARGO_TOML_FILE); - } - - if (doesFileExist(manifestPath, true)) { - return loadTomlFile(manifestPath); - } else { - return false; - // if (isManifestRequired) { - // warningOutro(`No Cargo.toml file found. Operation canceled.`); - // } else warnMessage(`No Cargo.toml file found. Skipping.`); - } -} - -export function findAllCargoToml( - startDir: string, - whitelist: string[] = [], - blacklist: string[] = ["node_modules", "dist", "target"], - maxDepth: number = 3, -): string[] { - const cargoTomlPaths: string[] = []; - - // Convert whitelist patterns to regular expressions for wildcard matching - const whitelistPatterns = whitelist.map( - (pattern) => new RegExp("^" + pattern.replace(/\*/g, ".*") + "$"), - ); - - // Helper function to check if a directory matches any whitelist pattern - function isWhitelisted(relativeDir: string): boolean { - if (whitelistPatterns.length === 0) { - return true; - } - return whitelistPatterns.some((regex) => regex.test(relativeDir)); - } - - // Helper function to recursively search directories - function searchDir(dir: string, depth: number): void { - // Stop searching if maxDepth is reached - if (depth > maxDepth) { - return; - } - - const items = readdirSync(dir); - for (const item of items) { - const itemPath = join(dir, item); - const stats = statSync(itemPath); - - if (stats.isFile() && item === "Cargo.toml") { - cargoTomlPaths.push(itemPath); - } - - if (stats.isDirectory()) { - const relativeDir = relative(startDir, itemPath); - - if (blacklist.includes(relativeDir) && !isWhitelisted(relativeDir)) { - continue; - } - - searchDir(itemPath, depth + 1); - } - } - } - - searchDir(startDir, 0); - - return cargoTomlPaths; -} - -export function getProgramPathsInWorkspace( - startDir: string, - workspaceDirs: string[], -) { - const programPaths = new Map(); - - let tempToml: false | ReturnType = false; - - if (doesFileExist(startDir)) startDir = dirname(startDir); - - const allTomls = findAllCargoToml(startDir, workspaceDirs); - - allTomls.map((progPath) => { - if (!doesFileExist(progPath)) return; - - tempToml = loadCargoToml(progPath); - if (!tempToml || tempToml.workspace) return; - - const name = tempToml?.lib?.name || tempToml?.package?.name; - if (!name) return; - // require that the package name matches the directory name - // if (name !== basename(dirname(tempToml.configPath))) return; - - programPaths.set(name, tempToml.configPath); - }); - - return programPaths; -} - -export function autoLocateProgramsInWorkspace( - manifestPath: string = join(process.cwd(), "Cargo.toml"), - workspaceDirs: string[] = ["temp", "programs/*", "program"], -): { - programs: Map; - cargoToml: false | CargoTomlWithConfigPath; -} { - // determine if we are in a program specific dir or the workspace - let cargoToml = loadCargoToml(manifestPath); - - if (!cargoToml) { - workspaceDirs.some((workspace) => { - const filePath = join( - process.cwd(), - workspace.replace(/\*+$/, ""), - "Cargo.toml", - ); - if (doesFileExist(filePath)) { - cargoToml = loadCargoToml(filePath); - if (cargoToml) return; - } - }); - } - - let programs = new Map(); - - if (cargoToml) { - // always update the current manifest path to the one of the loaded Cargo.toml - if (cargoToml.configPath) { - manifestPath = cargoToml.configPath; - } - - if (cargoToml.workspace?.members) { - workspaceDirs = cargoToml.workspace.members; - } - - programs = getProgramPathsInWorkspace(manifestPath, workspaceDirs); - } - - return { programs, cargoToml }; -} diff --git a/src/lib/cli.ts b/src/lib/cli.ts index 811343e..01b4fb2 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -2,124 +2,7 @@ * Assorted helper functions and wrappers for working in the CLI */ -import { join } from "path"; import { OutputConfiguration } from "@commander-js/extra-typings"; -import { - directoryExists, - doesFileExist, - findFileInRepo, - isInCurrentDir, - loadTomlFile, - loadYamlFile, -} from "@/lib/utils"; -import { SolanaToml, SolanaTomlWithConfigPath } from "@/types/config"; -import { DEFAULT_CLI_YAML_PATH, DEFAULT_CONFIG_FILE } from "@/const/solana"; -import { COMMON_OPTIONS } from "@/const/commands"; -import { warningOutro, warnMessage } from "@/lib/logs"; -import { SolanaCliYaml } from "@/types/solana"; - -/** - * Load the Solana CLI's config file - */ -export function loadSolanaCliConfig(filePath: string = DEFAULT_CLI_YAML_PATH) { - const cliConfig = loadYamlFile(filePath); - - // auto convert the rpc url to the cluster moniker - if (cliConfig?.json_rpc_url) { - switch (cliConfig.json_rpc_url) { - case "https://api.devnet.solana.com": { - cliConfig.json_rpc_url = "devnet"; - break; - } - case "https://api.testnet.solana.com": { - cliConfig.json_rpc_url = "testnet"; - break; - } - case "https://api.mainnet-beta.solana.com": { - cliConfig.json_rpc_url = "mainnet"; - break; - } - case "http://localhost:8899": { - cliConfig.json_rpc_url = "localhost"; - break; - } - } - } - - return cliConfig; -} - -/** - * Load the Solana.toml config file and handle the default settings overrides - */ -export function loadConfigToml( - configPath: string = DEFAULT_CONFIG_FILE, - settings: object = {}, - isConfigRequired: boolean = false, -): SolanaTomlWithConfigPath { - // allow the config path to be a directory, with a Solana.toml in it - if (directoryExists(configPath)) { - configPath = join(configPath, DEFAULT_CONFIG_FILE); - } - - // attempt to locate the closest config file - if (configPath === DEFAULT_CONFIG_FILE) { - // accept both `Solana.toml` and `solana.toml` (case insensitive) - const newPath = findFileInRepo(DEFAULT_CONFIG_FILE); - if (newPath) { - configPath = newPath; - if (!isInCurrentDir(newPath)) { - // todo: should we prompt the user if they want to use this one? - warnMessage(`Using closest Solana.toml located at: ${newPath}`); - } - } - } - - let config: SolanaToml = { configPath }; - - if (doesFileExist(configPath, true)) { - config = loadTomlFile(configPath) || config; - } else { - if (isConfigRequired) { - warningOutro(`No Solana.toml config file found. Operation canceled.`); - } else warnMessage(`No Solana.toml config file found. Skipping.`); - } - - const defaultSettings: SolanaToml["settings"] = { - cluster: COMMON_OPTIONS.url.defaultValue, - accountDir: COMMON_OPTIONS.accountDir.defaultValue, - keypair: COMMON_OPTIONS.keypair.defaultValue, - }; - - config.settings = Object.assign(defaultSettings, config.settings || {}); - - config = deconflictSolanaTomlConfig(config, settings); - - config.configPath = configPath; - return config as SolanaTomlWithConfigPath; -} - -/** - * Used to deconflict a Solana.toml's declarations with the provided input, - * setting the desired priority of values - */ -export function deconflictSolanaTomlConfig(config: SolanaToml, args: any) { - if (args?.url && args.url !== COMMON_OPTIONS.url.defaultValue) { - config.settings.cluster = args.url; - } - if ( - args?.accountDir && - args.accountDir !== COMMON_OPTIONS.accountDir.defaultValue - ) { - config.settings.accountDir = args.accountDir; - } - - if (args?.keypair && args.keypair !== COMMON_OPTIONS.keypair.defaultValue) { - config.settings.keypair = args.keypair; - } - - return config; -} /** * Default Commander output configuration to be passed into `configureOutput()` diff --git a/src/lib/git.ts b/src/lib/git.ts deleted file mode 100644 index 294fb57..0000000 --- a/src/lib/git.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { join, resolve } from "path"; -import { execSync } from "child_process"; -import { existsSync } from "fs"; -import { createFolders } from "./utils"; - -/** - * Clone or force update a git repo into the target location - */ -export function cloneOrUpdateRepo( - repoUrl: string, - targetFolder: string, - branch: string = null, -): boolean { - try { - targetFolder = resolve(targetFolder); - const commands: string[] = []; - if (existsSync(targetFolder)) { - commands.push( - `git -C ${targetFolder} fetch origin ${branch || ""}`, - `git -C ${targetFolder} reset --hard ${ - branch ? `origin/${branch}` : "origin" - }`, - `git -C ${targetFolder} pull origin ${branch || ""}`, - ); - } else { - commands.push( - `git clone ${ - branch ? `--branch ${branch}` : "" - } ${repoUrl} ${targetFolder}`, - ); - } - - execSync(commands.join(" && "), { - stdio: "ignore", // hide output - // stdio: "inherit", // show output - }); - - if (existsSync(targetFolder)) return true; - else return false; - } catch (error) { - console.error( - "[cloneOrUpdateRepo]", - "Unable to clone repo:", - error.message, - ); - return false; - } -} - -export function isGitRepo(targetFolder: string): boolean { - try { - // Run git command to check if inside a git repository - const result = execSync( - `git -C ${targetFolder} rev-parse --is-inside-work-tree`, - { - stdio: "pipe", // so we can parse output - }, - ) - .toString() - .trim(); - - return result === "true"; - } catch (error) { - // If command fails, it means it's not a git repo - return false; - } -} - -export function initGitRepo( - targetFolder: string, - commitMessage: string = "init", -): boolean { - try { - createFolders(targetFolder, true); - const commands: string[] = []; - - // Combine all Git commands in a single line - commands.push( - `git init ${targetFolder}`, - `git add ${join(targetFolder, ".")} --force`, - `git -C ${targetFolder} commit -m "${commitMessage}"`, - ); - - // Execute the command - execSync(commands.join(" && "), { - stdio: "ignore", // hide output - // stdio: "inherit", // show output - }); - - return true; - } catch (error) { - console.error( - "[initGitRepo]", - "Unable to execute 'git init'", - // error.message, - ); - return false; - } -} diff --git a/src/lib/install.ts b/src/lib/install.ts index 393b2b9..1017a05 100644 --- a/src/lib/install.ts +++ b/src/lib/install.ts @@ -368,6 +368,42 @@ export async function installYarn({}: InstallCommandPropsBase = {}) { return false; } +/** + * Install the mucho cli + * note: we have to assume `npm` is already available + */ +export async function installMucho({}: InstallCommandPropsBase = {}) { + const spinner = ora("Installing the mucho cli...").start(); + try { + let installedVersion = await installedToolVersion("mucho"); + if (installedVersion) { + spinner.info(`mucho ${installedVersion} is already installed`); + return true; + } + + spinner.text = `Installing the2 mucho cli`; + await shellExec(`npm install -g mucho`); + + spinner.text = "Verifying mucho was installed"; + installedVersion = await installedToolVersion("mucho"); + if (installedVersion) { + spinner.succeed(`mucho ${installedVersion} installed`); + return installedVersion; + } else { + spinner.fail("mucho cli failed to install"); + return false; + } + } catch (err) { + spinner.fail("Unable to install the mucho cli"); + if (typeof err == "string") console.error(err); + else if (err instanceof Error) console.error(err.message); + else console.error(err.message); + } + + // default return false + return false; +} + /** * Install the trident fuzzer */ diff --git a/src/lib/programs.ts b/src/lib/programs.ts deleted file mode 100644 index 787d0c1..0000000 --- a/src/lib/programs.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { dirname, join, resolve } from "path"; -import { - ProgramsByClusterLabels, - SolanaTomlCloneLocalProgram, -} from "@/types/config"; -import { directoryExists, loadFileNamesToMap } from "@/lib//utils"; -import { DEFAULT_BUILD_DIR } from "@/const/solana"; -import { warnMessage } from "@/lib/logs"; - -/** - * List all the local program binaries in the specified build directory - */ -export function listLocalPrograms({ - labels = {}, - buildDir = DEFAULT_BUILD_DIR, - basePath, - configPath, - cluster = "localnet", -}: { - buildDir?: string; - basePath?: string; - labels?: ProgramsByClusterLabels; - configPath?: string; - cluster?: keyof ProgramsByClusterLabels; -} = {}): { - locatedPrograms: SolanaTomlCloneLocalProgram; - buildDirListing: Map; - allFound: boolean; -} { - let allFound: boolean = false; - let locatedPrograms: SolanaTomlCloneLocalProgram = {}; - let buildDirListing: Map = new Map(); - - if (basePath) { - buildDir = resolve(join(basePath, buildDir)); - } else if (configPath) { - buildDir = resolve(join(dirname(configPath), buildDir)); - } else { - buildDir = resolve(join(process.cwd(), buildDir)); - } - - if (!directoryExists(buildDir)) { - warnMessage(`Unable to locate build output directory: ${buildDir}`); - return { locatedPrograms, buildDirListing, allFound }; - } - - buildDirListing = loadFileNamesToMap(buildDir, ".so"); - - if (!Object.prototype.hasOwnProperty.call(labels, cluster)) { - // warnMessage(`Unable to locate 'programs.${cluster}'`); - return { locatedPrograms, buildDirListing, allFound }; - } - - let missingCounter = 0; - - buildDirListing.forEach((binaryName, programName) => { - if ( - Object.prototype.hasOwnProperty.call(labels[cluster], programName) && - !Object.hasOwn(locatedPrograms, programName) - ) { - locatedPrograms[programName] = { - address: labels[cluster][programName], - filePath: join(buildDir, binaryName), - }; - } else { - missingCounter++; - - if (!Object.prototype.hasOwnProperty.call(labels[cluster], programName)) { - warnMessage( - `Compiled program '${programName}' was found with no config info`, - ); - } else { - warnMessage( - `Unable to locate compiled program '${programName}' from config`, - ); - } - } - }); - - if (missingCounter == 0) allFound = true; - - return { locatedPrograms, buildDirListing, allFound }; -} diff --git a/src/lib/prompts/build.ts b/src/lib/prompts/build.ts deleted file mode 100644 index d8b9c23..0000000 --- a/src/lib/prompts/build.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { select } from "@inquirer/prompts"; -import { type SolanaCluster } from "@/types/config"; - -export async function promptToSelectCluster( - message: string = "Select a cluster?", - defaultValue: SolanaCluster = "mainnet", -): Promise { - console.log(); // print a line separator - return select({ - message, - theme: { - // style: { - // todo: we could customize the message here? - // error: (text) => text, - // }, - }, - default: defaultValue, - choices: [ - { - short: "m", - name: "m) mainnet", - value: "mainnet", - }, - { - short: "d", - name: "d) devnet", - value: "devnet", - }, - { - short: "t", - name: "t) testnet", - value: "testnet", - }, - { - short: "l", - name: "l) localnet", - value: "localnet", - // description: defaultValue.startsWith("l") - // ? "Default value selected" - // : "", - }, - ], - }) - .then(async (answer) => { - // if (!answer) return false; - - if (answer.startsWith("m")) answer = "mainnet"; - - return answer; - }) - .catch(() => { - /** - * it seems that if we execute the run clone command within another command - * (like nesting it under test-validator and prompting the user) - * the user may not be able to exit the test-validator process via Ctrl+c - * todo: investigate this more to see if we can still allow the command to continue - */ - // do nothing on user cancel instead of exiting the cli - console.log("Operation canceled."); - process.exit(); - // todo: support selecting a default value here? - return defaultValue; - }); -} diff --git a/src/lib/prompts/clone.ts b/src/lib/prompts/clone.ts deleted file mode 100644 index 7c2b9d9..0000000 --- a/src/lib/prompts/clone.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { select } from "@inquirer/prompts"; -import { cloneCommand } from "@/commands/clone"; - -export async function promptToAutoClone(): Promise { - console.log(); // print a line separator - return select({ - message: "Would you like to perform the 'clone' command now?", - default: "y", - choices: [ - { - name: "(y) Yes", - short: "y", - value: true, - description: `Yes, clone all the accounts and programs in Solana.toml`, - }, - { - name: "(n) No", - short: "n", - value: false, - description: "Do not run the 'clone' command now", - }, - ], - }) - .then(async (answer) => { - if (answer !== true) return false; - - // run the clone command with default options - // todo: could we pass options in here if we want? - await cloneCommand().parseAsync([]); - }) - .catch(() => { - /** - * it seems that if we execute the run clone command within another command - * (like nesting it under test-validator and prompting the user) - * the user may not be able to exit the test-validator process via Ctrl+c - * todo: investigate this more to see if we can still allow the command to continue - */ - // do nothing on user cancel instead of exiting the cli - console.log("Operation canceled."); - process.exit(); - return; - }); -} diff --git a/src/lib/prompts/git.ts b/src/lib/prompts/git.ts deleted file mode 100644 index e193316..0000000 --- a/src/lib/prompts/git.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { select } from "@inquirer/prompts"; -import { initGitRepo, isGitRepo } from "@/lib/git"; -import { warningOutro } from "@/lib/logs"; - -export async function promptToInitGitRepo( - defaultGitDir: string, - exitOnFailToCreate: boolean = true, -): Promise { - return select({ - message: "Would you like to initialize a git repo?", - default: "y", - choices: [ - { - name: "(y) Yes", - short: "y", - value: true, - description: `Default repo directory: ${defaultGitDir}`, - }, - // todo: support the user manually defining the repo root, including from the cli args - // { - // name: "(m) Manually define the repo root", - // short: "m", - // value: "m", - // description: "", - // }, - { - name: "(n) No", - short: "n", - value: false, - description: "Skip it", - }, - ], - }) - .then((answer) => { - if (answer == true) { - initGitRepo(defaultGitDir); - if (!isGitRepo(defaultGitDir) && exitOnFailToCreate) { - warningOutro( - `Unable to initialize a new git repo at: ${defaultGitDir}`, - ); - } - } - - return true; - }) - .catch((err) => { - // do nothing on user cancel - return false; - }); -} diff --git a/src/lib/setup.ts b/src/lib/setup.ts index cfcfb24..580f4ef 100644 --- a/src/lib/setup.ts +++ b/src/lib/setup.ts @@ -25,6 +25,7 @@ export async function checkInstalledTools({ avm: false, anchor: false, yarn: false, + mucho: false, trident: false, zest: false, verify: false, diff --git a/src/lib/shell/index.ts b/src/lib/shell.ts similarity index 100% rename from src/lib/shell/index.ts rename to src/lib/shell.ts diff --git a/src/lib/shell/build.ts b/src/lib/shell/build.ts deleted file mode 100644 index cffc652..0000000 --- a/src/lib/shell/build.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { spawn } from "child_process"; - -type BuildProgramCommandInput = { - verbose?: boolean; - workspace?: boolean; - manifestPath?: string; - toolsVersion?: string; -}; - -export function buildProgramCommand({ - verbose = false, - manifestPath, - workspace = false, - toolsVersion, -}: BuildProgramCommandInput) { - const command: string[] = ["cargo build-sbf"]; - - if (manifestPath) { - command.push(`--manifest-path ${manifestPath}`); - } - if (workspace) { - command.push(`--workspace`); - } - if (toolsVersion) { - if (!toolsVersion.startsWith("v")) toolsVersion = `v${toolsVersion}`; - command.push(`--tools-version ${toolsVersion}`); - } - - // todo: research features and how best to include them - // --features ... - // Space-separated list of features to activate - - // --sbf-sdk - // Path to the Solana SBF SDK [env: SBF_SDK_PATH=] [default: stable dir] - - // --tools-version - // platform-tools version to use or to install, a version string, e.g. "v1.32" - - return command.join(" "); -} - -type RunBuildCommandInput = { - programName: string; - command: string; - args?: string[]; -}; - -export async function runBuildCommand({ - programName, - command, - args, -}: RunBuildCommandInput): Promise { - return new Promise((resolve, reject) => { - let output = ""; - let errorOutput = ""; - - // // Spawn a child process - const child = spawn(command, args, { - detached: false, // run the command in the same session - stdio: "inherit", // Stream directly to the user's terminal - shell: true, // Runs in shell for compatibility with shell commands - // cwd: // todo: do we want this? - }); - - child.stdout.on("data", (data) => { - output += data.toString(); - }); - - child.stderr.on("data", (data) => { - errorOutput += data.toString(); - }); - - child.on("exit", (code) => { - if (code === 0) { - resolve(true); - } else { - console.error(errorOutput); - resolve(false); - } - }); - - process.on("error", (error) => { - reject(false); - }); - }); -} diff --git a/src/lib/shell/clone.ts b/src/lib/shell/clone.ts deleted file mode 100644 index f00abba..0000000 --- a/src/lib/shell/clone.ts +++ /dev/null @@ -1,458 +0,0 @@ -import path from "path"; -import shellExec from "shell-exec"; -import { - createFolders, - doesFileExist, - loadFileNamesToMap, - loadJsonFile, -} from "@/lib/utils"; -import { - CloneAccountsFromConfigResult, - CloneSettings, - SolanaCluster, - SolanaToml, - SolanaTomlCloneConfig, -} from "@/types/config"; -import { parseRpcUrlOrMoniker } from "@/lib/solana"; -import { - DEFAULT_ACCOUNTS_DIR, - DEFAULT_ACCOUNTS_DIR_TEMP, -} from "@/const/solana"; -import { warnMessage } from "@/lib/logs"; - -export type JsonAccountStruct = { - pubkey: string; - account: JsonAccountInfo; -}; - -type JsonAccountInfo = { - lamports: number; - // Array with the first element being a base64 string and the second element specifying the encoding format - data: [string, string | "base64"]; - owner: string; - executable: boolean; - rentEpoch: number; - space: number; -}; - -// todo: create interface for common Clone input fields -type CloneAccountInput = { - saveDir: string; - address: string; - url?: SolanaCluster | string; -}; - -type CloneProgramInput = { - saveDir: string; - address: string; - url?: SolanaCluster | string; -}; - -export async function cloneAccount({ - address, - saveDir = DEFAULT_ACCOUNTS_DIR, - url, -}: CloneAccountInput | undefined) { - let command: string[] = [ - // comment for better diffs - `solana account ${address}`, - "--output json", - // todo: enable this for production, maybe handled with a verbose mode or something? - // "--output json-compact", - ]; - - // note: when no url/cluster is specified, the user's `solana config` url will be used - if (url) { - command.push( - `--url ${parseRpcUrlOrMoniker(url, true /* enforce the "beta" label */)}`, - ); - } - - /** - * todo: should we force save by default? i think so... - * - * todo: we could also help detect drift from an existing locally cached account and any new ones pulled in - */ - const saveFile = path.resolve(saveDir, `${address}.json`); - createFolders(saveFile); - command.push(`--output-file ${saveFile}`); - - await shellExec(command.join(" ")); - - if (doesFileExist(saveFile)) { - return loadJsonFile(saveFile) || false; - } else { - // console.error("Failed to clone:", address); - return false; - } -} - -/** - * Clone a program from a cluster and store it locally as a `.so` binary - */ -export async function cloneProgram({ - address, - saveDir = "programs", - url, -}: CloneProgramInput | undefined) { - let command: string[] = [ - // comment for better diffs - `solana program dump`, - ]; - - const saveFile = path.resolve(saveDir, `${address}.so`); - createFolders(saveDir); - command.push(address, saveFile); - - // note: when no url/cluster is specified, the user's `solana config` url will be used - if (url) { - command.push( - `--url ${parseRpcUrlOrMoniker(url, true /* enforce the "beta" label */)}`, - ); - } - - await shellExec(command.join(" ")); - - if (doesFileExist(saveFile)) { - return true; - } else { - // console.error("Failed to clone program:", address); - return false; - } -} - -/** - * Clone all the programs listed in the config toml file - */ -export async function cloneProgramsFromConfig( - config: SolanaToml, - settings: CloneSettings, - currentAccounts: ReturnType, -) { - if (!config?.clone?.program) return null; - - for (const key in config.clone.program) { - if (!config.clone.program.hasOwnProperty(key)) { - continue; - } - - const program = config.clone.program[key]; - - // todo: should we validate any of the data? - - // set the default info - if (!program?.name) program.name = key; - - // the auto clone message is intentionally displayed separate - // so we can potentially skip recloning - if (settings.autoClone) { - warnMessage(`Auto clone program: ${program.address}`); - } - if (program.frequency === "always") { - console.log("Always clone program:", program.address); - } else if (settings.force === true) { - console.log("Force clone program:", program.address); - } else if (currentAccounts.has(program.address)) { - console.log("Skipping clone program:", program.address); - continue; - } else { - console.log("Clone program:", program.address); - } - - const newProgram = await cloneProgram({ - address: program.address, - saveDir: DEFAULT_ACCOUNTS_DIR_TEMP, - url: program.cluster || config?.settings?.cluster, - // saveDir: path.resolve(saveDirTemp, "program") - }); - - if (!newProgram) { - console.error("Failed to clone program:", program.address); - continue; - } - - // todo: handle the diff and deconflict - } -} - -/** - * Clone all the tokens listed in the config toml file - */ -export async function cloneTokensFromConfig( - config: SolanaToml, - settings: CloneSettings, - currentAccounts: ReturnType, -) { - if (!config?.clone?.token) return null; - - for (const key in config.clone.token) { - if (!config.clone.token.hasOwnProperty(key)) { - continue; - } - - const token = config.clone.token[key]; - // todo: should we validate any of the data? - - // set the default token info - if (!token?.name) token.name = key; - - if (token.frequency === "always") { - console.log("Always clone token:", token.address); - } else if (settings.force === true) { - console.log("Force clone token:", token.address); - } else if (currentAccounts.has(token.address)) { - console.log("Skipping clone token:", token.address); - continue; - } else { - console.log("Clone token:", token.address); - } - - /** - * todo: we should likely check if any of the accounts are already cloned - * - for ones that are already cloned: - * - default not reclone - * - have some options to control when to clone and when to notify the user - * - - * - if they do not exist, clone them - */ - - // todo: if cloning lots of accounts, we can likely make this more efficient - // todo: handle errors on cloning (like if the clone failed and the json file does not exist) - const newAccount = await cloneAccount({ - saveDir: DEFAULT_ACCOUNTS_DIR_TEMP, - address: token.address, - url: token.cluster || config.settings.cluster, - }); - - if (!newAccount) { - console.error("Failed to clone token:", token.address); - continue; - } - - /** - * todo: we should ensure the cloned account's `owner` program is auto cloned - * - from the same network as the token - * - not to override any manually defined configs settings - */ - - if (settings.force == true) { - // do nothing since we are going to force clone/refresh - } else if ( - doesFileExist( - path.join(config.settings.accountDir, `${token.address}.json`), - ) - ) { - // detect diff from any existing accounts already cloned - const oldAccount = loadJsonFile( - path.resolve(config.settings.accountDir, `${token.address}.json`), - ); - - if (JSON.stringify(newAccount) !== JSON.stringify(oldAccount)) { - warnMessage(`${token.address} has changed`); - - // todo: do we want to support another options to error on diff? - - if (settings.prompt) { - // console.log( - // "todo: prompt the user to determine if they want to clone", - // ); - } - - continue; - } else { - // console.log(" ", token.address, "did not change"); - // delete the new file one to avoid dirtying the git history - // unlinkSync( - // path.resolve(saveDirTemp, `${token.address}.json`), - // ); - } - } - } -} - -/** - * Clone all the accounts listed in the config toml file - */ -export async function cloneAccountsFromConfig( - config: SolanaToml, - settings: CloneSettings, - currentAccounts: ReturnType, -): Promise { - if (!config?.clone?.account) return false; - - // accumulator to track any `owner`s (aka programs) that will need to be cloned - const owners = new Map(); - const changedAccounts = new Map(); - - let newAccount: JsonAccountStruct | false = false; - - for (const key in config.clone.account) { - if (!config.clone.account.hasOwnProperty(key)) { - continue; - } - - const account = config.clone.account[key]; - // todo: should we validate any of the data? - - // set the default account info - if (!account?.name) account.name = key; - - if (account.frequency === "always") { - console.log("Always clone account:", account.address); - } else if (settings.force === true) { - console.log("Force clone account:", account.address); - } else if (currentAccounts.has(account.address)) { - newAccount = loadJsonFile( - path.join( - config.settings.accountDir, - currentAccounts.get(account.address), - ), - ); - - owners.set( - newAccount.account.owner, - account.cluster || config.settings.cluster, - ); - console.log("Skipping clone account:", account.address); - continue; - } else { - console.log("Clone account:", account.address); - } - - /** - * todo: we should likely check if any of the accounts are already cloned - * - for ones that are already cloned: - * - default not reclone - * - have some options to control when to clone and when to notify the user - * - - * - if they do not exist, clone them - */ - - // todo: if cloning lots of accounts, we can likely make this more efficient - // todo: handle errors on cloning (like if the clone failed and the json file does not exist) - newAccount = await cloneAccount({ - saveDir: DEFAULT_ACCOUNTS_DIR_TEMP, - address: account.address, - url: account.cluster || config.settings.cluster, - }); - - if (!newAccount) { - console.error("Failed to clone account:", account.address); - continue; - } - - owners.set( - newAccount.account.owner, - account.cluster || config.settings.cluster, - ); - - if (settings.force == true) { - // do nothing since we are going to force clone/refresh - } else if ( - doesFileExist( - path.join(config.settings.accountDir, `${account.address}.json`), - ) - ) { - const oldAccount = loadJsonFile( - path.resolve(config.settings.accountDir, `${account.address}.json`), - ); - - if (JSON.stringify(newAccount) !== JSON.stringify(oldAccount)) { - warnMessage(`${account.address} has changed`); - - // todo: do we want to support another options to error on diff? - - if (settings.prompt) { - changedAccounts.set(newAccount.account.owner, newAccount); - // console.log( - // "todo: prompt the user to determine if they want to clone", - // ); - } - - continue; - } else { - // console.log(" ", account.address, "did not change"); - // delete the new file one to avoid dirtying the git history - // unlinkSync( - // path.resolve(saveDirTemp, `${account.address}.json`), - // ); - } - } - } - - return { - owners, - changedAccounts, - }; -} - -/** - * Merge a hashmap of owners into the TOML config format - */ -export function mergeOwnersMapWithConfig( - owners: Map, - config: SolanaToml["clone"]["program"] = {}, -): SolanaToml["clone"]["program"] { - if (!owners) return config; - - // force remove the default programs - owners.delete("11111111111111111111111111111111"); - owners.delete("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - owners.delete("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); - // todo: add other programs that are builtins on the network - // todo: handle the builtin programs being migrated to the core-bpf - - if (owners.size == 0) return config; - - owners.forEach((cluster, address) => { - config[address] = { - address: address, - cluster: cluster, - }; - }); - - return config; -} - -export function validateExpectedCloneCounts( - accountDir: string, - clone: SolanaToml["clone"], -): { expected: number; actual: number } { - accountDir = path.resolve(accountDir); - const clonedAccounts = loadFileNamesToMap(accountDir, ".json"); - - const clonedPrograms = loadFileNamesToMap(accountDir, ".so"); - - const actual = clonedAccounts.size + clonedPrograms.size; - - // handle auto cloned programs - const autoCloned = new Map(); - - // count the number of deduplicated `owners` for all the cloned accounts - clonedAccounts.forEach((filename, key) => { - autoCloned.set( - loadJsonFile(path.join(accountDir, filename)).account - .owner, - "", // this value here does not matter - ); - }); - - let expected: number = 0; - - if (clone) { - if (clone.account) { - expected += Object.keys(clone.account).length; - } - if (clone.token) { - expected += Object.keys(clone.token).length; - } - if (clone.program) { - clone.program = - mergeOwnersMapWithConfig(autoCloned, clone?.program || {}) || {}; - - expected += Object.keys(clone.program).length; - } - } - - return { actual, expected }; -} diff --git a/src/lib/shell/deploy.ts b/src/lib/shell/deploy.ts deleted file mode 100644 index 31521dd..0000000 --- a/src/lib/shell/deploy.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { SolanaCluster } from "@/types/config"; -import { parseRpcUrlOrMoniker } from "@/lib/solana"; -import shellExec from "shell-exec"; -import { parseJson } from "@/lib/utils"; -import { ProgramInfoStruct } from "@/types/solana"; - -type DeployProgramCommandInput = { - programId: string; - programPath: string; - verbose?: boolean; - workspace?: boolean; - manifestPath?: string; - upgradeAuthority?: string; - keypair?: string; - url?: SolanaCluster | string; -}; - -export function buildDeployProgramCommand({ - programPath, - programId, - verbose = false, - manifestPath, - workspace = false, - url, - keypair, - upgradeAuthority, -}: DeployProgramCommandInput) { - const command: string[] = ["solana program deploy"]; - - // command.push(`--output json`); - - // note: when no url/cluster is specified, the user's `solana config` url will be used - if (url) { - command.push( - `--url ${parseRpcUrlOrMoniker(url, true /* enforce the "beta" label */)}`, - ); - } - - if (keypair) { - // todo: detect if the keypair exists? - command.push(`--keypair ${keypair}`); - } - - // when not set, defaults to the cli keypair - if (upgradeAuthority) { - // todo: detect if this file path exists? - command.push(`--upgrade-authority ${upgradeAuthority}`); - } - - // programId must be a file path for initial deployments - if (programId) { - // todo: detect if this file path exists? - command.push(`--program-id ${programId}`); - } - - // todo: validate the `programPath` file exists - command.push(programPath); - - return command.join(" "); -} - -export async function getDeployedProgramInfo( - programId: string, - cluster: string, -): Promise { - const command: string[] = [ - "solana program show", - "--output json", - `--url ${parseRpcUrlOrMoniker( - cluster, - true /* enforce the "beta" label */, - )}`, - programId, - ]; - - let { stderr, stdout } = await shellExec(command.join(" ")); - - if (stderr) { - const error = stderr.trim().split("\n"); - // let parsed: string | null = null; - - // console.log("error:"); - // console.log(error); - - return false; - } - - if (!stdout.trim()) return false; - - const programInfo = parseJson(stdout); - if (programInfo.authority == "none") programInfo.authority = false; - - return programInfo; -} diff --git a/src/lib/shell/test-validator.ts b/src/lib/shell/test-validator.ts deleted file mode 100644 index 0f29815..0000000 --- a/src/lib/shell/test-validator.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { spawn } from "child_process"; -import { resolve } from "path"; -import { rmSync } from "fs"; -import { - createFolders, - directoryExists, - loadFileNamesToMap, - moveFiles, -} from "@/lib/utils"; -import { - DEFAULT_ACCOUNTS_DIR, - DEFAULT_ACCOUNTS_DIR_LOADED, - DEFAULT_TEST_LEDGER_DIR, -} from "@/const/solana"; -import { warnMessage } from "@/lib/logs"; -import { SolanaTomlClone } from "@/types/config"; - -type BuildTestValidatorCommandInput = { - verbose?: boolean; - reset?: boolean; - accountDir?: string; - ledgerDir?: string; - authority?: string; - localPrograms?: SolanaTomlClone["program"]; -}; - -export function buildTestValidatorCommand({ - reset = false, - verbose = false, - accountDir = DEFAULT_ACCOUNTS_DIR, - ledgerDir = DEFAULT_TEST_LEDGER_DIR, - authority, - localPrograms, -}: BuildTestValidatorCommandInput = {}) { - const command: string[] = ["solana-test-validator"]; - - const stagingDir = resolve(DEFAULT_ACCOUNTS_DIR_LOADED); - - if (reset) { - rmSync(stagingDir, { - recursive: true, - force: true, - }); - - command.push("--reset"); - } - - if (ledgerDir) { - createFolders(ledgerDir); - command.push(`--ledger ${ledgerDir}`); - } - - // auto load in the account from the provided json files - if (accountDir) { - accountDir = resolve(accountDir); - - if (directoryExists(accountDir)) { - // clone the dir to a different temp location - - createFolders(stagingDir, false); - moveFiles(accountDir, stagingDir, true); - - // todo: update/reset the required account values (like `data` and maybe `owners`) - - command.push(`--account-dir ${stagingDir}`); - - // get the list of all the local binaries - const clonedPrograms = loadFileNamesToMap(accountDir, ".so"); - clonedPrograms.forEach((_value, address) => { - // console.log(`address: ${address}`), - - // todo: what is the real difference between using `--upgradeable-program` and `--bpf-program` here? - - if (authority) { - // todo: support setting a custom authority for each program (reading it from the config toml) - command.push( - `--upgradeable-program ${address}`, - resolve(accountDir, `${address}.so`), - authority, - ); - } else { - command.push( - `--bpf-program ${address}`, - resolve(accountDir, `${address}.so`), - ); - } - }); - - // todo: support loading in local binaries via `--bpf-program` - } else { - if (verbose) { - warnMessage(`Accounts directory does not exist: ${accountDir}`); - warnMessage("Skipping cloning of fixtures"); - } - } - } - - // load the local programs in directly from their build dir - if (localPrograms) { - for (const key in localPrograms) { - if (Object.prototype.hasOwnProperty.call(localPrograms, key)) { - command.push( - `--upgradeable-program ${localPrograms[key].address}`, - localPrograms[key].filePath, - authority, - ); - } - } - } - - // todo: support cloning programs via `--clone-upgradeable-program`? - - return command.join(" "); -} - -type RunTestValidatorInput = { - command?: string; - args?: string[]; -}; - -export function runTestValidator({ command, args }: RunTestValidatorInput) { - console.log(""); // print a line separator - - // Spawn a child process - const child = spawn(command, args, { - detached: false, // run the command in the same session - stdio: "inherit", // Stream directly to the user's terminal - shell: true, // Runs in shell for compatibility with shell commands - // cwd: // todo: do we want this? - }); - - // Handle child process events - child.on("error", (err) => { - console.error(`Failed to start the command: ${err.message}`); - }); - - child.on("exit", (code) => { - if (code === 0) { - console.log("Command executed successfully."); - } else { - console.error(`Command exited with code ${code}`); - } - }); -} diff --git a/src/lib/solana.ts b/src/lib/solana.ts deleted file mode 100644 index d5786dd..0000000 --- a/src/lib/solana.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Keypair } from "@solana/web3.js"; -import { doesFileExist, loadJsonFile } from "@/lib/utils"; -import { DEFAULT_KEYPAIR_PATH } from "@/const/solana"; -import { ProgramsByClusterLabels, SolanaCluster } from "@/types/config"; -import { warnMessage } from "@/lib/logs"; -import { checkCommand, VERSION_REGEX } from "@/lib/shell"; -import { PlatformToolsVersions } from "@/types"; - -export function loadKeypairFromFile( - filePath: string = DEFAULT_KEYPAIR_PATH, -): Keypair | null { - if (!doesFileExist(filePath)) return null; - const jsonBytes = loadJsonFile(filePath); - return Keypair.fromSecretKey(Buffer.from(jsonBytes)); -} - -/** - * Parse the provided url to correct it into a valid moniker or rpc url - */ -export function parseRpcUrlOrMoniker( - input: string, - includeBetaLabel: boolean = true, - allowUrl: boolean = true, -): SolanaCluster | string { - if (allowUrl && input.match(/^http?s/i)) { - try { - return new URL(input).toString(); - } catch (err) { - console.error("Unable to parse 'url':", input); - process.exit(1); - } - return input; - } else if (input.startsWith("local") || input.startsWith("l")) { - return "localhost"; - } else if (input.startsWith("t")) { - return "testnet"; - } else if (input.startsWith("d")) { - return "devnet"; - } else if (input.startsWith("m")) { - return includeBetaLabel ? "mainnet-beta" : "mainnet"; - } else { - warnMessage("Unable to parse url or moniker. Falling back to mainnet"); - return includeBetaLabel ? "mainnet-beta" : "mainnet"; - } -} - -/** - * Validate and sanitize the provided cluster moniker - */ -export function getSafeClusterMoniker( - cluster: SolanaCluster | string, - labels?: ProgramsByClusterLabels, -): false | keyof ProgramsByClusterLabels { - cluster = parseRpcUrlOrMoniker(cluster, true, false); - - if (!labels) { - labels = { - devnet: {}, - localnet: {}, - mainnet: {}, - testnet: {}, - }; - } - - // allow equivalent cluster names - switch (cluster) { - case "localhost": - case "localnet": { - cluster = "localnet"; - break; - } - case "mainnet": - case "mainnet-beta": { - cluster = "mainnet"; - break; - } - // we do not need to handle these since there is not a common equivalent - // case "devnet": - // case "testnet": - // default: - } - - if (Object.hasOwn(labels, cluster)) { - return cluster as keyof ProgramsByClusterLabels; - } else return false; -} - -/** - * Get the listing of the user's platform tools versions - */ -export async function getPlatformToolsVersions(): Promise { - const res = await checkCommand("cargo build-sbf --version"); - const tools: PlatformToolsVersions = {}; - - if (!res) return tools; - - res.split("\n").map((line) => { - line = line.trim().toLowerCase(); - if (!line) return; - - const version = VERSION_REGEX.exec(line)?.[1]; - - if (line.startsWith("rustc")) tools.rust = version; - if (line.startsWith("platform-tools")) tools.platformTools = version; - if (line.startsWith("solana-cargo-build-")) tools.buildSbf = version; - }); - - return tools; -} diff --git a/src/types/anchor.ts b/src/types/anchor.ts deleted file mode 100644 index dff22f7..0000000 --- a/src/types/anchor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ProgramsByClusterLabels, SolanaTomlCloneConfig } from "./config"; - -export type AnchorToml = { - configPath?: string; - programs?: ProgramsByClusterLabels; - test?: { - validator?: { - /** rpc url to use in order to clone */ - url?: string; - /** program to clone */ - clone?: SolanaTomlCloneConfig[]; - /** accounts to clone */ - // note: `account` also supports `filename` but we do nothing with that here - account?: SolanaTomlCloneConfig[]; - }; - }; -}; - -export type AnchorTomlWithConfigPath = Omit & - NonNullable<{ configPath: AnchorToml["configPath"] }>; diff --git a/src/types/cargo.ts b/src/types/cargo.ts deleted file mode 100644 index 93b6e34..0000000 --- a/src/types/cargo.ts +++ /dev/null @@ -1,22 +0,0 @@ -export type CargoToml = { - configPath?: string; - workspace?: { - members: string[]; - }; - package: { - name: string; - version: string; - description?: string; - edition: string; - }; - lib?: { - "crate-type"?: ("cdylib" | "lib")[]; - name?: string; - }; - dependencies?: { - [dependencyName: string]: string; - }; -}; - -export type CargoTomlWithConfigPath = Omit & - NonNullable<{ configPath: CargoToml["configPath"] }>; diff --git a/src/types/config.ts b/src/types/config.ts deleted file mode 100644 index ab6bbe6..0000000 --- a/src/types/config.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { JsonAccountStruct } from "@/lib/shell/clone"; - -export type SolanaTomlWithConfigPath = Omit & - NonNullable<{ configPath: SolanaToml["configPath"] }>; - -export type SolanaToml = { - configPath?: string; - settings?: Partial<{ - /** - * default cluster to use for all operations. - * when not set, fallback to the Solana CLI cluster - */ - cluster: SolanaCluster; - /** local directory path to store any cloned accounts */ - accountDir: string; - /** path to the local authority keypair */ - keypair: string; - /** - * custom rpc urls for each network that will be used to override the default public endpoints - */ - networks: Partial<{ - mainnet: string; - devnet: string; - testnet: string; - localnet: string; - }>; - }>; - programs?: ProgramsByClusterLabels; - clone?: Partial; -}; - -export type SolanaCluster = - | "mainnet" - | "mainnet-beta" - | "devnet" - | "testnet" - | "localhost" - | "localnet"; - -export type SolanaTomlCloneConfig = { - address: string; - filePath?: string; - name?: string; - cluster?: SolanaCluster | string; - // default - `cached` - frequency?: "cached" | "always"; -}; - -export type SolanaTomlClone = { - program: { - [key: string]: SolanaTomlCloneConfig; - }; - account: { - [key: string]: SolanaTomlCloneConfig; - }; - token: { - [key: string]: SolanaTomlCloneConfig & { - amount?: number; - mintAuthority?: string; - freezeAuthority?: string; - holders?: Array<{ - owner: string; - amount?: number; - }>; - }; - }; -}; - -/** - * Composite type of SolanaTomlClone["program"] but with the `filePath` required - */ -export type SolanaTomlCloneLocalProgram = { - [K in keyof SolanaTomlClone["program"]]: Omit< - SolanaTomlClone["program"][K], - "filePath" - > & { filePath: string }; -}; - -export type CloneSettings = { - autoClone?: boolean; - force?: boolean; - prompt?: boolean; -}; - -export type CloneAccountsFromConfigResult = { - owners: Map; - changedAccounts: Map; -}; - -export type ProgramsByClusterLabels = Partial<{ - localnet?: Record; - devnet?: Record; - testnet?: Record; - mainnet?: Record; -}>; diff --git a/src/types/index.ts b/src/types/index.ts index 08cc395..20f536f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,6 +6,7 @@ export type ToolNames = | "avm" | "anchor" | "yarn" + | "mucho" | "zest" | "verify" | "trident"; @@ -22,12 +23,6 @@ export type ToolCommandConfig = { dependencies?: ToolNames[]; }; -export type PlatformToolsVersions = Partial<{ - rust: string; - platformTools: string; - buildSbf: string; -}>; - /** * */ diff --git a/src/types/solana.ts b/src/types/solana.ts deleted file mode 100644 index 8080f34..0000000 --- a/src/types/solana.ts +++ /dev/null @@ -1,19 +0,0 @@ -export type ProgramInfoStruct = { - programId: string; - owner: string; - programdataAddress: string; - authority: false | "none" | string; - lastDeploySlot: number; - dataLen: number; - lamports: number; -}; - -export type SolanaCliYaml = Partial<{ - json_rpc_url: string; - websocket_url: string; - keypair_path: string; - address_labels: { - [key in string]: string; - }; - commitment: "processed" | "confirmed" | "finalized"; -}>;