Merge remote-tracking branch 'origin/master' into feat/automation-ux
This commit is contained in:
commit
c36f7c61c0
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.14.5",
|
||||
"version": "2.14.8",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b23fb3b17961fb04badd9487913a683fcf26dbe6
|
||||
Subproject commit 319c8499e7c3d33fbb96cf4d73a922690709686c
|
|
@ -19,6 +19,8 @@ import { WriteStream, ReadStream } from "fs"
|
|||
import { newid } from "../../docIds/newid"
|
||||
import { DDInstrumentedDatabase } from "../instrumentation"
|
||||
|
||||
const DATABASE_NOT_FOUND = "Database does not exist."
|
||||
|
||||
function buildNano(couchInfo: { url: string; cookie: string }) {
|
||||
return Nano({
|
||||
url: couchInfo.url,
|
||||
|
@ -31,6 +33,8 @@ function buildNano(couchInfo: { url: string; cookie: string }) {
|
|||
})
|
||||
}
|
||||
|
||||
type DBCall<T> = () => Promise<T>
|
||||
|
||||
export function DatabaseWithConnection(
|
||||
dbName: string,
|
||||
connection: string,
|
||||
|
@ -78,7 +82,11 @@ export class DatabaseImpl implements Database {
|
|||
return this.instanceNano || DatabaseImpl.nano
|
||||
}
|
||||
|
||||
async checkSetup() {
|
||||
private getDb() {
|
||||
return this.nano().db.use(this.name)
|
||||
}
|
||||
|
||||
private async checkAndCreateDb() {
|
||||
let shouldCreate = !this.pouchOpts?.skip_setup
|
||||
// check exists in a lightweight fashion
|
||||
let exists = await this.exists()
|
||||
|
@ -95,14 +103,22 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
}
|
||||
}
|
||||
return this.nano().db.use(this.name)
|
||||
return this.getDb()
|
||||
}
|
||||
|
||||
private async updateOutput(fnc: any) {
|
||||
// this function fetches the DB and handles if DB creation is needed
|
||||
private async performCall<T>(
|
||||
call: (db: Nano.DocumentScope<any>) => Promise<DBCall<T>> | DBCall<T>
|
||||
): Promise<any> {
|
||||
const db = this.getDb()
|
||||
const fnc = await call(db)
|
||||
try {
|
||||
return await fnc()
|
||||
} catch (err: any) {
|
||||
if (err.statusCode) {
|
||||
if (err.statusCode === 404 && err.reason === DATABASE_NOT_FOUND) {
|
||||
await this.checkAndCreateDb()
|
||||
return await this.performCall(call)
|
||||
} else if (err.statusCode) {
|
||||
err.status = err.statusCode
|
||||
}
|
||||
throw err
|
||||
|
@ -110,11 +126,12 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
|
||||
async get<T extends Document>(id?: string): Promise<T> {
|
||||
const db = await this.checkSetup()
|
||||
return this.performCall(db => {
|
||||
if (!id) {
|
||||
throw new Error("Unable to get doc without a valid _id.")
|
||||
}
|
||||
return this.updateOutput(() => db.get(id))
|
||||
return () => db.get(id)
|
||||
})
|
||||
}
|
||||
|
||||
async getMultiple<T extends Document>(
|
||||
|
@ -147,7 +164,7 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
|
||||
async remove(idOrDoc: string | Document, rev?: string) {
|
||||
const db = await this.checkSetup()
|
||||
return this.performCall(db => {
|
||||
let _id: string
|
||||
let _rev: string
|
||||
|
||||
|
@ -162,7 +179,8 @@ export class DatabaseImpl implements Database {
|
|||
if (!_id || !_rev) {
|
||||
throw new Error("Unable to remove doc without a valid _id and _rev.")
|
||||
}
|
||||
return this.updateOutput(() => db.destroy(_id, _rev))
|
||||
return () => db.destroy(_id, _rev)
|
||||
})
|
||||
}
|
||||
|
||||
async post(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||
|
@ -176,7 +194,7 @@ export class DatabaseImpl implements Database {
|
|||
if (!document._id) {
|
||||
throw new Error("Cannot store document without _id field.")
|
||||
}
|
||||
const db = await this.checkSetup()
|
||||
return this.performCall(async db => {
|
||||
if (!document.createdAt) {
|
||||
document.createdAt = new Date().toISOString()
|
||||
}
|
||||
|
@ -193,28 +211,32 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
}
|
||||
}
|
||||
return this.updateOutput(() => db.insert(document))
|
||||
return () => db.insert(document)
|
||||
})
|
||||
}
|
||||
|
||||
async bulkDocs(documents: AnyDocument[]) {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.bulk({ docs: documents }))
|
||||
return this.performCall(db => {
|
||||
return () => db.bulk({ docs: documents })
|
||||
})
|
||||
}
|
||||
|
||||
async allDocs<T extends Document>(
|
||||
params: DatabaseQueryOpts
|
||||
): Promise<AllDocsResponse<T>> {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.list(params))
|
||||
return this.performCall(db => {
|
||||
return () => db.list(params)
|
||||
})
|
||||
}
|
||||
|
||||
async query<T extends Document>(
|
||||
viewName: string,
|
||||
params: DatabaseQueryOpts
|
||||
): Promise<AllDocsResponse<T>> {
|
||||
const db = await this.checkSetup()
|
||||
return this.performCall(db => {
|
||||
const [database, view] = viewName.split("/")
|
||||
return this.updateOutput(() => db.view(database, view, params))
|
||||
return () => db.view(database, view, params)
|
||||
})
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
|
@ -231,8 +253,9 @@ export class DatabaseImpl implements Database {
|
|||
}
|
||||
|
||||
async compact() {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.compact())
|
||||
return this.performCall(db => {
|
||||
return () => db.compact()
|
||||
})
|
||||
}
|
||||
|
||||
// All below functions are in-frequently called, just utilise PouchDB
|
||||
|
|
|
@ -31,13 +31,6 @@ export class DDInstrumentedDatabase implements Database {
|
|||
})
|
||||
}
|
||||
|
||||
checkSetup(): Promise<DocumentScope<any>> {
|
||||
return tracer.trace("db.checkSetup", span => {
|
||||
span?.addTags({ db_name: this.name })
|
||||
return this.db.checkSetup()
|
||||
})
|
||||
}
|
||||
|
||||
get<T extends Document>(id?: string | undefined): Promise<T> {
|
||||
return tracer.trace("db.get", span => {
|
||||
span?.addTags({ db_name: this.name, doc_id: id })
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
loading = false
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
export async function confirm() {
|
||||
loading = true
|
||||
if (!onConfirm || (await onConfirm()) !== keepOpen) {
|
||||
hide()
|
||||
|
|
|
@ -19,10 +19,15 @@
|
|||
export let lastStep
|
||||
|
||||
let syncAutomationsEnabled = $licensing.syncAutomationsEnabled
|
||||
let triggerAutomationRunEnabled = $licensing.triggerAutomationRunEnabled
|
||||
let collectBlockAllowedSteps = [TriggerStepID.APP, TriggerStepID.WEBHOOK]
|
||||
let selectedAction
|
||||
let actionVal
|
||||
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
||||
let lockedFeatures = [
|
||||
ActionStepID.COLLECT,
|
||||
ActionStepID.TRIGGER_AUTOMATION_RUN,
|
||||
]
|
||||
|
||||
$: collectBlockExists = checkForCollectStep($selectedAutomation)
|
||||
|
||||
|
@ -36,6 +41,10 @@
|
|||
disabled: !lastStep || !syncAutomationsEnabled || collectBlockExists,
|
||||
message: collectDisabledMessage(),
|
||||
},
|
||||
TRIGGER_AUTOMATION_RUN: {
|
||||
disabled: !triggerAutomationRunEnabled,
|
||||
message: collectDisabledMessage(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +158,7 @@
|
|||
<div class="item-body">
|
||||
<Icon name={action.icon} />
|
||||
<Body size="XS">{action.name}</Body>
|
||||
{#if isDisabled && !syncAutomationsEnabled && action.stepId === ActionStepID.COLLECT}
|
||||
{#if isDisabled && !syncAutomationsEnabled && !triggerAutomationRunEnabled && lockedFeatures.includes(action.stepId)}
|
||||
<div class="tag-color">
|
||||
<Tags>
|
||||
<Tag icon="LockClosed">Premium</Tag>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
import CodeEditorModal from "./CodeEditorModal.svelte"
|
||||
import QuerySelector from "./QuerySelector.svelte"
|
||||
import QueryParamSelector from "./QueryParamSelector.svelte"
|
||||
import AutomationSelector from "./AutomationSelector.svelte"
|
||||
import CronBuilder from "./CronBuilder.svelte"
|
||||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||
|
@ -51,7 +52,6 @@
|
|||
export let testData
|
||||
export let schemaProperties
|
||||
export let isTestModal = false
|
||||
|
||||
let webhookModal
|
||||
let drawer
|
||||
let fillWidth = true
|
||||
|
@ -101,7 +101,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onChange = Utils.sequential(async (e, key) => {
|
||||
// We need to cache the schema as part of the definition because it is
|
||||
// used in the server to detect relationships. It would be far better to
|
||||
|
@ -145,6 +144,7 @@
|
|||
if (!block || !automation) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Find previous steps to the selected one
|
||||
let allSteps = [...automation.steps]
|
||||
|
||||
|
@ -156,22 +156,96 @@
|
|||
// Extract all outputs from all previous steps as available bindingsx§x
|
||||
let bindings = []
|
||||
let loopBlockCount = 0
|
||||
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
|
||||
const runtimeBinding = determineRuntimeBinding(name, idx, isLoopBlock)
|
||||
const categoryName = determineCategoryName(idx, isLoopBlock, bindingName)
|
||||
|
||||
bindings.push(
|
||||
createBindingObject(
|
||||
name,
|
||||
value,
|
||||
icon,
|
||||
idx,
|
||||
loopBlockCount,
|
||||
isLoopBlock,
|
||||
runtimeBinding,
|
||||
categoryName,
|
||||
bindingName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const determineRuntimeBinding = (name, idx, isLoopBlock) => {
|
||||
let runtimeName
|
||||
|
||||
/* Begin special cases for generating custom schemas based on triggers */
|
||||
if (idx === 0 && automation.trigger?.event === "app:trigger") {
|
||||
return `trigger.fields.${name}`
|
||||
}
|
||||
|
||||
if (
|
||||
(idx === 0 && automation.trigger?.event === "row:update") ||
|
||||
automation.trigger?.event === "row:save"
|
||||
) {
|
||||
if (name !== "id" && name !== "revision") return `trigger.row.${name}`
|
||||
}
|
||||
/* End special cases for generating custom schemas based on triggers */
|
||||
|
||||
if (isLoopBlock) {
|
||||
runtimeName = `loop.${name}`
|
||||
} else if (block.name.startsWith("JS")) {
|
||||
runtimeName = `steps[${idx - loopBlockCount}].${name}`
|
||||
} else {
|
||||
runtimeName = `steps.${idx - loopBlockCount}.${name}`
|
||||
}
|
||||
return idx === 0 ? `trigger.${name}` : runtimeName
|
||||
}
|
||||
|
||||
const determineCategoryName = (idx, isLoopBlock, bindingName) => {
|
||||
if (idx === 0) return "Trigger outputs"
|
||||
if (isLoopBlock) return "Loop Outputs"
|
||||
return bindingName
|
||||
? `${bindingName} outputs`
|
||||
: `Step ${idx - loopBlockCount} outputs`
|
||||
}
|
||||
|
||||
const createBindingObject = (
|
||||
name,
|
||||
value,
|
||||
icon,
|
||||
idx,
|
||||
loopBlockCount,
|
||||
isLoopBlock,
|
||||
runtimeBinding,
|
||||
categoryName,
|
||||
bindingName
|
||||
) => {
|
||||
return {
|
||||
readableBinding: bindingName
|
||||
? `${bindingName}.${name}`
|
||||
: runtimeBinding,
|
||||
runtimeBinding,
|
||||
type: value.type,
|
||||
description: value.description,
|
||||
icon,
|
||||
category: categoryName,
|
||||
display: {
|
||||
type: value.type,
|
||||
name,
|
||||
rank: isLoopBlock ? idx + 1 : idx - loopBlockCount,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for (let idx = 0; idx < blockIdx; idx++) {
|
||||
let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
|
||||
let isLoopBlock =
|
||||
allSteps[idx]?.stepId === ActionStepID.LOOP &&
|
||||
allSteps.find(x => x.blockToLoop === block.id)
|
||||
allSteps.some(x => x.blockToLoop === block.id)
|
||||
let schema = cloneDeep(allSteps[idx]?.schema?.outputs?.properties) ?? {}
|
||||
let bindingName =
|
||||
automation.stepNames?.[allSteps[idx - loopBlockCount].id]
|
||||
|
||||
// If the previous block was a loop block, decrement the index so the following
|
||||
// steps are in the correct order
|
||||
if (wasLoopBlock) {
|
||||
loopBlockCount++
|
||||
continue
|
||||
}
|
||||
|
||||
let schema = allSteps[idx]?.schema?.outputs?.properties ?? {}
|
||||
|
||||
// If its a Loop Block, we need to add this custom schema
|
||||
if (isLoopBlock) {
|
||||
schema = {
|
||||
currentItem: {
|
||||
|
@ -180,54 +254,45 @@
|
|||
},
|
||||
}
|
||||
}
|
||||
const outputs = Object.entries(schema)
|
||||
let bindingIcon = ""
|
||||
let bindingRank = 0
|
||||
if (idx === 0) {
|
||||
bindingIcon = automation.trigger.icon
|
||||
} else if (isLoopBlock) {
|
||||
bindingIcon = "Reuse"
|
||||
bindingRank = idx + 1
|
||||
} else {
|
||||
bindingIcon = allSteps[idx].icon
|
||||
bindingRank = idx - loopBlockCount
|
||||
}
|
||||
let bindingName =
|
||||
automation.stepNames?.[allSteps[idx - loopBlockCount].id]
|
||||
bindings = bindings.concat(
|
||||
outputs.map(([name, value]) => {
|
||||
let runtimeName = isLoopBlock
|
||||
? `loop.${name}`
|
||||
: block.name.startsWith("JS")
|
||||
? `steps[${idx - loopBlockCount}].${name}`
|
||||
: `steps.${idx - loopBlockCount}.${name}`
|
||||
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
||||
|
||||
let categoryName
|
||||
if (idx === 0) {
|
||||
categoryName = "Trigger outputs"
|
||||
} else if (isLoopBlock) {
|
||||
categoryName = "Loop Outputs"
|
||||
} else if (bindingName) {
|
||||
categoryName = `${bindingName} outputs`
|
||||
} else {
|
||||
categoryName = `Step ${idx - loopBlockCount} outputs`
|
||||
if (idx === 0 && automation.trigger?.event === "app:trigger") {
|
||||
schema = Object.fromEntries(
|
||||
Object.keys(automation.trigger.inputs.fields || []).map(key => [
|
||||
key,
|
||||
{ type: automation.trigger.inputs.fields[key] },
|
||||
])
|
||||
)
|
||||
}
|
||||
if (
|
||||
(idx === 0 && automation.trigger.event === "row:update") ||
|
||||
(idx === 0 && automation.trigger.event === "row:save")
|
||||
) {
|
||||
let table = $tables.list.find(
|
||||
table => table._id === automation.trigger.inputs.tableId
|
||||
)
|
||||
// We want to generate our own schema for the bindings from the table schema itself
|
||||
for (const key in table?.schema) {
|
||||
schema[key] = {
|
||||
type: table.schema[key].type,
|
||||
}
|
||||
}
|
||||
// remove the original binding
|
||||
delete schema.row
|
||||
}
|
||||
let icon =
|
||||
idx === 0
|
||||
? automation.trigger.icon
|
||||
: isLoopBlock
|
||||
? "Reuse"
|
||||
: allSteps[idx].icon
|
||||
|
||||
if (wasLoopBlock) {
|
||||
loopBlockCount++
|
||||
continue
|
||||
}
|
||||
|
||||
return {
|
||||
readableBinding: bindingName ? `${bindingName}.${name}` : runtime,
|
||||
runtimeBinding: runtime,
|
||||
type: value.type,
|
||||
description: value.description,
|
||||
icon: bindingIcon,
|
||||
category: categoryName,
|
||||
display: {
|
||||
type: value.type,
|
||||
name: name,
|
||||
rank: bindingRank,
|
||||
},
|
||||
}
|
||||
})
|
||||
Object.entries(schema).forEach(([name, value]) =>
|
||||
addBinding(name, value, icon, idx, isLoopBlock, bindingName)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -245,10 +310,8 @@
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
function lookForFilters(properties) {
|
||||
if (!properties) {
|
||||
return []
|
||||
|
@ -286,7 +349,8 @@
|
|||
value.customType !== "code" &&
|
||||
value.customType !== "queryParams" &&
|
||||
value.customType !== "cron" &&
|
||||
value.customType !== "triggerSchema"
|
||||
value.customType !== "triggerSchema" &&
|
||||
value.customType !== "automationFields"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -421,6 +485,12 @@
|
|||
on:change={e => onChange(e, key)}
|
||||
value={inputData[key]}
|
||||
/>
|
||||
{:else if value.customType === "automationFields"}
|
||||
<AutomationSelector
|
||||
on:change={e => onChange(e, key)}
|
||||
value={inputData[key]}
|
||||
{bindings}
|
||||
/>
|
||||
{:else if value.customType === "queryParams"}
|
||||
<QueryParamSelector
|
||||
on:change={e => onChange(e, key)}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<script>
|
||||
import { Select, Label } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { automationStore, selectedAutomation } from "builderStore"
|
||||
import { TriggerStepID } from "constants/backend/automations"
|
||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value
|
||||
export let bindings = []
|
||||
const onChangeAutomation = e => {
|
||||
value.automationId = e.detail
|
||||
dispatch("change", value)
|
||||
}
|
||||
|
||||
const onChange = (e, field) => {
|
||||
value[field] = e.detail
|
||||
dispatch("change", value)
|
||||
}
|
||||
$: if (value?.automationId == null) value = { automationId: "" }
|
||||
|
||||
$: automationFields =
|
||||
$automationStore.automations.find(
|
||||
automation => automation._id === value?.automationId
|
||||
)?.definition?.trigger?.inputs?.fields || []
|
||||
|
||||
$: filteredAutomations = $automationStore.automations.filter(
|
||||
automation =>
|
||||
automation.definition.trigger.stepId === TriggerStepID.APP &&
|
||||
automation._id !== $selectedAutomation._id
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="schema-field">
|
||||
<Label>Automation</Label>
|
||||
<div class="field-width">
|
||||
<Select
|
||||
on:change={onChangeAutomation}
|
||||
value={value.automationId}
|
||||
options={filteredAutomations}
|
||||
getOptionValue={automation => automation._id}
|
||||
getOptionLabel={automation => automation.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if Object.keys(automationFields)}
|
||||
{#each Object.keys(automationFields) as field}
|
||||
<div class="schema-field">
|
||||
<Label>{field}</Label>
|
||||
<div class="field-width">
|
||||
<DrawerBindableInput
|
||||
panel={AutomationBindingPanel}
|
||||
extraThin
|
||||
value={value[field]}
|
||||
on:change={e => onChange(e, field)}
|
||||
type="string"
|
||||
{bindings}
|
||||
fillWidth={true}
|
||||
updateOnChange={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.field-width {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.schema-field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.schema-field :global(label) {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
|
@ -13,6 +13,7 @@
|
|||
const appPrefix = "/app"
|
||||
let touched = false
|
||||
let error
|
||||
let modal
|
||||
|
||||
$: appUrl = screenUrl
|
||||
? `${window.location.origin}${appPrefix}${screenUrl}`
|
||||
|
@ -50,6 +51,7 @@
|
|||
</script>
|
||||
|
||||
<ModalContent
|
||||
bind:this={modal}
|
||||
size="M"
|
||||
title={"Screen details"}
|
||||
{confirmText}
|
||||
|
@ -58,6 +60,7 @@
|
|||
cancelText={"Back"}
|
||||
disabled={!screenUrl || error || !touched}
|
||||
>
|
||||
<form on:submit|preventDefault={() => modal.confirm()}>
|
||||
<Input
|
||||
label="Enter a URL for the new screen"
|
||||
{error}
|
||||
|
@ -67,6 +70,7 @@
|
|||
<div class="app-server" title={appUrl}>
|
||||
{appUrl}
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
stepAction("addStep")
|
||||
}}
|
||||
>
|
||||
Add Step
|
||||
Add step
|
||||
</ActionButton>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
@ -21,6 +21,7 @@ export const ActionStepID = {
|
|||
QUERY_ROWS: "QUERY_ROWS",
|
||||
LOOP: "LOOP",
|
||||
COLLECT: "COLLECT",
|
||||
TRIGGER_AUTOMATION_RUN: "TRIGGER_AUTOMATION_RUN",
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: "discord",
|
||||
slack: "slack",
|
||||
|
|
|
@ -125,6 +125,10 @@ export const createLicensingStore = () => {
|
|||
const syncAutomationsEnabled = license.features.includes(
|
||||
Constants.Features.SYNC_AUTOMATIONS
|
||||
)
|
||||
const triggerAutomationRunEnabled = license.features.includes(
|
||||
Constants.Features.TRIGGER_AUTOMATION_RUN
|
||||
)
|
||||
|
||||
const perAppBuildersEnabled = license.features.includes(
|
||||
Constants.Features.APP_BUILDERS
|
||||
)
|
||||
|
@ -147,6 +151,7 @@ export const createLicensingStore = () => {
|
|||
auditLogsEnabled,
|
||||
enforceableSSO,
|
||||
syncAutomationsEnabled,
|
||||
triggerAutomationRunEnabled,
|
||||
isViewPermissionsEnabled,
|
||||
perAppBuildersEnabled,
|
||||
}
|
||||
|
|
|
@ -6093,15 +6093,44 @@
|
|||
"key": "steps",
|
||||
"nested": true,
|
||||
"labelHidden": true,
|
||||
"resetOn": [
|
||||
"dataSource",
|
||||
"actionType"
|
||||
],
|
||||
"defaultValue": [
|
||||
{}
|
||||
]
|
||||
"resetOn": ["dataSource", "actionType"],
|
||||
"defaultValue": [{}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tag": "style",
|
||||
"type": "select",
|
||||
"label": "Size",
|
||||
"key": "size",
|
||||
"options": [
|
||||
{
|
||||
"label": "Medium",
|
||||
"value": "spectrum--medium"
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": "spectrum--large"
|
||||
}
|
||||
],
|
||||
"defaultValue": "spectrum--medium"
|
||||
},
|
||||
{
|
||||
"tag": "style",
|
||||
"type": "select",
|
||||
"label": "Button position",
|
||||
"key": "buttonPosition",
|
||||
"options": [
|
||||
{
|
||||
"label": "Bottom",
|
||||
"value": "bottom"
|
||||
},
|
||||
{
|
||||
"label": "Top",
|
||||
"value": "top"
|
||||
}
|
||||
],
|
||||
"defaultValue": "bottom"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
export let noRowsMessage
|
||||
export let steps
|
||||
export let dataSource
|
||||
export let buttonPosition = "bottom"
|
||||
export let size
|
||||
|
||||
const { fetchDatasourceSchema } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
@ -127,6 +129,7 @@
|
|||
type="form"
|
||||
context="form"
|
||||
props={{
|
||||
size,
|
||||
dataSource,
|
||||
actionType: actionType === "Create" ? "Create" : "Update",
|
||||
readonly: actionType === "View",
|
||||
|
@ -154,8 +157,33 @@
|
|||
size: "shrink",
|
||||
}}
|
||||
>
|
||||
<BlockComponent type="container" order={0}>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "column",
|
||||
gap: "S",
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent type="heading" props={{ text: step.title }} />
|
||||
{#if buttonPosition === "top"}
|
||||
<BlockComponent
|
||||
type="buttongroup"
|
||||
props={{ buttons: step.buttons }}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
<BlockComponent type="text" props={{ text: step.desc }} order={1} />
|
||||
<BlockComponent type="container" order={2}>
|
||||
|
@ -176,16 +204,13 @@
|
|||
{/each}
|
||||
</div>
|
||||
</BlockComponent>
|
||||
{#if buttonPosition === "bottom"}
|
||||
<BlockComponent
|
||||
type="buttongroup"
|
||||
props={{ buttons: step.buttons }}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-top": "16px",
|
||||
},
|
||||
}}
|
||||
order={3}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{/each}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8c466d6ef2a0c09b843ef63276793ab5af2e96f7
|
||||
Subproject commit 9d80daaa5b79da68730d6c5f497f629c47a78ef8
|
|
@ -0,0 +1,12 @@
|
|||
const actual = jest.requireActual("@budibase/pro")
|
||||
const pro = {
|
||||
...actual,
|
||||
features: {
|
||||
...actual.features,
|
||||
isTriggerAutomationRunEnabled: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export = pro
|
|
@ -15,6 +15,7 @@ import * as delay from "./steps/delay"
|
|||
import * as queryRow from "./steps/queryRows"
|
||||
import * as loop from "./steps/loop"
|
||||
import * as collect from "./steps/collect"
|
||||
import * as triggerAutomationRun from "./steps/triggerAutomationRun"
|
||||
import env from "../environment"
|
||||
import {
|
||||
AutomationStepSchema,
|
||||
|
@ -41,6 +42,7 @@ const ACTION_IMPLS: Record<
|
|||
FILTER: filter.run,
|
||||
QUERY_ROWS: queryRow.run,
|
||||
COLLECT: collect.run,
|
||||
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.run,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.run,
|
||||
slack: slack.run,
|
||||
|
@ -62,6 +64,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
|
|||
QUERY_ROWS: queryRow.definition,
|
||||
LOOP: loop.definition,
|
||||
COLLECT: collect.definition,
|
||||
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.definition,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.definition,
|
||||
slack: slack.definition,
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
AutomationActionStepId,
|
||||
AutomationStepSchema,
|
||||
AutomationStepInput,
|
||||
AutomationStepType,
|
||||
AutomationIOType,
|
||||
AutomationResults,
|
||||
Automation,
|
||||
AutomationCustomIOType,
|
||||
} from "@budibase/types"
|
||||
import * as triggers from "../triggers"
|
||||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import { features } from "@budibase/pro"
|
||||
|
||||
export const definition: AutomationStepSchema = {
|
||||
name: "Trigger an automation",
|
||||
tagline: "Triggers an automation synchronously",
|
||||
icon: "Sync",
|
||||
description: "Triggers an automation synchronously",
|
||||
type: AutomationStepType.ACTION,
|
||||
internal: true,
|
||||
features: {},
|
||||
stepId: AutomationActionStepId.TRIGGER_AUTOMATION_RUN,
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {
|
||||
automation: {
|
||||
type: AutomationIOType.OBJECT,
|
||||
properties: {
|
||||
automationId: {
|
||||
type: AutomationIOType.STRING,
|
||||
customType: AutomationCustomIOType.AUTOMATION,
|
||||
},
|
||||
},
|
||||
customType: AutomationCustomIOType.AUTOMATION_FIELDS,
|
||||
title: "automatioFields",
|
||||
required: ["automationId"],
|
||||
},
|
||||
timeout: {
|
||||
type: AutomationIOType.NUMBER,
|
||||
title: "Timeout (ms)",
|
||||
},
|
||||
},
|
||||
required: ["automationId"],
|
||||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
success: {
|
||||
type: AutomationIOType.BOOLEAN,
|
||||
description: "Whether the automation was successful",
|
||||
},
|
||||
value: {
|
||||
type: AutomationIOType.OBJECT,
|
||||
description: "Automation Result",
|
||||
},
|
||||
},
|
||||
required: ["success", "value"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export async function run({ inputs }: AutomationStepInput) {
|
||||
const { automationId, ...fieldParams } = inputs.automation
|
||||
|
||||
if (await features.isTriggerAutomationRunEnabled()) {
|
||||
if (!inputs.automation.automationId) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
} else {
|
||||
const db = context.getAppDB()
|
||||
let automation = await db.get<Automation>(inputs.automation.automationId)
|
||||
|
||||
const response: AutomationResults = await triggers.externalTrigger(
|
||||
automation,
|
||||
{
|
||||
fields: { ...fieldParams },
|
||||
timeout: inputs.timeout * 1000 || 120000,
|
||||
},
|
||||
{ getResponses: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: response.steps,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
jest.spyOn(global.console, "error")
|
||||
|
||||
import * as setup from "./utilities"
|
||||
import * as automation from "../index"
|
||||
import { serverLogAutomation } from "../../tests/utilities/structures"
|
||||
|
||||
describe("Test triggering an automation from another automation", () => {
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeAll(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await automation.shutdown()
|
||||
setup.afterAll()
|
||||
})
|
||||
|
||||
it("should trigger an other server log automation", async () => {
|
||||
let automation = serverLogAutomation()
|
||||
let newAutomation = await config.createAutomation(automation)
|
||||
|
||||
const inputs: any = {
|
||||
automation: { automationId: newAutomation._id, timeout: 12000 },
|
||||
}
|
||||
const res = await setup.runStep(
|
||||
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||
inputs
|
||||
)
|
||||
// Check if the SERVER_LOG step was successful
|
||||
expect(res.value[1].outputs.success).toBe(true)
|
||||
})
|
||||
|
||||
it("should fail gracefully if the automation id is incorrect", async () => {
|
||||
const inputs: any = { automation: { automationId: null, timeout: 12000 } }
|
||||
const res = await setup.runStep(
|
||||
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||
inputs
|
||||
)
|
||||
expect(res.success).toBe(false)
|
||||
})
|
||||
})
|
|
@ -21,6 +21,7 @@ import {
|
|||
Table,
|
||||
INTERNAL_TABLE_SOURCE_ID,
|
||||
TableSourceType,
|
||||
AutomationIOType,
|
||||
} from "@budibase/types"
|
||||
|
||||
const { BUILTIN_ROLE_IDS } = roles
|
||||
|
@ -153,6 +154,56 @@ export function basicAutomation(appId?: string): Automation {
|
|||
}
|
||||
}
|
||||
|
||||
export function serverLogAutomation(appId?: string): Automation {
|
||||
return {
|
||||
name: "My Automation",
|
||||
screenId: "kasdkfldsafkl",
|
||||
live: true,
|
||||
uiTree: {},
|
||||
definition: {
|
||||
trigger: {
|
||||
stepId: AutomationTriggerStepId.APP,
|
||||
name: "test",
|
||||
tagline: "test",
|
||||
icon: "test",
|
||||
description: "test",
|
||||
type: AutomationStepType.TRIGGER,
|
||||
id: "test",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {},
|
||||
},
|
||||
outputs: {
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
stepId: AutomationActionStepId.SERVER_LOG,
|
||||
name: "Backend log",
|
||||
tagline: "Console log a value in the backend",
|
||||
icon: "Monitoring",
|
||||
description: "Logs the given text to the server (using console.log)",
|
||||
internal: true,
|
||||
features: {
|
||||
LOOPING: true,
|
||||
},
|
||||
inputs: {
|
||||
text: "log statement",
|
||||
},
|
||||
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||
id: "y8lkZbeSe",
|
||||
type: AutomationStepType.ACTION,
|
||||
},
|
||||
],
|
||||
},
|
||||
type: "automation",
|
||||
appId: appId!,
|
||||
}
|
||||
}
|
||||
|
||||
export function loopAutomation(tableId: string, loopOpts?: any): Automation {
|
||||
if (!loopOpts) {
|
||||
loopOpts = {
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
"manifest": "node ./scripts/gen-collection-info.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/handlebars-helpers": "^0.11.11",
|
||||
"@budibase/handlebars-helpers": "^0.12.0",
|
||||
"dayjs": "^1.10.8",
|
||||
"handlebars": "^4.7.6",
|
||||
"lodash": "4.17.21",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"vm2": "^3.9.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const { atob } = require("../utilities")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
const cloneDeep = require("lodash.clonedeep")
|
||||
const { LITERAL_MARKER } = require("../helpers/constants")
|
||||
const { getHelperList } = require("./list")
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ export enum AutomationCustomIOType {
|
|||
TRIGGER_SCHEMA = "triggerSchema",
|
||||
CRON = "cron",
|
||||
WEBHOOK_URL = "webhookUrl",
|
||||
AUTOMATION = "automation",
|
||||
AUTOMATION_FIELDS = "automationFields",
|
||||
}
|
||||
|
||||
export enum AutomationTriggerStepId {
|
||||
|
@ -61,6 +63,7 @@ export enum AutomationActionStepId {
|
|||
LOOP = "LOOP",
|
||||
COLLECT = "COLLECT",
|
||||
OPENAI = "OPENAI",
|
||||
TRIGGER_AUTOMATION_RUN = "TRIGGER_AUTOMATION_RUN",
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord = "discord",
|
||||
slack = "slack",
|
||||
|
|
|
@ -121,7 +121,6 @@ export interface Database {
|
|||
name: string
|
||||
|
||||
exists(): Promise<boolean>
|
||||
checkSetup(): Promise<Nano.DocumentScope<any>>
|
||||
get<T extends Document>(id?: string): Promise<T>
|
||||
getMultiple<T extends Document>(
|
||||
ids: string[],
|
||||
|
|
|
@ -9,6 +9,7 @@ export enum Feature {
|
|||
BRANDING = "branding",
|
||||
SCIM = "scim",
|
||||
SYNC_AUTOMATIONS = "syncAutomations",
|
||||
TRIGGER_AUTOMATION_RUN = "triggerAutomationRun",
|
||||
APP_BUILDERS = "appBuilders",
|
||||
OFFLINE = "offline",
|
||||
EXPANDED_PUBLIC_API = "expandedPublicApi",
|
||||
|
|
|
@ -22,6 +22,7 @@ export enum LockName {
|
|||
QUOTA_USAGE_EVENT = "quota_usage_event",
|
||||
APP_MIGRATION = "app_migrations",
|
||||
PROCESS_AUTO_COLUMNS = "process_auto_columns",
|
||||
PROCESS_USER_INVITE = "process_user_invite",
|
||||
}
|
||||
|
||||
export type LockOptions = {
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
InviteUserRequest,
|
||||
InviteUsersRequest,
|
||||
InviteUsersResponse,
|
||||
LockName,
|
||||
LockType,
|
||||
MigrationType,
|
||||
SaveUserResponse,
|
||||
SearchUsersRequest,
|
||||
|
@ -27,6 +29,7 @@ import {
|
|||
platform,
|
||||
tenancy,
|
||||
db,
|
||||
locks,
|
||||
} from "@budibase/backend-core"
|
||||
import { checkAnyUserExists } from "../../../utilities/users"
|
||||
import { isEmailConfigured } from "../../../utilities/email"
|
||||
|
@ -380,9 +383,16 @@ export const inviteAccept = async (
|
|||
) => {
|
||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||
try {
|
||||
await locks.doWithLock(
|
||||
{
|
||||
type: LockType.AUTO_EXTEND,
|
||||
name: LockName.PROCESS_USER_INVITE,
|
||||
resource: inviteCode,
|
||||
systemLock: true,
|
||||
},
|
||||
async () => {
|
||||
// info is an extension of the user object that was stored by global
|
||||
const { email, info }: any = await cache.invite.getCode(inviteCode)
|
||||
await cache.invite.deleteCode(inviteCode)
|
||||
const { email, info } = await cache.invite.getCode(inviteCode)
|
||||
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
||||
let request: any = {
|
||||
firstName,
|
||||
|
@ -393,7 +403,7 @@ export const inviteAccept = async (
|
|||
roles: info.apps,
|
||||
tenantId: info.tenantId,
|
||||
}
|
||||
let builder: { global: boolean; apps?: string[] } = {
|
||||
const builder: { global: boolean; apps?: string[] } = {
|
||||
global: info?.builder?.global || false,
|
||||
}
|
||||
|
||||
|
@ -408,23 +418,25 @@ export const inviteAccept = async (
|
|||
}
|
||||
|
||||
const saved = await userSdk.db.save(request)
|
||||
const db = tenancy.getGlobalDB()
|
||||
const user = await db.get<User>(saved._id)
|
||||
await events.user.inviteAccepted(user)
|
||||
await events.user.inviteAccepted(saved)
|
||||
return saved
|
||||
})
|
||||
|
||||
await cache.invite.deleteCode(inviteCode)
|
||||
|
||||
ctx.body = {
|
||||
_id: user._id!,
|
||||
_rev: user._rev!,
|
||||
email: user.email,
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (err: any) {
|
||||
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
||||
// explicitly re-throw limit exceeded errors
|
||||
ctx.throw(400, err)
|
||||
}
|
||||
console.warn("Error inviting user", err)
|
||||
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
||||
ctx.throw(400, err || "Unable to create new user, invitation invalid.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@budibase/types": "^2.3.17",
|
||||
"@swc/core": "1.3.71",
|
||||
"@swc/jest": "0.2.27",
|
||||
"@trendyol/jest-testcontainers": "2.1.1",
|
||||
"@types/jest": "29.5.3",
|
||||
"@types/node-fetch": "2.6.4",
|
||||
|
@ -32,8 +34,6 @@
|
|||
"jest": "29.7.0",
|
||||
"prettier": "2.7.1",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"@swc/core": "1.3.71",
|
||||
"@swc/jest": "0.2.27",
|
||||
"timekeeper": "2.2.0",
|
||||
"ts-jest": "29.1.1",
|
||||
"ts-node": "10.8.1",
|
||||
|
@ -43,6 +43,7 @@
|
|||
"dependencies": {
|
||||
"@budibase/backend-core": "^2.3.17",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "2.6.7"
|
||||
"node-fetch": "2.6.7",
|
||||
"stripe": "^14.11.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -951,6 +951,13 @@
|
|||
resolved "https://registry.npmjs.org/@types/node/-/node-18.15.1.tgz"
|
||||
integrity sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==
|
||||
|
||||
"@types/node@>=8.1.0":
|
||||
version "20.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
|
||||
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/stack-utils@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz"
|
||||
|
@ -4549,6 +4556,14 @@ strip-json-comments@^3.1.1:
|
|||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
stripe@^14.11.0:
|
||||
version "14.11.0"
|
||||
resolved "https://registry.yarnpkg.com/stripe/-/stripe-14.11.0.tgz#1df63c31bcff3b136457c2b7584f917509e8030c"
|
||||
integrity sha512-NmFEkDC0PldP7CQtdPgKs5dVZA/pF+IepldbmB+Kk9B4d7EBkWnbANp0y+/zJcbRGul48s8hmQzeqNWUlWW0wg==
|
||||
dependencies:
|
||||
"@types/node" ">=8.1.0"
|
||||
qs "^6.11.0"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
|
||||
|
@ -4807,6 +4822,11 @@ uid2@0.0.x:
|
|||
resolved "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz"
|
||||
integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz"
|
||||
|
|
|
@ -3,26 +3,14 @@
|
|||
packages_to_remove="@budibase/backend-core @budibase/bbui @budibase/builder @budibase/cli @budibase/client @budibase/frontend-core @budibase/pro @budibase/sdk @budibase/server @budibase/shared-core @budibase/string-templates @budibase/types @budibase/worker"
|
||||
|
||||
package_json_path="$1"
|
||||
package_json=$(cat "$package_json_path")
|
||||
|
||||
process_package() {
|
||||
pkg_path="$1"
|
||||
package_json=$(cat "$pkg_path")
|
||||
has_changes=false
|
||||
|
||||
for package_name in $packages_to_remove; do
|
||||
if echo "$package_json" | jq -e --arg package_name "$package_name" '.dependencies | has($package_name)' > /dev/null; then
|
||||
package_json=$(echo "$package_json" | jq "del(.dependencies[\"$package_name\"])")
|
||||
has_changes=true
|
||||
fi
|
||||
jq "del(.dependencies[\"$package_name\"])" $pkg_path > tmp_file.json && mv tmp_file.json $pkg_path
|
||||
jq "del(.resolutions[\"$package_name\"])" $pkg_path > tmp_file.json && mv tmp_file.json $pkg_path
|
||||
done
|
||||
|
||||
if [ "$has_changes" = true ]; then
|
||||
echo "$package_json" > "$pkg_path"
|
||||
fi
|
||||
}
|
||||
|
||||
process_package "$package_json_path"
|
||||
|
||||
package_json=$(cat "$package_json_path")
|
||||
echo "$package_json" | jq "del(.resolutions)" > "$1"
|
||||
|
|
Loading…
Reference in New Issue