@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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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:
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
.get("
|
|
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
|
-
|
|
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;
|