Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.

app password

Changed files
+190 -176
cli
+94 -153
cli/Cargo.lock
···
]
[[package]]
-
name = "async-lock"
-
version = "3.4.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
-
dependencies = [
-
"event-listener",
-
"event-listener-strategy",
-
"pin-project-lite",
-
]
-
-
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb"
[[package]]
-
name = "concurrent-queue"
-
version = "2.5.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
-
dependencies = [
-
"crossbeam-utils",
-
]
-
-
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+
dependencies = [
+
"core-foundation-sys",
+
"libc",
+
]
+
+
[[package]]
+
name = "core-foundation"
+
version = "0.10.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
···
]
[[package]]
-
name = "crossbeam-epoch"
-
version = "0.9.18"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
-
dependencies = [
-
"crossbeam-utils",
-
]
-
-
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
-
name = "event-listener"
-
version = "5.4.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
-
dependencies = [
-
"concurrent-queue",
-
"parking",
-
"pin-project-lite",
-
]
-
-
[[package]]
-
name = "event-listener-strategy"
-
version = "0.5.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
-
dependencies = [
-
"event-listener",
-
"pin-project-lite",
-
]
-
-
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "futures"
+
version = "0.3.31"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+
dependencies = [
+
"futures-channel",
+
"futures-core",
+
"futures-executor",
+
"futures-io",
+
"futures-sink",
+
"futures-task",
+
"futures-util",
+
]
+
+
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
+
"futures-sink",
]
[[package]]
···
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+
[[package]]
+
name = "futures-executor"
+
version = "0.3.31"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+
dependencies = [
+
"futures-core",
+
"futures-task",
+
"futures-util",
+
]
[[package]]
name = "futures-io"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
+
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
···
[[package]]
-
name = "home"
-
version = "0.5.12"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
-
dependencies = [
-
"windows-sys 0.61.2",
-
]
-
-
[[package]]
name = "html5ever"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "jacquard"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"bytes",
"getrandom 0.2.16",
···
[[package]]
name = "jacquard-api"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"bon",
"bytes",
···
[[package]]
name = "jacquard-common"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"base64 0.22.1",
"bon",
···
[[package]]
name = "jacquard-derive"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"heck 0.5.0",
"jacquard-lexicon",
···
[[package]]
name = "jacquard-identity"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"bon",
"bytes",
···
"jacquard-common",
"jacquard-lexicon",
"miette",
-
"moka",
+
"mini-moka",
"percent-encoding",
"reqwest",
"serde",
···
[[package]]
name = "jacquard-lexicon"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"cid",
"dashmap",
···
[[package]]
name = "jacquard-oauth"
-
version = "0.8.0"
+
version = "0.9.0"
dependencies = [
"base64 0.22.1",
"bytes",
···
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
-
name = "malloc_buf"
-
version = "0.0.6"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
-
dependencies = [
-
"libc",
-
]
-
-
[[package]]
name = "markup5ever"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
+
name = "mini-moka"
+
version = "0.11.0"
+
source = "git+https://github.com/moka-rs/mini-moka?rev=da864e849f5d034f32e02197fee9bb5d5af36d3d#da864e849f5d034f32e02197fee9bb5d5af36d3d"
+
dependencies = [
+
"crossbeam-channel",
+
"crossbeam-utils",
+
"dashmap",
+
"smallvec",
+
"tagptr",
+
"triomphe",
+
"web-time",
+
]
+
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
-
name = "moka"
-
version = "0.12.11"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077"
-
dependencies = [
-
"async-lock",
-
"crossbeam-channel",
-
"crossbeam-epoch",
-
"crossbeam-utils",
-
"equivalent",
-
"event-listener",
-
"futures-util",
-
"parking_lot",
-
"portable-atomic",
-
"rustc_version",
-
"smallvec",
-
"tagptr",
-
"uuid",
-
]
-
-
[[package]]
name = "multibase"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
-
name = "objc"
-
version = "0.2.7"
+
name = "objc2"
+
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+
checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [
-
"malloc_buf",
+
"objc2-encode",
+
]
+
+
[[package]]
+
name = "objc2-encode"
+
version = "4.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+
[[package]]
+
name = "objc2-foundation"
+
version = "0.3.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+
dependencies = [
+
"bitflags",
+
"objc2",
[[package]]
···
[[package]]
-
name = "parking"
-
version = "2.2.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
-
-
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
-
-
[[package]]
-
name = "portable-atomic"
-
version = "1.11.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "potential_utf"
···
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
[[package]]
-
name = "raw-window-handle"
-
version = "0.5.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
-
-
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
-
name = "rustc_version"
-
version = "0.4.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-
dependencies = [
-
"semver",
-
]
-
-
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
···
"core-foundation-sys",
"libc",
-
-
[[package]]
-
name = "semver"
-
version = "1.0.27"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
···
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
"system-configuration-sys",
···
[[package]]
+
name = "triomphe"
+
version = "0.1.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
+
+
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
-
name = "uuid"
-
version = "1.18.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
-
dependencies = [
-
"getrandom 0.3.4",
-
"js-sys",
-
"wasm-bindgen",
-
]
-
-
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "webbrowser"
-
version = "0.8.15"
+
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b"
+
checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97"
dependencies = [
-
"core-foundation",
-
"home",
+
"core-foundation 0.10.1",
"jni",
"log",
"ndk-context",
-
"objc",
-
"raw-window-handle",
+
"objc2",
+
"objc2-foundation",
"url",
"web-sys",
···
"bytes",
"clap",
"flate2",
+
"futures",
"jacquard",
"jacquard-api",
"jacquard-common",
+1
cli/Cargo.toml
···
walkdir = "2.5"
mime_guess = "2.0"
bytes = "1.10"
+
futures = "0.3.31"
+95 -23
cli/src/main.rs
···
use clap::Parser;
use jacquard::CowStr;
-
use jacquard::client::{Agent, FileAuthStore, AgentSessionExt};
+
use jacquard::client::{Agent, FileAuthStore, AgentSessionExt, MemoryCredentialSession};
use jacquard::oauth::client::OAuthClient;
use jacquard::oauth::loopback::LoopbackConfig;
use jacquard::prelude::IdentityResolver;
···
use flate2::write::GzEncoder;
use std::io::Write;
use base64::Engine;
+
use futures::stream::{self, StreamExt};
use place_wisp::fs::*;
···
#[arg(short, long)]
site: Option<String>,
-
/// Path to auth store file (will be created if missing)
+
/// Path to auth store file (will be created if missing, only used with OAuth)
#[arg(long, default_value = "/tmp/wisp-oauth-session.json")]
store: String,
+
+
/// App Password for authentication (alternative to OAuth)
+
#[arg(long)]
+
password: Option<CowStr<'static>>,
}
#[tokio::main]
async fn main() -> miette::Result<()> {
let args = Args::parse();
-
let oauth = OAuthClient::with_default_config(FileAuthStore::new(&args.store));
+
// Dispatch to appropriate authentication method
+
if let Some(password) = args.password {
+
run_with_app_password(args.input, password, args.path, args.site).await
+
} else {
+
run_with_oauth(args.input, args.store, args.path, args.site).await
+
}
+
}
+
+
/// Run deployment with app password authentication
+
async fn run_with_app_password(
+
input: CowStr<'static>,
+
password: CowStr<'static>,
+
path: PathBuf,
+
site: Option<String>,
+
) -> miette::Result<()> {
+
let (session, auth) =
+
MemoryCredentialSession::authenticated(input, password, None).await?;
+
println!("Signed in as {}", auth.handle);
+
+
let agent: Agent<_> = Agent::from(session);
+
deploy_site(&agent, path, site).await
+
}
+
+
/// Run deployment with OAuth authentication
+
async fn run_with_oauth(
+
input: CowStr<'static>,
+
store: String,
+
path: PathBuf,
+
site: Option<String>,
+
) -> miette::Result<()> {
+
let oauth = OAuthClient::with_default_config(FileAuthStore::new(&store));
let session = oauth
-
.login_with_local_server(args.input, Default::default(), LoopbackConfig::default())
+
.login_with_local_server(input, Default::default(), LoopbackConfig::default())
.await?;
let agent: Agent<_> = Agent::from(session);
+
deploy_site(&agent, path, site).await
+
}
+
/// Deploy the site using the provided agent
+
async fn deploy_site(
+
agent: &Agent<impl jacquard::client::AgentSession + IdentityResolver>,
+
path: PathBuf,
+
site: Option<String>,
+
) -> miette::Result<()> {
// Verify the path exists
-
if !args.path.exists() {
-
return Err(miette::miette!("Path does not exist: {}", args.path.display()));
+
if !path.exists() {
+
return Err(miette::miette!("Path does not exist: {}", path.display()));
}
// Get site name
-
let site_name = args.site.unwrap_or_else(|| {
-
args.path
+
let site_name = site.unwrap_or_else(|| {
+
path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("site")
···
println!("Deploying site '{}'...", site_name);
// Build directory tree
-
let root_dir = build_directory(&agent, &args.path).await?;
+
let root_dir = build_directory(agent, &path).await?;
// Count total files
let file_count = count_files(&root_dir);
···
) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<Directory<'static>>> + 'a>>
{
Box::pin(async move {
-
let mut entries = Vec::new();
+
// Collect all directory entries first
+
let dir_entries: Vec<_> = std::fs::read_dir(dir_path)
+
.into_diagnostic()?
+
.collect::<Result<Vec<_>, _>>()
+
.into_diagnostic()?;
+
+
// Separate files and directories
+
let mut file_tasks = Vec::new();
+
let mut dir_tasks = Vec::new();
-
for entry in std::fs::read_dir(dir_path).into_diagnostic()? {
-
let entry = entry.into_diagnostic()?;
+
for entry in dir_entries {
let path = entry.path();
let name = entry.file_name();
let name_str = name.to_str()
-
.ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))?;
+
.ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))?
+
.to_string();
// Skip hidden files
if name_str.starts_with('.') {
···
let metadata = entry.metadata().into_diagnostic()?;
if metadata.is_file() {
+
file_tasks.push((name_str, path));
+
} else if metadata.is_dir() {
+
dir_tasks.push((name_str, path));
+
}
+
}
+
+
// Process files concurrently with a limit of 5
+
let file_entries: Vec<Entry> = stream::iter(file_tasks)
+
.map(|(name, path)| async move {
let file_node = process_file(agent, &path).await?;
-
entries.push(Entry::new()
-
.name(CowStr::from(name_str.to_string()))
+
Ok::<_, miette::Report>(Entry::new()
+
.name(CowStr::from(name))
.node(EntryNode::File(Box::new(file_node)))
-
.build());
-
} else if metadata.is_dir() {
-
let subdir = build_directory(agent, &path).await?;
-
entries.push(Entry::new()
-
.name(CowStr::from(name_str.to_string()))
-
.node(EntryNode::Directory(Box::new(subdir)))
-
.build());
-
}
+
.build())
+
})
+
.buffer_unordered(5)
+
.collect::<Vec<_>>()
+
.await
+
.into_iter()
+
.collect::<miette::Result<Vec<_>>>()?;
+
+
// Process directories recursively (sequentially to avoid too much nesting)
+
let mut dir_entries = Vec::new();
+
for (name, path) in dir_tasks {
+
let subdir = build_directory(agent, &path).await?;
+
dir_entries.push(Entry::new()
+
.name(CowStr::from(name))
+
.node(EntryNode::Directory(Box::new(subdir)))
+
.build());
}
+
+
// Combine file and directory entries
+
let mut entries = file_entries;
+
entries.extend(dir_entries);
Ok(Directory::new()
.r#type(CowStr::from("directory"))