mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-01 15:03:11 +02:00
NodeJS Plugin Runtime (#53)
This commit is contained in:
@@ -1,305 +0,0 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
//! This example shows how to use swc to transpile TypeScript and JSX/TSX
|
||||
//! modules.
|
||||
//!
|
||||
//! It will only transpile, not typecheck (like Deno's `--no-check` flag).
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
use deno_ast::ParseParams;
|
||||
use deno_ast::{EmitOptions, MediaType, SourceMapOption, TranspileOptions};
|
||||
use deno_core::error::{AnyError, JsError};
|
||||
use deno_core::resolve_path;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::ModuleLoadResponse;
|
||||
use deno_core::ModuleLoader;
|
||||
use deno_core::ModuleSource;
|
||||
use deno_core::ModuleSourceCode;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::ModuleType;
|
||||
use deno_core::RequestedModuleType;
|
||||
use deno_core::ResolutionKind;
|
||||
use deno_core::RuntimeOptions;
|
||||
use deno_core::SourceMapGetter;
|
||||
use deno_core::{resolve_import, v8};
|
||||
use tokio::task::block_in_place;
|
||||
|
||||
use crate::deno_ops::op_yaml_parse;
|
||||
use crate::plugin::PluginCapability;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SourceMapStore(Rc<RefCell<HashMap<String, Vec<u8>>>>);
|
||||
|
||||
impl SourceMapGetter for SourceMapStore {
|
||||
fn get_source_map(&self, specifier: &str) -> Option<Vec<u8>> {
|
||||
self.0.borrow().get(specifier).cloned()
|
||||
}
|
||||
|
||||
fn get_source_line(&self, _file_name: &str, _line_number: usize) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct TypescriptModuleLoader {
|
||||
source_maps: SourceMapStore,
|
||||
}
|
||||
|
||||
impl ModuleLoader for TypescriptModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
_requested_module_type: RequestedModuleType,
|
||||
) -> ModuleLoadResponse {
|
||||
let source_maps = self.source_maps.clone();
|
||||
fn load(
|
||||
source_maps: SourceMapStore,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleSource, AnyError> {
|
||||
let path = module_specifier
|
||||
.to_file_path()
|
||||
.map_err(|_| anyhow!("Only file:// URLs are supported."))?;
|
||||
|
||||
let media_type = MediaType::from_path(&path);
|
||||
let (module_type, should_transpile) = match MediaType::from_path(&path) {
|
||||
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
|
||||
(ModuleType::JavaScript, false)
|
||||
}
|
||||
MediaType::Jsx => (ModuleType::JavaScript, true),
|
||||
MediaType::TypeScript
|
||||
| MediaType::Mts
|
||||
| MediaType::Cts
|
||||
| MediaType::Dts
|
||||
| MediaType::Dmts
|
||||
| MediaType::Dcts
|
||||
| MediaType::Tsx => (ModuleType::JavaScript, true),
|
||||
MediaType::Json => (ModuleType::Json, false),
|
||||
_ => bail!("Unknown extension {:?}", path.extension()),
|
||||
};
|
||||
|
||||
let code = std::fs::read_to_string(&path)?;
|
||||
let code = if should_transpile {
|
||||
let parsed = deno_ast::parse_module(ParseParams {
|
||||
specifier: module_specifier.clone(),
|
||||
text: Arc::from(code),
|
||||
media_type,
|
||||
capture_tokens: false,
|
||||
scope_analysis: false,
|
||||
maybe_syntax: None,
|
||||
})?;
|
||||
let res = parsed.transpile(
|
||||
&TranspileOptions::default(),
|
||||
&EmitOptions {
|
||||
source_map: SourceMapOption::Separate,
|
||||
inline_sources: true,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
let src = res.into_source();
|
||||
let source_map = src.source_map.unwrap();
|
||||
let source = src.source;
|
||||
source_maps
|
||||
.0
|
||||
.borrow_mut()
|
||||
.insert(module_specifier.to_string(), source_map);
|
||||
String::from_utf8(source).unwrap()
|
||||
} else {
|
||||
code
|
||||
};
|
||||
|
||||
Ok(ModuleSource::new(
|
||||
module_type,
|
||||
ModuleSourceCode::String(code.into()),
|
||||
module_specifier,
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
ModuleLoadResponse::Sync(load(source_maps, module_specifier))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_plugin_block(
|
||||
plugin_index_file: &str,
|
||||
fn_name: &str,
|
||||
fn_args: Vec<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
block_in_place(|| {
|
||||
tauri::async_runtime::block_on(run_plugin(plugin_index_file, fn_name, fn_args))
|
||||
})
|
||||
}
|
||||
|
||||
deno_core::extension!(
|
||||
yaak_runtime,
|
||||
ops = [ op_yaml_parse ],
|
||||
esm_entry_point = "ext:yaak_runtime/yaml.js",
|
||||
esm = [dir "src/plugin-runtime", "yaml.js"]
|
||||
);
|
||||
|
||||
async fn run_plugin(
|
||||
plugin_index_file: &str,
|
||||
fn_name: &str,
|
||||
fn_args: Vec<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let mut js_runtime = load_js_runtime()?;
|
||||
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
// Get the exported function we're calling
|
||||
let func_key = v8::String::new(scope, fn_name).unwrap();
|
||||
let func = module_namespace.get(scope, func_key.into()).unwrap();
|
||||
let func = v8::Local::<v8::Function>::try_from(func).unwrap();
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
|
||||
// Create Yaak context object
|
||||
let null = v8::null(tc_scope).into();
|
||||
let name = v8::String::new(tc_scope, "foo").unwrap().into();
|
||||
let value = v8::String::new(tc_scope, "bar").unwrap().into();
|
||||
let yaak_ctx: v8::Local<v8::Value> =
|
||||
v8::Object::with_prototype_and_properties(tc_scope, null, &[name], &[value]).into();
|
||||
|
||||
// Create the function arguments
|
||||
let passed_args = &mut fn_args
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let v: v8::Local<v8::Value> = deno_core::serde_v8::to_v8(tc_scope, a).unwrap();
|
||||
v
|
||||
})
|
||||
.collect::<Vec<v8::Local<v8::Value>>>();
|
||||
|
||||
let all_args = &mut vec![yaak_ctx];
|
||||
all_args.append(passed_args);
|
||||
|
||||
// Call the function
|
||||
let func_res = func.call(tc_scope, module_namespace.into(), all_args);
|
||||
|
||||
// Catch and return any thrown errors
|
||||
if tc_scope.has_caught() {
|
||||
let e = tc_scope.exception().unwrap();
|
||||
let js_error = JsError::from_v8_exception(tc_scope, e);
|
||||
return Err(Error::msg(js_error.stack.unwrap_or_default()));
|
||||
}
|
||||
|
||||
// Handle the result
|
||||
match func_res {
|
||||
None => Ok(serde_json::Value::Null),
|
||||
Some(res) => {
|
||||
if res.is_null() || res.is_undefined() {
|
||||
Ok(serde_json::Value::Null)
|
||||
} else {
|
||||
let value: serde_json::Value = deno_core::serde_v8::from_v8(tc_scope, res).unwrap();
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_plugin_capabilities_block(plugin_index_file: &str) -> Result<Vec<PluginCapability>, Error> {
|
||||
block_in_place(|| tauri::async_runtime::block_on(get_plugin_capabilities(plugin_index_file)))
|
||||
}
|
||||
|
||||
pub async fn get_plugin_capabilities(
|
||||
plugin_index_file: &str,
|
||||
) -> Result<Vec<PluginCapability>, Error> {
|
||||
let mut js_runtime = load_js_runtime()?;
|
||||
let module_namespace = load_main_module(&mut js_runtime, plugin_index_file).await?;
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
let property_names =
|
||||
match module_namespace.get_own_property_names(scope, v8::GetPropertyNamesArgs::default()) {
|
||||
None => return Ok(Vec::new()),
|
||||
Some(names) => names,
|
||||
};
|
||||
|
||||
let mut capabilities: Vec<PluginCapability> = Vec::new();
|
||||
for i in 0..property_names.length() {
|
||||
let name = property_names.get_index(scope, i);
|
||||
let name = match name {
|
||||
Some(name) => name,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
match name.to_rust_string_lossy(scope).as_str() {
|
||||
"pluginHookImport" => _ = capabilities.push(PluginCapability::Import),
|
||||
"pluginHookExport" => _ = capabilities.push(PluginCapability::Export),
|
||||
"pluginHookResponseFilter" => _ = capabilities.push(PluginCapability::Filter),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(capabilities)
|
||||
}
|
||||
|
||||
async fn load_main_module(
|
||||
js_runtime: &mut JsRuntime,
|
||||
plugin_index_file: &str,
|
||||
) -> Result<v8::Global<v8::Object>, Error> {
|
||||
let main_module = resolve_path(
|
||||
plugin_index_file,
|
||||
&std::env::current_dir().context("Unable to get CWD")?,
|
||||
)?;
|
||||
|
||||
// Load the main module so we can do stuff with it
|
||||
let mod_id = js_runtime.load_main_es_module(&main_module).await?;
|
||||
let result = js_runtime.mod_evaluate(mod_id);
|
||||
js_runtime.run_event_loop(Default::default()).await?;
|
||||
result.await?;
|
||||
|
||||
let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
|
||||
|
||||
Ok(module_namespace)
|
||||
}
|
||||
|
||||
fn load_js_runtime<'s>() -> Result<JsRuntime, Error> {
|
||||
let source_map_store = SourceMapStore(Rc::new(RefCell::new(HashMap::new())));
|
||||
|
||||
let mut ext_console = deno_console::deno_console::init_ops_and_esm();
|
||||
ext_console.esm_entry_point = Some("ext:deno_console/01_console.js");
|
||||
|
||||
let ext_yaak = yaak_runtime::init_ops_and_esm();
|
||||
|
||||
let js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(TypescriptModuleLoader {
|
||||
source_maps: source_map_store.clone(),
|
||||
})),
|
||||
source_map_getter: Some(Rc::new(source_map_store)),
|
||||
extensions: vec![ext_console, ext_yaak],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// let main_module = resolve_path(
|
||||
// plugin_index_file.to_str().unwrap(),
|
||||
// &std::env::current_dir().context("Unable to get CWD")?,
|
||||
// )?;
|
||||
//
|
||||
// // Load the main module so we can do stuff with it
|
||||
// let mod_id = js_runtime.load_main_es_module(&main_module).await?;
|
||||
// let result = js_runtime.mod_evaluate(mod_id);
|
||||
// js_runtime.run_event_loop(Default::default()).await?;
|
||||
// result.await?;
|
||||
//
|
||||
// let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
|
||||
// let scope = &mut js_runtime.handle_scope();
|
||||
// let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
|
||||
|
||||
Ok(js_runtime)
|
||||
}
|
||||
@@ -32,10 +32,12 @@ use tokio::sync::Mutex;
|
||||
|
||||
use ::grpc::manager::{DynamicMessage, GrpcHandle};
|
||||
use ::grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
|
||||
use plugin_runtime::manager::PluginManager;
|
||||
|
||||
use crate::analytics::{AnalyticsAction, AnalyticsResource};
|
||||
use crate::grpc::metadata_to_map;
|
||||
use crate::http_request::send_http_request;
|
||||
use crate::models::ImportResult;
|
||||
use crate::models::{
|
||||
cancel_pending_grpc_connections, cancel_pending_responses, create_http_response,
|
||||
delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar, delete_environment,
|
||||
@@ -53,22 +55,15 @@ use crate::models::{
|
||||
WorkspaceExportResources,
|
||||
};
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::plugin::{
|
||||
find_plugins, get_plugin, run_plugin_export_curl, run_plugin_filter, run_plugin_import,
|
||||
ImportResult, PluginCapability,
|
||||
};
|
||||
use crate::render::{render_request, variables_from_environment};
|
||||
use crate::updates::{UpdateMode, YaakUpdater};
|
||||
use crate::window_menu::app_menu;
|
||||
|
||||
mod analytics;
|
||||
mod deno;
|
||||
mod deno_ops;
|
||||
mod grpc;
|
||||
mod http_request;
|
||||
mod models;
|
||||
mod notifications;
|
||||
mod plugin;
|
||||
mod render;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod tauri_plugin_mac_window;
|
||||
@@ -284,8 +279,8 @@ async fn cmd_grpc_go(
|
||||
..conn.clone()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
return Ok(conn_id);
|
||||
}
|
||||
};
|
||||
@@ -740,22 +735,15 @@ async fn cmd_filter_response(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Have plugins register their own content type (regex?)
|
||||
let plugin_name = if content_type.contains("json") {
|
||||
"filter-jsonpath"
|
||||
} else {
|
||||
"filter-xpath"
|
||||
};
|
||||
|
||||
let body = read_to_string(response.body_path.unwrap()).unwrap();
|
||||
let plugin = match get_plugin(&w.app_handle(), plugin_name).map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to get plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
let filter_result = run_plugin_filter(&plugin, filter, &body)
|
||||
|
||||
// TODO: Have plugins register their own content type (regex?)
|
||||
let manager: State<PluginManager> = w.app_handle().state();
|
||||
manager
|
||||
.inner()
|
||||
.run_response_filter(filter, &body, &content_type)
|
||||
.await
|
||||
.expect("Failed to run filter");
|
||||
Ok(filter_result.filtered)
|
||||
.map(|r| r.data)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -764,140 +752,129 @@ async fn cmd_import_data(
|
||||
file_path: &str,
|
||||
_workspace_id: &str,
|
||||
) -> Result<WorkspaceExportResources, String> {
|
||||
let mut result: Option<ImportResult> = None;
|
||||
let file =
|
||||
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let plugins = find_plugins(w.app_handle(), &PluginCapability::Import)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
for plugin in plugins {
|
||||
let v = run_plugin_import(&plugin, file_contents)
|
||||
let manager: State<PluginManager> = w.app_handle().state();
|
||||
let import_response = manager.inner().run_import(file_contents).await?;
|
||||
let import_result: ImportResult =
|
||||
serde_json::from_str(import_response.data.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
// TODO: Track the plugin that ran, maybe return the run info in the plugin response?
|
||||
let plugin_name = import_response.info.unwrap_or_default().plugin;
|
||||
info!("Imported data using {}", plugin_name);
|
||||
analytics::track_event(
|
||||
&w.app_handle(),
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::Import,
|
||||
Some(json!({ "plugin": plugin_name })),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut imported_resources = WorkspaceExportResources::default();
|
||||
let mut id_map: HashMap<String, String> = HashMap::new();
|
||||
|
||||
fn maybe_gen_id(id: &str, model: ModelType, ids: &mut HashMap<String, String>) -> String {
|
||||
if !id.starts_with("GENERATE_ID::") {
|
||||
return id.to_string();
|
||||
}
|
||||
|
||||
let unique_key = id.replace("GENERATE_ID", "");
|
||||
if let Some(existing) = ids.get(unique_key.as_str()) {
|
||||
existing.to_string()
|
||||
} else {
|
||||
let new_id = generate_model_id(model);
|
||||
ids.insert(unique_key, new_id.clone());
|
||||
new_id
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_gen_id_opt(
|
||||
id: Option<String>,
|
||||
model: ModelType,
|
||||
ids: &mut HashMap<String, String>,
|
||||
) -> Option<String> {
|
||||
match id {
|
||||
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
for mut v in import_result.resources.workspaces {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
|
||||
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.workspaces.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} workspaces",
|
||||
imported_resources.workspaces.len()
|
||||
);
|
||||
|
||||
for mut v in import_result.resources.environments {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.environments.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} environments",
|
||||
imported_resources.environments.len()
|
||||
);
|
||||
|
||||
for mut v in import_result.resources.folders {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.folders.push(x.clone());
|
||||
}
|
||||
info!("Imported {} folders", imported_resources.folders.len());
|
||||
|
||||
for mut v in import_result.resources.http_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_http_request(&w, v)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if let Some(r) = v {
|
||||
info!("Imported data using {}", plugin.name);
|
||||
analytics::track_event(
|
||||
&w.app_handle(),
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::Import,
|
||||
Some(json!({ "plugin": plugin.name })),
|
||||
)
|
||||
.await;
|
||||
result = Some(r);
|
||||
break;
|
||||
}
|
||||
imported_resources.http_requests.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} http_requests",
|
||||
imported_resources.http_requests.len()
|
||||
);
|
||||
|
||||
match result {
|
||||
None => Err("No import handlers found".to_string()),
|
||||
Some(r) => {
|
||||
let mut imported_resources = WorkspaceExportResources::default();
|
||||
let mut id_map: HashMap<String, String> = HashMap::new();
|
||||
|
||||
let maybe_gen_id =
|
||||
|id: &str, model: ModelType, ids: &mut HashMap<String, String>| -> String {
|
||||
if !id.starts_with("GENERATE_ID::") {
|
||||
return id.to_string();
|
||||
}
|
||||
|
||||
let unique_key = id.replace("GENERATE_ID", "");
|
||||
if let Some(existing) = ids.get(unique_key.as_str()) {
|
||||
existing.to_string()
|
||||
} else {
|
||||
let new_id = generate_model_id(model);
|
||||
ids.insert(unique_key, new_id.clone());
|
||||
new_id
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_gen_id_opt = |id: Option<String>,
|
||||
model: ModelType,
|
||||
ids: &mut HashMap<String, String>|
|
||||
-> Option<String> {
|
||||
match id {
|
||||
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
for mut v in r.resources.workspaces {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
|
||||
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.workspaces.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} workspaces",
|
||||
imported_resources.workspaces.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.environments {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.environments.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} environments",
|
||||
imported_resources.environments.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.folders {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.folders.push(x.clone());
|
||||
}
|
||||
info!("Imported {} folders", imported_resources.folders.len());
|
||||
|
||||
for mut v in r.resources.http_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_http_request(&w, v)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
imported_resources.http_requests.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} http_requests",
|
||||
imported_resources.http_requests.len()
|
||||
);
|
||||
|
||||
for mut v in r.resources.grpc_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_grpc_request(&w, &v)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
imported_resources.grpc_requests.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} grpc_requests",
|
||||
imported_resources.grpc_requests.len()
|
||||
);
|
||||
|
||||
Ok(imported_resources)
|
||||
}
|
||||
for mut v in import_result.resources.grpc_requests {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_grpc_request(&w, &v)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
imported_resources.grpc_requests.push(x.clone());
|
||||
}
|
||||
info!(
|
||||
"Imported {} grpc_requests",
|
||||
imported_resources.grpc_requests.len()
|
||||
);
|
||||
|
||||
Ok(imported_resources)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -917,7 +894,11 @@ async fn cmd_request_to_curl(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rendered = render_request(&request, &workspace, environment.as_ref());
|
||||
Ok(run_plugin_export_curl(&app, &rendered)?)
|
||||
let request_json = serde_json::to_string(&rendered).map_err(|e| e.to_string())?;
|
||||
|
||||
let manager: State<PluginManager> = app.state();
|
||||
let import_response = manager.inner().run_export_curl(request_json.as_str()).await?;
|
||||
Ok(import_response.data)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -926,27 +907,21 @@ async fn cmd_curl_to_request(
|
||||
command: &str,
|
||||
workspace_id: &str,
|
||||
) -> Result<HttpRequest, String> {
|
||||
let plugin = match get_plugin(&app_handle, "importer-curl").map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to find plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let v = run_plugin_import(&plugin, command).await;
|
||||
match v {
|
||||
Ok(Some(r)) => r
|
||||
.resources
|
||||
.http_requests
|
||||
.get(0)
|
||||
.ok_or("No curl command found".to_string())
|
||||
.map(|r| {
|
||||
let mut request = r.clone();
|
||||
request.workspace_id = workspace_id.into();
|
||||
request.id = "".to_string();
|
||||
request
|
||||
}),
|
||||
Ok(None) => Err("Did not find curl request".to_string()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
let manager: State<PluginManager> = app_handle.state();
|
||||
let import_response = manager.inner().run_import(command).await?;
|
||||
let import_result: ImportResult =
|
||||
serde_json::from_str(import_response.data.as_str()).map_err(|e| e.to_string())?;
|
||||
import_result
|
||||
.resources
|
||||
.http_requests
|
||||
.get(0)
|
||||
.ok_or("No curl command found".to_string())
|
||||
.map(|r| {
|
||||
let mut request = r.clone();
|
||||
request.workspace_id = workspace_id.into();
|
||||
request.id = "".to_string();
|
||||
request
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1595,25 +1570,6 @@ async fn cmd_check_for_updates(
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let mut builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_fs::init());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_mac_window::init());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
builder = builder; // Don't complain about not being mut
|
||||
}
|
||||
|
||||
builder
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.targets([
|
||||
@@ -1621,6 +1577,7 @@ pub fn run() {
|
||||
Target::new(TargetKind::LogDir { file_name: None }),
|
||||
Target::new(TargetKind::Webview),
|
||||
])
|
||||
.level_for("plugin_runtime", log::LevelFilter::Info)
|
||||
.level_for("cookie_store", log::LevelFilter::Info)
|
||||
.level_for("h2", log::LevelFilter::Info)
|
||||
.level_for("hyper", log::LevelFilter::Info)
|
||||
@@ -1642,6 +1599,26 @@ pub fn run() {
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(plugin_runtime::init())
|
||||
.plugin(tauri_plugin_fs::init());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_mac_window::init());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
builder = builder; // Don't complain about not being mut
|
||||
}
|
||||
|
||||
builder
|
||||
.setup(|app| {
|
||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||
let app_config_dir = app.path().app_config_dir().unwrap();
|
||||
@@ -1650,13 +1627,13 @@ pub fn run() {
|
||||
app_config_dir.as_path().to_string_lossy(),
|
||||
);
|
||||
info!("App Data Dir: {}", app_data_dir.as_path().to_string_lossy());
|
||||
let dir = match is_dev() {
|
||||
let app_data_dir = match is_dev() {
|
||||
true => current_dir().unwrap(),
|
||||
false => app_data_dir,
|
||||
};
|
||||
|
||||
create_dir_all(dir.clone()).expect("Problem creating App directory!");
|
||||
let p = dir.join("db.sqlite");
|
||||
create_dir_all(app_data_dir.clone()).expect("Problem creating App directory!");
|
||||
let p = app_data_dir.join("db.sqlite");
|
||||
File::options()
|
||||
.write(true)
|
||||
.create(true)
|
||||
@@ -1679,6 +1656,10 @@ pub fn run() {
|
||||
let grpc_handle = GrpcHandle::new(&app.app_handle());
|
||||
app.manage(Mutex::new(grpc_handle));
|
||||
|
||||
// Add plugin manager
|
||||
let grpc_handle = GrpcHandle::new(&app.app_handle());
|
||||
app.manage(Mutex::new(grpc_handle));
|
||||
|
||||
// Add DB handle
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let opts = SqliteConnectOptions::from_str(p.to_str().unwrap()).unwrap();
|
||||
|
||||
@@ -1588,6 +1588,11 @@ pub struct WorkspaceExportResources {
|
||||
pub grpc_requests: Vec<GrpcRequest>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct ImportResult {
|
||||
pub resources: WorkspaceExportResources,
|
||||
}
|
||||
|
||||
pub async fn get_workspace_export_resources(
|
||||
window: &WebviewWindow,
|
||||
workspace_ids: Vec<&str>,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
((globalThis) => {
|
||||
const core = Deno.core;
|
||||
globalThis.YAML = {
|
||||
parse: core.ops.op_yaml_parse,
|
||||
stringify: core.ops.op_yaml_stringify,
|
||||
};
|
||||
})(globalThis);
|
||||
@@ -1,150 +0,0 @@
|
||||
use std::{fs, io};
|
||||
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::deno::{get_plugin_capabilities_block, run_plugin_block};
|
||||
use crate::models::{HttpRequest, WorkspaceExportResources};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PluginError {
|
||||
#[error("directory not found")]
|
||||
DirectoryNotFound(#[from] io::Error),
|
||||
#[error("anyhow error")]
|
||||
V8(#[from] anyhow::Error),
|
||||
// #[error("unknown data store error")]
|
||||
// Unknown,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct FilterResult {
|
||||
pub filtered: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct ImportResult {
|
||||
pub resources: WorkspaceExportResources,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Clone)]
|
||||
pub enum PluginCapability {
|
||||
Export,
|
||||
Import,
|
||||
Filter,
|
||||
}
|
||||
|
||||
pub struct PluginDef {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub capabilities: Vec<PluginCapability>,
|
||||
}
|
||||
|
||||
pub fn scan_plugins(app_handle: &AppHandle) -> Result<Vec<PluginDef>, PluginError> {
|
||||
let plugins_dir = app_handle
|
||||
.path()
|
||||
.resolve("plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let plugin_entries = fs::read_dir(plugins_dir)?;
|
||||
|
||||
let mut plugins = Vec::new();
|
||||
for entry in plugin_entries {
|
||||
let plugin_dir_entry = match entry {
|
||||
Err(_) => continue,
|
||||
Ok(entry) => entry,
|
||||
};
|
||||
|
||||
let plugin_index_file = plugin_dir_entry.path().join("index.mjs");
|
||||
let capabilities = get_plugin_capabilities_block(&plugin_index_file.to_str().unwrap())?;
|
||||
|
||||
plugins.push(PluginDef {
|
||||
name: plugin_dir_entry.file_name().to_string_lossy().to_string(),
|
||||
path: plugin_index_file.to_string_lossy().to_string(),
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
pub async fn find_plugins(
|
||||
app_handle: &AppHandle,
|
||||
capability: &PluginCapability,
|
||||
) -> Result<Vec<PluginDef>, PluginError> {
|
||||
let plugins = scan_plugins(app_handle)?
|
||||
.into_iter()
|
||||
.filter(|p| p.capabilities.contains(capability))
|
||||
.collect();
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
pub fn get_plugin(app_handle: &AppHandle, name: &str) -> Result<Option<PluginDef>, PluginError> {
|
||||
Ok(scan_plugins(app_handle)?
|
||||
.into_iter()
|
||||
.find(|p| p.name == name))
|
||||
}
|
||||
|
||||
pub async fn run_plugin_filter(
|
||||
plugin: &PluginDef,
|
||||
response_body: &str,
|
||||
filter: &str,
|
||||
) -> Option<FilterResult> {
|
||||
let result = run_plugin_block(
|
||||
&plugin.path,
|
||||
"pluginHookResponseFilter",
|
||||
vec![
|
||||
serde_json::to_value(response_body).unwrap(),
|
||||
serde_json::to_value(filter).unwrap(),
|
||||
],
|
||||
)
|
||||
.map_err(|e| e.to_string())
|
||||
.expect("Failed to run plugin");
|
||||
|
||||
if result.is_null() {
|
||||
error!("Plugin {} failed to run", plugin.name);
|
||||
return None;
|
||||
}
|
||||
|
||||
let resources: FilterResult =
|
||||
serde_json::from_value(result).expect("failed to parse filter plugin result json");
|
||||
Some(resources)
|
||||
}
|
||||
|
||||
pub fn run_plugin_export_curl(
|
||||
app_handle: &AppHandle,
|
||||
request: &HttpRequest,
|
||||
) -> Result<String, String> {
|
||||
let plugin = match get_plugin(app_handle, "exporter-curl").map_err(|e| e.to_string())? {
|
||||
None => return Err("Failed to get plugin".into()),
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
|
||||
let result = run_plugin_block(&plugin.path, "pluginHookExport", vec![request_json])
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let export_str: String = serde_json::from_value(result).map_err(|e| e.to_string())?;
|
||||
Ok(export_str)
|
||||
}
|
||||
|
||||
pub async fn run_plugin_import(
|
||||
plugin: &PluginDef,
|
||||
file_contents: &str,
|
||||
) -> Result<Option<ImportResult>, String> {
|
||||
let result = run_plugin_block(
|
||||
&plugin.path,
|
||||
"pluginHookImport",
|
||||
vec![serde_json::to_value(file_contents).map_err(|e| e.to_string())?],
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if result.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let resources: ImportResult = serde_json::from_value(result).map_err(|e| e.to_string())?;
|
||||
Ok(Some(resources))
|
||||
}
|
||||
Reference in New Issue
Block a user