@devaloop/devalang 0.0.1-beta.1 → 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 (207) hide show
  1. package/.devalang +9 -10
  2. package/Cargo.toml +84 -80
  3. package/README.md +10 -7
  4. package/docs/CHANGELOG.md +83 -0
  5. package/docs/ROADMAP.md +6 -2
  6. package/docs/TODO.md +3 -14
  7. package/examples/bus.deva +10 -0
  8. package/examples/chain.deva +19 -0
  9. package/examples/effect.deva +2 -0
  10. package/examples/filter.deva +11 -0
  11. package/examples/lfo.deva +9 -0
  12. package/examples/plugin.deva +10 -10
  13. package/examples/routing.deva +23 -0
  14. package/examples/synth.deva +11 -1
  15. package/examples/synth_types.deva +17 -0
  16. package/out-tsc/bin/project-version.json +6 -0
  17. package/out-tsc/core/functions/index.d.ts +5 -0
  18. package/out-tsc/core/functions/index.js +11 -0
  19. package/out-tsc/pkg/devalang_core.d.ts +2 -0
  20. package/out-tsc/pkg/devalang_core.js +17 -2
  21. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +1 -0
  22. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  23. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  24. package/package.json +23 -10
  25. package/project-version.json +3 -3
  26. package/rust/bindings/Cargo.toml +9 -0
  27. package/rust/bindings/src/lib.rs +86 -0
  28. package/rust/cli/addon/commands.rs +35 -0
  29. package/rust/cli/addon/download.rs +234 -0
  30. package/rust/cli/addon/install.rs +33 -0
  31. package/rust/cli/addon/list.rs +224 -0
  32. package/rust/cli/addon/metadata.rs +124 -0
  33. package/rust/cli/addon/mod.rs +8 -0
  34. package/rust/cli/addon/remove.rs +271 -0
  35. package/rust/cli/addon/update.rs +305 -0
  36. package/rust/cli/{install/addon.rs → addon/utils.rs} +34 -43
  37. package/rust/cli/build/commands.rs +153 -103
  38. package/rust/cli/build/mod.rs +2 -2
  39. package/rust/cli/build/process.rs +165 -146
  40. package/rust/cli/check/mod.rs +208 -208
  41. package/rust/cli/discover/commands.rs +53 -31
  42. package/rust/cli/discover/config.rs +2 -4
  43. package/rust/cli/discover/install.rs +139 -28
  44. package/rust/cli/discover/metadata.rs +3 -3
  45. package/rust/cli/login/commands.rs +124 -124
  46. package/rust/cli/me/commands.rs +52 -0
  47. package/rust/cli/me/mod.rs +1 -0
  48. package/rust/cli/mod.rs +2 -2
  49. package/rust/cli/parser.rs +76 -70
  50. package/rust/cli/play/commands.rs +375 -324
  51. package/rust/cli/play/mod.rs +5 -5
  52. package/rust/cli/play/process.rs +159 -150
  53. package/rust/cli/play/realtime.rs +91 -91
  54. package/rust/cli/telemetry/commands.rs +22 -22
  55. package/rust/cli/telemetry/event_creator.rs +80 -80
  56. package/rust/cli/telemetry/mod.rs +3 -3
  57. package/rust/cli/telemetry/send.rs +51 -51
  58. package/rust/cli/template/commands.rs +69 -69
  59. package/rust/config/driver.rs +112 -103
  60. package/rust/config/mod.rs +3 -3
  61. package/rust/config/ops.rs +26 -26
  62. package/rust/config/settings.rs +101 -101
  63. package/rust/core/audio/engine/driver.rs +237 -0
  64. package/rust/core/audio/engine/export.rs +169 -0
  65. package/rust/core/audio/engine/helpers.rs +178 -170
  66. package/rust/core/audio/engine/mod.rs +56 -7
  67. package/rust/core/audio/engine/notes/dsp.rs +88 -0
  68. package/rust/core/audio/engine/notes/mod.rs +53 -0
  69. package/rust/core/audio/engine/notes/params.rs +294 -0
  70. package/rust/core/audio/engine/sample/insert.rs +300 -0
  71. package/rust/core/audio/engine/sample/mod.rs +40 -0
  72. package/rust/core/audio/engine/sample/padding.rs +170 -0
  73. package/rust/core/audio/evaluator/condition.rs +61 -0
  74. package/rust/core/audio/evaluator/mod.rs +9 -0
  75. package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +152 -310
  76. package/rust/core/audio/evaluator/rhs.rs +16 -0
  77. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  78. package/rust/core/audio/interpreter/driver.rs +574 -542
  79. package/rust/core/audio/interpreter/mod.rs +2 -14
  80. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +179 -0
  81. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -0
  82. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  83. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +3 -0
  84. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +371 -0
  85. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  86. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  87. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  88. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  89. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  90. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  91. package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +2 -4
  92. package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +36 -5
  93. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -71
  94. package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +24 -26
  95. package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +36 -38
  96. package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +17 -19
  97. package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +115 -114
  98. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  99. package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
  100. package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +54 -4
  101. package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
  102. package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +242 -239
  103. package/rust/core/audio/loader/trigger.rs +98 -97
  104. package/rust/core/audio/mod.rs +6 -7
  105. package/rust/core/audio/special/easing.rs +189 -189
  106. package/rust/core/audio/special/env.rs +45 -45
  107. package/rust/core/audio/special/math.rs +134 -134
  108. package/rust/core/audio/special/modulator.rs +143 -143
  109. package/rust/core/builder/mod.rs +129 -86
  110. package/rust/core/debugger/{module.rs → logs.rs} +52 -55
  111. package/rust/core/debugger/mod.rs +30 -30
  112. package/rust/core/debugger/store.rs +38 -40
  113. package/rust/core/error/mod.rs +269 -269
  114. package/rust/core/lexer/driver.rs +2 -4
  115. package/rust/core/mod.rs +9 -10
  116. package/rust/core/parser/driver/block.rs +111 -0
  117. package/rust/core/parser/driver/cursor.rs +82 -0
  118. package/rust/core/parser/driver/driver_impl.rs +159 -0
  119. package/rust/core/parser/driver/mod.rs +6 -0
  120. package/rust/core/parser/driver/parse_array.rs +120 -0
  121. package/rust/core/parser/driver/parse_map.rs +247 -0
  122. package/rust/core/parser/driver/parser.rs +160 -0
  123. package/rust/core/parser/handler/arrow_call.rs +90 -15
  124. package/rust/core/parser/handler/at.rs +279 -279
  125. package/rust/core/parser/handler/bank.rs +104 -104
  126. package/rust/core/parser/handler/condition.rs +83 -83
  127. package/rust/core/parser/handler/dot.rs +148 -148
  128. package/rust/core/parser/handler/identifier/automate.rs +254 -254
  129. package/rust/core/parser/handler/identifier/call.rs +91 -91
  130. package/rust/core/parser/handler/identifier/emit.rs +70 -70
  131. package/rust/core/parser/handler/identifier/function.rs +113 -113
  132. package/rust/core/parser/handler/identifier/group.rs +89 -89
  133. package/rust/core/parser/handler/identifier/let_.rs +173 -173
  134. package/rust/core/parser/handler/identifier/mod.rs +55 -55
  135. package/rust/core/parser/handler/identifier/on.rs +107 -107
  136. package/rust/core/parser/handler/identifier/print.rs +49 -49
  137. package/rust/core/parser/handler/identifier/sleep.rs +96 -43
  138. package/rust/core/parser/handler/identifier/spawn.rs +91 -91
  139. package/rust/core/parser/handler/identifier/synth.rs +39 -3
  140. package/rust/core/parser/handler/loop_.rs +194 -194
  141. package/rust/core/parser/handler/pattern.rs +25 -2
  142. package/rust/core/parser/handler/tempo.rs +105 -57
  143. package/rust/core/parser/statement.rs +10 -11
  144. package/rust/core/plugin/loader.rs +137 -137
  145. package/rust/core/plugin/runner/mod.rs +11 -0
  146. package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +206 -72
  147. package/rust/core/plugin/runner/wasm32.rs +44 -0
  148. package/rust/core/preprocessor/loader/inject.rs +313 -0
  149. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  150. package/rust/core/preprocessor/loader/mod.rs +235 -0
  151. package/rust/core/preprocessor/module.rs +55 -60
  152. package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +107 -114
  153. package/rust/core/preprocessor/processor/mod.rs +1 -0
  154. package/rust/core/preprocessor/resolver/function.rs +69 -69
  155. package/rust/core/preprocessor/resolver/group.rs +122 -94
  156. package/rust/core/preprocessor/resolver/pattern.rs +14 -2
  157. package/rust/core/store/global.rs +57 -61
  158. package/rust/core/store/mod.rs +1 -5
  159. package/rust/lib.rs +323 -308
  160. package/rust/macros/Cargo.toml +14 -0
  161. package/rust/macros/src/lib.rs +52 -0
  162. package/rust/main.rs +336 -143
  163. package/rust/types/Cargo.toml +1 -1
  164. package/rust/types/src/addons.rs +57 -55
  165. package/rust/types/src/config.rs +82 -74
  166. package/rust/types/src/lib.rs +15 -12
  167. package/rust/types/src/plugin.rs +20 -0
  168. package/rust/types/src/store.rs +139 -0
  169. package/rust/types/src/telemetry.rs +85 -85
  170. package/rust/utils/Cargo.toml +5 -2
  171. package/rust/utils/src/file.rs +477 -94
  172. package/rust/utils/src/first_usage.rs +97 -97
  173. package/rust/utils/src/lib.rs +9 -9
  174. package/rust/utils/src/logger.rs +200 -200
  175. package/rust/utils/src/path.rs +158 -88
  176. package/rust/utils/src/signature.rs +41 -41
  177. package/rust/utils/src/spinner.rs +20 -20
  178. package/rust/utils/src/version.rs +58 -27
  179. package/rust/utils/src/watcher.rs +46 -46
  180. package/rust/web/api.rs +5 -5
  181. package/rust/web/auth.rs +5 -0
  182. package/rust/web/cdn.rs +34 -34
  183. package/rust/web/forge.rs +5 -0
  184. package/rust/web/mod.rs +2 -0
  185. package/tests/integration.rs +21 -21
  186. package/typescript/core/functions/index.ts +11 -0
  187. package/typescript/pkg/devalang_core.ts +20 -4
  188. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  189. package/rust/cli/bank/api.rs +0 -122
  190. package/rust/cli/bank/commands.rs +0 -275
  191. package/rust/cli/bank/mod.rs +0 -29
  192. package/rust/cli/install/bank.rs +0 -53
  193. package/rust/cli/install/commands.rs +0 -35
  194. package/rust/cli/install/mod.rs +0 -4
  195. package/rust/cli/install/plugin.rs +0 -61
  196. package/rust/core/audio/engine/sample.rs +0 -366
  197. package/rust/core/audio/engine/synth.rs +0 -325
  198. package/rust/core/audio/interpreter/arrow_call.rs +0 -311
  199. package/rust/core/audio/renderer.rs +0 -54
  200. package/rust/core/parser/driver.rs +0 -584
  201. package/rust/core/preprocessor/loader.rs +0 -637
  202. package/rust/core/store/export.rs +0 -28
  203. package/rust/core/store/function.rs +0 -40
  204. package/rust/core/store/import.rs +0 -28
  205. package/rust/core/store/variable.rs +0 -51
  206. package/rust/core/utils/mod.rs +0 -1
  207. package/rust/core/utils/path.rs +0 -37
@@ -0,0 +1,305 @@
1
+ use crate::config::ops::load_config;
2
+ use crate::{
3
+ cli::addon::{
4
+ download::download_addon,
5
+ metadata::{get_addon_from_api, get_addon_publisher_from_api},
6
+ },
7
+ web::cdn::get_cdn_url,
8
+ };
9
+ use devalang_core::config::driver::ProjectConfigExt;
10
+ use devalang_types::AddonType;
11
+ use devalang_utils::path as path_utils;
12
+ use std::fs;
13
+ use toml::Value as TomlValue;
14
+
15
+ #[derive(serde::Deserialize)]
16
+ pub struct AddonVersion {
17
+ pub version: String,
18
+ }
19
+
20
+ async fn fetch_latest_version(
21
+ addon_type: AddonType,
22
+ addon_name: String,
23
+ ) -> Result<AddonVersion, Box<dyn std::error::Error>> {
24
+ let cdn_url = get_cdn_url();
25
+
26
+ let addon_type = match addon_type {
27
+ AddonType::Bank => "bank",
28
+ AddonType::Plugin => "plugin",
29
+ AddonType::Preset => "preset",
30
+ AddonType::Template => "template",
31
+ };
32
+
33
+ let publisher_identifier = get_addon_publisher_from_api(&addon_name)
34
+ .await
35
+ .unwrap_or("unknown".to_string());
36
+
37
+ let url = format!(
38
+ "{}/{}/{}/{}/version",
39
+ cdn_url, addon_type, publisher_identifier, addon_name
40
+ );
41
+
42
+ let response = reqwest::get(url).await?;
43
+
44
+ if !response.status().is_success() {
45
+ return Err(format!("❌ Failed to fetch version: HTTP {}", response.status()).into());
46
+ }
47
+
48
+ let bytes = response.bytes().await?;
49
+
50
+ let version: AddonVersion = serde_json::from_slice(&bytes)?;
51
+
52
+ Ok(version)
53
+ }
54
+
55
+ pub async fn update_addon(slug: String) -> Result<(), String> {
56
+ let addon_metadata = get_addon_from_api(&slug).await?;
57
+ let deva_dir = path_utils::ensure_deva_dir()?;
58
+
59
+ // Represent the addon as "<publisher>.<addon>" for user-visible names
60
+ let publisher_and_name = format!("{}.{}", addon_metadata.publisher, addon_metadata.name);
61
+
62
+ match addon_metadata.addon_type {
63
+ devalang_types::AddonType::Bank => {
64
+ match fetch_latest_version(
65
+ addon_metadata.addon_type.clone(),
66
+ addon_metadata.name.clone(),
67
+ )
68
+ .await
69
+ {
70
+ Ok(latest) => {
71
+ // Determine local version from bank.toml if available
72
+ let local_bank_path = deva_dir
73
+ .join("banks")
74
+ .join(&addon_metadata.publisher)
75
+ .join(&addon_metadata.name);
76
+ let local_version = if local_bank_path.exists() {
77
+ let bank_toml = local_bank_path.join("bank.toml");
78
+ if bank_toml.exists() {
79
+ if let Ok(content) = fs::read_to_string(&bank_toml) {
80
+ if let Ok(parsed) =
81
+ toml::from_str::<devalang_types::BankFile>(&content)
82
+ {
83
+ parsed.bank.version
84
+ } else {
85
+ String::new()
86
+ }
87
+ } else {
88
+ String::new()
89
+ }
90
+ } else {
91
+ String::new()
92
+ }
93
+ } else {
94
+ String::new()
95
+ };
96
+
97
+ if local_version != latest.version {
98
+ println!(
99
+ "Updating bank '{}' from '{}' to '{}'...",
100
+ publisher_and_name, local_version, latest.version
101
+ );
102
+
103
+ // remove existing folder if present
104
+ let bank_dir = deva_dir
105
+ .join("banks")
106
+ .join(&addon_metadata.publisher)
107
+ .join(&addon_metadata.name);
108
+ if bank_dir.exists() {
109
+ fs::remove_dir_all(&bank_dir)
110
+ .map_err(|e| format!("Failed to remove old bank files: {}", e))?;
111
+ }
112
+
113
+ // download new (use publisher.addon as the external identifier)
114
+ download_addon(&publisher_and_name, &addon_metadata).await?;
115
+
116
+ // update config version when present
117
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
118
+ if let Some(mut config) = load_config(Some(&config_path)) {
119
+ if let Some(banks) = config.banks.as_mut() {
120
+ for bank in banks.iter_mut() {
121
+ let name_in_path = bank
122
+ .path
123
+ .strip_prefix("devalang://bank/")
124
+ .unwrap_or(&bank.path);
125
+ }
126
+
127
+ if let Err(e) = config.write_config(&config) {
128
+ eprintln!("Warning: failed to write updated config: {}", e);
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ println!(
135
+ "✅ Bank '{}' updated to version '{}'",
136
+ publisher_and_name, latest.version
137
+ );
138
+ } else {
139
+ println!(
140
+ "Bank '{}' is already up-to-date (version {})",
141
+ publisher_and_name, latest.version
142
+ );
143
+ }
144
+ }
145
+ Err(e) => {
146
+ return Err(format!(
147
+ "Failed to fetch latest version for bank '{}': {}",
148
+ publisher_and_name, e
149
+ ));
150
+ }
151
+ }
152
+ }
153
+
154
+ devalang_types::AddonType::Plugin => {
155
+ // Try to fetch latest version via CDN API; if available compare, otherwise fallback to redownload
156
+ match fetch_latest_version(
157
+ addon_metadata.addon_type.clone(),
158
+ addon_metadata.name.clone(),
159
+ )
160
+ .await
161
+ {
162
+ Ok(latest) => {
163
+ // Determine local plugin version by reading plugin.toml from preferred or fallback layout
164
+ let preferred = deva_dir.join("plugins").join(format!(
165
+ "{}/{}",
166
+ addon_metadata.publisher, addon_metadata.name
167
+ ));
168
+ let fallback = deva_dir
169
+ .join("plugins")
170
+ .join(&addon_metadata.publisher)
171
+ .join(&addon_metadata.name);
172
+
173
+ let mut local_version = String::new();
174
+ for candidate in [&preferred, &fallback] {
175
+ let toml_path = candidate.join("plugin.toml");
176
+ if toml_path.exists() {
177
+ if let Ok(content) = fs::read_to_string(&toml_path) {
178
+ if let Ok(value) = toml::from_str::<TomlValue>(&content) {
179
+ if let Some(v) = value
180
+ .get("plugin")
181
+ .and_then(|p| p.get("version"))
182
+ .and_then(|s| s.as_str())
183
+ {
184
+ local_version = v.to_string();
185
+ }
186
+ }
187
+ }
188
+ break;
189
+ }
190
+ }
191
+
192
+ if local_version != latest.version {
193
+ println!(
194
+ "Updating plugin '{}' from '{}' to '{}'...",
195
+ publisher_and_name, local_version, latest.version
196
+ );
197
+
198
+ // remove any existing layout
199
+ if preferred.exists() {
200
+ fs::remove_dir_all(&preferred)
201
+ .map_err(|e| format!("Failed to remove old plugin files: {}", e))?;
202
+ }
203
+ if fallback.exists() {
204
+ fs::remove_dir_all(&fallback)
205
+ .map_err(|e| format!("Failed to remove old plugin files: {}", e))?;
206
+ }
207
+
208
+ // download new
209
+ download_addon(&publisher_and_name, &addon_metadata).await?;
210
+
211
+ // update config version when present
212
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
213
+ if let Some(mut config) = load_config(Some(&config_path)) {
214
+ if let Some(plugins) = config.plugins.as_mut() {
215
+ for p in plugins.iter_mut() {
216
+ let name_in_path = p
217
+ .path
218
+ .strip_prefix("devalang://plugin/")
219
+ .unwrap_or(&p.path);
220
+ }
221
+
222
+ if let Err(e) = config.write_config(&config) {
223
+ eprintln!("Warning: failed to write updated config: {}", e);
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ println!(
230
+ "✅ Plugin '{}' updated to version '{}'",
231
+ publisher_and_name, latest.version
232
+ );
233
+ } else {
234
+ println!(
235
+ "Plugin '{}' is already up-to-date (version {})",
236
+ publisher_and_name, latest.version
237
+ );
238
+ }
239
+ }
240
+ Err(_) => {
241
+ // Fallback: redownload everything
242
+ println!(
243
+ "No version info for plugin '{}', redownloading to ensure latest.",
244
+ publisher_and_name
245
+ );
246
+
247
+ let plugin_dir = deva_dir
248
+ .join("plugins")
249
+ .join(&addon_metadata.publisher)
250
+ .join(&addon_metadata.name);
251
+ if plugin_dir.exists() {
252
+ fs::remove_dir_all(&plugin_dir)
253
+ .map_err(|e| format!("Failed to remove old plugin files: {}", e))?;
254
+ }
255
+
256
+ download_addon(&publisher_and_name, &addon_metadata).await?;
257
+
258
+ // clear version in config (unknown)
259
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
260
+ if let Some(mut config) = load_config(Some(&config_path)) {
261
+ if let Some(plugins) = config.plugins.as_mut() {
262
+ for p in plugins.iter_mut() {
263
+ let name_in_path = p
264
+ .path
265
+ .strip_prefix("devalang://plugin/")
266
+ .unwrap_or(&p.path);
267
+ }
268
+
269
+ if let Err(e) = config.write_config(&config) {
270
+ eprintln!("Warning: failed to write updated config: {}", e);
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ println!("✅ Plugin '{}' updated", publisher_and_name);
277
+ }
278
+ }
279
+ }
280
+
281
+ devalang_types::AddonType::Preset | devalang_types::AddonType::Template => {
282
+ println!(
283
+ "Update for presets/templates is not yet implemented; reinstalling to be safe."
284
+ );
285
+ let target_dir = match addon_metadata.addon_type {
286
+ devalang_types::AddonType::Preset => deva_dir.join("presets"),
287
+ _ => deva_dir.join("templates"),
288
+ };
289
+
290
+ let candidate = target_dir
291
+ .join(&addon_metadata.publisher)
292
+ .join(&addon_metadata.name);
293
+ if candidate.exists() {
294
+ fs::remove_dir_all(&candidate)
295
+ .map_err(|e| format!("Failed to remove old files: {}", e))?;
296
+ }
297
+
298
+ // use publisher.addon representation for user-facing messaging
299
+ download_addon(&publisher_and_name, &addon_metadata).await?;
300
+ println!("✅ Addon '{}' updated (reinstalled)", publisher_and_name);
301
+ }
302
+ }
303
+
304
+ Ok(())
305
+ }
@@ -1,26 +1,12 @@
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
- };
1
+ use crate::{config::settings::get_user_config, web::forge::get_forge_api_url};
6
2
  use devalang_types::AddonType;
7
- use std::path::Path;
8
3
 
9
- pub async fn install_addon(
4
+ pub async fn ask_api_for_signed_url(
10
5
  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();
6
+ publisher: String,
7
+ slug: &str,
8
+ ) -> Result<String, String> {
9
+ let forge_api_url = get_forge_api_url();
24
10
 
25
11
  use devalang_utils::logger::LogLevel;
26
12
  use devalang_utils::logger::Logger;
@@ -38,31 +24,36 @@ pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result
38
24
  return Err("Authentication required: run 'devalang login' to authenticate".to_string());
39
25
  }
40
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
+
41
36
  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
- )
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
+ }
54
48
  } 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
- )
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
+ }
66
57
  };
67
58
 
68
59
  let mut headers = reqwest::header::HeaderMap::new();
@@ -1,103 +1,153 @@
1
- use crate::{config::driver::ProjectConfig, core::utils::path::find_entry_file};
2
- use devalang_utils::logger::{LogLevel, Logger};
3
- use devalang_utils::watcher::watch_directory;
4
-
5
- #[cfg(feature = "cli")]
6
- pub fn handle_build_command(
7
- config: Option<ProjectConfig>,
8
- entry: Option<String>,
9
- output: Option<String>,
10
- watch: bool,
11
- debug: bool,
12
- compress: bool,
13
- ) -> Result<(), String> {
14
- let fetched_entry = if entry.is_none() {
15
- config
16
- .as_ref()
17
- .and_then(|c| c.defaults.entry.clone())
18
- .unwrap_or_default()
19
- } else {
20
- entry.clone().unwrap_or_default()
21
- };
22
-
23
- let fetched_output = if output.is_none() {
24
- config
25
- .as_ref()
26
- .and_then(|c| c.defaults.output.clone())
27
- .unwrap_or_default()
28
- } else {
29
- output.clone().unwrap_or_default()
30
- };
31
-
32
- let fetched_watch = if watch {
33
- watch
34
- } else {
35
- config
36
- .as_ref()
37
- .and_then(|c| c.defaults.watch)
38
- .unwrap_or(false)
39
- };
40
-
41
- let logger = Logger::new();
42
-
43
- if fetched_entry.is_empty() {
44
- logger.log_message(
45
- LogLevel::Error,
46
- "Entry path is not specified. Please provide a valid entry path.",
47
- );
48
- return Err("missing entry path".to_string());
49
- }
50
- if fetched_output.is_empty() {
51
- logger.log_message(
52
- LogLevel::Error,
53
- "Output directory is not specified. Please provide a valid output directory.",
54
- );
55
- return Err("missing output directory".to_string());
56
- }
57
-
58
- let entry_file = match find_entry_file(&fetched_entry) {
59
- Some(p) => p,
60
- None => {
61
- logger.log_message(
62
- LogLevel::Error,
63
- &format!("❌ index.deva not found in directory: {}", fetched_entry),
64
- );
65
- return Err("index.deva not found".to_string());
66
- }
67
- };
68
-
69
- // SECTION Begin build
70
- if fetched_watch {
71
- let _ = crate::cli::build::process::process_build(
72
- entry_file.clone(),
73
- fetched_output.clone(),
74
- debug,
75
- compress,
76
- );
77
-
78
- logger.log_message(
79
- LogLevel::Watcher,
80
- &format!("Watching for changes in '{}'...", fetched_entry),
81
- );
82
-
83
- watch_directory(entry_file.clone(), move || {
84
- logger.log_message(LogLevel::Watcher, "Detected changes, re-building...");
85
-
86
- let _ = crate::cli::build::process::process_build(
87
- entry_file.clone(),
88
- fetched_output.clone(),
89
- debug,
90
- compress,
91
- );
92
- })
93
- .unwrap();
94
- } else {
95
- let res =
96
- crate::cli::build::process::process_build(entry_file, fetched_output, debug, compress);
97
- if let Err(e) = res {
98
- return Err(e);
99
- }
100
- }
101
-
102
- Ok(())
103
- }
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,2 +1,2 @@
1
- pub mod commands;
2
- pub mod process;
1
+ pub mod commands;
2
+ pub mod process;