use std::path::{Path, PathBuf};
use anyhow::Context;
use dialoguer::Select;
use wasmer_api::backend::{gql::UserWithNamespaces, BackendClient};
use wasmer_deploy_schema::schema::{StringWebcPackageIdent, WebcPackageIdentifierV1};
use super::prompts::PackageCheckMode;
const WASM_STATIC_SERVER_PACKAGE: &str = "wasmer/static-web-server";
const WASM_STATIC_SERVER_VERSION: &str = "1";
const SAMPLE_INDEX_HTML: &str = r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{title}</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
max-width: 800px;
text-align: center;
padding: 50px 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 36px;
margin-bottom: 20px;
}
img {
max-width: 100%;
height: auto;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<img src="https://wasmer.io/images/logo.svg" alt="Your Logo" />
<h1>Hello World!</h1>
<h2>{title}</h2
<p>
Welcome to the Wasmer platform. This is a sample page that you can use as a
template to create a new app. <br />
Happy coding!
</p>
</div>
</body>
</html>
"#;
#[derive(clap::ValueEnum, Clone, Copy, Debug)]
pub enum PackageType {
#[clap(name = "regular")]
Regular,
#[clap(name = "static-website")]
StaticWebsite,
}
#[derive(Clone, Copy, Debug)]
pub enum CreateMode {
Create,
SelectExisting,
CreateOrSelect,
}
fn prompt_for_pacakge_type() -> Result<PackageType, anyhow::Error> {
Select::new()
.with_prompt("What type of package do you want to create?")
.items(&["Basic pacakge", "Static website"])
.interact()
.map(|idx| match idx {
0 => PackageType::Regular,
1 => PackageType::StaticWebsite,
_ => unreachable!(),
})
.map_err(anyhow::Error::from)
}
#[derive(Debug)]
pub struct PackageWizard {
pub path: PathBuf,
pub type_: Option<PackageType>,
pub create_mode: CreateMode,
pub namespace: Option<String>,
pub namespace_default: Option<String>,
pub name: Option<String>,
pub user: Option<UserWithNamespaces>,
}
pub struct PackageWizardOutput {
pub ident: StringWebcPackageIdent,
pub api: Option<wasmer_api::backend::gql::Package>,
pub local_path: Option<PathBuf>,
pub local_manifest: Option<wasmer_toml::Manifest>,
}
impl PackageWizard {
fn build_new_package(&self) -> Result<PackageWizardOutput, anyhow::Error> {
let owner = if let Some(namespace) = &self.namespace {
namespace.clone()
} else {
super::prompts::prompt_for_namespace(
"Who should own this package?",
None,
self.user.as_ref(),
)?
};
let ty = match self.type_ {
Some(t) => t,
None => prompt_for_pacakge_type()?,
};
let name = if let Some(name) = &self.name {
name.clone()
} else {
super::prompts::prompt_for_ident("What should the package be called?", None)?
};
if !self.path.is_dir() {
std::fs::create_dir_all(&self.path).with_context(|| {
format!("Failed to create directory: '{}'", self.path.display())
})?;
}
let ident = WebcPackageIdentifierV1 {
repository: None,
namespace: owner,
name,
tag: None,
};
let manifest = match ty {
PackageType::Regular => todo!(),
PackageType::StaticWebsite => initialize_static_site(&self.path, &ident)?,
};
let manifest_path = self.path.join("wasmer.toml");
let manifest_raw = manifest
.to_string()
.context("could not serialize package manifest")?;
std::fs::write(manifest_path, manifest_raw)
.with_context(|| format!("Failed to write manifest to '{}'", self.path.display()))?;
Ok(PackageWizardOutput {
ident: ident.into(),
api: None,
local_path: Some(self.path.clone()),
local_manifest: Some(manifest),
})
}
async fn prompt_existing_package(
&self,
api: Option<&BackendClient>,
) -> Result<PackageWizardOutput, anyhow::Error> {
let check = if api.is_some() {
Some(PackageCheckMode::MustExist)
} else {
None
};
eprintln!("Enter the name of an existing package:");
let (ident, api) = super::prompts::prompt_for_package("Package", None, check, api).await?;
Ok(PackageWizardOutput {
ident,
api,
local_path: None,
local_manifest: None,
})
}
pub async fn run(
self,
api: Option<&BackendClient>,
) -> Result<PackageWizardOutput, anyhow::Error> {
match self.create_mode {
CreateMode::Create => self.build_new_package(),
CreateMode::SelectExisting => self.prompt_existing_package(api).await,
CreateMode::CreateOrSelect => {
let index = Select::new()
.with_prompt("What package do you want to use?")
.items(&["Create new package", "Use existing package"])
.default(0)
.interact()?;
match index {
0 => self.build_new_package(),
1 => self.prompt_existing_package(api).await,
other => {
unreachable!("Unexpected index: {other}");
}
}
}
}
}
}
fn initialize_static_site(
path: &Path,
ident: &WebcPackageIdentifierV1,
) -> Result<wasmer_toml::Manifest, anyhow::Error> {
let full_name = format!("{}/{}", ident.namespace, ident.name);
let pubdir_name = "public";
let pubdir = path.join(pubdir_name);
if !pubdir.is_dir() {
std::fs::create_dir_all(&pubdir)
.with_context(|| format!("Failed to create directory: '{}'", pubdir.display()))?;
}
let index = pubdir.join("index.html");
if !index.is_file() {
std::fs::write(&index, SAMPLE_INDEX_HTML)
.with_context(|| "Could not write index.html file".to_string())?;
}
let manifest = wasmer_toml::Manifest {
base_directory_path: path.to_owned(),
module: None,
package: wasmer_toml::Package {
name: full_name.clone(),
version: "0.0.0".parse().unwrap(),
description: format!("{full_name} website"),
license: None,
license_file: None,
readme: None,
repository: None,
homepage: None,
wasmer_extra_flags: None,
disable_command_rename: false,
rename_commands_to_raw_command_name: false,
},
dependencies: Some(
vec![(
WASM_STATIC_SERVER_PACKAGE.to_string(),
WASM_STATIC_SERVER_VERSION.to_string(),
)]
.into_iter()
.collect(),
),
fs: Some(
vec![("public".to_string(), pubdir_name.into())]
.into_iter()
.collect(),
),
command: None,
};
Ok(manifest)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_package_wizard_create_static_site() {
let dir = tempfile::tempdir().unwrap();
PackageWizard {
path: dir.path().to_owned(),
type_: Some(PackageType::StaticWebsite),
create_mode: CreateMode::Create,
namespace: Some("christoph".to_string()),
namespace_default: None,
name: Some("test123".to_string()),
user: None,
}
.run(None)
.await
.unwrap();
let manifest = std::fs::read_to_string(dir.path().join("wasmer.toml")).unwrap();
pretty_assertions::assert_eq!(
manifest,
r#"[package]
name = 'christoph/test123'
version = '0.0.0'
description = 'christoph/test123 website'
[dependencies]
"wasmer/static-web-server" = '1'
[fs]
public = 'public'
"#,
);
assert!(dir.path().join("public").join("index.html").is_file());
}
}