@devaloop/devalang 0.0.1-beta.2 → 0.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/Cargo.toml +84 -81
  2. package/README.md +3 -2
  3. package/docs/CHANGELOG.md +41 -0
  4. package/docs/ROADMAP.md +3 -3
  5. package/examples/chain.deva +19 -0
  6. package/examples/plugin.deva +10 -10
  7. package/examples/routing.deva +23 -0
  8. package/out-tsc/bin/project-version.json +6 -0
  9. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -8
  10. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  11. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  12. package/package.json +23 -10
  13. package/project-version.json +3 -3
  14. package/rust/bindings/Cargo.toml +9 -0
  15. package/rust/bindings/src/lib.rs +86 -0
  16. package/rust/cli/addon/commands.rs +35 -0
  17. package/rust/cli/addon/download.rs +234 -0
  18. package/rust/cli/addon/install.rs +33 -0
  19. package/rust/cli/addon/list.rs +224 -0
  20. package/rust/cli/addon/metadata.rs +124 -0
  21. package/rust/cli/addon/mod.rs +8 -0
  22. package/rust/cli/addon/remove.rs +271 -0
  23. package/rust/cli/addon/update.rs +305 -0
  24. package/rust/cli/{install/addon.rs → addon/utils.rs} +109 -118
  25. package/rust/cli/build/commands.rs +153 -153
  26. package/rust/cli/build/process.rs +165 -165
  27. package/rust/cli/check/mod.rs +208 -208
  28. package/rust/cli/discover/commands.rs +275 -253
  29. package/rust/cli/discover/config.rs +109 -111
  30. package/rust/cli/discover/fs.rs +19 -19
  31. package/rust/cli/discover/install.rs +214 -103
  32. package/rust/cli/discover/metadata.rs +48 -48
  33. package/rust/cli/discover/mod.rs +5 -5
  34. package/rust/cli/me/commands.rs +52 -0
  35. package/rust/cli/me/mod.rs +1 -0
  36. package/rust/cli/mod.rs +12 -12
  37. package/rust/cli/parser.rs +30 -69
  38. package/rust/cli/play/commands.rs +375 -375
  39. package/rust/cli/play/process.rs +159 -159
  40. package/rust/core/audio/engine/driver.rs +19 -2
  41. package/rust/core/audio/engine/export.rs +169 -169
  42. package/rust/core/audio/engine/mod.rs +56 -56
  43. package/rust/core/audio/engine/notes/dsp.rs +88 -85
  44. package/rust/core/audio/engine/notes/mod.rs +53 -44
  45. package/rust/core/audio/engine/notes/params.rs +294 -294
  46. package/rust/core/audio/engine/sample/insert.rs +148 -47
  47. package/rust/core/audio/engine/sample/mod.rs +40 -40
  48. package/rust/core/audio/engine/sample/padding.rs +170 -170
  49. package/rust/core/audio/evaluator/condition.rs +61 -61
  50. package/rust/core/audio/evaluator/numeric.rs +152 -152
  51. package/rust/core/audio/evaluator/rhs.rs +16 -16
  52. package/rust/core/audio/evaluator/string_expr.rs +94 -94
  53. package/rust/core/audio/interpreter/driver.rs +574 -574
  54. package/rust/core/audio/interpreter/mod.rs +2 -2
  55. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +9 -5
  56. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -384
  57. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  58. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +1 -0
  59. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +66 -11
  60. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -3
  61. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -192
  62. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -24
  63. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -116
  64. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -97
  65. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -100
  66. package/rust/core/audio/interpreter/statements/automate.rs +16 -16
  67. package/rust/core/audio/interpreter/statements/call.rs +31 -1
  68. package/rust/core/audio/interpreter/statements/condition.rs +72 -72
  69. package/rust/core/audio/interpreter/statements/function.rs +24 -24
  70. package/rust/core/audio/interpreter/statements/let_.rs +36 -36
  71. package/rust/core/audio/interpreter/statements/load.rs +17 -17
  72. package/rust/core/audio/interpreter/statements/loop_.rs +115 -115
  73. package/rust/core/audio/interpreter/statements/spawn.rs +51 -2
  74. package/rust/core/audio/interpreter/statements/trigger.rs +242 -239
  75. package/rust/core/audio/loader/trigger.rs +98 -98
  76. package/rust/core/audio/player.rs +70 -70
  77. package/rust/core/audio/special/mod.rs +9 -9
  78. package/rust/core/builder/mod.rs +129 -129
  79. package/rust/core/debugger/lexer.rs +27 -27
  80. package/rust/core/debugger/logs.rs +52 -52
  81. package/rust/core/debugger/preprocessor.rs +27 -27
  82. package/rust/core/debugger/store.rs +38 -38
  83. package/rust/core/lexer/driver.rs +59 -59
  84. package/rust/core/lexer/handler/arrow.rs +82 -82
  85. package/rust/core/lexer/handler/at.rs +21 -21
  86. package/rust/core/lexer/handler/brace.rs +41 -41
  87. package/rust/core/lexer/handler/colon.rs +21 -21
  88. package/rust/core/lexer/handler/comment.rs +30 -30
  89. package/rust/core/lexer/handler/dot.rs +21 -21
  90. package/rust/core/lexer/handler/driver.rs +337 -337
  91. package/rust/core/lexer/handler/identifier.rs +47 -47
  92. package/rust/core/lexer/handler/indent.rs +66 -66
  93. package/rust/core/lexer/handler/mod.rs +15 -15
  94. package/rust/core/lexer/handler/newline.rs +23 -23
  95. package/rust/core/lexer/handler/number.rs +31 -31
  96. package/rust/core/lexer/handler/operator.rs +46 -46
  97. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  98. package/rust/core/lexer/handler/slash.rs +21 -21
  99. package/rust/core/lexer/handler/string.rs +63 -63
  100. package/rust/core/lexer/mod.rs +3 -3
  101. package/rust/core/mod.rs +9 -9
  102. package/rust/core/parser/driver/block.rs +111 -111
  103. package/rust/core/parser/driver/cursor.rs +82 -82
  104. package/rust/core/parser/driver/driver_impl.rs +21 -1
  105. package/rust/core/parser/driver/mod.rs +6 -6
  106. package/rust/core/parser/driver/parse_array.rs +120 -120
  107. package/rust/core/parser/driver/parse_map.rs +247 -223
  108. package/rust/core/parser/driver/parser.rs +160 -160
  109. package/rust/core/parser/handler/arrow_call.rs +65 -14
  110. package/rust/core/parser/handler/identifier/synth.rs +171 -135
  111. package/rust/core/parser/handler/mod.rs +9 -9
  112. package/rust/core/parser/handler/pattern.rs +24 -1
  113. package/rust/core/plugin/loader.rs +137 -137
  114. package/rust/core/plugin/mod.rs +2 -2
  115. package/rust/core/plugin/runner/non_wasm.rs +481 -297
  116. package/rust/core/plugin/runner/wasm32.rs +1 -0
  117. package/rust/core/preprocessor/loader/inject.rs +313 -278
  118. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -110
  119. package/rust/core/preprocessor/loader/mod.rs +235 -235
  120. package/rust/core/preprocessor/module.rs +55 -55
  121. package/rust/core/preprocessor/processor/handlers.rs +107 -107
  122. package/rust/core/preprocessor/resolver/bank.rs +49 -49
  123. package/rust/core/preprocessor/resolver/call.rs +124 -124
  124. package/rust/core/preprocessor/resolver/condition.rs +95 -95
  125. package/rust/core/preprocessor/resolver/driver.rs +324 -324
  126. package/rust/core/preprocessor/resolver/function.rs +69 -69
  127. package/rust/core/preprocessor/resolver/group.rs +122 -122
  128. package/rust/core/preprocessor/resolver/let_.rs +32 -32
  129. package/rust/core/preprocessor/resolver/loop_.rs +318 -318
  130. package/rust/core/preprocessor/resolver/mod.rs +16 -16
  131. package/rust/core/preprocessor/resolver/pattern.rs +95 -83
  132. package/rust/core/preprocessor/resolver/spawn.rs +99 -99
  133. package/rust/core/preprocessor/resolver/synth.rs +54 -54
  134. package/rust/core/preprocessor/resolver/tempo.rs +48 -48
  135. package/rust/core/preprocessor/resolver/trigger.rs +116 -116
  136. package/rust/core/preprocessor/resolver/value.rs +176 -176
  137. package/rust/core/store/global.rs +57 -57
  138. package/rust/lib.rs +323 -323
  139. package/rust/macros/Cargo.toml +14 -0
  140. package/rust/macros/src/lib.rs +52 -0
  141. package/rust/main.rs +311 -142
  142. package/rust/types/Cargo.toml +1 -1
  143. package/rust/types/src/addons.rs +3 -1
  144. package/rust/types/src/config.rs +1 -3
  145. package/rust/utils/Cargo.toml +5 -2
  146. package/rust/utils/src/file.rs +397 -14
  147. package/rust/utils/src/path.rs +31 -2
  148. package/rust/utils/src/version.rs +38 -7
  149. package/rust/web/auth.rs +5 -0
  150. package/rust/web/forge.rs +5 -0
  151. package/rust/web/mod.rs +5 -3
  152. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  153. package/rust/cli/bank/api.rs +0 -122
  154. package/rust/cli/bank/commands.rs +0 -306
  155. package/rust/cli/bank/mod.rs +0 -29
  156. package/rust/cli/install/bank.rs +0 -72
  157. package/rust/cli/install/commands.rs +0 -35
  158. package/rust/cli/install/mod.rs +0 -4
  159. package/rust/cli/install/plugin.rs +0 -80
@@ -1,118 +1,109 @@
1
- use crate::{
2
- cli::install::{bank::install_bank, plugin::install_plugin},
3
- config::settings::get_user_config,
4
- web::api::get_api_url,
5
- };
6
- use devalang_types::AddonType;
7
- use std::path::Path;
8
-
9
- pub async fn install_addon(
10
- addon_type: AddonType,
11
- name: &str,
12
- target_dir: &Path,
13
- ) -> Result<(), String> {
14
- match addon_type {
15
- AddonType::Bank => install_bank(name, target_dir).await,
16
- AddonType::Plugin => install_plugin(name, target_dir).await,
17
- AddonType::Preset => Err("Preset installation not implemented".into()),
18
- AddonType::Template => Err("Template installation not implemented".into()),
19
- }
20
- }
21
-
22
- pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result<String, String> {
23
- let api_url = get_api_url();
24
-
25
- use devalang_utils::logger::LogLevel;
26
- use devalang_utils::logger::Logger;
27
-
28
- // Require an authenticated user for addon installation: token must be present and non-empty
29
- let stored_token_opt = get_user_config().and_then(|cfg| {
30
- let t = cfg.session.clone();
31
- if t.trim().is_empty() { None } else { Some(t) }
32
- });
33
-
34
- if stored_token_opt.is_none() {
35
- let logger = Logger::new();
36
- let msg = "Authentication required run `devalang login` to authenticate";
37
- logger.log_message(LogLevel::Error, msg);
38
- return Err("Authentication required: run 'devalang login' to authenticate".to_string());
39
- }
40
-
41
- let request_url = if let Some(token) = &stored_token_opt {
42
- format!(
43
- "{}/v1/assets/url?type={}&slug={}&token={}",
44
- api_url,
45
- match addon_type {
46
- AddonType::Bank => "bank",
47
- AddonType::Plugin => "plugin",
48
- AddonType::Preset => "preset",
49
- AddonType::Template => "template",
50
- },
51
- slug,
52
- token
53
- )
54
- } else {
55
- format!(
56
- "{}/v1/assets/url?type={}&slug={}",
57
- api_url,
58
- match addon_type {
59
- AddonType::Bank => "bank",
60
- AddonType::Plugin => "plugin",
61
- AddonType::Preset => "preset",
62
- AddonType::Template => "template",
63
- },
64
- slug
65
- )
66
- };
67
-
68
- let mut headers = reqwest::header::HeaderMap::new();
69
- if let Some(token) = stored_token_opt {
70
- headers.insert(
71
- "Authorization",
72
- format!("Bearer {}", token).parse().unwrap(),
73
- );
74
- }
75
-
76
- let client: reqwest::Client = reqwest::Client::builder()
77
- .default_headers(headers)
78
- .build()
79
- .map_err(|_| "Failed to build HTTP client".to_string())?;
80
-
81
- let resp = client
82
- .get(&request_url)
83
- .send()
84
- .await
85
- .map_err(|e| format!("Failed to receive response: {}", e))?;
86
-
87
- let status = resp.status();
88
- let body_text = resp
89
- .text()
90
- .await
91
- .map_err(|e| format!("Failed to read response body: {}", e))?;
92
-
93
- // Try to parse JSON; if parsing fails, return body for diagnostics
94
- let json: serde_json::Value = match serde_json::from_str(&body_text) {
95
- Ok(v) => v,
96
- Err(_) => {
97
- return Err(format!(
98
- "Invalid JSON response (status {}): {}",
99
- status, body_text
100
- ));
101
- }
102
- };
103
-
104
- // Extract payload.url safely
105
- let signed_url_opt = json
106
- .get("payload")
107
- .and_then(|p| p.get("url"))
108
- .and_then(|u| u.as_str())
109
- .map(|s| s.to_string());
110
-
111
- if let Some(signed_url) = signed_url_opt {
112
- Ok(signed_url)
113
- } else {
114
- // Provide detailed diagnostics to help user understand why it's null
115
- let err_msg = format!("API returned no URL (status {}): {}", status, body_text);
116
- Err(err_msg)
117
- }
118
- }
1
+ use crate::{config::settings::get_user_config, web::forge::get_forge_api_url};
2
+ use devalang_types::AddonType;
3
+
4
+ pub async fn ask_api_for_signed_url(
5
+ addon_type: AddonType,
6
+ publisher: String,
7
+ slug: &str,
8
+ ) -> Result<String, String> {
9
+ let forge_api_url = get_forge_api_url();
10
+
11
+ use devalang_utils::logger::LogLevel;
12
+ use devalang_utils::logger::Logger;
13
+
14
+ // Require an authenticated user for addon installation: token must be present and non-empty
15
+ let stored_token_opt = get_user_config().and_then(|cfg| {
16
+ let t = cfg.session.clone();
17
+ if t.trim().is_empty() { None } else { Some(t) }
18
+ });
19
+
20
+ if stored_token_opt.is_none() {
21
+ let logger = Logger::new();
22
+ let msg = "Authentication required run `devalang login` to authenticate";
23
+ logger.log_message(LogLevel::Error, msg);
24
+ return Err("Authentication required: run 'devalang login' to authenticate".to_string());
25
+ }
26
+
27
+ // Build request URL. If publisher is empty, omit the publisher param so the API
28
+ // can resolve publisher by addon name itself (server-side lookup).
29
+ let kind = match addon_type {
30
+ AddonType::Bank => "bank",
31
+ AddonType::Plugin => "plugin",
32
+ AddonType::Preset => "preset",
33
+ AddonType::Template => "template",
34
+ };
35
+
36
+ let request_url = if let Some(token) = &stored_token_opt {
37
+ if publisher.trim().is_empty() {
38
+ format!(
39
+ "{}/v1/addon/url?type={}&slug={}&token={}",
40
+ forge_api_url, kind, slug, token
41
+ )
42
+ } else {
43
+ format!(
44
+ "{}/v1/addon/url?type={}&publisher={}&slug={}&token={}",
45
+ forge_api_url, kind, publisher, slug, token
46
+ )
47
+ }
48
+ } else {
49
+ if publisher.trim().is_empty() {
50
+ format!("{}/v1/addon/url?type={}&slug={}", forge_api_url, kind, slug)
51
+ } else {
52
+ format!(
53
+ "{}/v1/addon/url?type={}&publisher={}&slug={}",
54
+ forge_api_url, kind, publisher, slug
55
+ )
56
+ }
57
+ };
58
+
59
+ let mut headers = reqwest::header::HeaderMap::new();
60
+ if let Some(token) = stored_token_opt {
61
+ headers.insert(
62
+ "Authorization",
63
+ format!("Bearer {}", token).parse().unwrap(),
64
+ );
65
+ }
66
+
67
+ let client: reqwest::Client = reqwest::Client::builder()
68
+ .default_headers(headers)
69
+ .build()
70
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
71
+
72
+ let resp = client
73
+ .get(&request_url)
74
+ .send()
75
+ .await
76
+ .map_err(|e| format!("Failed to receive response: {}", e))?;
77
+
78
+ let status = resp.status();
79
+ let body_text = resp
80
+ .text()
81
+ .await
82
+ .map_err(|e| format!("Failed to read response body: {}", e))?;
83
+
84
+ // Try to parse JSON; if parsing fails, return body for diagnostics
85
+ let json: serde_json::Value = match serde_json::from_str(&body_text) {
86
+ Ok(v) => v,
87
+ Err(_) => {
88
+ return Err(format!(
89
+ "Invalid JSON response (status {}): {}",
90
+ status, body_text
91
+ ));
92
+ }
93
+ };
94
+
95
+ // Extract payload.url safely
96
+ let signed_url_opt = json
97
+ .get("payload")
98
+ .and_then(|p| p.get("url"))
99
+ .and_then(|u| u.as_str())
100
+ .map(|s| s.to_string());
101
+
102
+ if let Some(signed_url) = signed_url_opt {
103
+ Ok(signed_url)
104
+ } else {
105
+ // Provide detailed diagnostics to help user understand why it's null
106
+ let err_msg = format!("API returned no URL (status {}): {}", status, body_text);
107
+ Err(err_msg)
108
+ }
109
+ }
@@ -1,153 +1,153 @@
1
- use crate::config::driver::ProjectConfig;
2
- use devalang_utils::logger::{LogLevel, Logger};
3
- use devalang_utils::path::find_entry_file;
4
- use devalang_utils::watcher::watch_directory;
5
-
6
- #[cfg(feature = "cli")]
7
- pub fn handle_build_command(
8
- config: Option<ProjectConfig>,
9
- entry: Option<String>,
10
- output: Option<String>,
11
- output_format: Vec<crate::cli::parser::OutputFormat>,
12
- audio_format: crate::cli::parser::AudioFormat,
13
- sample_rate: u32,
14
- watch: bool,
15
- debug: bool,
16
- compress: bool,
17
- ) -> Result<(), String> {
18
- // determine fetched values from config or CLI
19
- let fetched_entry = if entry.is_none() {
20
- config
21
- .as_ref()
22
- .and_then(|c| c.defaults.entry.clone())
23
- .unwrap_or_default()
24
- } else {
25
- entry.clone().unwrap_or_default()
26
- };
27
-
28
- let fetched_output = if output.is_none() {
29
- config
30
- .as_ref()
31
- .and_then(|c| c.defaults.output.clone())
32
- .unwrap_or_default()
33
- } else {
34
- output.clone().unwrap_or_default()
35
- };
36
-
37
- let fetched_watch = if watch {
38
- watch
39
- } else {
40
- config
41
- .as_ref()
42
- .and_then(|c| c.defaults.watch)
43
- .unwrap_or(false)
44
- };
45
-
46
- let logger = Logger::new();
47
-
48
- // Determine final audio_format: prefer CLI, else config default if present
49
- let mut final_audio_format = audio_format;
50
- if let Some(cfg) = config.as_ref() {
51
- if let Some(af) = cfg.defaults.audio_format.as_ref() {
52
- // Only override if CLI provided an empty/placeholder — clap provides a default, so we only override when CLI wasn't explicit (rare).
53
- // We'll accept values: "wav16", "wav24", "wav32" (case-insensitive)
54
- let af_low = af.to_lowercase();
55
- final_audio_format = match af_low.as_str() {
56
- "wav24" => crate::cli::parser::AudioFormat::Wav24,
57
- "wav32" => crate::cli::parser::AudioFormat::Wav32,
58
- _ => crate::cli::parser::AudioFormat::Wav16,
59
- };
60
- }
61
- }
62
-
63
- // Determine final output_format: prefer CLI vector, else config default list
64
- let mut final_output_format: Vec<crate::cli::parser::OutputFormat> = output_format.clone();
65
- if final_output_format.is_empty() {
66
- if let Some(cfg) = config.as_ref() {
67
- if let Some(ofs) = cfg.defaults.output_format.as_ref() {
68
- for s in ofs {
69
- match s.to_lowercase().as_str() {
70
- "mid" | "midi" => {
71
- final_output_format.push(crate::cli::parser::OutputFormat::Mid);
72
- }
73
- _ => final_output_format.push(crate::cli::parser::OutputFormat::Wav),
74
- }
75
- }
76
- }
77
- }
78
- }
79
-
80
- if fetched_entry.is_empty() {
81
- logger.log_message(
82
- LogLevel::Error,
83
- "Entry path is not specified. Please provide a valid entry path.",
84
- );
85
- return Err("missing entry path".to_string());
86
- }
87
- if fetched_output.is_empty() {
88
- logger.log_message(
89
- LogLevel::Error,
90
- "Output directory is not specified. Please provide a valid output directory.",
91
- );
92
- return Err("missing output directory".to_string());
93
- }
94
-
95
- let entry_file = match find_entry_file(&fetched_entry) {
96
- Some(p) => p,
97
- None => {
98
- logger.log_message(
99
- LogLevel::Error,
100
- &format!("❌ index.deva not found in directory: {}", fetched_entry),
101
- );
102
- return Err("index.deva not found".to_string());
103
- }
104
- };
105
-
106
- // SECTION Begin build
107
- if fetched_watch {
108
- let _ = crate::cli::build::process::process_build(
109
- entry_file.clone(),
110
- fetched_output.clone(),
111
- final_output_format.clone(),
112
- final_audio_format,
113
- sample_rate,
114
- debug,
115
- compress,
116
- );
117
-
118
- logger.log_message(
119
- LogLevel::Watcher,
120
- &format!("Watching for changes in '{}'...", fetched_entry),
121
- );
122
-
123
- watch_directory(entry_file.clone(), move || {
124
- logger.log_message(LogLevel::Watcher, "Detected changes, re-building...");
125
-
126
- let _ = crate::cli::build::process::process_build(
127
- entry_file.clone(),
128
- fetched_output.clone(),
129
- final_output_format.clone(),
130
- final_audio_format,
131
- sample_rate,
132
- debug,
133
- compress,
134
- );
135
- })
136
- .unwrap();
137
- } else {
138
- let res = crate::cli::build::process::process_build(
139
- entry_file,
140
- fetched_output,
141
- final_output_format,
142
- final_audio_format,
143
- sample_rate,
144
- debug,
145
- compress,
146
- );
147
- if let Err(e) = res {
148
- return Err(e);
149
- }
150
- }
151
-
152
- Ok(())
153
- }
1
+ use crate::config::driver::ProjectConfig;
2
+ use devalang_utils::logger::{LogLevel, Logger};
3
+ use devalang_utils::path::find_entry_file;
4
+ use devalang_utils::watcher::watch_directory;
5
+
6
+ #[cfg(feature = "cli")]
7
+ pub fn handle_build_command(
8
+ config: Option<ProjectConfig>,
9
+ entry: Option<String>,
10
+ output: Option<String>,
11
+ output_format: Vec<crate::cli::parser::OutputFormat>,
12
+ audio_format: crate::cli::parser::AudioFormat,
13
+ sample_rate: u32,
14
+ watch: bool,
15
+ debug: bool,
16
+ compress: bool,
17
+ ) -> Result<(), String> {
18
+ // determine fetched values from config or CLI
19
+ let fetched_entry = if entry.is_none() {
20
+ config
21
+ .as_ref()
22
+ .and_then(|c| c.defaults.entry.clone())
23
+ .unwrap_or_default()
24
+ } else {
25
+ entry.clone().unwrap_or_default()
26
+ };
27
+
28
+ let fetched_output = if output.is_none() {
29
+ config
30
+ .as_ref()
31
+ .and_then(|c| c.defaults.output.clone())
32
+ .unwrap_or_default()
33
+ } else {
34
+ output.clone().unwrap_or_default()
35
+ };
36
+
37
+ let fetched_watch = if watch {
38
+ watch
39
+ } else {
40
+ config
41
+ .as_ref()
42
+ .and_then(|c| c.defaults.watch)
43
+ .unwrap_or(false)
44
+ };
45
+
46
+ let logger = Logger::new();
47
+
48
+ // Determine final audio_format: prefer CLI, else config default if present
49
+ let mut final_audio_format = audio_format;
50
+ if let Some(cfg) = config.as_ref() {
51
+ if let Some(af) = cfg.defaults.audio_format.as_ref() {
52
+ // Only override if CLI provided an empty/placeholder — clap provides a default, so we only override when CLI wasn't explicit (rare).
53
+ // We'll accept values: "wav16", "wav24", "wav32" (case-insensitive)
54
+ let af_low = af.to_lowercase();
55
+ final_audio_format = match af_low.as_str() {
56
+ "wav24" => crate::cli::parser::AudioFormat::Wav24,
57
+ "wav32" => crate::cli::parser::AudioFormat::Wav32,
58
+ _ => crate::cli::parser::AudioFormat::Wav16,
59
+ };
60
+ }
61
+ }
62
+
63
+ // Determine final output_format: prefer CLI vector, else config default list
64
+ let mut final_output_format: Vec<crate::cli::parser::OutputFormat> = output_format.clone();
65
+ if final_output_format.is_empty() {
66
+ if let Some(cfg) = config.as_ref() {
67
+ if let Some(ofs) = cfg.defaults.output_format.as_ref() {
68
+ for s in ofs {
69
+ match s.to_lowercase().as_str() {
70
+ "mid" | "midi" => {
71
+ final_output_format.push(crate::cli::parser::OutputFormat::Mid);
72
+ }
73
+ _ => final_output_format.push(crate::cli::parser::OutputFormat::Wav),
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ if fetched_entry.is_empty() {
81
+ logger.log_message(
82
+ LogLevel::Error,
83
+ "Entry path is not specified. Please provide a valid entry path.",
84
+ );
85
+ return Err("missing entry path".to_string());
86
+ }
87
+ if fetched_output.is_empty() {
88
+ logger.log_message(
89
+ LogLevel::Error,
90
+ "Output directory is not specified. Please provide a valid output directory.",
91
+ );
92
+ return Err("missing output directory".to_string());
93
+ }
94
+
95
+ let entry_file = match find_entry_file(&fetched_entry) {
96
+ Some(p) => p,
97
+ None => {
98
+ logger.log_message(
99
+ LogLevel::Error,
100
+ &format!("❌ index.deva not found in directory: {}", fetched_entry),
101
+ );
102
+ return Err("index.deva not found".to_string());
103
+ }
104
+ };
105
+
106
+ // SECTION Begin build
107
+ if fetched_watch {
108
+ let _ = crate::cli::build::process::process_build(
109
+ entry_file.clone(),
110
+ fetched_output.clone(),
111
+ final_output_format.clone(),
112
+ final_audio_format,
113
+ sample_rate,
114
+ debug,
115
+ compress,
116
+ );
117
+
118
+ logger.log_message(
119
+ LogLevel::Watcher,
120
+ &format!("Watching for changes in '{}'...", fetched_entry),
121
+ );
122
+
123
+ watch_directory(entry_file.clone(), move || {
124
+ logger.log_message(LogLevel::Watcher, "Detected changes, re-building...");
125
+
126
+ let _ = crate::cli::build::process::process_build(
127
+ entry_file.clone(),
128
+ fetched_output.clone(),
129
+ final_output_format.clone(),
130
+ final_audio_format,
131
+ sample_rate,
132
+ debug,
133
+ compress,
134
+ );
135
+ })
136
+ .unwrap();
137
+ } else {
138
+ let res = crate::cli::build::process::process_build(
139
+ entry_file,
140
+ fetched_output,
141
+ final_output_format,
142
+ final_audio_format,
143
+ sample_rate,
144
+ debug,
145
+ compress,
146
+ );
147
+ if let Err(e) = res {
148
+ return Err(e);
149
+ }
150
+ }
151
+
152
+ Ok(())
153
+ }