@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.
- package/.devalang +9 -10
- package/Cargo.toml +84 -80
- package/README.md +10 -7
- package/docs/CHANGELOG.md +83 -0
- package/docs/ROADMAP.md +6 -2
- package/docs/TODO.md +3 -14
- package/examples/bus.deva +10 -0
- package/examples/chain.deva +19 -0
- package/examples/effect.deva +2 -0
- package/examples/filter.deva +11 -0
- package/examples/lfo.deva +9 -0
- package/examples/plugin.deva +10 -10
- package/examples/routing.deva +23 -0
- package/examples/synth.deva +11 -1
- package/examples/synth_types.deva +17 -0
- package/out-tsc/bin/project-version.json +6 -0
- package/out-tsc/core/functions/index.d.ts +5 -0
- package/out-tsc/core/functions/index.js +11 -0
- package/out-tsc/pkg/devalang_core.d.ts +2 -0
- package/out-tsc/pkg/devalang_core.js +17 -2
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.js +79 -0
- package/package.json +23 -10
- package/project-version.json +3 -3
- package/rust/bindings/Cargo.toml +9 -0
- package/rust/bindings/src/lib.rs +86 -0
- package/rust/cli/addon/commands.rs +35 -0
- package/rust/cli/addon/download.rs +234 -0
- package/rust/cli/addon/install.rs +33 -0
- package/rust/cli/addon/list.rs +224 -0
- package/rust/cli/addon/metadata.rs +124 -0
- package/rust/cli/addon/mod.rs +8 -0
- package/rust/cli/addon/remove.rs +271 -0
- package/rust/cli/addon/update.rs +305 -0
- package/rust/cli/{install/addon.rs → addon/utils.rs} +34 -43
- package/rust/cli/build/commands.rs +153 -103
- package/rust/cli/build/mod.rs +2 -2
- package/rust/cli/build/process.rs +165 -146
- package/rust/cli/check/mod.rs +208 -208
- package/rust/cli/discover/commands.rs +53 -31
- package/rust/cli/discover/config.rs +2 -4
- package/rust/cli/discover/install.rs +139 -28
- package/rust/cli/discover/metadata.rs +3 -3
- package/rust/cli/login/commands.rs +124 -124
- package/rust/cli/me/commands.rs +52 -0
- package/rust/cli/me/mod.rs +1 -0
- package/rust/cli/mod.rs +2 -2
- package/rust/cli/parser.rs +76 -70
- package/rust/cli/play/commands.rs +375 -324
- package/rust/cli/play/mod.rs +5 -5
- package/rust/cli/play/process.rs +159 -150
- package/rust/cli/play/realtime.rs +91 -91
- package/rust/cli/telemetry/commands.rs +22 -22
- package/rust/cli/telemetry/event_creator.rs +80 -80
- package/rust/cli/telemetry/mod.rs +3 -3
- package/rust/cli/telemetry/send.rs +51 -51
- package/rust/cli/template/commands.rs +69 -69
- package/rust/config/driver.rs +112 -103
- package/rust/config/mod.rs +3 -3
- package/rust/config/ops.rs +26 -26
- package/rust/config/settings.rs +101 -101
- package/rust/core/audio/engine/driver.rs +237 -0
- package/rust/core/audio/engine/export.rs +169 -0
- package/rust/core/audio/engine/helpers.rs +178 -170
- package/rust/core/audio/engine/mod.rs +56 -7
- package/rust/core/audio/engine/notes/dsp.rs +88 -0
- package/rust/core/audio/engine/notes/mod.rs +53 -0
- package/rust/core/audio/engine/notes/params.rs +294 -0
- package/rust/core/audio/engine/sample/insert.rs +300 -0
- package/rust/core/audio/engine/sample/mod.rs +40 -0
- package/rust/core/audio/engine/sample/padding.rs +170 -0
- package/rust/core/audio/evaluator/condition.rs +61 -0
- package/rust/core/audio/evaluator/mod.rs +9 -0
- package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +152 -310
- package/rust/core/audio/evaluator/rhs.rs +16 -0
- package/rust/core/audio/evaluator/string_expr.rs +94 -0
- package/rust/core/audio/interpreter/driver.rs +574 -542
- package/rust/core/audio/interpreter/mod.rs +2 -14
- package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +179 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +3 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +371 -0
- package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
- package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
- package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +2 -4
- package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +36 -5
- package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -71
- package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +24 -26
- package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +36 -38
- package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +17 -19
- package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +115 -114
- package/rust/core/audio/interpreter/statements/mod.rs +12 -0
- package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
- package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +54 -4
- package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
- package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +242 -239
- package/rust/core/audio/loader/trigger.rs +98 -97
- package/rust/core/audio/mod.rs +6 -7
- package/rust/core/audio/special/easing.rs +189 -189
- package/rust/core/audio/special/env.rs +45 -45
- package/rust/core/audio/special/math.rs +134 -134
- package/rust/core/audio/special/modulator.rs +143 -143
- package/rust/core/builder/mod.rs +129 -86
- package/rust/core/debugger/{module.rs → logs.rs} +52 -55
- package/rust/core/debugger/mod.rs +30 -30
- package/rust/core/debugger/store.rs +38 -40
- package/rust/core/error/mod.rs +269 -269
- package/rust/core/lexer/driver.rs +2 -4
- package/rust/core/mod.rs +9 -10
- package/rust/core/parser/driver/block.rs +111 -0
- package/rust/core/parser/driver/cursor.rs +82 -0
- package/rust/core/parser/driver/driver_impl.rs +159 -0
- package/rust/core/parser/driver/mod.rs +6 -0
- package/rust/core/parser/driver/parse_array.rs +120 -0
- package/rust/core/parser/driver/parse_map.rs +247 -0
- package/rust/core/parser/driver/parser.rs +160 -0
- package/rust/core/parser/handler/arrow_call.rs +90 -15
- package/rust/core/parser/handler/at.rs +279 -279
- package/rust/core/parser/handler/bank.rs +104 -104
- package/rust/core/parser/handler/condition.rs +83 -83
- package/rust/core/parser/handler/dot.rs +148 -148
- package/rust/core/parser/handler/identifier/automate.rs +254 -254
- package/rust/core/parser/handler/identifier/call.rs +91 -91
- package/rust/core/parser/handler/identifier/emit.rs +70 -70
- package/rust/core/parser/handler/identifier/function.rs +113 -113
- package/rust/core/parser/handler/identifier/group.rs +89 -89
- package/rust/core/parser/handler/identifier/let_.rs +173 -173
- package/rust/core/parser/handler/identifier/mod.rs +55 -55
- package/rust/core/parser/handler/identifier/on.rs +107 -107
- package/rust/core/parser/handler/identifier/print.rs +49 -49
- package/rust/core/parser/handler/identifier/sleep.rs +96 -43
- package/rust/core/parser/handler/identifier/spawn.rs +91 -91
- package/rust/core/parser/handler/identifier/synth.rs +39 -3
- package/rust/core/parser/handler/loop_.rs +194 -194
- package/rust/core/parser/handler/pattern.rs +25 -2
- package/rust/core/parser/handler/tempo.rs +105 -57
- package/rust/core/parser/statement.rs +10 -11
- package/rust/core/plugin/loader.rs +137 -137
- package/rust/core/plugin/runner/mod.rs +11 -0
- package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +206 -72
- package/rust/core/plugin/runner/wasm32.rs +44 -0
- package/rust/core/preprocessor/loader/inject.rs +313 -0
- package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
- package/rust/core/preprocessor/loader/mod.rs +235 -0
- package/rust/core/preprocessor/module.rs +55 -60
- package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +107 -114
- package/rust/core/preprocessor/processor/mod.rs +1 -0
- package/rust/core/preprocessor/resolver/function.rs +69 -69
- package/rust/core/preprocessor/resolver/group.rs +122 -94
- package/rust/core/preprocessor/resolver/pattern.rs +14 -2
- package/rust/core/store/global.rs +57 -61
- package/rust/core/store/mod.rs +1 -5
- package/rust/lib.rs +323 -308
- package/rust/macros/Cargo.toml +14 -0
- package/rust/macros/src/lib.rs +52 -0
- package/rust/main.rs +336 -143
- package/rust/types/Cargo.toml +1 -1
- package/rust/types/src/addons.rs +57 -55
- package/rust/types/src/config.rs +82 -74
- package/rust/types/src/lib.rs +15 -12
- package/rust/types/src/plugin.rs +20 -0
- package/rust/types/src/store.rs +139 -0
- package/rust/types/src/telemetry.rs +85 -85
- package/rust/utils/Cargo.toml +5 -2
- package/rust/utils/src/file.rs +477 -94
- package/rust/utils/src/first_usage.rs +97 -97
- package/rust/utils/src/lib.rs +9 -9
- package/rust/utils/src/logger.rs +200 -200
- package/rust/utils/src/path.rs +158 -88
- package/rust/utils/src/signature.rs +41 -41
- package/rust/utils/src/spinner.rs +20 -20
- package/rust/utils/src/version.rs +58 -27
- package/rust/utils/src/watcher.rs +46 -46
- package/rust/web/api.rs +5 -5
- package/rust/web/auth.rs +5 -0
- package/rust/web/cdn.rs +34 -34
- package/rust/web/forge.rs +5 -0
- package/rust/web/mod.rs +2 -0
- package/tests/integration.rs +21 -21
- package/typescript/core/functions/index.ts +11 -0
- package/typescript/pkg/devalang_core.ts +20 -4
- package/typescript/scripts/version/copy-to-binary.ts +82 -0
- package/rust/cli/bank/api.rs +0 -122
- package/rust/cli/bank/commands.rs +0 -275
- package/rust/cli/bank/mod.rs +0 -29
- package/rust/cli/install/bank.rs +0 -53
- package/rust/cli/install/commands.rs +0 -35
- package/rust/cli/install/mod.rs +0 -4
- package/rust/cli/install/plugin.rs +0 -61
- package/rust/core/audio/engine/sample.rs +0 -366
- package/rust/core/audio/engine/synth.rs +0 -325
- package/rust/core/audio/interpreter/arrow_call.rs +0 -311
- package/rust/core/audio/renderer.rs +0 -54
- package/rust/core/parser/driver.rs +0 -584
- package/rust/core/preprocessor/loader.rs +0 -637
- package/rust/core/store/export.rs +0 -28
- package/rust/core/store/function.rs +0 -40
- package/rust/core/store/import.rs +0 -28
- package/rust/core/store/variable.rs +0 -51
- package/rust/core/utils/mod.rs +0 -1
- package/rust/core/utils/path.rs +0 -37
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
use devalang_utils::logger::{LogLevel, Logger};
|
|
2
|
+
|
|
3
|
+
#[derive(Clone)]
|
|
4
|
+
struct InstalledAddon {
|
|
5
|
+
name: String,
|
|
6
|
+
addon_type: String,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
pub async fn list_addons() -> Result<(), String> {
|
|
10
|
+
let deva_dir = devalang_utils::path::ensure_deva_dir()?;
|
|
11
|
+
let banks_dir = deva_dir.join("banks");
|
|
12
|
+
let plugins_dir = deva_dir.join("plugins");
|
|
13
|
+
let presets_dir = deva_dir.join("presets");
|
|
14
|
+
let templates_dir = deva_dir.join("templates");
|
|
15
|
+
|
|
16
|
+
let mut installed_addons = Vec::new();
|
|
17
|
+
|
|
18
|
+
if banks_dir.exists() {
|
|
19
|
+
if let Ok(entries) = std::fs::read_dir(&banks_dir) {
|
|
20
|
+
for entry in entries.flatten() {
|
|
21
|
+
if let Ok(file_type) = entry.file_type() {
|
|
22
|
+
if file_type.is_dir() {
|
|
23
|
+
if let Some(name) = entry.file_name().to_str() {
|
|
24
|
+
installed_addons.push(InstalledAddon {
|
|
25
|
+
name: name.to_string(),
|
|
26
|
+
addon_type: "bank".to_string(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if plugins_dir.exists() {
|
|
36
|
+
if let Ok(entries) = std::fs::read_dir(&plugins_dir) {
|
|
37
|
+
for entry in entries.flatten() {
|
|
38
|
+
if let Ok(file_type) = entry.file_type() {
|
|
39
|
+
if file_type.is_dir() {
|
|
40
|
+
if let Some(name) = entry.file_name().to_str() {
|
|
41
|
+
installed_addons.push(InstalledAddon {
|
|
42
|
+
name: name.to_string(),
|
|
43
|
+
addon_type: "plugin".to_string(),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if presets_dir.exists() {
|
|
53
|
+
if let Ok(entries) = std::fs::read_dir(&presets_dir) {
|
|
54
|
+
for entry in entries.flatten() {
|
|
55
|
+
if let Ok(file_type) = entry.file_type() {
|
|
56
|
+
if file_type.is_dir() {
|
|
57
|
+
if let Some(name) = entry.file_name().to_str() {
|
|
58
|
+
installed_addons.push(InstalledAddon {
|
|
59
|
+
name: name.to_string(),
|
|
60
|
+
addon_type: "preset".to_string(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if templates_dir.exists() {
|
|
70
|
+
if let Ok(entries) = std::fs::read_dir(&templates_dir) {
|
|
71
|
+
for entry in entries.flatten() {
|
|
72
|
+
if let Ok(file_type) = entry.file_type() {
|
|
73
|
+
if file_type.is_dir() {
|
|
74
|
+
if let Some(name) = entry.file_name().to_str() {
|
|
75
|
+
installed_addons.push(InstalledAddon {
|
|
76
|
+
name: name.to_string(),
|
|
77
|
+
addon_type: "template".to_string(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let logger = Logger::new();
|
|
87
|
+
|
|
88
|
+
if installed_addons.is_empty() {
|
|
89
|
+
logger.log_message(LogLevel::Info, "No addon installed.");
|
|
90
|
+
} else {
|
|
91
|
+
let installed_banks = installed_addons.iter().filter(|a| a.addon_type == "bank");
|
|
92
|
+
|
|
93
|
+
let installed_plugins = installed_addons.iter().filter(|a| a.addon_type == "plugin");
|
|
94
|
+
|
|
95
|
+
let installed_presets = installed_addons.iter().filter(|a| a.addon_type == "preset");
|
|
96
|
+
|
|
97
|
+
let installed_templates = installed_addons
|
|
98
|
+
.iter()
|
|
99
|
+
.filter(|a| a.addon_type == "template");
|
|
100
|
+
|
|
101
|
+
if installed_banks.clone().count() > 0 {
|
|
102
|
+
let trace: Vec<String> = installed_addons
|
|
103
|
+
.iter()
|
|
104
|
+
.map(|a| format!("{}", a.name))
|
|
105
|
+
.collect();
|
|
106
|
+
let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
|
|
107
|
+
|
|
108
|
+
logger.log_message_with_trace(LogLevel::Info, "Installed banks :", trace);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if installed_plugins.clone().count() > 0 {
|
|
112
|
+
let trace: Vec<String> = installed_addons
|
|
113
|
+
.iter()
|
|
114
|
+
.map(|a| format!("{}", a.name))
|
|
115
|
+
.collect();
|
|
116
|
+
let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
|
|
117
|
+
|
|
118
|
+
logger.log_message_with_trace(LogLevel::Info, "Installed plugins :", trace);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if installed_presets.clone().count() > 0 {
|
|
122
|
+
let trace: Vec<String> = installed_addons
|
|
123
|
+
.iter()
|
|
124
|
+
.map(|a| format!("{}", a.name))
|
|
125
|
+
.collect();
|
|
126
|
+
let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
|
|
127
|
+
|
|
128
|
+
logger.log_message_with_trace(LogLevel::Info, "Installed presets :", trace);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if installed_templates.clone().count() > 0 {
|
|
132
|
+
let trace: Vec<String> = installed_addons
|
|
133
|
+
.iter()
|
|
134
|
+
.map(|a| format!("{}", a.name))
|
|
135
|
+
.collect();
|
|
136
|
+
let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
|
|
137
|
+
|
|
138
|
+
logger.log_message_with_trace(LogLevel::Info, "Installed templates :", trace);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if let Some(addon_not_in_config) = find_unused_addons(installed_addons.clone()) {
|
|
142
|
+
let trace: Vec<String> = addon_not_in_config
|
|
143
|
+
.iter()
|
|
144
|
+
.map(|a| format!("{} ({})", a.name, a.addon_type))
|
|
145
|
+
.collect();
|
|
146
|
+
let trace: Vec<&str> = trace.iter().map(|s| s.as_str()).collect();
|
|
147
|
+
|
|
148
|
+
logger.log_message_with_trace(
|
|
149
|
+
LogLevel::Warning,
|
|
150
|
+
"Some addons are installed but not referenced in the .devalang configuration file :",
|
|
151
|
+
trace,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
logger.log_message(
|
|
156
|
+
LogLevel::Success,
|
|
157
|
+
format!("Found {} installed addon(s).", installed_addons.len()).as_str(),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn find_unused_addons(addons: Vec<InstalledAddon>) -> Option<Vec<InstalledAddon>> {
|
|
165
|
+
let mut unused_addons = Vec::new();
|
|
166
|
+
|
|
167
|
+
let config_path = match devalang_utils::path::get_devalang_config_path() {
|
|
168
|
+
Ok(path) => path,
|
|
169
|
+
Err(_) => return None,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let config = match crate::config::ops::load_config(Some(&config_path)) {
|
|
173
|
+
Some(cfg) => cfg,
|
|
174
|
+
None => return None,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
let mut referenced_addons = Vec::new();
|
|
178
|
+
|
|
179
|
+
if let Some(banks) = config.banks {
|
|
180
|
+
for bank in banks {
|
|
181
|
+
if let Some(name) = bank.path.strip_prefix("devalang://bank/") {
|
|
182
|
+
referenced_addons.push((name.to_string(), "bank".to_string()));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if let Some(plugins) = config.plugins {
|
|
188
|
+
for plugin in plugins {
|
|
189
|
+
if let Some(name) = plugin.path.strip_prefix("devalang://plugin/") {
|
|
190
|
+
referenced_addons.push((name.to_string(), "plugin".to_string()));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// TODO: Enable when presets and templates are supported in config
|
|
196
|
+
// if let Some(presets) = config.presets {
|
|
197
|
+
// for preset in presets {
|
|
198
|
+
// if let Some(name) = preset.path.strip_prefix("devalang://preset/") {
|
|
199
|
+
// referenced_addons.push((name.to_string(), "preset".to_string()));
|
|
200
|
+
// }
|
|
201
|
+
// }
|
|
202
|
+
// }
|
|
203
|
+
|
|
204
|
+
// TODO: Enable when presets and templates are supported in config
|
|
205
|
+
// if let Some(templates) = config.templates {
|
|
206
|
+
// for template in templates {
|
|
207
|
+
// if let Some(name) = template.path.strip_prefix("devalang://template/") {
|
|
208
|
+
// referenced_addons.push((name.to_string(), "template".to_string()));
|
|
209
|
+
// }
|
|
210
|
+
// }
|
|
211
|
+
// }
|
|
212
|
+
|
|
213
|
+
for addon in addons {
|
|
214
|
+
if !referenced_addons.contains(&(addon.name.clone(), addon.addon_type.clone())) {
|
|
215
|
+
unused_addons.push(addon);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if unused_addons.is_empty() {
|
|
220
|
+
None
|
|
221
|
+
} else {
|
|
222
|
+
Some(unused_addons)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
use crate::web::api::get_api_url;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone)]
|
|
4
|
+
pub struct AddonToDownloadMetadata {
|
|
5
|
+
pub name: String,
|
|
6
|
+
pub publisher: String,
|
|
7
|
+
pub addon_type: devalang_types::AddonType,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub async fn get_addon_publisher_from_api(slug: &str) -> Result<String, String> {
|
|
11
|
+
let api_url = get_api_url();
|
|
12
|
+
|
|
13
|
+
let request_url = format!("{}/v1/products/getBySlug/{}", api_url, slug);
|
|
14
|
+
|
|
15
|
+
let client: reqwest::Client = reqwest::Client::builder()
|
|
16
|
+
.build()
|
|
17
|
+
.map_err(|_| "Failed to build HTTP client".to_string())?;
|
|
18
|
+
|
|
19
|
+
let resp = client
|
|
20
|
+
.get(&request_url)
|
|
21
|
+
.send()
|
|
22
|
+
.await
|
|
23
|
+
.map_err(|e| format!("Failed to receive response: {}", e))?;
|
|
24
|
+
|
|
25
|
+
let status = resp.status();
|
|
26
|
+
let body_text = resp
|
|
27
|
+
.text()
|
|
28
|
+
.await
|
|
29
|
+
.map_err(|e| format!("Failed to read response body: {}", e))?;
|
|
30
|
+
|
|
31
|
+
let json: serde_json::Value = match serde_json::from_str(&body_text) {
|
|
32
|
+
Ok(v) => v,
|
|
33
|
+
Err(_) => {
|
|
34
|
+
return Err(format!(
|
|
35
|
+
"Invalid JSON response (status {}): {}",
|
|
36
|
+
status, body_text
|
|
37
|
+
));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let payload = json.get("payload");
|
|
42
|
+
let publisher = payload
|
|
43
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
44
|
+
.get("publisher")
|
|
45
|
+
.and_then(|v| v.get("name"))
|
|
46
|
+
.and_then(|v| v.as_str())
|
|
47
|
+
.unwrap_or("unknown")
|
|
48
|
+
.to_string();
|
|
49
|
+
|
|
50
|
+
Ok(publisher)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub async fn get_addon_from_api(slug: &str) -> Result<AddonToDownloadMetadata, String> {
|
|
54
|
+
let api_url = get_api_url();
|
|
55
|
+
|
|
56
|
+
let request_url = format!("{}/v1/products/getBySlug/{}", api_url, slug);
|
|
57
|
+
|
|
58
|
+
let client: reqwest::Client = reqwest::Client::builder()
|
|
59
|
+
.build()
|
|
60
|
+
.map_err(|_| "Failed to build HTTP client".to_string())?;
|
|
61
|
+
|
|
62
|
+
let resp = client
|
|
63
|
+
.get(&request_url)
|
|
64
|
+
.send()
|
|
65
|
+
.await
|
|
66
|
+
.map_err(|e| format!("Failed to receive response: {}", e))?;
|
|
67
|
+
|
|
68
|
+
let status = resp.status();
|
|
69
|
+
let body_text = resp
|
|
70
|
+
.text()
|
|
71
|
+
.await
|
|
72
|
+
.map_err(|e| format!("Failed to read response body: {}", e))?;
|
|
73
|
+
|
|
74
|
+
let json: serde_json::Value = match serde_json::from_str(&body_text) {
|
|
75
|
+
Ok(v) => v,
|
|
76
|
+
Err(_) => {
|
|
77
|
+
return Err(format!(
|
|
78
|
+
"Invalid JSON response (status {}): {}",
|
|
79
|
+
status, body_text
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let payload = json.get("payload");
|
|
85
|
+
let addon_type = payload
|
|
86
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
87
|
+
.get("addon")
|
|
88
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
89
|
+
.get("addon_type")
|
|
90
|
+
.and_then(|v| v.as_str())
|
|
91
|
+
.unwrap_or("unknown");
|
|
92
|
+
|
|
93
|
+
let addon_type_enum = match addon_type {
|
|
94
|
+
"bank" => devalang_types::AddonType::Bank,
|
|
95
|
+
"plugin" => devalang_types::AddonType::Plugin,
|
|
96
|
+
"preset" => devalang_types::AddonType::Preset,
|
|
97
|
+
"template" => devalang_types::AddonType::Template,
|
|
98
|
+
_ => {
|
|
99
|
+
return Err(format!("Unknown addon type: {}", addon_type));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
let addon_metadata = AddonToDownloadMetadata {
|
|
104
|
+
name: payload
|
|
105
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
106
|
+
.get("addon")
|
|
107
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
108
|
+
.get("name")
|
|
109
|
+
.and_then(|v| v.as_str())
|
|
110
|
+
.unwrap_or("unknown")
|
|
111
|
+
.to_string(),
|
|
112
|
+
publisher: payload
|
|
113
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
114
|
+
.get("publisher")
|
|
115
|
+
.unwrap_or(&serde_json::Value::Null)
|
|
116
|
+
.get("name")
|
|
117
|
+
.and_then(|v| v.as_str())
|
|
118
|
+
.unwrap_or("unknown")
|
|
119
|
+
.to_string(),
|
|
120
|
+
addon_type: addon_type_enum,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
Ok(addon_metadata)
|
|
124
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
use crate::config::ops::load_config;
|
|
2
|
+
use devalang_core::config::driver::ProjectConfigExt;
|
|
3
|
+
use devalang_utils::path as path_utils;
|
|
4
|
+
use std::fs;
|
|
5
|
+
|
|
6
|
+
pub async fn remove_addon(name: String) -> Result<(), String> {
|
|
7
|
+
let deva_dir = path_utils::ensure_deva_dir()?;
|
|
8
|
+
|
|
9
|
+
// Helper to extract publisher from a slug like 'publisher.name'
|
|
10
|
+
let extract_publisher = |s: &str| s.splitn(2, '.').next().unwrap_or("").to_string();
|
|
11
|
+
|
|
12
|
+
// Try to find in config first (banks/plugins)
|
|
13
|
+
if let Ok(config_path) = path_utils::get_devalang_config_path() {
|
|
14
|
+
if let Some(mut config) = load_config(Some(&config_path)) {
|
|
15
|
+
// BANKS
|
|
16
|
+
if let Some(banks) = config.banks.as_mut() {
|
|
17
|
+
if let Some(pos) = banks.iter().position(|b| {
|
|
18
|
+
let slug = b.path.strip_prefix("devalang://bank/").unwrap_or(&b.path);
|
|
19
|
+
// accept exact slug, exact path, or match by local name suffix (publisher/name or publisher.name)
|
|
20
|
+
slug == name
|
|
21
|
+
|| b.path == name
|
|
22
|
+
|| slug.ends_with(&format!("/{}", name))
|
|
23
|
+
|| slug.ends_with(&format!(".{}", name))
|
|
24
|
+
}) {
|
|
25
|
+
let entry = banks.remove(pos);
|
|
26
|
+
let slug = entry
|
|
27
|
+
.path
|
|
28
|
+
.strip_prefix("devalang://bank/")
|
|
29
|
+
.unwrap_or(&entry.path)
|
|
30
|
+
.to_string();
|
|
31
|
+
|
|
32
|
+
// parse publisher/name from slug (support 'publisher/name' or 'publisher.name')
|
|
33
|
+
let (publisher, local_name) = if slug.contains('/') {
|
|
34
|
+
let mut it = slug.splitn(2, '/');
|
|
35
|
+
(
|
|
36
|
+
it.next().unwrap().to_string(),
|
|
37
|
+
it.next().unwrap().to_string(),
|
|
38
|
+
)
|
|
39
|
+
} else if slug.contains('.') {
|
|
40
|
+
let mut it = slug.splitn(2, '.');
|
|
41
|
+
(
|
|
42
|
+
it.next().unwrap().to_string(),
|
|
43
|
+
it.next().unwrap().to_string(),
|
|
44
|
+
)
|
|
45
|
+
} else {
|
|
46
|
+
return Err(format!("Cannot parse bank slug '{}'", slug));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let local_path = deva_dir.join("banks").join(&publisher).join(&local_name);
|
|
50
|
+
|
|
51
|
+
if !local_path.exists() {
|
|
52
|
+
return Err(format!(
|
|
53
|
+
"Local files for bank '{}' not found at '{}', aborting",
|
|
54
|
+
slug,
|
|
55
|
+
local_path.display()
|
|
56
|
+
));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fs::remove_dir_all(&local_path)
|
|
60
|
+
.map_err(|e| format!("Failed to remove addon files: {}", e))?;
|
|
61
|
+
|
|
62
|
+
if let Err(e) = config.write_config(&config) {
|
|
63
|
+
eprintln!("Warning: failed to write updated config: {}", e);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
println!("✅ Bank '{}' removed (publisher '{}')", slug, publisher);
|
|
67
|
+
return Ok(());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// PLUGINS
|
|
72
|
+
if let Some(plugins) = config.plugins.as_mut() {
|
|
73
|
+
if let Some(pos) = plugins.iter().position(|p| {
|
|
74
|
+
let slug = p.path.strip_prefix("devalang://plugin/").unwrap_or(&p.path);
|
|
75
|
+
// accept exact slug, exact path, or match by local name suffix
|
|
76
|
+
slug == name
|
|
77
|
+
|| p.path == name
|
|
78
|
+
|| slug.ends_with(&format!("/{}", name))
|
|
79
|
+
|| slug.ends_with(&format!(".{}", name))
|
|
80
|
+
}) {
|
|
81
|
+
let entry = plugins.remove(pos);
|
|
82
|
+
let slug = entry
|
|
83
|
+
.path
|
|
84
|
+
.strip_prefix("devalang://plugin/")
|
|
85
|
+
.unwrap_or(&entry.path)
|
|
86
|
+
.to_string();
|
|
87
|
+
|
|
88
|
+
// parse publisher/name from slug (support 'publisher/name' or 'publisher.name')
|
|
89
|
+
let (publisher, local_name) = if slug.contains('/') {
|
|
90
|
+
let mut it = slug.splitn(2, '/');
|
|
91
|
+
(
|
|
92
|
+
it.next().unwrap().to_string(),
|
|
93
|
+
it.next().unwrap().to_string(),
|
|
94
|
+
)
|
|
95
|
+
} else if slug.contains('.') {
|
|
96
|
+
let mut it = slug.splitn(2, '.');
|
|
97
|
+
(
|
|
98
|
+
it.next().unwrap().to_string(),
|
|
99
|
+
it.next().unwrap().to_string(),
|
|
100
|
+
)
|
|
101
|
+
} else {
|
|
102
|
+
return Err(format!("Cannot parse plugin slug '{}'", slug));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
let local_path = deva_dir.join("plugins").join(&publisher).join(&local_name);
|
|
106
|
+
|
|
107
|
+
if !local_path.exists() {
|
|
108
|
+
return Err(format!(
|
|
109
|
+
"Local files for plugin '{}' not found at '{}', aborting",
|
|
110
|
+
slug,
|
|
111
|
+
local_path.display()
|
|
112
|
+
));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fs::remove_dir_all(&local_path)
|
|
116
|
+
.map_err(|e| format!("Failed to remove addon files: {}", e))?;
|
|
117
|
+
|
|
118
|
+
if let Err(e) = config.write_config(&config) {
|
|
119
|
+
eprintln!("Warning: failed to write updated config: {}", e);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
println!("✅ Plugin '{}' removed (publisher '{}')", slug, publisher);
|
|
123
|
+
return Ok(());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Not found in config: search filesystem under .deva and infer type
|
|
130
|
+
let dirs = ["banks", "plugins", "presets", "templates"];
|
|
131
|
+
for &d in &dirs {
|
|
132
|
+
let folder = deva_dir.join(d);
|
|
133
|
+
if !folder.exists() {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// If name looks like a slug (contains '.' or '/'), parse publisher and name and try candidate paths
|
|
138
|
+
if name.contains('.') || name.contains('/') {
|
|
139
|
+
let (publisher, local_name) = if name.contains('/') {
|
|
140
|
+
let mut it = name.splitn(2, '/');
|
|
141
|
+
(
|
|
142
|
+
it.next().unwrap().to_string(),
|
|
143
|
+
it.next().unwrap().to_string(),
|
|
144
|
+
)
|
|
145
|
+
} else {
|
|
146
|
+
let mut it = name.splitn(2, '.');
|
|
147
|
+
(
|
|
148
|
+
it.next().unwrap().to_string(),
|
|
149
|
+
it.next().unwrap().to_string(),
|
|
150
|
+
)
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
let candidate1 = folder.join(&publisher).join(&local_name);
|
|
154
|
+
let candidate2 = folder.join(format!("{}.{}", publisher, local_name));
|
|
155
|
+
let candidate3 = folder.join(&name);
|
|
156
|
+
|
|
157
|
+
let candidate = if candidate1.exists() {
|
|
158
|
+
candidate1
|
|
159
|
+
} else if candidate2.exists() {
|
|
160
|
+
candidate2
|
|
161
|
+
} else {
|
|
162
|
+
candidate3
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if candidate.exists() {
|
|
166
|
+
fs::remove_dir_all(&candidate)
|
|
167
|
+
.map_err(|e| format!("Failed to remove addon files: {}", e))?;
|
|
168
|
+
|
|
169
|
+
// also attempt to remove from config if possible
|
|
170
|
+
if let Ok(config_path) = path_utils::get_devalang_config_path() {
|
|
171
|
+
if let Some(mut config) = load_config(Some(&config_path)) {
|
|
172
|
+
match d {
|
|
173
|
+
"banks" => {
|
|
174
|
+
if let Some(banks) = config.banks.as_mut() {
|
|
175
|
+
let pattern1 =
|
|
176
|
+
format!("devalang://bank/{}/{}", publisher, local_name);
|
|
177
|
+
let pattern2 =
|
|
178
|
+
format!("devalang://bank/{}.{}", publisher, local_name);
|
|
179
|
+
banks.retain(|b| {
|
|
180
|
+
b.path != pattern1
|
|
181
|
+
&& b.path != pattern2
|
|
182
|
+
&& !b.path.ends_with(&local_name)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
"plugins" => {
|
|
187
|
+
if let Some(plugins) = config.plugins.as_mut() {
|
|
188
|
+
let pattern1 =
|
|
189
|
+
format!("devalang://plugin/{}/{}", publisher, local_name);
|
|
190
|
+
let pattern2 =
|
|
191
|
+
format!("devalang://plugin/{}.{}", publisher, local_name);
|
|
192
|
+
plugins.retain(|p| {
|
|
193
|
+
p.path != pattern1
|
|
194
|
+
&& p.path != pattern2
|
|
195
|
+
&& !p.path.ends_with(&local_name)
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
_ => {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if let Err(e) = config.write_config(&config) {
|
|
203
|
+
eprintln!("Warning: failed to write updated config: {}", e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
println!(
|
|
209
|
+
"✅ Addon '{}/{}' removed from .deva/{} (publisher '{}')",
|
|
210
|
+
publisher, local_name, d, publisher
|
|
211
|
+
);
|
|
212
|
+
return Ok(());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Otherwise, scan directory entries: match exact name or suffix '.name'
|
|
217
|
+
if let Ok(entries) = fs::read_dir(&folder) {
|
|
218
|
+
for entry in entries.flatten() {
|
|
219
|
+
if let Ok(file_type) = entry.file_type() {
|
|
220
|
+
if !file_type.is_dir() {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
let file_name = entry.file_name();
|
|
225
|
+
let file_name = file_name.to_string_lossy();
|
|
226
|
+
if file_name == name || file_name.ends_with(&format!(".{}", name)) {
|
|
227
|
+
let slug = file_name.to_string();
|
|
228
|
+
let publisher = extract_publisher(&slug);
|
|
229
|
+
let path = entry.path();
|
|
230
|
+
fs::remove_dir_all(&path)
|
|
231
|
+
.map_err(|e| format!("Failed to remove addon files: {}", e))?;
|
|
232
|
+
|
|
233
|
+
// try to remove from config when banks/plugins
|
|
234
|
+
if let Ok(config_path) = path_utils::get_devalang_config_path() {
|
|
235
|
+
if let Some(mut config) = load_config(Some(&config_path)) {
|
|
236
|
+
match d {
|
|
237
|
+
"banks" => {
|
|
238
|
+
if let Some(banks) = config.banks.as_mut() {
|
|
239
|
+
banks.retain(|b| {
|
|
240
|
+
b.path != format!("devalang://bank/{}", slug)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
"plugins" => {
|
|
245
|
+
if let Some(plugins) = config.plugins.as_mut() {
|
|
246
|
+
plugins.retain(|p| {
|
|
247
|
+
p.path != format!("devalang://plugin/{}", slug)
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
_ => {}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if let Err(e) = config.write_config(&config) {
|
|
255
|
+
eprintln!("Warning: failed to write updated config: {}", e);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
println!(
|
|
261
|
+
"✅ Addon '{}' removed from .deva/{} (publisher '{}')",
|
|
262
|
+
slug, d, publisher
|
|
263
|
+
);
|
|
264
|
+
return Ok(());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Err(format!("Addon '{}' not found", name))
|
|
271
|
+
}
|