1
1
use clap:: { Parser , Subcommand } ;
2
2
use devenv_tasks:: {
3
- Config , RunMode , TaskConfig , TasksUi , VerbosityLevel , signal_handler:: SignalHandler ,
3
+ Config , RunMode , SudoContext , TaskConfig , TasksUi , VerbosityLevel ,
4
+ signal_handler:: SignalHandler ,
4
5
} ;
5
- use std:: env;
6
+ use std:: { env, fs , path :: PathBuf } ;
6
7
7
8
#[ derive( Parser ) ]
8
9
#[ clap( author, version, about) ]
@@ -19,6 +20,14 @@ enum Command {
19
20
20
21
#[ clap( long, value_enum, default_value_t = RunMode :: Single , help = "The execution mode for tasks (affects dependency resolution)" ) ]
21
22
mode : RunMode ,
23
+
24
+ #[ clap(
25
+ long,
26
+ value_parser,
27
+ env = "DEVENV_TASK_FILE" ,
28
+ help = "Path to a JSON file containing task definitions"
29
+ ) ]
30
+ task_file : Option < PathBuf > ,
22
31
} ,
23
32
Export {
24
33
#[ clap( ) ]
@@ -28,6 +37,14 @@ enum Command {
28
37
29
38
#[ tokio:: main]
30
39
async fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
40
+ // Detect and handle sudo.
41
+ // Drop privileges immediately to avoid creating any files as root.
42
+ let sudo_context = SudoContext :: detect ( ) ;
43
+ if let Some ( ref ctx) = sudo_context {
44
+ ctx. drop_privileges ( )
45
+ . map_err ( |e| format ! ( "Failed to drop privileges: {}" , e) ) ?;
46
+ }
47
+
31
48
let args = Args :: parse ( ) ;
32
49
33
50
// Determine verbosity level from DEVENV_CMDLINE
@@ -46,19 +63,45 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
46
63
47
64
// Keeping backwards compatibility for existing scripts that might set DEVENV_TASKS_QUIET
48
65
if let Ok ( quiet_var) = env:: var ( "DEVENV_TASKS_QUIET" )
49
- && ( quiet_var == "true" || quiet_var == "1" ) {
50
- verbosity = VerbosityLevel :: Quiet ;
51
- }
66
+ && ( quiet_var == "true" || quiet_var == "1" )
67
+ {
68
+ verbosity = VerbosityLevel :: Quiet ;
69
+ }
52
70
53
71
match args. command {
54
- Command :: Run { roots, mode } => {
55
- let tasks_json = env:: var ( "DEVENV_TASKS" ) ?;
56
- let tasks: Vec < TaskConfig > = serde_json:: from_str ( & tasks_json) ?;
72
+ Command :: Run {
73
+ roots,
74
+ mode,
75
+ task_file,
76
+ } => {
77
+ let tasks: Vec < TaskConfig > = {
78
+ let task_source = || {
79
+ task_file
80
+ . as_ref ( )
81
+ . map ( |p| format ! ( "tasks file at {}" , p. display( ) ) )
82
+ . unwrap_or_else ( || "DEVENV_TASKS" . to_string ( ) )
83
+ } ;
57
84
85
+ let data = env:: var ( "DEVENV_TASKS" ) . or_else ( |_| {
86
+ task_file
87
+ . as_ref ( )
88
+ . ok_or_else ( || {
89
+ "No task file specified and DEVENV_TASKS environment variable not set"
90
+ . to_string ( )
91
+ } )
92
+ . and_then ( |path| {
93
+ fs:: read_to_string ( path)
94
+ . map_err ( |e| format ! ( "Failed to read {}: {e}" , task_source( ) ) )
95
+ } )
96
+ } ) ?;
97
+ serde_json:: from_str ( & data)
98
+ . map_err ( |e| format ! ( "Failed to parse {} as JSON: {e}" , task_source( ) ) ) ?
99
+ } ;
58
100
let config = Config {
59
101
tasks,
60
102
roots,
61
103
run_mode : mode,
104
+ sudo_context : sudo_context. clone ( ) ,
62
105
} ;
63
106
64
107
// Create a global signal handler
0 commit comments