@devaloop/devalang 0.0.1-alpha.9 → 0.0.1-beta.2

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 (322) hide show
  1. package/.cargo/config.toml +2 -0
  2. package/.devalang +6 -1
  3. package/.github/workflows/ci.yml +103 -0
  4. package/Cargo.toml +81 -48
  5. package/README.md +137 -154
  6. package/docs/CHANGELOG.md +428 -1
  7. package/docs/CONTRIBUTING.md +101 -0
  8. package/docs/ROADMAP.md +14 -7
  9. package/docs/TODO.md +16 -15
  10. package/examples/automation.deva +42 -0
  11. package/examples/bank.deva +7 -0
  12. package/examples/bus.deva +10 -0
  13. package/examples/duration.deva +9 -0
  14. package/examples/effect.deva +2 -0
  15. package/examples/events.deva +12 -0
  16. package/examples/filter.deva +11 -0
  17. package/examples/function.deva +15 -0
  18. package/examples/index.deva +57 -12
  19. package/examples/lfo.deva +9 -0
  20. package/examples/loop.deva +5 -12
  21. package/examples/pattern.deva +8 -0
  22. package/examples/plugin.deva +16 -0
  23. package/examples/synth.deva +11 -1
  24. package/examples/synth_types.deva +17 -0
  25. package/examples/variables.deva +1 -1
  26. package/out-tsc/bin/index.d.ts +2 -0
  27. package/out-tsc/bin/index.js +51 -7
  28. package/out-tsc/core/functions/index.d.ts +42 -0
  29. package/out-tsc/core/functions/index.js +87 -0
  30. package/out-tsc/core/index.d.ts +6 -0
  31. package/out-tsc/core/index.js +22 -0
  32. package/out-tsc/core/types/index.d.ts +4 -0
  33. package/out-tsc/core/types/index.js +20 -0
  34. package/out-tsc/core/types/plugin.d.ts +18 -0
  35. package/out-tsc/core/types/plugin.js +2 -0
  36. package/out-tsc/core/types/result.d.ts +27 -0
  37. package/out-tsc/core/types/result.js +2 -0
  38. package/out-tsc/core/types/statement.d.ts +106 -0
  39. package/out-tsc/core/types/statement.js +2 -0
  40. package/out-tsc/core/types/value.d.ts +43 -0
  41. package/out-tsc/core/types/value.js +2 -0
  42. package/out-tsc/index.d.ts +7 -0
  43. package/out-tsc/index.js +42 -1
  44. package/out-tsc/pkg/devalang_core.d.ts +15 -0
  45. package/out-tsc/pkg/devalang_core.js +65 -0
  46. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +34 -0
  47. package/out-tsc/scripts/copy-wasm-dts.d.ts +1 -0
  48. package/out-tsc/scripts/copy-wasm-dts.js +73 -0
  49. package/out-tsc/scripts/postinstall.d.ts +1 -0
  50. package/out-tsc/scripts/postinstall.js +83 -0
  51. package/out-tsc/scripts/version/bump.d.ts +1 -0
  52. package/out-tsc/scripts/version/fetch.d.ts +1 -0
  53. package/out-tsc/scripts/version/index.d.ts +1 -0
  54. package/out-tsc/scripts/version/sync.d.ts +1 -0
  55. package/package.json +28 -7
  56. package/project-version.json +4 -4
  57. package/rust/cli/bank/api.rs +122 -0
  58. package/rust/cli/bank/commands.rs +306 -0
  59. package/rust/cli/bank/mod.rs +29 -0
  60. package/rust/cli/build/commands.rs +153 -0
  61. package/rust/cli/build/mod.rs +2 -0
  62. package/rust/cli/build/process.rs +165 -0
  63. package/rust/cli/check/mod.rs +208 -0
  64. package/rust/cli/discover/commands.rs +253 -0
  65. package/rust/cli/discover/config.rs +111 -0
  66. package/rust/cli/discover/fs.rs +19 -0
  67. package/rust/cli/discover/install.rs +103 -0
  68. package/rust/cli/discover/metadata.rs +48 -0
  69. package/rust/cli/discover/mod.rs +5 -0
  70. package/rust/cli/{init.rs → init/commands.rs} +32 -23
  71. package/rust/cli/init/mod.rs +1 -0
  72. package/rust/cli/install/addon.rs +118 -0
  73. package/rust/cli/install/bank.rs +72 -0
  74. package/rust/cli/install/commands.rs +35 -0
  75. package/rust/cli/install/mod.rs +4 -0
  76. package/rust/cli/install/plugin.rs +80 -0
  77. package/rust/cli/login/commands.rs +124 -0
  78. package/rust/cli/login/mod.rs +1 -0
  79. package/rust/cli/mod.rs +9 -202
  80. package/rust/cli/parser.rs +359 -0
  81. package/rust/cli/play/commands.rs +375 -0
  82. package/rust/cli/play/io.rs +17 -0
  83. package/rust/cli/play/mod.rs +5 -0
  84. package/rust/cli/play/process.rs +159 -0
  85. package/rust/cli/play/realtime.rs +91 -0
  86. package/rust/cli/play/utils.rs +23 -0
  87. package/rust/cli/telemetry/commands.rs +22 -0
  88. package/rust/cli/telemetry/event_creator.rs +80 -0
  89. package/rust/cli/telemetry/mod.rs +3 -0
  90. package/rust/cli/telemetry/send.rs +51 -0
  91. package/rust/cli/{template.rs → template/commands.rs} +17 -5
  92. package/rust/cli/template/mod.rs +1 -0
  93. package/rust/cli/update/commands.rs +6 -0
  94. package/rust/cli/update/mod.rs +1 -0
  95. package/rust/config/driver.rs +112 -0
  96. package/rust/config/mod.rs +3 -16
  97. package/rust/config/ops.rs +26 -0
  98. package/rust/config/settings.rs +101 -0
  99. package/rust/core/audio/engine/driver.rs +220 -0
  100. package/rust/core/audio/engine/export.rs +169 -0
  101. package/rust/core/audio/engine/helpers.rs +178 -0
  102. package/rust/core/audio/engine/mod.rs +56 -0
  103. package/rust/core/audio/engine/notes/dsp.rs +85 -0
  104. package/rust/core/audio/engine/notes/mod.rs +44 -0
  105. package/rust/core/audio/engine/notes/params.rs +294 -0
  106. package/rust/core/audio/engine/sample/insert.rs +199 -0
  107. package/rust/core/audio/engine/sample/mod.rs +40 -0
  108. package/rust/core/audio/engine/sample/padding.rs +170 -0
  109. package/rust/core/audio/evaluator/condition.rs +61 -0
  110. package/rust/core/audio/evaluator/mod.rs +9 -0
  111. package/rust/core/audio/evaluator/numeric.rs +152 -0
  112. package/rust/core/audio/evaluator/rhs.rs +16 -0
  113. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  114. package/rust/core/audio/interpreter/driver.rs +574 -216
  115. package/rust/core/audio/interpreter/mod.rs +2 -12
  116. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +175 -0
  117. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +384 -0
  118. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +2 -0
  119. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +316 -0
  120. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  121. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  122. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  123. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  124. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  125. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  126. package/rust/core/audio/interpreter/statements/automate.rs +16 -0
  127. package/rust/core/audio/interpreter/statements/call.rs +295 -0
  128. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -69
  129. package/rust/core/audio/interpreter/statements/function.rs +24 -0
  130. package/rust/core/audio/interpreter/statements/let_.rs +36 -0
  131. package/rust/core/audio/interpreter/statements/load.rs +17 -0
  132. package/rust/core/audio/interpreter/statements/loop_.rs +115 -0
  133. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  134. package/rust/core/audio/interpreter/statements/sleep.rs +28 -0
  135. package/rust/core/audio/interpreter/statements/spawn.rs +253 -0
  136. package/rust/core/audio/interpreter/statements/tempo.rs +40 -0
  137. package/rust/core/audio/interpreter/statements/trigger.rs +239 -0
  138. package/rust/core/audio/loader/mod.rs +1 -1
  139. package/rust/core/audio/loader/trigger.rs +98 -52
  140. package/rust/core/audio/mod.rs +2 -2
  141. package/rust/core/audio/player.rs +28 -12
  142. package/rust/core/audio/special/easing.rs +189 -0
  143. package/rust/core/audio/special/env.rs +45 -0
  144. package/rust/core/audio/special/math.rs +134 -0
  145. package/rust/core/audio/special/mod.rs +9 -0
  146. package/rust/core/audio/special/modulator.rs +143 -0
  147. package/rust/core/builder/mod.rs +129 -80
  148. package/rust/core/debugger/lexer.rs +4 -4
  149. package/rust/core/debugger/logs.rs +52 -0
  150. package/rust/core/debugger/mod.rs +11 -2
  151. package/rust/core/debugger/preprocessor.rs +4 -4
  152. package/rust/core/debugger/store.rs +38 -25
  153. package/rust/core/error/mod.rs +221 -12
  154. package/rust/core/lexer/driver.rs +59 -0
  155. package/rust/core/lexer/handler/arrow.rs +62 -11
  156. package/rust/core/lexer/handler/at.rs +5 -5
  157. package/rust/core/lexer/handler/brace.rs +11 -11
  158. package/rust/core/lexer/handler/colon.rs +5 -5
  159. package/rust/core/lexer/handler/comment.rs +3 -3
  160. package/rust/core/lexer/handler/dot.rs +6 -6
  161. package/rust/core/lexer/handler/driver.rs +143 -32
  162. package/rust/core/lexer/handler/identifier.rs +11 -5
  163. package/rust/core/lexer/handler/indent.rs +18 -4
  164. package/rust/core/lexer/handler/mod.rs +6 -5
  165. package/rust/core/lexer/handler/newline.rs +3 -3
  166. package/rust/core/lexer/handler/number.rs +5 -5
  167. package/rust/core/lexer/handler/operator.rs +5 -3
  168. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  169. package/rust/core/lexer/handler/slash.rs +21 -0
  170. package/rust/core/lexer/handler/string.rs +3 -3
  171. package/rust/core/lexer/mod.rs +1 -49
  172. package/rust/core/lexer/token.rs +17 -12
  173. package/rust/core/mod.rs +9 -10
  174. package/rust/core/parser/driver/block.rs +111 -0
  175. package/rust/core/parser/driver/cursor.rs +82 -0
  176. package/rust/core/parser/driver/driver_impl.rs +139 -0
  177. package/rust/core/parser/driver/mod.rs +6 -0
  178. package/rust/core/parser/driver/parse_array.rs +120 -0
  179. package/rust/core/parser/driver/parse_map.rs +223 -0
  180. package/rust/core/parser/driver/parser.rs +160 -0
  181. package/rust/core/parser/handler/arrow_call.rs +277 -126
  182. package/rust/core/parser/handler/at.rs +142 -25
  183. package/rust/core/parser/handler/bank.rs +83 -20
  184. package/rust/core/parser/handler/condition.rs +14 -5
  185. package/rust/core/parser/handler/dot.rs +111 -75
  186. package/rust/core/parser/handler/identifier/automate.rs +254 -0
  187. package/rust/core/parser/handler/identifier/call.rs +74 -24
  188. package/rust/core/parser/handler/identifier/emit.rs +70 -0
  189. package/rust/core/parser/handler/identifier/function.rs +113 -0
  190. package/rust/core/parser/handler/identifier/group.rs +28 -14
  191. package/rust/core/parser/handler/identifier/let_.rs +61 -21
  192. package/rust/core/parser/handler/identifier/mod.rs +24 -20
  193. package/rust/core/parser/handler/identifier/on.rs +107 -0
  194. package/rust/core/parser/handler/identifier/print.rs +49 -0
  195. package/rust/core/parser/handler/identifier/sleep.rs +77 -14
  196. package/rust/core/parser/handler/identifier/spawn.rs +81 -31
  197. package/rust/core/parser/handler/identifier/synth.rs +102 -32
  198. package/rust/core/parser/handler/loop_.rs +144 -22
  199. package/rust/core/parser/handler/mod.rs +6 -5
  200. package/rust/core/parser/handler/pattern.rs +74 -0
  201. package/rust/core/parser/handler/tempo.rs +67 -9
  202. package/rust/core/parser/mod.rs +3 -4
  203. package/rust/core/parser/statement.rs +6 -92
  204. package/rust/core/plugin/loader.rs +137 -0
  205. package/rust/core/plugin/mod.rs +2 -0
  206. package/rust/core/plugin/runner/mod.rs +11 -0
  207. package/rust/core/plugin/runner/non_wasm.rs +297 -0
  208. package/rust/core/plugin/runner/wasm32.rs +43 -0
  209. package/rust/core/preprocessor/loader/inject.rs +278 -0
  210. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  211. package/rust/core/preprocessor/loader/mod.rs +235 -0
  212. package/rust/core/preprocessor/mod.rs +4 -4
  213. package/rust/core/preprocessor/module.rs +55 -50
  214. package/rust/core/preprocessor/processor/handlers.rs +107 -0
  215. package/rust/core/preprocessor/processor/mod.rs +1 -0
  216. package/rust/core/preprocessor/resolver/bank.rs +14 -12
  217. package/rust/core/preprocessor/resolver/call.rs +106 -105
  218. package/rust/core/preprocessor/resolver/condition.rs +13 -10
  219. package/rust/core/preprocessor/resolver/driver.rs +145 -48
  220. package/rust/core/preprocessor/resolver/function.rs +69 -0
  221. package/rust/core/preprocessor/resolver/group.rs +122 -61
  222. package/rust/core/preprocessor/resolver/let_.rs +13 -12
  223. package/rust/core/preprocessor/resolver/loop_.rs +240 -13
  224. package/rust/core/preprocessor/resolver/mod.rs +8 -6
  225. package/rust/core/preprocessor/resolver/pattern.rs +83 -0
  226. package/rust/core/preprocessor/resolver/spawn.rs +83 -42
  227. package/rust/core/preprocessor/resolver/synth.rs +15 -11
  228. package/rust/core/preprocessor/resolver/tempo.rs +13 -14
  229. package/rust/core/preprocessor/resolver/trigger.rs +32 -28
  230. package/rust/core/preprocessor/resolver/value.rs +111 -13
  231. package/rust/core/store/global.rs +57 -39
  232. package/rust/core/store/mod.rs +0 -3
  233. package/rust/lib.rs +323 -117
  234. package/rust/main.rs +388 -65
  235. package/rust/types/Cargo.toml +11 -0
  236. package/rust/types/src/addons.rs +55 -0
  237. package/rust/types/src/ast.rs +202 -0
  238. package/rust/types/src/config.rs +84 -0
  239. package/rust/types/src/lib.rs +15 -0
  240. package/rust/types/src/plugin.rs +20 -0
  241. package/rust/types/src/store.rs +139 -0
  242. package/rust/types/src/telemetry.rs +85 -0
  243. package/rust/utils/Cargo.toml +26 -0
  244. package/rust/utils/src/error.rs +186 -0
  245. package/rust/utils/src/file.rs +94 -0
  246. package/rust/utils/src/first_usage.rs +97 -0
  247. package/rust/utils/{mod.rs → src/lib.rs} +6 -3
  248. package/rust/utils/{logger.rs → src/logger.rs} +94 -17
  249. package/rust/utils/src/path.rs +129 -0
  250. package/rust/utils/src/signature.rs +41 -0
  251. package/rust/utils/{spinner.rs → src/spinner.rs} +7 -8
  252. package/rust/utils/src/version.rs +27 -0
  253. package/rust/utils/{watcher.rs → src/watcher.rs} +17 -4
  254. package/rust/web/api.rs +5 -0
  255. package/rust/web/cdn.rs +34 -0
  256. package/rust/web/mod.rs +3 -0
  257. package/rust/web/sso.rs +5 -0
  258. package/templates/minimal/README.md +143 -127
  259. package/templates/welcome/README.md +143 -127
  260. package/templates/welcome/src/index.deva +56 -8
  261. package/templates/welcome/src/variables.deva +2 -4
  262. package/tests/integration.rs +21 -0
  263. package/tests/rust/cli_check_build.rs +21 -0
  264. package/tests/rust/cli_help.rs +12 -0
  265. package/tests/rust/cli_template_list.rs +10 -0
  266. package/tests/rust/cli_version.rs +11 -0
  267. package/tests/typescript/index.spec.ts +136 -0
  268. package/tests/typescript/playhead.spec.ts +36 -0
  269. package/tests/typescript/render_e2e.spec.ts +77 -0
  270. package/tsconfig.json +12 -10
  271. package/typescript/bin/index.ts +19 -5
  272. package/typescript/core/functions/index.ts +94 -0
  273. package/typescript/core/index.ts +6 -0
  274. package/typescript/core/types/index.ts +4 -0
  275. package/typescript/core/types/plugin.ts +19 -0
  276. package/typescript/core/types/result.ts +29 -0
  277. package/typescript/core/types/statement.ts +47 -0
  278. package/typescript/core/types/value.ts +29 -0
  279. package/typescript/index.ts +8 -1
  280. package/typescript/pkg/devalang_core.d.ts +4 -0
  281. package/typescript/pkg/devalang_core.ts +65 -0
  282. package/typescript/scripts/copy-wasm-dts.ts +41 -0
  283. package/typescript/scripts/postinstall.ts +85 -0
  284. package/typescript/scripts/version/bump.ts +0 -1
  285. package/typescript/scripts/version/index.ts +0 -1
  286. package/docs/COMMANDS.md +0 -85
  287. package/docs/CONFIG.md +0 -30
  288. package/docs/SYNTAX.md +0 -210
  289. package/out-tsc/bin/devalang.exe +0 -0
  290. package/out-tsc/scripts/postbuild.js +0 -11
  291. package/rust/cli/build.rs +0 -137
  292. package/rust/cli/check.rs +0 -117
  293. package/rust/cli/play.rs +0 -193
  294. package/rust/config/loader.rs +0 -13
  295. package/rust/core/audio/engine.rs +0 -203
  296. package/rust/core/audio/evaluator.rs +0 -31
  297. package/rust/core/audio/interpreter/arrow_call.rs +0 -129
  298. package/rust/core/audio/interpreter/call.rs +0 -64
  299. package/rust/core/audio/interpreter/let_.rs +0 -19
  300. package/rust/core/audio/interpreter/load.rs +0 -18
  301. package/rust/core/audio/interpreter/loop_.rs +0 -67
  302. package/rust/core/audio/interpreter/sleep.rs +0 -36
  303. package/rust/core/audio/interpreter/spawn.rs +0 -66
  304. package/rust/core/audio/interpreter/tempo.rs +0 -16
  305. package/rust/core/audio/interpreter/trigger.rs +0 -69
  306. package/rust/core/audio/renderer.rs +0 -54
  307. package/rust/core/parser/driver.rs +0 -331
  308. package/rust/core/preprocessor/loader.rs +0 -193
  309. package/rust/core/preprocessor/processor.rs +0 -76
  310. package/rust/core/shared/duration.rs +0 -8
  311. package/rust/core/shared/mod.rs +0 -2
  312. package/rust/core/shared/value.rs +0 -18
  313. package/rust/core/store/export.rs +0 -28
  314. package/rust/core/store/import.rs +0 -28
  315. package/rust/core/store/variable.rs +0 -28
  316. package/rust/core/utils/mod.rs +0 -2
  317. package/rust/core/utils/path.rs +0 -31
  318. package/rust/core/utils/validation.rs +0 -37
  319. package/rust/utils/file.rs +0 -35
  320. package/rust/utils/signature.rs +0 -17
  321. package/rust/utils/version.rs +0 -15
  322. package/typescript/scripts/postbuild.ts +0 -8
@@ -0,0 +1,111 @@
1
+ use devalang_core::config::driver::ProjectConfigExt;
2
+ use devalang_types::{AddonWithMetadata, ProjectConfigBankEntry, ProjectConfigPluginEntry};
3
+ use devalang_utils::path as path_utils;
4
+
5
+ pub async fn add_addons_to_config(addons: Vec<AddonWithMetadata>) -> Result<(), String> {
6
+ let config_path = path_utils::get_devalang_config_path()?;
7
+ let mut config = crate::config::ops::load_config(Some(&config_path))
8
+ .ok_or_else(|| format!("Failed to load config from '{}'", config_path.display()))?;
9
+
10
+ for addon in addons {
11
+ let addon_path_as_devalang_protocol = format!(
12
+ "devalang://{}/{}.{}",
13
+ addon.addon_type, addon.metadata.author, addon.metadata.name
14
+ );
15
+
16
+ match addon.addon_type.as_str() {
17
+ "bank" => {
18
+ if config.banks.is_none() {
19
+ config.banks = Some(Vec::new());
20
+ }
21
+
22
+ let banks = config.banks.as_mut().unwrap();
23
+
24
+ let exists = banks
25
+ .iter()
26
+ .any(|b| b.path == addon_path_as_devalang_protocol);
27
+ if exists {
28
+ println!("Bank '{}' already in config", addon.name);
29
+ continue;
30
+ }
31
+
32
+ banks.push(ProjectConfigBankEntry {
33
+ path: addon_path_as_devalang_protocol,
34
+ version: Some(addon.metadata.version.clone()),
35
+ });
36
+ }
37
+
38
+ "plugin" => {
39
+ if config.plugins.is_none() {
40
+ config.plugins = Some(Vec::new());
41
+ }
42
+
43
+ let plugins = config.plugins.as_mut().unwrap();
44
+
45
+ let exists = plugins
46
+ .iter()
47
+ .any(|p| p.path == addon_path_as_devalang_protocol);
48
+ if exists {
49
+ println!("Plugin '{}' already in config", addon.name);
50
+ continue;
51
+ }
52
+
53
+ plugins.push(ProjectConfigPluginEntry {
54
+ path: addon_path_as_devalang_protocol,
55
+ version: Some(addon.metadata.version.clone()),
56
+ });
57
+ }
58
+
59
+ // "preset" => {
60
+ // if config.presets.is_none() {
61
+ // config.presets = Some(Vec::new());
62
+ // }
63
+
64
+ // let presets = config.presets.as_mut().unwrap();
65
+
66
+ // let exists = presets.iter().any(|p| p.path == addon_path_as_deva_protocol);
67
+ // if exists {
68
+ // println!("Preset '{}' already in config", addon.name);
69
+ // continue;
70
+ // }
71
+
72
+ // presets.push(ProjectConfigPresetEntry {
73
+ // path: addon_path_as_deva_protocol,
74
+ // version: Some(addon.metadata.version.clone()),
75
+ // });
76
+ // }
77
+
78
+ // "template" => {
79
+ // if config.templates.is_none() {
80
+ // config.templates = Some(Vec::new());
81
+ // }
82
+
83
+ // let templates = config.templates.as_mut().unwrap();
84
+
85
+ // let exists = templates.iter().any(|t| t.path == addon_path_as_deva_protocol);
86
+ // if exists {
87
+ // println!("Template '{}' already in config", addon.name);
88
+ // continue;
89
+ // }
90
+
91
+ // templates.push(ProjectConfigTemplateEntry {
92
+ // path: addon_path_as_deva_protocol,
93
+ // version: Some(addon.metadata.version.clone()),
94
+ // });
95
+ // }
96
+ _ => {
97
+ println!(
98
+ "Unknown addon type '{}' for addon '{}'",
99
+ addon.addon_type, addon.name
100
+ );
101
+ }
102
+ }
103
+ }
104
+
105
+ // Update config with new addons
106
+ if let Err(e) = config.write_config(&config) {
107
+ return Err(format!("Failed to write config: {}", e));
108
+ }
109
+
110
+ Ok(())
111
+ }
@@ -0,0 +1,19 @@
1
+ use std::{fs, io, path::Path};
2
+
3
+ pub fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), io::Error> {
4
+ if !dst.exists() {
5
+ fs::create_dir_all(dst)?;
6
+ }
7
+ for entry in fs::read_dir(src)? {
8
+ let entry = entry?;
9
+ let ty = entry.file_type()?;
10
+ let from = entry.path();
11
+ let to = dst.join(entry.file_name());
12
+ if ty.is_dir() {
13
+ copy_dir_all(&from, &to)?;
14
+ } else {
15
+ fs::copy(&from, &to)?;
16
+ }
17
+ }
18
+ Ok(())
19
+ }
@@ -0,0 +1,103 @@
1
+ use devalang_types::{AddonMetadata, AddonWithMetadata, DiscoveredAddon};
2
+ use devalang_utils::path as path_utils;
3
+
4
+ pub async fn install_selected_addons(
5
+ addons: Vec<DiscoveredAddon>,
6
+ ) -> Result<Vec<AddonWithMetadata>, String> {
7
+ let mut addons_enriched = Vec::new();
8
+
9
+ let tmp_dir = path_utils::ensure_deva_dir()?.join("tmp");
10
+
11
+ for addon in addons {
12
+ std::fs::create_dir_all(tmp_dir.join(&addon.name))
13
+ .map_err(|e| format!("Failed to create directory for addon {}: {}", addon.name, e))?;
14
+
15
+ let addon_path = tmp_dir.join(&addon.name);
16
+ devalang_utils::file::extract_zip_safely(&addon.path, &addon_path)
17
+ .map_err(|e| format!("Failed to extract addon {}: {}", addon.name, e))?;
18
+
19
+ let base = path_utils::ensure_deva_dir()?;
20
+ let target_addon_dir = match addon.addon_type.as_str() {
21
+ "bank" => base.join("banks"),
22
+ "plugin" => base.join("plugins"),
23
+ "preset" => base.join("presets"),
24
+ "template" => base.join("templates"),
25
+ _ => {
26
+ return Err(format!("Unknown addon type for addon {}", addon.name));
27
+ }
28
+ };
29
+
30
+ std::fs::create_dir_all(&target_addon_dir).map_err(|e| {
31
+ format!(
32
+ "Failed to create target directory for addon {}: {}",
33
+ addon.name, e
34
+ )
35
+ })?;
36
+
37
+ let target_addon_path_dir = target_addon_dir.join(&addon.name);
38
+ if target_addon_path_dir.exists() {
39
+ println!(
40
+ "Target addon directory {} already exists",
41
+ target_addon_path_dir.display()
42
+ );
43
+ continue;
44
+ }
45
+
46
+ if let Err(e) = std::fs::rename(&addon_path, &target_addon_path_dir) {
47
+ crate::cli::discover::fs::copy_dir_all(&addon_path, &target_addon_path_dir).map_err(
48
+ |err| {
49
+ format!(
50
+ "Failed to move addon {}: {} (rename error: {})",
51
+ addon.name, err, e
52
+ )
53
+ },
54
+ )?;
55
+ let _ = std::fs::remove_dir_all(&addon_path);
56
+ }
57
+
58
+ let addon_metadata_filename = match addon.addon_type.as_str() {
59
+ "bank" => "bank.toml",
60
+ "plugin" => "plugin.toml",
61
+ "preset" => "preset.toml",
62
+ "template" => "template.toml",
63
+ _ => {
64
+ return Err(format!("Unknown addon type for addon {}", addon.name));
65
+ }
66
+ };
67
+
68
+ let addon_metadata_path = target_addon_path_dir.join(addon_metadata_filename);
69
+ let addon_metadata_content = std::fs::read_to_string(&addon_metadata_path)
70
+ .map_err(|e| format!("Failed to read metadata for addon {}: {}", addon.name, e))?;
71
+
72
+ let parsed_meta = crate::cli::discover::metadata::parse_metadata_file(
73
+ &addon.addon_type,
74
+ &addon_metadata_content,
75
+ )
76
+ .unwrap_or(AddonMetadata {
77
+ name: addon.name.clone(),
78
+ author: "unknown".to_string(),
79
+ version: "".to_string(),
80
+ description: "".to_string(),
81
+ access: "".to_string(),
82
+ });
83
+
84
+ addons_enriched.push(AddonWithMetadata {
85
+ name: addon.name.clone(),
86
+ path: addon.path.clone().to_string_lossy().to_string(),
87
+ addon_type: addon.addon_type.clone(),
88
+ metadata: parsed_meta,
89
+ });
90
+
91
+ if let Err(e) = std::fs::remove_file(&addon.path) {
92
+ eprintln!(
93
+ "Failed to remove zipped file for addon {}: {}",
94
+ addon.name, e
95
+ );
96
+ }
97
+ }
98
+
99
+ // Best-effort cleanup of temporary extraction directory
100
+ let _ = std::fs::remove_dir_all(&tmp_dir);
101
+
102
+ Ok(addons_enriched)
103
+ }
@@ -0,0 +1,48 @@
1
+ use devalang_types::AddonMetadata;
2
+ use toml::Value;
3
+
4
+ pub fn parse_metadata_file(addon_type: &str, metadata_content: &str) -> Option<AddonMetadata> {
5
+ let parsed = metadata_content.parse::<Value>().ok()?;
6
+
7
+ let table = (match addon_type {
8
+ "bank" => parsed.get("bank"),
9
+ "plugin" => parsed.get("plugin"),
10
+ "preset" => parsed.get("preset"),
11
+ "template" => parsed.get("template"),
12
+ _ => None,
13
+ })?;
14
+
15
+ let name = table
16
+ .get("name")
17
+ .and_then(|v| v.as_str())
18
+ .unwrap_or("")
19
+ .to_string();
20
+ let version = table
21
+ .get("version")
22
+ .and_then(|v| v.as_str())
23
+ .unwrap_or("")
24
+ .to_string();
25
+ let description = table
26
+ .get("description")
27
+ .and_then(|v| v.as_str())
28
+ .unwrap_or("")
29
+ .to_string();
30
+ let author = table
31
+ .get("author")
32
+ .and_then(|v| v.as_str())
33
+ .unwrap_or("unknown")
34
+ .to_string();
35
+ let access = table
36
+ .get("access")
37
+ .and_then(|v| v.as_str())
38
+ .unwrap_or("")
39
+ .to_string();
40
+
41
+ Some(AddonMetadata {
42
+ name,
43
+ author,
44
+ version,
45
+ description,
46
+ access,
47
+ })
48
+ }
@@ -0,0 +1,5 @@
1
+ pub mod commands;
2
+ pub mod config;
3
+ pub mod fs;
4
+ pub mod install;
5
+ pub mod metadata;
@@ -1,29 +1,35 @@
1
- use std::{ fs, path::Path };
2
- use include_dir::{ include_dir, Dir };
3
- use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
1
+ use crate::cli::template::commands::get_available_templates;
2
+ use devalang_utils::file::copy_dir_recursive;
3
+ use include_dir::{Dir, include_dir};
4
+ use std::{fs, path::Path};
4
5
 
5
6
  #[cfg(feature = "cli")]
6
- use inquire::{ Select, Confirm };
7
+ use inquire::{Confirm, Select};
7
8
 
8
9
  static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
9
10
 
10
11
  #[cfg(feature = "cli")]
11
12
  pub fn handle_init_command(name: Option<String>, template: Option<String>) {
12
13
  let current_dir = std::env::current_dir().unwrap();
13
- let project_name = name
14
- .clone()
15
- .unwrap_or_else(|| {
16
- current_dir.file_name().unwrap_or_default().to_string_lossy().to_string()
17
- });
14
+ let project_name = name.clone().unwrap_or_else(|| {
15
+ current_dir
16
+ .file_name()
17
+ .unwrap_or_default()
18
+ .to_string_lossy()
19
+ .to_string()
20
+ });
18
21
 
19
22
  // Select a template if not provided
20
23
  let selected_template = template.unwrap_or_else(|| {
21
- Select::new("Select a template for your project:", get_available_templates())
22
- .prompt()
23
- .unwrap_or_else(|_| {
24
- eprintln!("No template selected. Exiting...");
25
- std::process::exit(1);
26
- })
24
+ Select::new(
25
+ "Select a template for your project:",
26
+ get_available_templates(),
27
+ )
28
+ .prompt()
29
+ .unwrap_or_else(|_| {
30
+ eprintln!("No template selected. Exiting...");
31
+ std::process::exit(1);
32
+ })
27
33
  });
28
34
 
29
35
  if selected_template.is_empty() {
@@ -34,12 +40,11 @@ pub fn handle_init_command(name: Option<String>, template: Option<String>) {
34
40
  if name.is_none() {
35
41
  // Case of initialization in the current directory
36
42
  if fs::read_dir(&current_dir).unwrap().next().is_some() {
37
- let confirm = Confirm::new(
38
- "The current directory is not empty. Do you want to continue?"
39
- )
40
- .with_default(false)
41
- .prompt()
42
- .unwrap_or(false);
43
+ let confirm =
44
+ Confirm::new("The current directory is not empty. Do you want to continue?")
45
+ .with_default(false)
46
+ .prompt()
47
+ .unwrap_or(false);
43
48
 
44
49
  if !confirm {
45
50
  eprintln!("Operation cancelled by the user.");
@@ -65,7 +70,11 @@ pub fn handle_init_command(name: Option<String>, template: Option<String>) {
65
70
  fs::create_dir_all(&target_path).expect("Error creating project directory");
66
71
 
67
72
  scaffold_project_current_dir(&target_path, selected_template);
68
- println!("✅ Initialized '{}' project in: {}", project_name, target_path.display());
73
+ println!(
74
+ "✅ Initialized '{}' project in: {}",
75
+ project_name,
76
+ target_path.display()
77
+ );
69
78
  }
70
79
  }
71
80
 
@@ -75,5 +84,5 @@ fn scaffold_project_current_dir(path: &Path, template: String) {
75
84
  std::process::exit(1);
76
85
  });
77
86
 
78
- copy_dir_recursive(template_dir, path, &template_dir.path());
87
+ copy_dir_recursive(template_dir, path, template_dir.path());
79
88
  }
@@ -0,0 +1 @@
1
+ pub mod commands;
@@ -0,0 +1,118 @@
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
+ }
@@ -0,0 +1,72 @@
1
+ use crate::{
2
+ cli::install::addon::ask_api_for_signed_url, config::ops::load_config,
3
+ web::cdn::download_from_cdn,
4
+ };
5
+ use devalang_core::config::driver::ProjectConfigExt;
6
+ use devalang_types::AddonType;
7
+ use devalang_types::ProjectConfigBankEntry;
8
+ use devalang_utils::{
9
+ logger::{LogLevel, Logger},
10
+ path as path_utils,
11
+ };
12
+ use std::path::Path;
13
+
14
+ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
15
+ let logger = Logger::new();
16
+
17
+ let signed_url = ask_api_for_signed_url(AddonType::Bank, name).await?;
18
+
19
+ let bank_dir = target_dir.join("banks");
20
+ let archive_path = path_utils::ensure_deva_dir()?
21
+ .join("tmp")
22
+ .join(format!("{}.devabank", name));
23
+ let extract_path = bank_dir.join(name);
24
+
25
+ download_from_cdn(&signed_url, &archive_path)
26
+ .await
27
+ .map_err(|e| format!("Failed to download: {}", e))?;
28
+
29
+ if extract_path.exists() {
30
+ logger.log_message(
31
+ LogLevel::Warning,
32
+ &format!(
33
+ "Bank '{}' already exists at '{}'. Skipping install.",
34
+ name,
35
+ extract_path.display()
36
+ ),
37
+ );
38
+
39
+ return Ok(());
40
+ }
41
+
42
+ // Add the bank to the config
43
+ let config_path = path_utils::get_devalang_config_path()?;
44
+ let mut config = load_config(Some(&config_path))
45
+ .ok_or_else(|| format!("Failed to load config from '{}'", config_path.display()))?;
46
+
47
+ let dependency_path = format!("devalang://bank/{}", name);
48
+
49
+ devalang_utils::file::extract_zip_safely(&archive_path, &extract_path)
50
+ .map_err(|e| format!("Failed to extract: {}", e))?;
51
+
52
+ // Add the bank to the config if not already present
53
+ if config.banks.is_none() {
54
+ config.banks = Some(Vec::new());
55
+ }
56
+
57
+ if let Some(banks) = config.banks.as_mut() {
58
+ let exists = banks.iter().any(|b| b.path == dependency_path);
59
+ if !exists {
60
+ banks.push(ProjectConfigBankEntry {
61
+ path: dependency_path.clone(),
62
+ version: None,
63
+ });
64
+
65
+ if let Err(e) = config.write_config(&config) {
66
+ eprintln!("Warning: failed to write config: {}", e);
67
+ }
68
+ }
69
+ }
70
+
71
+ Ok(())
72
+ }
@@ -0,0 +1,35 @@
1
+ use crate::cli::install::addon::install_addon;
2
+ #[cfg(feature = "cli")]
3
+ use devalang_types::AddonType;
4
+ use devalang_utils::path as path_utils;
5
+
6
+ /// Handles the installation command for a given addon type and name.
7
+ #[cfg(feature = "cli")]
8
+ pub async fn handle_install_command(name: String, addon_type: AddonType) -> Result<(), String> {
9
+ use devalang_utils::{
10
+ logger::{LogLevel, Logger},
11
+ spinner::start_spinner,
12
+ };
13
+
14
+ let logger = Logger::new();
15
+ let deva_dir = path_utils::ensure_deva_dir()?;
16
+
17
+ let spinner = start_spinner("Installing...");
18
+
19
+ if let Err(e) = install_addon(addon_type.clone(), name.as_str(), &deva_dir).await {
20
+ spinner.finish_and_clear();
21
+ logger.log_message_with_trace(
22
+ LogLevel::Error,
23
+ &format!("Error installing {:?} '{}'", addon_type, name),
24
+ vec![&e],
25
+ );
26
+ } else {
27
+ spinner.finish_and_clear();
28
+ logger.log_message(
29
+ LogLevel::Success,
30
+ &format!("{:?} '{}' installed successfully!", addon_type, name),
31
+ );
32
+ }
33
+
34
+ Ok(())
35
+ }
@@ -0,0 +1,4 @@
1
+ pub mod addon;
2
+ pub mod bank;
3
+ pub mod commands;
4
+ pub mod plugin;