diff --git a/packages/pro b/packages/pro index 45f5673d5e..219be3ef38 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 45f5673d5e5ab3c22deb6663cea2e31a628aa133 +Subproject commit 219be3ef380ac8cb16b34892f62117d31b50eec7 diff --git a/packages/server/src/api/controllers/plugin/index.ts b/packages/server/src/api/controllers/plugin/index.ts index 2a4d69b671..6f7cceca10 100644 --- a/packages/server/src/api/controllers/plugin/index.ts +++ b/packages/server/src/api/controllers/plugin/index.ts @@ -11,6 +11,7 @@ import { UploadPluginResponse, FetchPluginResponse, DeletePluginResponse, + PluginMetadata, } from "@budibase/types" import env from "../../../environment" import { clientAppSocket } from "../../../websockets" @@ -53,10 +54,11 @@ export async function create( const { source, url, headers, githubToken } = ctx.request.body try { - let metadata - let directory + let metadata: PluginMetadata + let directory: string + // Generating random name as a backup and needed for url - let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000) + const name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000) switch (source) { case PluginSource.NPM: { @@ -81,12 +83,14 @@ export async function create( directory = directoryUrl break } + default: + ctx.throw(400, "Invalid source") } - pluginCore.validate(metadata?.schema) + pluginCore.validate(metadata.schema) // Only allow components in cloud - if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) { + if (!env.SELF_HOSTED && metadata.schema?.type !== PluginType.COMPONENT) { throw new Error( "Only component plugins are supported outside of self-host" ) diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index e3e88c25c4..6d6bf00ee0 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -7,7 +7,7 @@ import { default as queries } from "./app/queries" import { default as rows } from "./app/rows" import { default as links } from "./app/links" import { default as users } from "./users" -import { default as plugins } from "./plugins" +import * as plugins from "./plugins" import * as views from "./app/views" import * as permissions from "./app/permissions" import * as rowActions from "./app/rowActions" diff --git a/packages/server/src/sdk/plugins/index.ts b/packages/server/src/sdk/plugins/index.ts index 863cfa0db6..03dd2be256 100644 --- a/packages/server/src/sdk/plugins/index.ts +++ b/packages/server/src/sdk/plugins/index.ts @@ -1,5 +1,41 @@ -import * as plugins from "./plugins" +import { KoaFile, Plugin, PluginSource, PluginType } from "@budibase/types" +import { + db as dbCore, + objectStore, + plugins as pluginCore, + tenancy, +} from "@budibase/backend-core" +import { fileUpload } from "../../api/controllers/plugin/file" +import env from "../../environment" +import { clientAppSocket } from "../../websockets" +import { sdk as pro } from "@budibase/pro" -export default { - ...plugins, +export async function fetch(type?: PluginType): Promise { + const db = tenancy.getGlobalDB() + const response = await db.allDocs( + dbCore.getPluginParams(null, { + include_docs: true, + }) + ) + let plugins = response.rows.map((row: any) => row.doc) as Plugin[] + plugins = await objectStore.enrichPluginURLs(plugins) + if (type) { + return plugins.filter((plugin: Plugin) => plugin.schema?.type === type) + } else { + return plugins + } +} + +export async function processUploaded(plugin: KoaFile, source: PluginSource) { + const { metadata, directory } = await fileUpload(plugin) + pluginCore.validate(metadata.schema) + + // Only allow components in cloud + if (!env.SELF_HOSTED && metadata.schema?.type !== PluginType.COMPONENT) { + throw new Error("Only component plugins are supported outside of self-host") + } + + const doc = await pro.plugins.storePlugin(metadata, directory, source) + clientAppSocket?.emit("plugin-update", { name: doc.name, hash: doc.hash }) + return doc } diff --git a/packages/server/src/sdk/plugins/plugins.ts b/packages/server/src/sdk/plugins/plugins.ts deleted file mode 100644 index bff24dcef7..0000000000 --- a/packages/server/src/sdk/plugins/plugins.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { KoaFile, Plugin, PluginSource, PluginType } from "@budibase/types" -import { - db as dbCore, - objectStore, - plugins as pluginCore, - tenancy, -} from "@budibase/backend-core" -import { fileUpload } from "../../api/controllers/plugin/file" -import env from "../../environment" -import { clientAppSocket } from "../../websockets" -import { sdk as pro } from "@budibase/pro" - -export async function fetch(type?: PluginType): Promise { - const db = tenancy.getGlobalDB() - const response = await db.allDocs( - dbCore.getPluginParams(null, { - include_docs: true, - }) - ) - let plugins = response.rows.map((row: any) => row.doc) as Plugin[] - plugins = await objectStore.enrichPluginURLs(plugins) - if (type) { - return plugins.filter((plugin: Plugin) => plugin.schema?.type === type) - } else { - return plugins - } -} - -export async function processUploaded(plugin: KoaFile, source?: PluginSource) { - const { metadata, directory } = await fileUpload(plugin) - pluginCore.validate(metadata?.schema) - - // Only allow components in cloud - if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) { - throw new Error("Only component plugins are supported outside of self-host") - } - - const doc = await pro.plugins.storePlugin(metadata, directory, source) - clientAppSocket?.emit("plugin-update", { name: doc.name, hash: doc.hash }) - return doc -} diff --git a/packages/server/src/utilities/fileSystem/plugin.ts b/packages/server/src/utilities/fileSystem/plugin.ts index 2949daef61..1c62e9c1e9 100644 --- a/packages/server/src/utilities/fileSystem/plugin.ts +++ b/packages/server/src/utilities/fileSystem/plugin.ts @@ -1,4 +1,4 @@ -import { Plugin } from "@budibase/types" +import { Plugin, PluginUpload } from "@budibase/types" import { budibaseTempDir } from "../budibaseDir" import fs from "fs" import { join } from "path" @@ -8,23 +8,22 @@ import stream from "stream" const DATASOURCE_PATH = join(budibaseTempDir(), "datasource") const AUTOMATION_PATH = join(budibaseTempDir(), "automation") -export const getPluginMetadata = async (path: string) => { - let metadata: any = {} +export const getPluginMetadata = async ( + path: string +): Promise => { + let pkg: any + let schema: any try { - const pkg = fs.readFileSync(join(path, "package.json"), "utf8") - const schema = fs.readFileSync(join(path, "schema.json"), "utf8") - - metadata.schema = JSON.parse(schema) - metadata.package = JSON.parse(pkg) - - if ( - !metadata.package.name || - !metadata.package.version || - !metadata.package.description - ) { - throw new Error( - "package.json is missing one of 'name', 'version' or 'description'." - ) + pkg = JSON.parse(fs.readFileSync(join(path, "pkg.json"), "utf8")) + schema = JSON.parse(fs.readFileSync(join(path, "schema.json"), "utf8")) + if (!pkg.name) { + throw new Error("package.json is missing 'name'.") + } + if (!pkg.version) { + throw new Error("package.json is missing 'version'.") + } + if (!pkg.description) { + throw new Error("package.json is missing 'description'.") } } catch (err: any) { throw new Error( @@ -32,7 +31,7 @@ export const getPluginMetadata = async (path: string) => { ) } - return { metadata, directory: path } + return { metadata: { package: pkg, schema }, directory: path } } async function getPluginImpl(path: string, plugin: Plugin) { diff --git a/packages/server/src/watch.ts b/packages/server/src/watch.ts index 8f3fe22023..16b8800887 100644 --- a/packages/server/src/watch.ts +++ b/packages/server/src/watch.ts @@ -3,7 +3,8 @@ import env from "./environment" import chokidar from "chokidar" import fs from "fs" import { constants, tenancy } from "@budibase/backend-core" -import pluginsSdk from "./sdk/plugins" +import { processUploaded } from "./sdk/plugins" +import { PluginSource } from "@budibase/types" export function watch() { const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz") @@ -27,7 +28,7 @@ export function watch() { const split = path.split("/") const name = split[split.length - 1] console.log("Importing plugin:", path) - await pluginsSdk.processUploaded({ name, path }) + await processUploaded({ name, path }, PluginSource.FILE) } catch (err: any) { const message = err?.message ? err?.message : err console.error("Failed to import plugin:", message) diff --git a/packages/types/src/documents/global/plugin.ts b/packages/types/src/documents/global/plugin.ts index eeddc04e58..56611d2d24 100644 --- a/packages/types/src/documents/global/plugin.ts +++ b/packages/types/src/documents/global/plugin.ts @@ -24,10 +24,7 @@ export interface Plugin extends Document { source: PluginSource package: { [key: string]: any } hash: string - schema: { - type: PluginType - [key: string]: any - } + schema: PluginSchema iconFileName?: string // Populated on read jsUrl?: string @@ -36,3 +33,24 @@ export interface Plugin extends Document { } export const PLUGIN_TYPE_ARR = Object.values(PluginType) + +export interface PluginSchema { + type: PluginType + [key: string]: any +} + +interface Package { + name: string + version: string + description: string +} + +export interface PluginMetadata { + schema: PluginSchema + package: Package +} + +export interface PluginUpload { + metadata: PluginMetadata + directory: string +}