Deno plugins (#42)

Switch from BoaJS to Deno core
This commit is contained in:
Gregory Schier
2024-06-07 10:47:41 -07:00
committed by GitHub
parent f43b38c893
commit acfc254a58
32 changed files with 1378 additions and 5663 deletions

1395
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,11 +26,13 @@ openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installa
[dependencies]
grpc = { path = "./grpc" }
templates = { path = "./templates" }
anyhow = "1.0.86"
base64 = "0.22.0"
boa_engine = { version = "0.18.0", features = ["annex-b"] }
boa_runtime = { version = "0.18.0" }
chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2"
deno_ast = { version = "0.39.0", features = ["transpiling"] }
deno_console = "0.155.0"
deno_core = { version = "0.284.0" }
hex_color = "3.0.0"
http = "0.2.10"
log = "0.4.21"
@@ -40,8 +42,9 @@ reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "br
reqwest_cookie_store = "0.6.0"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
serde_yaml = "0.9.34+deprecated"
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
tauri = { version = "2.0.0-beta", features = ["config-toml", "devtools", "protocol-asset"] }
tauri = { version = "2.0.0-beta", features = ["devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = "2.1.0-beta"
tauri-plugin-deep-link = "2.0.0-beta"
tauri-plugin-dialog = "2.0.0-beta"

View File

@@ -1,297 +0,0 @@
var j = "(?:" + [
"\\|\\|",
"\\&\\&",
";;",
"\\|\\&",
"\\<\\(",
"\\<\\<\\<",
">>",
">\\&",
"<\\&",
"[&;()|<>]"
].join("|") + ")", D = new RegExp("^" + j + "$"), q = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, _ = "'", G = '"', U = "$", $ = "", z = 4294967296;
for (var L = 0; L < 4; L++)
$ += (z * Math.random()).toString(16);
var J = new RegExp("^" + $);
function X(n, s) {
for (var e = s.lastIndex, t = [], c; c = s.exec(n); )
t.push(c), s.lastIndex === c.index && (s.lastIndex += 1);
return s.lastIndex = e, t;
}
function F(n, s, e) {
var t = typeof n == "function" ? n(e) : n[e];
return typeof t > "u" && e != "" ? t = "" : typeof t > "u" && (t = "$"), typeof t == "object" ? s + $ + JSON.stringify(t) + $ : s + t;
}
function K(n, s, e) {
e || (e = {});
var t = e.escape || "\\", c = "(\\" + t + `['"` + q + `]|[^\\s'"` + q + "])+", m = new RegExp([
"(" + j + ")",
// control chars
"(" + c + "|" + M + "|" + Q + ")+"
].join("|"), "g"), f = X(n, m);
if (f.length === 0)
return [];
s || (s = {});
var w = !1;
return f.map(function(r) {
var a = r[0];
if (!a || w)
return;
if (D.test(a))
return { op: a };
var x = !1, C = !1, d = "", O = !1, i;
function T() {
i += 1;
var v, p, R = a.charAt(i);
if (R === "{") {
if (i += 1, a.charAt(i) === "}")
throw new Error("Bad substitution: " + a.slice(i - 2, i + 1));
if (v = a.indexOf("}", i), v < 0)
throw new Error("Bad substitution: " + a.slice(i));
p = a.slice(i, v), i = v;
} else if (/[*@#?$!_-]/.test(R))
p = R, i += 1;
else {
var g = a.slice(i);
v = g.match(/[^\w\d_]/), v ? (p = g.slice(0, v.index), i += v.index - 1) : (p = g, i = a.length);
}
return F(s, "", p);
}
for (i = 0; i < a.length; i++) {
var u = a.charAt(i);
if (O = O || !x && (u === "*" || u === "?"), C)
d += u, C = !1;
else if (x)
u === x ? x = !1 : x == _ ? d += u : u === t ? (i += 1, u = a.charAt(i), u === G || u === t || u === U ? d += u : d += t + u) : u === U ? d += T() : d += u;
else if (u === G || u === _)
x = u;
else {
if (D.test(u))
return { op: a };
if (V.test(u)) {
w = !0;
var b = { comment: n.slice(r.index + i + 1) };
return d.length ? [d, b] : [b];
} else
u === t ? C = !0 : u === U ? d += T() : d += u;
}
}
return O ? { op: "glob", pattern: d } : d;
}).reduce(function(r, a) {
return typeof a > "u" ? r : r.concat(a);
}, []);
}
var Y = function(s, e, t) {
var c = K(s, e, t);
return typeof e != "function" ? c : c.reduce(function(m, f) {
if (typeof f == "object")
return m.concat(f);
var w = f.split(RegExp("(" + $ + ".*?" + $ + ")", "g"));
return w.length === 1 ? m.concat(w[0]) : m.concat(w.filter(Boolean).map(function(r) {
return J.test(r) ? JSON.parse(r.split($)[1]) : r;
}));
}, []);
}, Z = Y;
const ae = "curl", se = "cURL", ie = "cURL command line tool", H = ["d", "data", "data-raw", "data-urlencode", "data-binary", "data-ascii"], ee = [
["url"],
// Specify the URL explicitly
["user", "u"],
// Authentication
["digest"],
// Apply auth as digest
["header", "H"],
["cookie", "b"],
["get", "G"],
// Put the post data in the URL
["d", "data"],
// Add url encoded data
["data-raw"],
["data-urlencode"],
["data-binary"],
["data-ascii"],
["form", "F"],
// Add multipart data
["request", "X"],
// Request method
H
].flatMap((n) => n);
function oe(n) {
if (!n.match(/^\s*curl /))
return null;
const s = [], e = n.replace(/\ncurl/g, "; curl");
let t = [];
const m = Z(e).flatMap((r) => typeof r == "string" && r.startsWith("-") && !r.startsWith("--") && r.length > 2 ? [r.slice(0, 2), r.slice(2)] : r);
for (const r of m) {
if (typeof r == "string") {
r.startsWith("$") ? t.push(r.slice(1)) : t.push(r);
continue;
}
if ("comment" in r)
continue;
const { op: a } = r;
if (a === ";") {
s.push(t), t = [];
continue;
}
if (a != null && a.startsWith("$")) {
const x = a.slice(2, a.length - 1).replace(/\\'/g, "'");
t.push(x);
continue;
}
a === "glob" && t.push(r.pattern);
}
s.push(t);
const f = {
model: "workspace",
id: N("workspace"),
name: "Curl Import"
};
return {
resources: {
httpRequests: s.filter((r) => r[0] === "curl").map((r) => te(r, f.id)),
workspaces: [f]
}
};
}
function te(n, s) {
const e = {}, t = [];
for (let o = 1; o < n.length; o++) {
let l = n[o];
if (typeof l == "string" && (l = l.trim()), typeof l == "string" && l.match(/^-{1,2}[\w-]+/)) {
const E = l[0] === "-" && l[1] !== "-";
let h = l.replace(/^-{1,2}/, "");
if (!ee.includes(h))
continue;
let y;
const S = n[o + 1];
E && h.length > 1 ? (y = h.slice(1), h = h.slice(0, 1)) : typeof S == "string" && !S.startsWith("-") ? (y = S, o++) : y = !0, e[h] = e[h] || [], e[h].push(y);
} else
l && t.push(l);
}
let c, m;
const f = A(e, t[0] || "", ["url"]), [w, r] = W(f, "?");
c = (r == null ? void 0 : r.split("&").map((o) => {
const l = W(o, "=");
return { name: l[0] ?? "", value: l[1] ?? "", enabled: !0 };
})) ?? [], m = w ?? f;
const [a, x] = A(e, "", ["u", "user"]).split(/:(.*)$/), C = A(e, !1, ["digest"]), d = a ? C ? "digest" : "basic" : null, O = a ? {
username: a.trim(),
password: (x ?? "").trim()
} : {}, i = [
...e.header || [],
...e.H || []
].map((o) => {
const [l, E] = o.split(/:(.*)$/);
return E ? {
name: (l ?? "").trim(),
value: E.trim(),
enabled: !0
} : {
name: (l ?? "").trim().replace(/;$/, ""),
value: "",
enabled: !0
};
}), T = [
...e.cookie || [],
...e.b || []
].map((o) => {
const l = o.split("=", 1)[0], E = o.replace(`${l}=`, "");
return `${l}=${E}`;
}).join("; "), u = i.find((o) => o.name.toLowerCase() === "cookie");
T && u ? u.value += `; ${T}` : T && i.push({
name: "Cookie",
value: T,
enabled: !0
});
const b = ne(e), v = i.find((o) => o.name.toLowerCase() === "content-type"), p = v ? v.value.split(";")[0] : null, R = [
...e.form || [],
...e.F || []
].map((o) => {
const l = o.split("="), E = l[0] ?? "", h = l[1] ?? "", y = {
name: E,
enabled: !0
};
return h.indexOf("@") === 0 ? y.file = h.slice(1) : y.value = h, y;
});
let g = {}, I = null;
const B = A(e, !1, ["G", "get"]);
b.length > 0 && B ? c.push(...b) : b.length > 0 && (p == null || p === "application/x-www-form-urlencoded") ? (I = p ?? "application/x-www-form-urlencoded", g = {
form: b.map((o) => ({
...o,
name: decodeURIComponent(o.name || ""),
value: decodeURIComponent(o.value || "")
}))
}, i.push({
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: !0
})) : b.length > 0 ? (I = p === "application/json" || p === "text/xml" || p === "text/plain" ? p : "other", g = {
text: b.map(({ name: o, value: l }) => o && l ? `${o}=${l}` : o || l).join("&")
}) : R.length && (I = p ?? "multipart/form-data", g = {
form: R
}, p == null && i.push({
name: "Content-Type",
value: "multipart/form-data",
enabled: !0
}));
let P = A(e, "", ["X", "request"]).toUpperCase();
return P === "" && g && (P = "text" in g || "form" in g ? "POST" : "GET"), {
id: N("http_request"),
model: "http_request",
workspaceId: s,
name: "",
urlParameters: c,
url: m,
method: P,
headers: i,
authentication: O,
authenticationType: d,
body: g,
bodyType: I,
folderId: null,
sortPriority: 0
};
}
const ne = (n) => {
let s = [];
for (const e of H) {
const t = n[e];
if (!(!t || t.length === 0))
for (const c of t) {
if (typeof c != "string")
continue;
const [m, f] = c.split("=");
c.startsWith("@") ? s.push({
name: m ?? "",
value: "",
filePath: c.slice(1),
enabled: !0
}) : s.push({
name: m ?? "",
value: e === "data-urlencode" ? encodeURIComponent(f ?? "") : f ?? "",
enabled: !0
});
}
}
return s;
}, A = (n, s, e) => {
for (const t of e)
if (n[t] && n[t].length)
return n[t][0];
return s;
};
function W(n, s) {
const e = n.indexOf(s);
return e > -1 ? [n.slice(0, e), n.slice(e + 1)] : [n];
}
const k = {};
function N(n) {
return k[n] = (k[n] ?? -1) + 1, `GENERATE_ID::${n.toUpperCase()}_${k[n]}`;
}
export {
ie as description,
ae as id,
te as importCommand,
se as name,
oe as pluginHookImport
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +0,0 @@
const S = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", _ = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", O = [_, S];
function v(e) {
var g;
const t = k(e);
if (t == null)
return;
const o = i(t.info);
if (!O.includes(o.schema) || !Array.isArray(t.item))
return;
const u = A(t.auth), s = {
workspaces: [],
environments: [],
httpRequests: [],
folders: []
}, n = {
model: "workspace",
id: h("workspace"),
name: o.name || "Postman Import",
description: o.description || "",
variables: ((g = t.variable) == null ? void 0 : g.map((r) => ({
name: r.key,
value: r.value
}))) ?? []
};
s.workspaces.push(n);
const T = (r, p = null) => {
if (typeof r.name == "string" && Array.isArray(r.item)) {
const a = {
model: "folder",
workspaceId: n.id,
id: h("folder"),
name: r.name,
folderId: p
};
s.folders.push(a);
for (const l of r.item)
T(l, a.id);
} else if (typeof r.name == "string" && "request" in r) {
const a = i(r.request), l = j(a.body), w = A(a.auth), d = w.authenticationType == null ? u : w, q = {
model: "http_request",
id: h("http_request"),
workspaceId: n.id,
folderId: p,
name: r.name,
method: a.method || "GET",
url: typeof a.url == "string" ? a.url : i(a.url).raw,
body: l.body,
bodyType: l.bodyType,
authentication: d.authentication,
authenticationType: d.authenticationType,
headers: [
...l.headers,
...d.headers,
...b(a.header).map((m) => ({
name: m.key,
value: m.value,
enabled: !m.disabled
}))
]
};
s.httpRequests.push(q);
} else
console.log("Unknown item", r, p);
};
for (const r of t.item)
T(r);
return { resources: f(s) };
}
function A(e) {
const t = i(e);
return "basic" in t ? {
headers: [],
authenticationType: "basic",
authentication: {
username: t.basic.username || "",
password: t.basic.password || ""
}
} : "bearer" in t ? {
headers: [],
authenticationType: "bearer",
authentication: {
token: t.bearer.token || ""
}
} : { headers: [], authenticationType: null, authentication: {} };
}
function j(e) {
var o, c, u, s;
const t = i(e);
return "graphql" in t ? {
headers: [
{
name: "Content-Type",
value: "application/json",
enabled: !0
}
],
bodyType: "graphql",
body: {
text: JSON.stringify(
{ query: t.graphql.query, variables: k(t.graphql.variables) },
null,
2
)
}
} : "urlencoded" in t ? {
headers: [
{
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: !0
}
],
bodyType: "application/x-www-form-urlencoded",
body: {
form: b(t.urlencoded).map((n) => ({
enabled: !n.disabled,
name: n.key ?? "",
value: n.value ?? ""
}))
}
} : "formdata" in t ? {
headers: [
{
name: "Content-Type",
value: "multipart/form-data",
enabled: !0
}
],
bodyType: "multipart/form-data",
body: {
form: b(t.formdata).map(
(n) => n.src != null ? {
enabled: !n.disabled,
contentType: n.contentType ?? null,
name: n.key ?? "",
file: n.src ?? ""
} : {
enabled: !n.disabled,
name: n.key ?? "",
value: n.value ?? ""
}
)
}
} : "raw" in t ? {
headers: [
{
name: "Content-Type",
value: ((c = (o = t.options) == null ? void 0 : o.raw) == null ? void 0 : c.language) === "json" ? "application/json" : "",
enabled: !0
}
],
bodyType: ((s = (u = t.options) == null ? void 0 : u.raw) == null ? void 0 : s.language) === "json" ? "application/json" : "other",
body: {
text: t.raw ?? ""
}
} : { headers: [], bodyType: null, body: {} };
}
function k(e) {
try {
return i(JSON.parse(e));
} catch {
}
return null;
}
function i(e) {
return Object.prototype.toString.call(e) === "[object Object]" ? e : {};
}
function b(e) {
return Object.prototype.toString.call(e) === "[object Array]" ? e : [];
}
function f(e) {
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(f) : typeof e == "object" && e != null ? Object.fromEntries(
Object.entries(e).map(([t, o]) => [t, f(o)])
) : e;
}
const y = {};
function h(e) {
return y[e] = (y[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${y[e]}`;
}
export {
v as pluginHookImport
};

View File

@@ -1,17 +0,0 @@
function u(r) {
let e;
try {
e = JSON.parse(r);
} catch {
return;
}
if (!(!t(e) || !("yaakSchema" in e)))
return "requests" in e.resources && (e.resources.httpRequests = e.resources.requests, delete e.resources.requests), { resources: e.resources };
}
function t(r) {
return Object.prototype.toString.call(r) === "[object Object]";
}
export {
t as isJSObject,
u as pluginHookImport
};

237
src-tauri/src/deno.rs Normal file
View File

@@ -0,0 +1,237 @@
// 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 crate::deno_ops::op_yaml_parse;
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;
#[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_deno_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_deno_2(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_deno_2(
plugin_index_file: &str,
fn_name: &str,
fn_args: Vec<serde_json::Value>,
) -> Result<serde_json::Value, 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 mut 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,
&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);
// 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)
}
}
}
}

14
src-tauri/src/deno_ops.rs Normal file
View File

@@ -0,0 +1,14 @@
use deno_core::error::AnyError;
use deno_core::op2;
#[op2]
#[serde] pub fn op_yaml_parse(#[string] text: String) -> Result<serde_json::Value, AnyError> {
let value = serde_yaml::from_str(&text)?;
Ok(value)
}
#[op2]
#[string] pub fn op_yaml_stringify(#[serde] value: serde_json::Value) -> Result<String, AnyError> {
let value = serde_yaml::to_string(&value)?;
Ok(value)
}

View File

@@ -71,6 +71,8 @@ mod render;
mod tauri_plugin_mac_window;
mod updates;
mod window_menu;
mod deno;
mod deno_ops;
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
@@ -118,7 +120,6 @@ async fn cmd_dismiss_notification(
notification_id: &str,
yaak_notifier: State<'_, Mutex<YaakNotifier>>,
) -> Result<(), String> {
info!("SEEN? {notification_id}");
yaak_notifier.lock().await.seen(&app, notification_id).await
}
@@ -736,7 +737,7 @@ async fn cmd_filter_response(
};
let body = read_to_string(response.body_path.unwrap()).unwrap();
let filter_result = plugin::run_plugin_filter(&w.app_handle(), plugin_name, filter, &body)
let filter_result = plugin::run_plugin_filter(plugin_name, filter, &body)
.await
.expect("Failed to run filter");
Ok(filter_result.filtered)
@@ -759,7 +760,7 @@ async fn cmd_import_data(
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
for plugin_name in plugins {
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
let v = run_plugin_import(plugin_name, file_contents)
.await
.map_err(|e| e.to_string())?;
if let Some(r) = v {
@@ -808,13 +809,12 @@ async fn cmd_import_data(
}
};
info!("Importing resources");
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 workspace: {}", x.name);
}
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);
@@ -825,8 +825,8 @@ async fn cmd_import_data(
);
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.environments.push(x.clone());
info!("Imported environment: {}", x.name);
}
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);
@@ -838,8 +838,8 @@ async fn cmd_import_data(
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 folder: {}", x.name);
}
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);
@@ -853,8 +853,8 @@ async fn cmd_import_data(
.await
.map_err(|e| e.to_string())?;
imported_resources.http_requests.push(x.clone());
info!("Imported request: {}", x.name);
}
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);
@@ -868,8 +868,8 @@ async fn cmd_import_data(
.await
.map_err(|e| e.to_string())?;
imported_resources.grpc_requests.push(x.clone());
info!("Imported request: {}", x.name);
}
info!("Imported {} grpc_requests", imported_resources.grpc_requests.len());
Ok(imported_resources)
}
@@ -893,16 +893,15 @@ 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)?)
Ok(run_plugin_export_curl(&rendered)?)
}
#[tauri::command]
async fn cmd_curl_to_request(
app: AppHandle,
command: &str,
workspace_id: &str,
) -> Result<HttpRequest, String> {
let v = run_plugin_import(&app, "importer-curl", command)
let v = run_plugin_import("importer-curl", command)
.await
.map_err(|e| e.to_string());
match v {
@@ -1581,7 +1580,9 @@ pub fn run() {
.level_for("tokio_util", log::LevelFilter::Info)
.level_for("tonic", log::LevelFilter::Info)
.level_for("tower", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Warn)
.level_for("swc_ecma_codegen", log::LevelFilter::Off)
.level_for("swc_ecma_transforms_base", log::LevelFilter::Off)
.with_colors(ColoredLevelConfig::default())
.level(if is_dev() {
log::LevelFilter::Trace

View File

@@ -0,0 +1,7 @@
((globalThis) => {
const core = Deno.core;
globalThis.YAML = {
parse: core.ops.op_yaml_parse,
stringify: core.ops.op_yaml_stringify,
};
})(globalThis);

View File

@@ -1,17 +1,9 @@
use std::rc::Rc;
use std::path;
use boa_engine::builtins::promise::PromiseState;
use boa_engine::{
js_string, module::SimpleModuleLoader, property::Attribute, Context, JsNativeError, JsValue,
Module, Source,
};
use boa_runtime::Console;
use log::{debug, error};
use log::error;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager};
use crate::deno::run_plugin_deno_block;
use crate::models::{HttpRequest, WorkspaceExportResources};
#[derive(Default, Debug, Deserialize, Serialize)]
@@ -25,140 +17,68 @@ pub struct ImportResult {
}
pub async fn run_plugin_filter(
app_handle: &AppHandle,
plugin_name: &str,
response_body: &str,
filter: &str,
) -> Option<FilterResult> {
let result_json = run_plugin(
app_handle,
plugin_name,
"pluginHookResponseFilter",
&[js_string!(response_body).into(), js_string!(filter).into()],
);
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
let plugin_index_file = plugin_dir.join(plugin_name).join("build/index.mjs");
if result_json.is_null() {
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
"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_json).expect("failed to parse filter plugin result json");
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 mut context = Context::default();
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
let result_json = run_plugin(
app_handle,
"exporter-curl",
"pluginHookExport",
&[JsValue::from_json(&request_json, &mut context).map_err(|e| e.to_string())?],
);
pub fn run_plugin_export_curl(request: &HttpRequest) -> Result<String, String> {
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
let plugin_index_file = plugin_dir.join("exporter-curl").join("build/index.mjs");
let resources: String = serde_json::from_value(result_json).map_err(|e| e.to_string())?;
Ok(resources)
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
"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(
app_handle: &AppHandle,
plugin_name: &str,
file_contents: &str,
) -> Result<Option<ImportResult>, String> {
let result_json = run_plugin(
app_handle,
plugin_name,
"pluginHookImport",
&[js_string!(file_contents).into()],
);
let plugin_dir = path::Path::new("/Users/gschier/Workspace/yaak/plugins");
let plugin_index_file = plugin_dir.join(plugin_name).join("build/index.mjs");
if result_json.is_null() {
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
"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_json).map_err(|e| e.to_string())?;
let resources: ImportResult = serde_json::from_value(result).map_err(|e| e.to_string())?;
Ok(Some(resources))
}
fn run_plugin(
app_handle: &AppHandle,
plugin_name: &str,
entrypoint: &str,
js_args: &[JsValue],
) -> serde_json::Value {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join(plugin_name);
let plugin_index_file = plugin_dir.join("index.mjs");
debug!(
"Running plugin dir={:?} file={:?}",
plugin_dir, plugin_index_file
);
let loader = Rc::new(SimpleModuleLoader::new(plugin_dir).unwrap());
let context = &mut Context::builder()
.module_loader(loader.clone())
.build()
.expect("failed to create context");
add_runtime(context);
let source = Source::from_filepath(&plugin_index_file).expect("Error opening file");
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
let module = Module::parse(source, None, context).expect("failed to parse module");
// Insert parsed entrypoint into the module loader
loader.insert(plugin_index_file, module.clone());
let promise_result = module.load_link_evaluate(context);
// Very important to push forward the job queue after queueing promises.
context.run_jobs();
// Checking if the final promise didn't return an error.
match promise_result.state() {
PromiseState::Pending => {
panic!("Promise was pending");
}
PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined())
}
PromiseState::Rejected(err) => {
panic!("Failed to link: {}", err.display());
}
}
let namespace = module.namespace(context);
let result = namespace
.get(js_string!(entrypoint), context)
.expect("failed to get entrypoint")
.as_callable()
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
.expect("Failed to get entrypoint")
.call(&JsValue::undefined(), js_args, context)
.expect("Failed to call entrypoint");
match result.is_undefined() {
true => json!(null), // to_json doesn't work with undefined (yet)
false => result
.to_json(context)
.expect("failed to convert result to json"),
}
}
fn add_runtime(context: &mut Context) {
let console = Console::init(context);
context
.register_global_property(js_string!(Console::NAME), console, Attribute::all())
.expect("the console builtin shouldn't exist");
}