From 334da1fe3ebf3417507e192ab889733ea643cb46 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Fri, 10 Jun 2022 00:15:21 -0700 Subject: [PATCH] new: Rework terminal color handling. (#135) * Remove env vars. * Remove color from snapshots. * Copy stuff. * Update handler. * Add option and update docs. * Add config setting. * Update snapshots. * Update json schema. * Fix defaults. * Update snapshots. * Polish. * Update windows tests. * Update console. * Testing. * Ok lets try again. * More fixes. * Remove test. * Add back logging. * More debugging. * And again. * Remove logs. --- .github/workflows/moon.yml | 4 +- Makefile.toml | 2 +- crates/cli/src/app.rs | 3 + crates/cli/src/commands/project.rs | 4 +- crates/cli/src/helpers.rs | 178 ++++++++++++++++++ crates/cli/src/lib.rs | 4 + ...t__node__infers_globs_from_workspaces.snap | 40 ++-- ...infers_globs_from_workspaces_expanded.snap | 40 ++-- ...node__infers_projects_from_workspaces.snap | 42 +++-- ...ers_projects_from_workspaces_expanded.snap | 42 +++-- .../project_test__advanced_config.snap | 4 +- .../snapshots/project_test__basic_config.snap | 6 +- .../project_test__depends_on_paths.snap | 6 +- .../snapshots/project_test__empty_config.snap | 4 +- .../snapshots/project_test__no_config.snap | 4 +- .../project_test__unknown_project.snap | 2 +- .../snapshots/project_test__with_tasks.snap | 6 +- ...bles_up_invalid_global_project_config.snap | 2 +- ...gs__bubbles_up_invalid_project_config.snap | 2 +- ...__bubbles_up_invalid_workspace_config.snap | 2 +- ...n_test__errors_for_cycle_in_task_deps.snap | 4 +- .../run_test__errors_for_unknown_project.snap | 4 +- ...t__errors_for_unknown_task_in_project.snap | 4 +- ...de__handles_process_exit_code_nonzero.snap | 4 +- ...t__node__handles_process_exit_nonzero.snap | 4 +- ...test__node__handles_unhandled_promise.snap | 4 +- ...ion_manager__errors_for_invalid_value.snap | 4 +- ..._system__handles_process_exit_nonzero.snap | 4 +- ...system__retries_on_failure_till_count.snap | 4 +- ...windows__handles_process_exit_nonzero.snap | 3 +- ...indows__retries_on_failure_till_count.snap | 3 +- ..._target_scopes__errors_for_deps_scope.snap | 4 +- ..._target_scopes__errors_for_self_scope.snap | 4 +- crates/config/src/validators.rs | 4 + crates/config/src/workspace/mod.rs | 25 ++- crates/config/src/workspace/node.rs | 6 +- crates/config/templates/workspace.yml | 44 +++-- crates/logger/src/color.rs | 27 +-- crates/logger/src/logger.rs | 9 +- crates/terminal/src/terminal.rs | 6 - crates/utils/src/lib.rs | 4 + crates/utils/src/process.rs | 64 ++++--- crates/utils/src/time.rs | 4 +- crates/workspace/src/actions/run_target.rs | 13 +- packages/cli/CHANGELOG.md | 13 ++ website/docs/commands/overview.mdx | 48 ++++- website/docs/config/workspace.mdx | 19 ++ website/static/schemas/workspace.json | 19 ++ 48 files changed, 545 insertions(+), 207 deletions(-) create mode 100644 crates/cli/src/helpers.rs diff --git a/.github/workflows/moon.yml b/.github/workflows/moon.yml index de42e84ed46..20d03c5b2ed 100644 --- a/.github/workflows/moon.yml +++ b/.github/workflows/moon.yml @@ -56,6 +56,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: run - args: -- --log trace ci - env: - CLICOLOR_FORCE: true + args: -- --color --log trace ci diff --git a/Makefile.toml b/Makefile.toml index bf5fc19bc81..0fd1c57732b 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -66,7 +66,7 @@ script = "rm -rf *.profraw crates/**/*.profraw tests/fixtures/**/*.profraw" ## OTHER -[tasks.gen-json-schemas] +[tasks.json-schemas] command = "cargo" args = ["run", "-p", "moon_config"] diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index 8ad2ebc875c..561b5d5d025 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -196,6 +196,9 @@ pub struct App { )] pub cache: CacheMode, + #[clap(long, env = "MOON_COLOR", help = "Force colored output for moon")] + pub color: bool, + #[clap( arg_enum, long, diff --git a/crates/cli/src/commands/project.rs b/crates/cli/src/commands/project.rs index d704fdc3e59..32fa65ef6d3 100644 --- a/crates/cli/src/commands/project.rs +++ b/crates/cli/src/commands/project.rs @@ -2,8 +2,8 @@ use console::Term; use itertools::Itertools; use moon_logger::color; use moon_terminal::{ExtendedTerm, Label}; +use moon_utils::is_test_env; use moon_workspace::Workspace; -use std::env; pub async fn project(id: &str, json: bool) -> Result<(), Box> { let workspace = Workspace::load().await?; @@ -23,7 +23,7 @@ pub async fn project(id: &str, json: bool) -> Result<(), Box bool { + true +} + // Extend validator lib pub trait VecValidate { fn validate(&self) -> Result<(), ValidationErrors>; diff --git a/crates/config/src/workspace/mod.rs b/crates/config/src/workspace/mod.rs index 858fea1a029..700085ef022 100644 --- a/crates/config/src/workspace/mod.rs +++ b/crates/config/src/workspace/mod.rs @@ -7,7 +7,7 @@ mod vcs; use crate::constants; use crate::errors::map_figment_error_to_validation_errors; use crate::types::{FileGlob, FilePath}; -use crate::validators::{validate_child_relative_path, validate_id}; +use crate::validators::{default_bool_true, validate_child_relative_path, validate_id}; use figment::value::{Dict, Map}; use figment::{ providers::{Format, Serialized, Yaml}, @@ -49,9 +49,29 @@ fn validate_projects(projects: &ProjectsMap) -> Result<(), ValidationError> { Ok(()) } +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize, Validate)] +#[serde(rename_all = "camelCase")] +pub struct ActionRunnerConfig { + #[serde(default = "default_bool_true")] + pub inherit_colors_for_piped_tasks: bool, +} + +impl Default for ActionRunnerConfig { + fn default() -> Self { + ActionRunnerConfig { + inherit_colors_for_piped_tasks: true, + } + } +} + /// Docs: https://moonrepo.dev/docs/config/workspace #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize, Validate)] +#[serde(rename_all = "camelCase")] pub struct WorkspaceConfig { + #[serde(default)] + #[validate] + pub action_runner: ActionRunnerConfig, + #[serde(default)] #[validate] pub node: NodeConfig, @@ -229,6 +249,7 @@ mod tests { assert_eq!( config, WorkspaceConfig { + action_runner: ActionRunnerConfig::default(), node: NodeConfig::default(), projects: HashMap::new(), typescript: TypeScriptConfig::default(), @@ -260,6 +281,7 @@ node: assert_eq!( config, WorkspaceConfig { + action_runner: ActionRunnerConfig::default(), node: NodeConfig { package_manager: PackageManager::Yarn, ..NodeConfig::default() @@ -756,6 +778,7 @@ vcs: assert_eq!( config, WorkspaceConfig { + action_runner: ActionRunnerConfig::default(), node: NodeConfig::default(), projects: HashMap::new(), typescript: TypeScriptConfig::default(), diff --git a/crates/config/src/workspace/node.rs b/crates/config/src/workspace/node.rs index fa48d18a332..71812d4bcb1 100644 --- a/crates/config/src/workspace/node.rs +++ b/crates/config/src/workspace/node.rs @@ -1,4 +1,4 @@ -use crate::validators::validate_semver_version; +use crate::validators::{default_bool_true, validate_semver_version}; use moon_lang_node::{NODE, NODENV, NVMRC, PNPM, YARN}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -22,10 +22,6 @@ pub fn default_yarn_version() -> String { env::var("MOON_YARN_VERSION").unwrap_or_else(|_| YARN.default_version.to_string()) } -fn default_bool_true() -> bool { - true -} - fn validate_node_version(value: &str) -> Result<(), ValidationError> { validate_semver_version("node.version", value) } diff --git a/crates/config/templates/workspace.yml b/crates/config/templates/workspace.yml index 1c07fa02375..f65617f7687 100644 --- a/crates/config/templates/workspace.yml +++ b/crates/config/templates/workspace.yml @@ -1,9 +1,9 @@ $schema: 'https://moonrepo.dev/schemas/workspace.json' -# A map of all projects found within the workspace. Each entry requires a unique project ID -# as the map key, and a file system path to the project folder as the map value. File paths -# are relative from the workspace root, and cannot reference projects located outside the -# workspace boundary. +# REQUIRED: A map of all projects found within the workspace, or a list or file system globs. +# When using a map, each entry requires a unique project ID as the map key, and a file system +# path to the project folder as the map value. File paths are relative from the workspace root, +# and cannot reference projects located outside the workspace boundary. projects: {%- for glob in project_globs | sort %} - '{{ glob }}' @@ -12,7 +12,7 @@ projects: {{ id }}: '{{ source }}' {%- endfor %} -# OPTIONAL: Configures Node.js within the toolchain. moon manages its own version of Node.js +# Configures Node.js within the toolchain. moon manages its own version of Node.js # instead of relying on a version found on the host machine. This ensures deterministic # and reproducible builds across any machine. node: @@ -20,55 +20,61 @@ node: # We suggest using the latest active LTS version: https://nodejs.org/en/about/releases version: '{{ node_version }}' - # OPTIONAL: The package manager to use when managing dependencies. + # The package manager to use when managing dependencies. # Accepts "npm", "pnpm", or "yarn". Defaults to "npm". packageManager: '{{ package_manager }}' {% if package_manager != "npm" -%} - # OPTIONAL: The version of the package manager (above) to use. + # The version of the package manager (above) to use. {{ package_manager }}: version: '{{ package_manager_version }}' {%- elif package_manager == "npm" and package_manager_version != "inherit" -%} - # OPTIONAL: The version of the package manager (above) to use. + # The version of the package manager (above) to use. npm: version: '{{ package_manager_version }}' {%- endif %} - # OPTIONAL: Add `node.version` as a constaint in the root `package.json` `engines`. + # Add `node.version` as a constaint in the root `package.json` `engines`. addEnginesConstraint: true - # OPTIONAL: Dedupe dependencies after the lockfile has changed. + # Dedupe dependencies after the lockfile has changed. dedupeOnLockfileChange: true - # OPTIONAL: Sync a project's `dependsOn` as normal dependencies within the project's + # Sync a project's `dependsOn` as normal dependencies within the project's # `package.json`. Will use "workspace:*" ranges when available in the package manager. syncProjectWorkspaceDependencies: true - # OPTIONAL: Sync `node.version` to a 3rd-party version manager's config file. + # Sync `node.version` to a 3rd-party version manager's config file. # Accepts "nodenv" (.node-version), "nvm" (.nvmrc), or none. # syncVersionManagerConfig: 'nvm' -# OPTIONAL: Configures how moon integrates with TypeScript. +# Configures how moon integrates with TypeScript. typescript: - # OPTIONAL: Name of `tsconfig.json` file in project root. + # Name of `tsconfig.json` file in project root. projectConfigFileName: 'tsconfig.json' - # OPTIONAL: Name of `tsconfig.json` file in workspace root. + # Name of `tsconfig.json` file in workspace root. rootConfigFileName: 'tsconfig.json' - # OPTIONAL: Sync a project's `dependsOn` as TypeScript project references within the + # Sync a project's `dependsOn` as TypeScript project references within the # project's `tsconfig.json` and the workspace root `tsconfig.json`. syncProjectReferences: true -# OPTIONAL: Configures the version control system to utilize within the workspace. A VCS +# Configures the version control system to utilize within the workspace. A VCS # is required for determining touched (added, modified, etc) files, calculating file hashes, # computing affected files, and much more. vcs: - # OPTIONAL: The manager/binary to use when managing the repository. + # The manager/binary to use when managing the repository. # Accepts "git", or "svn". Defaults to "git". manager: 'git' - # OPTIONAL: The default branch (master/main/trunk) in the repository for comparing the + # The default branch (master/main/trunk) in the repository for comparing the # local branch against. For git, this is is typically "master" or "main", # and must include the remote prefix (before /). For svn, this should always be "trunk". defaultBranch: 'master' + +# Configures aspects of the action runner. +actionRunner: + # Force colors to be inherited for all tasks that are ran as a child process + # and their output is piped to the action runner. + inheritColorsForPipedTasks: true diff --git a/crates/logger/src/color.rs b/crates/logger/src/color.rs index 176d11dbee4..c2f334240ca 100644 --- a/crates/logger/src/color.rs +++ b/crates/logger/src/color.rs @@ -2,7 +2,7 @@ // https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg pub use console::style; -use console::{colors_enabled, pad_str, Alignment}; +use console::{pad_str, Alignment}; use dirs::home_dir as get_home_dir; use log::Level; use std::env; @@ -25,10 +25,6 @@ pub enum Color { } pub fn paint(color: u8, value: &str) -> String { - if no_color() || supports_color() < 2 { - return value.to_owned(); - } - style(value).color256(color).to_string() } @@ -85,10 +81,6 @@ pub fn target(value: &str) -> String { // Based on https://github.com/debug-js/debug/blob/master/src/common.js#L41 pub fn log_target(value: &str) -> String { - if no_color() { - return value.to_owned(); - } - let mut hash: u32 = 0; for b in value.bytes() { @@ -124,18 +116,13 @@ pub fn no_color() -> bool { env::var("NO_COLOR").is_ok() } -// 0 = no // 1 = 8 // 2 = 256 // 3 = 16m pub fn supports_color() -> u8 { - if no_color() { - return 0; - } - if let Ok(var) = env::var("TERM") { if var == "dumb" { - return 0; + return 1; } else if var.contains("truecolor") { return 3; } else if var.contains("256") { @@ -151,15 +138,7 @@ pub fn supports_color() -> u8 { } } - if colors_enabled() { - return 2; - } - - if env::var("CI").is_ok() { - return 2; - } - - 0 + 2 } pub const COLOR_LIST: [u8; 76] = [ diff --git a/crates/logger/src/logger.rs b/crates/logger/src/logger.rs index 0f1e545590f..72c3594fbb4 100644 --- a/crates/logger/src/logger.rs +++ b/crates/logger/src/logger.rs @@ -3,6 +3,7 @@ use chrono::prelude::*; use chrono::Local; use fern::Dispatch; use log::LevelFilter; +use std::env; use std::io; static mut FIRST_LOG: bool = true; @@ -37,11 +38,17 @@ impl Logger { } } + let formatted_timestamp = if env::var("MOON_TEST").is_ok() { + String::from("YYYY-MM-DD") // Snapshots + } else { + current_timestamp.format(date_format).to_string() + }; + let prefix = format!( "{}{} {}{}", color::muted("["), color::log_level(record.level()), - color::muted(¤t_timestamp.format(date_format).to_string()), + color::muted(&formatted_timestamp), color::muted("]"), ); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8e51a51574d..f5aa3cc9bee 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -3,7 +3,6 @@ use console::{measure_text_width, style, Attribute, Style, Term}; use core::fmt::Debug; use moon_logger::color; use moon_logger::color::Color; -use std::env; use std::io; pub enum Label { @@ -40,11 +39,6 @@ impl ExtendedTerm for Term { .attr(Attribute::Bold) .color256(Color::Black as u8); - // Dont show styles in tests unless we force it - if env::var("MOON_TEST").is_ok() { - style = style.force_styling(true); - } - match kind { Label::Brand => { style = style.on_color256(Color::Purple as u8); diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 18dee9600c3..7dbc9eded03 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -40,3 +40,7 @@ pub fn is_offline() -> bool { offline } + +pub fn is_test_env() -> bool { + env::var("MOON_TEST").is_ok() +} diff --git a/crates/utils/src/process.rs b/crates/utils/src/process.rs index 6c48fbdf82c..ec91c458d8b 100644 --- a/crates/utils/src/process.rs +++ b/crates/utils/src/process.rs @@ -4,10 +4,9 @@ use moon_logger::{color, logging_enabled, trace}; use std::env; use std::ffi::OsStr; use std::path::Path; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command as TokioCommand; -use tokio::sync::RwLock; use tokio::task; pub use std::process::{ExitStatus, Output, Stdio}; @@ -181,10 +180,6 @@ impl Command { .cmd .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .envs(env::vars()) - // Inherit ANSI colors since they're stripped from pipes - .env("FORCE_COLOR", env::var("FORCE_COLOR").unwrap_or_default()) - .env("TERM", env::var("TERM").unwrap_or_default()) .spawn() .map_err(|e| map_io_to_process_error(e, &self.bin))?; @@ -194,33 +189,41 @@ impl Command { // this *real ugly* implementation to solve it. There's gotta be a // better way to do this? // https://stackoverflow.com/a/49063262 - let err = BufReader::new(child.stderr.take().unwrap()); - let out = BufReader::new(child.stdout.take().unwrap()); - - // Spawn additional threads for logging the buffer - let stderr = Arc::new(RwLock::new(vec![])); - let stdout = Arc::new(RwLock::new(vec![])); - let stderr_clone = Arc::clone(&stderr); - let stdout_clone = Arc::clone(&stdout); + let stderr = BufReader::new(child.stderr.take().unwrap()); + let stdout = BufReader::new(child.stdout.take().unwrap()); + let captured_stderr = Arc::new(RwLock::new(vec![])); + let captured_stdout = Arc::new(RwLock::new(vec![])); + let captured_stderr_clone = Arc::clone(&captured_stderr); + let captured_stdout_clone = Arc::clone(&captured_stdout); task::spawn(async move { - let mut lines = err.lines(); - let mut stderr_write = stderr_clone.write().await; + let mut lines = stderr.lines(); + let mut captured_lines = vec![]; while let Some(line) = lines.next_line().await.unwrap() { eprintln!("{}", line); - stderr_write.push(line); + captured_lines.push(line); } + + captured_stderr_clone + .write() + .unwrap() + .extend(captured_lines); }); task::spawn(async move { - let mut lines = out.lines(); - let mut stdout_write = stdout_clone.write().await; + let mut lines = stdout.lines(); + let mut captured_lines = vec![]; while let Some(line) = lines.next_line().await.unwrap() { println!("{}", line); - stdout_write.push(line); + captured_lines.push(line); } + + captured_stdout_clone + .write() + .unwrap() + .extend(captured_lines); }); // Attempt to capture the child output @@ -230,11 +233,11 @@ impl Command { .map_err(|e| map_io_to_process_error(e, &self.bin))?; if output.stderr.is_empty() { - output.stderr = stderr.read().await.join("").into_bytes(); + output.stderr = captured_stderr.read().unwrap().join("\n").into_bytes(); } if output.stdout.is_empty() { - output.stdout = stdout.read().await.join("").into_bytes(); + output.stdout = captured_stdout.read().unwrap().join("\n").into_bytes(); } self.handle_nonzero_status(&output)?; @@ -242,6 +245,23 @@ impl Command { Ok(output) } + pub fn inherit_colors(&mut self) -> &mut Command { + if let Ok(level) = env::var("FORCE_COLOR") { + self.env("FORCE_COLOR", &level); + self.env("CLICOLOR_FORCE", &level); + } else if env::var("NO_COLOR").is_ok() { + self.env("NO_COLOR", "1"); + } + + // Force a terminal width so that we have consistent sizing + // in our cached output, and its the same across all machines + // https://help.gnome.org/users/gnome-terminal/stable/app-terminal-sizes.html.en + self.env("COLUMNS", "80"); + self.env("LINES", "24"); + + self + } + pub fn no_error_on_failure(&mut self) -> &mut Command { self.error = false; self diff --git a/crates/utils/src/time.rs b/crates/utils/src/time.rs index f92a3b6df34..fc5c40851f8 100644 --- a/crates/utils/src/time.rs +++ b/crates/utils/src/time.rs @@ -1,12 +1,12 @@ +use crate::is_test_env; use chrono::Duration; use chrono_humanize::HumanTime; -use std::env; use std::time::Duration as StdDuration; pub use chrono; pub fn elapsed(duration: StdDuration) -> String { - if env::var("MOON_TEST").is_ok() { + if is_test_env() { return String::from("100ms"); // Snapshots } diff --git a/crates/workspace/src/actions/run_target.rs b/crates/workspace/src/actions/run_target.rs index bdfe1cf245a..ea86d25a546 100644 --- a/crates/workspace/src/actions/run_target.rs +++ b/crates/workspace/src/actions/run_target.rs @@ -9,9 +9,8 @@ use moon_project::{Project, Target, Task}; use moon_terminal::output::{label_checkpoint, Checkpoint}; use moon_toolchain::{get_path_env_var, Executable}; use moon_utils::process::{output_to_string, Command, Output}; -use moon_utils::{is_ci, path, string_vec, time}; +use moon_utils::{is_ci, is_test_env, path, string_vec, time}; use std::collections::HashMap; -use std::env; use std::path::Path; use std::sync::Arc; use tokio::sync::RwLock; @@ -282,13 +281,21 @@ pub async fn run_target( let mut command = create_target_command(&workspace, &project, task).await?; command.args(passthrough_args); + if workspace + .config + .action_runner + .inherit_colors_for_piped_tasks + { + command.inherit_colors(); + } + // Run the command as a child process and capture its output. // If the process fails and `retry_count` is greater than 0, // attempt the process again in case it passes. let attempt_total = task.options.retry_count + 1; let mut attempt_index = 1; let mut attempts = vec![]; - let stream_output = is_primary || is_ci() && env::var("MOON_TEST").is_err(); + let stream_output = is_primary || is_ci() && !is_test_env(); let output; loop { diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a9f9db7c80b..d8aad2489fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## Unreleased + +#### 🚀 Updates + +- Added support for a list of globs when configuring `projects` in `.moon/workspace.yml`. +- Added an `actionRunner.inheritColorsForPipedTasks` setting to `.moon/workspace.yml` for inheriting + terminal colors for piped tasks. +- Added a global `--color` option to the CLI. Also supports a new `MOON_COLOR` environment variable. + +#### 🐞 Fixes + +- Fixed many issues around terminal color output and handling. + ## 0.2.0 #### 🚀 Updates diff --git a/website/docs/commands/overview.mdx b/website/docs/commands/overview.mdx index 952ef97b914..27cae96fcb0 100644 --- a/website/docs/commands/overview.mdx +++ b/website/docs/commands/overview.mdx @@ -5,6 +5,7 @@ title: Overview The following options are available for _all_ moon commands. - `--cache ` - The mode for [cache operations](#caching). +- `--color` - Force [colored output](#colors) for moon (not tasks). - `--help` - Display the help menu for the current command. - `--log ` - The lowest [log level to output](#logging). - `--version` - Display the version of the CLI. @@ -31,6 +32,48 @@ $ moon --cache off run app:build $ MOON_CACHE=off moon run app:build ``` +## Colors + +Colored output is a complicated subject, with differing implementations and standards across tooling +and operating systems. moon aims to normalize this as much as possible, by doing the following: + +- By default, moon colors are inherited from your terminal settings (`TERM` and `COLORTERM` + environment variables). +- Colors can be force enabled by passing the `--color` option (preferred), or `MOON_COLOR` or + `FORCE_COLOR` environment variables. + +```shell +$ moon --color run app:build +# Or +$ MOON_COLOR=2 moon run app:build +``` + +When forcing colors with `MOON_COLOR` or `FORCE_COLOR`, you may set it to one of the following +numerical values for the desired level of color support. This is automatically inferred if you use +`--color`. + +- `0` - No colors +- `1` - 16 colors (standard terminal colors) +- `2` - 256 colors +- `3` - 16 million colors (truecolor) + +### Piped output + +When tasks (child processes) are piped, colors and ANSI escape sequences are lost, since the target +is not a TTY and we do not implement a PTY. This is a common pattern this is quite annoying. +However, many tools and CLIs support a `--color` option to work around this limitation and to always +force colors, even when not a TTY. + +To mitigate this problem as a whole, and to avoid requiring `--color` for every task, moon supports +the [`actionRunner.inheritColorsForPipedTasks`](../config/workspace#inheritcolorsforpipedtasks) +configuration setting. When enabled, all piped child processes will inherit the color settings of +the currently running terminal. + +## Debugging + +At this point in time, moon provides no debugging utilities besides [logging](#logging) with the +`debug`/`trace` levels. + ## Logging By default, moon aims to output as little as possible, as we want to preserve the original output of @@ -50,8 +93,3 @@ $ moon --log trace run app:build # Or $ MOON_LOG=trace moon run app:build ``` - -## Debugging - -At this point in time, moon provides no debugging utilities besides [logging](#logging) with the -`debug`/`trace` levels. diff --git a/website/docs/config/workspace.mdx b/website/docs/config/workspace.mdx index 76bdd8fd789..96de90c985f 100644 --- a/website/docs/config/workspace.mdx +++ b/website/docs/config/workspace.mdx @@ -10,6 +10,25 @@ import RequiredLabel from '@site/src/components/Docs/RequiredLabel'; The `.moon/workspace.yml` file configures available projects and their locations, the toolchain, and the workspace development environment. +## `actionRunner` + +> `ActionRunnerConfig` + +Configures aspects of the action runner. + +### `inheritColorsForPipedTasks` + +> `boolean` + +Force colors to be inherited from the current terminal for all tasks that are ran as a child process +and their output is piped to the action runner. +[View more about color handling in moon](../commands/overview#colors). + +```yaml title=".moon/workspace.yml" {2} +actionRunner: + inheritColorsForPipedTasks: true # Default +``` + ## `projects` > `Record | string[]` diff --git a/website/static/schemas/workspace.json b/website/static/schemas/workspace.json index b01f714612b..ba86665fa40 100644 --- a/website/static/schemas/workspace.json +++ b/website/static/schemas/workspace.json @@ -4,6 +4,16 @@ "description": "Docs: https://moonrepo.dev/docs/config/workspace", "type": "object", "properties": { + "actionRunner": { + "default": { + "inheritColorsForPipedTasks": true + }, + "allOf": [ + { + "$ref": "#/definitions/ActionRunnerConfig" + } + ] + }, "node": { "default": { "addEnginesConstraint": true, @@ -67,6 +77,15 @@ } }, "definitions": { + "ActionRunnerConfig": { + "type": "object", + "properties": { + "inheritColorsForPipedTasks": { + "default": true, + "type": "boolean" + } + } + }, "NodeConfig": { "type": "object", "properties": {