@devaloop/devalang 0.0.1-alpha.1 → 0.0.1-alpha.11

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 (168) hide show
  1. package/.devalang +9 -0
  2. package/Cargo.toml +15 -6
  3. package/README.md +79 -81
  4. package/docs/CHANGELOG.md +213 -0
  5. package/docs/ROADMAP.md +11 -8
  6. package/docs/TODO.md +32 -29
  7. package/examples/bank.deva +9 -0
  8. package/examples/condition.deva +20 -0
  9. package/examples/duration.deva +9 -0
  10. package/examples/group.deva +12 -0
  11. package/examples/index.deva +12 -5
  12. package/examples/loop.deva +16 -0
  13. package/examples/samples/hat-808.wav +0 -0
  14. package/examples/synth.deva +14 -0
  15. package/examples/variables.deva +9 -0
  16. package/out-tsc/bin/devalang.exe +0 -0
  17. package/out-tsc/scripts/version/fetch.js +1 -5
  18. package/package.json +5 -4
  19. package/project-version.json +3 -3
  20. package/rust/cli/bank.rs +455 -0
  21. package/rust/cli/build.rs +114 -28
  22. package/rust/cli/check.rs +96 -103
  23. package/rust/cli/driver.rs +280 -0
  24. package/rust/cli/init.rs +79 -0
  25. package/rust/cli/install.rs +17 -0
  26. package/rust/cli/mod.rs +8 -1
  27. package/rust/cli/play.rs +193 -0
  28. package/rust/cli/template.rs +57 -0
  29. package/rust/cli/update.rs +4 -0
  30. package/rust/common/cdn.rs +11 -0
  31. package/rust/common/mod.rs +1 -0
  32. package/rust/config/driver.rs +76 -0
  33. package/rust/config/loader.rs +110 -0
  34. package/rust/config/mod.rs +2 -0
  35. package/rust/core/audio/engine.rs +242 -0
  36. package/rust/core/audio/evaluator.rs +31 -0
  37. package/rust/core/audio/interpreter/arrow_call.rs +142 -0
  38. package/rust/core/audio/interpreter/call.rs +70 -0
  39. package/rust/core/audio/interpreter/condition.rs +69 -0
  40. package/rust/core/audio/interpreter/driver.rs +236 -0
  41. package/rust/core/audio/interpreter/let_.rs +19 -0
  42. package/rust/core/audio/interpreter/load.rs +18 -0
  43. package/rust/core/audio/interpreter/loop_.rs +67 -0
  44. package/rust/core/audio/interpreter/mod.rs +12 -0
  45. package/rust/core/audio/interpreter/sleep.rs +36 -0
  46. package/rust/core/audio/interpreter/spawn.rs +84 -0
  47. package/rust/core/audio/interpreter/tempo.rs +16 -0
  48. package/rust/core/audio/interpreter/trigger.rs +102 -0
  49. package/rust/core/audio/loader/mod.rs +1 -0
  50. package/rust/core/audio/loader/trigger.rs +64 -0
  51. package/rust/core/audio/mod.rs +6 -0
  52. package/rust/core/audio/player.rs +54 -0
  53. package/rust/core/audio/renderer.rs +54 -0
  54. package/rust/core/builder/mod.rs +70 -27
  55. package/rust/core/debugger/lexer.rs +27 -0
  56. package/rust/core/debugger/mod.rs +13 -49
  57. package/rust/core/debugger/preprocessor.rs +27 -0
  58. package/rust/core/debugger/store.rs +25 -0
  59. package/rust/core/error/mod.rs +60 -0
  60. package/rust/core/lexer/handler/arrow.rs +31 -0
  61. package/rust/core/lexer/handler/at.rs +21 -0
  62. package/rust/core/lexer/handler/brace.rs +41 -0
  63. package/rust/core/lexer/handler/colon.rs +21 -0
  64. package/rust/core/lexer/handler/comment.rs +30 -0
  65. package/rust/core/lexer/handler/dot.rs +21 -0
  66. package/rust/core/lexer/handler/driver.rs +241 -0
  67. package/rust/core/lexer/handler/identifier.rs +41 -0
  68. package/rust/core/lexer/handler/indent.rs +52 -0
  69. package/rust/core/lexer/handler/mod.rs +15 -0
  70. package/rust/core/lexer/handler/newline.rs +23 -0
  71. package/rust/core/lexer/handler/number.rs +31 -0
  72. package/rust/core/lexer/handler/operator.rs +44 -0
  73. package/rust/core/lexer/handler/slash.rs +21 -0
  74. package/rust/core/lexer/handler/string.rs +63 -0
  75. package/rust/core/lexer/mod.rs +37 -319
  76. package/rust/core/lexer/token.rs +87 -0
  77. package/rust/core/mod.rs +6 -2
  78. package/rust/core/parser/driver.rs +339 -0
  79. package/rust/core/parser/handler/arrow_call.rs +151 -0
  80. package/rust/core/parser/handler/at.rs +162 -0
  81. package/rust/core/parser/handler/bank.rs +41 -0
  82. package/rust/core/parser/handler/condition.rs +74 -0
  83. package/rust/core/parser/handler/dot.rs +178 -0
  84. package/rust/core/parser/handler/identifier/call.rs +41 -0
  85. package/rust/core/parser/handler/identifier/group.rs +75 -0
  86. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  87. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  88. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  89. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  90. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  91. package/rust/core/parser/handler/loop_.rs +72 -0
  92. package/rust/core/parser/handler/mod.rs +8 -0
  93. package/rust/core/parser/handler/tempo.rs +47 -0
  94. package/rust/core/parser/mod.rs +3 -200
  95. package/rust/core/parser/statement.rs +96 -0
  96. package/rust/core/preprocessor/loader.rs +308 -0
  97. package/rust/core/preprocessor/mod.rs +2 -24
  98. package/rust/core/preprocessor/module.rs +42 -56
  99. package/rust/core/preprocessor/processor.rs +76 -0
  100. package/rust/core/preprocessor/resolver/bank.rs +41 -51
  101. package/rust/core/preprocessor/resolver/call.rs +123 -0
  102. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  103. package/rust/core/preprocessor/resolver/driver.rs +232 -0
  104. package/rust/core/preprocessor/resolver/group.rs +61 -0
  105. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  106. package/rust/core/preprocessor/resolver/loop_.rs +76 -67
  107. package/rust/core/preprocessor/resolver/mod.rs +12 -111
  108. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  109. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  110. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  111. package/rust/core/preprocessor/resolver/trigger.rs +90 -154
  112. package/rust/core/preprocessor/resolver/value.rs +78 -0
  113. package/rust/core/shared/bank.rs +21 -0
  114. package/rust/core/shared/duration.rs +9 -0
  115. package/rust/core/shared/mod.rs +3 -0
  116. package/rust/core/shared/value.rs +29 -0
  117. package/rust/core/store/export.rs +28 -0
  118. package/rust/core/store/global.rs +39 -0
  119. package/rust/core/store/import.rs +28 -0
  120. package/rust/core/store/mod.rs +4 -0
  121. package/rust/core/store/variable.rs +28 -0
  122. package/rust/core/utils/mod.rs +2 -0
  123. package/rust/core/utils/path.rs +31 -0
  124. package/rust/core/utils/validation.rs +37 -0
  125. package/rust/installer/bank.rs +55 -0
  126. package/rust/installer/mod.rs +1 -0
  127. package/rust/lib.rs +162 -1
  128. package/rust/main.rs +104 -31
  129. package/rust/utils/file.rs +35 -0
  130. package/rust/utils/installer.rs +56 -0
  131. package/rust/utils/logger.rs +108 -34
  132. package/rust/utils/mod.rs +5 -3
  133. package/rust/utils/{loader.rs → spinner.rs} +2 -0
  134. package/rust/utils/watcher.rs +33 -0
  135. package/templates/minimal/.devalang +5 -0
  136. package/templates/minimal/README.md +202 -0
  137. package/templates/minimal/src/index.deva +2 -0
  138. package/templates/welcome/.devalang +5 -0
  139. package/templates/welcome/README.md +202 -0
  140. package/templates/welcome/samples/kick-808.wav +0 -0
  141. package/templates/welcome/src/index.deva +13 -0
  142. package/templates/welcome/src/variables.deva +5 -0
  143. package/typescript/scripts/version/fetch.ts +1 -6
  144. package/docs/COMMANDS.md +0 -31
  145. package/docs/SYNTAX.md +0 -148
  146. package/examples/exported.deva +0 -7
  147. package/rust/audio/mod.rs +0 -1
  148. package/rust/cli/new.rs +0 -1
  149. package/rust/core/parser/at.rs +0 -142
  150. package/rust/core/parser/bank.rs +0 -42
  151. package/rust/core/parser/dot.rs +0 -107
  152. package/rust/core/parser/identifer.rs +0 -91
  153. package/rust/core/parser/loop_.rs +0 -62
  154. package/rust/core/parser/tempo.rs +0 -42
  155. package/rust/core/parser/variable.rs +0 -129
  156. package/rust/core/preprocessor/dependencies.rs +0 -54
  157. package/rust/core/preprocessor/resolver/at.rs +0 -24
  158. package/rust/core/types/cli.rs +0 -160
  159. package/rust/core/types/mod.rs +0 -7
  160. package/rust/core/types/module.rs +0 -41
  161. package/rust/core/types/parser.rs +0 -73
  162. package/rust/core/types/statement.rs +0 -105
  163. package/rust/core/types/store.rs +0 -116
  164. package/rust/core/types/token.rs +0 -83
  165. package/rust/core/types/variable.rs +0 -32
  166. package/rust/runner/executer.rs +0 -44
  167. package/rust/runner/mod.rs +0 -1
  168. package/rust/utils/path.rs +0 -46
@@ -0,0 +1,9 @@
1
+ # This file demonstrates the use of duration in Devalang.
2
+
3
+ @load "./samples/kick-808.wav" as kickCustom
4
+ @load "./samples/hat-808.wav" as hatCustom
5
+
6
+ .kickCustom 1/4
7
+ .hatCustom 1/8
8
+ .kickCustom 1/4
9
+ .hatCustom 1/8
@@ -0,0 +1,12 @@
1
+ # This file demonstrates the use of grouping in Devalang.
2
+
3
+ @import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
4
+
5
+ @load "./samples/kick-808.wav" as kickCustom
6
+ @load "./samples/hat-808.wav" as hatCustom
7
+
8
+ group myGroup:
9
+ .kickCustom duration params
10
+ .hatCustom duration params
11
+
12
+ @export { myGroup }
@@ -1,9 +1,16 @@
1
- @import { duration, default_bank, params, loopCount, tempo } from "./examples/exported.deva"
2
- @load "./examples/samples/kick-808.wav" as sample
1
+ # This file demonstrates the use of main features in Devalang.
2
+
3
+ @import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
4
+ @import { myLead } from "./synth.deva"
5
+ @import { myLoop } from "./loop.deva"
6
+
7
+ @load "./samples/kick-808.wav" as kickCustom
8
+ @load "./samples/hat-808.wav" as hatCustom
3
9
 
4
10
  bpm tempo
5
11
 
6
- bank default_bank
12
+ group myTrack:
13
+ spawn myLoop
14
+ spawn myLead
7
15
 
8
- loop loopCount:
9
- .sample duration params
16
+ call myTrack
@@ -0,0 +1,16 @@
1
+ # This file demonstrates the use of a loop in Devalang.
2
+
3
+ @import { duration, default_bank, params, loopCount, tempo } from "./variables.deva"
4
+
5
+ @load "./samples/kick-808.wav" as kickCustom
6
+ @load "./samples/hat-808.wav" as hatCustom
7
+
8
+ group myLoop:
9
+ loop loopCount:
10
+ .kickCustom duration params
11
+
12
+ # Uncomment the next line (.hatCustom) while executing "play" command
13
+ # with `--repeat` option to see magic happen !
14
+ # .hatCustom duration params
15
+
16
+ @export { myLoop }
Binary file
@@ -0,0 +1,14 @@
1
+ # This file defines a simple synth lead using Devalang's syntax.
2
+
3
+ group myLead:
4
+ let mySynth = synth sine
5
+
6
+ mySynth -> note(C4, { duration: 400 })
7
+ mySynth -> note(G4, { duration: 400 })
8
+ mySynth -> note(E4, { duration: 600 })
9
+ mySynth -> note(A4, { duration: 400 })
10
+ mySynth -> note(F4, { duration: 800 })
11
+ mySynth -> note(D4, { duration: 400 })
12
+ mySynth -> note(B3, { duration: 600 })
13
+
14
+ @export { myLead }
@@ -0,0 +1,9 @@
1
+ # This file only exports variables for use in other files.
2
+
3
+ let duration = auto
4
+ let default_bank = 808
5
+ let params = { decay: 10, delay: 30 }
6
+ let loopCount = 5
7
+ let tempo = 115
8
+
9
+ @export { duration, default_bank, params, loopCount, tempo }
Binary file
@@ -16,19 +16,15 @@ exports.fetchVersion = void 0;
16
16
  const fs_1 = __importDefault(require("fs"));
17
17
  const child_process_1 = require("child_process");
18
18
  const fetchVersion = (projectVersionPath) => __awaiter(void 0, void 0, void 0, function* () {
19
- // Lire le fichier
20
19
  const data = JSON.parse(fs_1.default.readFileSync(projectVersionPath, "utf-8"));
21
- // Incrémenter le numéro de build
22
20
  data.build = (data.build || 0) + 1;
23
- // Récupérer le dernier hash git
24
21
  try {
25
22
  const commit = (0, child_process_1.execSync)("git rev-parse HEAD").toString().trim();
26
23
  data.lastCommit = commit;
27
24
  }
28
25
  catch (err) {
29
- console.warn("⚠️ Impossible de récupérer le hash git.");
26
+ console.warn("⚠️ Unable to fetch git commit hash. Ensure you are in a git repository.");
30
27
  }
31
- // Écrire la mise à jour
32
28
  fs_1.default.writeFileSync(projectVersionPath, JSON.stringify(data, null, 2));
33
29
  });
34
30
  exports.fetchVersion = fetchVersion;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devaloop/devalang",
3
3
  "private": false,
4
- "version": "0.0.1-alpha.1",
4
+ "version": "0.0.1-alpha.11",
5
5
  "description": "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text.",
6
6
  "main": "out-tsc/index.js",
7
7
  "bin": {
@@ -9,13 +9,14 @@
9
9
  },
10
10
  "scripts": {
11
11
  "prepublish": "cargo build --release && npm run script:postbuild",
12
- "rust:dev": "cargo run",
13
12
  "rust:dev:build": "cargo run build --entry examples --output output",
14
13
  "rust:dev:check": "cargo run check --entry examples --output output",
14
+ "rust:wasm:web": "wasm-pack build --target=web --no-default-features",
15
+ "rust:wasm:node": "wasm-pack build --target=nodejs --no-default-features",
15
16
  "script:postbuild": "tsc && node out-tsc/scripts/postbuild.js",
16
17
  "script:version:bump": "tsc && node out-tsc/scripts/version/index.js"
17
18
  },
18
- "homepage": "https://devaloop.com",
19
+ "homepage": "https://devalang.com",
19
20
  "keywords": [
20
21
  "devalang",
21
22
  "music",
@@ -40,4 +41,4 @@
40
41
  "dependencies": {
41
42
  "@types/node": "^24.0.3"
42
43
  }
43
- }
44
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.0.1-alpha.1",
2
+ "version": "0.0.1-alpha.11",
3
3
  "channel": "alpha",
4
- "lastCommit": "",
5
- "build": 1
4
+ "lastCommit": "f643b05f7c5d97c5d22cb255c3641cfc9e385ea2",
5
+ "build": 10
6
6
  }
@@ -0,0 +1,455 @@
1
+ use std::fs;
2
+
3
+ use serde::{ Deserialize, Serialize };
4
+ use crate::{
5
+ common::cdn::get_cdn_url,
6
+ config::loader::{ load_config, remove_bank_from_config, update_bank_version_in_config },
7
+ core::shared::bank::{ BankFile, BankInfo },
8
+ installer::bank::install_bank,
9
+ };
10
+
11
+ #[derive(Debug, Deserialize)]
12
+ pub struct BankList {
13
+ bank: Vec<BankInfo>,
14
+ }
15
+
16
+ #[derive(Debug, Deserialize)]
17
+ pub struct BankInfoFetched {
18
+ pub name: String,
19
+ pub version: String,
20
+ pub description: String,
21
+ pub author: String,
22
+ pub latest_version: String,
23
+ }
24
+
25
+ #[derive(Debug, Deserialize)]
26
+ pub struct BankVersion {
27
+ pub version: String,
28
+ }
29
+
30
+ pub async fn handle_update_bank_command(name: Option<String>) -> Result<(), String> {
31
+ let deva_dir = std::path::Path::new("./.deva");
32
+ let bank_dir = deva_dir.join("bank");
33
+
34
+ if !bank_dir.exists() {
35
+ fs
36
+ ::create_dir_all(bank_dir.clone())
37
+ .map_err(|e| format!("Failed to create bank directory: {}", e))?;
38
+ }
39
+
40
+ if let Some(name) = name {
41
+ let bank_path = bank_dir.join(&name);
42
+ if !bank_path.exists() {
43
+ return Err(format!("Bank '{}' is not installed", name));
44
+ }
45
+
46
+ // Update specific bank
47
+ let latest_version = match fetch_latest_version(name.clone()).await {
48
+ Ok(version) => version,
49
+ Err(err) => {
50
+ eprintln!("❌ Error fetching latest version for '{}': {}", name, err);
51
+ return Err(format!("Failed to fetch latest version for '{}'", name));
52
+ }
53
+ };
54
+
55
+ let local_bank_info_path = bank_path.join("bank.toml");
56
+ let local_version = match
57
+ fs
58
+ ::read_to_string(&local_bank_info_path)
59
+ .ok()
60
+ .and_then(|content| toml::from_str::<BankFile>(&content).ok())
61
+ .map(|bf| bf.bank.version)
62
+ {
63
+ Some(version) => version,
64
+ None => {
65
+ eprintln!("⚠️ Unable to read local version for '{}', forcing reinstall...", name);
66
+ "".to_string() // Force update
67
+ }
68
+ };
69
+
70
+ if local_version != latest_version.version {
71
+ if let Err(e) = update_bank(&name, &latest_version.version).await {
72
+ eprintln!("❌ Error updating bank '{}': {}", name, e);
73
+ } else {
74
+ println!("✅ Bank '{}' updated to version '{}'", name, latest_version.version);
75
+ }
76
+ } else {
77
+ println!("Bank '{}' is already up-to-date (version {})", name, latest_version.version);
78
+
79
+ // Verify if the bank directory exists
80
+ if !bank_path.exists() {
81
+ eprintln!("❌ Bank directory for '{}' does not exist, reinstalling...", name);
82
+ if let Err(e) = install_bank(&name, deva_dir).await {
83
+ eprintln!("❌ Error reinstalling bank '{}': {}", name, e);
84
+ } else {
85
+ println!("✅ Bank '{}' reinstalled successfully!", name);
86
+ }
87
+ }
88
+ }
89
+ } else {
90
+ // Update all banks
91
+ let root_dir = deva_dir
92
+ .parent()
93
+ .ok_or_else(|| "Failed to determine root directory".to_string())?;
94
+
95
+ let config_path = root_dir.join(".devalang");
96
+ if !config_path.exists() {
97
+ return Err(
98
+ format!(
99
+ "Config file not found at '{}'. Please run 'devalang init' before adding an addon",
100
+ config_path.display()
101
+ )
102
+ );
103
+ }
104
+
105
+ let mut config = load_config(Some(&config_path)).ok_or_else(||
106
+ format!("Failed to load config from '{}'", config_path.display())
107
+ )?;
108
+
109
+ let config_banks = config.banks.clone();
110
+
111
+ // Install or update all banks
112
+
113
+ if let Some(banks) = config_banks {
114
+ for bank in banks {
115
+ let bank_name = bank.path
116
+ .strip_prefix("devalang://bank/")
117
+ .unwrap_or(&bank.path)
118
+ .to_string();
119
+
120
+ let latest_version = match fetch_latest_version(bank_name.clone()).await {
121
+ Ok(version) => version,
122
+ Err(err) => {
123
+ eprintln!("❌ Error fetching latest version for '{}': {}", bank_name, err);
124
+ continue;
125
+ }
126
+ };
127
+
128
+ if let Some(local_bank_version) = bank.version {
129
+ if latest_version.version != local_bank_version {
130
+ if let Err(e) = update_bank(&bank_name, &latest_version.version).await {
131
+ eprintln!("❌ Error updating bank '{}': {}", bank_name, e);
132
+ } else {
133
+ println!(
134
+ "✅ Bank '{}' updated to version '{}'",
135
+ bank_name,
136
+ latest_version.version
137
+ );
138
+ }
139
+ } else {
140
+ println!(
141
+ "Bank '{}' is already up-to-date (version {})",
142
+ bank_name,
143
+ local_bank_version
144
+ );
145
+
146
+ // Verify if the bank directory exists
147
+ let bank_path = bank_dir.join(&bank_name);
148
+
149
+ if !bank_path.exists() {
150
+ eprintln!("❌ Bank directory for '{}' does not exist, reinstalling...", bank_name);
151
+ if let Err(e) = install_bank(&bank_name, deva_dir).await {
152
+ eprintln!("❌ Error reinstalling bank '{}': {}", bank_name, e);
153
+ } else {
154
+ println!("✅ Bank '{}' reinstalled successfully!", bank_name);
155
+ }
156
+ }
157
+ }
158
+ } else {
159
+ // If the bank version is not specified in the config, install it
160
+ if let Err(e) = install_bank(&bank_name, deva_dir).await {
161
+ eprintln!("❌ Error installing bank '{}': {}", bank_name, e);
162
+ } else {
163
+ println!("✅ Bank '{}' installed successfully!", bank_name);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ Ok(())
171
+ }
172
+
173
+ async fn update_bank(bank_name: &str, latest_version: &str) -> Result<(), String> {
174
+ let deva_dir = std::path::Path::new("./.deva");
175
+
176
+ // First, delete the existing bank directory
177
+ let bank_dir = deva_dir.join("bank").join(bank_name);
178
+
179
+ if bank_dir.exists() {
180
+ std::fs::remove_dir_all(&bank_dir).unwrap_or_else(|_| {
181
+ eprintln!("⚠️ Failed to remove old bank directory for '{}', aborting !", bank_name);
182
+ std::process::exit(1);
183
+ });
184
+ }
185
+
186
+ // Now, install the new version
187
+ if let Err(e) = install_bank(bank_name, deva_dir).await {
188
+ eprintln!("❌ Error installing bank '{}': {}", bank_name, e);
189
+ } else {
190
+ println!("✅ Bank '{}' installed successfully!", bank_name);
191
+ }
192
+
193
+ let root_dir = deva_dir
194
+ .parent()
195
+ .ok_or_else(|| "Failed to determine root directory".to_string())?;
196
+
197
+ let config_path = root_dir.join(".devalang");
198
+ if !config_path.exists() {
199
+ return Err(
200
+ format!(
201
+ "Config file not found at '{}'. Please run 'devalang init' before adding an addon",
202
+ config_path.display()
203
+ )
204
+ );
205
+ }
206
+
207
+ let mut config = load_config(Some(&config_path)).ok_or_else(||
208
+ format!("Failed to load config from '{}'", config_path.display())
209
+ )?;
210
+
211
+ // Update the bank version in the config
212
+ update_bank_version_in_config(&mut config, bank_name, latest_version);
213
+
214
+ Ok(())
215
+ }
216
+
217
+ pub async fn handle_remove_bank_command(name: String) -> Result<(), String> {
218
+ let deva_dir = std::path::Path::new("./.deva");
219
+ let bank_dir = deva_dir.join("bank");
220
+
221
+ let bank_path = bank_dir.join(&name);
222
+
223
+ if !bank_path.exists() {
224
+ return Err(format!("Bank '{}' is not installed", name));
225
+ }
226
+
227
+ std::fs::remove_dir_all(&bank_path).map_err(|e| format!("Failed to remove bank: {}", e))?;
228
+
229
+ // Remove the bank from the config
230
+ let root_dir = deva_dir
231
+ .parent()
232
+ .ok_or_else(|| "Failed to determine root directory".to_string())?;
233
+
234
+ let config_path = root_dir.join(".devalang");
235
+ if !config_path.exists() {
236
+ return Err(
237
+ format!(
238
+ "Config file not found at '{}'. Please run 'devalang init' before adding an addon",
239
+ config_path.display()
240
+ )
241
+ );
242
+ }
243
+
244
+ let mut config = load_config(Some(&config_path)).ok_or_else(||
245
+ format!("Failed to load config from '{}'", config_path.display())
246
+ )?;
247
+
248
+ remove_bank_from_config(&mut config, &name);
249
+
250
+ println!("✅ Bank '{}' removed successfully", name);
251
+
252
+ Ok(())
253
+ }
254
+
255
+ pub async fn handle_bank_available_command() -> Result<(), String> {
256
+ let bank_list = match list_external_banks().await {
257
+ Ok(list) => list,
258
+ Err(err) => {
259
+ eprintln!("❌ Error fetching bank list: {}", err);
260
+ return Err("Failed to fetch bank list".into());
261
+ }
262
+ };
263
+
264
+ println!("Available banks for current project :");
265
+ println!(" ");
266
+
267
+ for bank in bank_list {
268
+ println!("📦 {}", bank.name);
269
+ println!(" - Version: {}", bank.version);
270
+ println!(" - Description: {}", bank.description);
271
+ println!(" - Author: {}", bank.author);
272
+ println!(" ");
273
+ }
274
+
275
+ Ok(())
276
+ }
277
+
278
+ pub async fn handle_bank_info_command(
279
+ name: String
280
+ ) -> Result<BankInfo, Box<dyn std::error::Error>> {
281
+ let cdn_url = get_cdn_url();
282
+ let url = format!("{}/bank/{}/info", cdn_url, name);
283
+
284
+ let response = match reqwest::get(&url).await {
285
+ Ok(resp) => resp,
286
+ Err(err) => {
287
+ return Err("Failed to fetch bank info".into());
288
+ }
289
+ };
290
+
291
+ if !response.status().is_success() {
292
+ return Err(format!("Failed to fetch bank info: HTTP {}", response.status()).into());
293
+ }
294
+
295
+ let bytes = match response.bytes().await {
296
+ Ok(b) => b,
297
+ Err(err) => {
298
+ eprintln!("❌ Error reading response body: {}", err);
299
+ return Err("Failed to read response body".into());
300
+ }
301
+ };
302
+
303
+ let parsed: BankInfo = serde_json::from_slice(&bytes)?;
304
+
305
+ println!("📦 Bank Info for '{}':", name);
306
+ println!(" - Name: {}", parsed.name);
307
+ println!(" - Version: {}", parsed.version);
308
+ println!(" - Description: {}", parsed.description);
309
+ println!(" - Author: {}", parsed.author);
310
+
311
+ Ok(parsed)
312
+ }
313
+
314
+ pub async fn handle_bank_list_command() -> Result<(), String> {
315
+ let bank_list = match list_installed_banks().await {
316
+ Ok(list) => list,
317
+ Err(err) => {
318
+ eprintln!("❌ Error fetching bank list: {}", err);
319
+ return Err("Failed to fetch bank list".into());
320
+ }
321
+ };
322
+
323
+ println!("Installed banks for current project :");
324
+
325
+ for bank_toml in bank_list {
326
+ let latest_version = match fetch_latest_version(bank_toml.bank.name.clone()).await {
327
+ Ok(version) => version,
328
+ Err(err) => {
329
+ eprintln!(
330
+ "❌ Error fetching latest version for '{}': {}",
331
+ bank_toml.bank.name,
332
+ err
333
+ );
334
+ continue;
335
+ }
336
+ };
337
+
338
+ let is_latest = if latest_version.version == bank_toml.bank.version { "✅" } else { "❗" };
339
+
340
+ println!(" ");
341
+ println!("📦 {}", bank_toml.bank.name);
342
+ println!(
343
+ " - Version: v{} {} (latest: v{})",
344
+ bank_toml.bank.version,
345
+ is_latest,
346
+ latest_version.version
347
+ );
348
+ println!(" - Description: {}", bank_toml.bank.description);
349
+ println!(" - Author: {}", bank_toml.bank.author);
350
+ }
351
+
352
+ Ok(())
353
+ }
354
+
355
+ async fn fetch_latest_version(
356
+ bank_name: String
357
+ ) -> Result<BankVersion, Box<dyn std::error::Error>> {
358
+ let cdn_url = get_cdn_url();
359
+ let url = format!("{}/bank/{}/version", cdn_url, bank_name);
360
+
361
+ let response = reqwest::get(url).await?;
362
+
363
+ if !response.status().is_success() {
364
+ return Err(format!("❌ Failed to fetch version: HTTP {}", response.status()).into());
365
+ }
366
+
367
+ let bytes = response.bytes().await?;
368
+
369
+ let version: BankVersion = serde_json::from_slice(&bytes)?;
370
+
371
+ Ok(version)
372
+ }
373
+
374
+ async fn list_external_banks() -> Result<Vec<BankInfo>, Box<dyn std::error::Error>> {
375
+ let cdn_url = get_cdn_url();
376
+ let url = format!("{}/bank/list", cdn_url);
377
+
378
+ let response = reqwest::get(url).await?;
379
+
380
+ if !response.status().is_success() {
381
+ return Err(format!("❌ Failed to fetch bank list: HTTP {}", response.status()).into());
382
+ }
383
+
384
+ let bytes = response.bytes().await?;
385
+
386
+ let parsed: BankList = serde_json::from_slice(&bytes)?;
387
+
388
+ Ok(parsed.bank)
389
+ }
390
+
391
+ async fn list_installed_banks() -> Result<Vec<BankFile>, String> {
392
+ let deva_dir = std::path::Path::new("./.deva");
393
+ let bank_dir = deva_dir.join("bank");
394
+
395
+ let mut banks = Vec::new();
396
+
397
+ if !bank_dir.exists() {
398
+ return Ok(banks); // No banks installed
399
+ }
400
+
401
+ // let installed_banks = std::fs
402
+ // ::read_dir(bank_dir)
403
+ // .map_err(|e| format!("Failed to read bank directory: {}", e))?;
404
+
405
+ let root_dir = deva_dir
406
+ .parent()
407
+ .ok_or_else(|| "Failed to determine root directory".to_string())?;
408
+
409
+ let config_path = root_dir.join(".devalang");
410
+ if !config_path.exists() {
411
+ return Err(
412
+ format!(
413
+ "Config file not found at '{}'. Please run 'devalang init' before adding an addon",
414
+ config_path.display()
415
+ )
416
+ );
417
+ }
418
+
419
+ let mut config = load_config(Some(&config_path)).ok_or_else(||
420
+ format!("Failed to load config from '{}'", config_path.display())
421
+ )?;
422
+
423
+ let config_banks = config.banks.clone();
424
+
425
+ if let Some(banks_in_toml) = config_banks {
426
+ for bank in banks_in_toml {
427
+ let bank_name = bank.path
428
+ .strip_prefix("devalang://bank/")
429
+ .unwrap_or(&bank.path)
430
+ .to_string();
431
+
432
+ let bank_path = bank_dir.join(&bank_name);
433
+ if bank_path.exists() {
434
+ let bank_info_path = bank_path.join("bank.toml");
435
+
436
+ if bank_info_path.exists() {
437
+ let content = std::fs
438
+ ::read_to_string(&bank_info_path)
439
+ .map_err(|e| format!("Failed to read bank info: {}", e))?;
440
+
441
+ match toml::from_str::<BankFile>(&content) {
442
+ Ok(bank_info) => banks.push(bank_info),
443
+ Err(err) => {
444
+ eprintln!("❌ Error parsing bank info for '{}': {}", bank_name, err);
445
+ }
446
+ }
447
+ } else {
448
+ eprintln!("❌ Bank info file not found for '{}'", bank_name);
449
+ }
450
+ }
451
+ }
452
+ }
453
+
454
+ Ok(banks)
455
+ }