Merge branch 'plugins-dev-experience' of github.com:Budibase/budibase into plugins-dev-experience

This commit is contained in:
Andrew Kingston 2022-08-22 11:27:18 +01:00
commit 975afc29d2
8 changed files with 88 additions and 43 deletions

View File

@ -28,6 +28,9 @@
let importModal let importModal
$: showImportButton = false $: showImportButton = false
$: customIntegrations = Object.entries(integrations).filter(
entry => entry[1].custom
)
checkShowImport() checkShowImport()
@ -163,9 +166,10 @@
/> />
{/each} {/each}
</div> </div>
{#if customIntegrations.length > 0}
<Body size="S">Custom data source</Body> <Body size="S">Custom data source</Body>
<div class="item-list"> <div class="item-list">
{#each Object.entries(integrations).filter(entry => entry[1].custom) as [integrationType, schema]} {#each customIntegrations as [integrationType, schema]}
<DatasourceCard <DatasourceCard
on:selected={evt => selectIntegration(evt.detail)} on:selected={evt => selectIntegration(evt.detail)}
{schema} {schema}
@ -174,6 +178,7 @@
/> />
{/each} {/each}
</div> </div>
{/if}
</Layout> </Layout>
</ModalContent> </ModalContent>
</Modal> </Modal>

View File

@ -9,6 +9,19 @@ const { runPkgCommand } = require("../exec")
const { join } = require("path") const { join } = require("path")
const { success, error, info } = require("../utils") const { success, error, info } = require("../utils")
function checkInPlugin() {
if (!fs.existsSync("package.json")) {
throw new Error(
"Please run in a plugin directory - must contain package.json"
)
}
if (!fs.existsSync("schema.json")) {
throw new Error(
"Please run in a plugin directory - must contain schema.json"
)
}
}
async function init(opts) { async function init(opts) {
const type = opts["init"] || opts const type = opts["init"] || opts
if (!type || !PLUGIN_TYPES_ARR.includes(type)) { if (!type || !PLUGIN_TYPES_ARR.includes(type)) {
@ -35,13 +48,15 @@ async function init(opts) {
// get the skeleton // get the skeleton
console.log(info("Retrieving project...")) console.log(info("Retrieving project..."))
await getSkeleton(type, name) await getSkeleton(type, name)
await fleshOutSkeleton(name, desc, version) await fleshOutSkeleton(type, name, desc, version)
console.log(info("Installing dependencies...")) console.log(info("Installing dependencies..."))
await runPkgCommand("install", join(process.cwd(), name)) await runPkgCommand("install", join(process.cwd(), name))
console.log(info(`Plugin created in directory "${name}"`)) console.log(info(`Plugin created in directory "${name}"`))
} }
async function verify() { async function verify() {
// will throw errors if not acceptable
checkInPlugin()
console.log(info("Verifying plugin...")) console.log(info("Verifying plugin..."))
const schema = fs.readFileSync("schema.json", "utf8") const schema = fs.readFileSync("schema.json", "utf8")
const pkg = fs.readFileSync("package.json", "utf8") const pkg = fs.readFileSync("package.json", "utf8")

View File

@ -6,7 +6,7 @@ const { join } = require("path")
const tar = require("tar") const tar = require("tar")
const { processStringSync } = require("@budibase/string-templates") const { processStringSync } = require("@budibase/string-templates")
const HBS_FILES = ["package.json.hbs", "schema.json.hbs"] const HBS_FILES = ["package.json.hbs", "schema.json.hbs", "README.md.hbs"]
async function getSkeletonUrl(type) { async function getSkeletonUrl(type) {
const resp = await fetch( const resp = await fetch(
@ -40,7 +40,7 @@ exports.getSkeleton = async (type, name) => {
fs.rmSync(tarballFile) fs.rmSync(tarballFile)
} }
exports.fleshOutSkeleton = async (name, description, version) => { exports.fleshOutSkeleton = async (type, name, description, version) => {
for (let file of HBS_FILES) { for (let file of HBS_FILES) {
const oldFile = join(name, file), const oldFile = join(name, file),
newFile = join(name, file.substring(0, file.length - 4)) newFile = join(name, file.substring(0, file.length - 4))

View File

@ -22,6 +22,8 @@ function validateComponent(schema) {
const validator = joi.object({ const validator = joi.object({
type: joi.string().allow("component").required(), type: joi.string().allow("component").required(),
metadata: joi.object().unknown(true).required(), metadata: joi.object().unknown(true).required(),
hash: joi.string().optional(),
version: joi.string().optional(),
schema: joi schema: joi
.object({ .object({
name: joi.string().required(), name: joi.string().required(),
@ -53,6 +55,8 @@ function validateDatasource(schema) {
const validator = joi.object({ const validator = joi.object({
type: joi.string().allow("datasource").required(), type: joi.string().allow("datasource").required(),
metadata: joi.object().unknown(true).required(), metadata: joi.object().unknown(true).required(),
hash: joi.string().optional(),
version: joi.string().optional(),
schema: joi.object({ schema: joi.object({
docs: joi.string(), docs: joi.string(),
friendlyName: joi.string().required(), friendlyName: joi.string().required(),

View File

@ -17,15 +17,12 @@ const bullboard = require("./automations/bullboard")
const { logAlert } = require("@budibase/backend-core/logging") const { logAlert } = require("@budibase/backend-core/logging")
const { pinoSettings } = require("@budibase/backend-core") const { pinoSettings } = require("@budibase/backend-core")
const { Thread } = require("./threads") const { Thread } = require("./threads")
const chokidar = require("chokidar")
const fs = require("fs") const fs = require("fs")
const path = require("path")
import redis from "./utilities/redis" import redis from "./utilities/redis"
import * as migrations from "./migrations" import * as migrations from "./migrations"
import { events, installation, tenancy } from "@budibase/backend-core" import { events, installation, tenancy } from "@budibase/backend-core"
import { createAdminUser, getChecklist } from "./utilities/workerRequests" import { createAdminUser, getChecklist } from "./utilities/workerRequests"
import { processPlugin } from "./api/controllers/plugin" import { watch } from "./watch"
import { DEFAULT_TENANT_ID } from "@budibase/backend-core/constants"
const app = new Koa() const app = new Koa()
@ -144,28 +141,7 @@ module.exports = server.listen(env.PORT || 0, async () => {
env.PLUGINS_DIR && env.PLUGINS_DIR &&
fs.existsSync(env.PLUGINS_DIR) fs.existsSync(env.PLUGINS_DIR)
) { ) {
const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz") watch()
chokidar
.watch(watchPath, {
ignored: "**/node_modules",
awaitWriteFinish: true,
})
.on("all", async (event: string, path: string) => {
// Sanity checks
if (!path?.endsWith(".tar.gz") || !fs.existsSync(path)) {
return
}
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
try {
const split = path.split("/")
const name = split[split.length - 1]
console.log("Importing plugin:", path)
await processPlugin({ name, path })
} catch (err) {
console.log("Failed to import plugin:", err)
}
})
})
} }
// check for version updates // check for version updates

View File

@ -93,7 +93,11 @@ module.exports = {
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.name === integration) { if (plugin.name === integration) {
// need to use commonJS require due to its dynamic runtime nature // need to use commonJS require due to its dynamic runtime nature
return getDatasourcePlugin(plugin.name, plugin.jsUrl) return getDatasourcePlugin(
plugin.name,
plugin.jsUrl,
plugin.schema?.hash
)
} }
} }
}, },

View File

@ -352,13 +352,21 @@ exports.extractPluginTarball = async file => {
return { metadata, directory: path } return { metadata, directory: path }
} }
exports.getDatasourcePlugin = async (name, url) => { exports.getDatasourcePlugin = async (name, url, hash) => {
if (!fs.existsSync(DATASOURCE_PATH)) { if (!fs.existsSync(DATASOURCE_PATH)) {
fs.mkdirSync(DATASOURCE_PATH) fs.mkdirSync(DATASOURCE_PATH)
} }
const filename = join(DATASOURCE_PATH, name) const filename = join(DATASOURCE_PATH, name)
const metadataName = `${filename}.bbmetadata`
if (fs.existsSync(filename)) { if (fs.existsSync(filename)) {
const currentHash = fs.readFileSync(metadataName, "utf8")
// if hash is the same return the file, otherwise remove it and re-download
if (currentHash === hash) {
return require(filename) return require(filename)
} else {
console.log(`Updating plugin: ${name}`)
fs.unlinkSync(filename)
}
} }
const fullUrl = checkSlashesInUrl( const fullUrl = checkSlashesInUrl(
`${env.MINIO_URL}/${ObjectStoreBuckets.PLUGINS}/${url}` `${env.MINIO_URL}/${ObjectStoreBuckets.PLUGINS}/${url}`
@ -367,6 +375,7 @@ exports.getDatasourcePlugin = async (name, url) => {
if (response.status === 200) { if (response.status === 200) {
const content = await response.text() const content = await response.text()
fs.writeFileSync(filename, content) fs.writeFileSync(filename, content)
fs.writeFileSync(metadataName, hash)
require(filename) require(filename)
} else { } else {
throw new Error( throw new Error(

View File

@ -0,0 +1,32 @@
import path from "path"
import * as env from "./environment"
import chokidar from "chokidar"
import fs from "fs"
import { tenancy } from "@budibase/backend-core"
import { DEFAULT_TENANT_ID } from "@budibase/backend-core/constants"
import { processPlugin } from "./api/controllers/plugin"
export function watch() {
const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz")
chokidar
.watch(watchPath, {
ignored: "**/node_modules",
awaitWriteFinish: true,
})
.on("all", async (event: string, path: string) => {
// Sanity checks
if (!path?.endsWith(".tar.gz") || !fs.existsSync(path)) {
return
}
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
try {
const split = path.split("/")
const name = split[split.length - 1]
console.log("Importing plugin:", path)
await processPlugin({ name, path })
} catch (err) {
console.log("Failed to import plugin:", err)
}
})
})
}