max / makenotwork
151 files changed,
+1803 insertions,
-1181 deletions
| @@ -37,3 +37,35 @@ tempfile = "3.20" | |||
| 37 | 37 | tower = { version = "0.5", features = ["util"] } | |
| 38 | 38 | http-body-util = "0.1" | |
| 39 | 39 | reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } | |
| 40 | + | ||
| 41 | + | [lints.rust] | |
| 42 | + | unused = "warn" | |
| 43 | + | unreachable_pub = "warn" | |
| 44 | + | ||
| 45 | + | [lints.clippy] | |
| 46 | + | pedantic = { level = "warn", priority = -1 } | |
| 47 | + | # Allow-list tuned from a measured breakdown across server/multithreaded/pter | |
| 48 | + | # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything | |
| 49 | + | # else in `pedantic` stays a warning. Keep this block identical across repos. | |
| 50 | + | module_name_repetitions = "allow" | |
| 51 | + | # Doc lints. No docs-completeness push is underway. | |
| 52 | + | missing_errors_doc = "allow" | |
| 53 | + | missing_panics_doc = "allow" | |
| 54 | + | doc_markdown = "allow" | |
| 55 | + | # Numeric casts. Endemic and mostly intentional in size and byte math. | |
| 56 | + | cast_possible_truncation = "allow" | |
| 57 | + | cast_sign_loss = "allow" | |
| 58 | + | cast_precision_loss = "allow" | |
| 59 | + | cast_possible_wrap = "allow" | |
| 60 | + | cast_lossless = "allow" | |
| 61 | + | # Subjective structure and style nags. High churn, low signal. | |
| 62 | + | must_use_candidate = "allow" | |
| 63 | + | too_many_lines = "allow" | |
| 64 | + | struct_excessive_bools = "allow" | |
| 65 | + | similar_names = "allow" | |
| 66 | + | items_after_statements = "allow" | |
| 67 | + | single_match_else = "allow" | |
| 68 | + | # Frequent false-positives in TUI and router-heavy code. | |
| 69 | + | match_same_arms = "allow" | |
| 70 | + | unnecessary_wraps = "allow" | |
| 71 | + | type_complexity = "allow" |
| @@ -156,7 +156,7 @@ impl RecipeCtx { | |||
| 156 | 156 | /// empty list: preflight then cannot claim a version is a duplicate, and | |
| 157 | 157 | /// `cargo publish` still refuses one, so the check degrades to advisory | |
| 158 | 158 | /// rather than blocking a release on registry availability. | |
| 159 | - | fn published_versions(&self, name: &str) -> Vec<String> { | |
| 159 | + | fn published_versions(name: &str) -> Vec<String> { | |
| 160 | 160 | let url = format!("https://crates.io/api/v1/crates/{name}"); | |
| 161 | 161 | let Ok(out) = std::process::Command::new("curl") | |
| 162 | 162 | .args([ | |
| @@ -328,20 +328,17 @@ impl RecipeCtx { | |||
| 328 | 328 | fn step_budget(&self, step: Step) -> std::time::Duration { | |
| 329 | 329 | self.cfg | |
| 330 | 330 | .step_timeout_secs | |
| 331 | - | .map(std::time::Duration::from_secs) | |
| 332 | - | .unwrap_or_else(|| default_step_budget(step)) | |
| 331 | + | .map_or_else(|| default_step_budget(step), std::time::Duration::from_secs) | |
| 333 | 332 | } | |
| 334 | 333 | ||
| 335 | 334 | /// The current step's wall-clock deadline (or a default if no step is open, | |
| 336 | 335 | /// which only happens before the first `step()` โ commands then run under an | |
| 337 | 336 | /// implicit `Build` step opened by `ensure_step`). | |
| 338 | 337 | fn step_deadline(&self) -> std::time::Instant { | |
| 339 | - | self.current | |
| 340 | - | .lock() | |
| 341 | - | .unwrap() | |
| 342 | - | .as_ref() | |
| 343 | - | .map(|s| s.deadline) | |
| 344 | - | .unwrap_or_else(|| std::time::Instant::now() + self.step_budget(Step::Build)) | |
| 338 | + | self.current.lock().unwrap().as_ref().map_or_else( | |
| 339 | + | || std::time::Instant::now() + self.step_budget(Step::Build), | |
| 340 | + | |s| s.deadline, | |
| 341 | + | ) | |
| 345 | 342 | } | |
| 346 | 343 | ||
| 347 | 344 | /// Drive `fut` on the runtime, but stop early on two conditions the recipe | |
| @@ -367,10 +364,10 @@ impl RecipeCtx { | |||
| 367 | 364 | }; | |
| 368 | 365 | tokio::select! { | |
| 369 | 366 | r = &mut fut => r, | |
| 370 | - | _ = tokio::time::sleep_until(deadline.into()) => { | |
| 367 | + | () = tokio::time::sleep_until(deadline.into()) => { | |
| 371 | 368 | Err(anyhow::anyhow!("`{what}` exceeded its per-step deadline")) | |
| 372 | 369 | } | |
| 373 | - | _ = watch => { | |
| 370 | + | () = watch => { | |
| 374 | 371 | Err(anyhow::anyhow!("build superseded by a newer request; aborting `{what}`")) | |
| 375 | 372 | } | |
| 376 | 373 | } | |
| @@ -444,8 +441,7 @@ impl RecipeCtx { | |||
| 444 | 441 | .lock() | |
| 445 | 442 | .unwrap() | |
| 446 | 443 | .as_ref() | |
| 447 | - | .map(|s| s.step) | |
| 448 | - | .unwrap_or(Step::Build) | |
| 444 | + | .map_or(Step::Build, |s| s.step) | |
| 449 | 445 | } | |
| 450 | 446 | ||
| 451 | 447 | /// The capability-scoped executor for `name`, or an error if the host isn't | |
| @@ -541,6 +537,12 @@ impl RecipeCtx { | |||
| 541 | 537 | ||
| 542 | 538 | // ----- error bridging: anyhow -> Rhai runtime error ----- | |
| 543 | 539 | ||
| 540 | + | // Rhai host functions return `Result<_, Box<EvalAltResult>>` by convention, so | |
| 541 | + | // this bridge must yield the boxed form to be usable with `.map_err(rhai_err)`. | |
| 542 | + | #[allow( | |
| 543 | + | clippy::unnecessary_box_returns, | |
| 544 | + | reason = "rhai's error type is used boxed throughout its host-function API" | |
| 545 | + | )] | |
| 544 | 546 | fn rhai_err(e: impl std::fmt::Display) -> Box<EvalAltResult> { | |
| 545 | 547 | Box::new(EvalAltResult::ErrorRuntime( | |
| 546 | 548 | e.to_string().into(), | |
| @@ -638,7 +640,10 @@ pub fn version_from_repo(repo: &str, version_path: Option<&str>) -> Result<Versi | |||
| 638 | 640 | let path = root.join(vp); | |
| 639 | 641 | let raw = std::fs::read_to_string(&path) | |
| 640 | 642 | .with_context(|| format!("reading version file {}", path.display()))?; | |
| 641 | - | let ver = if vp.ends_with(".json") { | |
| 643 | + | let ver = if std::path::Path::new(vp) | |
| 644 | + | .extension() | |
| 645 | + | .is_some_and(|e| e.eq_ignore_ascii_case("json")) | |
| 646 | + | { | |
| 642 | 647 | version_from_tauri_json(&raw)? | |
| 643 | 648 | } else { | |
| 644 | 649 | version_from_cargo_toml(&raw)? | |
| @@ -724,7 +729,10 @@ pub fn check_version_consistency( | |||
| 724 | 729 | let path = root.join(vp); | |
| 725 | 730 | let raw = std::fs::read_to_string(&path) | |
| 726 | 731 | .with_context(|| format!("reading version file {}", path.display()))?; | |
| 727 | - | consider(vp, &raw, vp.ends_with(".json"))?; | |
| 732 | + | let is_json = std::path::Path::new(vp) | |
| 733 | + | .extension() | |
| 734 | + | .is_some_and(|e| e.eq_ignore_ascii_case("json")); | |
| 735 | + | consider(vp, &raw, is_json)?; | |
| 728 | 736 | } | |
| 729 | 737 | let tauri_conf = root.join("src-tauri").join("tauri.conf.json"); | |
| 730 | 738 | if version_path != Some("src-tauri/tauri.conf.json") && tauri_conf.exists() { | |
| @@ -893,7 +901,7 @@ fn resolve_artifact_match(listing: &str, glob: &str, required: bool) -> Result<S | |||
| 893 | 901 | ||
| 894 | 902 | /// Build a Rhai engine with the host API bound to `ctx`. Sandboxed: recipes | |
| 895 | 903 | /// touch the outside world only through these functions. | |
| 896 | - | pub fn build_engine(ctx: Arc<RecipeCtx>) -> Engine { | |
| 904 | + | pub fn build_engine(ctx: &Arc<RecipeCtx>) -> Engine { | |
| 897 | 905 | let mut engine = Engine::new(); | |
| 898 | 906 | // Defensive caps โ recipes are first-party but bound the blast radius. | |
| 899 | 907 | engine.set_max_operations(5_000_000); | |
| @@ -1088,8 +1096,7 @@ pub fn build_engine(ctx: Arc<RecipeCtx>) -> Engine { | |||
| 1088 | 1096 | .args(["ls-remote", url]) | |
| 1089 | 1097 | .env("GIT_TERMINAL_PROMPT", "0") | |
| 1090 | 1098 | .output() | |
| 1091 | - | .map(|o| o.status.success()) | |
| 1092 | - | .unwrap_or(false) | |
| 1099 | + | .is_ok_and(|o| o.status.success()) | |
| 1093 | 1100 | }); | |
| 1094 | 1101 | ||
| 1095 | 1102 | // Ask the publishing host whether cargo has credentials, rather | |
| @@ -1102,10 +1109,9 @@ pub fn build_engine(ctx: Arc<RecipeCtx>) -> Engine { | |||
| 1102 | 1109 | test -s \"${CARGO_HOME:-$HOME/.cargo}/credentials.toml\" \ | |
| 1103 | 1110 | || test -s \"${CARGO_HOME:-$HOME/.cargo}/credentials\"", | |
| 1104 | 1111 | ) | |
| 1105 | - | .map(|(code, _)| code == 0) | |
| 1106 | - | .unwrap_or(false); | |
| 1112 | + | .is_ok_and(|(code, _)| code == 0); | |
| 1107 | 1113 | ||
| 1108 | - | let published = ctx.published_versions(&meta.name); | |
| 1114 | + | let published = RecipeCtx::published_versions(&meta.name); | |
| 1109 | 1115 | let problems = crate_publish_problems(&meta, clonable, &published, creds); | |
| 1110 | 1116 | if !problems.is_empty() { | |
| 1111 | 1117 | return Err(format!( | |
| @@ -1242,7 +1248,7 @@ pub fn build_engine(ctx: Arc<RecipeCtx>) -> Engine { | |||
| 1242 | 1248 | artifact: &str, | |
| 1243 | 1249 | meta: Map| | |
| 1244 | 1250 | -> Result<String, Box<EvalAltResult>> { | |
| 1245 | - | ctx.publish(channel, app, target, version, artifact, meta) | |
| 1251 | + | ctx.publish(channel, app, target, version, artifact, &meta) | |
| 1246 | 1252 | .map_err(rhai_err) | |
| 1247 | 1253 | }, | |
| 1248 | 1254 | ); | |
| @@ -1254,7 +1260,7 @@ pub fn build_engine(ctx: Arc<RecipeCtx>) -> Engine { | |||
| 1254 | 1260 | // transport โ the only security session where the Developer ID key is | |
| 1255 | 1261 | // usable (design ยง7 "THE WALL"). Capability-gated by the host's `sign` | |
| 1256 | 1262 | // grant. --- | |
| 1257 | - | register_macos_fns(&mut engine, &ctx); | |
| 1263 | + | register_macos_fns(&mut engine, ctx); | |
| 1258 | 1264 | ||
| 1259 | 1265 | engine | |
| 1260 | 1266 | } | |
| @@ -1292,7 +1298,7 @@ impl RecipeCtx { | |||
| 1292 | 1298 | let entry = entry?; | |
| 1293 | 1299 | let name = entry.file_name().to_string_lossy().into_owned(); | |
| 1294 | 1300 | assert_artifact_version(&name, &self.version)?; | |
| 1295 | - | if entry.file_type().map(|t| t.is_file()).unwrap_or(false) { | |
| 1301 | + | if entry.file_type().is_ok_and(|t| t.is_file()) { | |
| 1296 | 1302 | let digest = sha256_file(&entry.path())?; | |
| 1297 | 1303 | self.artifact_hashes.lock().unwrap().insert(name, digest); | |
| 1298 | 1304 | } | |
| @@ -1359,7 +1365,7 @@ impl RecipeCtx { | |||
| 1359 | 1365 | target: &str, | |
| 1360 | 1366 | version: &str, | |
| 1361 | 1367 | artifact: &str, | |
| 1362 | - | meta: Map, | |
| 1368 | + | meta: &Map, | |
| 1363 | 1369 | ) -> Result<String> { | |
| 1364 | 1370 | // Never let a superseded build ship. This is the last and most important | |
| 1365 | 1371 | // cooperative-cancel checkpoint: even if a long-running step finished | |
| @@ -1773,7 +1779,7 @@ mod tests { | |||
| 1773 | 1779 | Arc::new(AtomicBool::new(false)), | |
| 1774 | 1780 | None, | |
| 1775 | 1781 | )); | |
| 1776 | - | let engine = build_engine(ctx); | |
| 1782 | + | let engine = build_engine(&ctx); | |
| 1777 | 1783 | engine.eval::<String>("feature_flags()").unwrap() | |
| 1778 | 1784 | } | |
| 1779 | 1785 | ||
| @@ -2140,7 +2146,7 @@ mod tests { | |||
| 2140 | 2146 | // whole matrix so a new Step variant can't silently get a 0 budget. | |
| 2141 | 2147 | for step in Step::ALL { | |
| 2142 | 2148 | assert!( | |
| 2143 | - | default_step_budget(step) >= std::time::Duration::from_secs(60), | |
| 2149 | + | default_step_budget(step) >= std::time::Duration::from_mins(1), | |
| 2144 | 2150 | "{step} budget must be a sane ceiling", | |
| 2145 | 2151 | ); | |
| 2146 | 2152 | } |
| @@ -24,7 +24,7 @@ pub fn channel() -> EventTx { | |||
| 24 | 24 | /// Send an event without caring whether anyone is listening. `StepLogChunk` goes | |
| 25 | 25 | /// to the high-rate log channel, everything else to the status channel. | |
| 26 | 26 | pub fn emit(tx: &EventTx, event: Event) { | |
| 27 | - | eventbus::emit(tx, event) | |
| 27 | + | eventbus::emit(tx, event); | |
| 28 | 28 | } | |
| 29 | 29 | ||
| 30 | 30 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
| @@ -78,7 +78,7 @@ async fn main() -> Result<()> { | |||
| 78 | 78 | match &api_token { | |
| 79 | 79 | Some(_) => tracing::info!("build endpoints require a bearer token"), | |
| 80 | 80 | None => { | |
| 81 | - | tracing::warn!(%addr, "BENTO_API_TOKEN unset; build endpoints are UNAUTHENTICATED (loopback bind)") | |
| 81 | + | tracing::warn!(%addr, "BENTO_API_TOKEN unset; build endpoints are UNAUTHENTICATED (loopback bind)"); | |
| 82 | 82 | } | |
| 83 | 83 | } | |
| 84 | 84 | ||
| @@ -105,7 +105,7 @@ async fn main() -> Result<()> { | |||
| 105 | 105 | let cfg = app_state.cfg.clone(); | |
| 106 | 106 | let pool = app_state.pool.clone(); | |
| 107 | 107 | tokio::spawn(async move { | |
| 108 | - | let mut tick = tokio::time::interval(std::time::Duration::from_secs(6 * 60 * 60)); | |
| 108 | + | let mut tick = tokio::time::interval(std::time::Duration::from_hours(6)); | |
| 109 | 109 | loop { | |
| 110 | 110 | tick.tick().await; // fires immediately, then every 6h | |
| 111 | 111 | let active: bento_daemon::retention::ActiveVersions = | |
| @@ -119,7 +119,7 @@ async fn main() -> Result<()> { | |||
| 119 | 119 | .collect(); | |
| 120 | 120 | let cfg = cfg.clone(); | |
| 121 | 121 | let _ = tokio::task::spawn_blocking(move || { | |
| 122 | - | bento_daemon::retention::prune_once(&cfg, &active) | |
| 122 | + | bento_daemon::retention::prune_once(&cfg, &active); | |
| 123 | 123 | }) | |
| 124 | 124 | .await; | |
| 125 | 125 | } |
| @@ -122,7 +122,7 @@ impl OtaRegistry { | |||
| 122 | 122 | } | |
| 123 | 123 | ||
| 124 | 124 | pub fn get(&self, channel: &str) -> Option<&dyn OtaBackend> { | |
| 125 | - | self.backends.get(channel).map(|b| b.as_ref()) | |
| 125 | + | self.backends.get(channel).map(std::convert::AsRef::as_ref) | |
| 126 | 126 | } | |
| 127 | 127 | ||
| 128 | 128 | /// The standard registry: `tauri-mnw` wired to the MNW OTA endpoint. | |
| @@ -145,12 +145,12 @@ pub struct TauriMnwBackend { | |||
| 145 | 145 | } | |
| 146 | 146 | ||
| 147 | 147 | impl OtaBackend for TauriMnwBackend { | |
| 148 | - | fn id(&self) -> &str { | |
| 148 | + | fn id(&self) -> &'static str { | |
| 149 | 149 | "tauri-mnw" | |
| 150 | 150 | } | |
| 151 | 151 | ||
| 152 | 152 | fn supports(&self, target: Target) -> bool { | |
| 153 | - | use crate::domain::Platform::*; | |
| 153 | + | use crate::domain::Platform::{Linux, Macos, Windows}; | |
| 154 | 154 | // Tauri's updater covers the desktop trio; mobile rides testflight/play. | |
| 155 | 155 | matches!(target.platform, Macos | Linux | Windows) | |
| 156 | 156 | } |
| @@ -34,7 +34,7 @@ fn prune_root(root: &Path, label: &str, active: &ActiveVersions) { | |||
| 34 | 34 | let Ok(apps) = std::fs::read_dir(root) else { | |
| 35 | 35 | return; | |
| 36 | 36 | }; | |
| 37 | - | for app in apps.filter_map(|e| e.ok()) { | |
| 37 | + | for app in apps.filter_map(std::result::Result::ok) { | |
| 38 | 38 | let app_dir = app.path(); | |
| 39 | 39 | if app_dir.is_dir() { | |
| 40 | 40 | let app_name = app.file_name().to_string_lossy().into_owned(); | |
| @@ -50,7 +50,7 @@ fn prune_app_dir(app_dir: &Path, app_name: &str, label: &str, active: &ActiveVer | |||
| 50 | 50 | // Only consider subdirectories whose name parses as a semver โ anything | |
| 51 | 51 | // else (a stray file, a non-version dir) is left untouched. | |
| 52 | 52 | let mut versions: Vec<(Version, String, PathBuf)> = entries | |
| 53 | - | .filter_map(|e| e.ok()) | |
| 53 | + | .filter_map(std::result::Result::ok) | |
| 54 | 54 | .filter(|e| e.path().is_dir()) | |
| 55 | 55 | .filter_map(|e| { | |
| 56 | 56 | let name = e.file_name().to_string_lossy().into_owned(); | |
| @@ -70,10 +70,10 @@ fn prune_app_dir(app_dir: &Path, app_name: &str, label: &str, active: &ActiveVer | |||
| 70 | 70 | } | |
| 71 | 71 | match std::fs::remove_dir_all(&path) { | |
| 72 | 72 | Ok(()) => { | |
| 73 | - | tracing::info!(%ver, dir = %path.display(), "retention: pruned old {label} version") | |
| 73 | + | tracing::info!(%ver, dir = %path.display(), "retention: pruned old {label} version"); | |
| 74 | 74 | } | |
| 75 | 75 | Err(e) => { | |
| 76 | - | tracing::warn!(error = %e, dir = %path.display(), "retention: could not prune {label} dir") | |
| 76 | + | tracing::warn!(error = %e, dir = %path.display(), "retention: could not prune {label} dir"); | |
| 77 | 77 | } | |
| 78 | 78 | } | |
| 79 | 79 | } | |
| @@ -106,7 +106,7 @@ mod tests { | |||
| 106 | 106 | ||
| 107 | 107 | let mut kept: Vec<String> = std::fs::read_dir(root.join("demo")) | |
| 108 | 108 | .unwrap() | |
| 109 | - | .filter_map(|e| e.ok()) | |
| 109 | + | .filter_map(std::result::Result::ok) | |
| 110 | 110 | .map(|e| e.file_name().to_string_lossy().into_owned()) | |
| 111 | 111 | .collect(); | |
| 112 | 112 | kept.sort(); |
| @@ -413,18 +413,18 @@ async fn run_target( | |||
| 413 | 413 | // runtime worker. | |
| 414 | 414 | let ctx_run = ctx.clone(); | |
| 415 | 415 | let outcome = tokio::task::spawn_blocking(move || { | |
| 416 | - | let engine = engine::build_engine(ctx_run.clone()); | |
| 416 | + | let engine = engine::build_engine(&ctx_run); | |
| 417 | 417 | let res = engine.run(&recipe_src); | |
| 418 | 418 | let last_step = ctx_run.current_step(); | |
| 419 | 419 | match &res { | |
| 420 | - | Ok(_) => { | |
| 420 | + | Ok(()) => { | |
| 421 | 421 | let _ = ctx_run.finish_step(Status::Ok); | |
| 422 | 422 | } | |
| 423 | 423 | Err(_) => { | |
| 424 | 424 | let _ = ctx_run.finish_step(Status::Failed); | |
| 425 | 425 | } | |
| 426 | 426 | } | |
| 427 | - | res.map(|_| ()).map_err(|e| (last_step, e.to_string())) | |
| 427 | + | res.map_err(|e| (last_step, e.to_string())) | |
| 428 | 428 | }) | |
| 429 | 429 | .await; | |
| 430 | 430 | ||
| @@ -544,11 +544,11 @@ async fn fail_target( | |||
| 544 | 544 | /// reach the blocking bodies, so a build was marked failed while codesign kept | |
| 545 | 545 | /// running on the mac. | |
| 546 | 546 | async fn finalize_build(state: AppState, build_id: i64, mut set: tokio::task::JoinSet<()>) { | |
| 547 | - | const MAX_WAIT: std::time::Duration = std::time::Duration::from_secs(6 * 60 * 60); | |
| 547 | + | const MAX_WAIT: std::time::Duration = std::time::Duration::from_hours(6); | |
| 548 | 548 | let deadline = tokio::time::Instant::now() + MAX_WAIT; | |
| 549 | 549 | loop { | |
| 550 | 550 | match tokio::time::timeout_at(deadline, set.join_next()).await { | |
| 551 | - | Ok(Some(Ok(()))) => continue, | |
| 551 | + | Ok(Some(Ok(()))) => {} | |
| 552 | 552 | Ok(Some(Err(e))) => { | |
| 553 | 553 | // A panic in run_target itself (outside its inner spawn_blocking, | |
| 554 | 554 | // which is already caught). Cancellation = a superseding build. | |
| @@ -652,7 +652,7 @@ fn collected_artifacts(state: &AppState, app: &AppId, version: &Version) -> Vec< | |||
| 652 | 652 | let Ok(rd) = std::fs::read_dir(&dir) else { | |
| 653 | 653 | return Vec::new(); | |
| 654 | 654 | }; | |
| 655 | - | rd.filter_map(|e| e.ok()) | |
| 655 | + | rd.filter_map(std::result::Result::ok) | |
| 656 | 656 | .map(|e| e.file_name().to_string_lossy().into_owned()) | |
| 657 | 657 | .collect() | |
| 658 | 658 | } |
| @@ -166,7 +166,7 @@ pub fn build_syncs(topo: &Topology) -> ExecutorMap { | |||
| 166 | 166 | #[cfg(test)] | |
| 167 | 167 | mod tests { | |
| 168 | 168 | use super::*; | |
| 169 | - | use ops_exec::Action; | |
| 169 | + | use ops_exec::{Action, SyncOpts}; | |
| 170 | 170 | ||
| 171 | 171 | fn host(toml_host: &str) -> Host { | |
| 172 | 172 | // Only a host is under test here, so parse just the `[[host]]` table. | |
| @@ -213,7 +213,7 @@ mod tests { | |||
| 213 | 213 | .pull_glob( | |
| 214 | 214 | "/nonexistent/*.dmg", | |
| 215 | 215 | std::path::Path::new("/tmp"), | |
| 216 | - | &Default::default(), | |
| 216 | + | &SyncOpts::default(), | |
| 217 | 217 | ) | |
| 218 | 218 | .await | |
| 219 | 219 | .expect_err("nothing matches, so this must error either way"); | |
| @@ -231,7 +231,7 @@ mod tests { | |||
| 231 | 231 | .pull_glob( | |
| 232 | 232 | "/x/*.dmg", | |
| 233 | 233 | std::path::Path::new("/tmp"), | |
| 234 | - | &Default::default(), | |
| 234 | + | &SyncOpts::default(), | |
| 235 | 235 | ) | |
| 236 | 236 | .await | |
| 237 | 237 | .expect_err("AgentRpc has no glob form"); | |
| @@ -264,7 +264,7 @@ mod tests { | |||
| 264 | 264 | .pull_file( | |
| 265 | 265 | std::path::Path::new("/home/max/.tauri/passwords.env"), | |
| 266 | 266 | std::path::Path::new("/tmp/out"), | |
| 267 | - | &Default::default(), | |
| 267 | + | &SyncOpts::default(), | |
| 268 | 268 | ) | |
| 269 | 269 | .await | |
| 270 | 270 | .expect_err("a path outside pull_root must be denied"); | |
| @@ -280,7 +280,7 @@ mod tests { | |||
| 280 | 280 | .pull_file( | |
| 281 | 281 | std::path::Path::new("/home/max/Code/Apps/goingson/x.dmg"), | |
| 282 | 282 | std::path::Path::new("/tmp/out"), | |
| 283 | - | &Default::default(), | |
| 283 | + | &SyncOpts::default(), | |
| 284 | 284 | ) | |
| 285 | 285 | .await | |
| 286 | 286 | .expect_err("no pull_root โ no pulls"); |
| @@ -31,6 +31,8 @@ | |||
| 31 | 31 | //! naming what is missing, rather than the `ok` that a build-level status alone | |
| 32 | 32 | //! would imply. | |
| 33 | 33 | ||
| 34 | + | use std::fmt::Write as _; | |
| 35 | + | ||
| 34 | 36 | use chrono::{DateTime, Utc}; | |
| 35 | 37 | use ops_status::{Action, Condition, Field, Method, Node, Payload, Status, Value}; | |
| 36 | 38 | use serde_json::json; | |
| @@ -183,10 +185,10 @@ fn release_completeness(app: &AppStatusView, build: &BuildView) -> Option<Condit | |||
| 183 | 185 | app.declared_targets.len() | |
| 184 | 186 | ); | |
| 185 | 187 | if !failed.is_empty() { | |
| 186 | - | detail.push_str(&format!("; failed: {}", failed.join(", "))); | |
| 188 | + | write!(detail, "; failed: {}", failed.join(", ")).unwrap(); | |
| 187 | 189 | } | |
| 188 | 190 | if !missing.is_empty() { | |
| 189 | - | detail.push_str(&format!("; never ran: {}", missing.join(", "))); | |
| 191 | + | write!(detail, "; never ran: {}", missing.join(", ")).unwrap(); | |
| 190 | 192 | } | |
| 191 | 193 | ||
| 192 | 194 | Some(Condition { |
| @@ -35,6 +35,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 35 | 35 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | |
| 36 | 36 | ||
| 37 | 37 | [[package]] | |
| 38 | + | name = "aws-lc-rs" | |
| 39 | + | version = "1.17.3" | |
| 40 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 41 | + | checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" | |
| 42 | + | dependencies = [ | |
| 43 | + | "aws-lc-sys", | |
| 44 | + | "zeroize", | |
| 45 | + | ] | |
| 46 | + | ||
| 47 | + | [[package]] | |
| 48 | + | name = "aws-lc-sys" | |
| 49 | + | version = "0.43.0" | |
| 50 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 51 | + | checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" | |
| 52 | + | dependencies = [ | |
| 53 | + | "cc", | |
| 54 | + | "cmake", | |
| 55 | + | "dunce", | |
| 56 | + | "fs_extra", | |
| 57 | + | "pkg-config", | |
| 58 | + | ] | |
| 59 | + | ||
| 60 | + | [[package]] | |
| 38 | 61 | name = "base64" | |
| 39 | 62 | version = "0.22.1" | |
| 40 | 63 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -80,6 +103,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 80 | 103 | checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" | |
| 81 | 104 | dependencies = [ | |
| 82 | 105 | "find-msvc-tools", | |
| 106 | + | "jobserver", | |
| 107 | + | "libc", | |
| 83 | 108 | "shlex", | |
| 84 | 109 | ] | |
| 85 | 110 | ||
| @@ -96,6 +121,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 96 | 121 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | |
| 97 | 122 | ||
| 98 | 123 | [[package]] | |
| 124 | + | name = "cmake" | |
| 125 | + | version = "0.1.58" | |
| 126 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 127 | + | checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" | |
| 128 | + | dependencies = [ | |
| 129 | + | "cc", | |
| 130 | + | ] | |
| 131 | + | ||
| 132 | + | [[package]] | |
| 133 | + | name = "combine" | |
| 134 | + | version = "4.6.7" | |
| 135 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 136 | + | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" | |
| 137 | + | dependencies = [ | |
| 138 | + | "bytes", | |
| 139 | + | "memchr", | |
| 140 | + | ] | |
| 141 | + | ||
| 142 | + | [[package]] | |
| 143 | + | name = "core-foundation" | |
| 144 | + | version = "0.10.1" | |
| 145 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 146 | + | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" | |
| 147 | + | dependencies = [ | |
| 148 | + | "core-foundation-sys", | |
| 149 | + | "libc", | |
| 150 | + | ] | |
| 151 | + | ||
| 152 | + | [[package]] | |
| 153 | + | name = "core-foundation-sys" | |
| 154 | + | version = "0.8.7" | |
| 155 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 156 | + | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | |
| 157 | + | ||
| 158 | + | [[package]] | |
| 99 | 159 | name = "displaydoc" | |
| 100 | 160 | version = "0.2.6" | |
| 101 | 161 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -107,6 +167,12 @@ dependencies = [ | |||
| 107 | 167 | ] | |
| 108 | 168 | ||
| 109 | 169 | [[package]] | |
| 170 | + | name = "dunce" | |
| 171 | + | version = "1.0.5" | |
| 172 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 173 | + | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" | |
| 174 | + | ||
| 175 | + | [[package]] | |
| 110 | 176 | name = "equivalent" | |
| 111 | 177 | version = "1.0.2" | |
| 112 | 178 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -150,6 +216,12 @@ dependencies = [ | |||
| 150 | 216 | ] | |
| 151 | 217 | ||
| 152 | 218 | [[package]] | |
| 219 | + | name = "fs_extra" | |
| 220 | + | version = "1.3.0" | |
| 221 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 222 | + | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" | |
| 223 | + | ||
| 224 | + | [[package]] | |
| 153 | 225 | name = "futures-channel" | |
| 154 | 226 | version = "0.3.32" | |
| 155 | 227 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -348,7 +420,6 @@ dependencies = [ | |||
| 348 | 420 | "tokio", | |
| 349 | 421 | "tokio-rustls", | |
| 350 | 422 | "tower-service", | |
| 351 | - | "webpki-roots", | |
| 352 | 423 | ] | |
| 353 | 424 | ||
| 354 | 425 | [[package]] | |
| @@ -508,6 +579,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 508 | 579 | checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" | |
| 509 | 580 | ||
| 510 | 581 | [[package]] | |
| 582 | + | name = "jni" | |
| 583 | + | version = "0.22.4" | |
| 584 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 585 | + | checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" | |
| 586 | + | dependencies = [ | |
| 587 | + | "cfg-if", | |
| 588 | + | "combine", | |
| 589 | + | "jni-macros", | |
| 590 | + | "jni-sys", | |
| 591 | + | "log", | |
| 592 | + | "simd_cesu8", | |
| 593 | + | "thiserror", | |
| 594 | + | "walkdir", | |
| 595 | + | "windows-link", | |
| 596 | + | ] | |
| 597 | + | ||
| 598 | + | [[package]] | |
| 599 | + | name = "jni-macros" | |
| 600 | + | version = "0.22.4" | |
| 601 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 602 | + | checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" | |
| 603 | + | dependencies = [ | |
| 604 | + | "proc-macro2", | |
| 605 | + | "quote", | |
| 606 | + | "rustc_version", | |
| 607 | + | "simd_cesu8", | |
| 608 | + | "syn", | |
| 609 | + | ] | |
| 610 | + | ||
| 611 | + | [[package]] | |
| 612 | + | name = "jni-sys" | |
| 613 | + | version = "0.4.1" | |
| 614 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 615 | + | checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" | |
| 616 | + | dependencies = [ | |
| 617 | + | "jni-sys-macros", | |
| 618 | + | ] | |
| 619 | + | ||
| 620 | + | [[package]] | |
| 621 | + | name = "jni-sys-macros" | |
| 622 | + | version = "0.4.1" | |
| 623 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 624 | + | checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" | |
| 625 | + | dependencies = [ | |
| 626 | + | "quote", | |
| 627 | + | "syn", | |
| 628 | + | ] | |
| 629 | + | ||
| 630 | + | [[package]] | |
| 631 | + | name = "jobserver" | |
| 632 | + | version = "0.1.35" | |
| 633 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 634 | + | checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" | |
| 635 | + | dependencies = [ | |
| 636 | + | "getrandom 0.4.2", | |
| 637 | + | "libc", | |
| 638 | + | ] | |
| 639 | + | ||
| 640 | + | [[package]] | |
| 511 | 641 | name = "js-sys" | |
| 512 | 642 | version = "0.3.99" | |
| 513 | 643 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -603,6 +733,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 603 | 733 | checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | |
| 604 | 734 | ||
| 605 | 735 | [[package]] | |
| 736 | + | name = "openssl-probe" | |
| 737 | + | version = "0.2.1" | |
| 738 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 739 | + | checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" | |
| 740 | + | ||
| 741 | + | [[package]] | |
| 606 | 742 | name = "ops-exec" | |
| 607 | 743 | version = "0.1.0" | |
| 608 | 744 | dependencies = [ | |
| @@ -631,6 +767,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 631 | 767 | checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" | |
| 632 | 768 | ||
| 633 | 769 | [[package]] | |
| 770 | + | name = "pkg-config" | |
| 771 | + | version = "0.3.33" | |
| 772 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 773 | + | checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" | |
| 774 | + | ||
| 775 | + | [[package]] | |
| 634 | 776 | name = "potential_utf" | |
| 635 | 777 | version = "0.1.5" | |
| 636 | 778 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -693,6 +835,7 @@ version = "0.11.14" | |||
| 693 | 835 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 694 | 836 | checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" | |
| 695 | 837 | dependencies = [ | |
| 838 | + | "aws-lc-rs", | |
| 696 | 839 | "bytes", | |
| 697 | 840 | "getrandom 0.3.4", | |
| 698 | 841 | "lru-slab", | |
| @@ -791,9 +934,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" | |||
| 791 | 934 | ||
| 792 | 935 | [[package]] | |
| 793 | 936 | name = "reqwest" | |
| 794 | - | version = "0.12.28" | |
| 937 | + | version = "0.13.4" | |
| 795 | 938 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 796 | - | checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" | |
| 939 | + | checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" | |
| 797 | 940 | dependencies = [ | |
| 798 | 941 | "base64", | |
| 799 | 942 | "bytes", | |
| @@ -812,6 +955,7 @@ dependencies = [ | |||
| 812 | 955 | "quinn", | |
| 813 | 956 | "rustls", | |
| 814 | 957 | "rustls-pki-types", | |
| 958 | + | "rustls-platform-verifier", | |
| 815 | 959 | "serde", | |
| 816 | 960 | "serde_json", | |
| 817 | 961 | "serde_urlencoded", | |
| @@ -827,7 +971,6 @@ dependencies = [ | |||
| 827 | 971 | "wasm-bindgen-futures", | |
| 828 | 972 | "wasm-streams", | |
| 829 | 973 | "web-sys", | |
| 830 | - | "webpki-roots", | |
| 831 | 974 | ] | |
| 832 | 975 | ||
| 833 | 976 | [[package]] | |
| @@ -851,6 +994,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 851 | 994 | checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" | |
| 852 | 995 | ||
| 853 | 996 | [[package]] | |
| 997 | + | name = "rustc_version" | |
| 998 | + | version = "0.4.1" | |
| 999 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1000 | + | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" | |
| 1001 | + | dependencies = [ | |
| 1002 | + | "semver", | |
| 1003 | + | ] | |
| 1004 | + | ||
| 1005 | + | [[package]] | |
| 854 | 1006 | name = "rustix" | |
| 855 | 1007 | version = "1.1.4" | |
| 856 | 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -869,8 +1021,8 @@ version = "0.23.40" | |||
| 869 | 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 870 | 1022 | checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" | |
| 871 | 1023 | dependencies = [ | |
| 1024 | + | "aws-lc-rs", | |
| 872 | 1025 | "once_cell", | |
| 873 | - | "ring", | |
| 874 | 1026 | "rustls-pki-types", | |
| 875 | 1027 | "rustls-webpki", | |
| 876 | 1028 | "subtle", | |
| @@ -878,6 +1030,18 @@ dependencies = [ | |||
| 878 | 1030 | ] | |
| 879 | 1031 | ||
| 880 | 1032 | [[package]] | |
| 1033 | + | name = "rustls-native-certs" | |
| 1034 | + | version = "0.8.4" | |
| 1035 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1036 | + | checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" | |
| 1037 | + | dependencies = [ | |
| 1038 | + | "openssl-probe", | |
| 1039 | + | "rustls-pki-types", | |
| 1040 | + | "schannel", | |
| 1041 | + | "security-framework", | |
| 1042 | + | ] | |
| 1043 | + | ||
| 1044 | + | [[package]] | |
| 881 | 1045 | name = "rustls-pki-types" | |
| 882 | 1046 | version = "1.14.1" | |
| 883 | 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -888,11 +1052,39 @@ dependencies = [ | |||
| 888 | 1052 | ] | |
| 889 | 1053 | ||
| 890 | 1054 | [[package]] | |
| 1055 | + | name = "rustls-platform-verifier" | |
| 1056 | + | version = "0.7.0" | |
| 1057 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1058 | + | checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" | |
| 1059 | + | dependencies = [ | |
| 1060 | + | "core-foundation", | |
| 1061 | + | "core-foundation-sys", | |
| 1062 | + | "jni", | |
| 1063 | + | "log", | |
| 1064 | + | "once_cell", | |
| 1065 | + | "rustls", | |
| 1066 | + | "rustls-native-certs", | |
| 1067 | + | "rustls-platform-verifier-android", | |
| 1068 | + | "rustls-webpki", | |
| 1069 | + | "security-framework", | |
| 1070 | + | "security-framework-sys", | |
| 1071 | + | "webpki-root-certs", | |
| 1072 | + | "windows-sys 0.52.0", | |
| 1073 | + | ] | |
| 1074 | + | ||
| 1075 | + | [[package]] | |
| 1076 | + | name = "rustls-platform-verifier-android" | |
| 1077 | + | version = "0.1.1" | |
| 1078 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1079 | + | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" | |
| 1080 | + | ||
| 1081 | + | [[package]] | |
| 891 | 1082 | name = "rustls-webpki" | |
| 892 | 1083 | version = "0.103.13" | |
| 893 | 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 894 | 1085 | checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" | |
| 895 | 1086 | dependencies = [ | |
| 1087 | + | "aws-lc-rs", | |
| 896 | 1088 | "ring", | |
| 897 | 1089 | "rustls-pki-types", | |
| 898 | 1090 | "untrusted", | |
| @@ -911,6 +1103,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 911 | 1103 | checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" | |
| 912 | 1104 | ||
| 913 | 1105 | [[package]] | |
| 1106 | + | name = "same-file" | |
| 1107 | + | version = "1.0.6" | |
| 1108 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1109 | + | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" | |
| 1110 | + | dependencies = [ | |
| 1111 | + | "winapi-util", | |
| 1112 | + | ] | |
| 1113 | + | ||
| 1114 | + | [[package]] | |
| 1115 | + | name = "schannel" | |
| 1116 | + | version = "0.1.29" | |
| 1117 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1118 | + | checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" | |
| 1119 | + | dependencies = [ | |
| 1120 | + | "windows-sys 0.61.2", | |
| 1121 | + | ] | |
| 1122 | + | ||
| 1123 | + | [[package]] | |
| 1124 | + | name = "security-framework" | |
| 1125 | + | version = "3.7.0" | |
| 1126 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1127 | + | checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" | |
| 1128 | + | dependencies = [ | |
| 1129 | + | "bitflags", | |
| 1130 | + | "core-foundation", | |
| 1131 | + | "core-foundation-sys", | |
| 1132 | + | "libc", | |
| 1133 | + | "security-framework-sys", | |
| 1134 | + | ] | |
| 1135 | + | ||
| 1136 | + | [[package]] | |
| 1137 | + | name = "security-framework-sys" | |
| 1138 | + | version = "2.17.0" | |
| 1139 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1140 | + | checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" | |
| 1141 | + | dependencies = [ | |
| 1142 | + | "core-foundation-sys", | |
| 1143 | + | "libc", | |
| 1144 | + | ] | |
| 1145 | + | ||
| 1146 | + | [[package]] | |
| 914 | 1147 | name = "semver" | |
| 915 | 1148 | version = "1.0.28" | |
| 916 | 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1006,6 +1239,22 @@ dependencies = [ | |||
| 1006 | 1239 | ] | |
| 1007 | 1240 | ||
| 1008 | 1241 | [[package]] | |
| 1242 | + | name = "simd_cesu8" | |
| 1243 | + | version = "1.2.0" | |
| 1244 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1245 | + | checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" | |
| 1246 | + | dependencies = [ | |
| 1247 | + | "rustc_version", | |
| 1248 | + | "simdutf8", | |
| 1249 | + | ] | |
| 1250 | + | ||
| 1251 | + | [[package]] | |
| 1252 | + | name = "simdutf8" | |
| 1253 | + | version = "0.1.5" | |
| 1254 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1255 | + | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" | |
| 1256 | + | ||
| 1257 | + | [[package]] | |
| 1009 | 1258 | name = "slab" | |
| 1010 | 1259 | version = "0.4.12" | |
| 1011 | 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1381,6 +1630,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 1381 | 1630 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" | |
| 1382 | 1631 | ||
| 1383 | 1632 | [[package]] | |
| 1633 | + | name = "walkdir" | |
| 1634 | + | version = "2.5.0" | |
| 1635 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1636 | + | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" | |
| 1637 | + | dependencies = [ | |
| 1638 | + | "same-file", | |
| 1639 | + | "winapi-util", | |
| 1640 | + | ] | |
| 1641 | + | ||
| 1642 | + | [[package]] | |
| 1384 | 1643 | name = "want" | |
| 1385 | 1644 | version = "0.3.1" | |
| 1386 | 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1492,9 +1751,9 @@ dependencies = [ | |||
| 1492 | 1751 | ||
| 1493 | 1752 | [[package]] | |
| 1494 | 1753 | name = "wasm-streams" | |
| 1495 | - | version = "0.4.2" | |
| 1754 | + | version = "0.5.0" | |
| 1496 | 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1497 | - | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" | |
| 1756 | + | checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" | |
| 1498 | 1757 | dependencies = [ | |
| 1499 | 1758 | "futures-util", | |
| 1500 | 1759 | "js-sys", | |
| @@ -1536,15 +1795,24 @@ dependencies = [ | |||
| 1536 | 1795 | ] | |
| 1537 | 1796 | ||
| 1538 | 1797 | [[package]] | |
| 1539 | - | name = "webpki-roots" | |
| 1540 | - | version = "1.0.7" | |
| 1798 | + | name = "webpki-root-certs" | |
| 1799 | + | version = "1.0.9" | |
| 1541 | 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1542 | - | checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" | |
| 1801 | + | checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" | |
| 1543 | 1802 | dependencies = [ | |
| 1544 | 1803 | "rustls-pki-types", | |
| 1545 | 1804 | ] | |
| 1546 | 1805 | ||
| 1547 | 1806 | [[package]] | |
| 1807 | + | name = "winapi-util" | |
| 1808 | + | version = "0.1.11" | |
| 1809 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1810 | + | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" | |
| 1811 | + | dependencies = [ | |
| 1812 | + | "windows-sys 0.52.0", | |
| 1813 | + | ] | |
| 1814 | + | ||
| 1815 | + | [[package]] | |
| 1548 | 1816 | name = "windows-link" | |
| 1549 | 1817 | version = "0.2.1" | |
| 1550 | 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -21,3 +21,35 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } | |||
| 21 | 21 | ||
| 22 | 22 | [dev-dependencies] | |
| 23 | 23 | tempfile = "3.20" | |
| 24 | + | ||
| 25 | + | [lints.rust] | |
| 26 | + | unused = "warn" | |
| 27 | + | unreachable_pub = "warn" | |
| 28 | + | ||
| 29 | + | [lints.clippy] | |
| 30 | + | pedantic = { level = "warn", priority = -1 } | |
| 31 | + | # Allow-list tuned from a measured breakdown across server/multithreaded/pter | |
| 32 | + | # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything | |
| 33 | + | # else in `pedantic` stays a warning. Keep this block identical across repos. | |
| 34 | + | module_name_repetitions = "allow" | |
| 35 | + | # Doc lints. No docs-completeness push is underway. | |
| 36 | + | missing_errors_doc = "allow" | |
| 37 | + | missing_panics_doc = "allow" | |
| 38 | + | doc_markdown = "allow" | |
| 39 | + | # Numeric casts. Endemic and mostly intentional in size and byte math. | |
| 40 | + | cast_possible_truncation = "allow" | |
| 41 | + | cast_sign_loss = "allow" | |
| 42 | + | cast_precision_loss = "allow" | |
| 43 | + | cast_possible_wrap = "allow" | |
| 44 | + | cast_lossless = "allow" | |
| 45 | + | # Subjective structure and style nags. High churn, low signal. | |
| 46 | + | must_use_candidate = "allow" | |
| 47 | + | too_many_lines = "allow" | |
| 48 | + | struct_excessive_bools = "allow" | |
| 49 | + | similar_names = "allow" | |
| 50 | + | items_after_statements = "allow" | |
| 51 | + | single_match_else = "allow" | |
| 52 | + | # Frequent false-positives in TUI and router-heavy code. | |
| 53 | + | match_same_arms = "allow" | |
| 54 | + | unnecessary_wraps = "allow" | |
| 55 | + | type_complexity = "allow" |
| @@ -189,8 +189,7 @@ async fn run_step( | |||
| 189 | 189 | "{label} failed (exit {}): {}", | |
| 190 | 190 | out.status | |
| 191 | 191 | .code() | |
| 192 | - | .map(|c| c.to_string()) | |
| 193 | - | .unwrap_or_else(|| "signal".into()), | |
| 192 | + | .map_or_else(|| "signal".into(), |c| c.to_string()), | |
| 194 | 193 | failure_tail(&out), | |
| 195 | 194 | ); | |
| 196 | 195 | Ok(out) |
| @@ -19,3 +19,35 @@ anyhow = "1" | |||
| 19 | 19 | bytes = "1" | |
| 20 | 20 | synckit-client = { path = "../shared/synckit-client", default-features = false } | |
| 21 | 21 | uuid = "1" | |
| 22 | + | ||
| 23 | + | [lints.rust] | |
| 24 | + | unused = "warn" | |
| 25 | + | unreachable_pub = "warn" | |
| 26 | + | ||
| 27 | + | [lints.clippy] | |
| 28 | + | pedantic = { level = "warn", priority = -1 } | |
| 29 | + | # Allow-list tuned from a measured breakdown across server/multithreaded/pter | |
| 30 | + | # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything | |
| 31 | + | # else in `pedantic` stays a warning. Keep this block identical across repos. | |
| 32 | + | module_name_repetitions = "allow" | |
| 33 | + | # Doc lints. No docs-completeness push is underway. | |
| 34 | + | missing_errors_doc = "allow" | |
| 35 | + | missing_panics_doc = "allow" | |
| 36 | + | doc_markdown = "allow" | |
| 37 | + | # Numeric casts. Endemic and mostly intentional in size and byte math. | |
| 38 | + | cast_possible_truncation = "allow" | |
| 39 | + | cast_sign_loss = "allow" | |
| 40 | + | cast_precision_loss = "allow" | |
| 41 | + | cast_possible_wrap = "allow" | |
| 42 | + | cast_lossless = "allow" | |
| 43 | + | # Subjective structure and style nags. High churn, low signal. | |
| 44 | + | must_use_candidate = "allow" | |
| 45 | + | too_many_lines = "allow" | |
| 46 | + | struct_excessive_bools = "allow" | |
| 47 | + | similar_names = "allow" | |
| 48 | + | items_after_statements = "allow" | |
| 49 | + | single_match_else = "allow" | |
| 50 | + | # Frequent false-positives in TUI and router-heavy code. | |
| 51 | + | match_same_arms = "allow" | |
| 52 | + | unnecessary_wraps = "allow" | |
| 53 | + | type_complexity = "allow" |
| @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; | |||
| 4 | 4 | ||
| 5 | 5 | /// User info returned from the SSH key lookup endpoint. | |
| 6 | 6 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 7 | - | pub struct UserInfo { | |
| 7 | + | pub(crate) struct UserInfo { | |
| 8 | 8 | pub user_id: String, | |
| 9 | 9 | pub username: String, | |
| 10 | 10 | pub display_name: Option<String>, | |
| @@ -20,7 +20,11 @@ pub struct UserInfo { | |||
| 20 | 20 | ||
| 21 | 21 | /// A creator's project with item count and revenue. | |
| 22 | 22 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 23 | - | pub struct Project { | |
| 23 | + | #[allow( | |
| 24 | + | clippy::struct_field_names, | |
| 25 | + | reason = "project_type mirrors the server JSON/DB schema; renaming would diverge the field from the wire contract" | |
| 26 | + | )] | |
| 27 | + | pub(crate) struct Project { | |
| 24 | 28 | pub id: String, | |
| 25 | 29 | pub slug: String, | |
| 26 | 30 | pub title: String, | |
| @@ -32,7 +36,11 @@ pub struct Project { | |||
| 32 | 36 | ||
| 33 | 37 | /// An item within a project. | |
| 34 | 38 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 35 | - | pub struct Item { | |
| 39 | + | #[allow( | |
| 40 | + | clippy::struct_field_names, | |
| 41 | + | reason = "item_type mirrors the server JSON/DB schema; renaming would diverge the field from the wire contract" | |
| 42 | + | )] | |
| 43 | + | pub(crate) struct Item { | |
| 36 | 44 | pub id: String, | |
| 37 | 45 | pub title: String, | |
| 38 | 46 | pub item_type: String, | |
| @@ -43,7 +51,7 @@ pub struct Item { | |||
| 43 | 51 | ||
| 44 | 52 | /// Period comparison stats for the creator. | |
| 45 | 53 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 46 | - | pub struct CreatorStats { | |
| 54 | + | pub(crate) struct CreatorStats { | |
| 47 | 55 | pub current_revenue_cents: i64, | |
| 48 | 56 | pub previous_revenue_cents: i64, | |
| 49 | 57 | pub current_sales: i64, | |
| @@ -57,7 +65,7 @@ pub struct CreatorStats { | |||
| 57 | 65 | /// Response from the create-item internal endpoint. | |
| 58 | 66 | #[derive(Debug, Deserialize)] | |
| 59 | 67 | #[allow(dead_code)] | |
| 60 | - | pub struct ItemCreated { | |
| 68 | + | pub(crate) struct ItemCreated { | |
| 61 | 69 | pub item_id: String, | |
| 62 | 70 | pub project_id: String, | |
| 63 | 71 | } | |
| @@ -65,7 +73,7 @@ pub struct ItemCreated { | |||
| 65 | 73 | /// Response from the presign-upload internal endpoint. | |
| 66 | 74 | #[derive(Debug, Deserialize)] | |
| 67 | 75 | #[allow(dead_code)] | |
| 68 | - | pub struct PresignResponse { | |
| 76 | + | pub(crate) struct PresignResponse { | |
| 69 | 77 | pub upload_url: String, | |
| 70 | 78 | pub s3_key: String, | |
| 71 | 79 | pub expires_in: u64, | |
| @@ -77,7 +85,7 @@ pub struct PresignResponse { | |||
| 77 | 85 | /// fine for a small file and unacceptable for a multi-GB one; the multipart path | |
| 78 | 86 | /// holds one part at a time. It is also the only path that can carry a file past | |
| 79 | 87 | /// S3's 5 GiB single-PUT ceiling, which is what the tier limits allow for. | |
| 80 | - | pub const MULTIPART_THRESHOLD_BYTES: u64 = 64 * 1024 * 1024; | |
| 88 | + | pub(crate) const MULTIPART_THRESHOLD_BYTES: u64 = 64 * 1024 * 1024; | |
| 81 | 89 | ||
| 82 | 90 | /// How many presigned part URLs to request at a time. Must not exceed the | |
| 83 | 91 | /// server's own window cap. | |
| @@ -85,7 +93,7 @@ const PART_URL_WINDOW: u32 = 100; | |||
| 85 | 93 | ||
| 86 | 94 | /// An opened multipart upload session. | |
| 87 | 95 | #[derive(Debug, Clone, Deserialize)] | |
| 88 | - | pub struct MultipartStart { | |
| 96 | + | pub(crate) struct MultipartStart { | |
| 89 | 97 | pub upload_id: String, | |
| 90 | 98 | pub s3_key: String, | |
| 91 | 99 | pub part_size: u64, | |
| @@ -95,7 +103,7 @@ pub struct MultipartStart { | |||
| 95 | 103 | ||
| 96 | 104 | /// One presigned part target, with the exact length the signature binds. | |
| 97 | 105 | #[derive(Debug, Clone, Deserialize)] | |
| 98 | - | pub struct MultipartPartUrl { | |
| 106 | + | pub(crate) struct MultipartPartUrl { | |
| 99 | 107 | pub part_number: i32, | |
| 100 | 108 | pub content_length: u64, | |
| 101 | 109 | pub url: String, | |
| @@ -108,7 +116,7 @@ struct MultipartPartsResponse { | |||
| 108 | 116 | ||
| 109 | 117 | /// Full item detail returned from the get/update endpoints. | |
| 110 | 118 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 111 | - | pub struct ItemDetail { | |
| 119 | + | pub(crate) struct ItemDetail { | |
| 112 | 120 | pub id: String, | |
| 113 | 121 | pub title: String, | |
| 114 | 122 | pub description: Option<String>, | |
| @@ -130,7 +138,11 @@ pub struct ItemDetail { | |||
| 130 | 138 | ||
| 131 | 139 | /// A version of an item. | |
| 132 | 140 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 133 | - | pub struct Version { | |
| 141 | + | #[allow( | |
| 142 | + | clippy::struct_field_names, | |
| 143 | + | reason = "version_number mirrors the server JSON/DB schema; renaming would diverge the field from the wire contract" | |
| 144 | + | )] | |
| 145 | + | pub(crate) struct Version { | |
| 134 | 146 | pub id: String, | |
| 135 | 147 | pub version_number: String, | |
| 136 | 148 | pub changelog: Option<String>, | |
| @@ -143,7 +155,7 @@ pub struct Version { | |||
| 143 | 155 | ||
| 144 | 156 | /// A blog post summary. | |
| 145 | 157 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 146 | - | pub struct BlogPost { | |
| 158 | + | pub(crate) struct BlogPost { | |
| 147 | 159 | pub id: String, | |
| 148 | 160 | pub title: String, | |
| 149 | 161 | pub slug: String, | |
| @@ -155,7 +167,7 @@ pub struct BlogPost { | |||
| 155 | 167 | ||
| 156 | 168 | /// A promo code. | |
| 157 | 169 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 158 | - | pub struct PromoCode { | |
| 170 | + | pub(crate) struct PromoCode { | |
| 159 | 171 | pub id: String, | |
| 160 | 172 | pub code: String, | |
| 161 | 173 | pub code_purpose: String, | |
| @@ -170,7 +182,7 @@ pub struct PromoCode { | |||
| 170 | 182 | ||
| 171 | 183 | /// A license key. | |
| 172 | 184 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 173 | - | pub struct LicenseKey { | |
| 185 | + | pub(crate) struct LicenseKey { | |
| 174 | 186 | pub id: String, | |
| 175 | 187 | pub key_code: String, | |
| 176 | 188 | pub activation_count: i32, | |
| @@ -181,7 +193,7 @@ pub struct LicenseKey { | |||
| 181 | 193 | ||
| 182 | 194 | /// Response from the storage-info internal endpoint. | |
| 183 | 195 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 184 | - | pub struct StorageInfo { | |
| 196 | + | pub(crate) struct StorageInfo { | |
| 185 | 197 | pub storage_used_bytes: i64, | |
| 186 | 198 | pub max_storage_bytes: i64, | |
| 187 | 199 | pub allows_file_uploads: bool, | |
| @@ -189,7 +201,7 @@ pub struct StorageInfo { | |||
| 189 | 201 | ||
| 190 | 202 | /// A revenue bucket for analytics timeseries. | |
| 191 | 203 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 192 | - | pub struct AnalyticsBucket { | |
| 204 | + | pub(crate) struct AnalyticsBucket { | |
| 193 | 205 | pub label: String, | |
| 194 | 206 | pub revenue_cents: i64, | |
| 195 | 207 | pub sales_count: i64, | |
| @@ -197,7 +209,7 @@ pub struct AnalyticsBucket { | |||
| 197 | 209 | ||
| 198 | 210 | /// Per-project revenue summary. | |
| 199 | 211 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 200 | - | pub struct ProjectRevenue { | |
| 212 | + | pub(crate) struct ProjectRevenue { | |
| 201 | 213 | pub id: String, | |
| 202 | 214 | pub title: String, | |
| 203 | 215 | pub revenue_cents: i64, | |
| @@ -205,7 +217,7 @@ pub struct ProjectRevenue { | |||
| 205 | 217 | ||
| 206 | 218 | /// Analytics response with timeseries, comparison, and top projects. | |
| 207 | 219 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 208 | - | pub struct AnalyticsData { | |
| 220 | + | pub(crate) struct AnalyticsData { | |
| 209 | 221 | pub buckets: Vec<AnalyticsBucket>, | |
| 210 | 222 | pub current_revenue_cents: i64, | |
| 211 | 223 | pub previous_revenue_cents: i64, | |
| @@ -218,7 +230,7 @@ pub struct AnalyticsData { | |||
| 218 | 230 | ||
| 219 | 231 | /// A seller transaction. | |
| 220 | 232 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 221 | - | pub struct Transaction { | |
| 233 | + | pub(crate) struct Transaction { | |
| 222 | 234 | pub id: String, | |
| 223 | 235 | pub item_title: Option<String>, | |
| 224 | 236 | pub amount_cents: i32, | |
| @@ -229,14 +241,14 @@ pub struct Transaction { | |||
| 229 | 241 | ||
| 230 | 242 | /// CSV export result. | |
| 231 | 243 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 232 | - | pub struct ExportResult { | |
| 244 | + | pub(crate) struct ExportResult { | |
| 233 | 245 | pub csv: String, | |
| 234 | 246 | pub row_count: usize, | |
| 235 | 247 | } | |
| 236 | 248 | ||
| 237 | 249 | /// A registered SSH key. | |
| 238 | 250 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 239 | - | pub struct SshKeyInfo { | |
| 251 | + | pub(crate) struct SshKeyInfo { | |
| 240 | 252 | pub id: String, | |
| 241 | 253 | pub label: String, | |
| 242 | 254 | pub fingerprint: String, | |
| @@ -245,7 +257,7 @@ pub struct SshKeyInfo { | |||
| 245 | 257 | ||
| 246 | 258 | /// A tag on an item or from search. | |
| 247 | 259 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 248 | - | pub struct TagInfo { | |
| 260 | + | pub(crate) struct TagInfo { | |
| 249 | 261 | pub id: String, | |
| 250 | 262 | pub name: String, | |
| 251 | 263 | pub slug: String, | |
| @@ -255,14 +267,14 @@ pub struct TagInfo { | |||
| 255 | 267 | /// Result of a broadcast send. | |
| 256 | 268 | #[derive(Debug, Deserialize)] | |
| 257 | 269 | #[allow(dead_code)] // wire contract: every field the endpoint returns, read or not | |
| 258 | - | pub struct BroadcastResult { | |
| 270 | + | pub(crate) struct BroadcastResult { | |
| 259 | 271 | pub success: bool, | |
| 260 | 272 | pub recipient_count: usize, | |
| 261 | 273 | } | |
| 262 | 274 | ||
| 263 | 275 | /// A subscription tier. | |
| 264 | 276 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 265 | - | pub struct TierInfo { | |
| 277 | + | pub(crate) struct TierInfo { | |
| 266 | 278 | pub id: String, | |
| 267 | 279 | pub name: String, | |
| 268 | 280 | pub description: String, | |
| @@ -272,7 +284,7 @@ pub struct TierInfo { | |||
| 272 | 284 | ||
| 273 | 285 | /// A collection. | |
| 274 | 286 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 275 | - | pub struct CollectionInfo { | |
| 287 | + | pub(crate) struct CollectionInfo { | |
| 276 | 288 | pub id: String, | |
| 277 | 289 | pub slug: String, | |
| 278 | 290 | pub title: String, | |
| @@ -283,7 +295,7 @@ pub struct CollectionInfo { | |||
| 283 | 295 | ||
| 284 | 296 | /// Custom domain info. | |
| 285 | 297 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
| 286 | - | pub struct DomainInfo { | |
| 298 | + | pub(crate) struct DomainInfo { | |
| 287 | 299 | pub id: String, | |
| 288 | 300 | pub domain: String, | |
| 289 | 301 | pub verified: bool, | |
| @@ -294,14 +306,14 @@ pub struct DomainInfo { | |||
| 294 | 306 | /// Domain verification result. | |
| 295 | 307 | #[derive(Debug, Deserialize)] | |
| 296 | 308 | #[allow(dead_code)] // wire contract: every field the endpoint returns, read or not | |
| 297 | - | pub struct DomainVerifyResult { | |
| 309 | + | pub(crate) struct DomainVerifyResult { | |
| 298 | 310 | pub verified: bool, | |
| 299 | 311 | pub message: String, | |
| 300 | 312 | } | |
| 301 | 313 | ||
| 302 | 314 | /// Response from the git authorize endpoint. | |
| 303 | 315 | #[derive(Debug, Deserialize)] | |
| 304 | - | pub struct GitAuthResponse { | |
| 316 | + | pub(crate) struct GitAuthResponse { | |
| 305 | 317 | pub repo_path: String, | |
| 306 | 318 | } | |
| 307 | 319 | ||
| @@ -342,7 +354,7 @@ async fn empty_response(resp: reqwest::Response, context: &str) -> anyhow::Resul | |||
| 342 | 354 | ||
| 343 | 355 | /// Client for calling MNW internal API endpoints. | |
| 344 | 356 | #[derive(Clone)] | |
| 345 | - | pub struct MnwApiClient { | |
| 357 | + | pub(crate) struct MnwApiClient { | |
| 346 | 358 | http: reqwest::Client, | |
| 347 | 359 | base_url: String, | |
| 348 | 360 | service_token: String, | |
| @@ -352,7 +364,7 @@ pub struct MnwApiClient { | |||
| 352 | 364 | } | |
| 353 | 365 | ||
| 354 | 366 | impl MnwApiClient { | |
| 355 | - | pub fn new(base_url: String, service_token: String) -> Self { | |
| 367 | + | pub(crate) fn new(base_url: String, service_token: String) -> Self { | |
| 356 | 368 | let http = reqwest::Client::builder() | |
| 357 | 369 | .timeout(std::time::Duration::from_secs(5)) | |
| 358 | 370 | .build() | |
| @@ -368,7 +380,7 @@ impl MnwApiClient { | |||
| 368 | 380 | ||
| 369 | 381 | /// Record the actor assertion for the authenticated session. Subsequent | |
| 370 | 382 | /// internal calls forward it so the server can verify the acting identity. | |
| 371 | - | pub fn set_actor_token(&mut self, token: String) { | |
| 383 | + | pub(crate) fn set_actor_token(&mut self, token: String) { | |
| 372 | 384 | self.actor_token = Some(token); | |
| 373 | 385 | } | |
| 374 | 386 | ||
| @@ -379,7 +391,10 @@ impl MnwApiClient { | |||
| 379 | 391 | ||
| 380 | 392 | /// Look up a user by SSH key fingerprint. | |
| 381 | 393 | /// Returns `Ok(Some(info))` if found, `Ok(None)` if not found. | |
| 382 | - | pub async fn lookup_ssh_key(&self, fingerprint: &str) -> anyhow::Result<Option<UserInfo>> { | |
| 394 | + | pub(crate) async fn lookup_ssh_key( | |
| 395 | + | &self, | |
| 396 | + | fingerprint: &str, | |
| 397 | + | ) -> anyhow::Result<Option<UserInfo>> { | |
| 383 | 398 | let url = format!("{}/api/internal/ssh-key-lookup", self.base_url); | |
| 384 | 399 | let resp = self | |
| 385 | 400 | .http | |
| @@ -403,7 +418,7 @@ impl MnwApiClient { | |||
| 403 | 418 | } | |
| 404 | 419 | ||
| 405 | 420 | /// Fetch all projects for a creator with item counts and revenue. | |
| 406 | - | pub async fn get_projects(&self, user_id: &str) -> anyhow::Result<Vec<Project>> { | |
| 421 | + | pub(crate) async fn get_projects(&self, user_id: &str) -> anyhow::Result<Vec<Project>> { | |
| 407 | 422 | let url = format!("{}/api/internal/creator/projects", self.base_url); | |
| 408 | 423 | let resp = self | |
| 409 | 424 | .http | |
| @@ -418,7 +433,7 @@ impl MnwApiClient { | |||
| 418 | 433 | } | |
| 419 | 434 | ||
| 420 | 435 | /// Create a new project. | |
| 421 | - | pub async fn create_project( | |
| 436 | + | pub(crate) async fn create_project( | |
| 422 | 437 | &self, | |
| 423 | 438 | user_id: &str, | |
| 424 | 439 | title: &str, | |
| @@ -447,7 +462,7 @@ impl MnwApiClient { | |||
| 447 | 462 | } | |
| 448 | 463 | ||
| 449 | 464 | /// Fetch items in a project. | |
| 450 | - | pub async fn get_project_items( | |
| 465 | + | pub(crate) async fn get_project_items( | |
| 451 | 466 | &self, | |
| 452 | 467 | project_id: &str, | |
| 453 | 468 | user_id: &str, | |
| @@ -469,7 +484,11 @@ impl MnwApiClient { | |||
| 469 | 484 | } | |
| 470 | 485 | ||
| 471 | 486 | /// Fetch period comparison stats for a creator. | |
| 472 | - | pub async fn get_stats(&self, user_id: &str, range: &str) -> anyhow::Result<CreatorStats> { | |
| 487 | + | pub(crate) async fn get_stats( | |
| 488 | + | &self, | |
| 489 | + | user_id: &str, | |
| 490 | + | range: &str, | |
| 491 | + | ) -> anyhow::Result<CreatorStats> { | |
| 473 | 492 | let url = format!("{}/api/internal/creator/stats", self.base_url); | |
| 474 | 493 | let resp = self | |
| 475 | 494 | .http | |
| @@ -484,7 +503,7 @@ impl MnwApiClient { | |||
| 484 | 503 | } | |
| 485 | 504 | ||
| 486 | 505 | /// Fetch storage usage and limits for a creator. | |
| 487 | - | pub async fn get_storage_info(&self, user_id: &str) -> anyhow::Result<StorageInfo> { | |
| 506 | + | pub(crate) async fn get_storage_info(&self, user_id: &str) -> anyhow::Result<StorageInfo> { | |
| 488 | 507 | let url = format!("{}/api/internal/creator/storage", self.base_url); | |
| 489 | 508 | let resp = self | |
| 490 | 509 | .http | |
| @@ -499,7 +518,7 @@ impl MnwApiClient { | |||
| 499 | 518 | } | |
| 500 | 519 | ||
| 501 | 520 | /// Create an item in a project. | |
| 502 | - | pub async fn create_item( | |
| 521 | + | pub(crate) async fn create_item( | |
| 503 | 522 | &self, | |
| 504 | 523 | user_id: &str, | |
| 505 | 524 | project_id: &str, | |
| @@ -527,7 +546,7 @@ impl MnwApiClient { | |||
| 527 | 546 | } | |
| 528 | 547 | ||
| 529 | 548 | /// Get a presigned S3 upload URL. | |
| 530 | - | pub async fn presign_upload( | |
| 549 | + | pub(crate) async fn presign_upload( | |
| 531 | 550 | &self, | |
| 532 | 551 | user_id: &str, | |
| 533 | 552 | item_id: &str, | |
| @@ -555,7 +574,7 @@ impl MnwApiClient { | |||
| 555 | 574 | } | |
| 556 | 575 | ||
| 557 | 576 | /// Confirm a completed S3 upload. | |
| 558 | - | pub async fn confirm_upload( | |
| 577 | + | pub(crate) async fn confirm_upload( | |
| 559 | 578 | &self, | |
| 560 | 579 | user_id: &str, | |
| 561 | 580 | item_id: &str, | |
| @@ -586,7 +605,7 @@ impl MnwApiClient { | |||
| 586 | 605 | } | |
| 587 | 606 | ||
| 588 | 607 | /// Fetch full item detail. | |
| 589 | - | pub async fn get_item_detail( | |
| 608 | + | pub(crate) async fn get_item_detail( | |
| 590 | 609 | &self, | |
| 591 | 610 | user_id: &str, | |
| 592 | 611 | item_id: &str, | |
| @@ -605,7 +624,7 @@ impl MnwApiClient { | |||
| 605 | 624 | } | |
| 606 | 625 | ||
| 607 | 626 | /// Update item fields. Only non-None fields are changed. | |
| 608 | - | pub async fn update_item( | |
| 627 | + | pub(crate) async fn update_item( | |
| 609 | 628 | &self, | |
| 610 | 629 | user_id: &str, | |
| 611 | 630 | item_id: &str, | |
| @@ -642,7 +661,7 @@ impl MnwApiClient { | |||
| 642 | 661 | } | |
| 643 | 662 | ||
| 644 | 663 | /// Delete an item permanently. | |
| 645 | - | pub async fn delete_item(&self, user_id: &str, item_id: &str) -> anyhow::Result<()> { | |
| 664 | + | pub(crate) async fn delete_item(&self, user_id: &str, item_id: &str) -> anyhow::Result<()> { | |
| 646 | 665 | let url = format!("{}/api/internal/creator/items/{}", self.base_url, item_id); | |
| 647 | 666 | let resp = self | |
| 648 | 667 | .http | |
| @@ -657,7 +676,11 @@ impl MnwApiClient { | |||
| 657 | 676 | } | |
| 658 | 677 | ||
| 659 | 678 | /// Publish an item (set is_public=true). | |
| 660 | - | pub async fn publish_item(&self, user_id: &str, item_id: &str) -> anyhow::Result<ItemDetail> { | |
| 679 | + | pub(crate) async fn publish_item( | |
| 680 | + | &self, | |
| 681 | + | user_id: &str, | |
| 682 | + | item_id: &str, | |
| 683 | + | ) -> anyhow::Result<ItemDetail> { | |
| 661 | 684 | let url = format!( | |
| 662 | 685 | "{}/api/internal/creator/items/{}/publish", | |
| 663 | 686 | self.base_url, item_id | |
| @@ -675,7 +698,11 @@ impl MnwApiClient { | |||
| 675 | 698 | } | |
| 676 | 699 | ||
| 677 | 700 | /// Unpublish an item (set is_public=false). | |
| 678 | - | pub async fn unpublish_item(&self, user_id: &str, item_id: &str) -> anyhow::Result<ItemDetail> { | |
| 701 | + | pub(crate) async fn unpublish_item( | |
| 702 | + | &self, | |
| 703 | + | user_id: &str, | |
| 704 | + | item_id: &str, | |
| 705 | + | ) -> anyhow::Result<ItemDetail> { | |
| 679 | 706 | let url = format!( | |
| 680 | 707 | "{}/api/internal/creator/items/{}/unpublish", | |
| 681 | 708 | self.base_url, item_id | |
| @@ -693,7 +720,7 @@ impl MnwApiClient { | |||
| 693 | 720 | } | |
| 694 | 721 | ||
| 695 | 722 | /// Fetch versions for an item. | |
| 696 | - | pub async fn get_item_versions( | |
| 723 | + | pub(crate) async fn get_item_versions( | |
| 697 | 724 | &self, | |
| 698 | 725 | user_id: &str, | |
| 699 | 726 | item_id: &str, | |
| @@ -717,7 +744,7 @@ impl MnwApiClient { | |||
| 717 | 744 | // โโ Multipart upload session (large files) โโ | |
| 718 | 745 | ||
| 719 | 746 | /// Open a multipart upload session and get the part geometry. | |
| 720 | - | pub async fn multipart_start( | |
| 747 | + | pub(crate) async fn multipart_start( | |
| 721 | 748 | &self, | |
| 722 | 749 | item_id: &str, | |
| 723 | 750 | file_type: &str, | |
| @@ -810,7 +837,11 @@ impl MnwApiClient { | |||
| 810 | 837 | ||
| 811 | 838 | /// Release the parts of an abandoned session. Incomplete multipart uploads | |
| 812 | 839 | /// bill for their parts until aborted. | |
| 813 | - | pub async fn multipart_abort(&self, s3_key: &str, upload_id: &str) -> anyhow::Result<()> { | |
| 840 | + | pub(crate) async fn multipart_abort( | |
| 841 | + | &self, | |
| 842 | + | s3_key: &str, | |
| 843 | + | upload_id: &str, | |
| 844 | + | ) -> anyhow::Result<()> { | |
| 814 | 845 | let url = format!("{}/api/internal/upload/multipart/abort", self.base_url); | |
| 815 | 846 | let resp = self | |
| 816 | 847 | .http | |
| @@ -835,7 +866,7 @@ impl MnwApiClient { | |||
| 835 | 866 | /// does not leave parts billing indefinitely โ the single abort site means a | |
| 836 | 867 | /// future failure path added inside cannot forget to. | |
| 837 | 868 | #[allow(clippy::too_many_arguments)] | |
| 838 | - | pub async fn upload_file_multipart( | |
| 869 | + | pub(crate) async fn upload_file_multipart( | |
| 839 | 870 | &self, | |
| 840 | 871 | item_id: &str, | |
| 841 | 872 | file_type: &str, | |
| @@ -993,7 +1024,7 @@ impl MnwApiClient { | |||
| 993 | 1024 | } | |
| 994 | 1025 | ||
| 995 | 1026 | /// Upload a file to S3 using a presigned URL. | |
| 996 | - | pub async fn upload_to_s3( | |
| 1027 | + | pub(crate) async fn upload_to_s3( | |
| 997 | 1028 | &self, | |
| 998 | 1029 | presigned_url: &str, | |
| 999 | 1030 | file_path: &std::path::Path, | |
| @@ -1023,7 +1054,7 @@ impl MnwApiClient { | |||
| 1023 | 1054 | // โโ Blog posts โโ | |
| 1024 | 1055 | ||
| 1025 | 1056 | /// List blog posts for a project. | |
| 1026 | - | pub async fn list_blog_posts( | |
| 1057 | + | pub(crate) async fn list_blog_posts( | |
| 1027 | 1058 | &self, | |
| 1028 | 1059 | user_id: &str, | |
| 1029 | 1060 | project_id: &str, | |
| @@ -1045,7 +1076,7 @@ impl MnwApiClient { | |||
| 1045 | 1076 | } | |
| 1046 | 1077 | ||
| 1047 | 1078 | /// Create a blog post, optionally scheduled for future publication. | |
| 1048 | - | pub async fn create_blog_post( | |
| 1079 | + | pub(crate) async fn create_blog_post( | |
| 1049 | 1080 | &self, | |
| 1050 | 1081 | user_id: &str, | |
| 1051 | 1082 | project_id: &str, | |
| @@ -1078,7 +1109,11 @@ impl MnwApiClient { | |||
| 1078 | 1109 | } | |
| 1079 | 1110 | ||
| 1080 | 1111 | /// Delete a blog post. | |
| 1081 | - | pub async fn delete_blog_post(&self, user_id: &str, post_id: &str) -> anyhow::Result<()> { | |
| 1112 | + | pub(crate) async fn delete_blog_post( | |
| 1113 | + | &self, | |
| 1114 | + | user_id: &str, | |
| 1115 | + | post_id: &str, | |
| 1116 | + | ) -> anyhow::Result<()> { | |
| 1082 | 1117 | let url = format!("{}/api/internal/creator/blog/{}", self.base_url, post_id); | |
| 1083 | 1118 | let resp = self | |
| 1084 | 1119 | .http | |
| @@ -1095,7 +1130,7 @@ impl MnwApiClient { | |||
| 1095 | 1130 | // โโ Promo codes โโ | |
| 1096 | 1131 | ||
| 1097 | 1132 | /// List promo codes for a creator. | |
| 1098 | - | pub async fn list_promo_codes(&self, user_id: &str) -> anyhow::Result<Vec<PromoCode>> { | |
| 1133 | + | pub(crate) async fn list_promo_codes(&self, user_id: &str) -> anyhow::Result<Vec<PromoCode>> { | |
| 1099 | 1134 | let url = format!("{}/api/internal/creator/promo-codes", self.base_url); | |
| 1100 | 1135 | let resp = self | |
| 1101 | 1136 | .http | |
| @@ -1110,7 +1145,7 @@ impl MnwApiClient { | |||
| 1110 | 1145 | } | |
| 1111 | 1146 | ||
| 1112 | 1147 | /// Create a promo code. | |
| 1113 | - | pub async fn create_promo_code( | |
| 1148 | + | pub(crate) async fn create_promo_code( | |
| 1114 | 1149 | &self, | |
| 1115 | 1150 | user_id: &str, | |
| 1116 | 1151 | code: &str, | |
| @@ -1147,7 +1182,11 @@ impl MnwApiClient { | |||
| 1147 | 1182 | } | |
| 1148 | 1183 | ||
| 1149 | 1184 | /// Delete a promo code. | |
| 1150 | - | pub async fn delete_promo_code(&self, user_id: &str, code_id: &str) -> anyhow::Result<()> { | |
| 1185 | + | pub(crate) async fn delete_promo_code( | |
| 1186 | + | &self, | |
| 1187 | + | user_id: &str, | |
| 1188 | + | code_id: &str, | |
| 1189 | + | ) -> anyhow::Result<()> { | |
| 1151 | 1190 | let url = format!( | |
| 1152 | 1191 | "{}/api/internal/creator/promo-codes/{}", | |
| 1153 | 1192 | self.base_url, code_id | |
| @@ -1167,7 +1206,7 @@ impl MnwApiClient { | |||
| 1167 | 1206 | // โโ License keys โโ | |
| 1168 | 1207 | ||
| 1169 | 1208 | /// List license keys for an item. | |
| 1170 | - | pub async fn list_license_keys( | |
| 1209 | + | pub(crate) async fn list_license_keys( | |
| 1171 | 1210 | &self, | |
| 1172 | 1211 | user_id: &str, | |
| 1173 | 1212 | item_id: &str, | |
| @@ -1189,7 +1228,7 @@ impl MnwApiClient { | |||
| 1189 | 1228 | } | |
| 1190 | 1229 | ||
| 1191 | 1230 | /// Generate a new license key for an item. | |
| 1192 | - | pub async fn generate_license_key( | |
| 1231 | + | pub(crate) async fn generate_license_key( |
Lines truncated
| @@ -4,6 +4,8 @@ | |||
| 4 | 4 | //! dispatches to this module. All commands write output to a byte buffer and | |
| 5 | 5 | //! return it for the SSH channel. | |
| 6 | 6 | ||
| 7 | + | use std::fmt::Write as _; | |
| 8 | + | ||
| 7 | 9 | use crate::api::{MnwApiClient, UserInfo}; | |
| 8 | 10 | use crate::format; | |
| 9 | 11 | use crate::staging; | |
| @@ -29,7 +31,7 @@ pub(crate) fn sanitize_api_error(e: &anyhow::Error) -> String { | |||
| 29 | 31 | } | |
| 30 | 32 | ||
| 31 | 33 | /// Execute a non-interactive command and return the output bytes. | |
| 32 | - | pub async fn execute(command_line: &str, user: &UserInfo, api: &MnwApiClient) -> Vec<u8> { | |
| 34 | + | pub(crate) async fn execute(command_line: &str, user: &UserInfo, api: &MnwApiClient) -> Vec<u8> { | |
| 33 | 35 | let parts: Vec<&str> = command_line.split_whitespace().collect(); | |
| 34 | 36 | if parts.is_empty() { | |
| 35 | 37 | return help_text(); | |
| @@ -142,14 +144,16 @@ async fn cmd_projects(user: &UserInfo, api: &MnwApiClient, json: bool) -> Vec<u8 | |||
| 142 | 144 | for p in &projects { | |
| 143 | 145 | let status = if p.is_public { "public" } else { "draft" }; | |
| 144 | 146 | let revenue = format::format_cents(p.revenue_cents); | |
| 145 | - | out.push_str(&format!( | |
| 147 | + | write!( | |
| 148 | + | out, | |
| 146 | 149 | "{:<30} {:<12} {:<8} {:<6} {:<10}\r\n", | |
| 147 | 150 | truncate(&p.title, 29), | |
| 148 | 151 | p.project_type, | |
| 149 | 152 | status, | |
| 150 | 153 | p.item_count, | |
| 151 | 154 | revenue, | |
| 152 | - | )); | |
| 155 | + | ) | |
| 156 | + | .unwrap(); | |
| 153 | 157 | } | |
| 154 | 158 | out.into_bytes() | |
| 155 | 159 | } | |
| @@ -165,28 +169,34 @@ async fn cmd_analytics(user: &UserInfo, api: &MnwApiClient, range: &str, json: b | |||
| 165 | 169 | } | |
| 166 | 170 | ||
| 167 | 171 | let mut out = String::new(); | |
| 168 | - | out.push_str(&format!("Analytics ({range})\r\n\r\n")); | |
| 172 | + | write!(out, "Analytics ({range})\r\n\r\n").unwrap(); | |
| 169 | 173 | ||
| 170 | 174 | let rev = format::format_cents(data.current_revenue_cents); | |
| 171 | 175 | let prev_rev = format::format_cents(data.previous_revenue_cents); | |
| 172 | - | out.push_str(&format!("Revenue: {rev} (prev: {prev_rev})\r\n")); | |
| 173 | - | out.push_str(&format!( | |
| 176 | + | write!(out, "Revenue: {rev} (prev: {prev_rev})\r\n").unwrap(); | |
| 177 | + | write!( | |
| 178 | + | out, | |
| 174 | 179 | "Sales: {} (prev: {})\r\n", | |
| 175 | 180 | data.current_sales, data.previous_sales | |
| 176 | - | )); | |
| 177 | - | out.push_str(&format!( | |
| 181 | + | ) | |
| 182 | + | .unwrap(); | |
| 183 | + | write!( | |
| 184 | + | out, | |
| 178 | 185 | "Followers: {} (prev: {})\r\n", | |
| 179 | 186 | data.current_followers, data.previous_followers | |
| 180 | - | )); | |
| 187 | + | ) | |
| 188 | + | .unwrap(); | |
| 181 | 189 | ||
| 182 | 190 | if !data.top_projects.is_empty() { | |
| 183 | 191 | out.push_str("\r\nTop Projects:\r\n"); | |
| 184 | 192 | for p in &data.top_projects { | |
| 185 | - | out.push_str(&format!( | |
| 193 | + | write!( | |
| 194 | + | out, | |
| 186 | 195 | " {:<30} {}\r\n", | |
| 187 | 196 | truncate(&p.title, 29), | |
| 188 | 197 | format::format_cents(p.revenue_cents) | |
| 189 | - | )); | |
| 198 | + | ) | |
| 199 | + | .unwrap(); | |
| 190 | 200 | } | |
| 191 | 201 | } | |
| 192 | 202 | ||
| @@ -215,13 +225,15 @@ async fn cmd_transactions(user: &UserInfo, api: &MnwApiClient, json: bool) -> Ve | |||
| 215 | 225 | let title = tx.item_title.as_deref().unwrap_or("--"); | |
| 216 | 226 | let amount = format::format_cents(tx.amount_cents as i64); | |
| 217 | 227 | let date = tx.created_at.get(..10).unwrap_or(&tx.created_at); | |
| 218 | - | out.push_str(&format!( | |
| 228 | + | write!( | |
| 229 | + | out, | |
| 219 | 230 | "{:<30} {:<10} {:<12} {:<12}\r\n", | |
| 220 | 231 | truncate(title, 29), | |
| 221 | 232 | amount, | |
| 222 | 233 | tx.status, | |
| 223 | 234 | date, | |
| 224 | - | )); | |
| 235 | + | ) | |
| 236 | + | .unwrap(); | |
| 225 | 237 | } | |
| 226 | 238 | out.into_bytes() | |
| 227 | 239 | } | |
| @@ -253,7 +265,7 @@ async fn cmd_promo_list(user: &UserInfo, api: &MnwApiClient, json: bool) -> Vec< | |||
| 253 | 265 | out.push_str("\r\n"); | |
| 254 | 266 | for c in &codes { | |
| 255 | 267 | let discount = match (c.discount_type.as_deref(), c.discount_value) { | |
| 256 | - | (Some("percentage"), Some(v)) => format!("{}% off", v), | |
| 268 | + | (Some("percentage"), Some(v)) => format!("{v}% off"), | |
| 257 | 269 | (Some("fixed"), Some(v)) => format!("${}.{:02} off", v / 100, v % 100), | |
| 258 | 270 | _ => "Free".to_string(), | |
| 259 | 271 | }; | |
| @@ -266,13 +278,15 @@ async fn cmd_promo_list(user: &UserInfo, api: &MnwApiClient, json: bool) -> Vec< | |||
| 266 | 278 | Some(max) => format!("{}/{}", c.use_count, max), | |
| 267 | 279 | None => c.use_count.to_string(), | |
| 268 | 280 | }; | |
| 269 | - | out.push_str(&format!( | |
| 281 | + | write!( | |
| 282 | + | out, | |
| 270 | 283 | "{:<20} {:<12} {:<20} {:<10}\r\n", | |
| 271 | 284 | truncate(&c.code, 19), | |
| 272 | 285 | discount, | |
| 273 | 286 | truncate(scope, 19), | |
| 274 | 287 | uses, | |
| 275 | - | )); | |
| 288 | + | ) | |
| 289 | + | .unwrap(); | |
| 276 | 290 | } | |
| 277 | 291 | out.into_bytes() | |
| 278 | 292 | } | |
| @@ -326,13 +340,15 @@ async fn cmd_blog_list(user: &UserInfo, api: &MnwApiClient, slug: &str, json: bo | |||
| 326 | 340 | for p in &posts { | |
| 327 | 341 | let status = if p.is_published { "published" } else { "draft" }; | |
| 328 | 342 | let date = p.created_at.get(..10).unwrap_or(&p.created_at); | |
| 329 | - | out.push_str(&format!( | |
| 343 | + | write!( | |
| 344 | + | out, | |
| 330 | 345 | "{:<30} {:<20} {:<10} {:<12}\r\n", | |
| 331 | 346 | truncate(&p.title, 29), | |
| 332 | 347 | truncate(&p.slug, 19), | |
| 333 | 348 | status, | |
| 334 | 349 | date, | |
| 335 | - | )); | |
| 350 | + | ) | |
| 351 | + | .unwrap(); | |
| 336 | 352 | } | |
| 337 | 353 | out.into_bytes() | |
| 338 | 354 | } | |
| @@ -374,13 +390,15 @@ async fn cmd_collections(user: &UserInfo, api: &MnwApiClient, json: bool) -> Vec | |||
| 374 | 390 | out.push_str("\r\n"); | |
| 375 | 391 | for c in &collections { | |
| 376 | 392 | let status = if c.is_public { "public" } else { "draft" }; | |
| 377 | - | out.push_str(&format!( | |
| 393 | + | write!( | |
| 394 | + | out, | |
| 378 | 395 | "{:<25} {:<25} {:<8} {:<6}\r\n", | |
| 379 | 396 | truncate(&c.title, 24), | |
| 380 | 397 | truncate(&c.slug, 24), | |
| 381 | 398 | status, | |
| 382 | 399 | c.item_count, | |
| 383 | - | )); | |
| 400 | + | ) | |
| 401 | + | .unwrap(); | |
| 384 | 402 | } | |
| 385 | 403 | out.into_bytes() | |
| 386 | 404 | } | |
| @@ -396,7 +414,7 @@ async fn cmd_domain_show(user: &UserInfo, api: &MnwApiClient) -> Vec<u8> { | |||
| 396 | 414 | if !d.verified | |
| 397 | 415 | && let Some(ref instr) = d.instructions | |
| 398 | 416 | { | |
| 399 | - | out.push_str(&format!("{}\r\n", instr)); | |
| 417 | + | write!(out, "{instr}\r\n").unwrap(); | |
| 400 | 418 | } | |
| 401 | 419 | out.into_bytes() | |
| 402 | 420 | } | |
| @@ -413,7 +431,7 @@ async fn cmd_domain_add(user: &UserInfo, api: &MnwApiClient, domain: &str) -> Ve | |||
| 413 | 431 | Ok(d) => { | |
| 414 | 432 | let mut out = format!("Domain added: {}\r\n", d.domain); | |
| 415 | 433 | if let Some(ref instr) = d.instructions { | |
| 416 | - | out.push_str(&format!("{}\r\n", instr)); | |
| 434 | + | write!(out, "{instr}\r\n").unwrap(); | |
| 417 | 435 | } | |
| 418 | 436 | out.push_str("Run `domain verify` after adding the DNS record.\r\n"); | |
| 419 | 437 | out.into_bytes() | |
| @@ -437,7 +455,7 @@ async fn cmd_domain_remove(user: &UserInfo, api: &MnwApiClient) -> Vec<u8> { | |||
| 437 | 455 | } | |
| 438 | 456 | ||
| 439 | 457 | /// Execute a pipe-mode file upload (called from handler after stdin EOF). | |
| 440 | - | pub async fn execute_pipe_upload( | |
| 458 | + | pub(crate) async fn execute_pipe_upload( | |
| 441 | 459 | api: &MnwApiClient, | |
| 442 | 460 | upload: crate::ssh::handler::PipeUpload, | |
| 443 | 461 | ) -> anyhow::Result<String> { | |
| @@ -518,7 +536,7 @@ pub async fn execute_pipe_upload( | |||
| 518 | 536 | )) | |
| 519 | 537 | } | |
| 520 | 538 | ||
| 521 | - | pub fn help_text() -> Vec<u8> { | |
| 539 | + | pub(crate) fn help_text() -> Vec<u8> { | |
| 522 | 540 | b"Usage: ssh cli.makenot.work <command>\r\n\ | |
| 523 | 541 | \r\n\ | |
| 524 | 542 | Commands:\r\n\ |
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | use std::path::PathBuf; | |
| 4 | 4 | ||
| 5 | 5 | /// Application configuration. | |
| 6 | - | pub struct Config { | |
| 6 | + | pub(crate) struct Config { | |
| 7 | 7 | /// Port to listen on for SSH connections. | |
| 8 | 8 | pub port: u16, | |
| 9 | 9 | /// MNW API base URL (e.g., "http://localhost:3000"). | |
| @@ -20,7 +20,7 @@ pub struct Config { | |||
| 20 | 20 | ||
| 21 | 21 | impl Config { | |
| 22 | 22 | /// Load configuration from environment variables. | |
| 23 | - | pub fn from_env() -> anyhow::Result<Self> { | |
| 23 | + | pub(crate) fn from_env() -> anyhow::Result<Self> { | |
| 24 | 24 | let port: u16 = std::env::var("SSH_PORT") | |
| 25 | 25 | .unwrap_or_else(|_| "2222".to_string()) | |
| 26 | 26 | .parse() |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | //! Shared formatting utilities used across TUI screens and commands. | |
| 2 | 2 | ||
| 3 | 3 | /// Format a revenue amount in cents. Returns "$0" for zero. | |
| 4 | - | pub fn format_cents(cents: i64) -> String { | |
| 4 | + | pub(crate) fn format_cents(cents: i64) -> String { | |
| 5 | 5 | if cents == 0 { | |
| 6 | 6 | "$0".to_string() | |
| 7 | 7 | } else { | |
| @@ -10,7 +10,7 @@ pub fn format_cents(cents: i64) -> String { | |||
| 10 | 10 | } | |
| 11 | 11 | ||
| 12 | 12 | /// Format an item price in cents. Returns "Free" for zero. | |
| 13 | - | pub fn format_price(cents: i32) -> String { | |
| 13 | + | pub(crate) fn format_price(cents: i32) -> String { | |
| 14 | 14 | if cents == 0 { | |
| 15 | 15 | "Free".to_string() | |
| 16 | 16 | } else { | |
| @@ -19,7 +19,7 @@ pub fn format_price(cents: i32) -> String { | |||
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | /// Human-readable creator tier label. | |
| 22 | - | pub fn format_tier(tier: &str) -> &str { | |
| 22 | + | pub(crate) fn format_tier(tier: &str) -> &str { | |
| 23 | 23 | match tier { | |
| 24 | 24 | "basic" => "Basic", | |
| 25 | 25 | "small_files" => "Small Files", | |
| @@ -30,7 +30,7 @@ pub fn format_tier(tier: &str) -> &str { | |||
| 30 | 30 | } | |
| 31 | 31 | ||
| 32 | 32 | /// Human-readable project type label. | |
| 33 | - | pub fn format_project_type(pt: &str) -> &str { | |
| 33 | + | pub(crate) fn format_project_type(pt: &str) -> &str { | |
| 34 | 34 | match pt { | |
| 35 | 35 | "software" => "Software", | |
| 36 | 36 | "music" => "Music", | |
| @@ -45,7 +45,7 @@ pub fn format_project_type(pt: &str) -> &str { | |||
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | 47 | /// Human-readable item type label. | |
| 48 | - | pub fn format_item_type(it: &str) -> &str { | |
| 48 | + | pub(crate) fn format_item_type(it: &str) -> &str { | |
| 49 | 49 | match it { | |
| 50 | 50 | "audio" => "Audio", | |
| 51 | 51 | "text" => "Text", |
| @@ -50,8 +50,8 @@ async fn main() -> anyhow::Result<()> { | |||
| 50 | 50 | // Spawn hourly cleanup task for stale staging files (24h TTL) | |
| 51 | 51 | let cleanup_dir = config.staging_dir.clone(); | |
| 52 | 52 | tokio::spawn(async move { | |
| 53 | - | let ttl = std::time::Duration::from_secs(24 * 60 * 60); | |
| 54 | - | let mut interval = tokio::time::interval(std::time::Duration::from_secs(60 * 60)); | |
| 53 | + | let ttl = std::time::Duration::from_hours(24); | |
| 54 | + | let mut interval = tokio::time::interval(std::time::Duration::from_hours(1)); | |
| 55 | 55 | loop { | |
| 56 | 56 | interval.tick().await; | |
| 57 | 57 | staging::cleanup_stale(&cleanup_dir, ttl).await; | |
| @@ -92,7 +92,7 @@ async fn main() -> anyhow::Result<()> { | |||
| 92 | 92 | result = server.run_on_address(Arc::new(ssh_config), addr) => { | |
| 93 | 93 | result?; | |
| 94 | 94 | } | |
| 95 | - | _ = shutdown_signal() => { | |
| 95 | + | () = shutdown_signal() => { | |
| 96 | 96 | tracing::info!("shutdown signal received, stopping"); | |
| 97 | 97 | } | |
| 98 | 98 | } |
| @@ -18,10 +18,10 @@ const ALLOWED_TARGETS: &[&str] = &["linux", "darwin", "windows"]; | |||
| 18 | 18 | const ALLOWED_ARCHS: &[&str] = &["x86_64", "aarch64"]; | |
| 19 | 19 | ||
| 20 | 20 | /// Entry point for the `ota` subcommand. `rest` is everything after `ota`. | |
| 21 | - | pub async fn run(rest: &[String]) -> Result<()> { | |
| 21 | + | pub(crate) async fn run(rest: &[String]) -> Result<()> { | |
| 22 | 22 | match rest.first().map(String::as_str) { | |
| 23 | 23 | Some("publish") => publish(&rest[1..]).await, | |
| 24 | - | Some("-h") | Some("--help") | None => { | |
| 24 | + | Some("-h" | "--help") | None => { | |
| 25 | 25 | print_usage(); | |
| 26 | 26 | Ok(()) | |
| 27 | 27 | } | |
| @@ -352,12 +352,10 @@ async fn authenticate_oauth(client: &SyncKitClient, key: &str) -> Result<()> { | |||
| 352 | 352 | open_browser(&url); | |
| 353 | 353 | println!(" waiting for the authorization redirect (5 min timeout)..."); | |
| 354 | 354 | ||
| 355 | - | let (code, got_state) = tokio::time::timeout( | |
| 356 | - | std::time::Duration::from_secs(300), | |
| 357 | - | wait_for_code(&listener), | |
| 358 | - | ) | |
| 359 | - | .await | |
| 360 | - | .context("timed out waiting for OAuth authorization")??; | |
| 355 | + | let (code, got_state) = | |
| 356 | + | tokio::time::timeout(std::time::Duration::from_mins(5), wait_for_code(&listener)) | |
| 357 | + | .await | |
| 358 | + | .context("timed out waiting for OAuth authorization")??; | |
| 361 | 359 | ||
| 362 | 360 | if got_state.as_deref() != Some(state.as_str()) { | |
| 363 | 361 | bail!("OAuth state mismatch โ aborting (possible CSRF or a stale redirect)"); | |
| @@ -385,7 +383,7 @@ async fn wait_for_code(listener: &tokio::net::TcpListener) -> Result<(String, Op | |||
| 385 | 383 | .next() | |
| 386 | 384 | .and_then(|line| line.split_whitespace().nth(1)) | |
| 387 | 385 | .unwrap_or(""); | |
| 388 | - | let query = target.split_once('?').map(|(_, q)| q).unwrap_or(""); | |
| 386 | + | let query = target.split_once('?').map_or("", |(_, q)| q); | |
| 389 | 387 | ||
| 390 | 388 | let (mut code, mut state, mut oauth_err) = (None, None, None); | |
| 391 | 389 | for pair in query.split('&') { | |
| @@ -472,7 +470,7 @@ mod tests { | |||
| 472 | 470 | "https://example.test", | |
| 473 | 471 | ] | |
| 474 | 472 | .iter() | |
| 475 | - | .map(|s| s.to_string()) | |
| 473 | + | .map(std::string::ToString::to_string) | |
| 476 | 474 | .collect() | |
| 477 | 475 | } | |
| 478 | 476 |
| @@ -13,12 +13,12 @@ const MAX_FAILURES: usize = 10; | |||
| 13 | 13 | const WINDOW_SECS: u64 = 60; | |
| 14 | 14 | const PRUNE_THRESHOLD: usize = 1000; | |
| 15 | 15 | ||
| 16 | - | pub struct AuthRateLimiter { | |
| 16 | + | pub(crate) struct AuthRateLimiter { | |
| 17 | 17 | failures: Mutex<HashMap<IpAddr, Vec<Instant>>>, | |
| 18 | 18 | } | |
| 19 | 19 | ||
| 20 | 20 | impl AuthRateLimiter { | |
| 21 | - | pub fn new() -> Self { | |
| 21 | + | pub(crate) fn new() -> Self { | |
| 22 | 22 | Self { | |
| 23 | 23 | failures: Mutex::new(HashMap::new()), | |
| 24 | 24 | } | |
| @@ -26,9 +26,11 @@ impl AuthRateLimiter { | |||
| 26 | 26 | ||
| 27 | 27 | /// Returns `true` if the IP is allowed to attempt auth. | |
| 28 | 28 | /// Returns `false` if the IP has exceeded the failure threshold. | |
| 29 | - | pub fn check(&self, ip: IpAddr) -> bool { | |
| 29 | + | pub(crate) fn check(&self, ip: IpAddr) -> bool { | |
| 30 | 30 | let mut map = self.failures.lock().unwrap(); | |
| 31 | - | let cutoff = Instant::now() - std::time::Duration::from_secs(WINDOW_SECS); | |
| 31 | + | let cutoff = Instant::now() | |
| 32 | + | .checked_sub(std::time::Duration::from_secs(WINDOW_SECS)) | |
| 33 | + | .unwrap(); | |
| 32 | 34 | ||
| 33 | 35 | if let Some(times) = map.get_mut(&ip) { | |
| 34 | 36 | times.retain(|t| *t > cutoff); | |
| @@ -39,12 +41,14 @@ impl AuthRateLimiter { | |||
| 39 | 41 | } | |
| 40 | 42 | ||
| 41 | 43 | /// Record a failed auth attempt for the given IP. | |
| 42 | - | pub fn record_failure(&self, ip: IpAddr) { | |
| 44 | + | pub(crate) fn record_failure(&self, ip: IpAddr) { | |
| 43 | 45 | let mut map = self.failures.lock().unwrap(); | |
| 44 | 46 | ||
| 45 | 47 | // Prune stale entries when map grows large | |
| 46 | 48 | if map.len() > PRUNE_THRESHOLD { | |
| 47 | - | let cutoff = Instant::now() - std::time::Duration::from_secs(WINDOW_SECS); | |
| 49 | + | let cutoff = Instant::now() | |
| 50 | + | .checked_sub(std::time::Duration::from_secs(WINDOW_SECS)) | |
| 51 | + | .unwrap(); | |
| 48 | 52 | map.retain(|_, times| { | |
| 49 | 53 | times.retain(|t| *t > cutoff); | |
| 50 | 54 | !times.is_empty() |