mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 01:08:28 +02:00
Move JS monorepo packages to folder
This commit is contained in:
2
packages/plugin-runtime-types/.gitignore
vendored
Normal file
2
packages/plugin-runtime-types/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
lib
|
||||
node_modules
|
||||
47
packages/plugin-runtime-types/package-lock.json
generated
Normal file
47
packages/plugin-runtime-types/package-lock.json
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.17",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
packages/plugin-runtime-types/package.json
Normal file
26
packages/plugin-runtime-types/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.2.17",
|
||||
"main": "lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"bootstrap": "npm run build",
|
||||
"build": "run-s build:copy-types build:tsc",
|
||||
"build:tsc": "tsc",
|
||||
"build:copy-types": "run-p build:copy-types:*",
|
||||
"build:copy-types:root": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/*.ts ./src/bindings",
|
||||
"build:copy-types:next": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/serde_json/*.ts ./src/bindings/serde_json",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpy-cli": "^5.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
338
packages/plugin-runtime-types/src/bindings/events.ts
Normal file
338
packages/plugin-runtime-types/src/bindings/events.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type {
|
||||
Environment,
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
HttpResponse,
|
||||
Workspace,
|
||||
} from './models';
|
||||
import type { JsonValue } from './serde_json/JsonValue';
|
||||
|
||||
export type BootRequest = { dir: string; watch: boolean };
|
||||
|
||||
export type BootResponse = { name: string; version: string; capabilities: Array<string> };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest };
|
||||
|
||||
export type CallHttpRequestActionRequest = {
|
||||
key: string;
|
||||
pluginRefId: string;
|
||||
args: CallHttpRequestActionArgs;
|
||||
};
|
||||
|
||||
export type CallTemplateFunctionArgs = {
|
||||
purpose: RenderPurpose;
|
||||
values: { [key in string]?: string };
|
||||
};
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string; args: CallTemplateFunctionArgs };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null };
|
||||
|
||||
export type Color =
|
||||
| 'custom'
|
||||
| 'default'
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'notice'
|
||||
| 'warning'
|
||||
| 'danger';
|
||||
|
||||
export type CopyTextRequest = { text: string };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest };
|
||||
|
||||
export type ExportHttpRequestResponse = { content: string };
|
||||
|
||||
export type FilterRequest = { content: string; filter: string };
|
||||
|
||||
export type FilterResponse = { content: string };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string; limit?: number };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse> };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
export type GetHttpRequestActionsResponse = {
|
||||
actions: Array<HttpRequestAction>;
|
||||
pluginRefId: string;
|
||||
};
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null };
|
||||
|
||||
export type GetTemplateFunctionsResponse = {
|
||||
functions: Array<TemplateFunction>;
|
||||
pluginRefId: string;
|
||||
};
|
||||
|
||||
export type HttpRequestAction = { key: string; label: string; icon?: Icon };
|
||||
|
||||
export type Icon = 'copy' | 'info' | 'check_circle' | 'alert_triangle' | '_unknown';
|
||||
|
||||
export type ImportRequest = { content: string };
|
||||
|
||||
export type ImportResources = {
|
||||
workspaces: Array<Workspace>;
|
||||
environments: Array<Environment>;
|
||||
folders: Array<Folder>;
|
||||
httpRequests: Array<HttpRequest>;
|
||||
grpcRequests: Array<GrpcRequest>;
|
||||
};
|
||||
|
||||
export type ImportResponse = { resources: ImportResources };
|
||||
|
||||
export type InternalEvent = {
|
||||
id: string;
|
||||
pluginRefId: string;
|
||||
replyId: string | null;
|
||||
payload: InternalEventPayload;
|
||||
windowContext: WindowContext;
|
||||
};
|
||||
|
||||
export type InternalEventPayload =
|
||||
| ({ type: 'boot_request' } & BootRequest)
|
||||
| ({ type: 'boot_response' } & BootResponse)
|
||||
| { type: 'reload_request' }
|
||||
| { type: 'reload_response' }
|
||||
| { type: 'terminate_request' }
|
||||
| { type: 'terminate_response' }
|
||||
| ({ type: 'import_request' } & ImportRequest)
|
||||
| ({ type: 'import_response' } & ImportResponse)
|
||||
| ({ type: 'filter_request' } & FilterRequest)
|
||||
| ({ type: 'filter_response' } & FilterResponse)
|
||||
| ({ type: 'export_http_request_request' } & ExportHttpRequestRequest)
|
||||
| ({ type: 'export_http_request_response' } & ExportHttpRequestResponse)
|
||||
| ({ type: 'send_http_request_request' } & SendHttpRequestRequest)
|
||||
| ({ type: 'send_http_request_response' } & SendHttpRequestResponse)
|
||||
| ({ type: 'get_http_request_actions_request' } & GetHttpRequestActionsRequest)
|
||||
| ({ type: 'get_http_request_actions_response' } & GetHttpRequestActionsResponse)
|
||||
| ({ type: 'call_http_request_action_request' } & CallHttpRequestActionRequest)
|
||||
| { type: 'get_template_functions_request' }
|
||||
| ({ type: 'get_template_functions_response' } & GetTemplateFunctionsResponse)
|
||||
| ({ type: 'call_template_function_request' } & CallTemplateFunctionRequest)
|
||||
| ({ type: 'call_template_function_response' } & CallTemplateFunctionResponse)
|
||||
| ({ type: 'copy_text_request' } & CopyTextRequest)
|
||||
| ({ type: 'render_http_request_request' } & RenderHttpRequestRequest)
|
||||
| ({ type: 'render_http_request_response' } & RenderHttpRequestResponse)
|
||||
| ({ type: 'template_render_request' } & TemplateRenderRequest)
|
||||
| ({ type: 'template_render_response' } & TemplateRenderResponse)
|
||||
| ({ type: 'show_toast_request' } & ShowToastRequest)
|
||||
| ({ type: 'prompt_text_request' } & PromptTextRequest)
|
||||
| ({ type: 'prompt_text_response' } & PromptTextResponse)
|
||||
| ({ type: 'get_http_request_by_id_request' } & GetHttpRequestByIdRequest)
|
||||
| ({ type: 'get_http_request_by_id_response' } & GetHttpRequestByIdResponse)
|
||||
| ({ type: 'find_http_responses_request' } & FindHttpResponsesRequest)
|
||||
| ({ type: 'find_http_responses_response' } & FindHttpResponsesResponse)
|
||||
| { type: 'empty_response' };
|
||||
|
||||
export type OpenFileFilter = {
|
||||
name: string;
|
||||
/**
|
||||
* File extensions to require
|
||||
*/
|
||||
extensions: Array<string>;
|
||||
};
|
||||
|
||||
export type PromptTextRequest = {
|
||||
id: string;
|
||||
title: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string;
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string;
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
require?: boolean;
|
||||
};
|
||||
|
||||
export type PromptTextResponse = { value: string | null };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest; purpose: RenderPurpose };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest };
|
||||
|
||||
export type RenderPurpose = 'send' | 'preview';
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: HttpRequest };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse };
|
||||
|
||||
export type ShowToastRequest = { message: string; color?: Color; icon?: Icon };
|
||||
|
||||
export type TemplateFunction = {
|
||||
name: string;
|
||||
description?: string;
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>;
|
||||
args: Array<TemplateFunctionArg>;
|
||||
};
|
||||
|
||||
export type TemplateFunctionArg =
|
||||
| ({ type: 'text' } & TemplateFunctionTextArg)
|
||||
| ({
|
||||
type: 'select';
|
||||
} & TemplateFunctionSelectArg)
|
||||
| ({ type: 'checkbox' } & TemplateFunctionCheckboxArg)
|
||||
| ({
|
||||
type: 'http_request';
|
||||
} & TemplateFunctionHttpRequestArg)
|
||||
| ({ type: 'file' } & TemplateFunctionFileArg);
|
||||
|
||||
export type TemplateFunctionBaseArg = {
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateFunctionCheckboxArg = {
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateFunctionFileArg = {
|
||||
/**
|
||||
* The title of the file selection window
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Allow selecting multiple files
|
||||
*/
|
||||
multiple?: boolean;
|
||||
directory?: boolean;
|
||||
defaultPath?: string;
|
||||
filters?: Array<OpenFileFilter>;
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateFunctionHttpRequestArg = {
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateFunctionSelectArg = {
|
||||
/**
|
||||
* The options that will be available in the select input
|
||||
*/
|
||||
options: Array<TemplateFunctionSelectOption>;
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateFunctionSelectOption = { label: string; value: string };
|
||||
|
||||
export type TemplateFunctionTextArg = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* The name of the argument. Should be `camelCase` format
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean;
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue; purpose: RenderPurpose };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue };
|
||||
|
||||
export type WindowContext = { type: 'none' } | { type: 'label'; label: string };
|
||||
25
packages/plugin-runtime-types/src/bindings/models.ts
Normal file
25
packages/plugin-runtime-types/src/bindings/models.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, };
|
||||
|
||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, };
|
||||
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, };
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, };
|
||||
|
||||
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, };
|
||||
|
||||
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
|
||||
|
||||
export type HttpResponseHeader = { name: string, value: string, };
|
||||
|
||||
export type HttpResponseState = "initialized" | "connected" | "closed";
|
||||
|
||||
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type JsonValue = number | string | Array<JsonValue> | { [key in string]?: JsonValue };
|
||||
1
packages/plugin-runtime-types/src/helpers.ts
Normal file
1
packages/plugin-runtime-types/src/helpers.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
5
packages/plugin-runtime-types/src/index.ts
Normal file
5
packages/plugin-runtime-types/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type * from './plugins';
|
||||
export type * from './themes';
|
||||
|
||||
export * from './bindings/models';
|
||||
export * from './bindings/events';
|
||||
38
packages/plugin-runtime-types/src/plugins/Context.ts
Normal file
38
packages/plugin-runtime-types/src/plugins/Context.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type {
|
||||
FindHttpResponsesRequest,
|
||||
FindHttpResponsesResponse,
|
||||
GetHttpRequestByIdRequest,
|
||||
GetHttpRequestByIdResponse,
|
||||
PromptTextRequest,
|
||||
PromptTextResponse,
|
||||
RenderHttpRequestRequest,
|
||||
RenderHttpRequestResponse,
|
||||
SendHttpRequestRequest,
|
||||
SendHttpRequestResponse,
|
||||
ShowToastRequest,
|
||||
TemplateRenderRequest,
|
||||
TemplateRenderResponse,
|
||||
} from '..';
|
||||
|
||||
export type Context = {
|
||||
clipboard: {
|
||||
copyText(text: string): void;
|
||||
};
|
||||
toast: {
|
||||
show(args: ShowToastRequest): void;
|
||||
};
|
||||
prompt: {
|
||||
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
|
||||
};
|
||||
httpRequest: {
|
||||
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
|
||||
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
|
||||
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>;
|
||||
};
|
||||
httpResponse: {
|
||||
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
|
||||
};
|
||||
templates: {
|
||||
render(args: TemplateRenderRequest): Promise<TemplateRenderResponse['data']>;
|
||||
};
|
||||
};
|
||||
13
packages/plugin-runtime-types/src/plugins/FilterPlugin.ts
Normal file
13
packages/plugin-runtime-types/src/plugins/FilterPlugin.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type FilterPluginResponse = string[];
|
||||
|
||||
export type FilterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
canFilter(ctx: Context, args: { mimeType: string }): Promise<boolean>;
|
||||
onFilter(
|
||||
ctx: Context,
|
||||
args: { payload: string; mimeType: string },
|
||||
): Promise<FilterPluginResponse>;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { CallHttpRequestActionArgs, HttpRequestAction } from '..';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type HttpRequestActionPlugin = HttpRequestAction & {
|
||||
onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void;
|
||||
};
|
||||
17
packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts
Normal file
17
packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '..';
|
||||
import type { AtLeast } from '../helpers';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type ImportPluginResponse = null | {
|
||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
httpRequests: AtLeast<HttpRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
grpcRequests: AtLeast<GrpcRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
};
|
||||
|
||||
export type ImporterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
onImport(ctx: Context, args: { text: string }): Promise<ImportPluginResponse>;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { CallTemplateFunctionArgs, TemplateFunction } from '..';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
||||
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;
|
||||
};
|
||||
8
packages/plugin-runtime-types/src/plugins/ThemePlugin.ts
Normal file
8
packages/plugin-runtime-types/src/plugins/ThemePlugin.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Theme } from '../themes';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type ThemePlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
getTheme(ctx: Context, fileContents: string): Promise<Theme>;
|
||||
};
|
||||
18
packages/plugin-runtime-types/src/plugins/index.ts
Normal file
18
packages/plugin-runtime-types/src/plugins/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { FilterPlugin } from './FilterPlugin';
|
||||
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||
import type { ImporterPlugin } from './ImporterPlugin';
|
||||
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
||||
import type { ThemePlugin } from './ThemePlugin';
|
||||
|
||||
export type { Context } from './Context';
|
||||
|
||||
/**
|
||||
* The global structure of a Yaak plugin
|
||||
*/
|
||||
export type PluginDefinition = {
|
||||
importer?: ImporterPlugin;
|
||||
theme?: ThemePlugin;
|
||||
filter?: FilterPlugin;
|
||||
httpRequestActions?: HttpRequestActionPlugin[];
|
||||
templateFunctions?: TemplateFunctionPlugin[];
|
||||
};
|
||||
44
packages/plugin-runtime-types/src/themes/index.ts
Normal file
44
packages/plugin-runtime-types/src/themes/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export type Colors = {
|
||||
surface: string;
|
||||
surfaceHighlight?: string;
|
||||
surfaceActive?: string;
|
||||
|
||||
text: string;
|
||||
textSubtle?: string;
|
||||
textSubtlest?: string;
|
||||
|
||||
border?: string;
|
||||
borderSubtle?: string;
|
||||
borderFocus?: string;
|
||||
|
||||
shadow?: string;
|
||||
backdrop?: string;
|
||||
selection?: string;
|
||||
|
||||
primary?: string;
|
||||
secondary?: string;
|
||||
info?: string;
|
||||
success?: string;
|
||||
notice?: string;
|
||||
warning?: string;
|
||||
danger?: string;
|
||||
};
|
||||
|
||||
export type Theme = Colors & {
|
||||
id: string;
|
||||
name: string;
|
||||
components?: Partial<{
|
||||
dialog: Partial<Colors>;
|
||||
menu: Partial<Colors>;
|
||||
toast: Partial<Colors>;
|
||||
sidebar: Partial<Colors>;
|
||||
responsePane: Partial<Colors>;
|
||||
appHeader: Partial<Colors>;
|
||||
button: Partial<Colors>;
|
||||
banner: Partial<Colors>;
|
||||
placeholder: Partial<Colors>;
|
||||
urlBar: Partial<Colors>;
|
||||
editor: Partial<Colors>;
|
||||
input: Partial<Colors>;
|
||||
}>;
|
||||
};
|
||||
15
packages/plugin-runtime-types/tsconfig.json
Normal file
15
packages/plugin-runtime-types/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"target": "es6",
|
||||
"lib": ["es2021"],
|
||||
"declaration": true,
|
||||
"declarationDir": "./lib",
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"src/index.ts"
|
||||
]
|
||||
}
|
||||
3
packages/plugin-runtime/.gitignore
vendored
Normal file
3
packages/plugin-runtime/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
*.blob
|
||||
21
packages/plugin-runtime/package.json
Normal file
21
packages/plugin-runtime/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/plugin-runtime",
|
||||
"scripts": {
|
||||
"bootstrap": "npm run build",
|
||||
"build": "run-p build:*",
|
||||
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=../../src-tauri/vendored/plugin-runtime/index.cjs",
|
||||
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=../../src-tauri/vendored/plugin-runtime/index.worker.cjs",
|
||||
"build:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../../proto ../../proto/plugins/*.proto"
|
||||
},
|
||||
"dependencies": {
|
||||
"intercept-stdout": "^0.1.2",
|
||||
"long": "^5.2.3",
|
||||
"nice-grpc": "^2.1.9",
|
||||
"protobufjs": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/intercept-stdout": "^0.1.3",
|
||||
"grpc-tools": "^1.12.4",
|
||||
"ts-proto": "^2.2.0"
|
||||
}
|
||||
}
|
||||
21
packages/plugin-runtime/src/EventChannel.ts
Normal file
21
packages/plugin-runtime/src/EventChannel.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { InternalEvent } from '@yaakapp/api';
|
||||
import EventEmitter from 'node:events';
|
||||
import type { EventStreamEvent } from './gen/plugins/runtime';
|
||||
|
||||
export class EventChannel {
|
||||
emitter: EventEmitter = new EventEmitter();
|
||||
|
||||
emit(e: InternalEvent) {
|
||||
this.emitter.emit('__plugin_event__', { event: JSON.stringify(e) });
|
||||
}
|
||||
|
||||
async *listen(): AsyncGenerator<EventStreamEvent> {
|
||||
while (true) {
|
||||
yield new Promise<EventStreamEvent>((resolve) => {
|
||||
this.emitter.once('__plugin_event__', (event: EventStreamEvent) => {
|
||||
resolve(event);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
56
packages/plugin-runtime/src/PluginHandle.ts
Normal file
56
packages/plugin-runtime/src/PluginHandle.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { BootRequest, InternalEvent } from '@yaakapp-internal/plugins';
|
||||
import path from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import type { EventChannel } from './EventChannel';
|
||||
import type { PluginWorkerData } from './index.worker';
|
||||
|
||||
export class PluginHandle {
|
||||
#worker: Worker;
|
||||
|
||||
constructor(
|
||||
readonly pluginRefId: string,
|
||||
readonly bootRequest: BootRequest,
|
||||
readonly events: EventChannel,
|
||||
) {
|
||||
this.#worker = this.#createWorker();
|
||||
}
|
||||
|
||||
sendToWorker(event: InternalEvent) {
|
||||
this.#worker.postMessage(event);
|
||||
}
|
||||
|
||||
async terminate() {
|
||||
await this.#worker.terminate();
|
||||
}
|
||||
|
||||
#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,
|
||||
});
|
||||
|
||||
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.bootRequest.dir);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
async #handleError(err: Error) {
|
||||
console.error('Plugin errored', this.bootRequest.dir, err);
|
||||
}
|
||||
|
||||
async #handleExit(code: number) {
|
||||
if (code === 0) {
|
||||
console.log('Plugin exited successfully', this.bootRequest.dir);
|
||||
} else {
|
||||
console.log('Plugin exited with status', code, this.bootRequest.dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
packages/plugin-runtime/src/gen/plugins/runtime.ts
Normal file
126
packages/plugin-runtime/src/gen/plugins/runtime.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.3
|
||||
// protoc v3.19.1
|
||||
// source: plugins/runtime.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
|
||||
import { type CallContext, type CallOptions } from "nice-grpc-common";
|
||||
|
||||
export const protobufPackage = "yaak.plugins.runtime";
|
||||
|
||||
export interface EventStreamEvent {
|
||||
event: string;
|
||||
}
|
||||
|
||||
function createBaseEventStreamEvent(): EventStreamEvent {
|
||||
return { event: "" };
|
||||
}
|
||||
|
||||
export const EventStreamEvent: MessageFns<EventStreamEvent> = {
|
||||
encode(message: EventStreamEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.event !== "") {
|
||||
writer.uint32(10).string(message.event);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): EventStreamEvent {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseEventStreamEvent();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.event = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): EventStreamEvent {
|
||||
return { event: isSet(object.event) ? globalThis.String(object.event) : "" };
|
||||
},
|
||||
|
||||
toJSON(message: EventStreamEvent): unknown {
|
||||
const obj: any = {};
|
||||
if (message.event !== "") {
|
||||
obj.event = message.event;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<EventStreamEvent>): EventStreamEvent {
|
||||
return EventStreamEvent.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<EventStreamEvent>): EventStreamEvent {
|
||||
const message = createBaseEventStreamEvent();
|
||||
message.event = object.event ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export type PluginRuntimeDefinition = typeof PluginRuntimeDefinition;
|
||||
export const PluginRuntimeDefinition = {
|
||||
name: "PluginRuntime",
|
||||
fullName: "yaak.plugins.runtime.PluginRuntime",
|
||||
methods: {
|
||||
eventStream: {
|
||||
name: "EventStream",
|
||||
requestType: EventStreamEvent,
|
||||
requestStream: true,
|
||||
responseType: EventStreamEvent,
|
||||
responseStream: true,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface PluginRuntimeServiceImplementation<CallContextExt = {}> {
|
||||
eventStream(
|
||||
request: AsyncIterable<EventStreamEvent>,
|
||||
context: CallContext & CallContextExt,
|
||||
): ServerStreamingMethodResult<DeepPartial<EventStreamEvent>>;
|
||||
}
|
||||
|
||||
export interface PluginRuntimeClient<CallOptionsExt = {}> {
|
||||
eventStream(
|
||||
request: AsyncIterable<DeepPartial<EventStreamEvent>>,
|
||||
options?: CallOptions & CallOptionsExt,
|
||||
): AsyncIterable<EventStreamEvent>;
|
||||
}
|
||||
|
||||
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
|
||||
|
||||
export type DeepPartial<T> = T extends Builtin ? T
|
||||
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>;
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export type ServerStreamingMethodResult<Response> = { [Symbol.asyncIterator](): AsyncIterator<Response, void> };
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
fromJSON(object: any): T;
|
||||
toJSON(message: T): unknown;
|
||||
create(base?: DeepPartial<T>): T;
|
||||
fromPartial(object: DeepPartial<T>): T;
|
||||
}
|
||||
52
packages/plugin-runtime/src/index.ts
Normal file
52
packages/plugin-runtime/src/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { InternalEvent } from '@yaakapp/api';
|
||||
import { createChannel, createClient, Status } from 'nice-grpc';
|
||||
import { EventChannel } from './EventChannel';
|
||||
import type { PluginRuntimeClient} from './gen/plugins/runtime';
|
||||
import { PluginRuntimeDefinition } from './gen/plugins/runtime';
|
||||
import { PluginHandle } from './PluginHandle';
|
||||
|
||||
const port = process.env.PORT || '50051';
|
||||
|
||||
const channel = createChannel(`localhost:${port}`, undefined, {
|
||||
'grpc.max_receive_message_length': Number.MAX_SAFE_INTEGER,
|
||||
'grpc.max_send_message_length': Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
const client: PluginRuntimeClient = createClient(PluginRuntimeDefinition, channel);
|
||||
|
||||
const events = new EventChannel();
|
||||
const plugins: Record<string, PluginHandle> = {};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
for await (const e of client.eventStream(events.listen())) {
|
||||
const pluginEvent: InternalEvent = JSON.parse(e.event);
|
||||
// Handle special event to bootstrap plugin
|
||||
if (pluginEvent.payload.type === 'boot_request') {
|
||||
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, events);
|
||||
plugins[pluginEvent.pluginRefId] = plugin;
|
||||
}
|
||||
|
||||
// Once booted, forward all events to the plugin worker
|
||||
const plugin = plugins[pluginEvent.pluginRefId];
|
||||
if (!plugin) {
|
||||
console.warn('Failed to get plugin for event by', pluginEvent.pluginRefId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pluginEvent.payload.type === 'terminate_request') {
|
||||
await plugin.terminate();
|
||||
console.log('Terminated plugin worker', pluginEvent.pluginRefId);
|
||||
delete plugins[pluginEvent.pluginRefId];
|
||||
}
|
||||
|
||||
plugin.sendToWorker(pluginEvent);
|
||||
}
|
||||
console.log('Stream ended');
|
||||
} catch (err: any) {
|
||||
if (err.code === Status.CANCELLED) {
|
||||
console.log('Stream was cancelled by server');
|
||||
} else {
|
||||
console.log('Client stream errored', err);
|
||||
}
|
||||
}
|
||||
})();
|
||||
398
packages/plugin-runtime/src/index.worker.ts
Normal file
398
packages/plugin-runtime/src/index.worker.ts
Normal file
@@ -0,0 +1,398 @@
|
||||
import type {
|
||||
BootRequest,
|
||||
FindHttpResponsesResponse,
|
||||
GetHttpRequestByIdResponse,
|
||||
HttpRequestAction,
|
||||
ImportResponse,
|
||||
InternalEvent,
|
||||
InternalEventPayload,
|
||||
PromptTextResponse,
|
||||
RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
TemplateFunction,
|
||||
TemplateRenderResponse,
|
||||
WindowContext,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import type { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestActionPlugin';
|
||||
import type { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
|
||||
import interceptStdout from 'intercept-stdout';
|
||||
import * as console from 'node:console';
|
||||
import type { Stats} from 'node:fs';
|
||||
import { readFileSync, 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 {
|
||||
bootRequest: { dir: pluginDir, watch: enableWatch },
|
||||
pluginRefId,
|
||||
}: PluginWorkerData = workerData;
|
||||
const pathPkg = path.join(pluginDir, 'package.json');
|
||||
|
||||
const pathMod = path.posix.join(pluginDir, 'build', 'index.js');
|
||||
|
||||
async function importModule() {
|
||||
const id = require.resolve(pathMod);
|
||||
delete require.cache[id];
|
||||
return require(id);
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(readFileSync(pathPkg, 'utf8'));
|
||||
|
||||
prefixStdout(`[plugin][${pkg.name}] %s`);
|
||||
|
||||
let mod = await importModule();
|
||||
|
||||
const capabilities: string[] = [];
|
||||
if (typeof mod.pluginHookExport === 'function') capabilities.push('export');
|
||||
if (typeof mod.pluginHookImport === 'function') capabilities.push('import');
|
||||
if (typeof mod.pluginHookResponseFilter === 'function') capabilities.push('filter');
|
||||
|
||||
console.log('Plugin initialized', pkg.name, { capabilities, enableWatch });
|
||||
|
||||
function buildEventToSend(
|
||||
windowContext: WindowContext,
|
||||
payload: InternalEventPayload,
|
||||
replyId: string | null = null,
|
||||
): InternalEvent {
|
||||
return { pluginRefId, id: genId(), replyId, payload, windowContext };
|
||||
}
|
||||
|
||||
function sendEmpty(windowContext: WindowContext, replyId: string | null = null): string {
|
||||
return sendPayload(windowContext, { type: 'empty_response' }, replyId);
|
||||
}
|
||||
|
||||
function sendPayload(
|
||||
windowContext: WindowContext,
|
||||
payload: InternalEventPayload,
|
||||
replyId: string | null,
|
||||
): string {
|
||||
const event = buildEventToSend(windowContext, payload, replyId);
|
||||
sendEvent(event);
|
||||
return event.id;
|
||||
}
|
||||
|
||||
function sendEvent(event: InternalEvent) {
|
||||
if (event.payload.type !== 'empty_response') {
|
||||
console.log('Sending event to app', event.id, event.payload.type);
|
||||
}
|
||||
parentPort!.postMessage(event);
|
||||
}
|
||||
|
||||
async function sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||
windowContext: WindowContext,
|
||||
payload: InternalEventPayload,
|
||||
): Promise<T> {
|
||||
// 1. Build event to send
|
||||
const eventToSend = buildEventToSend(windowContext, payload, null);
|
||||
|
||||
// 2. Spawn listener in background
|
||||
const promise = new Promise<InternalEventPayload>(async (resolve) => {
|
||||
const cb = (event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
parentPort!.off('message', cb); // Unlisten, now that we're done
|
||||
resolve(event.payload); // Not type-safe but oh well
|
||||
}
|
||||
};
|
||||
parentPort!.on('message', cb);
|
||||
});
|
||||
|
||||
// 3. Send the event after we start listening (to prevent race)
|
||||
sendEvent(eventToSend);
|
||||
|
||||
// 4. Return the listener promise
|
||||
return promise as unknown as Promise<T>;
|
||||
}
|
||||
|
||||
async function reloadModule() {
|
||||
mod = await importModule();
|
||||
}
|
||||
|
||||
// Reload plugin if JS or package.json changes
|
||||
const windowContextNone: WindowContext = { type: 'none' };
|
||||
const cb = async () => {
|
||||
await reloadModule();
|
||||
return sendPayload(windowContextNone, { type: 'reload_response' }, null);
|
||||
};
|
||||
|
||||
if (enableWatch) {
|
||||
watchFile(pathMod, cb);
|
||||
watchFile(pathPkg, cb);
|
||||
}
|
||||
|
||||
const newCtx = (event: InternalEvent): Context => ({
|
||||
clipboard: {
|
||||
async copyText(text) {
|
||||
await sendAndWaitForReply(event.windowContext, { type: 'copy_text_request', text });
|
||||
},
|
||||
},
|
||||
toast: {
|
||||
async show(args) {
|
||||
await sendAndWaitForReply(event.windowContext, { type: 'show_toast_request', ...args });
|
||||
},
|
||||
},
|
||||
prompt: {
|
||||
async text(args) {
|
||||
const reply: PromptTextResponse = await sendAndWaitForReply(event.windowContext, {
|
||||
type: 'prompt_text_request',
|
||||
...args,
|
||||
});
|
||||
return reply.value;
|
||||
},
|
||||
},
|
||||
httpResponse: {
|
||||
async find(args) {
|
||||
const payload = { type: 'find_http_responses_request', ...args } as const;
|
||||
const { httpResponses } = await sendAndWaitForReply<FindHttpResponsesResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpResponses;
|
||||
},
|
||||
},
|
||||
httpRequest: {
|
||||
async getById(args) {
|
||||
const payload = { type: 'get_http_request_by_id_request', ...args } as const;
|
||||
const { httpRequest } = await sendAndWaitForReply<GetHttpRequestByIdResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpRequest;
|
||||
},
|
||||
async send(args) {
|
||||
const payload = { type: 'send_http_request_request', ...args } as const;
|
||||
const { httpResponse } = await sendAndWaitForReply<SendHttpRequestResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpResponse;
|
||||
},
|
||||
async render(args) {
|
||||
const payload = { type: 'render_http_request_request', ...args } as const;
|
||||
const { httpRequest } = await sendAndWaitForReply<RenderHttpRequestResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpRequest;
|
||||
},
|
||||
},
|
||||
templates: {
|
||||
/**
|
||||
* Invoke Yaak's template engine to render a value. If the value is a nested type
|
||||
* (eg. object), it will be recursively rendered.
|
||||
* */
|
||||
async render(args) {
|
||||
const payload = { type: 'template_render_request', ...args } as const;
|
||||
const result = await sendAndWaitForReply<TemplateRenderResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return result.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Message comes into the plugin to be processed
|
||||
parentPort!.on('message', async (event: InternalEvent) => {
|
||||
const { windowContext, payload, id: replyId } = event;
|
||||
const ctx = newCtx(event);
|
||||
try {
|
||||
if (payload.type === 'boot_request') {
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'boot_response',
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
capabilities,
|
||||
};
|
||||
sendPayload(windowContext, payload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'terminate_request') {
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'terminate_response',
|
||||
};
|
||||
sendPayload(windowContext, payload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'import_request' && typeof mod.pluginHookImport === 'function') {
|
||||
const reply: ImportResponse | null = await mod.pluginHookImport(ctx, payload.content);
|
||||
if (reply != null) {
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'import_response',
|
||||
resources: reply?.resources,
|
||||
};
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
} else {
|
||||
// Continue, to send back an empty reply
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'export_http_request_request' &&
|
||||
typeof mod.pluginHookExport === 'function'
|
||||
) {
|
||||
const reply: string = await mod.pluginHookExport(ctx, payload.httpRequest);
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'export_http_request_response',
|
||||
content: reply,
|
||||
};
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'filter_request' && typeof mod.pluginHookResponseFilter === 'function') {
|
||||
const reply: string = await mod.pluginHookResponseFilter(ctx, {
|
||||
filter: payload.filter,
|
||||
body: payload.content,
|
||||
});
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'filter_response',
|
||||
content: reply,
|
||||
};
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_http_request_actions_request' &&
|
||||
Array.isArray(mod.plugin?.httpRequestActions)
|
||||
) {
|
||||
const reply: HttpRequestAction[] = mod.plugin.httpRequestActions.map(
|
||||
(a: HttpRequestActionPlugin) => ({
|
||||
...a,
|
||||
// Add everything except onSelect
|
||||
onSelect: undefined,
|
||||
}),
|
||||
);
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_http_request_actions_response',
|
||||
pluginRefId,
|
||||
actions: reply,
|
||||
};
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_template_functions_request' &&
|
||||
Array.isArray(mod.plugin?.templateFunctions)
|
||||
) {
|
||||
const reply: TemplateFunction[] = mod.plugin.templateFunctions.map(
|
||||
(a: TemplateFunctionPlugin) => ({
|
||||
...a,
|
||||
// Add everything except render
|
||||
onRender: undefined,
|
||||
}),
|
||||
);
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_template_functions_response',
|
||||
pluginRefId,
|
||||
functions: reply,
|
||||
};
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_request_action_request' &&
|
||||
Array.isArray(mod.plugin?.httpRequestActions)
|
||||
) {
|
||||
const action = mod.plugin.httpRequestActions.find(
|
||||
(a: HttpRequestActionPlugin) => a.key === payload.key,
|
||||
);
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
sendEmpty(windowContext, replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_template_function_request' &&
|
||||
Array.isArray(mod.plugin?.templateFunctions)
|
||||
) {
|
||||
const action = mod.plugin.templateFunctions.find(
|
||||
(a: TemplateFunctionPlugin) => a.name === payload.name,
|
||||
);
|
||||
if (typeof action?.onRender === 'function') {
|
||||
const result = await action.onRender(ctx, payload.args);
|
||||
sendPayload(
|
||||
windowContext,
|
||||
{
|
||||
type: 'call_template_function_response',
|
||||
value: result ?? null,
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.type === 'reload_request') {
|
||||
await reloadModule();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Plugin call threw exception', payload.type, err);
|
||||
// TODO: Return errors to server
|
||||
}
|
||||
|
||||
// No matches, so send back an empty response so the caller doesn't block forever
|
||||
sendEmpty(windowContext, replyId);
|
||||
});
|
||||
}
|
||||
|
||||
initialize().catch((err) => {
|
||||
console.log('failed to boot plugin', err);
|
||||
});
|
||||
|
||||
function genId(len = 5): string {
|
||||
const alphabet = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let id = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
id += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function prefixStdout(s: string) {
|
||||
if (!s.includes('%s')) {
|
||||
throw new Error('Console prefix must contain a "%s" replacer');
|
||||
}
|
||||
interceptStdout((text) => {
|
||||
const lines = text.split(/\n/);
|
||||
let newText = '';
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i] == '') continue;
|
||||
newText += util.format(s, lines[i]) + '\n';
|
||||
}
|
||||
return newText.trimEnd();
|
||||
});
|
||||
}
|
||||
|
||||
const watchedFiles: Record<string, Stats> = {};
|
||||
|
||||
/**
|
||||
* Watch a file and trigger callback on change.
|
||||
*
|
||||
* We also track the stat for each file because fs.watch will
|
||||
* trigger a "change" event when the access date changes
|
||||
*/
|
||||
function watchFile(filepath: string, cb: (filepath: string) => void) {
|
||||
watch(filepath, (_event, _name) => {
|
||||
const stat = statSync(filepath);
|
||||
if (stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
|
||||
cb(filepath);
|
||||
}
|
||||
watchedFiles[filepath] = stat;
|
||||
});
|
||||
}
|
||||
24
packages/plugin-runtime/tsconfig.json
Normal file
24
packages/plugin-runtime/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2021",
|
||||
"lib": ["es2021"],
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node16",
|
||||
"sourceMap": true,
|
||||
"outDir": "build",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
"src/types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user