Skip to content

Commit cb09069

Browse files
author
Dmitrii Troitskii
committed
fix: ensure noUpdateNotifier from turbo.json is respected (#11510)
The `noUpdateNotifier` setting in `turbo.json` was not being reliably respected because the full configuration pipeline could fail before reaching the turbo.json source. The pipeline resolves sources in priority order (CLI overrides → env vars → local config → global auth → global config → turbo.json), and any error from a higher-priority source would abort the entire pipeline via `try_fold`. The subsequent `.unwrap_or_default()` would then return a default config with `no_update_notifier: false`, silently discarding the turbo.json setting. This commit: - Adds a fallback in the shim's config provider that reads `noUpdateNotifier` directly from turbo.json when the full config pipeline does not yield `no_update_notifier: true`. This ensures the setting is respected even when unrelated config sources fail. - Improves error logging when the config pipeline fails, replacing the silent `.unwrap_or_default()` with explicit debug logging. - Adds tests verifying that `noUpdateNotifier` is correctly parsed from turbo.json and propagated through the full config build and the shim config resolution path. Fixes #11510
1 parent 6ef1582 commit cb09069

File tree

4 files changed

+131
-3
lines changed

4 files changed

+131
-3
lines changed

crates/turborepo-config/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,4 +958,28 @@ mod test {
958958
"Linked worktree should be marked as shared"
959959
);
960960
}
961+
962+
#[test]
963+
fn test_no_update_notifier_from_turbo_json_full_build() {
964+
let tmp_dir = TempDir::new().unwrap();
965+
let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap();
966+
967+
repo_root
968+
.join_component("turbo.json")
969+
.create_with_contents(r#"{"noUpdateNotifier": true}"#)
970+
.unwrap();
971+
972+
let builder = TurborepoConfigBuilder {
973+
repo_root,
974+
override_config: ConfigurationOptions::default(),
975+
global_config_path: None,
976+
environment: Some(HashMap::default()),
977+
};
978+
979+
let config = builder.build().unwrap();
980+
assert!(
981+
config.no_update_notifier(),
982+
"noUpdateNotifier from turbo.json should be respected"
983+
);
984+
}
961985
}

crates/turborepo-lib/src/config/funnel.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ mod tests {
130130
assert_eq!(overrides.no_update_notifier, Some(true));
131131
}
132132

133+
#[test]
134+
fn test_turbo_json_no_update_notifier_propagates_through_shim_config() {
135+
let tmp_dir = TempDir::new().unwrap();
136+
let repo_root = AbsoluteSystemPathBuf::try_from(tmp_dir.path()).unwrap();
137+
repo_root
138+
.join_component(CONFIG_FILE)
139+
.create_with_contents(r#"{"noUpdateNotifier": true}"#)
140+
.unwrap();
141+
142+
let config = super::resolve_configuration_for_shim(&repo_root, None).unwrap();
143+
assert!(
144+
config.no_update_notifier(),
145+
"noUpdateNotifier from turbo.json should propagate through \
146+
resolve_configuration_for_shim"
147+
);
148+
}
149+
133150
#[test]
134151
fn test_cli_overrides_are_highest_precedence() {
135152
let tmp_dir = TempDir::new().unwrap();

crates/turborepo-lib/src/shim.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,62 @@ impl ConfigProvider for TurboConfigProvider {
6666
root: &AbsoluteSystemPath,
6767
root_turbo_json: Option<&AbsoluteSystemPathBuf>,
6868
) -> ShimConfigurationOptions {
69-
let config = crate::config::resolve_configuration_for_shim(root, root_turbo_json)
70-
.unwrap_or_default();
71-
ShimConfigurationOptions::new(Some(config.no_update_notifier()))
69+
let config = match crate::config::resolve_configuration_for_shim(root, root_turbo_json) {
70+
Ok(config) => config,
71+
Err(e) => {
72+
tracing::debug!("Failed to resolve configuration for shim: {e}");
73+
Default::default()
74+
}
75+
};
76+
77+
// If the full config pipeline didn't yield no_update_notifier, read it
78+
// directly from turbo.json as a fallback. The full config pipeline can
79+
// fail (e.g. malformed global config or auth files) before it reaches
80+
// the turbo.json source, silently losing this setting.
81+
let no_update_notifier = if config.no_update_notifier() {
82+
true
83+
} else {
84+
read_no_update_notifier_from_turbo_json(root, root_turbo_json)
85+
};
86+
87+
ShimConfigurationOptions::new(Some(no_update_notifier))
88+
}
89+
}
90+
91+
/// Reads `noUpdateNotifier` directly from turbo.json as a fallback.
92+
///
93+
/// This is used when the full configuration pipeline does not yield a
94+
/// `no_update_notifier` value, which can happen if a higher-priority
95+
/// configuration source (global config, auth file, environment variable)
96+
/// errors during resolution, aborting the pipeline before turbo.json is
97+
/// processed.
98+
fn read_no_update_notifier_from_turbo_json(
99+
root: &AbsoluteSystemPath,
100+
root_turbo_json: Option<&AbsoluteSystemPathBuf>,
101+
) -> bool {
102+
let turbo_json_path = root_turbo_json
103+
.cloned()
104+
.or_else(|| {
105+
turborepo_config::resolve_turbo_config_path(root)
106+
.ok()
107+
});
108+
109+
let Some(path) = turbo_json_path else {
110+
return false;
111+
};
112+
113+
let contents = match path.read_existing_to_string() {
114+
Ok(Some(contents)) => contents,
115+
_ => return false,
116+
};
117+
118+
// Parse the turbo.json and check for noUpdateNotifier
119+
match turborepo_turbo_json::RawRootTurboJson::parse(&contents, "turbo.json") {
120+
Ok(raw) => raw
121+
.no_update_notifier
122+
.map(|v| *v.as_inner())
123+
.unwrap_or(false),
124+
Err(_) => false,
72125
}
73126
}
74127

crates/turborepo-turbo-json/src/parser.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,40 @@ mod tests {
391391
assert_snapshot!(msg);
392392
}
393393

394+
#[test]
395+
fn test_no_update_notifier_parsed_from_root_turbo_json() {
396+
let json = r#"{"noUpdateNotifier": true}"#;
397+
let result = RawRootTurboJson::parse(json, "turbo.json").unwrap();
398+
assert_eq!(
399+
result.no_update_notifier.as_ref().map(|v| *v.as_inner()),
400+
Some(true),
401+
"noUpdateNotifier should be parsed from root turbo.json"
402+
);
403+
}
404+
405+
#[test]
406+
fn test_no_update_notifier_parsed_from_full_turbo_json() {
407+
let json = r#"{
408+
"$schema": "https://turborepo.dev/schema.json",
409+
"noUpdateNotifier": true,
410+
"tasks": {
411+
"build": {
412+
"dependsOn": ["prebuild", "^build"],
413+
"outputs": ["output-file.txt", "dist/**"]
414+
},
415+
"prebuild": {},
416+
"lint": {},
417+
"check-types": {}
418+
}
419+
}"#;
420+
let result = RawRootTurboJson::parse(json, "turbo.json").unwrap();
421+
assert_eq!(
422+
result.no_update_notifier.as_ref().map(|v| *v.as_inner()),
423+
Some(true),
424+
"noUpdateNotifier should be parsed from a full turbo.json"
425+
);
426+
}
427+
394428
#[test]
395429
fn test_unknown_key_in_task_definition() {
396430
// Task definitions should reject unknown keys

0 commit comments

Comments
 (0)