@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,224 @@
1
+ use devalang_utils::logger::{LogLevel, Logger};
2
+
3
+ #[derive(Clone)]
4
+ struct InstalledAddon {
5
+ name: String,
6
+ addon_type: String,
7
+ }
8
+
9
+ pub async fn list_addons() -> Result<(), String> {
10
+ let deva_dir = devalang_utils::path::ensure_deva_dir()?;
11
+ let banks_dir = deva_dir.join("banks");
12
+ let plugins_dir = deva_dir.join("plugins");
13
+ let presets_dir = deva_dir.join("presets");
14
+ let templates_dir = deva_dir.join("templates");
15
+
16
+ let mut installed_addons = Vec::new();
17
+
18
+ if banks_dir.exists() {
19
+ if let Ok(entries) = std::fs::read_dir(&banks_dir) {
20
+ for entry in entries.flatten() {
21
+ if let Ok(file_type) = entry.file_type() {
22
+ if file_type.is_dir() {
23
+ if let Some(name) = entry.file_name().to_str() {
24
+ installed_addons.push(InstalledAddon {
25
+ name: name.to_string(),
26
+ addon_type: "bank".to_string(),
27
+ });
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ if plugins_dir.exists() {
36
+ if let Ok(entries) = std::fs::read_dir(&plugins_dir) {
37
+ for entry in entries.flatten() {
38
+ if let Ok(file_type) = entry.file_type() {
39
+ if file_type.is_dir() {
40
+ if let Some(name) = entry.file_name().to_str() {
41
+ installed_addons.push(InstalledAddon {
42
+ name: name.to_string(),
43
+ addon_type: "plugin".to_string(),
44
+ });
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ if presets_dir.exists() {
53
+ if let Ok(entries) = std::fs::read_dir(&presets_dir) {
54
+ for entry in entries.flatten() {
55
+ if let Ok(file_type) = entry.file_type() {
56
+ if file_type.is_dir() {
57
+ if let Some(name) = entry.file_name().to_str() {
58
+ installed_addons.push(InstalledAddon {
59
+ name: name.to_string(),
60
+ addon_type: "preset".to_string(),
61
+ });
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ if templates_dir.exists() {
70
+ if let Ok(entries) = std::fs::read_dir(&templates_dir) {
71
+ for entry in entries.flatten() {
72
+ if let Ok(file_type) = entry.file_type() {
73
+ if file_type.is_dir() {
74
+ if let Some(name) = entry.file_name().to_str() {
75
+ installed_addons.push(InstalledAddon {
76
+ name: name.to_string(),
77
+ addon_type: "template".to_string(),
78
+ });
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ let logger = Logger::new();
87
+
88
+ if installed_addons.is_empty() {
89
+ logger.log_message(LogLevel::Info, "No addon installed.");
90
+ } else {
91
+ let installed_banks = installed_addons.iter().filter(|a| a.addon_type == "bank");
92
+
93
+ let installed_plugins = installed_addons.iter().filter(|a| a.addon_type == "plugin");
94
+
95
+ let installed_presets = installed_addons.iter().filter(|a| a.addon_type == "preset");
96
+
97
+ let installed_templates = installed_addons
98
+ .iter()
99
+ .filter(|a| a.addon_type == "template");
100
+
101
+ if installed_banks.clone().count() > 0 {
102
+ let trace: Vec<String> = installed_addons
103
+ .iter()
104
+ .map(|a| format!("{}", a.name))
105
+ .collect();
106
+ let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
107
+
108
+ logger.log_message_with_trace(LogLevel::Info, "Installed banks :", trace);
109
+ }
110
+
111
+ if installed_plugins.clone().count() > 0 {
112
+ let trace: Vec<String> = installed_addons
113
+ .iter()
114
+ .map(|a| format!("{}", a.name))
115
+ .collect();
116
+ let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
117
+
118
+ logger.log_message_with_trace(LogLevel::Info, "Installed plugins :", trace);
119
+ }
120
+
121
+ if installed_presets.clone().count() > 0 {
122
+ let trace: Vec<String> = installed_addons
123
+ .iter()
124
+ .map(|a| format!("{}", a.name))
125
+ .collect();
126
+ let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
127
+
128
+ logger.log_message_with_trace(LogLevel::Info, "Installed presets :", trace);
129
+ }
130
+
131
+ if installed_templates.clone().count() > 0 {
132
+ let trace: Vec<String> = installed_addons
133
+ .iter()
134
+ .map(|a| format!("{}", a.name))
135
+ .collect();
136
+ let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
137
+
138
+ logger.log_message_with_trace(LogLevel::Info, "Installed templates :", trace);
139
+ }
140
+
141
+ if let Some(addon_not_in_config) = find_unused_addons(installed_addons.clone()) {
142
+ let trace: Vec<String> = addon_not_in_config
143
+ .iter()
144
+ .map(|a| format!("{} ({})", a.name, a.addon_type))
145
+ .collect();
146
+ let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
147
+
148
+ logger.log_message_with_trace(
149
+ LogLevel::Warning,
150
+ "Some addons are installed but not referenced in the .devalang configuration file :",
151
+ trace,
152
+ );
153
+ }
154
+
155
+ logger.log_message(
156
+ LogLevel::Success,
157
+ format!("Found {} installed addon(s).", installed_addons.len()).as_str(),
158
+ );
159
+ }
160
+
161
+ Ok(())
162
+ }
163
+
164
+ fn find_unused_addons(addons: Vec<InstalledAddon>) -> Option<Vec<InstalledAddon>> {
165
+ let mut unused_addons = Vec::new();
166
+
167
+ let config_path = match devalang_utils::path::get_devalang_config_path() {
168
+ Ok(path) => path,
169
+ Err(_) => return None,
170
+ };
171
+
172
+ let config = match crate::config::ops::load_config(Some(&config_path)) {
173
+ Some(cfg) => cfg,
174
+ None => return None,
175
+ };
176
+
177
+ let mut referenced_addons = Vec::new();
178
+
179
+ if let Some(banks) = config.banks {
180
+ for bank in banks {
181
+ if let Some(name) = bank.path.strip_prefix("devalang://bank/") {
182
+ referenced_addons.push((name.to_string(), "bank".to_string()));
183
+ }
184
+ }
185
+ }
186
+
187
+ if let Some(plugins) = config.plugins {
188
+ for plugin in plugins {
189
+ if let Some(name) = plugin.path.strip_prefix("devalang://plugin/") {
190
+ referenced_addons.push((name.to_string(), "plugin".to_string()));
191
+ }
192
+ }
193
+ }
194
+
195
+ // TODO: Enable when presets and templates are supported in config
196
+ // if let Some(presets) = config.presets {
197
+ // for preset in presets {
198
+ // if let Some(name) = preset.path.strip_prefix("devalang://preset/") {
199
+ // referenced_addons.push((name.to_string(), "preset".to_string()));
200
+ // }
201
+ // }
202
+ // }
203
+
204
+ // TODO: Enable when presets and templates are supported in config
205
+ // if let Some(templates) = config.templates {
206
+ // for template in templates {
207
+ // if let Some(name) = template.path.strip_prefix("devalang://template/") {
208
+ // referenced_addons.push((name.to_string(), "template".to_string()));
209
+ // }
210
+ // }
211
+ // }
212
+
213
+ for addon in addons {
214
+ if !referenced_addons.contains(&(addon.name.clone(), addon.addon_type.clone())) {
215
+ unused_addons.push(addon);
216
+ }
217
+ }
218
+
219
+ if unused_addons.is_empty() {
220
+ None
221
+ } else {
222
+ Some(unused_addons)
223
+ }
224
+ }
@@ -0,0 +1,124 @@
1
+ use crate::web::api::get_api_url;
2
+
3
+ #[derive(Debug, Clone)]
4
+ pub struct AddonToDownloadMetadata {
5
+ pub name: String,
6
+ pub publisher: String,
7
+ pub addon_type: devalang_types::AddonType,
8
+ }
9
+
10
+ pub async fn get_addon_publisher_from_api(slug: &str) -> Result<String, String> {
11
+ let api_url = get_api_url();
12
+
13
+ let request_url = format!("{}/v1/products/getBySlug/{}", api_url, slug);
14
+
15
+ let client: reqwest::Client = reqwest::Client::builder()
16
+ .build()
17
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
18
+
19
+ let resp = client
20
+ .get(&request_url)
21
+ .send()
22
+ .await
23
+ .map_err(|e| format!("Failed to receive response: {}", e))?;
24
+
25
+ let status = resp.status();
26
+ let body_text = resp
27
+ .text()
28
+ .await
29
+ .map_err(|e| format!("Failed to read response body: {}", e))?;
30
+
31
+ let json: serde_json::Value = match serde_json::from_str(&body_text) {
32
+ Ok(v) => v,
33
+ Err(_) => {
34
+ return Err(format!(
35
+ "Invalid JSON response (status {}): {}",
36
+ status, body_text
37
+ ));
38
+ }
39
+ };
40
+
41
+ let payload = json.get("payload");
42
+ let publisher = payload
43
+ .unwrap_or(&serde_json::Value::Null)
44
+ .get("publisher")
45
+ .and_then(|v| v.get("name"))
46
+ .and_then(|v| v.as_str())
47
+ .unwrap_or("unknown")
48
+ .to_string();
49
+
50
+ Ok(publisher)
51
+ }
52
+
53
+ pub async fn get_addon_from_api(slug: &str) -> Result<AddonToDownloadMetadata, String> {
54
+ let api_url = get_api_url();
55
+
56
+ let request_url = format!("{}/v1/products/getBySlug/{}", api_url, slug);
57
+
58
+ let client: reqwest::Client = reqwest::Client::builder()
59
+ .build()
60
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
61
+
62
+ let resp = client
63
+ .get(&request_url)
64
+ .send()
65
+ .await
66
+ .map_err(|e| format!("Failed to receive response: {}", e))?;
67
+
68
+ let status = resp.status();
69
+ let body_text = resp
70
+ .text()
71
+ .await
72
+ .map_err(|e| format!("Failed to read response body: {}", e))?;
73
+
74
+ let json: serde_json::Value = match serde_json::from_str(&body_text) {
75
+ Ok(v) => v,
76
+ Err(_) => {
77
+ return Err(format!(
78
+ "Invalid JSON response (status {}): {}",
79
+ status, body_text
80
+ ));
81
+ }
82
+ };
83
+
84
+ let payload = json.get("payload");
85
+ let addon_type = payload
86
+ .unwrap_or(&serde_json::Value::Null)
87
+ .get("addon")
88
+ .unwrap_or(&serde_json::Value::Null)
89
+ .get("addon_type")
90
+ .and_then(|v| v.as_str())
91
+ .unwrap_or("unknown");
92
+
93
+ let addon_type_enum = match addon_type {
94
+ "bank" => devalang_types::AddonType::Bank,
95
+ "plugin" => devalang_types::AddonType::Plugin,
96
+ "preset" => devalang_types::AddonType::Preset,
97
+ "template" => devalang_types::AddonType::Template,
98
+ _ => {
99
+ return Err(format!("Unknown addon type: {}", addon_type));
100
+ }
101
+ };
102
+
103
+ let addon_metadata = AddonToDownloadMetadata {
104
+ name: payload
105
+ .unwrap_or(&serde_json::Value::Null)
106
+ .get("addon")
107
+ .unwrap_or(&serde_json::Value::Null)
108
+ .get("name")
109
+ .and_then(|v| v.as_str())
110
+ .unwrap_or("unknown")
111
+ .to_string(),
112
+ publisher: payload
113
+ .unwrap_or(&serde_json::Value::Null)
114
+ .get("publisher")
115
+ .unwrap_or(&serde_json::Value::Null)
116
+ .get("name")
117
+ .and_then(|v| v.as_str())
118
+ .unwrap_or("unknown")
119
+ .to_string(),
120
+ addon_type: addon_type_enum,
121
+ };
122
+
123
+ Ok(addon_metadata)
124
+ }
@@ -0,0 +1,8 @@
1
+ pub mod commands;
2
+ pub mod download;
3
+ pub mod install;
4
+ pub mod list;
5
+ pub mod metadata;
6
+ pub mod remove;
7
+ pub mod update;
8
+ pub mod utils;
@@ -0,0 +1,271 @@
1
+ use crate::config::ops::load_config;
2
+ use devalang_core::config::driver::ProjectConfigExt;
3
+ use devalang_utils::path as path_utils;
4
+ use std::fs;
5
+
6
+ pub async fn remove_addon(name: String) -> Result<(), String> {
7
+ let deva_dir = path_utils::ensure_deva_dir()?;
8
+
9
+ // Helper to extract publisher from a slug like 'publisher.name'
10
+ let extract_publisher = |s: &str| s.splitn(2, '.').next().unwrap_or("").to_string();
11
+
12
+ // Try to find in config first (banks/plugins)
13
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
14
+ if let Some(mut config) = load_config(Some(&config_path)) {
15
+ // BANKS
16
+ if let Some(banks) = config.banks.as_mut() {
17
+ if let Some(pos) = banks.iter().position(|b| {
18
+ let slug = b.path.strip_prefix("devalang://bank/").unwrap_or(&b.path);
19
+ // accept exact slug, exact path, or match by local name suffix (publisher/name or publisher.name)
20
+ slug == name
21
+ || b.path == name
22
+ || slug.ends_with(&format!("/{}", name))
23
+ || slug.ends_with(&format!(".{}", name))
24
+ }) {
25
+ let entry = banks.remove(pos);
26
+ let slug = entry
27
+ .path
28
+ .strip_prefix("devalang://bank/")
29
+ .unwrap_or(&entry.path)
30
+ .to_string();
31
+
32
+ // parse publisher/name from slug (support 'publisher/name' or 'publisher.name')
33
+ let (publisher, local_name) = if slug.contains('/') {
34
+ let mut it = slug.splitn(2, '/');
35
+ (
36
+ it.next().unwrap().to_string(),
37
+ it.next().unwrap().to_string(),
38
+ )
39
+ } else if slug.contains('.') {
40
+ let mut it = slug.splitn(2, '.');
41
+ (
42
+ it.next().unwrap().to_string(),
43
+ it.next().unwrap().to_string(),
44
+ )
45
+ } else {
46
+ return Err(format!("Cannot parse bank slug '{}'", slug));
47
+ };
48
+
49
+ let local_path = deva_dir.join("banks").join(&publisher).join(&local_name);
50
+
51
+ if !local_path.exists() {
52
+ return Err(format!(
53
+ "Local files for bank '{}' not found at '{}', aborting",
54
+ slug,
55
+ local_path.display()
56
+ ));
57
+ }
58
+
59
+ fs::remove_dir_all(&local_path)
60
+ .map_err(|e| format!("Failed to remove addon files: {}", e))?;
61
+
62
+ if let Err(e) = config.write_config(&config) {
63
+ eprintln!("Warning: failed to write updated config: {}", e);
64
+ }
65
+
66
+ println!("✅ Bank '{}' removed (publisher '{}')", slug, publisher);
67
+ return Ok(());
68
+ }
69
+ }
70
+
71
+ // PLUGINS
72
+ if let Some(plugins) = config.plugins.as_mut() {
73
+ if let Some(pos) = plugins.iter().position(|p| {
74
+ let slug = p.path.strip_prefix("devalang://plugin/").unwrap_or(&p.path);
75
+ // accept exact slug, exact path, or match by local name suffix
76
+ slug == name
77
+ || p.path == name
78
+ || slug.ends_with(&format!("/{}", name))
79
+ || slug.ends_with(&format!(".{}", name))
80
+ }) {
81
+ let entry = plugins.remove(pos);
82
+ let slug = entry
83
+ .path
84
+ .strip_prefix("devalang://plugin/")
85
+ .unwrap_or(&entry.path)
86
+ .to_string();
87
+
88
+ // parse publisher/name from slug (support 'publisher/name' or 'publisher.name')
89
+ let (publisher, local_name) = if slug.contains('/') {
90
+ let mut it = slug.splitn(2, '/');
91
+ (
92
+ it.next().unwrap().to_string(),
93
+ it.next().unwrap().to_string(),
94
+ )
95
+ } else if slug.contains('.') {
96
+ let mut it = slug.splitn(2, '.');
97
+ (
98
+ it.next().unwrap().to_string(),
99
+ it.next().unwrap().to_string(),
100
+ )
101
+ } else {
102
+ return Err(format!("Cannot parse plugin slug '{}'", slug));
103
+ };
104
+
105
+ let local_path = deva_dir.join("plugins").join(&publisher).join(&local_name);
106
+
107
+ if !local_path.exists() {
108
+ return Err(format!(
109
+ "Local files for plugin '{}' not found at '{}', aborting",
110
+ slug,
111
+ local_path.display()
112
+ ));
113
+ }
114
+
115
+ fs::remove_dir_all(&local_path)
116
+ .map_err(|e| format!("Failed to remove addon files: {}", e))?;
117
+
118
+ if let Err(e) = config.write_config(&config) {
119
+ eprintln!("Warning: failed to write updated config: {}", e);
120
+ }
121
+
122
+ println!("✅ Plugin '{}' removed (publisher '{}')", slug, publisher);
123
+ return Ok(());
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ // Not found in config: search filesystem under .deva and infer type
130
+ let dirs = ["banks", "plugins", "presets", "templates"];
131
+ for &d in &dirs {
132
+ let folder = deva_dir.join(d);
133
+ if !folder.exists() {
134
+ continue;
135
+ }
136
+
137
+ // If name looks like a slug (contains '.' or '/'), parse publisher and name and try candidate paths
138
+ if name.contains('.') || name.contains('/') {
139
+ let (publisher, local_name) = if name.contains('/') {
140
+ let mut it = name.splitn(2, '/');
141
+ (
142
+ it.next().unwrap().to_string(),
143
+ it.next().unwrap().to_string(),
144
+ )
145
+ } else {
146
+ let mut it = name.splitn(2, '.');
147
+ (
148
+ it.next().unwrap().to_string(),
149
+ it.next().unwrap().to_string(),
150
+ )
151
+ };
152
+
153
+ let candidate1 = folder.join(&publisher).join(&local_name);
154
+ let candidate2 = folder.join(format!("{}.{}", publisher, local_name));
155
+ let candidate3 = folder.join(&name);
156
+
157
+ let candidate = if candidate1.exists() {
158
+ candidate1
159
+ } else if candidate2.exists() {
160
+ candidate2
161
+ } else {
162
+ candidate3
163
+ };
164
+
165
+ if candidate.exists() {
166
+ fs::remove_dir_all(&candidate)
167
+ .map_err(|e| format!("Failed to remove addon files: {}", e))?;
168
+
169
+ // also attempt to remove from config if possible
170
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
171
+ if let Some(mut config) = load_config(Some(&config_path)) {
172
+ match d {
173
+ "banks" => {
174
+ if let Some(banks) = config.banks.as_mut() {
175
+ let pattern1 =
176
+ format!("devalang://bank/{}/{}", publisher, local_name);
177
+ let pattern2 =
178
+ format!("devalang://bank/{}.{}", publisher, local_name);
179
+ banks.retain(|b| {
180
+ b.path != pattern1
181
+ && b.path != pattern2
182
+ && !b.path.ends_with(&local_name)
183
+ });
184
+ }
185
+ }
186
+ "plugins" => {
187
+ if let Some(plugins) = config.plugins.as_mut() {
188
+ let pattern1 =
189
+ format!("devalang://plugin/{}/{}", publisher, local_name);
190
+ let pattern2 =
191
+ format!("devalang://plugin/{}.{}", publisher, local_name);
192
+ plugins.retain(|p| {
193
+ p.path != pattern1
194
+ && p.path != pattern2
195
+ && !p.path.ends_with(&local_name)
196
+ });
197
+ }
198
+ }
199
+ _ => {}
200
+ }
201
+
202
+ if let Err(e) = config.write_config(&config) {
203
+ eprintln!("Warning: failed to write updated config: {}", e);
204
+ }
205
+ }
206
+ }
207
+
208
+ println!(
209
+ "✅ Addon '{}/{}' removed from .deva/{} (publisher '{}')",
210
+ publisher, local_name, d, publisher
211
+ );
212
+ return Ok(());
213
+ }
214
+ }
215
+
216
+ // Otherwise, scan directory entries: match exact name or suffix '.name'
217
+ if let Ok(entries) = fs::read_dir(&folder) {
218
+ for entry in entries.flatten() {
219
+ if let Ok(file_type) = entry.file_type() {
220
+ if !file_type.is_dir() {
221
+ continue;
222
+ }
223
+ }
224
+ let file_name = entry.file_name();
225
+ let file_name = file_name.to_string_lossy();
226
+ if file_name == name || file_name.ends_with(&format!(".{}", name)) {
227
+ let slug = file_name.to_string();
228
+ let publisher = extract_publisher(&slug);
229
+ let path = entry.path();
230
+ fs::remove_dir_all(&path)
231
+ .map_err(|e| format!("Failed to remove addon files: {}", e))?;
232
+
233
+ // try to remove from config when banks/plugins
234
+ if let Ok(config_path) = path_utils::get_devalang_config_path() {
235
+ if let Some(mut config) = load_config(Some(&config_path)) {
236
+ match d {
237
+ "banks" => {
238
+ if let Some(banks) = config.banks.as_mut() {
239
+ banks.retain(|b| {
240
+ b.path != format!("devalang://bank/{}", slug)
241
+ });
242
+ }
243
+ }
244
+ "plugins" => {
245
+ if let Some(plugins) = config.plugins.as_mut() {
246
+ plugins.retain(|p| {
247
+ p.path != format!("devalang://plugin/{}", slug)
248
+ });
249
+ }
250
+ }
251
+ _ => {}
252
+ }
253
+
254
+ if let Err(e) = config.write_config(&config) {
255
+ eprintln!("Warning: failed to write updated config: {}", e);
256
+ }
257
+ }
258
+ }
259
+
260
+ println!(
261
+ "✅ Addon '{}' removed from .deva/{} (publisher '{}')",
262
+ slug, d, publisher
263
+ );
264
+ return Ok(());
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ Err(format!("Addon '{}' not found", name))
271
+ }