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