Skip to content

Commit ca14867

Browse files
refactor: consolidate configuration file resolving (#10619)
### Description This PR refactors the codebase to deduplicate the logic for resolving `turbo.json` and `turbo.jsonc` configuration files. - Introduced `resolve_turbo_config_path` in Rust (`crates/turborepo-lib/src/turbo_json/mod.rs`) and `resolveTurboConfigPath` in TypeScript (`packages/turbo-utils/src/getTurboConfigs.ts`). - Replaced duplicated file existence checks and conflict resolution with calls to these new utility functions. - Ensures that the behavior for handling `turbo.json` and `turbo.jsonc` (precedence, error on both existing) remains unchanged. ### Testing Instructions - The refactor is purely internal, and existing tests cover the behavior. - Run `cargo test` in the Rust workspace. - Run `pnpm check-types` in the root to verify TypeScript changes. --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 2185590 commit ca14867

File tree

4 files changed

+80
-58
lines changed

4 files changed

+80
-58
lines changed

crates/turborepo-lib/src/commands/mod.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
cli,
1111
config::{ConfigurationOptions, Error as ConfigError, TurborepoConfigBuilder},
1212
opts::Opts,
13-
turbo_json::{CONFIG_FILE, CONFIG_FILE_JSONC},
13+
turbo_json::resolve_turbo_config_path,
1414
Args,
1515
};
1616

@@ -146,26 +146,7 @@ impl CommandBase {
146146
self.repo_root.join_component("package.json")
147147
}
148148
fn root_turbo_json_path(&self) -> Result<AbsoluteSystemPathBuf, ConfigError> {
149-
let turbo_json_path = self.repo_root.join_component(CONFIG_FILE);
150-
let turbo_jsonc_path = self.repo_root.join_component(CONFIG_FILE_JSONC);
151-
152-
let turbo_json_exists = turbo_json_path.exists();
153-
let turbo_jsonc_exists = turbo_jsonc_path.exists();
154-
155-
if turbo_json_exists && turbo_jsonc_exists {
156-
return Err(ConfigError::MultipleTurboConfigs {
157-
directory: self.repo_root.to_string(),
158-
});
159-
}
160-
161-
if turbo_json_exists {
162-
Ok(turbo_json_path)
163-
} else if turbo_jsonc_exists {
164-
Ok(turbo_jsonc_path)
165-
} else {
166-
Ok(turbo_json_path) // Default to turbo.json path even if it doesn't
167-
// exist
168-
}
149+
resolve_turbo_config_path(&self.repo_root)
169150
}
170151

171152
pub fn api_auth(&self) -> Result<Option<APIAuth>, ConfigError> {

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

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use turborepo_repository::package_graph::PackageName;
2626
pub use crate::turbo_json::{RawTurboJson, UIMode};
2727
use crate::{
2828
cli::{EnvMode, LogOrder},
29-
turbo_json::{CONFIG_FILE, CONFIG_FILE_JSONC},
29+
turbo_json::resolve_turbo_config_path,
3030
};
3131

3232
#[derive(Debug, Error, Diagnostic)]
@@ -454,21 +454,7 @@ impl ConfigurationOptions {
454454
return Ok(path.clone());
455455
}
456456

457-
// Check if both files exist
458-
let turbo_json_path = repo_root.join_component(CONFIG_FILE);
459-
let turbo_jsonc_path = repo_root.join_component(CONFIG_FILE_JSONC);
460-
let turbo_json_exists = turbo_json_path.try_exists()?;
461-
let turbo_jsonc_exists = turbo_jsonc_path.try_exists()?;
462-
463-
match (turbo_json_exists, turbo_jsonc_exists) {
464-
(true, true) => Err(Error::MultipleTurboConfigs {
465-
directory: repo_root.to_string(),
466-
}),
467-
(true, false) => Ok(turbo_json_path),
468-
(false, true) => Ok(turbo_jsonc_path),
469-
// Default to turbo.json if neither exists
470-
(false, false) => Ok(turbo_json_path),
471-
}
457+
resolve_turbo_config_path(repo_root)
472458
}
473459

474460
pub fn allow_no_turbo_json(&self) -> bool {

crates/turborepo-lib/src/turbo_json/mod.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,37 @@ use crate::{boundaries::BoundariesConfig, config::UnnecessaryPackageTaskSyntaxEr
3636
const TURBO_ROOT: &str = "$TURBO_ROOT$";
3737
const TURBO_ROOT_SLASH: &str = "$TURBO_ROOT$/";
3838

39+
pub const CONFIG_FILE: &str = "turbo.json";
40+
pub const CONFIG_FILE_JSONC: &str = "turbo.jsonc";
41+
const ENV_PIPELINE_DELIMITER: &str = "$";
42+
const TOPOLOGICAL_PIPELINE_DELIMITER: &str = "^";
43+
44+
/// Given a directory path, determines which turbo.json configuration file to
45+
/// use. Returns an error if both turbo.json and turbo.jsonc exist in the same
46+
/// directory. Returns the path to the config file to use, defaulting to
47+
/// turbo.json if neither exists.
48+
pub fn resolve_turbo_config_path(
49+
dir_path: &turbopath::AbsoluteSystemPath,
50+
) -> Result<turbopath::AbsoluteSystemPathBuf, crate::config::Error> {
51+
use crate::config::Error;
52+
53+
let turbo_json_path = dir_path.join_component(CONFIG_FILE);
54+
let turbo_jsonc_path = dir_path.join_component(CONFIG_FILE_JSONC);
55+
56+
let turbo_json_exists = turbo_json_path.try_exists()?;
57+
let turbo_jsonc_exists = turbo_jsonc_path.try_exists()?;
58+
59+
match (turbo_json_exists, turbo_jsonc_exists) {
60+
(true, true) => Err(Error::MultipleTurboConfigs {
61+
directory: dir_path.to_string(),
62+
}),
63+
(true, false) => Ok(turbo_json_path),
64+
(false, true) => Ok(turbo_jsonc_path),
65+
// Default to turbo.json if neither exists
66+
(false, false) => Ok(turbo_json_path),
67+
}
68+
}
69+
3970
#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone, Deserializable)]
4071
#[serde(rename_all = "camelCase")]
4172
pub struct SpacesJson {
@@ -327,11 +358,6 @@ impl RawTaskDefinition {
327358
}
328359
}
329360

330-
pub const CONFIG_FILE: &str = "turbo.json";
331-
pub const CONFIG_FILE_JSONC: &str = "turbo.jsonc";
332-
const ENV_PIPELINE_DELIMITER: &str = "$";
333-
const TOPOLOGICAL_PIPELINE_DELIMITER: &str = "^";
334-
335361
impl TryFrom<Vec<Spanned<UnescapedString>>> for TaskOutputs {
336362
type Error = Error;
337363
fn try_from(outputs: Vec<Spanned<UnescapedString>>) -> Result<Self, Self::Error> {

packages/turbo-utils/src/getTurboConfigs.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,38 @@ import type { PackageJson, PNPMWorkspaceConfig } from "./types";
1717
const ROOT_GLOB = "{turbo.json,turbo.jsonc}";
1818
const ROOT_WORKSPACE_GLOB = "package.json";
1919

20+
/**
21+
* Given a directory path, determines which turbo config file to use.
22+
* Returns error information if both turbo.json and turbo.jsonc exist in the same directory.
23+
* Returns the path to the config file to use, or null if neither exists.
24+
*/
25+
function resolveTurboConfigPath(dirPath: string): {
26+
configPath: string | null;
27+
configExists: boolean;
28+
error?: string;
29+
} {
30+
const turboJsonPath = path.join(dirPath, "turbo.json");
31+
const turboJsoncPath = path.join(dirPath, "turbo.jsonc");
32+
33+
const turboJsonExists = fs.existsSync(turboJsonPath);
34+
const turboJsoncExists = fs.existsSync(turboJsoncPath);
35+
36+
if (turboJsonExists && turboJsoncExists) {
37+
const errorMessage = `Found both turbo.json and turbo.jsonc in the same directory: ${dirPath}\nPlease use either turbo.json or turbo.jsonc, but not both.`;
38+
return { configPath: null, configExists: false, error: errorMessage };
39+
}
40+
41+
if (turboJsonExists) {
42+
return { configPath: turboJsonPath, configExists: true };
43+
}
44+
45+
if (turboJsoncExists) {
46+
return { configPath: turboJsoncPath, configExists: true };
47+
}
48+
49+
return { configPath: null, configExists: false };
50+
}
51+
2052
export interface WorkspaceConfig {
2153
workspaceName: string;
2254
workspacePath: string;
@@ -189,27 +221,24 @@ export function getWorkspaceConfigs(
189221
const isWorkspaceRoot = workspacePath === turboRoot;
190222

191223
// Try and get turbo.json or turbo.jsonc
192-
const turboJsonPath = path.join(workspacePath, "turbo.json");
193-
const turboJsoncPath = path.join(workspacePath, "turbo.jsonc");
194-
195-
// Check if both files exist
196-
const turboJsonExists = fs.existsSync(turboJsonPath);
197-
const turboJsoncExists = fs.existsSync(turboJsoncPath);
198-
199-
if (turboJsonExists && turboJsoncExists) {
200-
const errorMessage = `Found both turbo.json and turbo.jsonc in the same directory: ${workspacePath}\nPlease use either turbo.json or turbo.jsonc, but not both.`;
201-
logger.error(errorMessage);
202-
throw new Error(errorMessage);
203-
}
224+
const {
225+
configPath: turboConfigPath,
226+
configExists,
227+
error,
228+
} = resolveTurboConfigPath(workspacePath);
204229

205230
let rawTurboJson = null;
206231
let turboConfig: SchemaV1 | undefined;
207232

208233
try {
209-
if (turboJsonExists) {
210-
rawTurboJson = fs.readFileSync(turboJsonPath, "utf8");
211-
} else if (turboJsoncExists) {
212-
rawTurboJson = fs.readFileSync(turboJsoncPath, "utf8");
234+
// TODO: Our code was allowing both config files to exist. This is a bug, needs to be fixed.
235+
if (error) {
236+
logger.error(error);
237+
throw new Error(error);
238+
}
239+
240+
if (configExists && turboConfigPath) {
241+
rawTurboJson = fs.readFileSync(turboConfigPath, "utf8");
213242
}
214243

215244
if (rawTurboJson) {

0 commit comments

Comments
 (0)