Skip to content

Commit 83c4a53

Browse files
upgrade a project to a scaffold project (#114)
* upgrade a project to a scaffold project * better constructor args handling of enums and booleans * remove unused enum arg handlings * feat: provide build_custom_cmd while waiting for it to be exposed * properly handle complex enums and objects * fix: missing reference issue * fix: boolean value --------- Co-authored-by: Willem Wyndham <willem@ahalabs.dev>
1 parent d5050d1 commit 83c4a53

10 files changed

Lines changed: 697 additions & 11 deletions

File tree

.github/workflows/rust.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ jobs:
2626
- run: rustup update
2727
- run: rustup target add wasm32v1-none
2828
- run: rustup component add rustfmt clippy
29-
- run: sudo apt-get update # required for the machine to build properly
29+
- name: Install system dependencies
30+
run: |
31+
sudo apt-get update
32+
sudo apt-get install -y libdbus-1-dev pkg-config
3033
- uses: taiki-e/install-action@nextest
3134
- uses: taiki-e/install-action@just
3235
- uses: cargo-bins/cargo-binstall@main

.github/workflows/tests.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
name: RPC Tests
32
on:
43
push:
@@ -34,6 +33,7 @@ jobs:
3433
- name: Install system dependencies
3534
run: |
3635
sudo apt-get update
36+
sudo apt-get install -y libdbus-1-dev pkg-config
3737
- uses: actions/cache@v3
3838
with:
3939
path: |
@@ -52,4 +52,3 @@ jobs:
5252
- run: just setup
5353
- run: just create
5454
- run: just test-integration
55-

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stellar-scaffold-cli/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ doctest = false
2828
[dependencies]
2929
openssl = { version = "0.10", features = ["vendored"] }
3030
stellar-build = { path = "../stellar-build", version = "0.0.2" }
31-
stellar-cli = { workspace = true}
32-
soroban-rpc = { workspace = true}
31+
stellar-cli = { workspace = true }
32+
soroban-rpc = { workspace = true }
33+
soroban-spec-tools = "22.8.1"
3334
clap = { version = "4.1.8", features = [
3435
"derive",
3536
"env",
3637
"deprecated",
3738
"string",
3839
] }
3940
degit = "0.1.3"
41+
dialoguer = "0.11.0"
4042
dirs = "6.0.0"
4143
flate2 = "1.0"
4244
tar = "0.4"

crates/stellar-scaffold-cli/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
CLI toolkit for Stellar smart contract development, providing project scaffolding, build automation, and development workflow tools.
44

5-
Stellar Scaffold CLI comes with three main commands:
5+
Stellar Scaffold CLI comes with four main commands:
66

77
* `stellar scaffold init` - Creates a new Stellar smart contract project with best practices and configurations in place, including an `environments.toml` file for managing network settings, accounts, and contracts across different environments.
88

9+
* `stellar scaffold upgrade` - Transforms an existing Soroban workspace into a full scaffold project by adding frontend components, environment configurations, and project structure. Preserves existing contracts while adding the complete development toolkit.
10+
911
* `stellar scaffold build` - Manages two key build processes:
1012
* Build smart contracts with metadata and handle dependencies
1113
* Generate TypeScript client packages for frontend integration
@@ -16,6 +18,8 @@ Stellar Scaffold CLI comes with three main commands:
1618

1719
## Getting Started
1820

21+
### New Project
22+
1923
1. Install the CLI:
2024
```bash
2125
cargo install stellar-scaffold-cli
@@ -27,7 +31,7 @@ Or [`cargo-binstall`](github.com/cargo-bins/cargo-binstall):
2731
cargo binstall stellar-scaffold-cli
2832
```
2933

30-
1. Create a new project:
34+
2. Create a new project:
3135
```bash
3236
stellar scaffold init my-project
3337
cd my-project
@@ -38,6 +42,20 @@ This creates:
3842
- A frontend application based on [scaffold-stellar-frontend](https://github.com/AhaLabs/scaffold-stellar-frontend)
3943
- Environment configurations for both contract and frontend development
4044

45+
### Upgrading Existing Workspace
46+
47+
If you have an existing Soroban workspace, you can upgrade it to a full scaffold project:
48+
```bash
49+
cd my-existing-workspace
50+
stellar scaffold upgrade
51+
```
52+
53+
This will:
54+
- Add the frontend application and development tools
55+
- Generate `environments.toml` with your existing contracts
56+
- Set up environment files and configurations
57+
- Preserve all your existing contract code and structure
58+
4159
3. Set up your environment:
4260
```bash
4361
cp .env.example .env
@@ -51,7 +69,6 @@ stellar scaffold watch --build-clients
5169
## Environment Configuration
5270

5371
Projects use `environments.toml` to define network settings, accounts, and contract configurations for different environments. Example:
54-
5572
```toml
5673
[development]
5774
network = {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use clap::value_parser;
2+
3+
use heck::ToKebabCase;
4+
use soroban_spec_tools::Spec;
5+
use std::collections::HashMap;
6+
use std::fmt::Debug;
7+
use std::path::PathBuf;
8+
use stellar_cli::{
9+
config::{self, sc_address},
10+
xdr::{self, ScSpecTypeDef},
11+
};
12+
13+
#[derive(thiserror::Error, Debug)]
14+
pub enum Error {
15+
#[error("function {0} was not found in the contract")]
16+
FunctionNotFoundInContractSpec(String),
17+
#[error(transparent)]
18+
Xdr(#[from] xdr::Error),
19+
#[error(transparent)]
20+
StrVal(#[from] soroban_spec_tools::Error),
21+
#[error(transparent)]
22+
ScAddress(#[from] sc_address::Error),
23+
#[error(transparent)]
24+
Config(#[from] config::Error),
25+
// #[error("")]
26+
// HelpMessage(String),
27+
}
28+
29+
pub fn build_custom_cmd(name: &str, spec: &Spec) -> Result<clap::Command, Error> {
30+
let func = spec
31+
.find_function(name)
32+
.map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?;
33+
34+
// Parse the function arguments
35+
let inputs_map = &func
36+
.inputs
37+
.iter()
38+
.map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone()))
39+
.collect::<HashMap<String, ScSpecTypeDef>>();
40+
let name: &'static str = Box::leak(name.to_string().into_boxed_str());
41+
let mut cmd = clap::Command::new(name)
42+
.no_binary_name(true)
43+
.term_width(300)
44+
.max_term_width(300);
45+
let kebab_name = name.to_kebab_case();
46+
if kebab_name != name {
47+
cmd = cmd.alias(kebab_name);
48+
}
49+
let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str());
50+
let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str());
51+
52+
cmd = cmd.about(Some(doc)).long_about(long_doc);
53+
for (name, type_) in inputs_map {
54+
let mut arg = clap::Arg::new(name);
55+
let file_arg_name = fmt_arg_file_name(name);
56+
let mut file_arg = clap::Arg::new(&file_arg_name);
57+
arg = arg
58+
.long(name)
59+
.alias(name.to_kebab_case())
60+
.num_args(1)
61+
.value_parser(clap::builder::NonEmptyStringValueParser::new())
62+
.long_help(spec.doc(name, type_)?);
63+
64+
file_arg = file_arg
65+
.long(&file_arg_name)
66+
.alias(file_arg_name.to_kebab_case())
67+
.num_args(1)
68+
.hide(true)
69+
.value_parser(value_parser!(PathBuf))
70+
.conflicts_with(name);
71+
72+
if let Some(value_name) = spec.arg_value_name(type_, 0) {
73+
let value_name: &'static str = Box::leak(value_name.into_boxed_str());
74+
arg = arg.value_name(value_name);
75+
}
76+
77+
// Set up special-case arg rules
78+
arg = match type_ {
79+
ScSpecTypeDef::Bool => arg
80+
.num_args(0..1)
81+
.default_missing_value("true")
82+
.default_value("false")
83+
.num_args(0..=1),
84+
ScSpecTypeDef::Option(_val) => arg.required(false),
85+
ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => {
86+
arg.allow_hyphen_values(true)
87+
}
88+
_ => arg,
89+
};
90+
91+
cmd = cmd.arg(arg);
92+
cmd = cmd.arg(file_arg);
93+
}
94+
Ok(cmd)
95+
}
96+
97+
fn fmt_arg_file_name(name: &str) -> String {
98+
format!("{name}-file-path")
99+
}
100+
101+
fn arg_file_help(docs: &str) -> String {
102+
format!(
103+
r"{docs}
104+
Usage Notes:
105+
Each arg has a corresponding --<arg_name>-file-path which is a path to a file containing the corresponding JSON argument.
106+
Note: The only types which aren't JSON are Bytes and BytesN, which are raw bytes"
107+
)
108+
}

crates/stellar-scaffold-cli/src/commands/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod build;
77
pub mod generate;
88
pub mod init;
99
pub mod update_env;
10+
pub mod upgrade;
1011
pub mod watch;
1112

1213
const ABOUT: &str = "Build smart contracts with frontend support";
@@ -41,10 +42,11 @@ impl Root {
4142
pub async fn run(&mut self) -> Result<(), Error> {
4243
match &mut self.cmd {
4344
Cmd::Init(init_info) => init_info.run(&self.global_args).await?,
44-
Cmd::Build(build_info) => build_info.run().await?, // todo pass global args to build and watch
45+
Cmd::Build(build_info) => build_info.run().await?,
4546
Cmd::Generate(generate) => match &mut generate.cmd {
4647
generate::Command::Contract(contract) => contract.run(&self.global_args).await?,
4748
},
49+
Cmd::Upgrade(upgrade_info) => upgrade_info.run(&self.global_args).await?,
4850
Cmd::UpdateEnv(e) => e.run()?,
4951
Cmd::Watch(watch_info) => watch_info.run().await?,
5052
}
@@ -71,6 +73,9 @@ pub enum Cmd {
7173
/// generate contracts
7274
Generate(generate::Cmd),
7375

76+
/// Upgrade an existing Soroban workspace to a scaffold project
77+
Upgrade(upgrade::Cmd),
78+
7479
/// Update an environment variable in a .env file
7580
UpdateEnv(update_env::Cmd),
7681

@@ -88,6 +93,8 @@ pub enum Error {
8893
#[error(transparent)]
8994
Contract(#[from] generate::contract::Error),
9095
#[error(transparent)]
96+
Upgrade(#[from] upgrade::Error),
97+
#[error(transparent)]
9198
UpdateEnv(#[from] update_env::Error),
9299
#[error(transparent)]
93100
Watch(#[from] watch::Error),

0 commit comments

Comments
 (0)