outlayer_cli/commands/
create.rs1use anyhow::{Context, Result};
2use std::path::PathBuf;
3
4use crate::config::{
5 self, BuildSection, DeploySection, NetworkConfig, ProjectConfig, ProjectSection,
6 RunSection,
7};
8
9mod basic {
12 pub const CARGO_TOML: &str = include_str!("../../templates/basic/Cargo.toml.tmpl");
13 pub const MAIN_RS: &str = include_str!("../../templates/basic/src/main.rs");
14 pub const BUILD_SH: &str = include_str!("../../templates/basic/build.sh");
15 pub const GITIGNORE: &str = include_str!("../../templates/basic/gitignore.tmpl");
16}
17
18mod contract {
19 pub const CARGO_TOML: &str = include_str!("../../templates/contract/Cargo.toml.tmpl");
20 pub const MAIN_RS: &str = include_str!("../../templates/contract/src/main.rs");
21 pub const BUILD_SH: &str = include_str!("../../templates/contract/build.sh");
22 pub const GITIGNORE: &str = include_str!("../../templates/contract/gitignore.tmpl");
23}
24
25mod shared {
26 pub const SKILL_MD: &str = include_str!("../../templates/shared/skill.md");
27}
28
29struct TemplateFiles {
30 cargo_toml: &'static str,
31 main_rs: &'static str,
32 build_sh: &'static str,
33 gitignore: &'static str,
34}
35
36fn get_template(name: &str) -> Result<TemplateFiles> {
37 match name {
38 "basic" => Ok(TemplateFiles {
39 cargo_toml: basic::CARGO_TOML,
40 main_rs: basic::MAIN_RS,
41 build_sh: basic::BUILD_SH,
42 gitignore: basic::GITIGNORE,
43 }),
44 "contract" => Ok(TemplateFiles {
45 cargo_toml: contract::CARGO_TOML,
46 main_rs: contract::MAIN_RS,
47 build_sh: contract::BUILD_SH,
48 gitignore: contract::GITIGNORE,
49 }),
50 _ => anyhow::bail!(
51 "Unknown template: {name}. Available: basic, contract"
52 ),
53 }
54}
55
56pub async fn create(
58 network: &NetworkConfig,
59 name: &str,
60 template: &str,
61 dir: Option<String>,
62) -> Result<()> {
63 let creds = config::load_credentials(network)?;
64
65 let parent = dir
67 .map(PathBuf::from)
68 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
69 let project_dir = parent.join(name);
70
71 if project_dir.exists() && project_dir.join("outlayer.toml").exists() {
72 anyhow::bail!(
73 "Project already exists at {}. Use 'outlayer deploy' to update.",
74 project_dir.display()
75 );
76 }
77
78 std::fs::create_dir_all(&project_dir)
79 .with_context(|| format!("Failed to create directory {}", project_dir.display()))?;
80
81 eprintln!(
82 "Creating project \"{}\" ({} template) for {} in {}/",
83 name,
84 template,
85 creds.account_id,
86 project_dir.display()
87 );
88
89 scaffold(&project_dir, name, template)?;
91
92 let project_config = ProjectConfig {
94 project: ProjectSection {
95 name: name.to_string(),
96 owner: creds.account_id.clone(),
97 },
98 build: Some(BuildSection {
99 target: "wasm32-wasip2".to_string(),
100 source: "github".to_string(),
101 }),
102 deploy: Some(DeploySection {
103 repo: None,
104 wasm_path: None,
105 }),
106 run: Some(RunSection {
107 max_instructions: Some(1_000_000_000),
108 max_memory_mb: Some(128),
109 max_execution_seconds: Some(60),
110 secrets_profile: Some("default".to_string()),
111 payment_key_nonce: None,
112 }),
113 network: Some(network.network_id.clone()),
114 };
115
116 let toml_data = toml::to_string_pretty(&project_config)?;
117 std::fs::write(project_dir.join("outlayer.toml"), toml_data)?;
118 eprintln!(" Created outlayer.toml");
119
120 eprintln!("\nYour agent is ready. Next steps:");
121 eprintln!(" cd {name}");
122 eprintln!(" git init && git remote add origin <your-repo-url>");
123 eprintln!(" # edit src/main.rs");
124 eprintln!(" git push");
125 eprintln!(" outlayer deploy {name}");
126 eprintln!(
127 " outlayer run {}/{name} '{{\"command\": \"hello\"}}'",
128 creds.account_id
129 );
130 eprintln!();
131 eprintln!("To create a payment key for HTTPS calls:");
132 eprintln!(" outlayer keys create");
133
134 Ok(())
135}
136
137fn scaffold(project_dir: &PathBuf, project_name: &str, template_name: &str) -> Result<()> {
138 let cargo_name = project_name.replace('-', "_");
139 let tmpl = get_template(template_name)?;
140
141 let substitute = |s: &str| s.replace("{{PROJECT_NAME}}", &cargo_name);
142
143 std::fs::write(project_dir.join("Cargo.toml"), substitute(tmpl.cargo_toml))?;
145
146 std::fs::create_dir_all(project_dir.join("src"))?;
148 std::fs::write(project_dir.join("src/main.rs"), substitute(tmpl.main_rs))?;
149
150 let build_sh = project_dir.join("build.sh");
152 std::fs::write(&build_sh, substitute(tmpl.build_sh))?;
153 #[cfg(unix)]
154 {
155 use std::os::unix::fs::PermissionsExt;
156 std::fs::set_permissions(&build_sh, std::fs::Permissions::from_mode(0o755))?;
157 }
158
159 std::fs::write(project_dir.join(".gitignore"), tmpl.gitignore)?;
161
162 std::fs::write(project_dir.join("skill.md"), shared::SKILL_MD)?;
164
165 let desc = match template_name {
166 "contract" => "Rust + wasm32-wasip2 + OutLayer SDK (VRF, storage, RPC)",
167 _ => "Rust + wasm32-wasip2",
168 };
169 eprintln!(" Generated project scaffold ({desc})");
170 Ok(())
171}