Better plugin development experience (#118)

This commit is contained in:
Gregory Schier
2024-09-29 10:41:07 -07:00
committed by GitHub
parent 1c5e62a468
commit 917adcfb2e
33 changed files with 172753 additions and 66 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
src-tauri/vendored/**/* linguist-generated=true
src-tauri/gen/schemas/**/* linguist-generated=true

View File

@@ -22,12 +22,6 @@ npm -v
rustc --version
```
Clone the [plugins repository](https://github.com/yaakapp/plugins) to your machine:
```shell
git clone https://github.com/yaakapp/plugins.git /path/to/your/plugins-directory
```
Install the NPM dependencies:
```shell
@@ -40,6 +34,8 @@ Run the `bootstrap` command to do some initial setup:
npm run bootstrap
```
_NOTE: Run with `YAAK_PLUGINS_DIR=<Path to yaakapp/plugins>` to re-build bundled plugins_
## Run the App
After bootstrapping, start the app in development mode:
@@ -48,6 +44,8 @@ After bootstrapping, start the app in development mode:
npm start
```
_NOTE: If working on bundled plugins, run with `YAAK_PLUGINS_DIR=<Path to yaakapp/plugins>`_
## SQLite Migrations
New migrations can be created from the `src-tauri/` directory:

View File

@@ -6,7 +6,7 @@ import type { HttpRequest } from "./models";
import type { HttpResponse } from "./models";
import type { Workspace } from "./models";
export type BootRequest = { dir: string, };
export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, capabilities: Array<string>, };

View File

@@ -1,14 +1,15 @@
import { InternalEvent } from '@yaakapp/api';
import { BootRequest, InternalEvent } from '@yaakapp-internal/plugin';
import path from 'node:path';
import { Worker } from 'node:worker_threads';
import { EventChannel } from './EventChannel';
import { PluginWorkerData } from './index.worker';
export class PluginHandle {
#worker: Worker;
constructor(
readonly pluginDir: string,
readonly pluginRefId: string,
readonly bootRequest: BootRequest,
readonly events: EventChannel,
) {
this.#worker = this.#createWorker();
@@ -24,28 +25,32 @@ export class PluginHandle {
#createWorker(): Worker {
const workerPath = process.env.YAAK_WORKER_PATH ?? path.join(__dirname, 'index.worker.cjs');
const workerData: PluginWorkerData = {
pluginRefId: this.pluginRefId,
bootRequest: this.bootRequest,
};
const worker = new Worker(workerPath, {
workerData: { pluginDir: this.pluginDir, pluginRefId: this.pluginRefId },
workerData,
});
worker.on('message', (e) => this.events.emit(e));
worker.on('error', this.#handleError.bind(this));
worker.on('exit', this.#handleExit.bind(this));
console.log('Created plugin worker for ', this.pluginDir);
console.log('Created plugin worker for ', this.bootRequest.dir);
return worker;
}
async #handleError(err: Error) {
console.error('Plugin errored', this.pluginDir, err);
console.error('Plugin errored', this.bootRequest.dir, err);
}
async #handleExit(code: number) {
if (code === 0) {
console.log('Plugin exited successfully', this.pluginDir);
console.log('Plugin exited successfully', this.bootRequest.dir);
} else {
console.log('Plugin exited with status', code, this.pluginDir);
console.log('Plugin exited with status', code, this.bootRequest.dir);
}
}
}

View File

@@ -18,7 +18,7 @@ const plugins: Record<string, PluginHandle> = {};
const pluginEvent: InternalEvent = JSON.parse(e.event);
// Handle special event to bootstrap plugin
if (pluginEvent.payload.type === 'boot_request') {
const plugin = new PluginHandle(pluginEvent.payload.dir, pluginEvent.pluginRefId, events);
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, events);
plugins[pluginEvent.pluginRefId] = plugin;
}

View File

@@ -1,4 +1,5 @@
import {
BootRequest,
Context,
FindHttpResponsesResponse,
GetHttpRequestByIdResponse,
@@ -14,13 +15,21 @@ import { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestAct
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
import interceptStdout from 'intercept-stdout';
import * as console from 'node:console';
import { Stats, readFileSync, statSync, watch } from 'node:fs';
import { readFileSync, Stats, statSync, watch } from 'node:fs';
import path from 'node:path';
import * as util from 'node:util';
import { parentPort, workerData } from 'node:worker_threads';
export interface PluginWorkerData {
bootRequest: BootRequest;
pluginRefId: string;
}
async function initialize() {
const { pluginDir, pluginRefId } = workerData;
const {
bootRequest: { dir: pluginDir, watch: enableWatch },
pluginRefId,
}: PluginWorkerData = workerData;
const pathPkg = path.join(pluginDir, 'package.json');
const pathMod = path.posix.join(pluginDir, 'build', 'index.js');
@@ -102,8 +111,10 @@ async function initialize() {
return sendPayload({ type: 'reload_response' }, null);
};
watchFile(pathMod, cb);
watchFile(pathPkg, cb);
if (enableWatch) {
watchFile(pathMod, cb);
watchFile(pathPkg, cb);
}
const ctx: Context = {
clipboard: {

View File

@@ -3,8 +3,8 @@ const path = require('node:path');
const { execSync } = require('node:child_process');
const pluginsDir = process.env.YAAK_PLUGINS_DIR;
if (!pluginsDir) {
console.log('YAAK_PLUGINS_DIR is not set');
process.exit(1);
console.log('Skipping bundled plugins build because YAAK_PLUGINS_DIR is not set');
return;
}
console.log('Installing Yaak plugins dependencies', pluginsDir);

View File

@@ -2,4 +2,5 @@
# will have compiled files and executables
target/
vendored
vendored/*
!vendored/plugins

View File

@@ -1183,7 +1183,7 @@ async fn cmd_install_plugin(
w: WebviewWindow,
) -> Result<Plugin, String> {
plugin_manager
.add_plugin_by_dir(&directory)
.add_plugin_by_dir(&directory, true)
.await
.map_err(|e| e.to_string())?;
@@ -1511,13 +1511,12 @@ async fn cmd_plugin_info(
plugin_manager: State<'_, PluginManager>,
) -> Result<BootResponse, String> {
let plugin = get_plugin(&w, id).await.map_err(|e| e.to_string())?;
plugin_manager
Ok(plugin_manager
.get_plugin_by_dir(plugin.directory.as_str())
.await
.ok_or("Failed to find plugin info".to_string())?
.ok_or("Failed to find plugin for info".to_string())?
.info()
.await
.ok_or("Failed to find plugin".to_string())
.await)
}
#[tauri::command]

View File

@@ -0,0 +1,98 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin,
pluginHookExport: () => pluginHookExport
});
module.exports = __toCommonJS(src_exports);
var NEWLINE = "\\\n ";
var plugin = {
httpRequestActions: [{
key: "export-curl",
label: "Copy as Curl",
icon: "copy",
async onSelect(ctx, args) {
const rendered_request = await ctx.httpRequest.render({ httpRequest: args.httpRequest, purpose: "preview" });
const data = await pluginHookExport(ctx, rendered_request);
ctx.clipboard.copyText(data);
ctx.toast.show({ message: "Curl copied to clipboard", icon: "copy" });
}
}]
};
async function pluginHookExport(_ctx, request) {
const xs = ["curl"];
if (request.method) xs.push("-X", request.method);
if (request.url) xs.push(quote(request.url));
xs.push(NEWLINE);
for (const p of (request.urlParameters ?? []).filter(onlyEnabled)) {
xs.push("--url-query", quote(`${p.name}=${p.value}`));
xs.push(NEWLINE);
}
for (const h of (request.headers ?? []).filter(onlyEnabled)) {
xs.push("--header", quote(`${h.name}: ${h.value}`));
xs.push(NEWLINE);
}
if (Array.isArray(request.body?.form)) {
const flag = request.bodyType === "multipart/form-data" ? "--form" : "--data";
for (const p of (request.body?.form ?? []).filter(onlyEnabled)) {
if (p.file) {
let v = `${p.name}=@${p.file}`;
v += p.contentType ? `;type=${p.contentType}` : "";
xs.push(flag, v);
} else {
xs.push(flag, quote(`${p.name}=${p.value}`));
}
xs.push(NEWLINE);
}
} else if (typeof request.body?.text === "string") {
xs.push("--data-raw", `$${quote(request.body.text)}`);
xs.push(NEWLINE);
}
if (request.authenticationType === "basic" || request.authenticationType === "digest") {
if (request.authenticationType === "digest") xs.push("--digest");
xs.push(
"--user",
quote(`${request.authentication?.username ?? ""}:${request.authentication?.password ?? ""}`)
);
xs.push(NEWLINE);
}
if (request.authenticationType === "bearer") {
xs.push("--header", quote(`Authorization: Bearer ${request.authentication?.token ?? ""}`));
xs.push(NEWLINE);
}
if (xs[xs.length - 1] === NEWLINE) {
xs.splice(xs.length - 1, 1);
}
return xs.join(" ");
}
function quote(arg) {
const escaped = arg.replace(/'/g, "\\'");
return `'${escaped}'`;
}
function onlyEnabled(v) {
return v.enabled !== false && !!v.name;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin,
pluginHookExport
});

View File

@@ -0,0 +1,17 @@
{
"name": "exporter-curl",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9"
},
"devDependencies": {
"@types/node": "^20.14.9",
"typescript": "^5.5.2",
"vitest": "^1.4.0",
"@yaakapp/cli": "^0.0.42"
}
}

View File

@@ -0,0 +1,510 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookResponseFilter: () => pluginHookResponseFilter
});
module.exports = __toCommonJS(src_exports);
// ../../node_modules/jsonpath-plus/dist/index-node-esm.js
var import_vm = __toESM(require("vm"), 1);
var {
hasOwnProperty: hasOwnProp
} = Object.prototype;
function push(arr, item) {
arr = arr.slice();
arr.push(item);
return arr;
}
function unshift(item, arr) {
arr = arr.slice();
arr.unshift(item);
return arr;
}
var NewError = class extends Error {
/**
* @param {AnyResult} value The evaluated scalar value
*/
constructor(value) {
super('JSONPath should not be called with "new" (it prevents return of (unwrapped) scalar values)');
this.avoidNew = true;
this.value = value;
this.name = "NewError";
}
};
function JSONPath(opts, expr, obj, callback, otherTypeCallback) {
if (!(this instanceof JSONPath)) {
try {
return new JSONPath(opts, expr, obj, callback, otherTypeCallback);
} catch (e) {
if (!e.avoidNew) {
throw e;
}
return e.value;
}
}
if (typeof opts === "string") {
otherTypeCallback = callback;
callback = obj;
obj = expr;
expr = opts;
opts = null;
}
const optObj = opts && typeof opts === "object";
opts = opts || {};
this.json = opts.json || obj;
this.path = opts.path || expr;
this.resultType = opts.resultType || "value";
this.flatten = opts.flatten || false;
this.wrap = hasOwnProp.call(opts, "wrap") ? opts.wrap : true;
this.sandbox = opts.sandbox || {};
this.eval = opts.eval === void 0 ? "safe" : opts.eval;
this.ignoreEvalErrors = typeof opts.ignoreEvalErrors === "undefined" ? false : opts.ignoreEvalErrors;
this.parent = opts.parent || null;
this.parentProperty = opts.parentProperty || null;
this.callback = opts.callback || callback || null;
this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function() {
throw new TypeError("You must supply an otherTypeCallback callback option with the @other() operator.");
};
if (opts.autostart !== false) {
const args = {
path: optObj ? opts.path : expr
};
if (!optObj) {
args.json = obj;
} else if ("json" in opts) {
args.json = opts.json;
}
const ret = this.evaluate(args);
if (!ret || typeof ret !== "object") {
throw new NewError(ret);
}
return ret;
}
}
JSONPath.prototype.evaluate = function(expr, json, callback, otherTypeCallback) {
let currParent = this.parent, currParentProperty = this.parentProperty;
let {
flatten,
wrap
} = this;
this.currResultType = this.resultType;
this.currEval = this.eval;
this.currSandbox = this.sandbox;
callback = callback || this.callback;
this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
json = json || this.json;
expr = expr || this.path;
if (expr && typeof expr === "object" && !Array.isArray(expr)) {
if (!expr.path && expr.path !== "") {
throw new TypeError('You must supply a "path" property when providing an object argument to JSONPath.evaluate().');
}
if (!hasOwnProp.call(expr, "json")) {
throw new TypeError('You must supply a "json" property when providing an object argument to JSONPath.evaluate().');
}
({
json
} = expr);
flatten = hasOwnProp.call(expr, "flatten") ? expr.flatten : flatten;
this.currResultType = hasOwnProp.call(expr, "resultType") ? expr.resultType : this.currResultType;
this.currSandbox = hasOwnProp.call(expr, "sandbox") ? expr.sandbox : this.currSandbox;
wrap = hasOwnProp.call(expr, "wrap") ? expr.wrap : wrap;
this.currEval = hasOwnProp.call(expr, "eval") ? expr.eval : this.currEval;
callback = hasOwnProp.call(expr, "callback") ? expr.callback : callback;
this.currOtherTypeCallback = hasOwnProp.call(expr, "otherTypeCallback") ? expr.otherTypeCallback : this.currOtherTypeCallback;
currParent = hasOwnProp.call(expr, "parent") ? expr.parent : currParent;
currParentProperty = hasOwnProp.call(expr, "parentProperty") ? expr.parentProperty : currParentProperty;
expr = expr.path;
}
currParent = currParent || null;
currParentProperty = currParentProperty || null;
if (Array.isArray(expr)) {
expr = JSONPath.toPathString(expr);
}
if (!expr && expr !== "" || !json) {
return void 0;
}
const exprList = JSONPath.toPathArray(expr);
if (exprList[0] === "$" && exprList.length > 1) {
exprList.shift();
}
this._hasParentSelector = null;
const result = this._trace(exprList, json, ["$"], currParent, currParentProperty, callback).filter(function(ea) {
return ea && !ea.isParentSelector;
});
if (!result.length) {
return wrap ? [] : void 0;
}
if (!wrap && result.length === 1 && !result[0].hasArrExpr) {
return this._getPreferredOutput(result[0]);
}
return result.reduce((rslt, ea) => {
const valOrPath = this._getPreferredOutput(ea);
if (flatten && Array.isArray(valOrPath)) {
rslt = rslt.concat(valOrPath);
} else {
rslt.push(valOrPath);
}
return rslt;
}, []);
};
JSONPath.prototype._getPreferredOutput = function(ea) {
const resultType = this.currResultType;
switch (resultType) {
case "all": {
const path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
ea.pointer = JSONPath.toPointer(path);
ea.path = typeof ea.path === "string" ? ea.path : JSONPath.toPathString(ea.path);
return ea;
}
case "value":
case "parent":
case "parentProperty":
return ea[resultType];
case "path":
return JSONPath.toPathString(ea[resultType]);
case "pointer":
return JSONPath.toPointer(ea.path);
default:
throw new TypeError("Unknown result type");
}
};
JSONPath.prototype._handleCallback = function(fullRetObj, callback, type) {
if (callback) {
const preferredOutput = this._getPreferredOutput(fullRetObj);
fullRetObj.path = typeof fullRetObj.path === "string" ? fullRetObj.path : JSONPath.toPathString(fullRetObj.path);
callback(preferredOutput, type, fullRetObj);
}
};
JSONPath.prototype._trace = function(expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
let retObj;
if (!expr.length) {
retObj = {
path,
value: val,
parent,
parentProperty: parentPropName,
hasArrExpr
};
this._handleCallback(retObj, callback, "value");
return retObj;
}
const loc = expr[0], x = expr.slice(1);
const ret = [];
function addRet(elems) {
if (Array.isArray(elems)) {
elems.forEach((t) => {
ret.push(t);
});
} else {
ret.push(elems);
}
}
if ((typeof loc !== "string" || literalPriority) && val && hasOwnProp.call(val, loc)) {
addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr));
} else if (loc === "*") {
this._walk(val, (m) => {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true, true));
});
} else if (loc === "..") {
addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr));
this._walk(val, (m) => {
if (typeof val[m] === "object") {
addRet(this._trace(expr.slice(), val[m], push(path, m), val, m, callback, true));
}
});
} else if (loc === "^") {
this._hasParentSelector = true;
return {
path: path.slice(0, -1),
expr: x,
isParentSelector: true
};
} else if (loc === "~") {
retObj = {
path: push(path, loc),
value: parentPropName,
parent,
parentProperty: null
};
this._handleCallback(retObj, callback, "property");
return retObj;
} else if (loc === "$") {
addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));
} else if (/^(-?\d*):(-?\d*):?(\d*)$/u.test(loc)) {
addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
} else if (loc.indexOf("?(") === 0) {
if (this.currEval === false) {
throw new Error("Eval [?(expr)] prevented in JSONPath expression.");
}
const safeLoc = loc.replace(/^\?\((.*?)\)$/u, "$1");
const nested = /@.?([^?]*)[['](\??\(.*?\))(?!.\)\])[\]']/gu.exec(safeLoc);
if (nested) {
this._walk(val, (m) => {
const npath = [nested[2]];
const nvalue = nested[1] ? val[m][nested[1]] : val[m];
const filterResults = this._trace(npath, nvalue, path, parent, parentPropName, callback, true);
if (filterResults.length > 0) {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
}
});
} else {
this._walk(val, (m) => {
if (this._eval(safeLoc, val[m], m, path, parent, parentPropName)) {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
}
});
}
} else if (loc[0] === "(") {
if (this.currEval === false) {
throw new Error("Eval [(expr)] prevented in JSONPath expression.");
}
addRet(this._trace(unshift(this._eval(loc, val, path[path.length - 1], path.slice(0, -1), parent, parentPropName), x), val, path, parent, parentPropName, callback, hasArrExpr));
} else if (loc[0] === "@") {
let addType = false;
const valueType = loc.slice(1, -2);
switch (valueType) {
case "scalar":
if (!val || !["object", "function"].includes(typeof val)) {
addType = true;
}
break;
case "boolean":
case "string":
case "undefined":
case "function":
if (typeof val === valueType) {
addType = true;
}
break;
case "integer":
if (Number.isFinite(val) && !(val % 1)) {
addType = true;
}
break;
case "number":
if (Number.isFinite(val)) {
addType = true;
}
break;
case "nonFinite":
if (typeof val === "number" && !Number.isFinite(val)) {
addType = true;
}
break;
case "object":
if (val && typeof val === valueType) {
addType = true;
}
break;
case "array":
if (Array.isArray(val)) {
addType = true;
}
break;
case "other":
addType = this.currOtherTypeCallback(val, path, parent, parentPropName);
break;
case "null":
if (val === null) {
addType = true;
}
break;
default:
throw new TypeError("Unknown value type " + valueType);
}
if (addType) {
retObj = {
path,
value: val,
parent,
parentProperty: parentPropName
};
this._handleCallback(retObj, callback, "value");
return retObj;
}
} else if (loc[0] === "`" && val && hasOwnProp.call(val, loc.slice(1))) {
const locProp = loc.slice(1);
addRet(this._trace(x, val[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true));
} else if (loc.includes(",")) {
const parts = loc.split(",");
for (const part of parts) {
addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true));
}
} else if (!literalPriority && val && hasOwnProp.call(val, loc)) {
addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr, true));
}
if (this._hasParentSelector) {
for (let t = 0; t < ret.length; t++) {
const rett = ret[t];
if (rett && rett.isParentSelector) {
const tmp = this._trace(rett.expr, val, rett.path, parent, parentPropName, callback, hasArrExpr);
if (Array.isArray(tmp)) {
ret[t] = tmp[0];
const tl = tmp.length;
for (let tt = 1; tt < tl; tt++) {
t++;
ret.splice(t, 0, tmp[tt]);
}
} else {
ret[t] = tmp;
}
}
}
}
return ret;
};
JSONPath.prototype._walk = function(val, f) {
if (Array.isArray(val)) {
const n = val.length;
for (let i = 0; i < n; i++) {
f(i);
}
} else if (val && typeof val === "object") {
Object.keys(val).forEach((m) => {
f(m);
});
}
};
JSONPath.prototype._slice = function(loc, expr, val, path, parent, parentPropName, callback) {
if (!Array.isArray(val)) {
return void 0;
}
const len = val.length, parts = loc.split(":"), step = parts[2] && Number.parseInt(parts[2]) || 1;
let start = parts[0] && Number.parseInt(parts[0]) || 0, end = parts[1] && Number.parseInt(parts[1]) || len;
start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);
end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);
const ret = [];
for (let i = start; i < end; i += step) {
const tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true);
tmp.forEach((t) => {
ret.push(t);
});
}
return ret;
};
JSONPath.prototype._eval = function(code, _v, _vname, path, parent, parentPropName) {
this.currSandbox._$_parentProperty = parentPropName;
this.currSandbox._$_parent = parent;
this.currSandbox._$_property = _vname;
this.currSandbox._$_root = this.json;
this.currSandbox._$_v = _v;
const containsPath = code.includes("@path");
if (containsPath) {
this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));
}
const scriptCacheKey = this.currEval + "Script:" + code;
if (!JSONPath.cache[scriptCacheKey]) {
let script = code.replace(/@parentProperty/gu, "_$_parentProperty").replace(/@parent/gu, "_$_parent").replace(/@property/gu, "_$_property").replace(/@root/gu, "_$_root").replace(/@([.\s)[])/gu, "_$_v$1");
if (containsPath) {
script = script.replace(/@path/gu, "_$_path");
}
if (this.currEval === "safe" || this.currEval === true || this.currEval === void 0) {
JSONPath.cache[scriptCacheKey] = new this.safeVm.Script(script);
} else if (this.currEval === "native") {
JSONPath.cache[scriptCacheKey] = new this.vm.Script(script);
} else if (typeof this.currEval === "function" && this.currEval.prototype && hasOwnProp.call(this.currEval.prototype, "runInNewContext")) {
const CurrEval = this.currEval;
JSONPath.cache[scriptCacheKey] = new CurrEval(script);
} else if (typeof this.currEval === "function") {
JSONPath.cache[scriptCacheKey] = {
runInNewContext: (context) => this.currEval(script, context)
};
} else {
throw new TypeError(`Unknown "eval" property "${this.currEval}"`);
}
}
try {
return JSONPath.cache[scriptCacheKey].runInNewContext(this.currSandbox);
} catch (e) {
if (this.ignoreEvalErrors) {
return false;
}
throw new Error("jsonPath: " + e.message + ": " + code);
}
};
JSONPath.cache = {};
JSONPath.toPathString = function(pathArr) {
const x = pathArr, n = x.length;
let p = "$";
for (let i = 1; i < n; i++) {
if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
p += /^[0-9*]+$/u.test(x[i]) ? "[" + x[i] + "]" : "['" + x[i] + "']";
}
}
return p;
};
JSONPath.toPointer = function(pointer) {
const x = pointer, n = x.length;
let p = "";
for (let i = 1; i < n; i++) {
if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
p += "/" + x[i].toString().replace(/~/gu, "~0").replace(/\//gu, "~1");
}
}
return p;
};
JSONPath.toPathArray = function(expr) {
const {
cache
} = JSONPath;
if (cache[expr]) {
return cache[expr].concat();
}
const subx = [];
const normalized = expr.replace(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ";$&;").replace(/[['](\??\(.*?\))[\]'](?!.\])/gu, function($0, $1) {
return "[#" + (subx.push($1) - 1) + "]";
}).replace(/\[['"]([^'\]]*)['"]\]/gu, function($0, prop) {
return "['" + prop.replace(/\./gu, "%@%").replace(/~/gu, "%%@@%%") + "']";
}).replace(/~/gu, ";~;").replace(/['"]?\.['"]?(?![^[]*\])|\[['"]?/gu, ";").replace(/%@%/gu, ".").replace(/%%@@%%/gu, "~").replace(/(?:;)?(\^+)(?:;)?/gu, function($0, ups) {
return ";" + ups.split("").join(";") + ";";
}).replace(/;;;|;;/gu, ";..;").replace(/;$|'?\]|'$/gu, "");
const exprList = normalized.split(";").map(function(exp) {
const match = exp.match(/#(\d+)/u);
return !match || !match[1] ? exp : subx[match[1]];
});
cache[expr] = exprList;
return cache[expr].concat();
};
JSONPath.prototype.vm = import_vm.default;
JSONPath.prototype.safeVm = import_vm.default;
var SafeScript = import_vm.default.Script;
// src/index.ts
function pluginHookResponseFilter(_ctx, args) {
const parsed = JSON.parse(args.body);
const filtered = JSONPath({ path: args.filter, json: parsed });
return JSON.stringify(filtered, null, 2);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookResponseFilter
});

View File

@@ -0,0 +1,17 @@
{
"name": "filter-jsonpath",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts"
},
"dependencies": {
"jsonpath-plus": "^9.0.0",
"@yaakapp/api": "^0.2.9"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"typescript": "^5.5.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
{
"name": "filter-xpath",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9",
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.34"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"typescript": "^5.5.2"
}
}

View File

@@ -0,0 +1,560 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// ../../node_modules/shell-quote/quote.js
var require_quote = __commonJS({
"../../node_modules/shell-quote/quote.js"(exports2, module2) {
"use strict";
module2.exports = function quote(xs) {
return xs.map(function(s) {
if (s && typeof s === "object") {
return s.op.replace(/(.)/g, "\\$1");
}
if (/["\s]/.test(s) && !/'/.test(s)) {
return "'" + s.replace(/(['\\])/g, "\\$1") + "'";
}
if (/["'\s]/.test(s)) {
return '"' + s.replace(/(["\\$`!])/g, "\\$1") + '"';
}
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, "$1\\$2");
}).join(" ");
};
}
});
// ../../node_modules/shell-quote/parse.js
var require_parse = __commonJS({
"../../node_modules/shell-quote/parse.js"(exports2, module2) {
"use strict";
var CONTROL = "(?:" + [
"\\|\\|",
"\\&\\&",
";;",
"\\|\\&",
"\\<\\(",
"\\<\\<\\<",
">>",
">\\&",
"<\\&",
"[&;()|<>]"
].join("|") + ")";
var controlRE = new RegExp("^" + CONTROL + "$");
var META = "|&;()<> \\t";
var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
var DOUBLE_QUOTE = "'((\\\\'|[^'])*?)'";
var hash = /^#$/;
var SQ = "'";
var DQ = '"';
var DS = "$";
var TOKEN = "";
var mult = 4294967296;
for (i = 0; i < 4; i++) {
TOKEN += (mult * Math.random()).toString(16);
}
var i;
var startsWithToken = new RegExp("^" + TOKEN);
function matchAll(s, r) {
var origIndex = r.lastIndex;
var matches = [];
var matchObj;
while (matchObj = r.exec(s)) {
matches.push(matchObj);
if (r.lastIndex === matchObj.index) {
r.lastIndex += 1;
}
}
r.lastIndex = origIndex;
return matches;
}
function getVar(env, pre, key) {
var r = typeof env === "function" ? env(key) : env[key];
if (typeof r === "undefined" && key != "") {
r = "";
} else if (typeof r === "undefined") {
r = "$";
}
if (typeof r === "object") {
return pre + TOKEN + JSON.stringify(r) + TOKEN;
}
return pre + r;
}
function parseInternal(string, env, opts) {
if (!opts) {
opts = {};
}
var BS = opts.escape || "\\";
var BAREWORD = "(\\" + BS + `['"` + META + `]|[^\\s'"` + META + "])+";
var chunker = new RegExp([
"(" + CONTROL + ")",
// control chars
"(" + BAREWORD + "|" + SINGLE_QUOTE + "|" + DOUBLE_QUOTE + ")+"
].join("|"), "g");
var matches = matchAll(string, chunker);
if (matches.length === 0) {
return [];
}
if (!env) {
env = {};
}
var commented = false;
return matches.map(function(match) {
var s = match[0];
if (!s || commented) {
return void 0;
}
if (controlRE.test(s)) {
return { op: s };
}
var quote = false;
var esc = false;
var out = "";
var isGlob = false;
var i2;
function parseEnvVar() {
i2 += 1;
var varend;
var varname;
var char = s.charAt(i2);
if (char === "{") {
i2 += 1;
if (s.charAt(i2) === "}") {
throw new Error("Bad substitution: " + s.slice(i2 - 2, i2 + 1));
}
varend = s.indexOf("}", i2);
if (varend < 0) {
throw new Error("Bad substitution: " + s.slice(i2));
}
varname = s.slice(i2, varend);
i2 = varend;
} else if (/[*@#?$!_-]/.test(char)) {
varname = char;
i2 += 1;
} else {
var slicedFromI = s.slice(i2);
varend = slicedFromI.match(/[^\w\d_]/);
if (!varend) {
varname = slicedFromI;
i2 = s.length;
} else {
varname = slicedFromI.slice(0, varend.index);
i2 += varend.index - 1;
}
}
return getVar(env, "", varname);
}
for (i2 = 0; i2 < s.length; i2++) {
var c = s.charAt(i2);
isGlob = isGlob || !quote && (c === "*" || c === "?");
if (esc) {
out += c;
esc = false;
} else if (quote) {
if (c === quote) {
quote = false;
} else if (quote == SQ) {
out += c;
} else {
if (c === BS) {
i2 += 1;
c = s.charAt(i2);
if (c === DQ || c === BS || c === DS) {
out += c;
} else {
out += BS + c;
}
} else if (c === DS) {
out += parseEnvVar();
} else {
out += c;
}
}
} else if (c === DQ || c === SQ) {
quote = c;
} else if (controlRE.test(c)) {
return { op: s };
} else if (hash.test(c)) {
commented = true;
var commentObj = { comment: string.slice(match.index + i2 + 1) };
if (out.length) {
return [out, commentObj];
}
return [commentObj];
} else if (c === BS) {
esc = true;
} else if (c === DS) {
out += parseEnvVar();
} else {
out += c;
}
}
if (isGlob) {
return { op: "glob", pattern: out };
}
return out;
}).reduce(function(prev, arg) {
return typeof arg === "undefined" ? prev : prev.concat(arg);
}, []);
}
module2.exports = function parse2(s, env, opts) {
var mapped = parseInternal(s, env, opts);
if (typeof env !== "function") {
return mapped;
}
return mapped.reduce(function(acc, s2) {
if (typeof s2 === "object") {
return acc.concat(s2);
}
var xs = s2.split(RegExp("(" + TOKEN + ".*?" + TOKEN + ")", "g"));
if (xs.length === 1) {
return acc.concat(xs[0]);
}
return acc.concat(xs.filter(Boolean).map(function(x) {
if (startsWithToken.test(x)) {
return JSON.parse(x.split(TOKEN)[1]);
}
return x;
}));
}, []);
};
}
});
// ../../node_modules/shell-quote/index.js
var require_shell_quote = __commonJS({
"../../node_modules/shell-quote/index.js"(exports2) {
"use strict";
exports2.quote = require_quote();
exports2.parse = require_parse();
}
});
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
var import_shell_quote = __toESM(require_shell_quote());
var DATA_FLAGS = ["d", "data", "data-raw", "data-urlencode", "data-binary", "data-ascii"];
var SUPPORTED_ARGS = [
["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
DATA_FLAGS
].flatMap((v) => v);
function pluginHookImport(ctx, rawData) {
if (!rawData.match(/^\s*curl /)) {
return null;
}
const commands = [];
const normalizedData = rawData.replace(/\ncurl/g, "; curl");
let currentCommand = [];
const parsed = (0, import_shell_quote.parse)(normalizedData);
const normalizedParseEntries = parsed.flatMap((entry) => {
if (typeof entry === "string" && entry.startsWith("-") && !entry.startsWith("--") && entry.length > 2) {
return [entry.slice(0, 2), entry.slice(2)];
}
return entry;
});
for (const parseEntry of normalizedParseEntries) {
if (typeof parseEntry === "string") {
if (parseEntry.startsWith("$")) {
currentCommand.push(parseEntry.slice(1));
} else {
currentCommand.push(parseEntry);
}
continue;
}
if ("comment" in parseEntry) {
continue;
}
const { op } = parseEntry;
if (op === ";") {
commands.push(currentCommand);
currentCommand = [];
continue;
}
if (op?.startsWith("$")) {
const str = op.slice(2, op.length - 1).replace(/\\'/g, "'");
currentCommand.push(str);
continue;
}
if (op === "glob") {
currentCommand.push(parseEntry.pattern);
}
}
commands.push(currentCommand);
const workspace = {
model: "workspace",
id: generateId("workspace"),
name: "Curl Import"
};
const requests = commands.filter((command) => command[0] === "curl").map((v) => importCommand(v, workspace.id));
return {
resources: {
httpRequests: requests,
workspaces: [workspace]
}
};
}
function importCommand(parseEntries, workspaceId) {
const pairsByName = {};
const singletons = [];
for (let i = 1; i < parseEntries.length; i++) {
let parseEntry = parseEntries[i];
if (typeof parseEntry === "string") {
parseEntry = parseEntry.trim();
}
if (typeof parseEntry === "string" && parseEntry.match(/^-{1,2}[\w-]+/)) {
const isSingleDash = parseEntry[0] === "-" && parseEntry[1] !== "-";
let name = parseEntry.replace(/^-{1,2}/, "");
if (!SUPPORTED_ARGS.includes(name)) {
continue;
}
let value;
const nextEntry = parseEntries[i + 1];
if (isSingleDash && name.length > 1) {
value = name.slice(1);
name = name.slice(0, 1);
} else if (typeof nextEntry === "string" && !nextEntry.startsWith("-")) {
value = nextEntry;
i++;
} else {
value = true;
}
pairsByName[name] = pairsByName[name] || [];
pairsByName[name].push(value);
} else if (parseEntry) {
singletons.push(parseEntry);
}
}
let urlParameters;
let url;
const urlArg = getPairValue(pairsByName, singletons[0] || "", ["url"]);
const [baseUrl, search] = splitOnce(urlArg, "?");
urlParameters = search?.split("&").map((p) => {
const v = splitOnce(p, "=");
return { name: decodeURIComponent(v[0] ?? ""), value: decodeURIComponent(v[1] ?? ""), enabled: true };
}) ?? [];
url = baseUrl ?? urlArg;
const [username, password] = getPairValue(pairsByName, "", ["u", "user"]).split(/:(.*)$/);
const isDigest = getPairValue(pairsByName, false, ["digest"]);
const authenticationType = username ? isDigest ? "digest" : "basic" : null;
const authentication = username ? {
username: username.trim(),
password: (password ?? "").trim()
} : {};
const headers = [
...pairsByName["header"] || [],
...pairsByName["H"] || []
].map((header) => {
const [name, value] = header.split(/:(.*)$/);
if (!value) {
return {
name: (name ?? "").trim().replace(/;$/, ""),
value: "",
enabled: true
};
}
return {
name: (name ?? "").trim(),
value: value.trim(),
enabled: true
};
});
const cookieHeaderValue = [
...pairsByName["cookie"] || [],
...pairsByName["b"] || []
].map((str) => {
const name = str.split("=", 1)[0];
const value = str.replace(`${name}=`, "");
return `${name}=${value}`;
}).join("; ");
const existingCookieHeader = headers.find((header) => header.name.toLowerCase() === "cookie");
if (cookieHeaderValue && existingCookieHeader) {
existingCookieHeader.value += `; ${cookieHeaderValue}`;
} else if (cookieHeaderValue) {
headers.push({
name: "Cookie",
value: cookieHeaderValue,
enabled: true
});
}
const dataParameters = pairsToDataParameters(pairsByName);
const contentTypeHeader = headers.find((header) => header.name.toLowerCase() === "content-type");
const mimeType = contentTypeHeader ? contentTypeHeader.value.split(";")[0] : null;
const formDataParams = [
...pairsByName["form"] || [],
...pairsByName["F"] || []
].map((str) => {
const parts = str.split("=");
const name = parts[0] ?? "";
const value = parts[1] ?? "";
const item = {
name,
enabled: true
};
if (value.indexOf("@") === 0) {
item["file"] = value.slice(1);
} else {
item["value"] = value;
}
return item;
});
let body = {};
let bodyType = null;
const bodyAsGET = getPairValue(pairsByName, false, ["G", "get"]);
if (dataParameters.length > 0 && bodyAsGET) {
urlParameters.push(...dataParameters);
} else if (dataParameters.length > 0 && (mimeType == null || mimeType === "application/x-www-form-urlencoded")) {
bodyType = mimeType ?? "application/x-www-form-urlencoded";
body = {
form: dataParameters.map((parameter) => ({
...parameter,
name: decodeURIComponent(parameter.name || ""),
value: decodeURIComponent(parameter.value || "")
}))
};
headers.push({
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: true
});
} else if (dataParameters.length > 0) {
bodyType = mimeType === "application/json" || mimeType === "text/xml" || mimeType === "text/plain" ? mimeType : "other";
body = {
text: dataParameters.map(({ name, value }) => name && value ? `${name}=${value}` : name || value).join("&")
};
} else if (formDataParams.length) {
bodyType = mimeType ?? "multipart/form-data";
body = {
form: formDataParams
};
if (mimeType == null) {
headers.push({
name: "Content-Type",
value: "multipart/form-data",
enabled: true
});
}
}
let method = getPairValue(pairsByName, "", ["X", "request"]).toUpperCase();
if (method === "" && body) {
method = "text" in body || "form" in body ? "POST" : "GET";
}
const request = {
id: generateId("http_request"),
model: "http_request",
workspaceId,
name: "",
urlParameters,
url,
method,
headers,
authentication,
authenticationType,
body,
bodyType,
folderId: null,
sortPriority: 0
};
return request;
}
function pairsToDataParameters(keyedPairs) {
let dataParameters = [];
for (const flagName of DATA_FLAGS) {
const pairs = keyedPairs[flagName];
if (!pairs || pairs.length === 0) {
continue;
}
for (const p of pairs) {
if (typeof p !== "string") continue;
const [name, value] = p.split("=");
if (p.startsWith("@")) {
dataParameters.push({
name: name ?? "",
value: "",
filePath: p.slice(1),
enabled: true
});
} else {
dataParameters.push({
name: name ?? "",
value: flagName === "data-urlencode" ? encodeURIComponent(value ?? "") : value ?? "",
enabled: true
});
}
}
}
return dataParameters;
}
var getPairValue = (pairsByName, defaultValue, names) => {
for (const name of names) {
if (pairsByName[name] && pairsByName[name].length) {
return pairsByName[name][0];
}
}
return defaultValue;
};
function splitOnce(str, sep) {
const index = str.indexOf(sep);
if (index > -1) {
return [str.slice(0, index), str.slice(index + 1)];
}
return [str];
}
var idCount = {};
function generateId(model) {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,19 @@
{
"name": "importer-curl",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9",
"shell-quote": "^1.8.1"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"@types/shell-quote": "^1.7.5",
"typescript": "^5.5.2",
"vitest": "^1.4.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"name": "importer-insomnia",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9",
"yaml": "^2.4.2"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"typescript": "^5.5.2"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
{
"name": "importer-openapi",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9",
"openapi-to-postmanv2": "^4.23.1",
"yaml": "^2.4.2"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"@types/openapi-to-postmanv2": "^3.2.4",
"typescript": "^5.5.2"
}
}

View File

@@ -0,0 +1,301 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
var POSTMAN_2_1_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
var POSTMAN_2_0_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json";
var VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA];
function pluginHookImport(_ctx, contents) {
const root = parseJSONToRecord(contents);
if (root == null) return;
const info = toRecord(root.info);
const isValidSchema = VALID_SCHEMAS.includes(info.schema);
if (!isValidSchema || !Array.isArray(root.item)) {
return;
}
const globalAuth = importAuth(root.auth);
const exportResources = {
workspaces: [],
environments: [],
httpRequests: [],
folders: []
};
const workspace = {
model: "workspace",
id: generateId("workspace"),
name: info.name || "Postman Import",
description: info.description?.content ?? info.description ?? "",
variables: root.variable?.map((v) => ({
name: v.key,
value: v.value
})) ?? []
};
exportResources.workspaces.push(workspace);
const importItem = (v, folderId = null) => {
if (typeof v.name === "string" && Array.isArray(v.item)) {
const folder = {
model: "folder",
workspaceId: workspace.id,
id: generateId("folder"),
name: v.name,
folderId
};
exportResources.folders.push(folder);
for (const child of v.item) {
importItem(child, folder.id);
}
} else if (typeof v.name === "string" && "request" in v) {
const r = toRecord(v.request);
const bodyPatch = importBody(r.body);
const requestAuthPath = importAuth(r.auth);
const authPatch = requestAuthPath.authenticationType == null ? globalAuth : requestAuthPath;
const headers = toArray(r.header).map((h) => {
return {
name: h.key,
value: h.value,
enabled: !h.disabled
};
});
for (const bodyPatchHeader of bodyPatch.headers) {
const existingHeader = headers.find((h) => h.name.toLowerCase() === bodyPatchHeader.name.toLowerCase());
if (existingHeader) {
continue;
}
headers.push(bodyPatchHeader);
}
const { url, urlParameters } = convertUrl(r.url);
const request = {
model: "http_request",
id: generateId("http_request"),
workspaceId: workspace.id,
folderId,
name: v.name,
method: r.method || "GET",
url,
urlParameters,
body: bodyPatch.body,
bodyType: bodyPatch.bodyType,
authentication: authPatch.authentication,
authenticationType: authPatch.authenticationType,
headers
};
exportResources.httpRequests.push(request);
} else {
console.log("Unknown item", v, folderId);
}
};
for (const item of root.item) {
importItem(item);
}
return { resources: convertTemplateSyntax(exportResources) };
}
function convertUrl(url) {
if (typeof url === "string") {
return { url, urlParameters: [] };
}
url = toRecord(url);
let v = "";
if ("protocol" in url && typeof url.protocol === "string") {
v += `${url.protocol}://`;
}
if ("host" in url) {
v += `${Array.isArray(url.host) ? url.host.join(".") : url.host}`;
}
if ("port" in url && typeof url.port === "string") {
v += `:${url.port}`;
}
if ("path" in url && Array.isArray(url.path) && url.path.length > 0) {
v += `/${Array.isArray(url.path) ? url.path.join("/") : url.path}`;
}
const params = [];
if ("query" in url && Array.isArray(url.query) && url.query.length > 0) {
for (const query of url.query) {
params.push({
name: query.key ?? "",
value: query.value ?? "",
enabled: !query.disabled
});
}
}
if ("variable" in url && Array.isArray(url.variable) && url.variable.length > 0) {
for (const v2 of url.variable) {
params.push({
name: ":" + (v2.key ?? ""),
value: v2.value ?? "",
enabled: !v2.disabled
});
}
}
if ("hash" in url && typeof url.hash === "string") {
v += `#${url.hash}`;
}
return { url: v, urlParameters: params };
}
function importAuth(rawAuth) {
const auth = toRecord(rawAuth);
if ("basic" in auth) {
return {
authenticationType: "basic",
authentication: {
username: auth.basic.username || "",
password: auth.basic.password || ""
}
};
} else if ("bearer" in auth) {
return {
authenticationType: "bearer",
authentication: {
token: auth.bearer.token || ""
}
};
} else {
return { authenticationType: null, authentication: {} };
}
}
function importBody(rawBody) {
const body = toRecord(rawBody);
if (body.mode === "graphql") {
return {
headers: [
{
name: "Content-Type",
value: "application/json",
enabled: true
}
],
bodyType: "graphql",
body: {
text: JSON.stringify(
{ query: body.graphql.query, variables: parseJSONToRecord(body.graphql.variables) },
null,
2
)
}
};
} else if (body.mode === "urlencoded") {
return {
headers: [
{
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: true
}
],
bodyType: "application/x-www-form-urlencoded",
body: {
form: toArray(body.urlencoded).map((f) => ({
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? ""
}))
}
};
} else if (body.mode === "formdata") {
return {
headers: [
{
name: "Content-Type",
value: "multipart/form-data",
enabled: true
}
],
bodyType: "multipart/form-data",
body: {
form: toArray(body.formdata).map(
(f) => f.src != null ? {
enabled: !f.disabled,
contentType: f.contentType ?? null,
name: f.key ?? "",
file: f.src ?? ""
} : {
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? ""
}
)
}
};
} else if (body.mode === "raw") {
return {
headers: [
{
name: "Content-Type",
value: body.options?.raw?.language === "json" ? "application/json" : "",
enabled: true
}
],
bodyType: body.options?.raw?.language === "json" ? "application/json" : "other",
body: {
text: body.raw ?? ""
}
};
} else if (body.mode === "file") {
return {
headers: [],
bodyType: "binary",
body: {
filePath: body.file?.src
}
};
} else {
return { headers: [], bodyType: null, body: {} };
}
}
function parseJSONToRecord(jsonStr) {
try {
return toRecord(JSON.parse(jsonStr));
} catch (err) {
}
return null;
}
function toRecord(value) {
if (Object.prototype.toString.call(value) === "[object Object]") return value;
else return {};
}
function toArray(value) {
if (Object.prototype.toString.call(value) === "[object Array]") return value;
else return [];
}
function convertTemplateSyntax(obj) {
if (typeof obj === "string") {
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}");
} else if (Array.isArray(obj) && obj != null) {
return obj.map(convertTemplateSyntax);
} else if (typeof obj === "object" && obj != null) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)])
);
} else {
return obj;
}
}
var idCount = {};
function generateId(model) {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,19 @@
{
"name": "importer-postman",
"private": true,
"version": "0.0.1",
"main": "./build/index.js",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"esbuild": "^0.21.5",
"typescript": "^5.5.2",
"vitest": "^1.4.0"
}
}

View File

@@ -0,0 +1,52 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
function pluginHookImport(_ctx, contents) {
let parsed;
try {
parsed = JSON.parse(contents);
} catch (err) {
return void 0;
}
if (!isJSObject(parsed)) {
return void 0;
}
const isYaakExport = "yaakSchema" in parsed;
if (!isYaakExport) {
return;
}
if ("requests" in parsed.resources) {
parsed.resources.httpRequests = parsed.resources.requests;
delete parsed.resources["requests"];
}
return { resources: parsed.resources };
}
function isJSObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,17 @@
{
"name": "importer-yaak",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js"
},
"dependencies": {
"@yaakapp/api": "^0.2.9"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/node": "^20.14.9",
"esbuild": "^0.21.5",
"typescript": "^5.5.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "template-function-response",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts"
},
"dependencies": {
"@yaakapp/api": "^0.2.9",
"jsonpath-plus": "^9.0.0",
"xpath": "^0.0.34",
"@xmldom/xmldom": "^0.8.10"
},
"devDependencies": {
"@yaakapp/cli": "^0.0.42",
"@types/jsonpath": "^0.2.4",
"@types/node": "^20.14.9",
"typescript": "^5.5.2",
"vitest": "^1.4.0"
}
}

View File

@@ -39,7 +39,6 @@ impl Builder {
create_dir_all(app_path.clone()).expect("Problem creating App directory!");
let db_file_path = app_path.join("db.sqlite");
info!("Opening SQLite DB at {db_file_path:?}");
{
let db_file_path = db_file_path.clone();
@@ -66,7 +65,7 @@ impl Builder {
async fn must_migrate_db<R: Runtime>(app_handle: &AppHandle<R>, path: &PathBuf) {
let app_data_dir = app_handle.path().app_data_dir().unwrap();
let sqlite_file_path = app_data_dir.join("db.sqlite");
info!("Creating database file at {:?}", sqlite_file_path);
File::options()
.write(true)
@@ -76,7 +75,7 @@ async fn must_migrate_db<R: Runtime>(app_handle: &AppHandle<R>, path: &PathBuf)
let p_string = sqlite_file_path.to_string_lossy().replace(' ', "%20");
let url = format!("sqlite://{}?mode=rwc", p_string);
info!("Connecting to database at {}", url);
let opts = SqliteConnectOptions::from_str(path.to_string_lossy().to_string().as_str()).unwrap();
let pool = SqlitePool::connect_with(opts)
@@ -86,11 +85,11 @@ async fn must_migrate_db<R: Runtime>(app_handle: &AppHandle<R>, path: &PathBuf)
.path()
.resolve("migrations", BaseDirectory::Resource)
.expect("failed to resolve resource");
info!("Running database migrations from: {}", p.to_string_lossy());
let mut m = Migrator::new(p).await.expect("Failed to load migrations");
m.set_ignore_missing(true); // So we can roll back versions and not crash
m.run(&pool).await.expect("Failed to run migrations");
info!("Database migrations complete");
}

View File

@@ -6,7 +6,7 @@ import type { HttpRequest } from "./models";
import type { HttpResponse } from "./models";
import type { Workspace } from "./models";
export type BootRequest = { dir: string, };
export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, capabilities: Array<string>, };

View File

@@ -74,6 +74,7 @@ pub enum InternalEventPayload {
#[ts(export, export_to="events.ts")]
pub struct BootRequest {
pub dir: String,
pub watch: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

View File

@@ -12,6 +12,7 @@ use crate::server::plugin_runtime::plugin_runtime_server::PluginRuntimeServer;
use crate::server::PluginRuntimeServerImpl;
use log::{info, warn};
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -32,6 +33,12 @@ pub struct PluginManager {
server: Arc<PluginRuntimeServerImpl>,
}
#[derive(Clone)]
struct PluginCandidate {
dir: String,
watch: bool,
}
impl PluginManager {
pub fn new<R: Runtime>(app_handle: AppHandle<R>) -> PluginManager {
let (events_tx, mut events_rx) = mpsc::channel(128);
@@ -123,24 +130,45 @@ impl PluginManager {
plugin_manager
}
pub async fn list_plugin_dirs<R: Runtime>(&self, app_handle: &AppHandle<R>) -> Vec<String> {
let plugins_dir = app_handle
async fn list_plugin_dirs<R: Runtime>(
&self,
app_handle: &AppHandle<R>,
) -> Vec<PluginCandidate> {
let bundled_plugins_dir = &app_handle
.path()
.resolve("vendored/plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let bundled_plugin_dirs = read_plugins_dir(&plugins_dir)
let plugins_dir = match env::var("YAAK_PLUGINS_DIR") {
Ok(d) => &PathBuf::from(d),
Err(_) => bundled_plugins_dir,
};
info!("Loading bundled plugins from {plugins_dir:?}");
let bundled_plugin_dirs: Vec<PluginCandidate> = read_plugins_dir(&plugins_dir)
.await
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str());
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str())
.iter()
.map(|d| {
let is_vendored = plugins_dir.starts_with(bundled_plugins_dir);
PluginCandidate {
dir: d.into(),
watch: !is_vendored,
}
})
.collect();
let plugins = list_plugins(app_handle).await.unwrap_or_default();
let installed_plugin_dirs = plugins
let installed_plugin_dirs: Vec<PluginCandidate> = plugins
.iter()
.map(|p| p.directory.to_owned())
.collect::<Vec<String>>();
.map(|p| PluginCandidate {
dir: p.directory.to_owned(),
watch: true,
})
.collect();
let plugin_dirs = [bundled_plugin_dirs, installed_plugin_dirs].concat();
plugin_dirs
[bundled_plugin_dirs, installed_plugin_dirs].concat()
}
pub async fn uninstall(&self, dir: &str) -> Result<()> {
@@ -152,12 +180,11 @@ impl PluginManager {
}
async fn remove_plugin(&self, plugin: &PluginHandle) -> Result<()> {
let mut plugins = self.plugins.lock().await;
// Terminate the plugin
plugin.terminate().await?;
// Remove the plugin from the list
let mut plugins = self.plugins.lock().await;
let pos = plugins.iter().position(|p| p.ref_id == plugin.ref_id);
if let Some(pos) = pos {
plugins.remove(pos);
@@ -166,26 +193,22 @@ impl PluginManager {
Ok(())
}
pub async fn add_plugin_by_dir(&self, dir: &str) -> Result<()> {
pub async fn add_plugin_by_dir(&self, dir: &str, watch: bool) -> Result<()> {
info!("Adding plugin by dir {dir}");
let maybe_tx = self.server.app_to_plugin_events_tx.lock().await;
let tx = match &*maybe_tx {
None => return Err(ClientNotInitializedErr),
Some(tx) => tx,
};
let ph = PluginHandle::new(dir, tx.clone());
self.plugins.lock().await.push(ph.clone());
let plugin = self
.get_plugin_by_dir(dir)
.await
.ok_or(PluginNotFoundErr(dir.to_string()))?;
let plugin_handle = PluginHandle::new(dir, tx.clone());
// Boot the plugin
let event = self
.send_to_plugin_and_wait(
&plugin,
&plugin_handle,
&InternalEventPayload::BootRequest(BootRequest {
dir: dir.to_string(),
watch,
}),
)
.await?;
@@ -195,7 +218,10 @@ impl PluginManager {
_ => return Err(UnknownEventErr),
};
plugin.set_boot_response(&resp).await;
plugin_handle.set_boot_response(&resp).await;
// Add the new plugin after it boots
self.plugins.lock().await.push(plugin_handle.clone());
Ok(())
}
@@ -204,18 +230,30 @@ impl PluginManager {
&self,
app_handle: &AppHandle<R>,
) -> Result<()> {
for dir in self.list_plugin_dirs(app_handle).await {
let dirs = self.list_plugin_dirs(app_handle).await;
for d in dirs.clone() {
// First remove the plugin if it exists
if let Some(plugin) = self.get_plugin_by_dir(dir.as_str()).await {
if let Some(plugin) = self.get_plugin_by_dir(d.dir.as_str()).await {
if let Err(e) = self.remove_plugin(&plugin).await {
warn!("Failed to remove plugin {dir} {e:?}");
warn!("Failed to remove plugin {} {e:?}", d.dir);
}
}
if let Err(e) = self.add_plugin_by_dir(dir.as_str()).await {
warn!("Failed to add plugin {dir} {e:?}");
if let Err(e) = self.add_plugin_by_dir(d.dir.as_str(), d.watch).await {
warn!("Failed to add plugin {} {e:?}", d.dir);
}
}
info!(
"Initialized all plugins:\n - {}",
self.plugins
.lock()
.await
.iter()
.map(|p| p.dir.to_string())
.collect::<Vec<String>>()
.join("\n - "),
);
Ok(())
}
@@ -271,7 +309,7 @@ impl PluginManager {
pub async fn get_plugin_by_name(&self, name: &str) -> Option<PluginHandle> {
for plugin in self.plugins.lock().await.iter().cloned() {
let info = plugin.info().await?;
let info = plugin.info().await;
if info.name == name {
return Some(plugin);
}
@@ -446,8 +484,7 @@ impl PluginManager {
.get_plugin_by_ref_id(ref_id.as_str())
.await
.ok_or(PluginNotFoundErr(ref_id))?;
let info = plugin.info().await.unwrap();
Ok((resp, info.name))
Ok((resp, plugin.info().await.name))
}
}
}

View File

@@ -11,7 +11,7 @@ pub struct PluginHandle {
pub ref_id: String,
pub dir: String,
pub(crate) to_plugin_tx: Arc<Mutex<mpsc::Sender<tonic::Result<EventStreamEvent>>>>,
pub(crate) boot_resp: Arc<Mutex<Option<BootResponse>>>,
pub(crate) boot_resp: Arc<Mutex<BootResponse>>,
}
impl PluginHandle {
@@ -22,11 +22,11 @@ impl PluginHandle {
ref_id: ref_id.clone(),
dir: dir.to_string(),
to_plugin_tx: Arc::new(Mutex::new(tx)),
boot_resp: Arc::new(Mutex::new(None)),
boot_resp: Arc::new(Mutex::new(BootResponse::default())),
}
}
pub async fn info(&self) -> Option<BootResponse> {
pub async fn info(&self) -> BootResponse {
let resp = &*self.boot_resp.lock().await;
resp.clone()
}
@@ -72,6 +72,6 @@ impl PluginHandle {
pub async fn set_boot_response(&self, resp: &BootResponse) {
let mut boot_resp = self.boot_resp.lock().await;
*boot_resp = Some(resp.clone());
*boot_resp = resp.clone();
}
}

View File

@@ -12,7 +12,6 @@ export function useHttpRequestActions() {
const httpRequestActions = useQuery({
queryKey: ['http_request_actions', pluginsKey],
refetchOnWindowFocus: false,
queryFn: async () => {
const responses = (await invokeCmd(
'cmd_http_request_actions',