use super::*;
use crate::TraceSrc;
use anyhow::{bail, Context};
use cargo_generate::{GenerateArgs, TemplatePath, Vcs};
use std::{fs, path::Path};
pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template";
#[derive(Clone, Debug, Default, Deserialize, Parser)]
#[clap(name = "new")]
pub struct Create {
pub path: PathBuf,
#[arg(short, long)]
pub name: Option<String>,
#[clap(short, long)]
pub template: Option<String>,
#[clap(long, conflicts_with_all(["revision", "tag"]))]
pub branch: Option<String>,
#[clap(long, conflicts_with_all(["branch", "tag"]))]
pub revision: Option<String>,
#[clap(long, conflicts_with_all(["branch", "revision"]))]
pub tag: Option<String>,
#[clap(long)]
pub subtemplate: Option<String>,
#[clap(short, long)]
pub option: Vec<String>,
#[clap(short, long)]
pub yes: bool,
#[arg(long, value_parser)]
pub vcs: Option<Vcs>,
}
impl Create {
pub async fn create(mut self) -> Result<StructuredOutput> {
if self.name.is_none() {
self.name = Some(create::name_from_path(&self.path)?);
}
check_path(&self.path).await?;
if self.template.is_none() {
check_connectivity().await?;
}
resolve_template_and_branch(&mut self.template, &mut self.branch);
std::fs::create_dir_all(&self.path)?;
let args = GenerateArgs {
define: self.option,
destination: Some(self.path),
init: true,
name: self.name,
silent: self.yes,
vcs: self.vcs,
template_path: TemplatePath {
auto_path: self.template,
branch: self.branch,
revision: self.revision,
subfolder: self.subtemplate,
tag: self.tag,
..Default::default()
},
verbose: crate::logging::VERBOSITY
.get()
.map(|f| f.verbose)
.unwrap_or(false),
..Default::default()
};
tracing::debug!(dx_src = ?TraceSrc::Dev, "Creating new project with args: {args:#?}");
let path = cargo_generate::generate(args)?;
_ = post_create(&path, &self.vcs.unwrap_or(Vcs::Git));
Ok(StructuredOutput::Success)
}
}
pub(crate) fn resolve_template_and_branch(
template: &mut Option<String>,
branch: &mut Option<String>,
) {
if template.is_none() {
use crate::dx_build_info::{PKG_VERSION_MAJOR, PKG_VERSION_MINOR};
*template = Some(DEFAULT_TEMPLATE.to_string());
if branch.is_none() {
*branch = Some(format!("v{PKG_VERSION_MAJOR}.{PKG_VERSION_MINOR}"));
}
};
}
pub(crate) fn name_from_path(path: &Path) -> Result<String> {
use path_absolutize::Absolutize;
Ok(path
.absolutize()?
.to_path_buf()
.file_name()
.context("Current path does not include directory name".to_string())?
.to_str()
.context("Current directory name is not a valid UTF-8 string".to_string())?
.to_string())
}
pub(crate) fn post_create(path: &Path, vcs: &Vcs) -> Result<()> {
let metadata = if let Some(parent_dir) = path.parent() {
match cargo_metadata::MetadataCommand::new()
.current_dir(parent_dir)
.exec()
{
Ok(v) => Some(v),
Err(cargo_metadata::Error::CargoMetadata { .. }) => None,
Err(err) => {
anyhow::bail!("Couldn't retrieve cargo metadata: {:?}", err)
}
}
} else {
None
};
let is_workspace = metadata.is_some();
metadata.and_then(|metadata| {
let cargo_toml_path = &metadata.workspace_root.join("Cargo.toml");
let cargo_toml_str = std::fs::read_to_string(cargo_toml_path).ok()?;
let relative_path = path.strip_prefix(metadata.workspace_root).ok()?;
let mut cargo_toml: toml_edit::DocumentMut = cargo_toml_str.parse().ok()?;
cargo_toml
.get_mut("workspace")?
.get_mut("members")?
.as_array_mut()?
.push(relative_path.display().to_string());
std::fs::write(cargo_toml_path, cargo_toml.to_string()).ok()
});
let mut cmd = Command::new("cargo");
let cmd = cmd.arg("fmt").current_dir(path);
let output = cmd.output().expect("failed to execute process");
if !output.status.success() {
tracing::error!(dx_src = ?TraceSrc::Dev, "cargo fmt failed");
tracing::error!(dx_src = ?TraceSrc::Build, "stdout: {}", String::from_utf8_lossy(&output.stdout));
tracing::error!(dx_src = ?TraceSrc::Build, "stderr: {}", String::from_utf8_lossy(&output.stderr));
}
let toml_paths = [path.join("Cargo.toml"), path.join("Dioxus.toml")];
for toml_path in &toml_paths {
let Ok(toml) = std::fs::read_to_string(toml_path) else {
continue;
};
let mut toml = toml.parse::<toml_edit::DocumentMut>().map_err(|e| {
anyhow::anyhow!("failed to parse toml at {}: {}", toml_path.display(), e)
})?;
toml.as_table_mut().fmt();
let as_string = toml.to_string();
let new_string = remove_triple_newlines(&as_string);
let mut file = std::fs::File::create(toml_path)?;
file.write_all(new_string.as_bytes())?;
}
let readme_path = path.join("README.md");
let readme = std::fs::read_to_string(&readme_path)?;
let new_readme = remove_triple_newlines(&readme);
let mut file = std::fs::File::create(readme_path)?;
file.write_all(new_readme.as_bytes())?;
if !is_workspace {
vcs.initialize(path, Some("main"), true)?;
}
tracing::info!(dx_src = ?TraceSrc::Dev, "Generated project at {}\n\n`cd` to your project and run `dx serve` to start developing.\nMore information is available in the generated `README.md`.\n\nBuild cool things! ✌️", path.display());
Ok(())
}
fn remove_triple_newlines(string: &str) -> String {
let mut new_string = String::new();
for char in string.chars() {
if char == '\n' && new_string.ends_with("\n\n") {
continue;
}
new_string.push(char);
}
new_string
}
pub(crate) async fn check_path(path: &std::path::PathBuf) -> Result<()> {
match fs::metadata(path) {
Ok(_metadata) => {
bail!(
"A file or directory with the given project name \"{}\" already exists.",
path.to_string_lossy()
)
}
Err(_err) => Ok(()),
}
}
pub(crate) async fn check_connectivity() -> Result<()> {
if crate::verbosity_or_default().offline {
return Ok(());
}
use crate::styles::{GLOW_STYLE, LINK_STYLE};
let client = reqwest::Client::new();
for x in 0..=5 {
tokio::select! {
res = client.head("https://github.com/DioxusLabs/").header("User-Agent", "dioxus-cli").send() => {
if res.is_ok() {
return Ok(());
}
tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
},
_ = tokio::time::sleep(std::time::Duration::from_millis(if x == 1 { 500 } else { 2000 })) => {}
}
if x == 0 {
eprintln!("{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for {LINK_STYLE}https://github.com/dioxuslabs{LINK_STYLE:#}...")
} else {
eprintln!(
"{GLOW_STYLE}warning{GLOW_STYLE:#}: ({x}/5) Taking a while, maybe your internet is down?"
);
}
}
bail!(
"Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project."
)
}