@@ -11,7 +11,7 @@ use clap::ValueEnum;
11
11
use miette:: { NamedSource , SourceSpan } ;
12
12
use serde:: { Deserialize , Serialize } ;
13
13
use struct_iterable:: Iterable ;
14
- use turbopath:: AbsoluteSystemPath ;
14
+ use turbopath:: { AbsoluteSystemPath , RelativeUnixPath } ;
15
15
use turborepo_errors:: Spanned ;
16
16
use turborepo_repository:: package_graph:: ROOT_PKG_NAME ;
17
17
use turborepo_unescape:: UnescapedString ;
@@ -33,6 +33,8 @@ pub use loader::TurboJsonLoader;
33
33
34
34
use crate :: { boundaries:: BoundariesConfig , config:: UnnecessaryPackageTaskSyntaxError } ;
35
35
36
+ const TURBO_ROOT : & str = "$TURBO_ROOT$" ;
37
+
36
38
#[ derive( Serialize , Deserialize , Debug , Default , PartialEq , Clone , Deserializable ) ]
37
39
#[ serde( rename_all = "camelCase" ) ]
38
40
pub struct SpacesJson {
@@ -343,10 +345,12 @@ impl TryFrom<Vec<Spanned<UnescapedString>>> for TaskOutputs {
343
345
}
344
346
}
345
347
346
- impl TryFrom < RawTaskDefinition > for TaskDefinition {
347
- type Error = Error ;
348
-
349
- fn try_from ( raw_task : RawTaskDefinition ) -> Result < Self , Error > {
348
+ impl TaskDefinition {
349
+ pub fn from_raw (
350
+ mut raw_task : RawTaskDefinition ,
351
+ path_to_repo_root : & RelativeUnixPath ,
352
+ ) -> Result < Self , Error > {
353
+ replace_turbo_root_token ( & mut raw_task, path_to_repo_root) ?;
350
354
let outputs = raw_task. outputs . unwrap_or_default ( ) . try_into ( ) ?;
351
355
352
356
let cache = raw_task. cache . is_none_or ( |c| c. into_inner ( ) ) ;
@@ -572,6 +576,8 @@ impl TryFrom<RawTurboJson> for TurboJson {
572
576
}
573
577
}
574
578
579
+ let tasks = raw_turbo. tasks . clone ( ) . unwrap_or_default ( ) ;
580
+
575
581
Ok ( TurboJson {
576
582
text : raw_turbo. span . text ,
577
583
path : raw_turbo. span . path ,
@@ -598,7 +604,7 @@ impl TryFrom<RawTurboJson> for TurboJson {
598
604
599
605
global_deps
600
606
} ,
601
- tasks : raw_turbo . tasks . unwrap_or_default ( ) ,
607
+ tasks,
602
608
// copy these over, we don't need any changes here.
603
609
extends : raw_turbo
604
610
. extends
@@ -629,7 +635,7 @@ impl TurboJson {
629
635
path : & AbsoluteSystemPath ,
630
636
) -> Result < TurboJson , Error > {
631
637
let raw_turbo_json = RawTurboJson :: read ( repo_root, path) ?;
632
- raw_turbo_json . try_into ( )
638
+ TurboJson :: try_from ( raw_turbo_json )
633
639
}
634
640
635
641
pub fn task ( & self , task_id : & TaskId , task_name : & TaskName ) -> Option < RawTaskDefinition > {
@@ -765,17 +771,75 @@ fn gather_env_vars(
765
771
Ok ( ( ) )
766
772
}
767
773
774
+ // Takes an input/output glob that might start with TURBO_ROOT_PREFIX
775
+ // and swap it with the relative path to the turbo root.
776
+ fn replace_turbo_root_token_in_string (
777
+ input : & mut Spanned < UnescapedString > ,
778
+ path_to_repo_root : & RelativeUnixPath ,
779
+ ) -> Result < ( ) , Error > {
780
+ match input. find ( TURBO_ROOT ) {
781
+ Some ( 0 ) => {
782
+ // Replace
783
+ input
784
+ . as_inner_mut ( )
785
+ . replace_range ( ..TURBO_ROOT . len ( ) , path_to_repo_root. as_str ( ) ) ;
786
+ Ok ( ( ) )
787
+ }
788
+ // Handle negations
789
+ Some ( 1 ) if input. starts_with ( '!' ) => {
790
+ input
791
+ . as_inner_mut ( )
792
+ . replace_range ( 1 ..TURBO_ROOT . len ( ) + 1 , path_to_repo_root. as_str ( ) ) ;
793
+ Ok ( ( ) )
794
+ }
795
+ // We do not allow for TURBO_ROOT to be used in the middle of a glob
796
+ Some ( _) => {
797
+ let ( span, text) = input. span_and_text ( "turbo.json" ) ;
798
+ Err ( Error :: InvalidTurboRootUse { span, text } )
799
+ }
800
+ None => Ok ( ( ) ) ,
801
+ }
802
+ }
803
+
804
+ fn replace_turbo_root_token (
805
+ task_definition : & mut RawTaskDefinition ,
806
+ path_to_repo_root : & RelativeUnixPath ,
807
+ ) -> Result < ( ) , Error > {
808
+ for input in task_definition
809
+ . inputs
810
+ . iter_mut ( )
811
+ . flat_map ( |inputs| inputs. iter_mut ( ) )
812
+ {
813
+ replace_turbo_root_token_in_string ( input, path_to_repo_root) ?;
814
+ }
815
+
816
+ for output in task_definition
817
+ . outputs
818
+ . iter_mut ( )
819
+ . flat_map ( |outputs| outputs. iter_mut ( ) )
820
+ {
821
+ replace_turbo_root_token_in_string ( output, path_to_repo_root) ?;
822
+ }
823
+
824
+ Ok ( ( ) )
825
+ }
826
+
768
827
#[ cfg( test) ]
769
828
mod tests {
829
+ use std:: sync:: Arc ;
830
+
770
831
use anyhow:: Result ;
771
832
use biome_deserialize:: json:: deserialize_from_json_str;
772
833
use biome_json_parser:: JsonParserOptions ;
773
834
use pretty_assertions:: assert_eq;
774
835
use serde_json:: json;
775
836
use test_case:: test_case;
837
+ use turbopath:: RelativeUnixPath ;
776
838
use turborepo_unescape:: UnescapedString ;
777
839
778
- use super :: { RawTurboJson , SpacesJson , Spanned , TurboJson , UIMode } ;
840
+ use super :: {
841
+ replace_turbo_root_token_in_string, RawTurboJson , SpacesJson , Spanned , TurboJson , UIMode ,
842
+ } ;
779
843
use crate :: {
780
844
boundaries:: BoundariesConfig ,
781
845
cli:: OutputLogsMode ,
@@ -787,7 +851,7 @@ mod tests {
787
851
#[ test_case( "{}" , "empty boundaries" ) ]
788
852
#[ test_case( r#"{"tags": {} }"# , "empty tags" ) ]
789
853
#[ test_case(
790
- r#"{"tags": { "my-tag": { "dependencies": { "allow": ["my-package"] } } } } "# ,
854
+ r#"{"tags": { "my-tag": { "dependencies": { "allow": ["my-package"] } } }"# ,
791
855
"tags and dependencies"
792
856
) ]
793
857
#[ test_case(
@@ -954,6 +1018,29 @@ mod tests {
954
1018
}
955
1019
; "full (windows)"
956
1020
) ]
1021
+ #[ test_case(
1022
+ r#"{
1023
+ "inputs": ["$TURBO_ROOT$/config.txt"],
1024
+ "outputs": ["$TURBO_ROOT$/coverage/**", "!$TURBO_ROOT$/coverage/index.html"]
1025
+ }"# ,
1026
+ RawTaskDefinition {
1027
+ inputs: Some ( vec![ Spanned :: new( UnescapedString :: from( "$TURBO_ROOT$/config.txt" ) ) . with_range( 25 ..50 ) ] ) ,
1028
+ outputs: Some ( vec![
1029
+ Spanned :: new( UnescapedString :: from( "$TURBO_ROOT$/coverage/**" ) ) . with_range( 77 ..103 ) ,
1030
+ Spanned :: new( UnescapedString :: from( "!$TURBO_ROOT$/coverage/index.html" ) ) . with_range( 105 ..140 ) ,
1031
+ ] ) ,
1032
+ ..RawTaskDefinition :: default ( )
1033
+ } ,
1034
+ TaskDefinition {
1035
+ inputs: vec![ "../../config.txt" . to_owned( ) ] ,
1036
+ outputs: TaskOutputs {
1037
+ inclusions: vec![ "../../coverage/**" . to_owned( ) ] ,
1038
+ exclusions: vec![ "../../coverage/index.html" . to_owned( ) ] ,
1039
+ } ,
1040
+ ..TaskDefinition :: default ( )
1041
+ }
1042
+ ; "turbo root"
1043
+ ) ]
957
1044
fn test_deserialize_task_definition (
958
1045
task_definition_content : & str ,
959
1046
expected_raw_task_definition : RawTaskDefinition ,
@@ -968,7 +1055,8 @@ mod tests {
968
1055
deserialized_result. into_deserialized ( ) . unwrap ( ) ;
969
1056
assert_eq ! ( raw_task_definition, expected_raw_task_definition) ;
970
1057
971
- let task_definition: TaskDefinition = raw_task_definition. try_into ( ) ?;
1058
+ let task_definition =
1059
+ TaskDefinition :: from_raw ( raw_task_definition, RelativeUnixPath :: new ( "../.." ) . unwrap ( ) ) ?;
972
1060
assert_eq ! ( task_definition, expected_task_definition) ;
973
1061
974
1062
Ok ( ( ) )
@@ -1191,4 +1279,24 @@ mod tests {
1191
1279
& [ Spanned :: new( UnescapedString :: from( "api#server" ) ) ]
1192
1280
) ;
1193
1281
}
1282
+
1283
+ #[ test_case( "index.ts" , Ok ( "index.ts" ) ; "no token" ) ]
1284
+ #[ test_case( "$TURBO_ROOT$/config.txt" , Ok ( "../../config.txt" ) ; "valid token" ) ]
1285
+ #[ test_case( "!$TURBO_ROOT$/README.md" , Ok ( "!../../README.md" ) ; "negation" ) ]
1286
+ #[ test_case( "../$TURBO_ROOT$/config.txt" , Err ( "Cannot use '$TURBO_ROOT$' anywhere besides start of string." ) ; "invalid token" ) ]
1287
+ fn test_replace_turbo_root ( input : & ' static str , expected : Result < & str , & str > ) {
1288
+ let mut spanned_string = Spanned :: new ( UnescapedString :: from ( input) )
1289
+ . with_path ( Arc :: from ( "turbo.json" ) )
1290
+ . with_text ( format ! ( "\" {input}\" " ) )
1291
+ . with_range ( 1 ..( input. len ( ) ) ) ;
1292
+ let result = replace_turbo_root_token_in_string (
1293
+ & mut spanned_string,
1294
+ RelativeUnixPath :: new ( "../.." ) . unwrap ( ) ,
1295
+ ) ;
1296
+ let actual = match result {
1297
+ Ok ( ( ) ) => Ok ( spanned_string. as_inner ( ) . as_ref ( ) ) ,
1298
+ Err ( e) => Err ( e. to_string ( ) ) ,
1299
+ } ;
1300
+ assert_eq ! ( actual, expected. map_err( |s| s. to_owned( ) ) ) ;
1301
+ }
1194
1302
}
0 commit comments