Cleanup and progress

pull/3/head
Zef Hemel 2022-03-21 15:21:34 +01:00
parent 7e591c6f44
commit a916088215
31 changed files with 707 additions and 143 deletions

23
common/manifest.ts Normal file
View File

@ -0,0 +1,23 @@
import * as plugbox from "../plugbox/types";
import { EndpointHook } from "../plugbox/types";
export type CommandDef = {
// Function name to invoke
invoke: string;
// Bind to keyboard shortcut
key?: string;
mac?: string;
// If to show in slash invoked menu and if so, with what label
// should match slashCommandRegexp
slashCommand?: string;
};
export type SilverBulletHooks = {
commands?: {
[key: string]: CommandDef;
};
} & plugbox.EndpointHook;
export type Manifest = plugbox.Manifest<SilverBulletHooks>;

View File

@ -6,14 +6,14 @@
<style>
@import "../../webapp/src/styles/main.scss";
</style>
<script type="module" src="boot.ts"></script>
<script type="module" src="server.ts"></script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
</head>
<body>
<div id="root"></div>
<script type="module">
import "./boot";
import "./server";
</script>
</body>
</html>

View File

@ -12,8 +12,8 @@
"watch": "parcel watch",
"build": "parcel build",
"clean": "rm -rf dist",
"plugs": "node dist/bundler/plugbox-bundle.js plugs/core/core.plug.json webapp/generated/core.plug.json",
"server": "nodemon dist/server/server.js pages",
"plugs": "node dist/bundler/plugbox-bundle.js plugs/core/core.plug.json plugs/dist/core.plug.json",
"server": "nodemon -w dist/server dist/server/server.js pages",
"test": "jest"
},
"targets": {
@ -40,6 +40,7 @@
"test": {
"source": [
"plugbox/runtime.test.ts",
"plugbox/endpoint.test.ts",
"server/api.test.ts"
],
"outputFormat": "commonjs",
@ -75,12 +76,14 @@
"jest": "^27.5.1",
"knex": "^1.0.4",
"lodash": "^4.17.21",
"node-cron": "^3.0.0",
"nodemon": "^2.0.15",
"parcel": "^2.3.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"socket.io": "^4.4.1",
"socket.io-client": "^4.4.1",
"supertest": "^6.2.2",
"ts-jest": "^27.1.3",
"vm2": "^3.9.9",
"yargs": "^17.3.1"
@ -95,8 +98,10 @@
"@types/events": "^3.0.0",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
"@types/node-cron": "^3.0.1",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"@types/supertest": "^2.0.11",
"@vscode/sqlite3": "^5.0.7",
"assert": "^2.0.0",
"crypto-browserify": "^3.12.0",

View File

@ -6,9 +6,9 @@ import path from "path";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import {Manifest} from "../types";
import { Manifest } from "../types";
async function compile(filePath : string, functionName : string, debug: boolean) {
async function compile(filePath: string, functionName: string, debug: boolean) {
let outFile = "out.js";
let inFile = filePath;
@ -47,7 +47,9 @@ export default ${functionName};`
async function bundle(manifestPath: string, sourceMaps: boolean) {
const rootPath = path.dirname(manifestPath);
const manifest = JSON.parse((await readFile(manifestPath)).toString()) as Manifest<any>;
const manifest = JSON.parse(
(await readFile(manifestPath)).toString()
) as Manifest<any>;
for (let [name, def] of Object.entries(manifest.functions)) {
let jsFunctionName = "default",
@ -69,7 +71,10 @@ async function run() {
.parse();
let generatedManifest = await bundle(args._[0] as string, !!args.debug);
await writeFile(args._[1] as string, JSON.stringify(generatedManifest, null, 2));
await writeFile(
args._[1] as string,
JSON.stringify(generatedManifest, null, 2)
);
}
run().catch((e) => {

11
plugbox/cron.ts Normal file
View File

@ -0,0 +1,11 @@
import { System } from "./runtime";
import { CronHook } from "./types";
import cron from "node-cron";
export function cronSystem(system: System<CronHook>) {
let task = cron.schedule("* * * * *", () => {
});
// @ts-ignore
task.destroy();
}

47
plugbox/endpoint.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { createSandbox } from "./node_sandbox";
import { System } from "./runtime";
import { test, expect } from "@jest/globals";
import { EndPointDef, EndpointHook, Manifest } from "./types";
import express from "express";
import request from "supertest";
import { exposeSystem } from "./endpoints";
test("Run a plugbox endpoint server", async () => {
let system = new System<EndpointHook>();
let plug = await system.load(
"test",
{
functions: {
testhandler: {
code: `(() => {
return {
default: (req) => {
console.log("Req", req);
return {status: 200, body: [1, 2, 3], headers: {"Content-type": "application/json"}};
}
};
})()`,
},
},
hooks: {
endpoints: [{ method: "GET", path: "/", handler: "testhandler" }],
},
} as Manifest<EndpointHook>,
createSandbox(system)
);
const app = express();
const port = 3123;
app.use(exposeSystem(system));
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();
});

85
plugbox/endpoints.ts Normal file
View File

@ -0,0 +1,85 @@
import { System } from "./runtime";
import { EndpointHook } from "./types";
import express from "express";
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;
};
const endPointPrefix = "/_";
export function exposeSystem(system: System<EndpointHook>) {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
if (!req.path.startsWith(endPointPrefix)) {
return next();
}
Promise.resolve()
.then(async () => {
for (const [plugName, plug] of system.loadedPlugs.entries()) {
const manifest = plug.manifest;
if (!manifest) {
continue;
}
const endpoints = manifest.hooks?.endpoints;
if (endpoints) {
let prefix = `${endPointPrefix}/${plugName}`;
if (!req.path.startsWith(prefix)) {
continue;
}
for (const { path, method, handler } of endpoints) {
let prefixedPath = `${prefix}${path}`;
if (prefixedPath === req.path && method === req.method) {
try {
const response: EndpointResponse = await plug.invoke(
handler,
[
{
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);
});
};
}

View File

@ -8,6 +8,7 @@ import sandboxHtml from "bundle-text:./iframe_sandbox.html";
class IFrameWrapper implements WorkerLike {
private iframe: HTMLIFrameElement;
onMessage?: (message: any) => Promise<void>;
ready: Promise<void>;
constructor() {
const iframe = document.createElement("iframe", {});
@ -27,6 +28,12 @@ class IFrameWrapper implements WorkerLike {
});
});
document.body.appendChild(iframe);
this.ready = new Promise((resolve) => {
iframe.onload = () => {
resolve();
iframe.onload = null;
};
});
}
postMessage(message: any): void {

View File

@ -6,14 +6,12 @@ import * as fs from "fs";
import { safeRun } from "./util";
// @ts-ignore
import workerCode from "bundle-text:./node_worker.ts"
// ParcelJS will simply inline this into the bundle.
// const workerCode = fs.readFileSync(__dirname + "/node_worker.ts", "utf-8");
import workerCode from "bundle-text:./node_worker.ts";
class NodeWorkerWrapper implements WorkerLike {
onMessage?: (message: any) => Promise<void>;
private worker: Worker;
ready: Promise<void>;
constructor(worker: Worker) {
this.worker = worker;
@ -22,6 +20,9 @@ class NodeWorkerWrapper implements WorkerLike {
await this.onMessage!(message);
});
});
this.ready = new Promise((resolve) => {
worker.once("online", resolve);
});
}
postMessage(message: any): void {
@ -34,6 +35,9 @@ class NodeWorkerWrapper implements WorkerLike {
}
export function createSandbox(system: System<any>) {
let worker = new Worker(workerCode, {
eval: true,
});
return new Sandbox(
system,
new NodeWorkerWrapper(

View File

@ -3,18 +3,18 @@ const { parentPort } = require("worker_threads");
let loadedFunctions = new Map<string, Function>();
let pendingRequests = new Map<
number,
{
resolve: (result: unknown) => void;
reject: (e: any) => void;
}
>();
number,
{
resolve: (result: unknown) => void;
reject: (e: any) => void;
}
>();
let vm = new VM({
sandbox: {
console: console,
self: {
syscall: (reqId : number, name : string, args: any[]) => {
syscall: (reqId: number, name: string, args: any[]) => {
return new Promise((resolve, reject) => {
pendingRequests.set(reqId, { resolve, reject });
parentPort.postMessage({
@ -30,17 +30,17 @@ let vm = new VM({
},
});
function wrapScript(code : string) {
function wrapScript(code: string) {
return `(${code})["default"]`;
}
function safeRun(fn : any) {
fn().catch((e : any) => {
function safeRun(fn: any) {
fn().catch((e: any) => {
console.error(e);
});
}
parentPort.on("message", (data : any) => {
parentPort.on("message", (data: any) => {
safeRun(async () => {
switch (data.type) {
case "load":
@ -62,9 +62,10 @@ parentPort.on("message", (data : any) => {
parentPort.postMessage({
type: "result",
id: data.id,
result: result,
// TOOD: Figure out if this is necessary, because it's expensive
result: result && JSON.parse(JSON.stringify(result)),
});
} catch (e : any) {
} catch (e: any) {
// console.log("ERROR", e);
parentPort.postMessage({
type: "result",

71
plugbox/plug_loader.ts Normal file
View File

@ -0,0 +1,71 @@
import fs, { stat, watch } from "fs/promises";
import path from "path";
import { createSandbox } from "./node_sandbox";
import { System } from "./runtime";
import { safeRun } from "../server/util";
function extractPlugName(localPath: string): string {
const baseName = path.basename(localPath);
return baseName.substring(0, baseName.length - ".plug.json".length);
}
export class DiskPlugLoader<HookT> {
private system: System<HookT>;
private plugPath: string;
constructor(system: System<HookT>, plugPath: string) {
this.system = system;
this.plugPath = plugPath;
}
watcher() {
safeRun(async () => {
for await (const { filename, eventType } of watch(this.plugPath, {
recursive: true,
})) {
if (!filename.endsWith(".plug.json")) {
return;
}
try {
let localPath = path.join(this.plugPath, filename);
const plugName = extractPlugName(localPath);
try {
await fs.stat(localPath);
} catch (e) {
// Likely removed
await this.system.unload(plugName);
this.system.emit("plugRemoved", plugName);
}
const plugDef = await this.loadPlugFromFile(localPath);
this.system.emit("plugUpdated", plugName, plugDef);
} catch {
// ignore, error handled by loadPlug
}
}
});
}
private async loadPlugFromFile(localPath: string) {
const plug = await fs.readFile(localPath, "utf8");
const plugName = extractPlugName(localPath);
console.log("Now loading plug", plugName);
try {
const plugDef = JSON.parse(plug);
await this.system.load(plugName, plugDef, createSandbox(this.system));
return plugDef;
} catch (e) {
console.error("Could not parse plugin file", e);
throw e;
}
}
async loadPlugs() {
for (let filename of await fs.readdir(this.plugPath)) {
if (filename.endsWith(".plug.json")) {
let localPath = path.join(this.plugPath, filename);
await this.loadPlugFromFile(localPath);
}
}
}
}

View File

@ -75,5 +75,5 @@ test("Run a Node sandbox", async () => {
} catch (e: any) {
expect(e.message).toBe("#fail");
}
await system.stop();
await system.unloadAll();
});

View File

@ -4,6 +4,7 @@ import {
WorkerLike,
WorkerMessage,
} from "./types";
import { EventEmitter } from "../common/event";
interface SysCallMapping {
[key: string]: (...args: any) => Promise<any> | any;
@ -31,6 +32,7 @@ export class Sandbox {
}
async load(name: string, code: string): Promise<void> {
await this.worker.ready;
this.worker.postMessage({
type: "load",
name: name,
@ -119,18 +121,20 @@ export class Plug<HookT> {
if (!funDef) {
throw new Error(`Function ${name} not found in manifest`);
}
await this.sandbox.load(name, this.manifest!.functions[name].code!);
await this.sandbox.load(name, funDef.code!);
}
return await this.sandbox.invoke(name, args);
}
async dispatchEvent(name: string, data?: any): Promise<any[]> {
if (!this.manifest!.hooks?.events) {
return [];
}
let functionsToSpawn = this.manifest!.hooks.events[name];
if (functionsToSpawn) {
return await Promise.all(
functionsToSpawn.map(
async (functionToSpawn: string) =>
await this.invoke(functionToSpawn, [data])
functionsToSpawn.map((functionToSpawn: string) =>
this.invoke(functionToSpawn, [data])
)
);
} else {
@ -143,10 +147,21 @@ export class Plug<HookT> {
}
}
export class System<HookT> {
export type SystemJSON<HookT> = { [key: string]: Manifest<HookT> };
export type SystemEvents<HookT> = {
plugUpdated: (name: string, plug: Plug<HookT>) => void;
plugRemoved: (name: string) => void;
};
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
protected plugs = new Map<string, Plug<HookT>>();
registeredSyscalls: SysCallMapping = {};
constructor() {
super();
}
registerSyscalls(...registrationObjects: SysCallMapping[]) {
for (const registrationObject of registrationObjects) {
for (let p in registrationObject) {
@ -171,23 +186,63 @@ export class System<HookT> {
manifest: Manifest<HookT>,
sandbox: Sandbox
): Promise<Plug<HookT>> {
if (this.plugs.has(name)) {
await this.unload(name);
}
const plug = new Plug(this, name, sandbox);
await plug.load(manifest);
this.plugs.set(name, plug);
return plug;
}
async unload(name: string) {
const plug = this.plugs.get(name);
if (!plug) {
throw Error(`Plug ${name} not found`);
}
await plug.stop();
this.plugs.delete(name);
}
async dispatchEvent(name: string, data?: any): Promise<any[]> {
let promises = [];
for (let plug of this.plugs.values()) {
promises.push(plug.dispatchEvent(name, data));
for (let result of await plug.dispatchEvent(name, data)) {
promises.push(result);
}
}
return await Promise.all(promises);
}
async stop(): Promise<void[]> {
get loadedPlugs(): Map<string, Plug<HookT>> {
return this.plugs;
}
toJSON(): SystemJSON<HookT> {
let plugJSON: { [key: string]: Manifest<HookT> } = {};
for (let [name, plug] of this.plugs) {
if (!plug.manifest) {
continue;
}
plugJSON[name] = plug.manifest;
}
return plugJSON;
}
async replaceAllFromJSON(
json: SystemJSON<HookT>,
sandboxFactory: () => Sandbox
) {
await this.unloadAll();
for (let [name, manifest] of Object.entries(json)) {
console.log("Loading plug", name);
await this.load(name, manifest, sandboxFactory());
}
}
async unloadAll(): Promise<void[]> {
return Promise.all(
Array.from(this.plugs.values()).map((plug) => plug.stop())
Array.from(this.plugs.keys()).map(this.unload.bind(this))
);
}
}

View File

@ -17,9 +17,7 @@ declare global {
let postMessage = self.postMessage.bind(self);
if (window.parent !== window) {
console.log("running in an iframe");
postMessage = window.parent.postMessage.bind(window.parent);
// postMessage({ type: "test" }, "*");
}
self.syscall = async (id: number, name: string, args: any[]) => {
@ -43,7 +41,6 @@ return fn["default"].apply(null, arguments);`;
}
self.addEventListener("message", (event: { data: WorkerMessage }) => {
// console.log("Got a message", event.data);
safeRun(async () => {
let messageEvent = event;
let data = messageEvent.data;

View File

@ -1,7 +1,3 @@
export type EventHook = {
events: { [key: string]: string[] };
};
export type WorkerMessageType = "load" | "invoke" | "syscall-response";
export type WorkerMessage = {
@ -37,7 +33,30 @@ export interface FunctionDef {
code?: string;
}
export type EventHook = {
events?: { [key: string]: string[] };
};
export type EndpointHook = {
endpoints?: EndPointDef[];
};
export type EndPointDef = {
method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS";
path: string;
handler: string; // function name
};
export type CronHook = {
crons?: CronDef[];
};
export type CronDef = {
cron: string;
handler: string; // function name
};
export interface WorkerLike {
ready: Promise<void>;
onMessage?: (message: any) => Promise<void>;
postMessage(message: any): void;
terminate(): void;

View File

@ -5,6 +5,7 @@ import { safeRun } from "./util";
class WebWorkerWrapper implements WorkerLike {
private worker: Worker;
onMessage?: (message: any) => Promise<void>;
ready: Promise<void>;
constructor(worker: Worker) {
this.worker = worker;
@ -15,6 +16,7 @@ class WebWorkerWrapper implements WorkerLike {
await this.onMessage!(data);
});
});
this.ready = Promise.resolve();
}
postMessage(message: any): void {
this.worker.postMessage(message);

View File

@ -36,8 +36,16 @@
"events": {
"page:click": ["taskToggle", "clickNavigate"],
"editor:complete": ["pageComplete"],
"page:index": ["indexLinks"]
}
"page:index": ["indexLinks"],
"load": ["welcome"]
},
"endpoints": [
{
"method": "GET",
"path": "/",
"handler": "endpointTest"
}
]
},
"functions": {
"indexLinks": {
@ -76,8 +84,11 @@
"toggle_h2": {
"path": "./markup.ts:toggleH2"
},
"server_test": {
"path": "./server.ts:test"
"endpointTest": {
"path": "./server.ts:endpointTest"
},
"welcome": {
"path": "./server.ts:welcome"
}
}
}

View File

@ -1,5 +1,15 @@
import { syscall } from "./lib/syscall";
export function test() {
console.log("I'm running on the server!");
return 5;
import { EndpointRequest, EndpointResponse } from "../../plugbox/endpoints";
export function endpointTest(req: EndpointRequest): EndpointResponse {
console.log("I'm running on the server!", req);
return {
status: 200,
body: "Hello world!",
};
}
export function welcome() {
for (var i = 0; i < 10; i++) {
console.log("Welcome to you all!!!");
}
}

View File

@ -6,6 +6,8 @@ import { Server } from "socket.io";
import { SocketServer } from "./api_server";
import * as path from "path";
import * as fs from "fs";
import { SilverBulletHooks } from "../common/manifest";
import { System } from "../plugbox/runtime";
describe("Server test", () => {
let io: Server,
@ -38,7 +40,11 @@ describe("Server test", () => {
const port = httpServer.address().port;
// @ts-ignore
clientSocket = new Client(`http://localhost:${port}`);
socketServer = new SocketServer(tmpDir, io);
socketServer = new SocketServer(
tmpDir,
io,
new System<SilverBulletHooks>()
);
clientSocket.on("connect", done);
await socketServer.init();
});

View File

@ -4,9 +4,7 @@ import * as path from "path";
import { IndexApi } from "./index_api";
import { PageApi } from "./page_api";
import { System } from "../plugbox/runtime";
import { createSandbox } from "../plugbox/node_sandbox";
import { NuggetHook } from "../webapp/types";
import corePlug from "../webapp/generated/core.plug.json";
import { SilverBulletHooks } from "../common/manifest";
import pageIndexSyscalls from "./syscalls/page_index";
export class ClientConnection {
@ -25,12 +23,16 @@ export class SocketServer {
private apis = new Map<string, ApiProvider>();
readonly rootPath: string;
private serverSocket: Server;
system: System<NuggetHook>;
system: System<SilverBulletHooks>;
constructor(rootPath: string, serverSocket: Server) {
constructor(
rootPath: string,
serverSocket: Server,
system: System<SilverBulletHooks>
) {
this.rootPath = path.resolve(rootPath);
this.serverSocket = serverSocket;
this.system = new System<NuggetHook>();
this.system = system;
}
async registerApi(name: string, apiProvider: ApiProvider) {
@ -52,12 +54,6 @@ export class SocketServer {
)
);
let plug = await this.system.load(
"core",
corePlug,
createSandbox(this.system)
);
this.serverSocket.on("connection", (socket) => {
const clientConn = new ClientConnection(socket);
@ -112,6 +108,9 @@ export class SocketServer {
});
});
}
console.log("Sending the sytem to the client");
socket.emit("loadSystem", this.system.toJSON());
});
}

35
server/express_server.ts Normal file
View File

@ -0,0 +1,35 @@
import { Express } from "express";
import { System } from "../plugbox/runtime";
import { SilverBulletHooks } from "../common/manifest";
import { exposeSystem } from "../plugbox/endpoints";
import { readFile } from "fs/promises";
export class ExpressServer {
app: Express;
system: System<SilverBulletHooks>;
private rootPath: string;
constructor(
app: Express,
rootPath: string,
distDir: string,
system: System<SilverBulletHooks>
) {
this.app = app;
this.rootPath = rootPath;
this.system = system;
app.use(exposeSystem(this.system));
// Fallback, serve index.html
let cachedIndex: string | undefined = undefined;
app.get("/*", async (req, res) => {
if (!cachedIndex) {
cachedIndex = await readFile(`${distDir}/index.html`, "utf8");
}
res.status(200).header("Content-Type", "text/html").send(cachedIndex);
});
}
async init() {}
}

View File

@ -10,20 +10,20 @@ import path from "path";
import { stat } from "fs/promises";
import { Cursor, cursorEffect } from "../webapp/cursorEffect";
import { System } from "../plugbox/runtime";
import { NuggetHook } from "../webapp/types";
import { SilverBulletHooks } from "../common/manifest";
export class PageApi implements ApiProvider {
openPages: Map<string, Page>;
pageStore: DiskStorage;
rootPath: string;
connectedSockets: Set<Socket>;
private system: System<NuggetHook>;
private system: System<SilverBulletHooks>;
constructor(
rootPath: string,
connectedSockets: Set<Socket>,
openPages: Map<string, Page>,
system: System<NuggetHook>
system: System<SilverBulletHooks>
) {
this.pageStore = new DiskStorage(rootPath);
this.rootPath = rootPath;
@ -34,6 +34,20 @@ export class PageApi implements ApiProvider {
async init(): Promise<void> {
this.fileWatcher();
this.system.on({
plugUpdated: (plugName, plugDef) => {
console.log("Plug updated on disk, broadcasting to all clients");
this.connectedSockets.forEach((socket) => {
socket.emit("plugUpdated", plugName, plugDef);
});
},
plugRemoved: (plugName) => {
console.log("Plug removed on disk, broadcasting to all clients");
this.connectedSockets.forEach((socket) => {
socket.emit("plugRemoved", plugName);
});
},
});
}
broadcastCursors(page: Page) {

View File

@ -1,10 +1,13 @@
import express from "express";
import { readFile } from "fs/promises";
import http from "http";
import { Server } from "socket.io";
import { SocketServer } from "./api_server";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { System } from "../plugbox/runtime";
import { SilverBulletHooks } from "../common/manifest";
import { ExpressServer } from "./express_server";
import { DiskPlugLoader } from "../plugbox/plug_loader";
let args = yargs(hideBin(process.argv))
.option("debug", {
@ -16,8 +19,12 @@ let args = yargs(hideBin(process.argv))
})
.parse();
const pagesPath = args._[0] as string;
const app = express();
const server = http.createServer(app);
const system = new System<SilverBulletHooks>();
const io = new Server(server, {
cors: {
methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
@ -29,18 +36,26 @@ const port = args.port;
const distDir = `${__dirname}/../webapp`;
app.use("/", express.static(distDir));
let socketServer = new SocketServer(args._[0] as string, io);
socketServer.init();
// Fallback, serve index.html
let cachedIndex: string | undefined = undefined;
app.get("/*", async (req, res) => {
if (!cachedIndex) {
cachedIndex = await readFile(`${distDir}/index.html`, "utf8");
}
res.status(200).header("Content-Type", "text/html").send(cachedIndex);
let socketServer = new SocketServer(pagesPath, io, system);
socketServer.init().catch((e) => {
console.error(e);
});
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
const expressServer = new ExpressServer(app, pagesPath, distDir, system);
expressServer
.init()
.then(async () => {
let plugLoader = new DiskPlugLoader(
system,
`${__dirname}/../../plugs/dist`
);
await plugLoader.loadPlugs();
plugLoader.watcher();
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
})
.catch((e) => {
console.error(e);
});

15
server/syscalls/shell.ts Normal file
View File

@ -0,0 +1,15 @@
import { promisify } from "util";
import { execFile } from "child_process";
const execFilePromise = promisify(execFile);
export default function (cwd: string) {
return {
"shell.run": async (cmd: string, args: string[]) => {
let { stdout, stderr } = await execFilePromise(cmd, args, {
cwd: cwd,
});
return { stdout, stderr };
},
};
}

View File

@ -17,5 +17,5 @@ window.editor = editor;
navigator.serviceWorker
.register(new URL("service_worker.ts", import.meta.url), { type: "module" })
.then((r) => {
console.log("Service worker registered", r);
// console.log("Service worker registered", r);
});

View File

@ -17,7 +17,7 @@ import {
} from "@codemirror/view";
import { throttle } from "./util";
import { Cursor, cursorEffect } from "./cursorEffect";
import { EventEmitter } from "./event";
import { EventEmitter } from "../common/event";
const throttleInterval = 250;

View File

@ -19,7 +19,6 @@ import {
KeyBinding,
keymap,
} from "@codemirror/view";
// import { debounce } from "lodash";
import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";
import { Plug, System } from "../plugbox/runtime";
@ -31,7 +30,6 @@ import { CommandPalette } from "./components/command_palette";
import { PageNavigator } from "./components/page_navigator";
import { TopBar } from "./components/top_bar";
import { Cursor } from "./cursorEffect";
import coreManifest from "./generated/core.plug.json";
import { lineWrapper } from "./line_wrapper";
import { markdown } from "./markdown";
import { IPageNavigator, PathPageNavigator } from "./navigator";
@ -49,9 +47,9 @@ import {
AppCommand,
AppViewState,
initialViewState,
NuggetHook,
slashCommandRegexp,
} from "./types";
import { SilverBulletHooks } from "../common/manifest";
import { safeRun } from "./util";
class PageState {
@ -65,20 +63,19 @@ class PageState {
}
export class Editor implements AppEventDispatcher {
private system = new System<SilverBulletHooks>();
editorView?: EditorView;
viewState: AppViewState;
viewDispatch: React.Dispatch<Action>;
openPages: Map<string, PageState>;
space: Space;
editorCommands: Map<string, AppCommand>;
plugs: Plug<NuggetHook>[];
navigationResolve?: (val: undefined) => void;
pageNavigator: IPageNavigator;
constructor(space: Space, parent: Element) {
this.editorCommands = new Map();
this.openPages = new Map();
this.plugs = [];
this.space = space;
this.viewState = initialViewState;
this.viewDispatch = () => {};
@ -91,6 +88,13 @@ export class Editor implements AppEventDispatcher {
parent: document.getElementById("editor")!,
});
this.pageNavigator = new PathPageNavigator();
this.system.registerSyscalls(
dbSyscalls,
editorSyscalls(this),
spaceSyscalls(this),
indexerSyscalls(this.space)
);
}
async init() {
@ -128,6 +132,39 @@ export class Editor implements AppEventDispatcher {
pages: pages,
});
},
loadSystem: (systemJSON) => {
safeRun(async () => {
console.log("Received SYSTEM", systemJSON);
await this.system.replaceAllFromJSON(systemJSON, () =>
createIFrameSandbox(this.system)
);
console.log("Loaded plugs, now updating editor comands");
this.editorCommands = new Map<string, AppCommand>();
for (let plug of this.system.loadedPlugs.values()) {
this.buildCommands(plug);
}
this.viewDispatch({
type: "update-commands",
commands: this.editorCommands,
});
});
},
plugUpdated: (plugName, plug) => {
safeRun(async () => {
console.log("Plug updated", plugName);
await this.system.load(
plugName,
plug,
createIFrameSandbox(this.system)
);
});
},
plugRemoved: (plugName) => {
safeRun(async () => {
console.log("Plug removed", plugName);
await this.system.unload(plugName);
});
},
});
if (this.pageNavigator.getCurrentPage() === "") {
@ -154,32 +191,16 @@ export class Editor implements AppEventDispatcher {
}
async loadPlugs() {
const system = new System<NuggetHook>();
const system = new System<SilverBulletHooks>();
system.registerSyscalls(
dbSyscalls,
editorSyscalls(this),
spaceSyscalls(this),
indexerSyscalls(this.space)
);
console.log("Now loading core plug");
let mainPlug = await system.load(
"core",
coreManifest,
createIFrameSandbox(system)
);
this.plugs.push(mainPlug);
this.editorCommands = new Map<string, AppCommand>();
for (let plug of this.plugs) {
this.buildCommands(plug);
}
this.viewDispatch({
type: "update-commands",
commands: this.editorCommands,
});
}
private buildCommands(plug: Plug<NuggetHook>) {
private buildCommands(plug: Plug<SilverBulletHooks>) {
const cmds = plug.manifest!.hooks.commands;
for (let name in cmds) {
let cmd = cmds[name];
@ -192,18 +213,8 @@ export class Editor implements AppEventDispatcher {
}
}
// TODO: Parallelize?
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
let results: any[] = [];
for (let plug of this.plugs) {
let plugResults = await plug.dispatchEvent(name, data);
if (plugResults) {
for (let result of plugResults) {
results.push(result);
}
}
}
return results;
return this.system.dispatchEvent(name, data);
}
get currentPage(): string | undefined {
@ -338,6 +349,7 @@ export class Editor implements AppEventDispatcher {
async plugCompleter(): Promise<CompletionResult | null> {
let allCompletionResults = await this.dispatchAppEvent("editor:complete");
console.log("Completion results", allCompletionResults);
if (allCompletionResults.length === 1) {
return allCompletionResults[0];
} else if (allCompletionResults.length > 1) {

View File

@ -5,7 +5,9 @@ import { ChangeSet, Text, Transaction } from "@codemirror/state";
import { CollabDocument, CollabEvents } from "./collab";
import { cursorEffect } from "./cursorEffect";
import { EventEmitter } from "./event";
import { EventEmitter } from "../common/event";
import { SystemJSON } from "../plugbox/runtime";
import { Manifest } from "../common/manifest";
export type SpaceEvents = {
connect: () => void;
@ -13,6 +15,9 @@ export type SpaceEvents = {
pageChanged: (meta: PageMeta) => void;
pageDeleted: (name: string) => void;
pageListUpdated: (pages: Set<PageMeta>) => void;
loadSystem: (systemJSON: SystemJSON<any>) => void;
plugUpdated: (plugName: string, plug: Manifest) => void;
plugRemoved: (plugName: string) => void;
} & CollabEvents;
export type KV = {
@ -35,6 +40,9 @@ export class Space extends EventEmitter<SpaceEvents> {
"pageCreated",
"pageChanged",
"pageDeleted",
"loadSystem",
"plugUpdated",
"plugRemoved",
].forEach((eventName) => {
socket.on(eventName, (...args) => {
this.emit(eventName as keyof SpaceEvents, ...args);
@ -54,7 +62,7 @@ export class Space extends EventEmitter<SpaceEvents> {
break;
}
}
if(!found) {
if (!found) {
this.allPages.add(meta);
console.log("New page created", meta);
this.emit("pageListUpdated", this.allPages);

View File

@ -1,12 +1,4 @@
import * as plugbox from "../plugbox/types";
export type NuggetHook = {
commands: {
[key: string]: CommandDef;
};
};
export type Manifest = plugbox.Manifest<NuggetHook>;
import { CommandDef } from "../common/manifest";
export type PageMeta = {
name: string;
@ -22,19 +14,6 @@ export type AppCommand = {
export const slashCommandRegexp = /\/[\w\-]*/;
export interface CommandDef {
// Function name to invoke
invoke: string;
// Bind to keyboard shortcut
key?: string;
mac?: string;
// If to show in slash invoked menu and if so, with what label
// should match slashCommandRegexp
slashCommand?: string;
}
export type Notification = {
id: number;
message: string;

138
yarn.lock
View File

@ -1553,6 +1553,11 @@
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
"@types/cookiejar@*":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8"
integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==
"@types/cors@^2.8.12":
version "2.8.12"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
@ -1621,6 +1626,11 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
"@types/node-cron@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.1.tgz#e01a874d4c2aa1a02ebc64cfd1cd8ebdbad7a996"
integrity sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg==
"@types/node@*", "@types/node@>=10.0.0", "@types/node@^17.0.21":
version "17.0.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644"
@ -1685,6 +1695,21 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/superagent@*":
version "4.1.15"
resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a"
integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==
dependencies:
"@types/cookiejar" "*"
"@types/node" "*"
"@types/supertest@^2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.11.tgz#2e70f69f220bc77b4f660d72c2e1a4231f44a77d"
integrity sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==
dependencies:
"@types/superagent" "*"
"@types/yargs-parser@*":
version "21.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
@ -1838,6 +1863,11 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
asap@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
asn1.js@^5.2.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@ -2395,7 +2425,7 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
component-emitter@~1.3.0:
component-emitter@^1.3.0, component-emitter@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
@ -2451,6 +2481,11 @@ cookie@0.4.2, cookie@~0.4.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
cookiejar@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
@ -2672,7 +2707,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.1.1, debug@~4.3.1, debug@~4.3.2:
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -2792,6 +2827,14 @@ dexie@^3.2.1:
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.1.tgz#ef21456d725e700c1ab7ac4307896e4fdabaf753"
integrity sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==
dezalgo@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
dependencies:
asap "^2.0.0"
wrappy "1"
diff-sequences@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
@ -3287,6 +3330,11 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-safe-stringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
fb-watchman@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
@ -3341,6 +3389,25 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
formidable@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff"
integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==
dependencies:
dezalgo "1.0.3"
hexoid "1.0.0"
once "1.4.0"
qs "6.9.3"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -3577,6 +3644,11 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hexoid@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -4666,7 +4738,7 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
methods@~1.1.2:
methods@^1.1.2, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
@ -4704,7 +4776,7 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mime@^2.4.4:
mime@^2.4.4, mime@^2.5.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
@ -4751,6 +4823,18 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
moment-timezone@^0.5.31:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0":
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -4823,6 +4907,13 @@ node-addon-api@^4.2.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
node-cron@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.0.tgz#b33252803e430f9cd8590cf85738efa1497a9522"
integrity sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==
dependencies:
moment-timezone "^0.5.31"
node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@ -4955,7 +5046,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@ -5538,11 +5629,23 @@ pupa@^2.1.1:
dependencies:
escape-goat "^2.0.0"
qs@6.9.3:
version "6.9.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
qs@6.9.7:
version "6.9.7"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
qs@^6.10.1:
version "6.10.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
dependencies:
side-channel "^1.0.4"
querystring-es3@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@ -6120,6 +6223,31 @@ stylehacks@^*:
browserslist "^4.16.6"
postcss-selector-parser "^6.0.4"
superagent@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.1.tgz#2ab187d38c3078c31c3771c0b751f10163a27136"
integrity sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==
dependencies:
component-emitter "^1.3.0"
cookiejar "^2.1.3"
debug "^4.3.3"
fast-safe-stringify "^2.1.1"
form-data "^4.0.0"
formidable "^2.0.1"
methods "^1.1.2"
mime "^2.5.0"
qs "^6.10.1"
readable-stream "^3.6.0"
semver "^7.3.5"
supertest@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.2.tgz#04a5998fd3efaff187cb69f07a169755d655b001"
integrity sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg==
dependencies:
methods "^1.1.2"
superagent "^7.1.0"
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"