@@ -4,10 +4,21 @@ use std::sync::Arc;
4
4
use anyhow:: Context ;
5
5
use forge_app:: { FsReadService , Infrastructure } ;
6
6
use forge_domain:: Workflow ;
7
+ use merge:: Merge ;
7
8
8
9
// Default forge.yaml content embedded in the binary
9
10
const DEFAULT_FORGE_WORKFLOW : & str = include_str ! ( "../../../forge.default.yaml" ) ;
10
11
12
+ /// Represents the possible sources of a workflow configuration
13
+ enum WorkflowSource < ' a > {
14
+ /// Explicitly provided path
15
+ ExplicitPath ( & ' a Path ) ,
16
+ /// Default configuration embedded in the binary
17
+ Default ,
18
+ /// Project-specific configuration in the current directory
19
+ ProjectConfig ,
20
+ }
21
+
11
22
/// A workflow loader to load the workflow from the given path.
12
23
/// It also resolves the internal paths specified in the workflow.
13
24
pub struct ForgeLoaderService < F > ( Arc < F > ) ;
@@ -19,31 +30,66 @@ impl<F> ForgeLoaderService<F> {
19
30
}
20
31
21
32
impl < F : Infrastructure > ForgeLoaderService < F > {
22
- /// loads the workflow from the given path.
23
- /// Loads the workflow from the given path if provided, otherwise tries to
24
- /// read from current directory's forge.yaml, and falls back to embedded
25
- /// default if neither exists.
33
+ /// Loads the workflow from the given path.
34
+ /// If a path is provided, uses that workflow directly without merging.
35
+ /// If no path is provided:
36
+ /// - Loads from current directory's forge.yaml merged with defaults (if
37
+ /// forge.yaml exists)
38
+ /// - Falls back to embedded default if forge.yaml doesn't exist
39
+ ///
40
+ /// When merging, the project's forge.yaml values take precedence over
41
+ /// defaults.
26
42
pub async fn load ( & self , path : Option < & Path > ) -> anyhow:: Result < Workflow > {
27
- let content = match path {
28
- Some ( path) => String :: from_utf8 ( self . 0 . file_read_service ( ) . read ( path) . await ?. to_vec ( ) ) ?,
29
- None => {
30
- let current_dir_config = Path :: new ( "forge.yaml" ) ;
31
- if current_dir_config. exists ( ) {
32
- String :: from_utf8 (
33
- self . 0
34
- . file_read_service ( )
35
- . read ( current_dir_config)
36
- . await ?
37
- . to_vec ( ) ,
38
- ) ?
39
- } else {
40
- DEFAULT_FORGE_WORKFLOW . to_string ( )
41
- }
42
- }
43
+ // Determine the workflow source
44
+ let source = match path {
45
+ Some ( path) => WorkflowSource :: ExplicitPath ( path) ,
46
+ None if Path :: new ( "forge.yaml" ) . exists ( ) => WorkflowSource :: ProjectConfig ,
47
+ None => WorkflowSource :: Default ,
43
48
} ;
44
49
45
- let workflow: Workflow =
46
- serde_yaml:: from_str ( & content) . with_context ( || "Failed to parse workflow" ) ?;
50
+ // Load the workflow based on its source
51
+ match source {
52
+ WorkflowSource :: ExplicitPath ( path) => self . load_from_explicit_path ( path) . await ,
53
+ WorkflowSource :: Default => self . load_default_workflow ( ) ,
54
+ WorkflowSource :: ProjectConfig => self . load_with_project_config ( ) . await ,
55
+ }
56
+ }
57
+
58
+ /// Loads a workflow from a specific file path
59
+ async fn load_from_explicit_path ( & self , path : & Path ) -> anyhow:: Result < Workflow > {
60
+ let content = String :: from_utf8 ( self . 0 . file_read_service ( ) . read ( path) . await ?. to_vec ( ) ) ?;
61
+ let workflow: Workflow = serde_yaml:: from_str ( & content)
62
+ . with_context ( || format ! ( "Failed to parse workflow from {}" , path. display( ) ) ) ?;
63
+ Ok ( workflow)
64
+ }
65
+
66
+ /// Loads the default workflow from embedded content
67
+ fn load_default_workflow ( & self ) -> anyhow:: Result < Workflow > {
68
+ let workflow: Workflow = serde_yaml:: from_str ( DEFAULT_FORGE_WORKFLOW )
69
+ . with_context ( || "Failed to parse default workflow" ) ?;
47
70
Ok ( workflow)
48
71
}
72
+
73
+ /// Loads workflow by merging project config with default workflow
74
+ async fn load_with_project_config ( & self ) -> anyhow:: Result < Workflow > {
75
+ let default_workflow = self . load_default_workflow ( ) ?;
76
+ let project_path = Path :: new ( "forge.yaml" ) ;
77
+
78
+ let project_content = String :: from_utf8 (
79
+ self . 0
80
+ . file_read_service ( )
81
+ . read ( project_path)
82
+ . await ?
83
+ . to_vec ( ) ,
84
+ ) ?;
85
+
86
+ let project_workflow: Workflow = serde_yaml:: from_str ( & project_content)
87
+ . with_context ( || "Failed to parse project workflow" ) ?;
88
+
89
+ // Merge workflows with project taking precedence
90
+ let mut merged_workflow = default_workflow;
91
+ merged_workflow. merge ( project_workflow) ;
92
+
93
+ Ok ( merged_workflow)
94
+ }
49
95
}
0 commit comments