@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
@@ -3,6 +3,7 @@ use devalang_utils::path as path_utils;
3
3
 
4
4
  pub async fn install_selected_addons(
5
5
  addons: Vec<DiscoveredAddon>,
6
+ no_clear_tmp: bool,
6
7
  ) -> Result<Vec<AddonWithMetadata>, String> {
7
8
  let mut addons_enriched = Vec::new();
8
9
 
@@ -16,14 +17,72 @@ pub async fn install_selected_addons(
16
17
  devalang_utils::file::extract_zip_safely(&addon.path, &addon_path)
17
18
  .map_err(|e| format!("Failed to extract addon {}: {}", addon.name, e))?;
18
19
 
20
+ // Read metadata from the extracted addon first so we can layout as <publisher>/<name>
21
+ // If the addon type is unknown (we found a .tar.gz), try to detect the
22
+ // metadata file inside the extracted folder to discover the real type.
23
+ let mut detected_addon_type = addon.addon_type.clone();
24
+ let mut parsed_meta_tmp = None;
25
+
26
+ if detected_addon_type == "unknown" {
27
+ // try detection by checking common metadata filenames
28
+ let candidates = ["bank.toml", "plugin.toml", "preset.toml", "template.toml"];
29
+ for candidate in &candidates {
30
+ let path = addon_path.join(candidate);
31
+ if path.exists() {
32
+ detected_addon_type = match *candidate {
33
+ "bank.toml" => "bank".to_string(),
34
+ "plugin.toml" => "plugin".to_string(),
35
+ "preset.toml" => "preset".to_string(),
36
+ "template.toml" => "template".to_string(),
37
+ _ => "unknown".to_string(),
38
+ };
39
+ if let Ok(content) = std::fs::read_to_string(&path) {
40
+ parsed_meta_tmp = crate::cli::discover::metadata::parse_metadata_file(
41
+ &detected_addon_type,
42
+ &content,
43
+ );
44
+ }
45
+ break;
46
+ }
47
+ }
48
+ } else {
49
+ let addon_metadata_filename = match addon.addon_type.as_str() {
50
+ "bank" => "bank.toml",
51
+ "plugin" => "plugin.toml",
52
+ "preset" => "preset.toml",
53
+ "template" => "template.toml",
54
+ _ => "",
55
+ };
56
+ if !addon_metadata_filename.is_empty() {
57
+ let addon_metadata_path_tmp = addon_path.join(addon_metadata_filename);
58
+ let addon_metadata_content_tmp =
59
+ std::fs::read_to_string(&addon_metadata_path_tmp).ok();
60
+ if let Some(content) = addon_metadata_content_tmp {
61
+ parsed_meta_tmp = crate::cli::discover::metadata::parse_metadata_file(
62
+ &addon.addon_type,
63
+ &content,
64
+ );
65
+ }
66
+ }
67
+ }
68
+
19
69
  let base = path_utils::ensure_deva_dir()?;
20
- let target_addon_dir = match addon.addon_type.as_str() {
70
+ // Use detected_addon_type (may have been found from metadata)
71
+ // final_addon_type is what we will report and use for metadata lookup
72
+ let final_addon_type = if detected_addon_type == "unknown" {
73
+ addon.addon_type.clone()
74
+ } else {
75
+ detected_addon_type.clone()
76
+ };
77
+
78
+ let target_addon_dir = match final_addon_type.as_str() {
21
79
  "bank" => base.join("banks"),
22
80
  "plugin" => base.join("plugins"),
23
81
  "preset" => base.join("presets"),
24
82
  "template" => base.join("templates"),
25
83
  _ => {
26
- return Err(format!("Unknown addon type for addon {}", addon.name));
84
+ // Fallback: place unknown archives into plugins by default
85
+ base.join("plugins")
27
86
  }
28
87
  };
29
88
 
@@ -34,7 +93,30 @@ pub async fn install_selected_addons(
34
93
  )
35
94
  })?;
36
95
 
37
- let target_addon_path_dir = target_addon_dir.join(&addon.name);
96
+ // prefer parsed metadata publisher/name, fall back to discovered publisher/name
97
+ let publisher_folder = parsed_meta_tmp
98
+ .as_ref()
99
+ .and_then(|m| {
100
+ if !m.publisher.is_empty() {
101
+ Some(m.publisher.clone())
102
+ } else {
103
+ None
104
+ }
105
+ })
106
+ .unwrap_or_else(|| addon.publisher.clone());
107
+
108
+ let name_folder = parsed_meta_tmp
109
+ .as_ref()
110
+ .and_then(|m| {
111
+ if !m.name.is_empty() {
112
+ Some(m.name.clone())
113
+ } else {
114
+ None
115
+ }
116
+ })
117
+ .unwrap_or_else(|| addon.name.clone());
118
+
119
+ let target_addon_path_dir = target_addon_dir.join(&publisher_folder).join(&name_folder);
38
120
  if target_addon_path_dir.exists() {
39
121
  println!(
40
122
  "Target addon directory {} already exists",
@@ -54,37 +136,51 @@ pub async fn install_selected_addons(
54
136
  )?;
55
137
  let _ = std::fs::remove_dir_all(&addon_path);
56
138
  }
57
-
58
- let addon_metadata_filename = match addon.addon_type.as_str() {
139
+ // Attempt to read final metadata file (use parsed_tmp as fallback)
140
+ let addon_metadata_filename = match final_addon_type.as_str() {
59
141
  "bank" => "bank.toml",
60
142
  "plugin" => "plugin.toml",
61
143
  "preset" => "preset.toml",
62
144
  "template" => "template.toml",
63
- _ => {
64
- return Err(format!("Unknown addon type for addon {}", addon.name));
65
- }
145
+ _ => "",
66
146
  };
67
147
 
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
- });
148
+ let parsed_meta = if addon_metadata_filename.is_empty() {
149
+ parsed_meta_tmp.unwrap_or(AddonMetadata {
150
+ name: "".to_string(),
151
+ publisher: "".to_string(),
152
+ version: "".to_string(),
153
+ description: "".to_string(),
154
+ access: "".to_string(),
155
+ })
156
+ } else {
157
+ let addon_metadata_path = target_addon_path_dir.join(addon_metadata_filename);
158
+ if let Ok(content) = std::fs::read_to_string(&addon_metadata_path) {
159
+ crate::cli::discover::metadata::parse_metadata_file(&final_addon_type, &content)
160
+ .unwrap_or(parsed_meta_tmp.unwrap_or(AddonMetadata {
161
+ name: "".to_string(),
162
+ publisher: "".to_string(),
163
+ version: "".to_string(),
164
+ description: "".to_string(),
165
+ access: "".to_string(),
166
+ }))
167
+ } else {
168
+ parsed_meta_tmp.unwrap_or(AddonMetadata {
169
+ name: "".to_string(),
170
+ publisher: "".to_string(),
171
+ version: "".to_string(),
172
+ description: "".to_string(),
173
+ access: "".to_string(),
174
+ })
175
+ }
176
+ };
83
177
 
178
+ // Record location as the final moved directory path
84
179
  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(),
180
+ name: name_folder.clone(),
181
+ publisher: publisher_folder.clone(),
182
+ path: target_addon_path_dir.clone().to_string_lossy().to_string(),
183
+ addon_type: final_addon_type.clone(),
88
184
  metadata: parsed_meta,
89
185
  });
90
186
 
@@ -96,8 +192,23 @@ pub async fn install_selected_addons(
96
192
  }
97
193
  }
98
194
 
99
- // Best-effort cleanup of temporary extraction directory
100
- let _ = std::fs::remove_dir_all(&tmp_dir);
195
+ // Best-effort cleanup of temporary extraction directory: remove `.deva/tmp`
196
+ // only if it's empty after installing the selected addons and when the
197
+ // user did not pass --no-clear-tmp.
198
+ if !no_clear_tmp {
199
+ if tmp_dir.exists() {
200
+ match std::fs::read_dir(&tmp_dir) {
201
+ Ok(mut rd) => {
202
+ if rd.next().is_none() {
203
+ let _ = std::fs::remove_dir_all(&tmp_dir);
204
+ }
205
+ }
206
+ Err(_) => {
207
+ // ignore errors here - best-effort
208
+ }
209
+ }
210
+ }
211
+ }
101
212
 
102
213
  Ok(addons_enriched)
103
214
  }
@@ -27,8 +27,8 @@ pub fn parse_metadata_file(addon_type: &str, metadata_content: &str) -> Option<A
27
27
  .and_then(|v| v.as_str())
28
28
  .unwrap_or("")
29
29
  .to_string();
30
- let author = table
31
- .get("author")
30
+ let publisher = table
31
+ .get("publisher")
32
32
  .and_then(|v| v.as_str())
33
33
  .unwrap_or("unknown")
34
34
  .to_string();
@@ -40,7 +40,7 @@ pub fn parse_metadata_file(addon_type: &str, metadata_content: &str) -> Option<A
40
40
 
41
41
  Some(AddonMetadata {
42
42
  name,
43
- author,
43
+ publisher,
44
44
  version,
45
45
  description,
46
46
  access,
@@ -1,124 +1,124 @@
1
- use crate::config::settings::set_user_config_value;
2
- use crate::web::sso::get_sso_url;
3
- use std::str::FromStr;
4
- use tiny_http::Header;
5
- use tiny_http::{Response, Server};
6
- use webbrowser;
7
-
8
- /// Handle the login command
9
- /// This function initiates the login process by opening the browser and waiting for the callback.
10
- #[cfg(feature = "cli")]
11
- pub async fn handle_login_command() -> Result<(), String> {
12
- use devalang_utils::logger::LogLevel;
13
- use devalang_utils::logger::Logger;
14
- use devalang_utils::spinner::start_spinner;
15
-
16
- let logger = Logger::new();
17
-
18
- let mut listener_port = 7878;
19
-
20
- let test_port_already_in_use = format!("127.0.0.1:{}", listener_port);
21
- while std::net::TcpListener::bind(&test_port_already_in_use).is_err() {
22
- listener_port += 1;
23
- }
24
-
25
- let redirect_uri = format!("http://127.0.0.1:{}/callback", listener_port);
26
- let login_url = format!(
27
- "{}/?response_type=code&referer=cli&redirect_uri={}",
28
- get_sso_url(),
29
- redirect_uri
30
- );
31
-
32
- if webbrowser::open(&login_url).is_ok() {
33
- logger.log_message(LogLevel::Info, "Opening browser for login...");
34
- logger.log_message(
35
- LogLevel::Info,
36
- &format!(
37
- "If the browser does not open, please visit the following URL: {}",
38
- login_url
39
- ),
40
- );
41
- } else {
42
- logger.log_message(
43
- LogLevel::Info,
44
- "Please open the following URL in your browser to login:",
45
- );
46
- logger.log_message(LogLevel::Info, &login_url);
47
- }
48
-
49
- let server = Server::http(format!("127.0.0.1:{}", listener_port)).unwrap();
50
-
51
- let spinner = start_spinner("Waiting for authentication...");
52
-
53
- for request in server.incoming_requests() {
54
- let query = request.url().to_string();
55
- if request.url().starts_with("/callback") {
56
- if query.contains("session=") || query.contains("error=") {
57
- let token = query.split("session=").nth(1).unwrap_or("").to_string();
58
-
59
- if !token.is_empty() {
60
- let response_html = r#"
61
- <html>
62
- <body>
63
- <h1>Authentication Successful</h1>
64
- <h2>You can now close this window.</h2>
65
- </body>
66
- </html>
67
- "#;
68
-
69
- let response = Response::from_string(response_html)
70
- .with_status_code(200)
71
- .with_header(Header::from_str("Content-Type: text/html").unwrap());
72
-
73
- request.respond(response).unwrap();
74
-
75
- save_token(&token);
76
-
77
- spinner.finish_and_clear();
78
-
79
- logger.log_message(
80
- LogLevel::Success,
81
- "Authentication successful. Token saved to ~/.devalang/config.json",
82
- );
83
-
84
- break;
85
- } else {
86
- spinner.finish_and_clear();
87
- logger.log_message(LogLevel::Error, "Invalid session token.");
88
- request
89
- .respond(Response::from_string("Invalid session token."))
90
- .unwrap();
91
-
92
- break;
93
- }
94
- } else {
95
- println!("Invalid callback: {}", request.url());
96
-
97
- spinner.finish_and_clear();
98
- logger.log_message(LogLevel::Error, "Invalid callback.");
99
- request
100
- .respond(Response::from_string("Invalid callback."))
101
- .unwrap();
102
-
103
- break;
104
- }
105
- } else if request.url().starts_with("/favicon.ico") {
106
- // Ignore favicon requests
107
- } else {
108
- spinner.finish_and_clear();
109
- logger.log_message(LogLevel::Error, "Invalid request.");
110
- request
111
- .respond(Response::from_string("Invalid request."))
112
- .unwrap();
113
-
114
- break;
115
- }
116
- }
117
-
118
- Ok(())
119
- }
120
-
121
- /// Save the session token to a file in the user's home directory
122
- fn save_token(token: &str) {
123
- set_user_config_value("session", serde_json::Value::String(token.to_string()));
124
- }
1
+ use crate::config::settings::set_user_config_value;
2
+ use crate::web::sso::get_sso_url;
3
+ use std::str::FromStr;
4
+ use tiny_http::Header;
5
+ use tiny_http::{Response, Server};
6
+ use webbrowser;
7
+
8
+ /// Handle the login command
9
+ /// This function initiates the login process by opening the browser and waiting for the callback.
10
+ #[cfg(feature = "cli")]
11
+ pub async fn handle_login_command() -> Result<(), String> {
12
+ use devalang_utils::logger::LogLevel;
13
+ use devalang_utils::logger::Logger;
14
+ use devalang_utils::spinner::start_spinner;
15
+
16
+ let logger = Logger::new();
17
+
18
+ let mut listener_port = 7878;
19
+
20
+ let test_port_already_in_use = format!("127.0.0.1:{}", listener_port);
21
+ while std::net::TcpListener::bind(&test_port_already_in_use).is_err() {
22
+ listener_port += 1;
23
+ }
24
+
25
+ let redirect_uri = format!("http://127.0.0.1:{}/callback", listener_port);
26
+ let login_url = format!(
27
+ "{}/?response_type=code&referer=cli&redirect_uri={}",
28
+ get_sso_url(),
29
+ redirect_uri
30
+ );
31
+
32
+ if webbrowser::open(&login_url).is_ok() {
33
+ logger.log_message(LogLevel::Info, "Opening browser for login...");
34
+ logger.log_message(
35
+ LogLevel::Info,
36
+ &format!(
37
+ "If the browser does not open, please visit the following URL: {}",
38
+ login_url
39
+ ),
40
+ );
41
+ } else {
42
+ logger.log_message(
43
+ LogLevel::Info,
44
+ "Please open the following URL in your browser to login:",
45
+ );
46
+ logger.log_message(LogLevel::Info, &login_url);
47
+ }
48
+
49
+ let server = Server::http(format!("127.0.0.1:{}", listener_port)).unwrap();
50
+
51
+ let spinner = start_spinner("Waiting for authentication...");
52
+
53
+ for request in server.incoming_requests() {
54
+ let query = request.url().to_string();
55
+ if request.url().starts_with("/callback") {
56
+ if query.contains("session=") || query.contains("error=") {
57
+ let token = query.split("session=").nth(1).unwrap_or("").to_string();
58
+
59
+ if !token.is_empty() {
60
+ let response_html = r#"
61
+ <html>
62
+ <body>
63
+ <h1>Authentication Successful</h1>
64
+ <h2>You can now close this window.</h2>
65
+ </body>
66
+ </html>
67
+ "#;
68
+
69
+ let response = Response::from_string(response_html)
70
+ .with_status_code(200)
71
+ .with_header(Header::from_str("Content-Type: text/html").unwrap());
72
+
73
+ request.respond(response).unwrap();
74
+
75
+ save_token(&token);
76
+
77
+ spinner.finish_and_clear();
78
+
79
+ logger.log_message(
80
+ LogLevel::Success,
81
+ "Authentication successful. Token saved to ~/.devalang/config.json",
82
+ );
83
+
84
+ break;
85
+ } else {
86
+ spinner.finish_and_clear();
87
+ logger.log_message(LogLevel::Error, "Invalid session token.");
88
+ request
89
+ .respond(Response::from_string("Invalid session token."))
90
+ .unwrap();
91
+
92
+ break;
93
+ }
94
+ } else {
95
+ println!("Invalid callback: {}", request.url());
96
+
97
+ spinner.finish_and_clear();
98
+ logger.log_message(LogLevel::Error, "Invalid callback.");
99
+ request
100
+ .respond(Response::from_string("Invalid callback."))
101
+ .unwrap();
102
+
103
+ break;
104
+ }
105
+ } else if request.url().starts_with("/favicon.ico") {
106
+ // Ignore favicon requests
107
+ } else {
108
+ spinner.finish_and_clear();
109
+ logger.log_message(LogLevel::Error, "Invalid request.");
110
+ request
111
+ .respond(Response::from_string("Invalid request."))
112
+ .unwrap();
113
+
114
+ break;
115
+ }
116
+ }
117
+
118
+ Ok(())
119
+ }
120
+
121
+ /// Save the session token to a file in the user's home directory
122
+ fn save_token(token: &str) {
123
+ set_user_config_value("session", serde_json::Value::String(token.to_string()));
124
+ }
@@ -0,0 +1,52 @@
1
+ pub async fn handle_me_command() -> Result<(), String> {
2
+ let auth_url = crate::web::auth::get_auth_url();
3
+
4
+ let url = format!("{}/v1/auth/me", auth_url);
5
+
6
+ let client = reqwest::Client::new();
7
+ let mut req = client.get(url);
8
+
9
+ if let Some(user) = crate::config::settings::get_user_config() {
10
+ if !user.session.is_empty() {
11
+ req = req.bearer_auth(user.session);
12
+ }
13
+ } else {
14
+ return Err("No user session found. Please log in.".to_string());
15
+ }
16
+
17
+ let response = req.send().await.map_err(|e| e.to_string())?;
18
+
19
+ if !response.status().is_success() {
20
+ return Err(format!("Failed to get user info: HTTP {}", response.status()).into());
21
+ }
22
+
23
+ let status = response.status();
24
+ let body_text = response
25
+ .text()
26
+ .await
27
+ .map_err(|e| format!("Failed to read response body: {}", e))?;
28
+
29
+ let json: serde_json::Value = match serde_json::from_str(&body_text) {
30
+ Ok(v) => v,
31
+ Err(_) => {
32
+ return Err(format!(
33
+ "Invalid JSON response (status {}): {}",
34
+ status, body_text
35
+ ));
36
+ }
37
+ };
38
+
39
+ let payload = json.get("payload");
40
+
41
+ let email = payload
42
+ .unwrap_or(&serde_json::Value::Null)
43
+ .get("userData")
44
+ .unwrap_or(&serde_json::Value::Null)
45
+ .get("email")
46
+ .and_then(|v| v.as_str())
47
+ .unwrap_or("unknown");
48
+
49
+ println!("Logged in as: {}", email);
50
+
51
+ Ok(())
52
+ }
@@ -0,0 +1 @@
1
+ pub mod commands;
package/rust/cli/mod.rs CHANGED
@@ -1,10 +1,10 @@
1
- pub mod bank;
1
+ pub mod addon;
2
2
  pub mod build;
3
3
  pub mod check;
4
4
  pub mod discover;
5
5
  pub mod init;
6
- pub mod install;
7
6
  pub mod login;
7
+ pub mod me;
8
8
  pub mod parser;
9
9
  pub mod play;
10
10
  pub mod telemetry;