diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 99aef0287d..a5d7c1f100 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -35,10 +35,6 @@ exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR exports.SEPARATOR = SEPARATOR -function isDevApp(app) { - return app.appId.startsWith(exports.APP_DEV_PREFIX) -} - /** * If creating DB allDocs/query params with only a single top level ID this can be used, this * is usually the case as most of our docs are top level e.g. tables, automations, users and so on. @@ -62,6 +58,18 @@ function getDocParams(docType, docId = null, otherProps = {}) { } } +exports.isDevAppID = appId => { + return appId.startsWith(exports.APP_DEV_PREFIX) +} + +exports.isProdAppID = appId => { + return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId) +} + +function isDevApp(app) { + return exports.isDevAppID(app.appId) +} + /** * Given an app ID this will attempt to retrieve the tenant ID from it. * @return {null|string} The tenant ID found within the app ID. diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index cd4bfdc6e2..6befecd9ba 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -14,6 +14,7 @@ exports.Databases = { DEBOUNCE: "debounce", SESSIONS: "session", USER_CACHE: "users", + FLAGS: "flags", } exports.SEPARATOR = SEPARATOR diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js index e82eeff670..e5040c3c45 100644 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ b/packages/builder/cypress/integration/createAutomation.spec.js @@ -24,7 +24,7 @@ context("Create a automation", () => { }) // Create action - cy.contains("Action").click() + cy.contains("Internal").click() cy.contains("Create Row").click() cy.get(".setup").within(() => { cy.get(".spectrum-Picker-label").click() diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 4ad8e5287d..1a6f1d5b2b 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -23,6 +23,7 @@ process.env.MINIO_SECRET_KEY = "budibase" process.env.COUCH_DB_USER = "budibase" process.env.COUCH_DB_PASSWORD = "budibase" process.env.INTERNAL_API_KEY = "budibase" +process.env.ALLOW_DEV_AUTOMATIONS = 1 // Stop info logs polluting test outputs process.env.LOG_LEVEL = "error" diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index c372f27bb7..f8e7db04a0 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -78,8 +78,11 @@ const automationActions = store => ({ }, trigger: async ({ automation }) => { const { _id } = automation - const TRIGGER_AUTOMATION_URL = `/api/automations/${_id}/trigger` - return await api.post(TRIGGER_AUTOMATION_URL) + return await api.post(`/api/automations/${_id}/trigger`) + }, + test: async ({ automation }) => { + const { _id } = automation + return await api.post(`/api/automations/${_id}/test`) }, select: automation => { store.update(state => { diff --git a/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte b/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte index af5c9e449e..a04541cfad 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte @@ -14,15 +14,17 @@ disabled: hasTrigger, }, { - label: "Action", + label: "Internal", value: "ACTION", + internal: true, icon: "Actions", disabled: !hasTrigger, }, { - label: "Logic", - value: "LOGIC", - icon: "Filter", + label: "External", + value: "ACTION", + internal: false, + icon: "Extension", disabled: !hasTrigger, }, ] @@ -32,9 +34,13 @@ let popover let webhookModal $: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value + $: selectedInternal = + selectedIndex == null ? null : tabs[selectedIndex].internal $: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] $: blocks = sortBy(entry => entry[1].name)( Object.entries($automationStore.blockDefinitions[selectedTab] ?? {}) + ).filter( + entry => selectedInternal == null || entry[1].internal === selectedInternal ) function onChangeTab(idx) { diff --git a/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte b/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte index 3ba59f36a6..bf6c45074c 100644 --- a/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte +++ b/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte @@ -25,7 +25,7 @@ } async function testAutomation() { - const result = await automationStore.actions.trigger({ + const result = await automationStore.actions.test({ automation: $automationStore.selectedAutomation.automation, }) if (result.status === 200) { diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index cec5dc9a5d..27c542cf09 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@adobe/spectrum-css-workflow-icons@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4" - integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w== - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" @@ -28,94 +23,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@budibase/bbui@^0.9.117-alpha.2": - version "0.9.117" - resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.117.tgz#89a5e2d89e8f225d0b8357a582bb1c57ba95addb" - integrity sha512-OXNPizbHxM5Jh6q20NptCUvNXKjaMcJognUb4LWAXhpcfmNZ8Yt/+eijAhZhyp+lFMYMS3D1NvVWAGraH1Xh6A== - dependencies: - "@adobe/spectrum-css-workflow-icons" "^1.2.1" - "@spectrum-css/actionbutton" "^1.0.1" - "@spectrum-css/actiongroup" "^1.0.1" - "@spectrum-css/avatar" "^3.0.2" - "@spectrum-css/button" "^3.0.1" - "@spectrum-css/buttongroup" "^3.0.2" - "@spectrum-css/checkbox" "^3.0.2" - "@spectrum-css/dialog" "^3.0.1" - "@spectrum-css/divider" "^1.0.3" - "@spectrum-css/dropzone" "^3.0.2" - "@spectrum-css/fieldgroup" "^3.0.2" - "@spectrum-css/fieldlabel" "^3.0.1" - "@spectrum-css/icon" "^3.0.1" - "@spectrum-css/illustratedmessage" "^3.0.2" - "@spectrum-css/inputgroup" "^3.0.2" - "@spectrum-css/label" "^2.0.10" - "@spectrum-css/link" "^3.1.1" - "@spectrum-css/menu" "^3.0.1" - "@spectrum-css/modal" "^3.0.1" - "@spectrum-css/pagination" "^3.0.3" - "@spectrum-css/picker" "^1.0.1" - "@spectrum-css/popover" "^3.0.1" - "@spectrum-css/progressbar" "^1.0.2" - "@spectrum-css/progresscircle" "^1.0.2" - "@spectrum-css/radio" "^3.0.2" - "@spectrum-css/search" "^3.0.2" - "@spectrum-css/sidenav" "^3.0.2" - "@spectrum-css/statuslight" "^3.0.2" - "@spectrum-css/stepper" "^3.0.3" - "@spectrum-css/switch" "^1.0.2" - "@spectrum-css/table" "^3.0.1" - "@spectrum-css/tabs" "^3.0.1" - "@spectrum-css/tags" "^3.0.2" - "@spectrum-css/textfield" "^3.0.1" - "@spectrum-css/toast" "^3.0.1" - "@spectrum-css/tooltip" "^3.0.3" - "@spectrum-css/treeview" "^3.0.2" - "@spectrum-css/typography" "^3.0.1" - "@spectrum-css/underlay" "^2.0.9" - "@spectrum-css/vars" "^3.0.1" - dayjs "^1.10.4" - svelte-flatpickr "^3.1.0" - svelte-portal "^1.0.0" - -"@budibase/handlebars-helpers@^0.11.4": - version "0.11.5" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.5.tgz#e9cc90a44e94ad536992cf10906829b633e94bc5" - integrity sha512-ZxpyNtTHxS8Y+yTicbgWvYDAydooUSjOf3Y+wmTE2d4NpDgO0g0IjepLfZV+KASv9XBr//ylJdjE4hClX9NTFw== - dependencies: - array-sort "^1.0.0" - define-property "^2.0.2" - extend-shallow "^3.0.2" - "falsey" "^1.0.0" - for-in "^1.0.2" - get-object "^0.2.0" - get-value "^3.0.1" - handlebars "^4.7.7" - handlebars-utils "^1.0.6" - has-value "^2.0.2" - helper-date "^1.0.1" - helper-markdown "^1.0.0" - helper-md "^0.2.2" - html-tag "^2.0.0" - is-even "^1.0.0" - is-glob "^4.0.1" - kind-of "^6.0.3" - micromatch "^3.1.5" - relative "^3.0.2" - striptags "^3.1.1" - to-gfm-code-block "^0.1.1" - year "^0.2.1" - -"@budibase/string-templates@^0.9.117-alpha.2": - version "0.9.117" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.117.tgz#7106cc09497083128916ecee2e0208173e9fd27b" - integrity sha512-vaVS1EfiMFmRVErYM5ch/yNMN/OOcJhjvFeEmNDcjCfH9cw+yeA2d/rbZ4LedXgT4itMDvvybJkohqgD2JodYg== - dependencies: - "@budibase/handlebars-helpers" "^0.11.4" - dayjs "^1.10.4" - handlebars "^4.7.6" - handlebars-utils "^1.0.6" - lodash "^4.17.20" - "@rollup/plugin-alias@^3.1.5": version "3.1.5" resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-3.1.5.tgz#73356a3a1eab2e1e2fd952f9f53cd89fc740d952" @@ -157,46 +64,16 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@spectrum-css/actionbutton@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30" - integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ== - -"@spectrum-css/actiongroup@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f" - integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ== - -"@spectrum-css/avatar@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95" - integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ== - -"@spectrum-css/button@^3.0.1", "@spectrum-css/button@^3.0.3": +"@spectrum-css/button@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg== -"@spectrum-css/buttongroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa" - integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw== - "@spectrum-css/card@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b" integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg== -"@spectrum-css/checkbox@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb" - integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg== - -"@spectrum-css/dialog@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34" - integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ== - "@spectrum-css/divider@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385" @@ -204,56 +81,11 @@ dependencies: "@spectrum-css/vars" "^3.0.2" -"@spectrum-css/dropzone@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc" - integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw== - -"@spectrum-css/fieldgroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2" - integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ== - -"@spectrum-css/fieldlabel@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449" - integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ== - -"@spectrum-css/icon@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba" - integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw== - -"@spectrum-css/illustratedmessage@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0" - integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA== - -"@spectrum-css/inputgroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5" - integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg== - -"@spectrum-css/label@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001" - integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ== - -"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3": +"@spectrum-css/link@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993" integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg== -"@spectrum-css/menu@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d" - integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ== - -"@spectrum-css/modal@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f" - integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg== - "@spectrum-css/page@^3.0.1": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0" @@ -261,106 +93,11 @@ dependencies: "@spectrum-css/vars" "^3.0.2" -"@spectrum-css/pagination@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65" - integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw== - -"@spectrum-css/picker@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487" - integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w== - -"@spectrum-css/popover@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09" - integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ== - -"@spectrum-css/progressbar@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67" - integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q== - -"@spectrum-css/progresscircle@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c" - integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ== - -"@spectrum-css/radio@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640" - integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw== - -"@spectrum-css/search@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882" - integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA== - -"@spectrum-css/sidenav@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597" - integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg== - -"@spectrum-css/statuslight@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5" - integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w== - -"@spectrum-css/stepper@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.3.tgz#ae89846886431e3edeee060207b8f81540f73a34" - integrity sha512-prAD61ImlOTs9b6PfB3cB08x4lAfxtvnW+RZiTYky0E8GgZdrc/MfCkL5/oqQaIQUtyQv/3Lb7ELAf/0K8QTXw== - -"@spectrum-css/switch@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44" - integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ== - -"@spectrum-css/table@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf" - integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg== - -"@spectrum-css/tabs@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc" - integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg== - -"@spectrum-css/tags@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac" - integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw== - -"@spectrum-css/textfield@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631" - integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg== - -"@spectrum-css/toast@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f" - integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w== - -"@spectrum-css/tooltip@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281" - integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A== - -"@spectrum-css/treeview@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046" - integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw== - -"@spectrum-css/typography@^3.0.1", "@spectrum-css/typography@^3.0.2": +"@spectrum-css/typography@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38" integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA== -"@spectrum-css/underlay@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7" - integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA== - "@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999" @@ -474,42 +211,13 @@ apexcharts@^3.19.2, apexcharts@^3.22.1: svg.resize.js "^1.4.3" svg.select.js "^3.0.1" -argparse@^1.0.10, argparse@^1.0.7: +argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-sort@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" - integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== - dependencies: - default-compare "^1.0.0" - get-value "^2.0.6" - kind-of "^5.0.2" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -532,28 +240,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autolinker@~0.28.0: - version "0.28.1" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" - integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= - dependencies: - gulp-header "^1.7.1" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -569,19 +260,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -624,22 +302,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -744,21 +406,6 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -836,16 +483,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - clone@~0.1.9: version "0.1.19" resolved "https://registry.yarnpkg.com/clone/-/clone-0.1.19.tgz#613fb68639b26a494ac53253e15b1a6bd88ada85" @@ -860,14 +497,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -930,11 +559,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -950,18 +574,13 @@ concat-stream@^1.4.4: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-with-sourcemaps@*, concat-with-sourcemaps@^1.1.0: +concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== dependencies: source-map "^0.6.1" -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1198,42 +817,16 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date.js@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" - integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw== - dependencies: - debug "~3.1.0" - -dayjs@^1.10.4, dayjs@^1.10.5: +dayjs@^1.10.5: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - decimal.js@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1244,13 +837,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -default-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" - integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== - dependencies: - kind-of "^5.0.2" - deferred-leveldown@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-0.2.0.tgz#2cef1f111e1c57870d8bbb8af2650e587cd2f5b4" @@ -1265,28 +851,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1380,11 +944,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -ent@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -1510,53 +1069,11 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1567,11 +1084,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -"falsey@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d" - integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w== - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1587,26 +1099,11 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - flatpickr@^4.5.2: version "4.6.9" resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -1626,18 +1123,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -1685,26 +1170,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-object@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" - integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw= - dependencies: - is-number "^2.0.2" - isobject "^0.2.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -get-value@^3.0.0, get-value@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" - integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== - dependencies: - isobject "^3.0.1" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1729,35 +1194,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -gulp-header@^1.7.1: - version "1.8.12" - resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" - integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== - dependencies: - concat-with-sourcemaps "*" - lodash.template "^4.4.0" - through2 "^2.0.0" - -handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" - integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== - dependencies: - kind-of "^6.0.0" - typeof-article "^0.1.1" - -handlebars@^4.7.6, handlebars@^4.7.7: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.0" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -1791,52 +1227,6 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-value@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" - integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== - dependencies: - get-value "^3.0.0" - has-values "^2.0.1" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-values@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" - integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== - dependencies: - kind-of "^6.0.2" - has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1861,44 +1251,11 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -helper-date@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/helper-date/-/helper-date-1.0.1.tgz#12fedea3ad8e44a7ca4c4efb0ff4104a5120cffb" - integrity sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w== - dependencies: - date.js "^0.3.1" - handlebars-utils "^1.0.4" - moment "^2.18.1" - -helper-markdown@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/helper-markdown/-/helper-markdown-1.0.0.tgz#ee7e9fc554675007d37eb90f7853b13ce74f3e10" - integrity sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA== - dependencies: - handlebars-utils "^1.0.2" - highlight.js "^9.12.0" - remarkable "^1.7.1" - -helper-md@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" - integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8= - dependencies: - ent "^2.2.0" - extend-shallow "^2.0.1" - fs-exists-sync "^0.1.0" - remarkable "^1.6.2" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.12.0: - version "9.18.5" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" - integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -1930,14 +1287,6 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -html-tag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" - integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== - dependencies: - is-self-closing "^1.0.1" - kind-of "^6.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2032,20 +1381,6 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2068,11 +1403,6 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" @@ -2097,79 +1427,16 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-even@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" - integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY= - dependencies: - is-odd "^0.1.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -2185,20 +1452,6 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== -is-number@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" @@ -2209,20 +1462,6 @@ is-object@~0.1.2: resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" integrity sha1-AO+8CIFsM8/ErIJR0TLhDcZQmNc= -is-odd@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" - integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc= - dependencies: - is-number "^3.0.0" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-potential-custom-element-name@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" @@ -2248,13 +1487,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-self-closing@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" - integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== - dependencies: - self-closing-tags "^1.0.1" - is-string@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" @@ -2279,11 +1511,6 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - is@~0.2.6: version "0.2.7" resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" @@ -2294,7 +1521,7 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2304,23 +1531,6 @@ isbuffer@~0.0.0: resolved "https://registry.yarnpkg.com/isbuffer/-/isbuffer-0.0.0.tgz#38c146d9df528b8bf9b0701c3d43cf12df3fc39b" integrity sha1-OMFG2d9Si4v5sHAcPUPPEt8/w5s= -isobject@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" - integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2434,30 +1644,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0, kind-of@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - level-blobs@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/level-blobs/-/level-blobs-0.1.7.tgz#9ab9b97bb99f1edbf9f78a3433e21ed56386bdaf" @@ -2565,11 +1751,6 @@ loader-utils@^1.1.0: emojis-list "^3.0.0" json5 "^1.0.1" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -2585,27 +1766,12 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.template@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.19, lodash@^4.17.20: +lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2629,18 +1795,6 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -2665,25 +1819,6 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -micromatch@^3.1.5: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -2726,14 +1861,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -2741,16 +1868,6 @@ mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -moment@^2.18.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - nanoid@^2.1.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" @@ -2761,28 +1878,6 @@ nanoid@^3.1.22: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -neo-async@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -2810,15 +1905,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -2843,13 +1929,6 @@ object-keys@~0.4.0: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -2869,13 +1948,6 @@ object.getownpropertydescriptors@^2.1.0: define-properties "^1.1.3" es-abstract "^1.18.0-next.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - object.values@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" @@ -2971,11 +2043,6 @@ parse5@5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -3017,11 +2084,6 @@ pify@^5.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - postcss-calc@^7.0.1: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" @@ -3453,7 +2515,7 @@ readable-stream@^1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@^2.2.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -3485,44 +2547,11 @@ readable-stream@~1.0.26, readable-stream@~1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexparam@1.3.0, regexparam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== -relative@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" - integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8= - dependencies: - isobject "^2.0.0" - -remarkable@^1.6.2, remarkable@^1.7.1: - version "1.7.4" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" - integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== - dependencies: - argparse "^1.0.10" - autolinker "~0.28.0" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" @@ -3585,11 +2614,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - resolve@^1.17.0, resolve@^1.19.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -3598,11 +2622,6 @@ resolve@^1.17.0, resolve@^1.19.0: is-core-module "^2.2.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -3731,13 +2750,6 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3755,11 +2767,6 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" -self-closing-tags@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" - integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== - semver@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" @@ -3772,16 +2779,6 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -3809,47 +2806,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -3858,16 +2814,6 @@ source-map-support@~0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -3883,13 +2829,6 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3915,14 +2854,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -3973,11 +2904,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -striptags@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" - integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== - style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" @@ -4027,11 +2953,6 @@ svelte-flatpickr@^3.1.0: dependencies: flatpickr "^4.5.2" -svelte-portal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3" - integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q== - svelte-spa-router@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.0.5.tgz#097506578ac2371c9556b9789bd397d408968d92" @@ -4132,49 +3053,11 @@ terser@^5.0.0: source-map "~0.7.2" source-map-support "~0.5.19" -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -to-gfm-code-block@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" - integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -4228,18 +3111,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeof-article@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" - integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8= - dependencies: - kind-of "^3.1.0" - -uglify-js@^3.1.4: - version "3.14.1" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06" - integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g== - unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -4250,16 +3121,6 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -4280,14 +3141,6 @@ unquote@~1.1.1: resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - uri-js@^4.2.2: version "4.4.0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" @@ -4295,16 +3148,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4405,11 +3248,6 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4455,17 +3293,7 @@ xtend@~3.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo= -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -year@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" - integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= diff --git a/packages/server/package.json b/packages/server/package.json index 6ff3a90ef2..14b299bea6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -109,7 +109,6 @@ "to-json-schema": "0.2.5", "uuid": "3.3.2", "validate.js": "0.13.1", - "worker-farm": "1.7.0", "yargs": "13.2.4", "zlib": "1.0.5" }, diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 2d164b415d..60a0928e33 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -1,12 +1,14 @@ const CouchDB = require("../../db") const actions = require("../../automations/actions") -const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") -const webhooks = require("./webhook") const { getAutomationParams, generateAutomationID } = require("../../db/utils") - -const WH_STEP_ID = triggers.BUILTIN_DEFINITIONS.WEBHOOK.stepId -const CRON_STEP_ID = triggers.BUILTIN_DEFINITIONS.CRON.stepId +const { + checkForWebhooks, + updateTestHistory, +} = require("../../automations/utils") +const { deleteEntityMetadata } = require("../../utilities") +const { MetadataTypes } = require("../../constants") +const { setTestFlag, clearTestFlag } = require("../../utilities/redis") /************************* * * @@ -14,6 +16,19 @@ const CRON_STEP_ID = triggers.BUILTIN_DEFINITIONS.CRON.stepId * * *************************/ +async function cleanupAutomationMetadata(appId, automationId) { + await deleteEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_INPUT, + automationId + ) + await deleteEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automationId + ) +} + function cleanAutomationInputs(automation) { if (automation == null) { return automation @@ -21,6 +36,10 @@ function cleanAutomationInputs(automation) { let steps = automation.definition.steps let trigger = automation.definition.trigger let allSteps = [...steps, trigger] + // live is not a property used anymore + if (automation.live != null) { + delete automation.live + } for (let step of allSteps) { if (step == null) { continue @@ -34,119 +53,6 @@ function cleanAutomationInputs(automation) { return automation } -/** - * This function handles checking of any cron jobs need to be created or deleted for automations. - * @param {string} appId The ID of the app in which we are checking for webhooks - * @param {object|undefined} oldAuto The old automation object if updating/deleting - * @param {object|undefined} newAuto The new automation object if creating/updating - */ -async function checkForCronTriggers({ appId, oldAuto, newAuto }) { - const oldTrigger = oldAuto ? oldAuto.definition.trigger : null - const newTrigger = newAuto ? newAuto.definition.trigger : null - function isCronTrigger(auto) { - return ( - auto && - auto.definition.trigger && - auto.definition.trigger.stepId === CRON_STEP_ID - ) - } - - const isLive = auto => auto && auto.live - - const cronTriggerRemoved = - isCronTrigger(oldAuto) && !isCronTrigger(newAuto) && oldTrigger.cronJobId - const cronTriggerDeactivated = !isLive(newAuto) && isLive(oldAuto) - - const cronTriggerActivated = isLive(newAuto) && !isLive(oldAuto) - - if (cronTriggerRemoved || (cronTriggerDeactivated && oldTrigger.cronJobId)) { - await triggers.automationQueue.removeRepeatableByKey(oldTrigger.cronJobId) - } - // need to create cron job - else if (isCronTrigger(newAuto) && cronTriggerActivated) { - const job = await triggers.automationQueue.add( - { - automation: newAuto, - event: { appId, timestamp: Date.now() }, - }, - { repeat: { cron: newTrigger.inputs.cron } } - ) - // Assign cron job ID from bull so we can remove it later if the cron trigger is removed - newTrigger.cronJobId = job.id - } - return newAuto -} - -/** - * This function handles checking if any webhooks need to be created or deleted for automations. - * @param {string} appId The ID of the app in which we are checking for webhooks - * @param {object|undefined} oldAuto The old automation object if updating/deleting - * @param {object|undefined} newAuto The new automation object if creating/updating - * @returns {Promise} After this is complete the new automation object may have been updated and should be - * written to DB (this does not write to DB as it would be wasteful to repeat). - */ -async function checkForWebhooks({ appId, oldAuto, newAuto }) { - const oldTrigger = oldAuto ? oldAuto.definition.trigger : null - const newTrigger = newAuto ? newAuto.definition.trigger : null - const triggerChanged = - oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id - function isWebhookTrigger(auto) { - return ( - auto && - auto.definition.trigger && - auto.definition.trigger.stepId === WH_STEP_ID - ) - } - // need to delete webhook - if ( - isWebhookTrigger(oldAuto) && - (!isWebhookTrigger(newAuto) || triggerChanged) && - oldTrigger.webhookId - ) { - try { - let db = new CouchDB(appId) - // need to get the webhook to get the rev - const webhook = await db.get(oldTrigger.webhookId) - const ctx = { - appId, - params: { id: webhook._id, rev: webhook._rev }, - } - // might be updating - reset the inputs to remove the URLs - if (newTrigger) { - delete newTrigger.webhookId - newTrigger.inputs = {} - } - await webhooks.destroy(ctx) - } catch (err) { - // don't worry about not being able to delete, if it doesn't exist all good - } - } - // need to create webhook - if ( - (!isWebhookTrigger(oldAuto) || triggerChanged) && - isWebhookTrigger(newAuto) - ) { - const ctx = { - appId, - request: { - body: new webhooks.Webhook( - "Automation webhook", - webhooks.WebhookType.AUTOMATION, - newAuto._id - ), - }, - } - await webhooks.save(ctx) - const id = ctx.body.webhook._id - newTrigger.webhookId = id - newTrigger.inputs = { - schemaUrl: `api/webhooks/schema/${appId}/${id}`, - triggerUrl: `api/webhooks/trigger/${appId}/${id}`, - } - } - return newAuto -} - exports.create = async function (ctx) { const db = new CouchDB(ctx.appId) let automation = ctx.request.body @@ -165,10 +71,6 @@ exports.create = async function (ctx) { appId: ctx.appId, newAuto: automation, }) - automation = await checkForCronTriggers({ - appId: ctx.appId, - newAuto: automation, - }) const response = await db.put(automation) automation._rev = response.rev @@ -193,14 +95,26 @@ exports.update = async function (ctx) { oldAuto: oldAutomation, newAuto: automation, }) - automation = await checkForCronTriggers({ - appId: ctx.appId, - oldAuto: oldAutomation, - newAuto: automation, - }) const response = await db.put(automation) automation._rev = response.rev + const oldAutoTrigger = + oldAutomation && oldAutomation.definition.trigger + ? oldAutomation.definition.trigger + : {} + const newAutoTrigger = + automation && automation.definition.trigger + ? automation.definition.trigger + : {} + // trigger has been updated, remove the test inputs + if (oldAutoTrigger.id !== newAutoTrigger.id) { + await deleteEntityMetadata( + ctx.appId, + MetadataTypes.AUTOMATION_TEST_INPUT, + automation._id + ) + } + ctx.status = 200 ctx.body = { message: `Automation ${automation._id} updated successfully.`, @@ -229,35 +143,29 @@ exports.find = async function (ctx) { exports.destroy = async function (ctx) { const db = new CouchDB(ctx.appId) - const oldAutomation = await db.get(ctx.params.id) + const automationId = ctx.params.id + const oldAutomation = await db.get(automationId) await checkForWebhooks({ appId: ctx.appId, oldAuto: oldAutomation, }) - await checkForCronTriggers({ - appId: ctx.appId, - oldAuto: oldAutomation, - }) - ctx.body = await db.remove(ctx.params.id, ctx.params.rev) + // delete metadata first + await cleanupAutomationMetadata(ctx.appId, automationId) + ctx.body = await db.remove(automationId, ctx.params.rev) } exports.getActionList = async function (ctx) { - ctx.body = actions.DEFINITIONS + ctx.body = actions.ACTION_DEFINITIONS } exports.getTriggerList = async function (ctx) { - ctx.body = triggers.BUILTIN_DEFINITIONS -} - -exports.getLogicList = async function (ctx) { - ctx.body = logic.BUILTIN_DEFINITIONS + ctx.body = triggers.TRIGGER_DEFINITIONS } module.exports.getDefinitionList = async function (ctx) { ctx.body = { - logic: logic.BUILTIN_DEFINITIONS, - trigger: triggers.BUILTIN_DEFINITIONS, - action: actions.DEFINITIONS, + trigger: triggers.TRIGGER_DEFINITIONS, + action: actions.ACTION_DEFINITIONS, } } @@ -268,15 +176,37 @@ module.exports.getDefinitionList = async function (ctx) { *********************/ exports.trigger = async function (ctx) { - const db = new CouchDB(ctx.appId) + const appId = ctx.appId + const db = new CouchDB(appId) let automation = await db.get(ctx.params.id) await triggers.externalTrigger(automation, { ...ctx.request.body, - appId: ctx.appId, + appId, }) - ctx.status = 200 ctx.body = { message: `Automation ${automation._id} has been triggered.`, automation, } } + +exports.test = async function (ctx) { + const appId = ctx.appId + const db = new CouchDB(appId) + let automation = await db.get(ctx.params.id) + await setTestFlag(automation._id) + const response = await triggers.externalTrigger( + automation, + { + ...ctx.request.body, + appId, + }, + { getResponses: true } + ) + // save a test history run + await updateTestHistory(ctx.appId, automation, { + ...ctx.request.body, + occurredAt: new Date().getTime(), + }) + await clearTestFlag(automation._id) + ctx.body = response +} diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index 4608ca6342..08f9072138 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -1,7 +1,11 @@ const CouchDB = require("../../../db") const Deployment = require("./Deployment") const { Replication } = require("@budibase/auth/db") -const { DocumentTypes } = require("../../../db/utils") +const { DocumentTypes, getAutomationParams } = require("../../../db/utils") +const { + disableAllCrons, + enableCronTrigger, +} = require("../../../automations/utils") // the max time we can wait for an invalidation to complete before considering it failed const MAX_PENDING_TIME_MS = 30 * 60000 @@ -58,6 +62,23 @@ async function storeDeploymentHistory(deployment) { return deployment } +async function initDeployedApp(prodAppId) { + const db = new CouchDB(prodAppId) + const automations = ( + await db.allDocs( + getAutomationParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) + const promises = [] + await disableAllCrons(prodAppId) + for (let automation of automations) { + promises.push(enableCronTrigger(prodAppId, automation)) + } + await Promise.all(promises) +} + async function deployApp(deployment) { try { const productionAppId = deployment.appId.replace("_dev", "") @@ -85,6 +106,7 @@ async function deployApp(deployment) { }, }) + await initDeployedApp(productionAppId) deployment.setStatus(DeploymentStatus.SUCCESS) await storeDeploymentHistory(deployment) } catch (err) { diff --git a/packages/server/src/api/controllers/metadata.js b/packages/server/src/api/controllers/metadata.js new file mode 100644 index 0000000000..75236650fd --- /dev/null +++ b/packages/server/src/api/controllers/metadata.js @@ -0,0 +1,46 @@ +const { MetadataTypes } = require("../../constants") +const CouchDB = require("../../db") +const { generateMetadataID } = require("../../db/utils") +const { saveEntityMetadata, deleteEntityMetadata } = require("../../utilities") + +exports.getTypes = async ctx => { + ctx.body = { + types: MetadataTypes, + } +} + +exports.saveMetadata = async ctx => { + const { type, entityId } = ctx.params + if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) { + ctx.throw(400, "Cannot save automation history type") + } + ctx.body = await saveEntityMetadata( + ctx.appId, + type, + entityId, + ctx.request.body + ) +} + +exports.deleteMetadata = async ctx => { + const { type, entityId } = ctx.params + await deleteEntityMetadata(ctx.appId, type, entityId) + ctx.body = { + message: "Metadata deleted successfully", + } +} + +exports.getMetadata = async ctx => { + const { type, entityId } = ctx.params + const db = new CouchDB(ctx.appId) + const id = generateMetadataID(type, entityId) + try { + ctx.body = await db.get(id) + } catch (err) { + if (err.status === 404) { + ctx.body = {} + } else { + ctx.throw(err.status, err) + } + } +} diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index d584b73e20..737c8c9b01 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -160,8 +160,6 @@ exports.execute = async function (ctx) { ) const integration = new Integration(datasource.config) - console.log(query) - // ctx.body = {} // call the relevant CRUD method on the integration class ctx.body = formatResponse(await integration[query.queryVerb](enrichedQuery)) // cleanup diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 84e06f5855..2299a20580 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -23,6 +23,19 @@ const CALCULATION_TYPES = { STATS: "stats", } +async function storeResponse(ctx, db, row, oldTable, table) { + row.type = "row" + const response = await db.put(row) + // don't worry about rev, tables handle rev/lastID updates + if (!isEqual(oldTable, table)) { + await db.put(table) + } + row._rev = response.rev + // process the row before return, to include relationships + row = await outputProcessing(ctx, table, row, { squash: false }) + return { row, table } +} + exports.patch = async ctx => { const appId = ctx.appId const db = new CouchDB(appId) @@ -77,14 +90,7 @@ exports.patch = async ctx => { return { row: ctx.body, table } } - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - row.type = "row" - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.save = async function (ctx) { @@ -118,14 +124,7 @@ exports.save = async function (ctx) { table, }) - row.type = "row" - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.fetchView = async ctx => { @@ -221,34 +220,47 @@ exports.destroy = async function (ctx) { const appId = ctx.appId const db = new CouchDB(appId) const { _id, _rev } = ctx.request.body - const row = await db.get(_id) + let row = await db.get(_id) if (row.tableId !== ctx.params.tableId) { throw "Supplied tableId doesn't match the row's tableId" } + const table = await db.get(row.tableId) + // update the row to include full relationships before deleting them + row = await outputProcessing(ctx, table, row, { squash: false }) + // now remove the relationships await linkRows.updateLinks({ appId, eventType: linkRows.EventType.ROW_DELETE, row, tableId: row.tableId, }) + + let response if (ctx.params.tableId === InternalTables.USER_METADATA) { ctx.params = { id: _id, } await userController.destroyMetadata(ctx) - return { response: ctx.body, row } + response = ctx.body } else { - const response = await db.remove(_id, _rev) - return { response, row } + response = await db.remove(_id, _rev) } + return { response, row } } exports.bulkDestroy = async ctx => { const appId = ctx.appId - const { rows } = ctx.request.body const db = new CouchDB(appId) + const tableId = ctx.params.tableId + const table = await db.get(tableId) + let { rows } = ctx.request.body + // before carrying out any updates, make sure the rows are ready to be returned + // they need to be the full rows (including previous relationships) for automations + rows = await outputProcessing(ctx, table, rows, { squash: false }) + + // remove the relationships first let updates = rows.map(row => linkRows.updateLinks({ appId, @@ -257,8 +269,7 @@ exports.bulkDestroy = async ctx => { tableId: row.tableId, }) ) - // TODO remove special user case in future - if (ctx.params.tableId === InternalTables.USER_METADATA) { + if (tableId === InternalTables.USER_METADATA) { updates = updates.concat( rows.map(row => { ctx.params = { diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index c7b674b4e4..599b16b583 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -9,6 +9,10 @@ const { } = require("@budibase/auth/permissions") const Joi = require("joi") const { bodyResource, paramResource } = require("../../middleware/resourceId") +const { + middleware: appInfoMiddleware, + AppType, +} = require("../../middleware/appInfo") const router = Router() @@ -22,7 +26,6 @@ function generateStepSchema(allowStepTypes) { tagline: Joi.string().required(), icon: Joi.string().required(), params: Joi.object(), - // TODO: validate args a bit more deeply args: Joi.object(), type: Joi.string().required().valid(...allowStepTypes), }).unknown(true) @@ -31,7 +34,6 @@ function generateStepSchema(allowStepTypes) { function generateValidator(existing = false) { // prettier-ignore return joiValidator.body(Joi.object({ - live: Joi.bool(), _id: existing ? Joi.string().required() : Joi.string(), _rev: existing ? Joi.string().required() : Joi.string(), name: Joi.string().required(), @@ -54,11 +56,6 @@ router authorized(BUILDER), controller.getActionList ) - .get( - "/api/automations/logic/list", - authorized(BUILDER), - controller.getLogicList - ) .get( "/api/automations/definitions/list", authorized(BUILDER), @@ -84,17 +81,25 @@ router generateValidator(false), controller.create ) - .post( - "/api/automations/:id/trigger", - paramResource("id"), - authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), - controller.trigger - ) .delete( "/api/automations/:id/:rev", paramResource("id"), authorized(BUILDER), controller.destroy ) + .post( + "/api/automations/:id/trigger", + appInfoMiddleware({ appType: AppType.PROD }), + paramResource("id"), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + controller.trigger + ) + .post( + "/api/automations/:id/test", + appInfoMiddleware({ appType: AppType.DEV }), + paramResource("id"), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + controller.test + ) module.exports = router diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 0b09a78bb8..2e1353df98 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -22,6 +22,7 @@ const datasourceRoutes = require("./datasource") const queryRoutes = require("./query") const hostingRoutes = require("./hosting") const backupRoutes = require("./backup") +const metadataRoutes = require("./metadata") const devRoutes = require("./dev") exports.mainRoutes = [ @@ -46,6 +47,7 @@ exports.mainRoutes = [ queryRoutes, hostingRoutes, backupRoutes, + metadataRoutes, devRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this diff --git a/packages/server/src/api/routes/metadata.js b/packages/server/src/api/routes/metadata.js new file mode 100644 index 0000000000..f4eba64358 --- /dev/null +++ b/packages/server/src/api/routes/metadata.js @@ -0,0 +1,38 @@ +const Router = require("@koa/router") +const controller = require("../controllers/metadata") +const { + middleware: appInfoMiddleware, + AppType, +} = require("../../middleware/appInfo") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("@budibase/auth/permissions") + +const router = Router() + +router + .post( + "/api/metadata/:type/:entityId", + authorized(BUILDER), + appInfoMiddleware({ appType: AppType.DEV }), + controller.saveMetadata + ) + .delete( + "/api/metadata/:type/:entityId", + authorized(BUILDER), + appInfoMiddleware({ appType: AppType.DEV }), + controller.deleteMetadata + ) + .get( + "/api/metadata/type", + authorized(BUILDER), + appInfoMiddleware({ appType: AppType.DEV }), + controller.getTypes + ) + .get( + "/api/metadata/:type/:entityId", + authorized(BUILDER), + appInfoMiddleware({ appType: AppType.DEV }), + controller.getMetadata + ) + +module.exports = router diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 5654c14c17..c412c34fdc 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -2,6 +2,7 @@ const { checkBuilderEndpoint, getAllTableRows, clearAllAutomations, + testAutomation, } = require("./utilities/TestFunctions") const setup = require("./utilities") const { basicAutomation } = setup.structures @@ -10,7 +11,6 @@ const MAX_RETRIES = 4 let ACTION_DEFINITIONS = {} let TRIGGER_DEFINITIONS = {} -let LOGIC_DEFINITIONS = {} describe("/automations", () => { let request = setup.getRequest() @@ -23,15 +23,6 @@ describe("/automations", () => { await config.init() }) - const triggerWorkflow = async automation => { - return await request - .post(`/api/automations/${automation._id}/trigger`) - .send({ name: "Test", description: "TEST" }) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) - } - describe("get definitions", () => { it("returns a list of definitions for actions", async () => { const res = await request @@ -44,7 +35,7 @@ describe("/automations", () => { ACTION_DEFINITIONS = res.body }) - it("returns a list of definitions for triggers", async () => { + it("returns a list of definitions for triggerInfo", async () => { const res = await request .get(`/api/automations/trigger/list`) .set(config.defaultHeaders()) @@ -55,17 +46,6 @@ describe("/automations", () => { TRIGGER_DEFINITIONS = res.body }) - it("returns a list of definitions for actions", async () => { - const res = await request - .get(`/api/automations/logic/list`) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) - - expect(Object.keys(res.body).length).not.toEqual(0) - LOGIC_DEFINITIONS = res.body - }) - it("returns all of the definitions in one", async () => { const res = await request .get(`/api/automations/definitions/list`) @@ -75,7 +55,6 @@ describe("/automations", () => { expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(Object.keys(ACTION_DEFINITIONS).length) expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length) - expect(Object.keys(res.body.logic).length).toEqual(Object.keys(LOGIC_DEFINITIONS).length) }) }) @@ -168,14 +147,13 @@ describe("/automations", () => { automation.definition.steps[0].inputs.row.tableId = table._id automation = await config.createAutomation(automation) await setup.delay(500) - const res = await triggerWorkflow(automation) + const res = await testAutomation(config, automation) // this looks a bit mad but we don't actually have a way to wait for a response from the automation to // know that it has finished all of its actions - this is currently the best way // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works // TODO: update when workflow logs are a thing for (let tries = 0; tries < MAX_RETRIES; tries++) { - expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`) - expect(res.body.automation.name).toEqual(automation.name) + expect(res.body).toBeDefined() await setup.delay(500) let elements = await getAllTableRows(config) // don't test it unless there are values to test diff --git a/packages/server/src/api/routes/tests/metadata.spec.js b/packages/server/src/api/routes/tests/metadata.spec.js new file mode 100644 index 0000000000..5befd492a2 --- /dev/null +++ b/packages/server/src/api/routes/tests/metadata.spec.js @@ -0,0 +1,65 @@ +const { testAutomation } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const { MetadataTypes } = require("../../../constants") + +describe("/metadata", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let automation + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + automation = await config.createAutomation() + }) + + async function createMetadata(data, type = MetadataTypes.AUTOMATION_TEST_INPUT) { + const res = await request + .post(`/api/metadata/${type}/${automation._id}`) + .send(data) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._rev).toBeDefined() + } + + async function getMetadata(type) { + const res = await request + .get(`/api/metadata/${type}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + return res.body + } + + describe("save", () => { + it("should be able to save some metadata", async () => { + await createMetadata({ test: "a" }) + const testInput = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(testInput.test).toBe("a") + }) + + it("should save history metadata on automation run", async () => { + // this should have created some history + await testAutomation(config, automation) + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY) + expect(metadata).toBeDefined() + expect(metadata.history.length).toBe(1) + expect(typeof metadata.history[0].occurredAt).toBe("number") + }) + }) + + describe("destroy", () => { + it("should be able to delete some test inputs", async () => { + const res = await request + .delete(`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(metadata.test).toBeUndefined() + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 944d2ac527..87190c3f76 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -101,3 +101,17 @@ exports.checkPermissionsEndpoint = async ({ exports.getDB = config => { return new CouchDB(config.getAppId()) } + +exports.testAutomation = async (config, automation) => { + return await config.request + .post(`/api/automations/${automation._id}/test`) + .send({ + row: { + name: "Test", + description: "TEST", + }, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) +} diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index a32f241aa3..23e05e5e95 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -88,8 +88,8 @@ module.exports = server.listen(env.PORT || 0, async () => { env._set("PORT", server.address().port) eventEmitter.emitPort(env.PORT) fileSystem.init() - await automations.init() await redis.init() + await automations.init() }) process.on("uncaughtException", err => { diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index b0b5f9f1ba..ef77387a40 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -1,4 +1,3 @@ -const sendgridEmail = require("./steps/sendgridEmail") const sendSmtpEmail = require("./steps/sendSmtpEmail") const createRow = require("./steps/createRow") const updateRow = require("./steps/updateRow") @@ -8,15 +7,14 @@ const bash = require("./steps/bash") const executeQuery = require("./steps/executeQuery") const outgoingWebhook = require("./steps/outgoingWebhook") const serverLog = require("./steps/serverLog") -const env = require("../environment") -const Sentry = require("@sentry/node") -const { - automationInit, - getExternalAutomationStep, -} = require("../utilities/fileSystem") +const discord = require("./steps/discord") +const slack = require("./steps/slack") +const zapier = require("./steps/zapier") +const integromat = require("./steps/integromat") +let filter = require("./steps/filter") +let delay = require("./steps/delay") -const BUILTIN_ACTIONS = { - SEND_EMAIL: sendgridEmail.run, +const ACTION_IMPLS = { SEND_EMAIL_SMTP: sendSmtpEmail.run, CREATE_ROW: createRow.run, UPDATE_ROW: updateRow.run, @@ -26,9 +24,15 @@ const BUILTIN_ACTIONS = { EXECUTE_BASH: bash.run, EXECUTE_QUERY: executeQuery.run, SERVER_LOG: serverLog.run, + DELAY: delay.run, + FILTER: filter.run, + // these used to be lowercase step IDs, maintain for backwards compat + discord: discord.run, + slack: slack.run, + zapier: zapier.run, + integromat: integromat.run, } -const BUILTIN_DEFINITIONS = { - SEND_EMAIL: sendgridEmail.definition, +const ACTION_DEFINITIONS = { SEND_EMAIL_SMTP: sendSmtpEmail.definition, CREATE_ROW: createRow.definition, UPDATE_ROW: updateRow.definition, @@ -38,47 +42,20 @@ const BUILTIN_DEFINITIONS = { EXECUTE_QUERY: executeQuery.definition, EXECUTE_BASH: bash.definition, SERVER_LOG: serverLog.definition, -} - -let MANIFEST = null - -/* istanbul ignore next */ -function buildBundleName(pkgName, version) { - return `${pkgName}@${version}.min.js` + DELAY: delay.definition, + FILTER: filter.definition, + // these used to be lowercase step IDs, maintain for backwards compat + discord: discord.definition, + slack: slack.definition, + zapier: zapier.definition, + integromat: integromat.definition, } /* istanbul ignore next */ -module.exports.getAction = async function (actionName) { - if (BUILTIN_ACTIONS[actionName] != null) { - return BUILTIN_ACTIONS[actionName] +exports.getAction = async function (actionName) { + if (ACTION_IMPLS[actionName] != null) { + return ACTION_IMPLS[actionName] } - // worker pools means that a worker may not have manifest - if (env.isProd() && MANIFEST == null) { - MANIFEST = await module.exports.init() - } - // env setup to get async packages - if (!MANIFEST || !MANIFEST.packages || !MANIFEST.packages[actionName]) { - return null - } - const pkg = MANIFEST.packages[actionName] - const bundleName = buildBundleName(pkg.stepId, pkg.version) - return getExternalAutomationStep(pkg.stepId, pkg.version, bundleName) } -module.exports.init = async function () { - try { - MANIFEST = await automationInit() - module.exports.DEFINITIONS = - MANIFEST && MANIFEST.packages - ? Object.assign(MANIFEST.packages, BUILTIN_DEFINITIONS) - : BUILTIN_DEFINITIONS - } catch (err) { - console.error(err) - Sentry.captureException(err) - } - return MANIFEST -} - -// definitions will have downloaded ones added to it, while builtin won't -module.exports.DEFINITIONS = BUILTIN_DEFINITIONS -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS +exports.ACTION_DEFINITIONS = ACTION_DEFINITIONS diff --git a/packages/server/src/automations/bullboard.js b/packages/server/src/automations/bullboard.js index 364c4afaa1..1f96b6c4d2 100644 --- a/packages/server/src/automations/bullboard.js +++ b/packages/server/src/automations/bullboard.js @@ -1,14 +1,22 @@ const { createBullBoard } = require("bull-board") const { BullAdapter } = require("bull-board/bullAdapter") -const { getQueues } = require("./triggers") const express = require("express") +const env = require("../environment") +const Queue = env.isTest() + ? require("../utilities/queue/inMemoryQueue") + : require("bull") +const { JobQueues } = require("../constants") +const { utils } = require("@budibase/auth/redis") +const { opts } = utils.getRedisOptions() + +let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) exports.pathPrefix = "/bulladmin" exports.init = () => { const expressApp = express() // Set up queues for bull board admin - const queues = getQueues() + const queues = [automationQueue] const adapters = [] for (let queue of queues) { adapters.push(new BullAdapter(queue)) @@ -18,3 +26,5 @@ exports.init = () => { expressApp.use(exports.pathPrefix, router) return expressApp } + +exports.queue = automationQueue diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 8f30b6b32f..87f35ce763 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -1,51 +1,17 @@ -const triggers = require("./triggers") -const actions = require("./actions") -const env = require("../environment") -const workerFarm = require("worker-farm") -const singleThread = require("./thread") -const { getAPIKey, update, Properties } = require("../utilities/usageQuota") - -let workers = workerFarm(require.resolve("./thread")) - -function runWorker(job) { - return new Promise((resolve, reject) => { - workers(job, err => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) -} - -async function updateQuota(automation) { - const appId = automation.appId - const apiObj = await getAPIKey(appId) - // this will fail, causing automation to escape if limits reached - await update(apiObj.apiKey, Properties.AUTOMATION, 1) - return apiObj.apiKey -} +const { processEvent } = require("./utils") +const { queue } = require("./bullboard") /** * This module is built purely to kick off the worker farm and manage the inputs/outputs */ -module.exports.init = async function () { - await actions.init() - triggers.automationQueue.process(async job => { - try { - if (env.USE_QUOTAS) { - job.data.automation.apiKey = await updateQuota(job.data.automation) - } - if (env.isProd()) { - await runWorker(job) - } else { - await singleThread(job) - } - } catch (err) { - console.error( - `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` - ) - } +exports.init = function () { + // this promise will not complete + return queue.process(async job => { + await processEvent(job) }) } + +exports.getQueues = () => { + return [queue] +} +exports.queue = queue diff --git a/packages/server/src/automations/logic.js b/packages/server/src/automations/logic.js deleted file mode 100644 index cbd3d42430..0000000000 --- a/packages/server/src/automations/logic.js +++ /dev/null @@ -1,20 +0,0 @@ -let filter = require("./steps/filter") -let delay = require("./steps/delay") - -let BUILTIN_LOGIC = { - DELAY: delay.run, - FILTER: filter.run, -} - -let BUILTIN_DEFINITIONS = { - DELAY: delay.definition, - FILTER: filter.definition, -} - -module.exports.getLogic = function (logicName) { - if (BUILTIN_LOGIC[logicName] != null) { - return BUILTIN_LOGIC[logicName] - } -} - -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index 76d6713c5b..6c44c1bbbb 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -1,12 +1,13 @@ const { execSync } = require("child_process") const { processStringSync } = require("@budibase/string-templates") -module.exports.definition = { +exports.definition = { name: "Bash Scripting", tagline: "Execute a bash command", icon: "ri-terminal-box-line", description: "Run a bash script", type: "ACTION", + internal: true, stepId: "EXECUTE_BASH", inputs: {}, schema: { @@ -24,7 +25,11 @@ module.exports.definition = { properties: { stdout: { type: "string", - description: "Standard output of your bash command or script.", + description: "Standard output of your bash command or script", + }, + success: { + type: "boolean", + description: "Whether the command was successful", }, }, }, @@ -32,7 +37,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, context }) { +exports.run = async function ({ inputs, context }) { if (inputs.code == null) { return { stdout: "Budibase bash automation failed: Invalid inputs", @@ -42,18 +47,20 @@ module.exports.run = async function ({ inputs, context }) { try { const command = processStringSync(inputs.code, context) - let stdout + let stdout, + success = true try { stdout = execSync(command, { timeout: 500 }) } catch (err) { stdout = err.message + success = false } return { stdout, + success, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index 889f7e98b9..e3c90fb15b 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -3,12 +3,13 @@ const automationUtils = require("../automationUtils") const env = require("../../environment") const usage = require("../../utilities/usageQuota") -module.exports.definition = { +exports.definition = { name: "Create Row", tagline: "Create a {{inputs.enriched.table.name}} row", icon: "ri-save-3-line", description: "Add a row to your database", type: "ACTION", + internal: true, stepId: "CREATE_ROW", inputs: {}, schema: { @@ -42,7 +43,7 @@ module.exports.definition = { }, success: { type: "boolean", - description: "Whether the action was successful", + description: "Whether the row creation was successful", }, id: { type: "string", @@ -58,7 +59,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { +exports.run = async function ({ inputs, appId, apiKey, emitter }) { if (inputs.row == null || inputs.row.tableId == null) { return { success: false, @@ -97,7 +98,6 @@ module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/delay.js b/packages/server/src/automations/steps/delay.js index f653d0a980..899d8f8401 100644 --- a/packages/server/src/automations/steps/delay.js +++ b/packages/server/src/automations/steps/delay.js @@ -1,11 +1,12 @@ let { wait } = require("../../utilities") -module.exports.definition = { +exports.definition = { name: "Delay", icon: "ri-time-line", tagline: "Delay for {{inputs.time}} milliseconds", description: "Delay the automation until an amount of time has passed", stepId: "DELAY", + internal: true, inputs: {}, schema: { inputs: { @@ -17,10 +18,22 @@ module.exports.definition = { }, required: ["time"], }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the delay was successful", + }, + }, + required: ["success"], + }, }, type: "LOGIC", } -module.exports.run = async function delay({ inputs }) { +exports.run = async function delay({ inputs }) { await wait(inputs.time) + return { + success: true, + } } diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 94243fa03a..10f39d2d0c 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -2,13 +2,14 @@ const rowController = require("../../api/controllers/row") const env = require("../../environment") const usage = require("../../utilities/usageQuota") -module.exports.definition = { +exports.definition = { description: "Delete a row from your database", icon: "ri-delete-bin-line", name: "Delete Row", tagline: "Delete a {{inputs.enriched.table.name}} row", type: "ACTION", stepId: "DELETE_ROW", + internal: true, inputs: {}, schema: { inputs: { @@ -42,7 +43,7 @@ module.exports.definition = { }, success: { type: "boolean", - description: "Whether the action was successful", + description: "Whether the deletion was successful", }, }, required: ["row", "success"], @@ -50,7 +51,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { +exports.run = async function ({ inputs, appId, apiKey, emitter }) { if (inputs.id == null || inputs.revision == null) { return { success: false, @@ -84,7 +85,6 @@ module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js new file mode 100644 index 0000000000..5d225644b1 --- /dev/null +++ b/packages/server/src/automations/steps/discord.js @@ -0,0 +1,83 @@ +const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") + +const DEFAULT_USERNAME = "Budibase Automate" +const DEFAULT_AVATAR_URL = "https://i.imgur.com/a1cmTKM.png" + +exports.definition = { + name: "Discord Message", + tagline: "Send a message to a Discord server", + description: "Send a message to a Discord server", + icon: "ri-discord-line", + stepId: "discord", + type: "ACTION", + internal: false, + inputs: {}, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Discord Webhook URL", + }, + username: { + type: "string", + title: "Bot Name", + }, + avatar_url: { + type: "string", + title: "Bot Avatar URL", + }, + content: { + type: "string", + title: "Message", + }, + }, + required: ["url", "content"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + response: { + type: "string", + description: "The response from the Discord Webhook", + }, + success: { + type: "boolean", + description: "Whether the message sent successfully", + }, + }, + }, + }, +} + +exports.run = async function ({ inputs }) { + let { url, username, avatar_url, content } = inputs + if (!username) { + username = DEFAULT_USERNAME + } + if (!avatar_url) { + avatar_url = DEFAULT_AVATAR_URL + } + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + username, + avatar_url, + content, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + const { status, message } = await getFetchResponse(response) + return { + httpStatus: status, + success: status === 200, + response: message, + } +} diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index d045a75544..d5799a8f7d 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -1,12 +1,14 @@ const queryController = require("../../api/controllers/query") +const { buildCtx } = require("./utils") -module.exports.definition = { +exports.definition = { name: "External Data Connector", tagline: "Execute Data Connector", icon: "ri-database-2-line", description: "Execute a query in an external data connector", type: "ACTION", stepId: "EXECUTE_QUERY", + internal: true, inputs: {}, schema: { inputs: { @@ -42,7 +44,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, emitter }) { +exports.run = async function ({ inputs, appId, emitter }) { if (inputs.query == null) { return { success: false, @@ -54,28 +56,22 @@ module.exports.run = async function ({ inputs, appId, emitter }) { const { queryId, ...rest } = inputs.query - const ctx = { + const ctx = buildCtx(appId, emitter, { + body: { + parameters: rest, + }, params: { queryId, }, - request: { - body: { - parameters: rest, - }, - }, - appId, - eventEmitter: emitter, - } - - await queryController.execute(ctx) + }) try { + await queryController.execute(ctx) return { response: ctx.body, - success: ctx.status === 200, + success: true, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index 33ffd3ee8e..70298b9f8f 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -1,11 +1,13 @@ const scriptController = require("../../api/controllers/script") +const { buildCtx } = require("./utils") -module.exports.definition = { +exports.definition = { name: "JS Scripting", tagline: "Execute JavaScript Code", icon: "ri-terminal-box-line", description: "Run a piece of JavaScript code in your automation", type: "ACTION", + internal: true, stepId: "EXECUTE_SCRIPT", inputs: {}, schema: { @@ -23,8 +25,7 @@ module.exports.definition = { properties: { value: { type: "string", - description: - "The result of the last statement of the executed script.", + description: "The result of the return statement", }, success: { type: "boolean", @@ -36,7 +37,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, context, emitter }) { +exports.run = async function ({ inputs, appId, context, emitter }) { if (inputs.code == null) { return { success: false, @@ -46,25 +47,20 @@ module.exports.run = async function ({ inputs, appId, context, emitter }) { } } - const ctx = { - request: { - body: { - script: inputs.code, - context, - }, + const ctx = buildCtx(appId, emitter, { + body: { + script: inputs.code, + context, }, - user: { appId }, - eventEmitter: emitter, - } + }) try { await scriptController.execute(ctx) return { - success: ctx.status === 200, + success: true, value: ctx.body, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js index 586e424cc4..84bdc10c1d 100644 --- a/packages/server/src/automations/steps/filter.js +++ b/packages/server/src/automations/steps/filter.js @@ -1,29 +1,30 @@ -const LogicConditions = { +const FilterConditions = { EQUAL: "EQUAL", NOT_EQUAL: "NOT_EQUAL", GREATER_THAN: "GREATER_THAN", LESS_THAN: "LESS_THAN", } -const PrettyLogicConditions = { - [LogicConditions.EQUAL]: "Equals", - [LogicConditions.NOT_EQUAL]: "Not equals", - [LogicConditions.GREATER_THAN]: "Greater than", - [LogicConditions.LESS_THAN]: "Less than", +const PrettyFilterConditions = { + [FilterConditions.EQUAL]: "Equals", + [FilterConditions.NOT_EQUAL]: "Not equals", + [FilterConditions.GREATER_THAN]: "Greater than", + [FilterConditions.LESS_THAN]: "Less than", } -module.exports.LogicConditions = LogicConditions -module.exports.PrettyLogicConditions = PrettyLogicConditions +exports.FilterConditions = FilterConditions +exports.PrettyFilterConditions = PrettyFilterConditions -module.exports.definition = { +exports.definition = { name: "Filter", tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}", icon: "ri-git-branch-line", description: "Filter any automations which do not meet certain conditions", type: "LOGIC", + internal: true, stepId: "FILTER", inputs: { - condition: LogicConditions.EQUALS, + condition: FilterConditions.EQUALS, }, schema: { inputs: { @@ -35,8 +36,8 @@ module.exports.definition = { condition: { type: "string", title: "Condition", - enum: Object.values(LogicConditions), - pretty: Object.values(PrettyLogicConditions), + enum: Object.values(FilterConditions), + pretty: Object.values(PrettyFilterConditions), }, value: { type: "string", @@ -57,7 +58,7 @@ module.exports.definition = { }, } -module.exports.run = async function filter({ inputs }) { +exports.run = async function filter({ inputs }) { let { field, condition, value } = inputs // coerce types so that we can use them if (!isNaN(value) && !isNaN(field)) { @@ -70,16 +71,16 @@ module.exports.run = async function filter({ inputs }) { let success = false if (typeof field !== "object" && typeof value !== "object") { switch (condition) { - case LogicConditions.EQUAL: + case FilterConditions.EQUAL: success = field === value break - case LogicConditions.NOT_EQUAL: + case FilterConditions.NOT_EQUAL: success = field !== value break - case LogicConditions.GREATER_THAN: + case FilterConditions.GREATER_THAN: success = field > value break - case LogicConditions.LESS_THAN: + case FilterConditions.LESS_THAN: success = field < value break } diff --git a/packages/server/src/automations/steps/integromat.js b/packages/server/src/automations/steps/integromat.js new file mode 100644 index 0000000000..e7ea03efca --- /dev/null +++ b/packages/server/src/automations/steps/integromat.js @@ -0,0 +1,87 @@ +const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") + +exports.definition = { + name: "Integromat Integration", + tagline: "Trigger an Integromat scenario", + description: + "Performs a webhook call to Integromat and gets the response (if configured)", + icon: "ri-shut-down-line", + stepId: "integromat", + type: "ACTION", + internal: false, + inputs: {}, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Webhook URL", + }, + value1: { + type: "string", + title: "Input Value 1", + }, + value2: { + type: "string", + title: "Input Value 2", + }, + value3: { + type: "string", + title: "Input Value 3", + }, + value4: { + type: "string", + title: "Input Value 4", + }, + value5: { + type: "string", + title: "Input Value 5", + }, + }, + required: ["url", "value1", "value2", "value3", "value4", "value5"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether call was successful", + }, + httpStatus: { + type: "number", + description: "The HTTP status code returned", + }, + response: { + type: "object", + description: "The webhook response - this can have properties", + }, + }, + required: ["success", "response"], + }, + }, +} + +exports.run = async function ({ inputs }) { + const { url, value1, value2, value3, value4, value5 } = inputs + + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + value1, + value2, + value3, + value4, + value5, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + const { status, message } = await getFetchResponse(response) + return { + httpStatus: status, + success: status === 200, + response: message, + } +} diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index c1edde7b4e..ea0f0ce6be 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") const RequestType = { POST: "POST", @@ -16,12 +17,13 @@ const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH] * GET/DELETE requests cannot handle body elements so they will not be sent if configured. */ -module.exports.definition = { +exports.definition = { name: "Outgoing webhook", tagline: "Send a {{inputs.requestMethod}} request", icon: "ri-send-plane-line", description: "Send a request of specified method to a URL", type: "ACTION", + internal: true, stepId: "OUTGOING_WEBHOOK", inputs: { requestMethod: "POST", @@ -60,6 +62,10 @@ module.exports.definition = { type: "object", description: "The response from the webhook", }, + httpStatus: { + type: "number", + description: "The HTTP status code returned", + }, success: { type: "boolean", description: "Whether the action was successful", @@ -70,7 +76,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { let { requestMethod, url, requestBody, headers } = inputs if (!url.startsWith("http")) { url = `http://${url}` @@ -107,19 +113,11 @@ module.exports.run = async function ({ inputs }) { JSON.parse(request.body) } const response = await fetch(url, request) - const contentType = response.headers.get("content-type") - const success = response.status === 200 - let resp - if (!success) { - resp = response.statusText - } else if (contentType && contentType.indexOf("application/json") !== -1) { - resp = await response.json() - } else { - resp = await response.text() - } + const { status, message } = await getFetchResponse(response) return { - response: resp, - success: success, + httpStatus: status, + response: message, + success: status === 200, } } catch (err) { /* istanbul ignore next */ diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index 764972b402..552c9b4d36 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.js +++ b/packages/server/src/automations/steps/sendSmtpEmail.js @@ -1,11 +1,12 @@ const { sendSmtpEmail } = require("../../utilities/workerRequests") -module.exports.definition = { +exports.definition = { description: "Send an email using SMTP", tagline: "Send SMTP email to {{inputs.to}}", icon: "ri-mail-open-line", name: "Send Email (SMTP)", type: "ACTION", + internal: true, stepId: "SEND_EMAIL_SMTP", inputs: {}, schema: { @@ -46,7 +47,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { let { to, from, subject, contents } = inputs if (!contents) { contents = "

No content

" @@ -58,7 +59,6 @@ module.exports.run = async function ({ inputs }) { response, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/sendgridEmail.js b/packages/server/src/automations/steps/sendgridEmail.js deleted file mode 100644 index 5485116e89..0000000000 --- a/packages/server/src/automations/steps/sendgridEmail.js +++ /dev/null @@ -1,74 +0,0 @@ -module.exports.definition = { - description: "Send an email using SendGrid", - tagline: "Send email to {{inputs.to}}", - icon: "ri-mail-open-line", - name: "Send Email (SendGrid)", - type: "ACTION", - stepId: "SEND_EMAIL", - inputs: {}, - schema: { - inputs: { - properties: { - apiKey: { - type: "string", - title: "SendGrid API key", - }, - to: { - type: "string", - title: "Send To", - }, - from: { - type: "string", - title: "Send From", - }, - subject: { - type: "string", - title: "Email Subject", - }, - contents: { - type: "string", - title: "Email Contents", - }, - }, - required: ["to", "from", "subject", "contents"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the email was sent", - }, - response: { - type: "object", - description: "A response from the email client, this may be an error", - }, - }, - required: ["success"], - }, - }, -} - -module.exports.run = async function ({ inputs }) { - const sgMail = require("@sendgrid/mail") - sgMail.setApiKey(inputs.apiKey) - const msg = { - to: inputs.to, - from: inputs.from, - subject: inputs.subject, - text: inputs.contents ? inputs.contents : "Empty", - } - - try { - let response = await sgMail.send(msg) - return { - success: true, - response, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } -} diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js index 7389b65f54..82e7d073e3 100644 --- a/packages/server/src/automations/steps/serverLog.js +++ b/packages/server/src/automations/steps/serverLog.js @@ -4,12 +4,13 @@ * GET/DELETE requests cannot handle body elements so they will not be sent if configured. */ -module.exports.definition = { +exports.definition = { name: "Backend log", tagline: "Console log a value in the backend", icon: "ri-server-line", description: "Logs the given text to the server (using console.log)", type: "ACTION", + internal: true, stepId: "SERVER_LOG", inputs: { text: "", @@ -36,6 +37,9 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId }) { +exports.run = async function ({ inputs, appId }) { console.log(`App ${appId} - ${inputs.text}`) + return { + success: true, + } } diff --git a/packages/server/src/automations/steps/slack.js b/packages/server/src/automations/steps/slack.js new file mode 100644 index 0000000000..ec6341a26f --- /dev/null +++ b/packages/server/src/automations/steps/slack.js @@ -0,0 +1,64 @@ +const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") + +exports.definition = { + name: "Slack Message", + tagline: "Send a message to Slack", + description: "Send a message to Slack", + icon: "ri-slack-line", + stepId: "slack", + type: "ACTION", + internal: false, + inputs: {}, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Incoming Webhook URL", + }, + text: { + type: "string", + title: "Message", + }, + }, + required: ["url", "text"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + success: { + type: "boolean", + description: "Whether the message sent successfully", + }, + response: { + type: "string", + description: "The response from the Slack Webhook", + }, + }, + }, + }, +} + +exports.run = async function ({ inputs }) { + let { url, text } = inputs + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + text, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + const { status, message } = await getFetchResponse(response) + return { + httpStatus: status, + response: message, + success: status === 200, + } +} diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 206e429efa..961f75dee7 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -1,12 +1,13 @@ const rowController = require("../../api/controllers/row") const automationUtils = require("../automationUtils") -module.exports.definition = { +exports.definition = { name: "Update Row", tagline: "Update a {{inputs.enriched.table.name}} row", icon: "ri-refresh-line", description: "Update a row in your database", type: "ACTION", + internal: true, stepId: "UPDATE_ROW", inputs: {}, schema: { @@ -53,7 +54,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, emitter }) { +exports.run = async function ({ inputs, appId, emitter }) { if (inputs.rowId == null || inputs.row == null) { return { success: false, @@ -100,7 +101,6 @@ module.exports.run = async function ({ inputs, appId, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/utils.js b/packages/server/src/automations/steps/utils.js new file mode 100644 index 0000000000..61f4a8080d --- /dev/null +++ b/packages/server/src/automations/steps/utils.js @@ -0,0 +1,36 @@ +exports.getFetchResponse = async fetched => { + let status = fetched.status, + message + const contentType = fetched.headers.get("content-type") + try { + if (contentType && contentType.indexOf("application/json") !== -1) { + message = await fetched.json() + } else { + message = await fetched.text() + } + } catch (err) { + message = "Failed to retrieve response" + } + return { status, message } +} + +// need to make sure all ctx structures have the +// throw added to them, so that controllers don't +// throw a ctx.throw undefined when error occurs +exports.buildCtx = (appId, emitter, { body, params } = {}) => { + const ctx = { + appId, + user: { appId }, + eventEmitter: emitter, + throw: (code, error) => { + throw error + }, + } + if (body) { + ctx.request = { body } + } + if (params) { + ctx.params = params + } + return ctx +} diff --git a/packages/server/src/automations/steps/zapier.js b/packages/server/src/automations/steps/zapier.js new file mode 100644 index 0000000000..bec90497cd --- /dev/null +++ b/packages/server/src/automations/steps/zapier.js @@ -0,0 +1,84 @@ +const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") + +exports.definition = { + name: "Zapier Webhook", + stepId: "zapier", + type: "ACTION", + internal: false, + description: "Trigger a Zapier Zap via webhooks", + tagline: "Trigger a Zapier Zap", + icon: "ri-flashlight-line", + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Webhook URL", + }, + value1: { + type: "string", + title: "Payload Value 1", + }, + value2: { + type: "string", + title: "Payload Value 2", + }, + value3: { + type: "string", + title: "Payload Value 3", + }, + value4: { + type: "string", + title: "Payload Value 4", + }, + value5: { + type: "string", + title: "Payload Value 5", + }, + }, + required: ["url"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + response: { + type: "string", + description: "The response from Zapier", + }, + }, + }, + }, +} + +exports.run = async function ({ inputs }) { + const { url, value1, value2, value3, value4, value5 } = inputs + + // send the platform to make sure zaps always work, even + // if no values supplied + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + platform: "budibase", + value1, + value2, + value3, + value4, + value5, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + const { status, message } = await getFetchResponse(response) + + return { + success: status === 200, + httpStatus: status, + response: message, + } +} diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index 05ec44d70e..e1b82e4327 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -1,34 +1,18 @@ jest.mock("../../utilities/usageQuota") jest.mock("../thread") jest.spyOn(global.console, "error") -jest.mock("worker-farm", () => { - return () => { - const value = jest - .fn() - .mockReturnValueOnce(undefined) - .mockReturnValueOnce("Error") - return (input, callback) => { - workerJob = input - if (callback) { - callback(value()) - } - } - } -}) require("../../environment") const automation = require("../index") const usageQuota = require("../../utilities/usageQuota") const thread = require("../thread") const triggers = require("../triggers") -const { basicAutomation, basicTable } = require("../../tests/utilities/structures") +const { basicAutomation } = require("../../tests/utilities/structures") const { wait } = require("../../utilities") const { makePartial } = require("../../tests/utilities") const { cleanInputValues } = require("../automationUtils") const setup = require("./utilities") -let workerJob - usageQuota.getAPIKey.mockReturnValue({ apiKey: "test" }) describe("Run through some parts of the automations system", () => { @@ -44,59 +28,12 @@ describe("Run through some parts of the automations system", () => { it("should be able to init in builder", async () => { await triggers.externalTrigger(basicAutomation(), { a: 1 }) await wait(100) - expect(workerJob).toBeUndefined() expect(thread).toHaveBeenCalled() }) it("should be able to init in prod", async () => { - await setup.runInProd(async () => { - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined - expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1) - expect(workerJob).toBeDefined() - }) - }) - - it("try error scenario", async () => { - await setup.runInProd(async () => { - // the second call will throw an error - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - expect(console.error).toHaveBeenCalled() - }) - }) - - it("should be able to check triggering row filling", async () => { - const automation = basicAutomation() - let table = basicTable() - table.schema.boolean = { - type: "boolean", - constraints: { - type: "boolean", - }, - } - table.schema.number = { - type: "number", - constraints: { - type: "number", - }, - } - table.schema.datetime = { - type: "datetime", - constraints: { - type: "datetime", - }, - } - table = await config.createTable(table) - automation.definition.trigger.inputs.tableId = table._id - const params = await triggers.fillRowOutput(automation, { appId: config.getAppId() }) - expect(params.row).toBeDefined() - const date = new Date(params.row.datetime) - expect(typeof params.row.name).toBe("string") - expect(typeof params.row.boolean).toBe("boolean") - expect(typeof params.row.number).toBe("number") - expect(date.getFullYear()).toBe(1970) + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) }) it("should check coercion", async () => { diff --git a/packages/server/src/automations/tests/delay.spec.js b/packages/server/src/automations/tests/delay.spec.js index 99046e8171..d06bd6cf00 100644 --- a/packages/server/src/automations/tests/delay.spec.js +++ b/packages/server/src/automations/tests/delay.spec.js @@ -4,7 +4,7 @@ describe("test the delay logic", () => { it("should be able to run the delay", async () => { const time = 100 const before = Date.now() - await setup.runStep(setup.logic.DELAY.stepId, { time: time }) + await setup.runStep(setup.actions.DELAY.stepId, { time: time }) const now = Date.now() // divide by two just so that test will always pass as long as there was some sort of delay expect(now - before).toBeGreaterThanOrEqual(time / 2) diff --git a/packages/server/src/automations/tests/filter.spec.js b/packages/server/src/automations/tests/filter.spec.js index 05361f43ed..7895433fe9 100644 --- a/packages/server/src/automations/tests/filter.spec.js +++ b/packages/server/src/automations/tests/filter.spec.js @@ -1,48 +1,48 @@ const setup = require("./utilities") -const { LogicConditions } = require("../steps/filter") +const { FilterConditions } = require("../steps/filter") describe("test the filter logic", () => { async function checkFilter(field, condition, value, pass = true) { - let res = await setup.runStep(setup.logic.FILTER.stepId, + let res = await setup.runStep(setup.actions.FILTER.stepId, { field, condition, value } ) expect(res.success).toEqual(pass) } it("should be able test equality", async () => { - await checkFilter("hello", LogicConditions.EQUAL, "hello", true) - await checkFilter("hello", LogicConditions.EQUAL, "no", false) + await checkFilter("hello", FilterConditions.EQUAL, "hello", true) + await checkFilter("hello", FilterConditions.EQUAL, "no", false) }) it("should be able to test greater than", async () => { - await checkFilter(10, LogicConditions.GREATER_THAN, 5, true) - await checkFilter(10, LogicConditions.GREATER_THAN, 15, false) + await checkFilter(10, FilterConditions.GREATER_THAN, 5, true) + await checkFilter(10, FilterConditions.GREATER_THAN, 15, false) }) it("should be able to test less than", async () => { - await checkFilter(5, LogicConditions.LESS_THAN, 10, true) - await checkFilter(15, LogicConditions.LESS_THAN, 10, false) + await checkFilter(5, FilterConditions.LESS_THAN, 10, true) + await checkFilter(15, FilterConditions.LESS_THAN, 10, false) }) it("should be able to in-equality", async () => { - await checkFilter("hello", LogicConditions.NOT_EQUAL, "no", true) - await checkFilter(10, LogicConditions.NOT_EQUAL, 10, false) + await checkFilter("hello", FilterConditions.NOT_EQUAL, "no", true) + await checkFilter(10, FilterConditions.NOT_EQUAL, 10, false) }) it("check number coercion", async () => { - await checkFilter("10", LogicConditions.GREATER_THAN, "5", true) + await checkFilter("10", FilterConditions.GREATER_THAN, "5", true) }) it("check date coercion", async () => { await checkFilter( (new Date()).toISOString(), - LogicConditions.GREATER_THAN, + FilterConditions.GREATER_THAN, (new Date(-10000)).toISOString(), true ) }) it("check objects always false", async () => { - await checkFilter({}, LogicConditions.EQUAL, {}, false) + await checkFilter({}, FilterConditions.EQUAL, {}, false) }) }) \ No newline at end of file diff --git a/packages/server/src/automations/tests/sendEmail.spec.js b/packages/server/src/automations/tests/sendEmail.spec.js deleted file mode 100644 index 5f3aabfff8..0000000000 --- a/packages/server/src/automations/tests/sendEmail.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -const setup = require("./utilities") - -jest.mock("@sendgrid/mail") - -describe("test the send email action", () => { - let inputs - let config = setup.getConfig() - - beforeEach(async () => { - await config.init() - inputs = { - to: "me@test.com", - from: "budibase@test.com", - subject: "Testing", - text: "Email contents", - } - }) - - afterAll(setup.afterAll) - - it("should be able to run the action", async () => { - const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, inputs) - expect(res.success).toEqual(true) - // the mocked module throws back the input - expect(res.response.to).toEqual("me@test.com") - }) - - it("should return an error if input an invalid email address", async () => { - const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, { - ...inputs, - to: "invalid@test.com", - }) - expect(res.success).toEqual(false) - }) - -}) diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js index ab9de55430..ca01ea9233 100644 --- a/packages/server/src/automations/tests/utilities/index.js +++ b/packages/server/src/automations/tests/utilities/index.js @@ -1,6 +1,5 @@ const TestConfig = require("../../../tests/utilities/TestConfiguration") const actions = require("../../actions") -const logic = require("../../logic") const emitter = require("../../../events/index") const env = require("../../../environment") @@ -34,16 +33,7 @@ exports.runInProd = async fn => { } exports.runStep = async function runStep(stepId, inputs) { - let step - if ( - Object.values(exports.actions) - .map(action => action.stepId) - .includes(stepId) - ) { - step = await actions.getAction(stepId) - } else { - step = logic.getLogic(stepId) - } + let step = await actions.getAction(stepId) expect(step).toBeDefined() return step({ inputs, @@ -56,5 +46,4 @@ exports.runStep = async function runStep(stepId, inputs) { exports.apiKey = "test" -exports.actions = actions.BUILTIN_DEFINITIONS -exports.logic = logic.BUILTIN_DEFINITIONS +exports.actions = actions.ACTION_DEFINITIONS diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index aada0ca0ca..a3e81a2274 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -1,5 +1,4 @@ const actions = require("./actions") -const logic = require("./logic") const automationUtils = require("./automationUtils") const AutomationEmitter = require("../events/AutomationEmitter") const { processObject } = require("@budibase/string-templates") @@ -8,7 +7,7 @@ const CouchDB = require("../db") const { DocumentTypes } = require("../db/utils") const { doInTenant } = require("@budibase/auth/tenancy") -const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId +const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId /** * The automation orchestrator is a class responsible for executing automations. @@ -30,15 +29,15 @@ class Orchestrator { // create an emitter which has the chain count for this automation run in it, so it can block // excessive chaining if required this._emitter = new AutomationEmitter(this._chainCount + 1) + this.executionOutput = { trigger: {}, steps: [] } + // setup the execution output + const triggerStepId = automation.definition.trigger.stepId + const triggerId = automation.definition.trigger.id + this.updateExecutionOutput(triggerId, triggerStepId, null, triggerOutput) } - async getStepFunctionality(type, stepId) { - let step = null - if (type === "ACTION") { - step = await actions.getAction(stepId) - } else if (type === "LOGIC") { - step = logic.getLogic(stepId) - } + async getStepFunctionality(stepId) { + let step = await actions.getAction(stepId) if (step == null) { throw `Cannot find automation step by name ${stepId}` } @@ -55,11 +54,20 @@ class Orchestrator { return this._app } + updateExecutionOutput(id, stepId, inputs, outputs) { + const stepObj = { id, stepId, inputs, outputs } + // first entry is always the trigger (constructor) + if (this.executionOutput.steps.length === 0) { + this.executionOutput.trigger = stepObj + } + this.executionOutput.steps.push(stepObj) + } + async execute() { let automation = this._automation const app = await this.getApp() for (let step of automation.definition.steps) { - let stepFn = await this.getStepFunctionality(step.type, step.stepId) + let stepFn = await this.getStepFunctionality(step.stepId) step.inputs = await processObject(step.inputs, this._context) step.inputs = automationUtils.cleanInputValues( step.inputs, @@ -81,27 +89,20 @@ class Orchestrator { break } this._context.steps.push(outputs) + this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs) } catch (err) { console.error(`Automation error - ${step.stepId} - ${err}`) + return err } } + return this.executionOutput } } -// callback is required for worker-farm to state that the worker thread has completed -module.exports = async (job, cb = null) => { - try { - const automationOrchestrator = new Orchestrator( - job.data.automation, - job.data.event - ) - await automationOrchestrator.execute() - if (cb) { - cb() - } - } catch (err) { - if (cb) { - cb(err) - } - } +module.exports = async job => { + const automationOrchestrator = new Orchestrator( + job.data.automation, + job.data.event + ) + return automationOrchestrator.execute() } diff --git a/packages/server/src/automations/triggerInfo/app.js b/packages/server/src/automations/triggerInfo/app.js new file mode 100644 index 0000000000..1c64795cf3 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/app.js @@ -0,0 +1,31 @@ +exports.definition = { + name: "App Action", + event: "app:trigger", + icon: "ri-window-fill", + tagline: "Automation fired from the frontend", + description: "Trigger an automation from an action inside your app", + stepId: "APP", + inputs: {}, + schema: { + inputs: { + properties: { + fields: { + type: "object", + customType: "triggerSchema", + title: "Fields", + }, + }, + required: [], + }, + outputs: { + properties: { + fields: { + type: "object", + description: "Fields submitted from the app frontend", + }, + }, + required: ["fields"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/cron.js b/packages/server/src/automations/triggerInfo/cron.js new file mode 100644 index 0000000000..31e79a4abf --- /dev/null +++ b/packages/server/src/automations/triggerInfo/cron.js @@ -0,0 +1,31 @@ +exports.definition = { + name: "Cron Trigger", + event: "cron:trigger", + icon: "ri-timer-line", + tagline: "Cron Trigger ({{inputs.cron}})", + description: "Triggers automation on a cron schedule.", + stepId: "CRON", + inputs: {}, + schema: { + inputs: { + properties: { + cron: { + type: "string", + customType: "cron", + title: "Expression", + }, + }, + required: ["cron"], + }, + outputs: { + properties: { + timestamp: { + type: "number", + description: "Timestamp the cron was executed", + }, + }, + required: ["timestamp"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/index.js b/packages/server/src/automations/triggerInfo/index.js new file mode 100644 index 0000000000..066993d324 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/index.js @@ -0,0 +1,15 @@ +const app = require("./app") +const cron = require("./cron") +const rowDeleted = require("./rowDeleted") +const rowSaved = require("./rowSaved") +const rowUpdated = require("./rowUpdated") +const webhook = require("./webhook") + +exports.definitions = { + ROW_SAVED: rowSaved.definition, + ROW_UPDATED: rowUpdated.definition, + ROW_DELETED: rowDeleted.definition, + WEBHOOK: webhook.definition, + APP: app.definition, + CRON: cron.definition, +} diff --git a/packages/server/src/automations/triggerInfo/rowDeleted.js b/packages/server/src/automations/triggerInfo/rowDeleted.js new file mode 100644 index 0000000000..c7ead1fec4 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowDeleted.js @@ -0,0 +1,32 @@ +exports.definition = { + name: "Row Deleted", + event: "row:delete", + icon: "ri-delete-bin-line", + tagline: "Row is deleted from {{inputs.enriched.table.name}}", + description: "Fired when a row is deleted from your database", + stepId: "ROW_DELETED", + inputs: {}, + schema: { + inputs: { + properties: { + tableId: { + type: "string", + customType: "table", + title: "Table", + }, + }, + required: ["tableId"], + }, + outputs: { + properties: { + row: { + type: "object", + customType: "row", + description: "The row that was deleted", + }, + }, + required: ["row"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/rowSaved.js b/packages/server/src/automations/triggerInfo/rowSaved.js new file mode 100644 index 0000000000..3a21a26878 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowSaved.js @@ -0,0 +1,40 @@ +exports.definition = { + name: "Row Created", + event: "row:save", + icon: "ri-save-line", + tagline: "Row is added to {{inputs.enriched.table.name}}", + description: "Fired when a row is added to your database", + stepId: "ROW_SAVED", + inputs: {}, + schema: { + inputs: { + properties: { + tableId: { + type: "string", + customType: "table", + title: "Table", + }, + }, + required: ["tableId"], + }, + outputs: { + properties: { + row: { + type: "object", + customType: "row", + description: "The new row that was created", + }, + id: { + type: "string", + description: "Row ID - can be used for updating", + }, + revision: { + type: "string", + description: "Revision of row", + }, + }, + required: ["row", "id"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/rowUpdated.js b/packages/server/src/automations/triggerInfo/rowUpdated.js new file mode 100644 index 0000000000..099ce0a6b2 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowUpdated.js @@ -0,0 +1,40 @@ +exports.definition = { + name: "Row Updated", + event: "row:update", + icon: "ri-refresh-line", + tagline: "Row is updated in {{inputs.enriched.table.name}}", + description: "Fired when a row is updated in your database", + stepId: "ROW_UPDATED", + inputs: {}, + schema: { + inputs: { + properties: { + tableId: { + type: "string", + customType: "table", + title: "Table", + }, + }, + required: ["tableId"], + }, + outputs: { + properties: { + row: { + type: "object", + customType: "row", + description: "The row that was updated", + }, + id: { + type: "string", + description: "Row ID - can be used for updating", + }, + revision: { + type: "string", + description: "Revision of row", + }, + }, + required: ["row", "id"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/webhook.js b/packages/server/src/automations/triggerInfo/webhook.js new file mode 100644 index 0000000000..dd83031d8f --- /dev/null +++ b/packages/server/src/automations/triggerInfo/webhook.js @@ -0,0 +1,36 @@ +exports.definition = { + name: "Webhook", + event: "web:trigger", + icon: "ri-global-line", + tagline: "Webhook endpoint is hit", + description: "Trigger an automation when a HTTP POST webhook is hit", + stepId: "WEBHOOK", + inputs: {}, + schema: { + inputs: { + properties: { + schemaUrl: { + type: "string", + customType: "webhookUrl", + title: "Schema URL", + }, + triggerUrl: { + type: "string", + customType: "webhookUrl", + title: "Trigger URL", + }, + }, + required: ["schemaUrl", "triggerUrl"], + }, + outputs: { + properties: { + body: { + type: "object", + description: "Body of the request which hit the webhook", + }, + }, + required: ["body"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 2a2d68ecb1..ae98f0e73a 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -1,244 +1,22 @@ const CouchDB = require("../db") const emitter = require("../events/index") -const env = require("../environment") -const Queue = env.isTest() - ? require("../utilities/queue/inMemoryQueue") - : require("bull") const { getAutomationParams } = require("../db/utils") const { coerce } = require("../utilities/rowProcessor") -const { utils } = require("@budibase/auth/redis") -const { JobQueues } = require("../constants") -const { - isExternalTable, - breakExternalTableId, -} = require("../integrations/utils") -const { getExternalTable } = require("../api/controllers/table/utils") +const { definitions } = require("./triggerInfo") +const { isDevAppID } = require("../db/utils") +// need this to call directly, so we can get a response +const { queue } = require("./bullboard") +const { checkTestFlag } = require("../utilities/redis") +const utils = require("./utils") +const env = require("../environment") -const { opts } = utils.getRedisOptions() -let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) - -const FAKE_STRING = "TEST" -const FAKE_BOOL = false -const FAKE_NUMBER = 1 -const FAKE_DATETIME = "1970-01-01T00:00:00.000Z" - -const BUILTIN_DEFINITIONS = { - ROW_SAVED: { - name: "Row Created", - event: "row:save", - icon: "ri-save-line", - tagline: "Row is added to {{inputs.enriched.table.name}}", - description: "Fired when a row is added to your database", - stepId: "ROW_SAVED", - inputs: {}, - schema: { - inputs: { - properties: { - tableId: { - type: "string", - customType: "table", - title: "Table", - }, - }, - required: ["tableId"], - }, - outputs: { - properties: { - row: { - type: "object", - customType: "row", - description: "The new row that was created", - }, - id: { - type: "string", - description: "Row ID - can be used for updating", - }, - revision: { - type: "string", - description: "Revision of row", - }, - }, - required: ["row", "id"], - }, - }, - type: "TRIGGER", - }, - ROW_UPDATED: { - name: "Row Updated", - event: "row:update", - icon: "ri-refresh-line", - tagline: "Row is updated in {{inputs.enriched.table.name}}", - description: "Fired when a row is updated in your database", - stepId: "ROW_UPDATED", - inputs: {}, - schema: { - inputs: { - properties: { - tableId: { - type: "string", - customType: "table", - title: "Table", - }, - }, - required: ["tableId"], - }, - outputs: { - properties: { - row: { - type: "object", - customType: "row", - description: "The row that was updated", - }, - id: { - type: "string", - description: "Row ID - can be used for updating", - }, - revision: { - type: "string", - description: "Revision of row", - }, - }, - required: ["row", "id"], - }, - }, - type: "TRIGGER", - }, - ROW_DELETED: { - name: "Row Deleted", - event: "row:delete", - icon: "ri-delete-bin-line", - tagline: "Row is deleted from {{inputs.enriched.table.name}}", - description: "Fired when a row is deleted from your database", - stepId: "ROW_DELETED", - inputs: {}, - schema: { - inputs: { - properties: { - tableId: { - type: "string", - customType: "table", - title: "Table", - }, - }, - required: ["tableId"], - }, - outputs: { - properties: { - row: { - type: "object", - customType: "row", - description: "The row that was deleted", - }, - }, - required: ["row"], - }, - }, - type: "TRIGGER", - }, - WEBHOOK: { - name: "Webhook", - event: "web:trigger", - icon: "ri-global-line", - tagline: "Webhook endpoint is hit", - description: "Trigger an automation when a HTTP POST webhook is hit", - stepId: "WEBHOOK", - inputs: {}, - schema: { - inputs: { - properties: { - schemaUrl: { - type: "string", - customType: "webhookUrl", - title: "Schema URL", - }, - triggerUrl: { - type: "string", - customType: "webhookUrl", - title: "Trigger URL", - }, - }, - required: ["schemaUrl", "triggerUrl"], - }, - outputs: { - properties: { - body: { - type: "object", - description: "Body of the request which hit the webhook", - }, - }, - required: ["body"], - }, - }, - type: "TRIGGER", - }, - APP: { - name: "App Action", - event: "app:trigger", - icon: "ri-window-fill", - tagline: "Automation fired from the frontend", - description: "Trigger an automation from an action inside your app", - stepId: "APP", - inputs: {}, - schema: { - inputs: { - properties: { - fields: { - type: "object", - customType: "triggerSchema", - title: "Fields", - }, - }, - required: [], - }, - outputs: { - properties: { - fields: { - type: "object", - description: "Fields submitted from the app frontend", - }, - }, - required: ["fields"], - }, - }, - type: "TRIGGER", - }, - CRON: { - name: "Cron Trigger", - event: "cron:trigger", - icon: "ri-timer-line", - tagline: "Cron Trigger ({{inputs.cron}})", - description: "Triggers automation on a cron schedule.", - stepId: "CRON", - inputs: {}, - schema: { - inputs: { - properties: { - cron: { - type: "string", - customType: "cron", - title: "Expression", - }, - }, - required: ["cron"], - }, - outputs: { - properties: { - timestamp: { - type: "number", - description: "Timestamp the cron was executed", - }, - }, - required: ["timestamp"], - }, - }, - type: "TRIGGER", - }, -} +const TRIGGER_DEFINITIONS = definitions async function queueRelevantRowAutomations(event, eventType) { if (event.appId == null) { throw `No appId specified for ${eventType} - check event emitters.` } + const db = new CouchDB(event.appId) let automations = await db.allDocs( getAutomationParams(null, { include_docs: true }) @@ -255,14 +33,22 @@ async function queueRelevantRowAutomations(event, eventType) { for (let automation of automations) { let automationDef = automation.definition let automationTrigger = automationDef ? automationDef.trigger : {} + // don't queue events which are for dev apps, only way to test automations is + // running tests on them, in production the test flag will never + // be checked due to lazy evaluation (first always false) if ( - !automation.live || - !automationTrigger.inputs || - automationTrigger.inputs.tableId !== event.row.tableId + !env.ALLOW_DEV_AUTOMATIONS && + isDevAppID(event.appId) && + !(await checkTestFlag(automation._id)) ) { continue } - automationQueue.add({ automation, event }) + if ( + automationTrigger.inputs && + automationTrigger.inputs.tableId === event.row.tableId + ) { + await queue.add({ automation, event }) + } } } @@ -290,51 +76,12 @@ emitter.on("row:delete", async function (event) { await queueRelevantRowAutomations(event, "row:delete") }) -async function fillRowOutput(automation, params) { - let triggerSchema = automation.definition.trigger - let tableId = triggerSchema.inputs.tableId - try { - let table - if (!isExternalTable(tableId)) { - const db = new CouchDB(params.appId) - table = await db.get(tableId) - } else { - const { datasourceId, tableName } = breakExternalTableId(tableId) - table = await getExternalTable(params.appId, datasourceId, tableName) - } - let row = {} - for (let schemaKey of Object.keys(table.schema)) { - const paramValue = params[schemaKey] - let propSchema = table.schema[schemaKey] - switch (propSchema.constraints.type) { - case "string": - row[schemaKey] = paramValue || FAKE_STRING - break - case "boolean": - row[schemaKey] = paramValue || FAKE_BOOL - break - case "number": - row[schemaKey] = paramValue || FAKE_NUMBER - break - case "datetime": - row[schemaKey] = paramValue || FAKE_DATETIME - break - } - } - params.row = row - } catch (err) { - /* istanbul ignore next */ - throw "Failed to find table for trigger" - } - return params -} - -module.exports.externalTrigger = async function (automation, params) { - // TODO: replace this with allowing user in builder to input values in future +exports.externalTrigger = async function ( + automation, + params, + { getResponses } = {} +) { if (automation.definition != null && automation.definition.trigger != null) { - if (automation.definition.trigger.inputs.tableId != null) { - params = await fillRowOutput(automation, params) - } if (automation.definition.trigger.stepId === "APP") { // values are likely to be submitted as strings, so we shall convert to correct type const coercedFields = {} @@ -345,14 +92,12 @@ module.exports.externalTrigger = async function (automation, params) { params.fields = coercedFields } } - - automationQueue.add({ automation, event: params }) + const data = { automation, event: params } + if (getResponses) { + return utils.processEvent({ data }) + } else { + return queue.add(data) + } } -module.exports.getQueues = () => { - return [automationQueue] -} -module.exports.fillRowOutput = fillRowOutput -module.exports.automationQueue = automationQueue - -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS +exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js new file mode 100644 index 0000000000..4bef91a153 --- /dev/null +++ b/packages/server/src/automations/utils.js @@ -0,0 +1,159 @@ +const runner = require("./thread") +const { definitions } = require("./triggerInfo") +const webhooks = require("../api/controllers/webhook") +const CouchDB = require("../db") +const { queue } = require("./bullboard") +const newid = require("../db/newid") +const { updateEntityMetadata } = require("../utilities") +const { MetadataTypes } = require("../constants") + +const WH_STEP_ID = definitions.WEBHOOK.stepId +const CRON_STEP_ID = definitions.CRON.stepId + +exports.processEvent = async job => { + try { + // need to actually await these so that an error can be captured properly + return await runner(job) + } catch (err) { + console.error( + `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` + ) + return { err } + } +} + +exports.updateTestHistory = async (appId, automation, history) => { + return updateEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automation._id, + metadata => { + if (metadata && Array.isArray(metadata.history)) { + metadata.history.push(history) + } else { + metadata = { + history: [history], + } + } + return metadata + } + ) +} + +// end the repetition and the job itself +exports.disableAllCrons = async appId => { + const promises = [] + const jobs = await queue.getRepeatableJobs() + for (let job of jobs) { + if (job.key.includes(`${appId}_cron`)) { + promises.push(queue.removeRepeatableByKey(job.key)) + promises.push(queue.removeJobs(job.id)) + } + } + return Promise.all(promises) +} + +/** + * This function handles checking of any cron jobs that need to be enabled/updated. + * @param {string} appId The ID of the app in which we are checking for webhooks + * @param {object|undefined} automation The automation object to be updated. + */ +exports.enableCronTrigger = async (appId, automation) => { + const trigger = automation ? automation.definition.trigger : null + function isCronTrigger(auto) { + return ( + auto && + auto.definition.trigger && + auto.definition.trigger.stepId === CRON_STEP_ID + ) + } + // need to create cron job + if (isCronTrigger(automation)) { + // make a job id rather than letting Bull decide, makes it easier to handle on way out + const jobId = `${appId}_cron_${newid()}` + const job = await queue.add( + { + automation, + event: { appId, timestamp: Date.now() }, + }, + { repeat: { cron: trigger.inputs.cron }, jobId } + ) + // Assign cron job ID from bull so we can remove it later if the cron trigger is removed + trigger.cronJobId = job.id + const db = new CouchDB(appId) + const response = await db.put(automation) + automation._id = response.id + automation._rev = response.rev + } + return automation +} + +/** + * This function handles checking if any webhooks need to be created or deleted for automations. + * @param {string} appId The ID of the app in which we are checking for webhooks + * @param {object|undefined} oldAuto The old automation object if updating/deleting + * @param {object|undefined} newAuto The new automation object if creating/updating + * @returns {Promise} After this is complete the new automation object may have been updated and should be + * written to DB (this does not write to DB as it would be wasteful to repeat). + */ +exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => { + const oldTrigger = oldAuto ? oldAuto.definition.trigger : null + const newTrigger = newAuto ? newAuto.definition.trigger : null + const triggerChanged = + oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id + function isWebhookTrigger(auto) { + return ( + auto && + auto.definition.trigger && + auto.definition.trigger.stepId === WH_STEP_ID + ) + } + // need to delete webhook + if ( + isWebhookTrigger(oldAuto) && + (!isWebhookTrigger(newAuto) || triggerChanged) && + oldTrigger.webhookId + ) { + try { + let db = new CouchDB(appId) + // need to get the webhook to get the rev + const webhook = await db.get(oldTrigger.webhookId) + const ctx = { + appId, + params: { id: webhook._id, rev: webhook._rev }, + } + // might be updating - reset the inputs to remove the URLs + if (newTrigger) { + delete newTrigger.webhookId + newTrigger.inputs = {} + } + await webhooks.destroy(ctx) + } catch (err) { + // don't worry about not being able to delete, if it doesn't exist all good + } + } + // need to create webhook + if ( + (!isWebhookTrigger(oldAuto) || triggerChanged) && + isWebhookTrigger(newAuto) + ) { + const ctx = { + appId, + request: { + body: new webhooks.Webhook( + "Automation webhook", + webhooks.WebhookType.AUTOMATION, + newAuto._id + ), + }, + } + await webhooks.save(ctx) + const id = ctx.body.webhook._id + newTrigger.webhookId = id + newTrigger.inputs = { + schemaUrl: `api/webhooks/schema/${appId}/${id}`, + triggerUrl: `api/webhooks/trigger/${appId}/${id}`, + } + } + return newAuto +} diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index bc7b5b368f..bea58fd260 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -123,5 +123,10 @@ exports.BaseQueryVerbs = { DELETE: "delete", } +exports.MetadataTypes = { + AUTOMATION_TEST_INPUT: "automationTestInput", + AUTOMATION_TEST_HISTORY: "automationTestHistory", +} + // pass through the list from the auth/core lib exports.ObjectStoreBuckets = ObjectStoreBuckets diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 34be44336c..67412e7e89 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -36,6 +36,18 @@ exports.IncludeDocs = IncludeDocs exports.getLinkDocuments = getLinkDocuments exports.createLinkView = createLinkView +function clearRelationshipFields(table, rows) { + for (let [key, field] of Object.entries(table.schema)) { + if (field.type === FieldTypes.LINK) { + rows = rows.map(row => { + delete row[key] + return row + }) + } + } + return rows +} + async function getLinksForRows(appId, rows) { const tableIds = [...new Set(rows.map(el => el.tableId))] // start by getting all the link values for performance reasons @@ -126,33 +138,6 @@ exports.updateLinks = async function (args) { } } -/** - * Update a row with information about the links that pertain to it. - * @param {string} appId The instance in which this row has been created. - * @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be - * a single row object or an array of rows - both will be handled. - * @returns {Promise} The updated row (this may be the same if no links were found). If an array was input - * then an array will be output, object input -> object output. - */ -exports.attachLinkIDs = async (appId, rows) => { - const links = await getLinksForRows(appId, rows) - // now iterate through the rows and all field information - for (let row of rows) { - // find anything that matches the row's ID we are searching for and join it - links - .filter(el => el.thisId === row._id) - .forEach(link => { - if (row[link.fieldName] == null) { - row[link.fieldName] = [] - } - row[link.fieldName].push(link.id) - }) - } - // if it was an array when it came in then handle it as an array in response - // otherwise return the first element as there was only one input - return rows -} - /** * Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row. * This is required for formula fields, this may only be utilised internally (for now). @@ -173,6 +158,9 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) + // clear any existing links that could be dupe'd + rows = clearRelationshipFields(table, rows) + // now get the docs and combine into the rows let linked = await getFullLinkedDocs(ctx, appId, links) const linkedTables = [] for (let row of rows) { diff --git a/packages/server/src/db/tests/linkTests.spec.js b/packages/server/src/db/tests/linkTests.spec.js index 3fed6938b7..8dad7be049 100644 --- a/packages/server/src/db/tests/linkTests.spec.js +++ b/packages/server/src/db/tests/linkTests.spec.js @@ -59,16 +59,4 @@ describe("test link functionality", () => { expect(Array.isArray(output)).toBe(true) }) }) - - describe("attachLinkIDs", () => { - it("should be able to attach linkIDs", async () => { - await config.init() - await config.createTable() - const table = await config.createLinkedTable() - const row = await config.createRow() - const linkRow = await config.createRow(basicLinkedRow(table._id, row._id)) - const attached = await links.attachLinkIDs(config.getAppId(), [linkRow]) - expect(attached[0].link[0]).toBe(row._id) - }) - }) }) \ No newline at end of file diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 706d5ed207..ec1c267fa2 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -7,6 +7,8 @@ const { APP_PREFIX, SEPARATOR, StaticDatabases, + isDevAppID, + isProdAppID, } = require("@budibase/auth/db") const UNICODE_MAX = "\ufff0" @@ -36,6 +38,7 @@ const DocumentTypes = { DATASOURCE_PLUS: "datasource_plus", QUERY: "query", DEPLOYMENTS: "deployments", + METADATA: "metadata", } const ViewNames = { @@ -63,6 +66,8 @@ const BudibaseInternalDB = { exports.APP_PREFIX = APP_PREFIX exports.APP_DEV_PREFIX = APP_DEV_PREFIX +exports.isDevAppID = isDevAppID +exports.isProdAppID = isProdAppID exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.ViewNames = ViewNames @@ -331,6 +336,18 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => { ) } +exports.generateMetadataID = (type, entityId) => { + return `${DocumentTypes.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}` +} + +exports.getMetadataParams = (type, entityId = null, otherProps = {}) => { + let docId = `${type}${SEPARATOR}` + if (entityId != null) { + docId += entityId + } + return getDocParams(DocumentTypes.METADATA, docId, otherProps) +} + /** * This can be used with the db.allDocs to get a list of IDs */ diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 9f69664ffb..9e029e440a 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -55,6 +55,7 @@ module.exports = { BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY, USERID_API_KEY: process.env.USERID_API_KEY, DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS, _set(key, value) { process.env[key] = value module.exports[key] = value diff --git a/packages/server/src/middleware/appInfo.js b/packages/server/src/middleware/appInfo.js new file mode 100644 index 0000000000..ee3655c6cc --- /dev/null +++ b/packages/server/src/middleware/appInfo.js @@ -0,0 +1,19 @@ +const { isDevAppID, isProdAppID } = require("../db/utils") + +exports.AppType = { + DEV: "dev", + PROD: "prod", +} + +exports.middleware = + ({ appType } = {}) => + (ctx, next) => { + const appId = ctx.appId + if (appType === exports.AppType.DEV && appId && !isDevAppID(appId)) { + ctx.throw(400, "Only apps in development support this endpoint") + } + if (appType === exports.AppType.PROD && appId && !isProdAppID(appId)) { + ctx.throw(400, "Only apps in production support this endpoint") + } + return next() + } diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index ac8336e769..4647878721 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -14,7 +14,7 @@ const DOMAIN_MAP = { views: usageQuota.Properties.VIEW, users: usageQuota.Properties.USER, // this will not be updated by endpoint calls - // instead it will be updated by triggers + // instead it will be updated by triggerInfo automationRuns: usageQuota.Properties.AUTOMATION, } diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index b19d7551e4..5226fd66ca 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -14,18 +14,12 @@ const { downloadTarball, } = require("./utilities") const { updateClientLibrary } = require("./clientLibrary") -const download = require("download") const env = require("../../environment") -const { homedir } = require("os") -const fetch = require("node-fetch") const { USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX, } = require("../../db/utils") -const DEFAULT_AUTOMATION_BUCKET = - "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" -const DEFAULT_AUTOMATION_DIRECTORY = ".budibase-automations" const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") @@ -205,30 +199,6 @@ exports.getComponentLibraryManifest = async (appId, library) => { return JSON.parse(resp) } -exports.automationInit = async () => { - const directory = - env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) - const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }) - } - // env setup to get async packages - let response = await fetch(`${bucket}/manifest.json`) - return response.json() -} - -exports.getExternalAutomationStep = async (name, version, bundleName) => { - const directory = - env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) - const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET - try { - return require(join(directory, bundleName)) - } catch (err) { - await download(`${bucket}/${name}/${version}/${bundleName}`, directory) - return require(join(directory, bundleName)) - } -} - /** * All file reads come through here just to make sure all of them make sense * allows a centralised location to check logic is all good. diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index dec17ab284..a81f9ddcf5 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,8 @@ const env = require("../environment") const { OBJ_STORE_DIRECTORY } = require("../constants") const { sanitizeKey } = require("@budibase/auth/src/objectStore") +const CouchDB = require("../db") +const { generateMetadataID } = require("../db/utils") const BB_CDN = "https://cdn.budi.live" @@ -55,3 +57,52 @@ exports.attachmentsRelativeURL = attachmentKey => { `${exports.objectStoreUrl()}/${attachmentKey}` ) } + +exports.updateEntityMetadata = async (appId, type, entityId, updateFn) => { + const db = new CouchDB(appId) + const id = generateMetadataID(type, entityId) + // read it to see if it exists, we'll overwrite it no matter what + let rev, + metadata = {} + try { + const oldMetadata = await db.get(id) + rev = oldMetadata._rev + metadata = updateFn(oldMetadata) + } catch (err) { + rev = null + metadata = updateFn({}) + } + metadata._id = id + if (rev) { + metadata._rev = rev + } + const response = await db.put(metadata) + return { + ...metadata, + _id: id, + _rev: response.rev, + } +} + +exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { + return exports.updateEntityMetadata(appId, type, entityId, () => { + return metadata + }) +} + +exports.deleteEntityMetadata = async (appId, type, entityId) => { + const db = new CouchDB(appId) + const id = generateMetadataID(type, entityId) + let rev + try { + const metadata = await db.get(id) + if (metadata) { + rev = metadata._rev + } + } catch (err) { + // don't need to error if it doesn't exist + } + if (id && rev) { + await db.remove(id, rev) + } +} diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index 6f1cf46606..151feabdbb 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -2,20 +2,24 @@ const { Client, utils } = require("@budibase/auth/redis") const { getGlobalIDFromUserMetadataID } = require("../db/utils") const APP_DEV_LOCK_SECONDS = 600 -let devAppClient, debounceClient +const AUTOMATION_TEST_FLAG_SECONDS = 60 +let devAppClient, debounceClient, flagClient // we init this as we want to keep the connection open all the time // reduces the performance hit exports.init = async () => { devAppClient = new Client(utils.Databases.DEV_LOCKS) debounceClient = new Client(utils.Databases.DEBOUNCE) + flagClient = new Client(utils.Databases.FLAGS) await devAppClient.init() await debounceClient.init() + await flagClient.init() } exports.shutdown = async () => { if (devAppClient) await devAppClient.finish() if (debounceClient) await debounceClient.finish() + if (flagClient) await flagClient.finish() } exports.doesUserHaveLock = async (devAppId, user) => { @@ -67,3 +71,16 @@ exports.checkDebounce = async id => { exports.setDebounce = async (id, seconds) => { await debounceClient.store(id, "debouncing", seconds) } + +exports.setTestFlag = async id => { + await flagClient.store(id, { testing: true }, AUTOMATION_TEST_FLAG_SECONDS) +} + +exports.checkTestFlag = async id => { + const flag = await flagClient.get(id) + return !!(flag && flag.testing) +} + +exports.clearTestFlag = async id => { + await devAppClient.delete(id) +} diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js index 7cb3ac7e02..bb4ac98bb7 100644 --- a/packages/server/src/utilities/rowProcessor/index.js +++ b/packages/server/src/utilities/rowProcessor/index.js @@ -185,10 +185,16 @@ exports.inputProcessing = (user = {}, table, row) => { * @param {object} ctx the request which is looking for enriched rows. * @param {object} table the table from which these rows came from originally, this is used to determine * the schema of the rows and then enrich. - * @param {object[]} rows the rows which are to be enriched. - * @returns {object[]} the enriched rows will be returned. + * @param {object[]|object} rows the rows which are to be enriched. + * @param {object} opts used to set some options for the output, such as disabling relationship squashing. + * @returns {object[]|object} the enriched rows will be returned. */ -exports.outputProcessing = async (ctx, table, rows) => { +exports.outputProcessing = async ( + ctx, + table, + rows, + opts = { squash: true } +) => { const appId = ctx.appId let wasArray = true if (!(rows instanceof Array)) { @@ -214,6 +220,12 @@ exports.outputProcessing = async (ctx, table, rows) => { } } } - enriched = await linkRows.squashLinksToPrimaryDisplay(appId, table, enriched) + if (opts.squash) { + enriched = await linkRows.squashLinksToPrimaryDisplay( + appId, + table, + enriched + ) + } return wasArray ? enriched : enriched[0] }