diff options
| author | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-31 13:08:08 -0500 |
|---|---|---|
| committer | Doan Luu Duc Tai <doan@luuduct.ai> | 2026-10-31 13:08:08 -0500 |
| commit | a80df869a92c330fbad9695057720b11a18af901 (patch) | |
| tree | 5307272821cb3adc3f0348413a3b904290890044 /src/app.rs | |
| parent | 9139398d465f5899cc31bc4cdd54daa7392ca550 (diff) | |
Forbid Rust lints and enforce strict typed TypeScript linting
Diffstat (limited to 'src/app.rs')
| -rw-r--r-- | src/app.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..d655774 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,252 @@ +// Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +#![doc = "Rust commands for personal web infrastructure."] + +mod archive; +mod chat; +mod cli; +mod files; +mod images; +mod process; +mod project; +mod release; +mod render; + +use std::path::{Path, PathBuf}; +use std::process::ExitCode; + +use crate::app::cli::{Action, Cli}; +use anyhow::Result; + +use crate::app::project::{Mode, Project}; + +#[doc = "Runs the checked command-line entry point and returns its process status."] +#[must_use] +pub fn run() -> ExitCode { + let arguments = match Cli::try_parse() { + Ok(arguments) => arguments, + Err(error) => { + let status = if error.use_stderr() { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + }; + drop(error.print()); + return status; + } + }; + match execute(arguments) { + Ok(()) => ExitCode::SUCCESS, + Err(error) => { + drop(process::error(format_args!("Error: {error:#}"))); + ExitCode::FAILURE + } + } +} + +fn execute(arguments: Cli) -> Result<()> { + if let Action::Restore { + archive, + destination, + } = &arguments.command + { + return archive::restore(&absolute(archive)?, &absolute(destination)?); + } + let project = Project::discover(arguments.root.as_deref())?; + if let Action::Chat { + init, + reset_owner, + owner_fingerprint, + } = arguments.command + { + return chat(&project, init, reset_owner.as_deref(), owner_fingerprint); + } + + let _operation = project.lock()?; + match arguments.command { + Action::Dev { port } => { + let mut settings = project.settings()?; + settings.mode = Mode::Local; + if let Some(selected_port) = port { + settings.port = selected_port; + } + images::prepare(&project, &mut settings)?; + project.up(&settings) + } + Action::Up { + production, + local, + port, + online, + } => { + let mut settings = project.settings()?; + if production { + settings.mode = Mode::Production; + } + if local { + settings.mode = Mode::Local; + } + if let Some(selected_port) = port { + settings.port = selected_port; + } + if online { + settings.online = true; + } + images::prepare(&project, &mut settings)?; + project.up(&settings) + } + Action::Config { production, port } => { + let mut settings = project.settings()?; + settings.mode = if production { + Mode::Production + } else { + Mode::Local + }; + if let Some(selected_port) = port { + settings.port = selected_port; + } + stack_config::initialize_public(&project.root)?; + render::prepare(&project, &settings)?; + process::line(format_args!( + "Public configuration: {}", + render::directory(&project, &settings).display() + )) + } + Action::Status => project.status(&project.settings()?), + Action::Down => project.down(&project.settings()?), + Action::Backup { + destination, + include_images, + recipient, + key, + } => { + let output = destination.map_or_else( + || Ok(project.root.join("var/exports")), + |path| absolute(&path), + )?; + let configuration = stack_config::Config::load(&project.root)?; + let selected_recipient = + recipient.unwrap_or(configuration.value("BACKUP_RECIPIENT")?.to_owned()); + archive::backup( + &project, + &output, + include_images, + &selected_recipient, + key.as_deref(), + ) + } + Action::Release { destination } => { + let output = destination + .map_or_else(|| Ok(project.root.join("dist")), |path| absolute(&path))?; + release::create(&project, &output) + } + Action::Activate { archive } => release::activate(&absolute(&archive)?, &project), + Action::Check => check(&project), + Action::Licenses => licenses(&project), + Action::Chat { .. } => anyhow::bail!("Chat action was not handled"), + Action::Restore { .. } => anyhow::bail!("Restore action was not handled"), + } +} + +fn chat( + project: &Project, + init: bool, + reset_owner: Option<&str>, + owner_fingerprint: bool, +) -> Result<()> { + if project.settings()?.mode == Mode::Production { + return chat::admin(project, init, reset_owner, owner_fingerprint); + } + let mut command = project.command("cargo")?; + command + .args(["run", "--locked", "-p", "stack-chat-server", "--", "--root"]) + .arg(&project.root); + if init { + command.arg("--init"); + } + if let Some(fingerprint) = reset_owner { + command.arg("--reset-owner").arg(fingerprint); + } + if owner_fingerprint { + command.arg("--owner-fingerprint"); + } + process::run(&mut command) +} + +fn absolute(path: &Path) -> Result<PathBuf> { + Ok(if path.is_absolute() { + path.to_path_buf() + } else { + std::env::current_dir()?.join(path) + }) +} + +fn licenses(project: &Project) -> Result<()> { + process::run(project.command("cargo")?.args([ + "about", + "generate", + "--workspace", + "--locked", + "about.hbs", + "--output-file", + "web/THIRD-PARTY-LICENSES.html", + ])) +} + +fn check(project: &Project) -> Result<()> { + process::run(project.command("sh")?.arg("infra/chat/build-typescript.sh"))?; + process::run( + project + .command("cargo")? + .args(["fmt", "--all", "--", "--check"]), + )?; + process::run(project.command("cargo")?.args([ + "clippy", + "--locked", + "--workspace", + "--all-targets", + "--all-features", + "--", + "-D", + "warnings", + ]))?; + process::run(project.command("cargo")?.args([ + "test", + "--locked", + "--workspace", + "--all-targets", + "--all-features", + ]))?; + process::run( + project + .command("cargo")? + .env("RUSTDOCFLAGS", "-D warnings") + .args([ + "doc", + "--locked", + "--workspace", + "--no-deps", + "--document-private-items", + ]), + )?; + process::run(project.command("cargo")?.args([ + "deny", + "--workspace", + "--all-features", + "check", + ]))?; + licenses(project) +} |
