use std::io::BufRead as _;
use tokf::auth::credentials;
use tokf::config;
use tokf::publish_shared::{collect_test_files_resolved, hash_filter, inline_lua_script};
use tokf::remote::http::Client;
use tokf::remote::publish_client;
pub fn cmd_publish(filter_name: &str, dry_run: bool, update_tests: bool) -> i32 {
let result = if update_tests {
publish_update_tests(filter_name, dry_run)
} else {
publish(filter_name, dry_run)
};
match result {
Ok(code) => code,
Err(e) => {
eprintln!("[tokf] error: {e:#}");
1
}
}
}
fn resolve_local_filter(filter_name: &str) -> anyhow::Result<config::ResolvedFilter> {
let search_dirs = config::default_search_dirs();
let resolved = config::discover_all_filters(&search_dirs)?;
let resolved_filter = resolved
.into_iter()
.find(|f| f.matches_name(filter_name))
.ok_or_else(|| anyhow::anyhow!("filter not found: {filter_name}"))?;
if resolved_filter.priority == tokf::config::STDLIB_PRIORITY {
anyhow::bail!(
"'{filter_name}' is a built-in stdlib filter — \
eject it first with `tokf eject {filter_name}`"
);
}
Ok(resolved_filter)
}
fn publish(filter_name: &str, dry_run: bool) -> anyhow::Result<i32> {
let filter_name = filter_name.strip_suffix(".toml").unwrap_or(filter_name);
let resolved_filter = resolve_local_filter(filter_name)?;
let filter_bytes = std::fs::read(&resolved_filter.source_path)?;
let filter_bytes = inline_lua_script(filter_bytes, &resolved_filter.source_path)?;
let (content_hash, command_pattern) = hash_filter(&filter_bytes)?;
let test_files = collect_test_files_resolved(&resolved_filter.source_path)?;
eprintln!("[tokf] publishing filter: {filter_name}");
eprintln!(" Command: {command_pattern}");
eprintln!(" Hash: {content_hash}");
eprintln!(" Tests: {} file(s)", test_files.len());
if dry_run {
eprintln!("[tokf] dry-run: no files uploaded");
return Ok(0);
}
ensure_license_accepted()?;
let client = Client::authed()?;
let (is_new, resp) = tokf::remote::retry::with_retry("publish", || {
publish_client::publish_filter(&client, &filter_bytes, &test_files)
})?;
if is_new {
eprintln!("[tokf] published {filter_name} (201 Created)");
} else {
eprintln!("[tokf] already exists (200 OK)");
}
eprintln!("Hash: {}", resp.content_hash);
eprintln!("Author: {}", resp.author);
eprintln!("URL: {}", resp.registry_url);
Ok(0)
}
fn publish_update_tests(filter_name: &str, dry_run: bool) -> anyhow::Result<i32> {
let filter_name = filter_name.strip_suffix(".toml").unwrap_or(filter_name);
let resolved_filter = resolve_local_filter(filter_name)?;
let filter_bytes = std::fs::read(&resolved_filter.source_path)?;
let (content_hash, _) = hash_filter(&filter_bytes)?;
let test_files = collect_test_files_resolved(&resolved_filter.source_path)?;
if test_files.is_empty() {
anyhow::bail!("no test files found for {filter_name}");
}
for (filename, bytes) in &test_files {
tokf_common::test_case::validate(bytes).map_err(|e| anyhow::anyhow!("{filename}: {e}"))?;
}
eprintln!("[tokf] updating test suite for: {filter_name}");
eprintln!(" Hash: {content_hash}");
eprintln!(" Tests: {} file(s)", test_files.len());
if dry_run {
for (name, _) in &test_files {
eprintln!(" - {name}");
}
eprintln!("[tokf] dry-run: no files uploaded");
return Ok(0);
}
let client = Client::authed()?;
let resp = tokf::remote::retry::with_retry("update-tests", || {
publish_client::update_tests(&client, &content_hash, &test_files)
})?;
eprintln!(
"[tokf] updated test suite for {filter_name} ({} file(s))",
resp.test_count
);
eprintln!("URL: {}", resp.registry_url);
Ok(0)
}
fn ensure_license_accepted() -> anyhow::Result<()> {
let accepted = credentials::load()
.and_then(|a| a.mit_license_accepted)
.unwrap_or(false);
if accepted {
return Ok(());
}
eprintln!("[tokf] This filter will be published under the MIT license.");
eprintln!("[tokf] Anyone may use, modify, and distribute it with attribution.");
eprint!("[tokf] Accept MIT license? [y/N]: ");
let mut input = String::new();
std::io::stdin()
.lock()
.read_line(&mut input)
.map_err(|e| anyhow::anyhow!("could not read input: {e}"))?;
if input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes") {
credentials::save_license_accepted(true)?;
eprintln!("[tokf] MIT license accepted.");
Ok(())
} else {
anyhow::bail!("MIT license not accepted — publish cancelled")
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn resolve_fails_for_unknown_filter() {
let search_dirs = config::default_search_dirs();
let resolved = config::discover_all_filters(&search_dirs).unwrap();
let found = resolved
.iter()
.find(|f| f.matches_name("nonexistent/xyz-abc-filter-99"));
assert!(found.is_none(), "expected no match for nonexistent filter");
}
}