Merge branch 'master' of github.com:budibase/budibase into revert-15637-revert-15612-smtp-types

This commit is contained in:
Sam Rose 2025-02-27 11:39:55 +00:00
commit 15ef247ba1
No known key found for this signature in database
10 changed files with 99 additions and 220 deletions

View File

@ -81,11 +81,11 @@ export const screenComponentErrorList = derived(
const errors: UIComponentError[] = []
function checkComponentErrors(component: Component, ancestors: string[]) {
errors.push(...getMissingAncestors(component, definitions, ancestors))
errors.push(
...getInvalidDatasources(screen, component, datasources, definitions)
)
errors.push(...getMissingRequiredSettings(component, definitions))
errors.push(...getMissingAncestors(component, definitions, ancestors))
for (const child of component._children || []) {
checkComponentErrors(child, [...ancestors, component._component])
@ -239,7 +239,10 @@ function getMissingAncestors(
ancestors: string[]
): UIComponentError[] {
const definition = definitions[component._component]
if (ancestors.some(a => !a.startsWith(BudibasePrefix))) {
// We don't have a way to know what components are used within a plugin component
return []
}
if (!definition?.requiredAncestors?.length) {
return []
}

View File

@ -1,142 +0,0 @@
import ClientApp from "./components/ClientApp.svelte"
import UpdatingApp from "./components/UpdatingApp.svelte"
import {
builderStore,
appStore,
blockStore,
componentStore,
environmentStore,
dndStore,
eventStore,
hoverStore,
stateStore,
routeStore,
} from "./stores"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
import { get } from "svelte/store"
import { initWebsocket } from "./websocket.js"
// Provide svelte and svelte/internal as globals for custom components
import * as svelte from "svelte"
import * as internal from "svelte/internal"
window.svelte_internal = internal
window.svelte = svelte
// Initialise spectrum icons
loadSpectrumIcons()
let app
const loadBudibase = async () => {
// Update builder store with any builder flags
builderStore.set({
...get(builderStore),
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"],
theme: window["##BUDIBASE_PREVIEW_THEME##"],
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
location: window["##BUDIBASE_LOCATION##"],
snippets: window["##BUDIBASE_SNIPPETS##"],
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
})
// Set app ID - this window flag is set by both the preview and the real
// server rendered app HTML
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
// Set the flag used to determine if the app is being loaded via an iframe
appStore.actions.setAppEmbedded(
window["##BUDIBASE_APP_EMBEDDED##"] === "true"
)
if (window.MIGRATING_APP) {
new UpdatingApp({
target: window.document.body,
})
return
}
// Fetch environment info
if (!get(environmentStore)?.loaded) {
await environmentStore.actions.fetchEnvironment()
}
// Register handler for runtime events from the builder
window.handleBuilderRuntimeEvent = (type, data) => {
if (!window["##BUDIBASE_IN_BUILDER##"]) {
return
}
if (type === "event-completed") {
eventStore.actions.resolveEvent(data)
} else if (type === "eject-block") {
const block = blockStore.actions.getBlock(data)
block?.eject()
} else if (type === "dragging-new-component") {
const { dragging, component } = data
if (dragging) {
const definition =
componentStore.actions.getComponentDefinition(component)
dndStore.actions.startDraggingNewComponent({ component, definition })
} else {
dndStore.actions.reset()
}
} else if (type === "request-context") {
const { selectedComponentInstance, screenslotInstance } =
get(componentStore)
const instance = selectedComponentInstance || screenslotInstance
const context = instance?.getDataContext()
let stringifiedContext = null
try {
stringifiedContext = JSON.stringify(context)
} catch (error) {
// Ignore - invalid context
}
eventStore.actions.dispatchEvent("provide-context", {
context: stringifiedContext,
})
} else if (type === "hover-component") {
hoverStore.actions.hoverComponent(data, false)
} else if (type === "builder-meta") {
builderStore.actions.setMetadata(data)
} else if (type === "builder-state") {
const [[key, value]] = Object.entries(data)
stateStore.actions.setValue(key, value)
} else if (type === "builder-url-test-data") {
const { route, testValue } = data
routeStore.actions.setTestUrlParams(route, testValue)
}
}
// Register any custom components
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
componentStore.actions.registerCustomComponent(component)
})
}
// Make a callback available for custom component bundles to register
// themselves at runtime
window.registerCustomComponent =
componentStore.actions.registerCustomComponent
// Initialise websocket
initWebsocket()
// Create app if one hasn't been created yet
if (!app) {
app = new ClientApp({
target: window.document.body,
})
}
}
// Attach to window so the HTML template can call this when it loads
window.loadBudibase = loadBudibase

@ -1 +1 @@
Subproject commit 45f5673d5e5ab3c22deb6663cea2e31a628aa133
Subproject commit e3843dd4eaced68ae063355b77df200dbc789c98

View File

@ -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"
)

View File

@ -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"

View File

@ -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<Plugin[]> {
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
}

View File

@ -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<Plugin[]> {
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
}

View File

@ -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,31 +8,31 @@ 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<PluginUpload> => {
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, "package.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(
`Unable to process schema.json/package.json in plugin. ${err.message}`
`Unable to process schema.json/package.json in plugin. ${err.message}`,
{ cause: err }
)
}
return { metadata, directory: path }
return { metadata: { package: pkg, schema }, directory: path }
}
async function getPluginImpl(path: string, plugin: Plugin) {

View File

@ -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)

View File

@ -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
}