Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 26 additions & 64 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
use tracing::{debug, info, warn};
use tracing::{debug, error, info, warn};

use crate::core::report::{CiteError, Style, styled};
use crate::core::report::CiteError;
use crate::core::{compiler, deploy, doctor, project, scaffold, uninstall, upgrade};
use colored::Colorize;

#[derive(Parser)]
#[command(
Expand Down Expand Up @@ -119,34 +120,21 @@ fn load_projects(
}

fn print_project_header(name: &str) {
eprintln!("{}", styled(format!("── {name} ──"), Style::Header));
info!("── {name} ──");
}

impl CliCommand {
pub async fn execute(self) -> Result<(), CiteError> {
match self {
CliCommand::Init { name, path } => {
let root = match path {
Some(p) => PathBuf::from(p),
Some(p) => PathBuf::from(p).join(&name),
None => resolve_path(None).join(&name),
};
let report = scaffold::init_project(&name, &root)?;

for d in &report.directories_created {
info!("Created directory: {d}");
}
for f in &report.files_created {
info!("Created file: {f}");
}
for f in &report.files_skipped {
info!("Skipped: {f}");
}
eprintln!(
scaffold::init_project(&name, &root)?;
println!(
"{}",
styled(
format!("Project '{name}' ready at {}", root.display()),
Style::Success,
)
format!("Project '{name}' ready at {}", root.display()).green()
);
Ok(())
}
Expand All @@ -162,16 +150,13 @@ impl CliCommand {
print_project_header(&ctx.manifest.project.name);
}
let outcome = doctor::lint_all(ctx);
outcome.print();
outcome.emit();
if outcome.has_warnings() {
overall_has_warnings = true;
}
}
if !overall_has_warnings {
eprintln!(
"{}",
styled("Lint complete — no issues found", Style::Success)
);
println!("{}", format!("Lint complete — no issues found").green());
}
Ok(())
}
Expand All @@ -187,9 +172,9 @@ impl CliCommand {
print_project_header(&ctx.manifest.project.name);
}
match compiler::compile(ctx, force).await {
Ok(report) => report.print(),
Ok(_) => {}
Err(e) => {
warn!("Build failed: {e}");
error!("Build failed: {e}");
has_errors = true;
}
}
Expand All @@ -199,6 +184,7 @@ impl CliCommand {
"Build failed in one or more projects".to_string(),
))
} else {
println!("{}", format!("Build complete").green());
Ok(())
}
}
Expand Down Expand Up @@ -226,6 +212,7 @@ impl CliCommand {
"Deploy failed in one or more projects".to_string(),
))
} else {
println!("{}", format!("Deploy complete").green());
Ok(())
}
}
Expand All @@ -238,38 +225,11 @@ impl CliCommand {
if multi {
print_project_header(&ctx.manifest.project.name);
} else {
eprintln!("{}", styled("Project Status", Style::Header));
}
eprintln!(" Name: {}", ctx.manifest.project.name);
eprintln!(" Project Root: {}", ctx.root.display());
eprintln!(" Artist ID: {}", ctx.manifest.project.artist_id);
if let Some(backend) = &ctx.manifest.backend
&& let Some(url) = &backend.staging_url
{
eprintln!(" Publishing to: {url}");
}
eprintln!(" Podcasts: {}", ctx.metadata.podcasts.len());
let build_path = ctx.build_dir().join("content.json");
if build_path.exists() {
eprintln!(" Build: {}", styled("exists", Style::Success));
if let Ok(meta) = std::fs::metadata(&build_path)
&& let Ok(modified) = meta.modified()
&& let Ok(elapsed) = modified.elapsed()
{
let secs = elapsed.as_secs();
let since = if secs < 60 {
"just now".to_string()
} else if secs < 3600 {
format!("{}m ago", secs / 60)
} else {
format!("{}h ago", secs / 3600)
};
eprintln!(" Built: {since}");
}
} else {
eprintln!(" Build: not built");
info!("Project Status");
}
project::print_status(ctx);
}
println!("{}", format!("Status complete").green());
Ok(())
}
CliCommand::Doctor { path } => {
Expand All @@ -288,7 +248,6 @@ impl CliCommand {
print_project_header(&ctx.manifest.project.name);
}
let outcome = doctor::run(ctx)?;
outcome.print();
if outcome.has_errors() {
overall_has_errors = true;
}
Expand All @@ -301,9 +260,9 @@ impl CliCommand {
"Doctor found validation errors".to_string(),
));
} else if !overall_has_warnings {
eprintln!(
println!(
"{}",
styled("Doctor check complete — no issues found", Style::Success)
format!("Doctor check complete — no issues found").green()
);
}
Ok(())
Expand All @@ -318,15 +277,15 @@ impl CliCommand {
print_project_header(&ctx.manifest.project.name);
}
ctx.clean()?;
eprintln!("{}", styled("Cleaned build artifacts", Style::Success));
println!("{}", format!("Cleaned build artifacts").green());
}
Ok(())
}
CliCommand::Rollback { id, path } => {
let root = resolve_path(path);
let ctx = load_project(&root)?;
let msg = deploy::rollback(&ctx, &id).await?;
eprintln!("{msg}");
info!("{msg}");
Ok(())
}
CliCommand::Login {
Expand All @@ -336,11 +295,14 @@ impl CliCommand {
} => {
let root = resolve_path(path);
let ctx = load_project(&root)?;
deploy::login(&ctx, email, password).await
deploy::login(&ctx, email, password).await?;
println!("{}", format!("Login complete").green());
Ok(())
}
CliCommand::Upgrade => {
let msg = upgrade::upgrade().await?;
eprintln!("{msg}");
info!("{msg}");
println!("{}", format!("Upgrade complete").green());
Ok(())
}
CliCommand::Uninstall { force } => uninstall::uninstall(force),
Expand Down
40 changes: 16 additions & 24 deletions src/core/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::path::PathBuf;

use crate::core::CiteError;
use crate::core::cache::{self, BuildCache};
use crate::core::metadata::{Podcast, TimelineEntry};
use crate::core::project::ProjectContext;
use crate::core::{CiteError, Style, styled};
use serde::Serialize;
use tracing::instrument;
use tracing::{info, instrument};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -47,28 +47,16 @@ pub enum CompileOutcome {
}

impl CompileOutcome {
pub fn print(&self) {
for line in self.to_lines() {
eprintln!("{line}");
}
}

pub fn to_lines(&self) -> Vec<String> {
pub fn emit(&self) {
match self {
CompileOutcome::UpToDate => {
vec![styled(
"Nothing to rebuild — all files up to date",
Style::Success,
)]
info!("Nothing to rebuild — all files up to date");
}
CompileOutcome::Complete { podcasts, artifact } => {
info!("Built {} podcast items", podcasts);
info!("Build artifact at {}", artifact.display());
info!("Build complete");
}
CompileOutcome::Complete { podcasts, artifact } => vec![
styled(format!("Built {} podcast items", podcasts), Style::Info),
styled(
format!("Build artifact at {}", artifact.display()),
Style::Info,
),
styled("Build completed successfully", Style::Success),
],
}
}
}
Expand All @@ -85,7 +73,9 @@ pub async fn compile(ctx: &ProjectContext, force: bool) -> Result<CompileOutcome
if cache.compiler_version == ctx.manifest.build.compiler_version {
let changed_hashes = cache.changed_since(&current_hashes);
if changed_hashes.is_empty() {
return Ok(CompileOutcome::UpToDate);
let result = CompileOutcome::UpToDate;
result.emit();
return Ok(result);
}
}
}
Expand All @@ -100,10 +90,12 @@ pub async fn compile(ctx: &ProjectContext, force: bool) -> Result<CompileOutcome
let cache = BuildCache::new(ctx.manifest.build.compiler_version, current_hashes);
cache.save(&cache_path).await?;

Ok(CompileOutcome::Complete {
let result = CompileOutcome::Complete {
podcasts: ctx.metadata.podcasts.len(),
artifact: build_dir.join("content.json"),
})
};
result.emit();
Ok(result)
}

async fn build_bundle(ctx: &ProjectContext) -> Result<ContentBundle, CiteError> {
Expand Down
36 changes: 14 additions & 22 deletions src/core/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::io::Write;
use std::path::PathBuf;

use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
Expand All @@ -7,9 +8,9 @@ use serde_json::Value;
use tracing::{info, instrument, warn};
use uuid::Uuid;

use crate::core::CiteError;
use crate::core::manifest::BackendConfig;
use crate::core::project::ProjectContext;
use crate::core::{CiteError, Style, styled};

const STORAGE_BUCKET: &str = "assets";

Expand Down Expand Up @@ -185,11 +186,8 @@ pub async fn deploy(ctx: &ProjectContext, dry_run: bool) -> Result<String, CiteE
let timeline_count = record.timeline_ids.len();
let asset_count = record.asset_paths.len();

Ok(styled(
format!(
"Deployed {podcast_count} podcast(s), {timeline_count} timeline(s), {asset_count} asset(s)"
),
Style::Success,
Ok(format!(
"Deployed {podcast_count} podcast(s), {timeline_count} timeline(s), {asset_count} asset(s)"
))
}

Expand Down Expand Up @@ -420,7 +418,7 @@ pub async fn rollback(ctx: &ProjectContext, deployment_id: &str) -> Result<Strin
warn!("Failed to remove local deployment record: {e}");
}

Ok(styled("Rollback complete", Style::Success))
Ok(format!("Rollback complete"))
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -493,7 +491,8 @@ pub async fn login(
let email = match email {
Some(e) => e,
None => {
eprint!("Email: ");
print!("Email: ");
let _ = std::io::stdout().flush();
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
s.trim().to_string()
Expand All @@ -502,7 +501,8 @@ pub async fn login(
let password = match password {
Some(p) => p,
None => {
eprint!("Password: ");
print!("Password: ");
let _ = std::io::stdout().flush();
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
s.trim().to_string()
Expand Down Expand Up @@ -545,23 +545,14 @@ pub async fn login(
email: email.clone(),
};
save_session(&session)?;
eprintln!(
"{}",
styled(format!("Logged in as {email}"), Style::Success)
);
info!("Logged in as {}", email);

match fetch_user_artists(backend, &token.access_token).await {
Ok(artists) if artists.is_empty() => {
eprintln!(
"{}",
styled("No artist linked to this account.", Style::Warning)
);
warn!("No artist linked to this account");
match prompt_create_artist(backend, &token.access_token).await? {
Some((id, name)) => {
eprintln!(
"{}",
styled(format!("Created artist '{name}' ({id})"), Style::Success)
);
info!("Created artist '{name}' ({id})");
}
None => {
info!("Skipped artist creation");
Expand Down Expand Up @@ -619,7 +610,8 @@ async fn fetch_user_artists(
}

fn prompt_line(label: &str) -> Result<String, CiteError> {
eprint!("{label}");
print!("{label}");
let _ = std::io::stdout().flush();
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
Ok(s.trim().to_string())
Expand Down
Loading
Loading