Updating plugins to not think about versions, only ever one version of each plugin by name, making plugins self host only and adding error checking for datasource implementations.
This commit is contained in:
parent
3ba2aaf567
commit
a5f475872d
|
@ -2,6 +2,7 @@ const { DocumentType, getPluginParams } = require("../../db/utils")
|
||||||
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||||
|
const env = require("../../environment")
|
||||||
|
|
||||||
exports.fetchAppComponentDefinitions = async function (ctx) {
|
exports.fetchAppComponentDefinitions = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
|
@ -32,6 +33,8 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for now custom components only supported in self-host
|
||||||
|
if (env.SELF_HOSTED) {
|
||||||
// Add custom components
|
// Add custom components
|
||||||
const globalDB = getGlobalDB()
|
const globalDB = getGlobalDB()
|
||||||
const response = await globalDB.allDocs(
|
const response = await globalDB.allDocs(
|
||||||
|
@ -49,6 +52,7 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
|
||||||
...plugin.schema.schema,
|
...plugin.schema.schema,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = definitions
|
ctx.body = definitions
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { ObjectStoreBuckets } from "../../constants"
|
import { ObjectStoreBuckets } from "../../constants"
|
||||||
import { extractPluginTarball } from "../../utilities/fileSystem"
|
import { extractPluginTarball, loadJSFile } from "../../utilities/fileSystem"
|
||||||
import { getGlobalDB } from "@budibase/backend-core/tenancy"
|
import { getGlobalDB } from "@budibase/backend-core/tenancy"
|
||||||
import { generatePluginID, getPluginParams } from "../../db/utils"
|
import { generatePluginID, getPluginParams } from "../../db/utils"
|
||||||
import { uploadDirectory } from "@budibase/backend-core/objectStore"
|
import { uploadDirectory } from "@budibase/backend-core/objectStore"
|
||||||
import { PluginType, FileType } from "@budibase/types"
|
import { PluginType, FileType } from "@budibase/types"
|
||||||
|
import env from "../../environment"
|
||||||
|
|
||||||
export async function getPlugins(type?: PluginType) {
|
export async function getPlugins(type?: PluginType) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
@ -49,6 +50,9 @@ export async function fetch(ctx: any) {
|
||||||
export async function destroy(ctx: any) {}
|
export async function destroy(ctx: any) {}
|
||||||
|
|
||||||
export async function processPlugin(plugin: FileType) {
|
export async function processPlugin(plugin: FileType) {
|
||||||
|
if (!env.SELF_HOSTED) {
|
||||||
|
throw new Error("Plugins not supported outside of self-host.")
|
||||||
|
}
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const { metadata, directory } = await extractPluginTarball(plugin)
|
const { metadata, directory } = await extractPluginTarball(plugin)
|
||||||
const version = metadata.package.version,
|
const version = metadata.package.version,
|
||||||
|
@ -56,7 +60,7 @@ export async function processPlugin(plugin: FileType) {
|
||||||
description = metadata.package.description
|
description = metadata.package.description
|
||||||
|
|
||||||
// first open the tarball into tmp directory
|
// first open the tarball into tmp directory
|
||||||
const bucketPath = `${name}/${version}/`
|
const bucketPath = `${name}/`
|
||||||
const files = await uploadDirectory(
|
const files = await uploadDirectory(
|
||||||
ObjectStoreBuckets.PLUGINS,
|
ObjectStoreBuckets.PLUGINS,
|
||||||
directory,
|
directory,
|
||||||
|
@ -66,8 +70,20 @@ export async function processPlugin(plugin: FileType) {
|
||||||
if (!jsFile) {
|
if (!jsFile) {
|
||||||
throw new Error(`Plugin missing .js file.`)
|
throw new Error(`Plugin missing .js file.`)
|
||||||
}
|
}
|
||||||
|
// validate the JS for a datasource
|
||||||
|
if (metadata.schema.type === PluginType.DATASOURCE) {
|
||||||
|
const js = loadJSFile(directory, jsFile.name)
|
||||||
|
// TODO: this isn't safe - but we need full node environment
|
||||||
|
// in future we should do this in a thread for safety
|
||||||
|
try {
|
||||||
|
eval(js)
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = err?.message ? err.message : JSON.stringify(err)
|
||||||
|
throw new Error(`JS invalid: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
const jsFileName = jsFile.name
|
const jsFileName = jsFile.name
|
||||||
const pluginId = generatePluginID(name, version)
|
const pluginId = generatePluginID(name)
|
||||||
|
|
||||||
// overwrite existing docs entirely if they exist
|
// overwrite existing docs entirely if they exist
|
||||||
let rev
|
let rev
|
||||||
|
@ -80,10 +96,10 @@ export async function processPlugin(plugin: FileType) {
|
||||||
const doc = {
|
const doc = {
|
||||||
_id: pluginId,
|
_id: pluginId,
|
||||||
_rev: rev,
|
_rev: rev,
|
||||||
|
...metadata,
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
description,
|
description,
|
||||||
...metadata,
|
|
||||||
jsUrl: `${bucketPath}${jsFileName}`,
|
jsUrl: `${bucketPath}${jsFileName}`,
|
||||||
}
|
}
|
||||||
const response = await db.put(doc)
|
const response = await db.put(doc)
|
||||||
|
|
|
@ -371,8 +371,8 @@ exports.getMemoryViewParams = (otherProps = {}) => {
|
||||||
return getDocParams(DocumentType.MEM_VIEW, null, otherProps)
|
return getDocParams(DocumentType.MEM_VIEW, null, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generatePluginID = (name, version) => {
|
exports.generatePluginID = name => {
|
||||||
return `${DocumentType.PLUGIN}${SEPARATOR}${name}${SEPARATOR}${version}`
|
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -66,13 +66,12 @@ if (environment.SELF_HOSTED) {
|
||||||
DEFINITIONS[SourceName.GOOGLE_SHEETS] = googlesheets.schema
|
DEFINITIONS[SourceName.GOOGLE_SHEETS] = googlesheets.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIntegrationAvailable(integration: string) {}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getDefinitions: async () => {
|
getDefinitions: async () => {
|
||||||
|
const pluginSchemas: { [key: string]: Integration } = {}
|
||||||
|
if (environment.SELF_HOSTED) {
|
||||||
const plugins = await getPlugins(PluginType.DATASOURCE)
|
const plugins = await getPlugins(PluginType.DATASOURCE)
|
||||||
// extract the actual schema from each custom
|
// extract the actual schema from each custom
|
||||||
const pluginSchemas: { [key: string]: Integration } = {}
|
|
||||||
for (let plugin of plugins) {
|
for (let plugin of plugins) {
|
||||||
const sourceId = plugin.name
|
const sourceId = plugin.name
|
||||||
pluginSchemas[sourceId] = {
|
pluginSchemas[sourceId] = {
|
||||||
|
@ -80,6 +79,7 @@ module.exports = {
|
||||||
custom: true,
|
custom: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...cloneDeep(DEFINITIONS),
|
...cloneDeep(DEFINITIONS),
|
||||||
...pluginSchemas,
|
...pluginSchemas,
|
||||||
|
@ -89,6 +89,7 @@ module.exports = {
|
||||||
if (INTEGRATIONS[integration]) {
|
if (INTEGRATIONS[integration]) {
|
||||||
return INTEGRATIONS[integration]
|
return INTEGRATIONS[integration]
|
||||||
}
|
}
|
||||||
|
if (environment.SELF_HOSTED) {
|
||||||
const plugins = await getPlugins(PluginType.DATASOURCE)
|
const plugins = await getPlugins(PluginType.DATASOURCE)
|
||||||
for (let plugin of plugins) {
|
for (let plugin of plugins) {
|
||||||
if (plugin.name === integration) {
|
if (plugin.name === integration) {
|
||||||
|
@ -100,5 +101,7 @@ module.exports = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
throw new Error("No datasource implementation found.")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,13 @@ exports.loadHandlebarsFile = path => {
|
||||||
return fs.readFileSync(path, "utf8")
|
return fs.readFileSync(path, "utf8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as above just with a different name.
|
||||||
|
*/
|
||||||
|
exports.loadJSFile = (directory, name) => {
|
||||||
|
return fs.readFileSync(join(directory, name), "utf8")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When return a file from the API need to write the file to the system temporarily so we
|
* When return a file from the API need to write the file to the system temporarily so we
|
||||||
* can create a read stream to send.
|
* can create a read stream to send.
|
||||||
|
|
|
@ -29,8 +29,9 @@ export function watch() {
|
||||||
const name = split[split.length - 1]
|
const name = split[split.length - 1]
|
||||||
console.log("Importing plugin:", path)
|
console.log("Importing plugin:", path)
|
||||||
await processPlugin({ name, path })
|
await processPlugin({ name, path })
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.log("Failed to import plugin:", err)
|
const message = err?.message ? err?.message : err
|
||||||
|
console.error("Failed to import plugin:", message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue