@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.14

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 (50) hide show
  1. package/.devalang +8 -9
  2. package/Cargo.toml +58 -54
  3. package/README.md +36 -21
  4. package/docs/CHANGELOG.md +40 -2
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +2 -2
  7. package/docs/TODO.md +5 -4
  8. package/examples/bank.deva +2 -4
  9. package/examples/function.deva +15 -0
  10. package/examples/index.deva +25 -11
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +6 -6
  13. package/project-version.json +3 -3
  14. package/rust/cli/bank.rs +2 -1
  15. package/rust/cli/build.rs +69 -30
  16. package/rust/cli/check.rs +45 -5
  17. package/rust/cli/driver.rs +40 -28
  18. package/rust/cli/install.rs +22 -7
  19. package/rust/cli/login.rs +134 -0
  20. package/rust/cli/mod.rs +2 -1
  21. package/rust/cli/play.rs +44 -19
  22. package/rust/common/api.rs +8 -0
  23. package/rust/common/cdn.rs +2 -5
  24. package/rust/common/mod.rs +3 -1
  25. package/rust/common/sso.rs +8 -0
  26. package/rust/config/driver.rs +19 -1
  27. package/rust/config/loader.rs +56 -10
  28. package/rust/core/audio/engine.rs +152 -42
  29. package/rust/core/audio/interpreter/arrow_call.rs +34 -15
  30. package/rust/core/audio/interpreter/call.rs +2 -2
  31. package/rust/core/audio/interpreter/driver.rs +19 -14
  32. package/rust/core/audio/interpreter/spawn.rs +2 -2
  33. package/rust/core/builder/mod.rs +11 -6
  34. package/rust/core/lexer/handler/indent.rs +16 -2
  35. package/rust/core/lexer/token.rs +1 -0
  36. package/rust/core/mod.rs +2 -1
  37. package/rust/core/parser/driver.rs +46 -5
  38. package/rust/core/parser/handler/arrow_call.rs +78 -18
  39. package/rust/core/parser/handler/bank.rs +35 -7
  40. package/rust/core/parser/handler/dot.rs +43 -22
  41. package/rust/core/plugin/loader.rs +48 -0
  42. package/rust/core/plugin/mod.rs +1 -0
  43. package/rust/core/preprocessor/loader.rs +6 -4
  44. package/rust/installer/addon.rs +80 -0
  45. package/rust/installer/bank.rs +24 -14
  46. package/rust/installer/mod.rs +4 -1
  47. package/rust/installer/plugin.rs +55 -0
  48. package/rust/main.rs +32 -9
  49. package/rust/utils/logger.rs +16 -0
  50. package/rust/utils/spinner.rs +2 -4
@@ -7,7 +7,7 @@ use crate::core::{
7
7
  bank::parse_bank_token,
8
8
  condition::parse_condition_token,
9
9
  dot::parse_dot_token,
10
- identifier::{function::parse_function_token, parse_identifier_token},
10
+ identifier::{ function::parse_function_token, parse_identifier_token },
11
11
  loop_::parse_loop_token,
12
12
  tempo::parse_tempo_token,
13
13
  },
@@ -107,7 +107,11 @@ impl Parser {
107
107
  tokens: Vec<Token>,
108
108
  global_store: &mut GlobalStore
109
109
  ) -> Vec<Statement> {
110
- self.tokens = tokens;
110
+ // Filtrer les tokens Whitespace et Newline avant parsing
111
+ self.tokens = tokens
112
+ .into_iter()
113
+ .filter(|t| t.kind != TokenKind::Whitespace && t.kind != TokenKind::Newline)
114
+ .collect();
111
115
  self.token_index = 0;
112
116
 
113
117
  let mut statements = Vec::new();
@@ -120,6 +124,11 @@ impl Parser {
120
124
  }
121
125
  };
122
126
 
127
+ if token.kind == TokenKind::Newline {
128
+ self.advance();
129
+ continue;
130
+ }
131
+
123
132
  let statement = match &token.kind {
124
133
  TokenKind::At => parse_at_token(self, global_store),
125
134
  TokenKind::Identifier => {
@@ -149,7 +158,6 @@ impl Parser {
149
158
  | TokenKind::LBrace
150
159
  | TokenKind::RBrace
151
160
  | TokenKind::Comma
152
- | TokenKind::Newline
153
161
  | TokenKind::Dedent
154
162
  | TokenKind::Indent => {
155
163
  self.advance();
@@ -189,17 +197,50 @@ impl Parser {
189
197
  let mut map = std::collections::HashMap::new();
190
198
 
191
199
  while !self.check_token(TokenKind::RBrace) && !self.is_eof() {
200
+ // Skip newlines, whitespace, indent, dedent before the key
201
+ while
202
+ self.check_token(TokenKind::Newline) ||
203
+ self.check_token(TokenKind::Whitespace) ||
204
+ self.check_token(TokenKind::Indent) ||
205
+ self.check_token(TokenKind::Dedent)
206
+ {
207
+ self.advance();
208
+ }
209
+
210
+ // Check if we are at the closing brace of the map
211
+ if self.check_token(TokenKind::RBrace) {
212
+ break;
213
+ }
214
+
192
215
  let key = if let Some(token) = self.advance() {
193
- token.lexeme.clone()
216
+ match token.kind {
217
+ | TokenKind::Whitespace
218
+ | TokenKind::Indent
219
+ | TokenKind::Dedent
220
+ | TokenKind::Newline => {
221
+ continue;
222
+ }
223
+ _ => token.lexeme.clone(),
224
+ }
194
225
  } else {
195
226
  break;
196
227
  };
197
228
 
229
+ // Skip newlines and whitespace before colon
230
+ while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
231
+ self.advance();
232
+ }
233
+
198
234
  if !self.match_token(TokenKind::Colon) {
199
235
  println!("Expected ':' after map key '{}'", key);
200
236
  break;
201
237
  }
202
238
 
239
+ // Skip newlines and whitespace before value
240
+ while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
241
+ self.advance();
242
+ }
243
+
203
244
  let value = if let Some(token) = self.peek_clone() {
204
245
  match token.kind {
205
246
  TokenKind::String => {
@@ -304,7 +345,7 @@ impl Parser {
304
345
  }
305
346
  collected.push(self.advance().unwrap().clone());
306
347
  }
307
-
348
+
308
349
  collected
309
350
  }
310
351
 
@@ -54,11 +54,43 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
54
54
  parser.advance(); // method
55
55
 
56
56
  let mut args = Vec::new();
57
+ let mut paren_depth = 0;
58
+ let mut map_depth = 0;
57
59
 
58
60
  while let Some(token) = parser.peek_clone() {
59
61
  if token.kind == TokenKind::Newline || token.kind == TokenKind::EOF {
60
62
  break;
61
63
  }
64
+ if token.kind == TokenKind::LParen {
65
+ paren_depth += 1;
66
+ }
67
+ if token.kind == TokenKind::RParen {
68
+ if paren_depth > 0 {
69
+ paren_depth -= 1;
70
+ parser.advance();
71
+ if paren_depth == 0 {
72
+ break;
73
+ }
74
+ continue;
75
+ } else {
76
+ break;
77
+ }
78
+ }
79
+ if token.kind == TokenKind::LBrace {
80
+ map_depth += 1;
81
+ }
82
+ if token.kind == TokenKind::RBrace {
83
+ if map_depth > 0 {
84
+ map_depth -= 1;
85
+ parser.advance();
86
+ if map_depth == 0 {
87
+ continue;
88
+ }
89
+ continue;
90
+ } else {
91
+ break;
92
+ }
93
+ }
62
94
 
63
95
  parser.advance();
64
96
 
@@ -79,49 +111,68 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
79
111
  }
80
112
  parser.advance(); // consume key token
81
113
  let key = inner_token.lexeme.clone();
82
-
83
114
  if let Some(colon_token) = parser.peek_clone() {
84
115
  if colon_token.kind == TokenKind::Colon {
85
116
  parser.advance(); // consume colon
86
117
  if let Some(value_token) = parser.peek_clone() {
87
118
  parser.advance(); // consume value token
88
119
  let value = match value_token.kind {
89
- TokenKind::Identifier =>
90
- Value::Identifier(value_token.lexeme.clone()),
120
+ TokenKind::Identifier => {
121
+ // Interpret bare true/false as booleans
122
+ if value_token.lexeme == "true" {
123
+ Value::Boolean(true)
124
+ } else if value_token.lexeme == "false" {
125
+ Value::Boolean(false)
126
+ } else {
127
+ Value::Identifier(value_token.lexeme.clone())
128
+ }
129
+ },
91
130
  TokenKind::String => Value::String(value_token.lexeme.clone()),
92
131
  TokenKind::Number => {
132
+ // Support decimals (e.g., 0.8) and beats (e.g., 1/4)
93
133
  if let Some(TokenKind::Slash) = parser.peek_kind() {
94
- parser.advance(); // consume slash
134
+ // Beat fraction
135
+ parser.advance(); // consume '/'
95
136
  if let Some(denominator_token) = parser.peek_clone() {
96
137
  if denominator_token.kind == TokenKind::Number {
97
138
  parser.advance(); // consume denominator
98
- let denominator =
99
- denominator_token.lexeme.clone();
100
- Value::Beat(
101
- format!(
102
- "{}/{}",
103
- value_token.lexeme,
104
- denominator
105
- )
106
- )
139
+ let denominator = denominator_token.lexeme.clone();
140
+ Value::Beat(format!("{}/{}", value_token.lexeme, denominator))
107
141
  } else {
108
142
  Value::Unknown
109
143
  }
110
144
  } else {
111
145
  Value::Unknown
112
146
  }
147
+ } else if let Some(next) = parser.peek_clone() {
148
+ // Decimal number handling: NUMBER '.' NUMBER -> f32
149
+ if next.kind == TokenKind::Dot {
150
+ // consume '.'
151
+ parser.advance();
152
+ if let Some(after_dot) = parser.peek_clone() {
153
+ if after_dot.kind == TokenKind::Number {
154
+ parser.advance(); // consume fractional digits
155
+ let combined = format!("{}.{}", value_token.lexeme, after_dot.lexeme);
156
+ Value::Number(combined.parse::<f32>().unwrap_or(0.0))
157
+ } else {
158
+ // Lone dot without number, fallback to integer part
159
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
160
+ }
161
+ } else {
162
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
163
+ }
164
+ } else {
165
+ // Regular integer number
166
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
167
+ }
113
168
  } else {
114
- // Regular number without slash
115
- Value::Number(
116
- value_token.lexeme.parse::<f32>().unwrap_or(0.0)
117
- )
169
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
118
170
  }
119
171
  }
120
172
  TokenKind::Boolean =>
121
173
  Value::Boolean(
122
174
  value_token.lexeme.parse::<bool>().unwrap_or(false)
123
175
  ),
124
-
125
176
  _ => Value::Unknown,
126
177
  };
127
178
  map.insert(key, value);
@@ -135,6 +186,15 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
135
186
  };
136
187
 
137
188
  args.push(value);
189
+
190
+ // Stop if we reach the end of the statement
191
+ if
192
+ paren_depth == 0 &&
193
+ map_depth == 0 &&
194
+ (token.kind == TokenKind::RParen || token.kind == TokenKind::RBrace)
195
+ {
196
+ break;
197
+ }
138
198
  }
139
199
 
140
200
  Statement {
@@ -14,13 +14,41 @@ pub fn parse_bank_token(parser: &mut Parser, _global_store: &mut GlobalStore) ->
14
14
 
15
15
  let bank_value = if let Some(token) = parser.peek_clone() {
16
16
  match token.kind {
17
- TokenKind::Identifier => {
18
- parser.advance();
19
- Value::Identifier(token.lexeme.clone())
20
- }
21
- TokenKind::Number => {
22
- parser.advance();
23
- Value::Number(token.lexeme.parse::<f32>().unwrap_or(0.0))
17
+ TokenKind::Identifier | TokenKind::Number => {
18
+ parser.advance(); // consume identifier or number
19
+
20
+ let mut value = token.lexeme.clone();
21
+
22
+ // Support namespaced banks: <author>.<bank_name>
23
+ if let Some(next) = parser.peek_clone() {
24
+ if next.kind == TokenKind::Dot {
25
+ parser.advance(); // consume '.'
26
+ if let Some(last) = parser.peek_clone() {
27
+ match last.kind {
28
+ TokenKind::Identifier | TokenKind::Number => {
29
+ parser.advance();
30
+ value = format!("{}.{}", value, last.lexeme);
31
+ Value::String(value)
32
+ }
33
+ _ => Value::Unknown,
34
+ }
35
+ } else {
36
+ Value::Unknown
37
+ }
38
+ } else {
39
+ match token.kind {
40
+ TokenKind::Identifier => Value::Identifier(value),
41
+ TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
42
+ _ => Value::Unknown,
43
+ }
44
+ }
45
+ } else {
46
+ match token.kind {
47
+ TokenKind::Identifier => Value::Identifier(value),
48
+ TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
49
+ _ => Value::Unknown,
50
+ }
51
+ }
24
52
  }
25
53
  _ => Value::Unknown,
26
54
  }
@@ -16,13 +16,23 @@ pub fn parse_dot_token(
16
16
 
17
17
  // Parse a single entity (namespace-friendly, stops at newline)
18
18
  let mut parts = Vec::new();
19
+ let current_line = dot_token.line;
19
20
 
20
21
  while let Some(token) = parser.peek_clone() {
22
+ // Ne jamais traverser une nouvelle ligne
23
+ if token.line != current_line {
24
+ break;
25
+ }
21
26
  match token.kind {
22
27
  TokenKind::Identifier | TokenKind::Number => {
23
28
  parts.push(token.lexeme.clone());
24
29
  parser.advance();
25
- if parser.peek_kind() != Some(TokenKind::Dot) {
30
+ // Le séparateur doit être un '.' sur la même ligne, sinon on s'arrête
31
+ if let Some(next) = parser.peek_clone() {
32
+ if next.line != current_line || next.kind != TokenKind::Dot {
33
+ break;
34
+ }
35
+ } else {
26
36
  break;
27
37
  }
28
38
  }
@@ -51,42 +61,53 @@ pub fn parse_dot_token(
51
61
  let mut value = Value::Null;
52
62
 
53
63
  if let Some(token) = parser.peek_clone() {
54
- match token.kind {
64
+ // La durée et la map d'effets ne sont valides que sur la même ligne
65
+ if token.line == current_line {
66
+ match token.kind {
55
67
  TokenKind::Number => {
56
68
  let numerator = token.lexeme.clone();
57
69
  parser.advance();
58
- if let Some(TokenKind::Slash) = parser.peek_kind() {
59
- parser.advance();
60
- if let Some(denominator_token) = parser.peek_clone() {
61
- if denominator_token.kind == TokenKind::Number {
62
- let denominator = denominator_token.lexeme.clone();
63
- parser.advance();
64
- duration = Duration::Beat(format!("{}/{}", numerator, denominator));
70
+ if let Some(peek) = parser.peek_clone() {
71
+ if peek.line == current_line {
72
+ if let Some(TokenKind::Slash) = parser.peek_kind() {
73
+ parser.advance();
74
+ if let Some(denominator_token) = parser.peek_clone() {
75
+ if denominator_token.line == current_line && denominator_token.kind == TokenKind::Number {
76
+ let denominator = denominator_token.lexeme.clone();
77
+ parser.advance();
78
+ duration = Duration::Beat(format!("{}/{}", numerator, denominator));
79
+ }
80
+ }
81
+ } else {
82
+ duration = parse_duration(numerator);
83
+ }
84
+ } else {
85
+ duration = parse_duration(numerator);
65
86
  }
87
+ } else {
88
+ duration = parse_duration(numerator);
66
89
  }
67
- } else {
68
- duration = parse_duration(numerator);
69
- }
70
- if let Some(next) = parser.peek_clone() {
71
- if next.kind == TokenKind::LBrace {
72
- value = parser.parse_map_value().unwrap_or(Value::Null);
90
+ if let Some(next) = parser.peek_clone() {
91
+ if next.line == current_line && next.kind == TokenKind::LBrace {
92
+ value = parser.parse_map_value().unwrap_or(Value::Null);
93
+ }
73
94
  }
74
- }
75
95
  }
76
96
  TokenKind::Identifier => {
77
97
  let id = token.lexeme.clone();
78
98
  parser.advance();
79
99
  duration = parse_duration(id);
80
- if let Some(next) = parser.peek_clone() {
81
- if next.kind == TokenKind::LBrace {
82
- value = parser.parse_map_value().unwrap_or(Value::Null);
100
+ if let Some(next) = parser.peek_clone() {
101
+ if next.line == current_line && next.kind == TokenKind::LBrace {
102
+ value = parser.parse_map_value().unwrap_or(Value::Null);
103
+ }
83
104
  }
84
- }
85
105
  }
86
106
  TokenKind::LBrace => {
87
- value = parser.parse_map_value().unwrap_or(Value::Null);
107
+ value = parser.parse_map_value().unwrap_or(Value::Null);
108
+ }
109
+ _ => {}
88
110
  }
89
- _ => {}
90
111
  }
91
112
  }
92
113
 
@@ -0,0 +1,48 @@
1
+ use std::path::Path;
2
+ use serde::Deserialize;
3
+
4
+ #[derive(Debug, Deserialize, Clone)]
5
+ pub struct PluginInfo {
6
+ pub name: String,
7
+ pub version: Option<String>,
8
+ pub description: Option<String>,
9
+ pub author: Option<String>,
10
+ }
11
+
12
+ #[derive(Debug, Deserialize, Clone)]
13
+ pub struct PluginFile {
14
+ pub plugin: PluginInfo,
15
+ }
16
+
17
+ pub fn load_plugin(name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
18
+ let root = Path::new(env!("CARGO_MANIFEST_DIR"));
19
+ let plugin_dir = root.join(".deva").join("plugin").join(name);
20
+ let toml_path = plugin_dir.join("plugin.toml");
21
+ let wasm_path = plugin_dir.join(format!("{}_bg.wasm", name));
22
+
23
+ if !toml_path.exists() {
24
+ return Err(format!("❌ Plugin file not found: {}", toml_path.display()));
25
+ }
26
+ if !wasm_path.exists() {
27
+ return Err(format!("❌ Plugin wasm not found: {}", wasm_path.display()));
28
+ }
29
+
30
+ let toml_content = std::fs::read_to_string(&toml_path)
31
+ .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
32
+ let plugin_file: PluginFile = toml::from_str(&toml_content)
33
+ .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
34
+
35
+ let wasm_bytes = std::fs::read(&wasm_path)
36
+ .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
37
+
38
+ Ok((plugin_file.plugin, wasm_bytes))
39
+ }
40
+
41
+ pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
42
+ if !uri.starts_with("devalang://plugin/") {
43
+ return Err("Invalid plugin URI".into());
44
+ }
45
+
46
+ let name = uri.trim_start_matches("devalang://plugin/");
47
+ load_plugin(name)
48
+ }
@@ -0,0 +1 @@
1
+ pub mod loader;
@@ -254,6 +254,8 @@ impl ModuleLoader {
254
254
  module: &mut Module,
255
255
  bank_name: &str
256
256
  ) -> Result<Module, String> {
257
+ let alias = bank_name.split('.').last().unwrap_or(bank_name);
258
+
257
259
  let bank_path = Path::new("./.deva/bank").join(bank_name);
258
260
  let bank_toml_path = bank_path.join("bank.toml");
259
261
 
@@ -277,23 +279,23 @@ impl ModuleLoader {
277
279
 
278
280
  bank_map.insert(bank_trigger.name.clone(), Value::String(bank_trigger_path.clone()));
279
281
 
280
- if module.variable_table.variables.contains_key(bank_name) {
282
+ if module.variable_table.variables.contains_key(alias) {
281
283
  eprintln!(
282
284
  "⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
283
- bank_name,
285
+ alias,
284
286
  module.path
285
287
  );
286
288
  continue;
287
289
  }
288
290
 
289
291
  module.variable_table.set(
290
- format!("{}.{}", bank_name, bank_trigger.name),
292
+ format!("{}.{}", alias, bank_trigger.name),
291
293
  Value::String(bank_trigger_path.clone())
292
294
  );
293
295
  }
294
296
 
295
297
  // Inject the map under the bank name
296
- module.variable_table.set(bank_name.to_string(), Value::Map(bank_map));
298
+ module.variable_table.set(alias.to_string(), Value::Map(bank_map));
297
299
 
298
300
  Ok(module.clone())
299
301
  }
@@ -0,0 +1,80 @@
1
+ use std::path::Path;
2
+ use crate::{
3
+ common::{ api::get_api_url },
4
+ installer::{ bank::install_bank, plugin::install_plugin },
5
+ };
6
+ use dirs::home_dir;
7
+
8
+ #[derive(Debug, Clone)]
9
+ pub enum AddonType {
10
+ Bank,
11
+ Plugin,
12
+ Preset,
13
+ }
14
+
15
+ pub async fn install_addon(
16
+ addon_type: AddonType,
17
+ name: &str,
18
+ target_dir: &Path
19
+ ) -> Result<(), String> {
20
+ match addon_type {
21
+ AddonType::Bank => install_bank(name, target_dir).await,
22
+ AddonType::Plugin => install_plugin(name, target_dir).await,
23
+ AddonType::Preset => Err("Preset installation not implemented".into()),
24
+ }
25
+ }
26
+
27
+ pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result<String, String> {
28
+ let api_url = get_api_url();
29
+
30
+ let mut stored_token_path = home_dir().unwrap();
31
+ stored_token_path.push(".devalang");
32
+ stored_token_path.push("session_token.json");
33
+
34
+ let stored_token = std::fs::read_to_string(&stored_token_path).unwrap_or_default();
35
+
36
+ let request_url = format!(
37
+ "{}/v1/assets/url?type={}&slug={}&token={}",
38
+ api_url,
39
+ match addon_type {
40
+ AddonType::Bank => "bank",
41
+ AddonType::Plugin => "plugin",
42
+ AddonType::Preset => "preset",
43
+ },
44
+ slug,
45
+ stored_token
46
+ );
47
+
48
+ let mut headers = reqwest::header::HeaderMap::new();
49
+
50
+ headers.insert("Authorization", format!("Bearer {}", stored_token).parse().unwrap());
51
+
52
+ let client: reqwest::Client = reqwest::Client
53
+ ::builder()
54
+ .default_headers(headers)
55
+ .build()
56
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
57
+
58
+ let req = client
59
+ .get(&request_url)
60
+ .send().await
61
+ .map_err(|_| "Failed to receive response".to_string())?;
62
+
63
+ let response_body: serde_json::Value = req
64
+ .json().await
65
+ .map_err(|_| "Failed to read response body".to_string())?;
66
+
67
+ let signed_url: String = serde_json
68
+ ::from_value(
69
+ response_body
70
+ .get("payload")
71
+ .cloned()
72
+ .unwrap_or_default()
73
+ .get("url")
74
+ .cloned()
75
+ .unwrap_or_default()
76
+ )
77
+ .map_err(|_| "Failed to parse response body".to_string())?;
78
+
79
+ Ok(signed_url)
80
+ }
@@ -1,33 +1,39 @@
1
1
  use std::path::{ Path, PathBuf };
2
2
  use crate::{
3
- common::cdn::get_cdn_url,
4
3
  config::loader::{ add_bank_to_config, load_config },
5
- installer::utils::{ download_file, extract_archive },
4
+ installer::{
5
+ addon::{ ask_api_for_signed_url, AddonType },
6
+ utils::{ download_file, extract_archive },
7
+ },
8
+ utils::logger::{ LogLevel, Logger },
6
9
  };
7
10
 
8
11
  pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
9
- let cdn_url = get_cdn_url();
10
- let url = format!("{}/bank/{}", cdn_url, name);
12
+ let logger = Logger::new();
13
+
14
+ let signed_url = ask_api_for_signed_url(AddonType::Bank, name).await?;
11
15
 
12
16
  let bank_dir = target_dir.join("bank");
13
17
  let archive_path = PathBuf::from(format!("./.deva/tmp/{}.devabank", name));
14
18
  let extract_path = bank_dir.join(name);
15
19
 
20
+ download_file(&signed_url, &archive_path).await.map_err(|e|
21
+ format!("Failed to download: {}", e)
22
+ )?;
23
+
16
24
  if extract_path.exists() {
17
- println!(
18
- "Bank '{}' already exists at '{}'. Skipping install.",
19
- name,
20
- extract_path.display()
25
+ logger.log_message(
26
+ LogLevel::Warning,
27
+ &format!(
28
+ "Bank '{}' already exists at '{}'. Skipping install.",
29
+ name,
30
+ extract_path.display()
31
+ )
21
32
  );
33
+
22
34
  return Ok(());
23
35
  }
24
36
 
25
- download_file(&url, &archive_path).await.map_err(|e| format!("Failed to download: {}", e))?;
26
-
27
- extract_archive(&archive_path, &extract_path).await.map_err(|e|
28
- format!("Failed to extract: {}", e)
29
- )?;
30
-
31
37
  // Add the bank to the config
32
38
  let root_dir = target_dir
33
39
  .parent()
@@ -49,6 +55,10 @@ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
49
55
 
50
56
  let dependency_path = &format!("devalang://bank/{}", name);
51
57
 
58
+ extract_archive(&archive_path, &extract_path).await.map_err(|e|
59
+ format!("Failed to extract: {}", e)
60
+ )?;
61
+
52
62
  add_bank_to_config(&mut config, &extract_path, &dependency_path);
53
63
 
54
64
  Ok(())
@@ -1,2 +1,5 @@
1
1
  pub mod bank;
2
- pub mod utils;
2
+ pub mod plugin;
3
+
4
+ pub mod utils;
5
+ pub mod addon;