Monorepo with yarn workspaces requires yarn 3.2

This commit is contained in:
Zef Hemel
2022-04-21 13:57:45 +02:00
parent 32f3501773
commit 1f842ec1d6
167 changed files with 10424 additions and 8263 deletions
-49
View File
@@ -1,49 +0,0 @@
import { createSandbox } from "../environments/node_sandbox";
import { expect, test } from "@jest/globals";
import { Manifest } from "../types";
import express from "express";
import request from "supertest";
import { EndpointHook, EndpointHookT } from "./endpoint";
import { System } from "../system";
test("Run a plugos endpoint server", async () => {
let system = new System<EndpointHookT>("server");
let plug = await system.load(
"test",
{
functions: {
testhandler: {
http: {
path: "/",
},
code: `(() => {
return {
default: (req) => {
console.log("Req", req);
return {status: 200, body: [1, 2, 3], headers: {"Content-type": "application/json"}};
}
};
})()`,
},
},
} as Manifest<EndpointHookT>,
createSandbox
);
const app = express();
const port = 3123;
system.addHook(new EndpointHook(app, "/_"));
let server = app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
let resp = await request(app)
.get("/_/test/?name=Pete")
.expect((resp) => {
expect(resp.status).toBe(200);
expect(resp.header["content-type"]).toContain("application/json");
expect(resp.text).toBe(JSON.stringify([1, 2, 3]));
});
server.close();
});
-134
View File
@@ -1,134 +0,0 @@
import { Hook, Manifest } from "../types";
import { Express, NextFunction, Request, Response } from "express";
import { System } from "../system";
export type EndpointRequest = {
method: string;
path: string;
query: { [key: string]: string };
headers: { [key: string]: string };
body: any;
};
export type EndpointResponse = {
status: number;
headers?: { [key: string]: string };
body: any;
};
export type EndpointHookT = {
http?: EndPointDef | EndPointDef[];
};
export type EndPointDef = {
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "ANY";
path: string;
};
export class EndpointHook implements Hook<EndpointHookT> {
private app: Express;
readonly prefix: string;
constructor(app: Express, prefix: string) {
this.app = app;
this.prefix = prefix;
}
apply(system: System<EndpointHookT>): void {
this.app.use((req: Request, res: Response, next: NextFunction) => {
if (!req.path.startsWith(this.prefix)) {
return next();
}
console.log("Endpoint request", req.path);
Promise.resolve()
.then(async () => {
// Iterate over all loaded plugins
for (const [plugName, plug] of system.loadedPlugs.entries()) {
const manifest = plug.manifest;
if (!manifest) {
continue;
}
const functions = manifest.functions;
console.log("Checking plug", plugName);
let prefix = `${this.prefix}/${plugName}`;
if (!req.path.startsWith(prefix)) {
continue;
}
for (const [name, functionDef] of Object.entries(functions)) {
if (!functionDef.http) {
continue;
}
let endpoints = Array.isArray(functionDef.http)
? functionDef.http
: [functionDef.http];
console.log(endpoints);
for (const { path, method } of endpoints) {
let prefixedPath = `${prefix}${path}`;
if (
prefixedPath === req.path &&
((method || "GET") === req.method || method === "ANY")
) {
try {
const response: EndpointResponse = await plug.invoke(name, [
{
path: req.path,
method: req.method,
body: req.body,
query: req.query,
headers: req.headers,
} as EndpointRequest,
]);
let resp = res.status(response.status);
if (response.headers) {
for (const [key, value] of Object.entries(
response.headers
)) {
resp = resp.header(key, value);
}
}
resp.send(response.body);
return;
} catch (e: any) {
console.error("Error executing function", e);
res.status(500).send(e.message);
return;
}
}
}
}
}
next();
})
.catch((e) => {
console.error(e);
next(e);
});
});
}
validateManifest(manifest: Manifest<EndpointHookT>): string[] {
let errors = [];
for (const [name, functionDef] of Object.entries(manifest.functions)) {
if (!functionDef.http) {
continue;
}
let endpoints = Array.isArray(functionDef.http)
? functionDef.http
: [functionDef.http];
for (let { path, method } of endpoints) {
if (!path) {
errors.push("Path not defined for endpoint");
}
if (
method &&
["GET", "POST", "PUT", "DELETE", "ANY"].indexOf(method) === -1
) {
errors.push(
`Invalid method ${method} for end point with with ${path}`
);
}
}
}
return errors;
}
}
-58
View File
@@ -1,58 +0,0 @@
import { Hook, Manifest } from "../types";
import { System } from "../system";
import { safeRun } from "../util";
// System events:
// - plug:load (plugName: string)
export type EventHookT = {
events?: string[];
};
export class EventHook implements Hook<EventHookT> {
private system?: System<EventHookT>;
async dispatchEvent(eventName: string, data?: any): Promise<any[]> {
if (!this.system) {
throw new Error("Event hook is not initialized");
}
let responses: any[] = [];
for (const plug of this.system.loadedPlugs.values()) {
for (const [name, functionDef] of Object.entries(
plug.manifest!.functions
)) {
if (functionDef.events && functionDef.events.includes(eventName)) {
// Only dispatch functions that can run in this environment
if (plug.canInvoke(name)) {
let result = await plug.invoke(name, [data]);
if (result !== undefined) {
responses.push(result);
}
}
}
}
}
return responses;
}
apply(system: System<EventHookT>): void {
this.system = system;
this.system.on({
plugLoaded: (name) => {
safeRun(async () => {
await this.dispatchEvent("plug:load", name);
});
},
});
}
validateManifest(manifest: Manifest<EventHookT>): string[] {
let errors = [];
for (const [name, functionDef] of Object.entries(manifest.functions)) {
if (functionDef.events && !Array.isArray(functionDef.events)) {
errors.push("'events' key must be an array of strings");
}
}
return errors;
}
}
-76
View File
@@ -1,76 +0,0 @@
import { Hook, Manifest } from "../types";
import cron, { ScheduledTask } from "node-cron";
import { safeRun } from "../util";
import { System } from "../system";
export type CronHookT = {
cron?: string | string[];
};
export class NodeCronHook implements Hook<CronHookT> {
apply(system: System<CronHookT>): void {
let tasks: ScheduledTask[] = [];
system.on({
plugLoaded: (name, plug) => {
reloadCrons();
},
plugUnloaded(name, plug) {
reloadCrons();
},
});
reloadCrons();
function reloadCrons() {
tasks.forEach((task) => task.stop());
tasks = [];
for (let plug of system.loadedPlugs.values()) {
if (!plug.manifest) {
continue;
}
for (const [name, functionDef] of Object.entries(
plug.manifest.functions
)) {
if (!functionDef.cron) {
continue;
}
const crons = Array.isArray(functionDef.cron)
? functionDef.cron
: [functionDef.cron];
for (let cronDef of crons) {
tasks.push(
cron.schedule(cronDef, () => {
console.log("Now acting on cron", cronDef);
safeRun(async () => {
try {
await plug.invoke(name, [cronDef]);
} catch (e: any) {
console.error("Execution of cron function failed", e);
}
});
})
);
}
}
}
}
}
validateManifest(manifest: Manifest<CronHookT>): string[] {
let errors = [];
for (const [name, functionDef] of Object.entries(manifest.functions)) {
if (!functionDef.cron) {
continue;
}
const crons = Array.isArray(functionDef.cron)
? functionDef.cron
: [functionDef.cron];
for (let cronDef of crons) {
if (!cron.validate(cronDef)) {
errors.push(`Invalid cron expression ${cronDef}`);
}
}
}
return errors;
}
}