Skip to content

feat: add noUpdateNotifier option to turbo.json #10409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 2, 2025
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
4 changes: 4 additions & 0 deletions crates/turborepo-lib/src/config/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const TURBO_MAPPING: &[(&str, &str)] = [
("turbo_cache", "cache"),
("turbo_tui_scrollback_length", "tui_scrollback_length"),
("turbo_concurrency", "concurrency"),
("turbo_no_update_notifier", "no_update_notifier"),
]
.as_slice();

Expand Down Expand Up @@ -166,6 +167,8 @@ impl ResolvedConfigurationOptions for EnvVars {

let allow_no_package_manager = self.truthy_value("allow_no_package_manager").flatten();

let no_update_notifier = self.truthy_value("no_update_notifier").flatten();

// Process daemon
let daemon = self.truthy_value("daemon").flatten();

Expand Down Expand Up @@ -231,6 +234,7 @@ impl ResolvedConfigurationOptions for EnvVars {
remote_cache_read_only,
run_summary,
allow_no_turbo_json,
no_update_notifier,

// Processed numbers
timeout,
Expand Down
5 changes: 5 additions & 0 deletions crates/turborepo-lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ pub struct ConfigurationOptions {
pub(crate) allow_no_turbo_json: Option<bool>,
pub(crate) tui_scrollback_length: Option<u64>,
pub(crate) concurrency: Option<String>,
pub(crate) no_update_notifier: Option<bool>,
}

#[derive(Default)]
Expand Down Expand Up @@ -463,6 +464,10 @@ impl ConfigurationOptions {
pub fn allow_no_turbo_json(&self) -> bool {
self.allow_no_turbo_json.unwrap_or_default()
}

pub fn no_update_notifier(&self) -> bool {
self.no_update_notifier.unwrap_or_default()
}
}

// Maps Some("") to None to emulate how Go handles empty strings
Expand Down
32 changes: 28 additions & 4 deletions crates/turborepo-lib/src/shim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ pub enum Error {
#[label = "Requires a path to be passed after it"]
flag_range: SourceSpan,
},
#[error("No value assigned to `--root-turbo-json` flag.")]
#[diagnostic(code(turbo::shim::empty_root_turbo_json))]
EmptyRootTurboJson {
#[backtrace]
backtrace: Backtrace,
#[source_code]
args_string: String,
#[label = "Requires a path to be passed after it"]
flag_range: SourceSpan,
},
#[error(transparent)]
#[diagnostic(transparent)]
Cli(#[from] cli::Error),
Expand Down Expand Up @@ -75,7 +85,12 @@ fn run_correct_turbo(
ui: ColorConfig,
) -> Result<i32, Error> {
if let Some(turbo_state) = LocalTurboState::infer(&repo_state.root) {
try_check_for_updates(&shim_args, turbo_state.version());
let mut builder = crate::config::TurborepoConfigBuilder::new(&repo_state.root);
if let Some(root_turbo_json) = &shim_args.root_turbo_json {
builder = builder.with_root_turbo_json_path(Some(root_turbo_json.clone()));
}
let config = builder.build().unwrap_or_default();
try_check_for_updates(&shim_args, turbo_state.version(), &config);

if turbo_state.local_is_self() {
env::set_var(
Expand All @@ -95,7 +110,12 @@ fn run_correct_turbo(
spawn_npx_turbo(&repo_state, local_config.turbo_version(), shim_args)
} else {
let version = get_version();
try_check_for_updates(&shim_args, version);
let mut builder = crate::config::TurborepoConfigBuilder::new(&repo_state.root);
if let Some(root_turbo_json) = &shim_args.root_turbo_json {
builder = builder.with_root_turbo_json_path(Some(root_turbo_json.clone()));
}
let config = builder.build().unwrap_or_default();
try_check_for_updates(&shim_args, version, &config);
// cli::run checks for this env var, rather than an arg, so that we can support
// calling old versions without passing unknown flags.
env::set_var(
Expand Down Expand Up @@ -251,8 +271,12 @@ fn is_turbo_binary_path_set() -> bool {
env::var("TURBO_BINARY_PATH").is_ok()
}

fn try_check_for_updates(args: &ShimArgs, current_version: &str) {
if args.should_check_for_update() {
fn try_check_for_updates(
args: &ShimArgs,
current_version: &str,
config: &crate::config::ConfigurationOptions,
) {
if args.should_check_for_update() && !config.no_update_notifier() {
// custom footer for update message
let footer = format!(
"Follow {username} for updates: {url}",
Expand Down
55 changes: 55 additions & 0 deletions crates/turborepo-lib/src/shim/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct ShimArgs {
pub forwarded_args: Vec<String>,
pub color: bool,
pub no_color: bool,
pub root_turbo_json: Option<AbsoluteSystemPathBuf>,
}

impl ShimArgs {
Expand All @@ -75,6 +76,8 @@ impl ShimArgs {
let mut is_forwarded_args = false;
let mut color = false;
let mut no_color = false;
let mut root_turbo_json_flag_idx = None;
let mut root_turbo_json = None;

let args = args.skip(1);
for (idx, arg) in args.enumerate() {
Expand Down Expand Up @@ -127,6 +130,18 @@ impl ShimArgs {
color = true;
} else if arg == "--no-color" {
no_color = true;
} else if root_turbo_json_flag_idx.is_some() {
// We've seen a `--root-turbo-json` and therefore add this as the path
root_turbo_json = Some(AbsoluteSystemPathBuf::from_unknown(&invocation_dir, arg));
root_turbo_json_flag_idx = None;
} else if arg == "--root-turbo-json" {
// If we see a `--root-turbo-json` we expect the next arg to be a path.
root_turbo_json_flag_idx = Some(idx);
} else if let Some(path) = arg.strip_prefix("--root-turbo-json=") {
// In the case where `--root-turbo-json` is passed as
// `--root-turbo-json=./path/to/turbo.json`, that entire chunk
// is a single arg, so we need to split it up.
root_turbo_json = Some(AbsoluteSystemPathBuf::from_unknown(&invocation_dir, path));
} else {
remaining_turbo_args.push(arg);
}
Expand All @@ -143,6 +158,17 @@ impl ShimArgs {
});
}

if let Some(idx) = root_turbo_json_flag_idx {
let (spans, args_string) =
Self::get_spans_in_args_string(vec![idx], env::args().skip(1));

return Err(Error::EmptyRootTurboJson {
backtrace: Backtrace::capture(),
args_string,
flag_range: spans[0],
});
}

if cwds.len() > 1 {
let (indices, args_string) = Self::get_spans_in_args_string(
cwds.iter().map(|(_, idx)| *idx).collect(),
Expand Down Expand Up @@ -175,6 +201,7 @@ impl ShimArgs {
forwarded_args,
color,
no_color,
root_turbo_json,
})
}

Expand Down Expand Up @@ -301,6 +328,7 @@ mod test {
pub color: bool,
pub no_color: bool,
pub relative_cwd: Option<&'static [&'static str]>,
pub relative_root_turbo_json: Option<&'static str>,
}

impl ExpectedArgs {
Expand All @@ -314,6 +342,7 @@ mod test {
color,
no_color,
relative_cwd,
relative_root_turbo_json,
} = self;
ShimArgs {
cwd: relative_cwd.map_or_else(
Expand All @@ -331,6 +360,8 @@ mod test {
force_update_check,
color,
no_color,
root_turbo_json: relative_root_turbo_json
.map(|path| AbsoluteSystemPathBuf::from_unknown(&invocation_dir, path)),
}
}
}
Expand Down Expand Up @@ -461,6 +492,30 @@ mod test {
}
; "cwd equals"
)]
#[test_case(
&["turbo", "--root-turbo-json", "path/to/turbo.json"],
ExpectedArgs {
relative_root_turbo_json: Some("path/to/turbo.json"),
..Default::default()
}
; "root turbo json value"
)]
#[test_case(
&["turbo", "--root-turbo-json=path/to/turbo.json"],
ExpectedArgs {
relative_root_turbo_json: Some("path/to/turbo.json"),
..Default::default()
}
; "root turbo json equals"
)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case for when the path is provided as an absolute path?

#[test_case(
&["turbo", "--root-turbo-json", "/absolute/path/to/turbo.json"],
ExpectedArgs {
relative_root_turbo_json: Some("/absolute/path/to/turbo.json"),
..Default::default()
}
; "root turbo json absolute path"
)]
fn test_shim_parsing(args: &[&str], expected: ExpectedArgs) {
let cwd = AbsoluteSystemPathBuf::new(if cfg!(windows) {
"Z:\\some\\dir"
Expand Down
3 changes: 3 additions & 0 deletions crates/turborepo-lib/src/turbo_json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ pub struct RawTurboJson {
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_dir: Option<Spanned<UnescapedString>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub no_update_notifier: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Spanned<Vec<Spanned<String>>>>,

Expand Down
2 changes: 1 addition & 1 deletion crates/turborepo-updater/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_millis(800);
// 1 day
const DEFAULT_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24);

const NOTIFIER_DISABLE_VARS: [&str; 2] = ["NO_UPDATE_NOTIFIER", "TURBO_NO_UPDATE_NOTIFIER"];
const NOTIFIER_DISABLE_VARS: [&str; 1] = ["NO_UPDATE_NOTIFIER"];
const ENVIRONMENTAL_DISABLE_VARS: [&str; 1] = ["CI"];

#[derive(ThisError, Debug)]
Expand Down
12 changes: 12 additions & 0 deletions docs/site/content/docs/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ Select a terminal UI for the repository.
}
```

### `noUpdateNotifier`

Default: `false`

When set to `true`, disables the update notification that appears when a new version of `turbo` is available.

```json title="./turbo.json"
{
"noUpdateNotifier": true
}
```

### `concurrency`

Default: `"10"`
Expand Down
5 changes: 5 additions & 0 deletions packages/turbo-types/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
"boundaries": {
"$ref": "#/definitions/RootBoundariesConfig",
"description": "Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents"
},
"noUpdateNotifier": {
"type": "boolean",
"description": "When set to `true`, disables the update notification that appears when a new version of `turbo` is available.\n\nDocumentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier",
"default": false
}
},
"additionalProperties": false,
Expand Down
5 changes: 5 additions & 0 deletions packages/turbo-types/schemas/schema.v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
"boundaries": {
"$ref": "#/definitions/RootBoundariesConfig",
"description": "Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents"
},
"noUpdateNotifier": {
"type": "boolean",
"description": "When set to `true`, disables the update notification that appears when a new version of `turbo` is available.\n\nDocumentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier",
"default": false
}
},
"additionalProperties": false,
Expand Down
9 changes: 9 additions & 0 deletions packages/turbo-types/src/types/config-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ export interface RootSchema extends BaseSchema {
* Configuration for `turbo boundaries`. Allows users to restrict a package's dependencies and dependents
*/
boundaries?: RootBoundariesConfig;

/**
* When set to `true`, disables the update notification that appears when a new version of `turbo` is available.
*
* Documentation: https://turborepo.com/docs/reference/configuration#noupdatenotifier
*
* @defaultValue `false`
*/
noUpdateNotifier?: boolean;
}

export interface Pipeline {
Expand Down
Loading