// now: Nix-based distributed command runner // Copyright (C) 2026 Eric Rodrigues Pires // // 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 . use std::{ ffi::{OsStr, OsString}, os::unix::ffi::OsStrExt, path::PathBuf, }; use clap::{CommandFactory, Parser}; use color_eyre::eyre::eyre; use crate::{command::Command, eyre::install_tracing_hook, utils::parse_bool_from_bytes}; fn force_ansi_colors_on_ci_runners(env_vars: &ahash::HashMap) { for key in [ OsStr::from_bytes(b"FORGEJO_ACTIONS"), OsStr::from_bytes(b"GITEA_ACTIONS"), OsStr::from_bytes(b"GITHUB_ACTIONS"), OsStr::from_bytes(b"GITLAB_CI"), ] { if env_vars .get(key) .is_some_and(|value| parse_bool_from_bytes(value.as_encoded_bytes())) { // Safety: called before any threads are spawned. unsafe { std::env::set_var("FORCE_COLOR", "1") }; return; } } if env_vars .get(OsStr::from_bytes(b"CI")) .is_some_and(|value| parse_bool_from_bytes(value.as_encoded_bytes())) { if env_vars .get(OsStr::from_bytes(b"TANGLED_PIPELINE_ID")) .is_some_and(|value| !value.is_empty()) { // Safety: called before any threads are spawned. unsafe { std::env::set_var("FORCE_COLOR", "1") }; return; } } } static LONG_ABOUT: &str = "now - Nix-based distributed command runner. \x1b[1;4mExamples:\x1b[0m \x1b[2m# Initialize a basic workflow in ./now.nix\x1b[0m now init \x1b[2m# Load envvars from a dotenv file and run the default job(s)\x1b[0m now run --env-file .env \x1b[2m# Run the \"deploy\" job (and all dependencies) from the specified workflow,\x1b[0m \x1b[2m# and specify a remote builder for the run\x1b[0m now run deploy \\ --builders \"ssh://mac aarch64-darwin\" \\ --workflow .now/remote.nix \x1b[2m# Abort immediately on the first failing job,\x1b[0m \x1b[2m# and run all jobs on the local machine\x1b[0m now run --abort --local-only"; #[derive(Parser)] #[command(name = "now", version, about, long_about = LONG_ABOUT)] struct Args { #[arg(hide = true)] workflow: Option, #[command(subcommand)] command: Command, } pub(crate) fn setup() -> color_eyre::Result { clap_complete::CompleteEnv::with_factory(Args::command).complete(); let args = Args::parse(); let command = match args.command { Command::Init { workflow } => { if args.workflow.is_some() && workflow.is_some() { return Err(eyre!("Conflicting workflow options")); } Command::Init { workflow: args.workflow.or(workflow), } } Command::List { workflow, flake, env_file, cwdir, tree, } => { if args.workflow.is_some() && (workflow.is_some() || flake.is_some()) { return Err(eyre!("Conflicting workflow options")); } Command::List { workflow: args.workflow.or(workflow), flake, env_file, cwdir, tree, } } Command::Eval { workflow, flake, env_file, cwdir, } => { if args.workflow.is_some() && (workflow.is_some() || flake.is_some()) { return Err(eyre!("Conflicting workflow options")); } Command::Eval { workflow: args.workflow.or(workflow), flake, env_file, cwdir, } } Command::Run { jobs, workflow, flake, dry_run, all_jobs, env_file, gcroot_dir, abort, termination_timeout, timeout, keep_alive, cwdir, builders, cores, local_only, remote_only, skip, logging, tracing, } => { if args.workflow.is_some() && (workflow.is_some() || flake.is_some()) { return Err(eyre!("Conflicting workflow options")); } if tracing.is_some() { install_tracing_hook()?; } else { color_eyre::install()?; } Command::Run { jobs, workflow: args.workflow.or(workflow), flake, dry_run, all_jobs, env_file, gcroot_dir, abort, termination_timeout, timeout, keep_alive, cwdir, builders, cores, local_only, remote_only, skip, logging, tracing, } } }; let env_vars: ahash::HashMap<_, _> = std::env::vars_os().collect(); force_ansi_colors_on_ci_runners(&env_vars); Ok(command) }