Skip to content

Commit

Permalink
Add delete account feature to remove an account from the accounts fil…
Browse files Browse the repository at this point in the history
…e. (#743)

Closes #498 

## Introduced changes

Add account delete feature to remove an account from a network in the
accounts file.

## Breaking changes

N/A

## Checklist

<!-- Make sure all of these are complete -->

- [ X] Linked relevant issue
- [ X] Updated relevant documentation
- [ X] Added relevant tests
- [ X] Performed self-review of the code
- [ X] Added changes to `CHANGELOG.md`

---------

Co-authored-by: Wojciech Szymczyk <wojciech.szymczyk@swmansion.com>
Co-authored-by: Kamil Jankowski <kamil.jankowski.x@gmail.com>
  • Loading branch information
3 people committed Oct 23, 2023
1 parent b40058e commit 41ffd30
Show file tree
Hide file tree
Showing 14 changed files with 647 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Added

- `show-config` subcommand to display currently used configuration
- `account delete` command for removing accounts from the accounts file

## [0.8.3] - 2023-10-17

Expand Down
121 changes: 120 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ primitive-types = "0.12.1"
shellexpand = "3.1.0"
toml = "0.7.6"
rpassword = "7.2.0"
promptly = "0.3.1"

[dev-dependencies]
ctor.workspace = true
Expand Down
6 changes: 6 additions & 0 deletions crates/cast/src/helpers/response_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub struct AccountAddResponse {
pub add_profile: String,
}

#[derive(Serialize)]
pub struct AccountDeleteResponse {
pub result: String,
pub scarb_result: String,
}

#[derive(Serialize)]
pub struct MulticallNewResponse {
pub path: Utf8PathBuf,
Expand Down
25 changes: 24 additions & 1 deletion crates/cast/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use anyhow::{anyhow, Result};
use camino::Utf8PathBuf;
use cast::helpers::constants::{DEFAULT_ACCOUNTS_FILE, DEFAULT_MULTICALL_CONTENTS};
use cast::helpers::scarb_utils::{parse_scarb_config, CastConfig};
use cast::{get_account, get_block_id, get_chain_id, get_provider, print_command_result};
use cast::{
chain_id_to_network_name, get_account, get_block_id, get_chain_id, get_provider,
print_command_result,
};
use clap::{Parser, Subcommand};

mod starknet_commands;
Expand Down Expand Up @@ -276,6 +279,26 @@ async fn main() -> Result<()> {
print_command_result("account deploy", &mut result, cli.int_format, cli.json)?;
Ok(())
}
account::Commands::Delete(delete) => {
config.account = delete
.name
.ok_or_else(|| anyhow!("required argument --name not provided"))?;
let network_name = match delete.network {
Some(network) => network,
None => chain_id_to_network_name(get_chain_id(&provider).await?),
};

let mut result = starknet_commands::account::delete::delete(
&config.account,
&config.accounts_file,
&cli.path_to_scarb_toml,
delete.delete_profile,
&network_name,
);

print_command_result("account delete", &mut result, cli.int_format, cli.json)?;
Ok(())
}
},
Commands::ShowConfig(_) => {
let mut result = starknet_commands::show_config::show_config(
Expand Down
114 changes: 114 additions & 0 deletions crates/cast/src/starknet_commands/account/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use anyhow::{anyhow, bail, Context, Result};
use camino::Utf8PathBuf;
use cast::helpers::response_structs::AccountDeleteResponse;
use cast::helpers::scarb_utils::get_scarb_manifest;
use clap::Args;
use promptly::prompt;
use serde_json::Map;
use std::fs::File;
use std::io::Read;
use std::io::Write;

#[derive(Args, Debug)]
#[command(about = "Delete account information from the accounts file")]
pub struct Delete {
/// Name of the account to be deleted
#[clap(short, long)]
pub name: Option<String>,

/// If passed with false, Scarb profile won't be removed
#[clap(long, num_args = 1, default_value = "true")]
pub delete_profile: Option<bool>,

/// Network where the account exists; defaults to network of rpc node
#[clap(long)]
pub network: Option<String>,
}

#[allow(clippy::too_many_arguments)]
pub fn delete(
name: &str,
path: &Utf8PathBuf,
path_to_scarb_toml: &Option<Utf8PathBuf>,
delete_profile: Option<bool>,
network_name: &str,
) -> Result<AccountDeleteResponse> {
let contents = std::fs::read_to_string(path.clone()).context("Couldn't read accounts file")?;
let items: serde_json::Value = serde_json::from_str(&contents)
.map_err(|_| anyhow!("Failed to parse accounts file at {path}"))?;

if items[&network_name].is_null() {
bail!("No accounts defined for network {}", network_name);
}
if items[&network_name][&name].is_null() {
bail!("Account with name {name} does not exist")
}

let mut items: Map<String, serde_json::Value> =
serde_json::from_str(&contents).expect("failed to read file { path }");

// Let's ask confirmation
let prompt_text =
format!("Do you want to remove the account {name} deployed to network {network_name} from local file {path}? (Y/n)");
let input: String = prompt(prompt_text)?;

if !input.starts_with('Y') {
bail!("Delete aborted");
}

// get to the nested object "nested"
let nested = items
.get_mut(network_name)
.expect("can't find network")
.as_object_mut()
.expect("invalid network format");

// now remove the child from there
nested.remove(name);

std::fs::write(path.clone(), serde_json::to_string_pretty(&items).unwrap())?;

let mut scarb_result = "Account not removed from Scarb.toml".to_string();
// delete profile if delete_profile is true or not passed
if delete_profile == Some(true) {
let manifest_path = match path_to_scarb_toml.clone() {
Some(path) => path,
None => get_scarb_manifest().context("Failed to obtain manifest path from scarb")?,
};
let mut toml_content = String::new();
let mut file = File::open(manifest_path.clone()).expect("Failed to open file");
file.read_to_string(&mut toml_content)
.expect("Failed to read file");

// Parse the TOML content
let mut parsed_toml: toml::Value =
toml::de::from_str(&toml_content).expect("Failed to parse TOML");

// Remove the nested section from the Value object.
if let Some(table) = parsed_toml
.get_mut("tool")
.and_then(toml::Value::as_table_mut)
{
if let Some(nested_table) = table.get_mut("sncast").and_then(toml::Value::as_table_mut)
{
if nested_table.remove(name).is_some() {
scarb_result = "Account removed from Scarb.toml".to_string();
}
}
}

// Serialize the modified TOML data back to a string
let modified_toml = toml::to_string(&parsed_toml).expect("Failed to serialize TOML");

// Write the modified content back to the file
let mut file = File::create(manifest_path).expect("Failed to create file");
file.write_all(modified_toml.as_bytes())
.expect("Failed to write to file");
};

let result = "Account successfully removed".to_string();
Ok(AccountDeleteResponse {
result,
scarb_result,
})
}
Loading

0 comments on commit 41ffd30

Please sign in to comment.