Skip to content
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
7 changes: 0 additions & 7 deletions crates/turborepo-lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,6 @@ pub enum Error {
#[error(transparent)]
#[diagnostic(transparent)]
UnnecessaryPackageTaskSyntax(Box<UnnecessaryPackageTaskSyntaxError>),
#[error("You can only extend from the root of the workspace.")]
ExtendFromNonRoot {
#[label("non-root workspace found here")]
span: Option<SourceSpan>,
#[source_code]
text: NamedSource<String>,
},
#[error("You must extend from the root of the workspace first.")]
ExtendsRootFirst {
#[label("'//' should be first")]
Expand Down
51 changes: 9 additions & 42 deletions crates/turborepo-lib/src/engine/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2090,14 +2090,9 @@ mod test {
"app should inherit 'build' task from shared-config via extends"
);

// Also verify the engine can be built with this task (with non_root_extends
// enabled)
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};
// Also verify the engine can be built with this task
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(Some(Spanned::new(TaskName::from("build"))))
.with_workspaces(vec![PackageName::from("app")])
.build()
Expand Down Expand Up @@ -2375,12 +2370,8 @@ mod test {
);

// Also verify has_task_definition_in_run works for deep chain
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(Some(Spanned::new(TaskName::from("task-d"))))
.with_workspaces(vec![PackageName::from("pkg-a")])
.build()
Expand Down Expand Up @@ -2491,12 +2482,8 @@ mod test {
assert_eq!(tasks.len(), 4, "Should have exactly 4 unique tasks");

// Also verify the engine builds successfully
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(Some(Spanned::new(TaskName::from("build"))))
.with_workspaces(vec![PackageName::from("app")])
.build()
Expand Down Expand Up @@ -3374,14 +3361,10 @@ mod test {
.collect();

let loader = TurboJsonLoader::noop(turbo_jsons);
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};

// All tasks should be found since they have config beyond extends
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(vec![
Spanned::new(TaskName::from("build")),
Spanned::new(TaskName::from("lint")),
Expand Down Expand Up @@ -3438,14 +3421,10 @@ mod test {
.collect();

let loader = TurboJsonLoader::noop(turbo_jsons);
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};

// Use add_all_tasks mode
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.add_all_tasks()
.with_workspaces(vec![PackageName::from("app")])
.build()
Expand Down Expand Up @@ -3504,13 +3483,9 @@ mod test {
.collect();

let loader = TurboJsonLoader::noop(turbo_jsons);
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};

let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(Some(Spanned::new(TaskName::from("build"))))
.with_workspaces(vec![PackageName::from("app")])
.build()
Expand Down Expand Up @@ -3906,14 +3881,10 @@ mod test {
.collect();

let loader = TurboJsonLoader::noop(turbo_jsons);
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};

// Build with both workspaces
let engine = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags)
.with_future_flags(FutureFlags::default())
.with_tasks(Some(Spanned::new(TaskName::from("build"))))
.with_workspaces(vec![PackageName::from("app1"), PackageName::from("app2")])
.build()
Expand Down Expand Up @@ -4062,12 +4033,8 @@ mod test {
);

let loader = TurboJsonLoader::noop(turbo_jsons);
let future_flags = FutureFlags {
non_root_extends: true,
..Default::default()
};
let engine_builder = EngineBuilder::new(&repo_root, &package_graph, &loader, false)
.with_future_flags(future_flags);
.with_future_flags(FutureFlags::default());

// Verify task_definition_chain gets definitions from shared-config, not root
let task_name = TaskName::from("build");
Expand Down
6 changes: 3 additions & 3 deletions crates/turborepo-lib/src/turbo_json/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ Turborepo uses two different schemas for `turbo.json` files depending on their l

- Can define global configuration options (`globalEnv`, `globalDependencies`, `globalPassThroughEnv`)
- Can set repository-wide settings (`remoteCache`, `ui`, `daemon`, `envMode`, etc.)
- Can define `futureFlags` for experimental features
- Cannot use `extends` field

- **Package `turbo.json`** (`RawPackageTurboJson`): Located in workspace packages
- Limited to task definitions and workspace-specific configuration
- Must use `extends: ["//"]` to inherit from root configuration
- Must use `extends: ["//", ...]` to inherit from root configuration (root must be first)
- Can extend from other packages (e.g., `["//", "shared-config"]`)
- Can define workspace-specific `tags` and `boundaries`
- Cannot define global settings or `futureFlags`
- Cannot define global settings

### Key Components

Expand Down
21 changes: 5 additions & 16 deletions crates/turborepo-lib/src/turbo_json/future_flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//! ```json
//! {
//! "futureFlags": {
//! "turboExtends": true
//! }
//! }
//! ```
Expand All @@ -26,26 +25,16 @@ use struct_iterable::Iterable;
///
/// Each flag represents an experimental feature that can be enabled
/// before it becomes the default behavior in a future version.
///
/// Note: Currently all previous future flags (turboExtendsKeyword,
/// nonRootExtends) have been graduated and are now enabled by default.
#[derive(Serialize, Default, Debug, Copy, Clone, Iterable, Deserializable, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[deserializable()]
pub struct FutureFlags {
/// Enable `$TURBO_EXTENDS$`
///
/// When enabled, allows using `$TURBO_EXTENDS$` in array fields.
/// This will change the default behavior of overriding the field to instead
/// append.
pub turbo_extends_keyword: bool,
/// Enable extending from a non-root `turbo.json`
///
/// When enabled, allows using extends targeting `turbo.json`s other than
/// root. All `turbo.json` must still extend from the root `turbo.json`
/// first.
pub non_root_extends: bool,
}
pub struct FutureFlags {}

impl FutureFlags {
/// Create a new FutureFlags with all features disabled
/// Create a new FutureFlags
pub fn new() -> Self {
Self::default()
}
Expand Down
14 changes: 4 additions & 10 deletions crates/turborepo-lib/src/turbo_json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,6 @@ mod tests {
"build": {}
},
"futureFlags": {
"turboExtendsKeyword": true
}
}"#;

Expand All @@ -1024,16 +1023,11 @@ mod tests {
);
let raw_turbo_json: RawTurboJson = deserialized_result.into_deserialized().unwrap();

// Verify that futureFlags is parsed correctly
// Verify that futureFlags is parsed correctly (empty now that flags are
// removed)
assert!(raw_turbo_json.future_flags.is_some());
let future_flags = raw_turbo_json.future_flags.as_ref().unwrap();
assert_eq!(
future_flags.as_inner(),
&FutureFlags {
turbo_extends_keyword: true,
non_root_extends: false,
}
);
assert_eq!(future_flags.as_inner(), &FutureFlags {});

// Verify that the futureFlags field doesn't cause errors during conversion to
// TurboJson
Expand All @@ -1050,7 +1044,7 @@ mod tests {
true ; "root config with global fields should succeed"
)]
#[test_case(
r#"{"futureFlags": {"turboExtendsKeyword": true}, "tasks": {"build": {}}}"#,
r#"{"futureFlags": {}, "tasks": {"build": {}}}"#,
true ; "root config with futureFlags should succeed"
)]
#[test_case(
Expand Down
90 changes: 19 additions & 71 deletions crates/turborepo-lib/src/turbo_json/processed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ const TOPOLOGICAL_PIPELINE_DELIMITER: &str = "^";
/// Returns (processed_array, extends_found)
fn extract_turbo_extends(
mut items: Vec<Spanned<UnescapedString>>,
future_flags: &FutureFlags,
_future_flags: &FutureFlags,
) -> (Vec<Spanned<UnescapedString>>, bool) {
if !future_flags.turbo_extends_keyword {
return (items, false);
}

if let Some(pos) = items.iter().position(|item| item.as_str() == TURBO_EXTENDS) {
items.remove(pos);
(items, true)
Expand Down Expand Up @@ -414,62 +410,29 @@ mod tests {
use crate::turbo_json::FutureFlags;

#[test]
fn test_extract_turbo_extends_with_flag_enabled() {
fn test_extract_turbo_extends_with_marker() {
let items = vec![
Spanned::new(UnescapedString::from("item1")),
Spanned::new(UnescapedString::from("$TURBO_EXTENDS$")),
Spanned::new(UnescapedString::from("item2")),
];

let (processed, extends) = extract_turbo_extends(
items,
&FutureFlags {
turbo_extends_keyword: true,
non_root_extends: false,
},
);
let (processed, extends) = extract_turbo_extends(items, &FutureFlags {});

assert!(extends);
assert_eq!(processed.len(), 2);
assert_eq!(processed[0].as_str(), "item1");
assert_eq!(processed[1].as_str(), "item2");
}

#[test]
fn test_extract_turbo_extends_with_flag_disabled() {
let items = vec![
Spanned::new(UnescapedString::from("item1")),
Spanned::new(UnescapedString::from("$TURBO_EXTENDS$")),
Spanned::new(UnescapedString::from("item2")),
];

let (processed, extends) = extract_turbo_extends(
items,
&FutureFlags {
turbo_extends_keyword: false,
non_root_extends: false,
},
);

assert!(!extends);
assert_eq!(processed.len(), 3);
assert_eq!(processed[1].as_str(), "$TURBO_EXTENDS$");
}

#[test]
fn test_extract_turbo_extends_no_marker() {
let items = vec![
Spanned::new(UnescapedString::from("item1")),
Spanned::new(UnescapedString::from("item2")),
];

let (processed, extends) = extract_turbo_extends(
items,
&FutureFlags {
turbo_extends_keyword: true,
non_root_extends: false,
},
);
let (processed, extends) = extract_turbo_extends(items, &FutureFlags {});

assert!(!extends);
assert_eq!(processed.len(), 2);
Expand Down Expand Up @@ -609,14 +572,7 @@ mod tests {
Spanned::new(UnescapedString::from("lib/**")),
];

let inputs = ProcessedInputs::new(
raw_globs,
&FutureFlags {
turbo_extends_keyword: true,
non_root_extends: false,
},
)
.unwrap();
let inputs = ProcessedInputs::new(raw_globs, &FutureFlags {}).unwrap();

assert!(inputs.extends);
assert_eq!(inputs.globs.len(), 2);
Expand All @@ -625,42 +581,34 @@ mod tests {
}

#[test]
fn test_processed_env_turbo_extends_disabled_errors() {
// When turbo_extends is disabled, $TURBO_EXTENDS$ triggers validation error
fn test_processed_env_with_turbo_extends() {
// $TURBO_EXTENDS$ is now always processed
let raw_env: Vec<Spanned<UnescapedString>> = vec![
Spanned::new(UnescapedString::from("NODE_ENV")),
Spanned::new(UnescapedString::from("$TURBO_EXTENDS$")),
Spanned::new(UnescapedString::from("API_KEY")),
];

let result = ProcessedEnv::new(
raw_env,
&FutureFlags {
turbo_extends_keyword: false,
non_root_extends: false,
},
);
assert!(result.is_err());
assert_matches!(result, Err(Error::InvalidEnvPrefix(_)));
let result = ProcessedEnv::new(raw_env, &FutureFlags {});
assert!(result.is_ok());
let env = result.unwrap();
assert!(env.extends);
assert_eq!(env.vars.len(), 2);
}

#[test]
fn test_processed_depends_on_turbo_extends_disabled_errors() {
// When turbo_extends is disabled, $TURBO_EXTENDS$ triggers validation error
fn test_processed_depends_on_with_turbo_extends() {
// $TURBO_EXTENDS$ is now always processed
let raw_deps: Vec<Spanned<UnescapedString>> = vec![
Spanned::new(UnescapedString::from("build")),
Spanned::new(UnescapedString::from("$TURBO_EXTENDS$")),
Spanned::new(UnescapedString::from("test")),
];

let result = ProcessedDependsOn::new(
Spanned::new(raw_deps),
&FutureFlags {
turbo_extends_keyword: false,
non_root_extends: false,
},
);
assert!(result.is_err());
assert_matches!(result, Err(Error::InvalidDependsOnValue { .. }));
let result = ProcessedDependsOn::new(Spanned::new(raw_deps), &FutureFlags {});
assert!(result.is_ok());
let depends_on = result.unwrap();
assert!(depends_on.extends);
assert_eq!(depends_on.deps.len(), 2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ source: crates/turborepo-lib/src/turbo_json/validator.rs
expression: error_messages
---
[
"You can only extend from the root of the workspace.",
"You must extend from the root of the workspace first.",
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ source: crates/turborepo-lib/src/turbo_json/validator.rs
expression: error_messages
---
[
"You can only extend from the root of the workspace.",
"You must extend from the root of the workspace first.",
]
Loading
Loading