Skip to content

Commit ba769dc

Browse files
feat: support task id entrypoints (#9344)
### Description Currently if you run `turbo cli#build` you will get the following failure*: ``` Error: × could not find task `cli#build` in project ``` *It works if you had `"tasks": {"cli#build": {...}}` in your root level `turbo.json` After this PR, passing a fully qualified task id to run will now work, regardless of how the task definition is defined: - In the root `turbo.json` as `build` - In the root `turbo.json` as `cli#build` - In the `cli` workspace's `turbo.json` as `build` The usage of `#` is safe here as you already have been unable to use `#` in a unqualified task name. - If you attempt to use a `#` in a task name in a workspace level `turbo.json` you will get an error about using package task syntax in a workspace file. - If you attempt to specify a task in the root `turbo.json` of the form `foo#bar` it will be read as the `bar` task in package `foo` - If you attempt to use `foo#bar#baz` as a task name in root `turbo.json` it will work currently with `foo#bar#baz` and after this PR it will continue to work ### Testing Instructions Added unit tests! Manual verification by running `turbo cli#build`.
1 parent 7bf0228 commit ba769dc

File tree

6 files changed

+161
-1
lines changed

6 files changed

+161
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ daemon-file-hashing = []
1919
anyhow = { workspace = true, features = ["backtrace"] }
2020
assert_cmd = { workspace = true }
2121
async-stream = "0.3.4"
22+
insta = { workspace = true }
2223
itertools = { workspace = true }
2324
port_scanner = { workspace = true }
2425
pretty_assertions = { workspace = true }

crates/turborepo-lib/src/engine/builder.rs

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,16 @@ impl<'a> EngineBuilder<'a> {
433433
};
434434

435435
let task_id_as_name = task_id.as_task_name();
436-
if turbo_json.tasks.contains_key(&task_id_as_name)
436+
if
437+
// See if pkg#task is defined e.g. `docs#build`. This can only happen in root turbo.json
438+
turbo_json.tasks.contains_key(&task_id_as_name)
439+
// See if task is defined e.g. `build`. This can happen in root or workspace turbo.json
440+
// This will fail if the user provided a task id e.g. turbo `docs#build`
437441
|| turbo_json.tasks.contains_key(task_name)
442+
// If user provided a task id, then we see if the task is defined
443+
// e.g. `docs#build` should resolve if there's a `build` in root turbo.json or docs workspace level turbo.json
444+
|| (matches!(workspace, PackageName::Root) && turbo_json.tasks.contains_key(&TaskName::from(task_name.task())))
445+
|| (workspace == &PackageName::from(task_id.package()) && turbo_json.tasks.contains_key(&TaskName::from(task_name.task())))
438446
{
439447
Ok(true)
440448
} else if !matches!(workspace, PackageName::Root) {
@@ -553,6 +561,7 @@ fn validate_task_name(task: Spanned<&str>) -> Result<(), Error> {
553561
mod test {
554562
use std::assert_matches::assert_matches;
555563

564+
use insta::assert_json_snapshot;
556565
use pretty_assertions::assert_eq;
557566
use serde_json::json;
558567
use tempfile::TempDir;
@@ -680,6 +689,10 @@ mod test {
680689
#[test_case(PackageName::from("b"), "build", "b#build", true ; "workspace task in workspace")]
681690
#[test_case(PackageName::from("b"), "test", "b#test", true ; "task missing from workspace")]
682691
#[test_case(PackageName::from("c"), "missing", "c#missing", false ; "task missing")]
692+
#[test_case(PackageName::from("c"), "c#curse", "c#curse", true ; "root defined task")]
693+
#[test_case(PackageName::from("b"), "c#curse", "c#curse", true ; "non-workspace root defined task")]
694+
#[test_case(PackageName::from("b"), "b#special", "b#special", true ; "workspace defined task")]
695+
#[test_case(PackageName::from("c"), "b#special", "b#special", false ; "non-workspace defined task")]
683696
fn test_task_definition(
684697
workspace: PackageName,
685698
task_name: &'static str,
@@ -694,6 +707,7 @@ mod test {
694707
"test": { "inputs": ["testing"] },
695708
"build": { "inputs": ["primary"] },
696709
"a#build": { "inputs": ["special"] },
710+
"c#curse": {},
697711
}
698712
})),
699713
),
@@ -702,6 +716,7 @@ mod test {
702716
turbo_json(json!({
703717
"tasks": {
704718
"build": { "inputs": ["outer"]},
719+
"special": {},
705720
}
706721
})),
707722
),
@@ -1245,4 +1260,116 @@ mod test {
12451260
.err();
12461261
assert_eq!(result.as_deref(), reason);
12471262
}
1263+
1264+
#[test]
1265+
fn test_run_package_task_exact() {
1266+
let repo_root_dir = TempDir::with_prefix("repo").unwrap();
1267+
let repo_root = AbsoluteSystemPathBuf::new(repo_root_dir.path().to_str().unwrap()).unwrap();
1268+
let package_graph = mock_package_graph(
1269+
&repo_root,
1270+
package_jsons! {
1271+
repo_root,
1272+
"app1" => ["libA"],
1273+
"app2" => ["libA"],
1274+
"libA" => []
1275+
},
1276+
);
1277+
let turbo_jsons = vec![
1278+
(
1279+
PackageName::Root,
1280+
turbo_json(json!({
1281+
"tasks": {
1282+
"build": { "dependsOn": ["^build"] },
1283+
"special": { "dependsOn": ["^build"] },
1284+
}
1285+
})),
1286+
),
1287+
(
1288+
PackageName::from("app2"),
1289+
turbo_json(json!({
1290+
"extends": ["//"],
1291+
"tasks": {
1292+
"another": { "dependsOn": ["^build"] },
1293+
}
1294+
})),
1295+
),
1296+
]
1297+
.into_iter()
1298+
.collect();
1299+
let loader = TurboJsonLoader::noop(turbo_jsons);
1300+
let engine = EngineBuilder::new(&repo_root, &package_graph, loader, false)
1301+
.with_tasks(vec![
1302+
Spanned::new(TaskName::from("app1#special")),
1303+
Spanned::new(TaskName::from("app2#another")),
1304+
])
1305+
.with_workspaces(vec![PackageName::from("app1"), PackageName::from("app2")])
1306+
.build()
1307+
.unwrap();
1308+
1309+
let expected = deps! {
1310+
"app1#special" => ["libA#build"],
1311+
"app2#another" => ["libA#build"],
1312+
"libA#build" => ["___ROOT___"]
1313+
};
1314+
assert_eq!(all_dependencies(&engine), expected);
1315+
}
1316+
1317+
#[test]
1318+
fn test_run_package_task_exact_error() {
1319+
let repo_root_dir = TempDir::with_prefix("repo").unwrap();
1320+
let repo_root = AbsoluteSystemPathBuf::new(repo_root_dir.path().to_str().unwrap()).unwrap();
1321+
let package_graph = mock_package_graph(
1322+
&repo_root,
1323+
package_jsons! {
1324+
repo_root,
1325+
"app1" => ["libA"],
1326+
"libA" => []
1327+
},
1328+
);
1329+
let turbo_jsons = vec![
1330+
(
1331+
PackageName::Root,
1332+
turbo_json(json!({
1333+
"tasks": {
1334+
"build": { "dependsOn": ["^build"] },
1335+
}
1336+
})),
1337+
),
1338+
(
1339+
PackageName::from("app1"),
1340+
turbo_json(json!({
1341+
"extends": ["//"],
1342+
"tasks": {
1343+
"another": { "dependsOn": ["^build"] },
1344+
}
1345+
})),
1346+
),
1347+
]
1348+
.into_iter()
1349+
.collect();
1350+
let loader = TurboJsonLoader::noop(turbo_jsons);
1351+
let engine = EngineBuilder::new(&repo_root, &package_graph, loader.clone(), false)
1352+
.with_tasks(vec![Spanned::new(TaskName::from("app1#special"))])
1353+
.with_workspaces(vec![PackageName::from("app1")])
1354+
.build();
1355+
assert!(engine.is_err());
1356+
let report = miette::Report::new(engine.unwrap_err());
1357+
let mut msg = String::new();
1358+
miette::JSONReportHandler::new()
1359+
.render_report(&mut msg, report.as_ref())
1360+
.unwrap();
1361+
assert_json_snapshot!(msg);
1362+
1363+
let engine = EngineBuilder::new(&repo_root, &package_graph, loader, false)
1364+
.with_tasks(vec![Spanned::new(TaskName::from("app1#another"))])
1365+
.with_workspaces(vec![PackageName::from("libA")])
1366+
.build();
1367+
assert!(engine.is_err());
1368+
let report = miette::Report::new(engine.unwrap_err());
1369+
let mut msg = String::new();
1370+
miette::JSONReportHandler::new()
1371+
.render_report(&mut msg, report.as_ref())
1372+
.unwrap();
1373+
assert_json_snapshot!(msg);
1374+
}
12481375
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: crates/turborepo-lib/src/engine/builder.rs
3+
expression: msg
4+
---
5+
"{\"message\": \"missing tasks in project\",\"severity\": \"error\",\"causes\": [],\"labels\": [],\"related\": [{\"message\": \"could not find task `app1#another` in project\",\"severity\": \"error\",\"causes\": [],\"filename\": \"\",\"labels\": [],\"related\": []}]}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: crates/turborepo-lib/src/engine/builder.rs
3+
expression: msg
4+
---
5+
"{\"message\": \"missing tasks in project\",\"severity\": \"error\",\"causes\": [],\"labels\": [],\"related\": [{\"message\": \"could not find task `app1#special` in project\",\"severity\": \"error\",\"causes\": [],\"filename\": \"\",\"labels\": [],\"related\": []}]}"

turborepo-tests/integration/tests/workspace-configs/cross-workspace.t

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,24 @@ Setup
2121
Cached: 0 cached, 2 total
2222
Time:\s*[\.0-9]+m?s (re)
2323

24+
$ ${TURBO} run cross-workspace#cross-workspace-task
25+
\xe2\x80\xa2 Packages in scope: add-keys, add-tasks, bad-json, blank-pkg, cached, config-change, cross-workspace, invalid-config, missing-workspace-config, omit-keys, override-values, persistent (esc)
26+
\xe2\x80\xa2 Running cross-workspace#cross-workspace-task in 12 packages (esc)
27+
\xe2\x80\xa2 Remote caching disabled (esc)
28+
blank-pkg:cross-workspace-underlying-task: cache hit, replaying logs 39566f6362976823
29+
blank-pkg:cross-workspace-underlying-task:
30+
blank-pkg:cross-workspace-underlying-task: > cross-workspace-underlying-task
31+
blank-pkg:cross-workspace-underlying-task: > echo cross-workspace-underlying-task from blank-pkg
32+
blank-pkg:cross-workspace-underlying-task:
33+
blank-pkg:cross-workspace-underlying-task: cross-workspace-underlying-task from blank-pkg
34+
cross-workspace:cross-workspace-task: cache hit, replaying logs bce507a110930f07
35+
cross-workspace:cross-workspace-task:
36+
cross-workspace:cross-workspace-task: > cross-workspace-task
37+
cross-workspace:cross-workspace-task: > echo cross-workspace-task
38+
cross-workspace:cross-workspace-task:
39+
cross-workspace:cross-workspace-task: cross-workspace-task
40+
41+
Tasks: 2 successful, 2 total
42+
Cached: 2 cached, 2 total
43+
Time:\s*[\.0-9]+m?s >>> FULL TURBO (re)
44+

0 commit comments

Comments
 (0)