diff --git a/.gitignore b/.gitignore index b6cfa424ad..a84d25d46d 100644 --- a/.gitignore +++ b/.gitignore @@ -83,4 +83,7 @@ typings/ .DS_Store # Nova Editor -.nova \ No newline at end of file +.nova + +# swap files (linux) +*.swp diff --git a/hosting/build/docker-compose.yaml b/hosting/build/docker-compose.yaml deleted file mode 100644 index 6988e3841b..0000000000 --- a/hosting/build/docker-compose.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: "3" - -services: - app-service: - build: ./server - volumes: - - ./server:/app - environment: - SELF_HOSTED: 1 - PORT: 4002 - - worker-service: - build: ./worker - environment: - SELF_HOSTED: 1, - PORT: 4003 diff --git a/hosting/build/server b/hosting/build/server deleted file mode 120000 index c40730cce5..0000000000 --- a/hosting/build/server +++ /dev/null @@ -1 +0,0 @@ -../../packages/server/ \ No newline at end of file diff --git a/hosting/build/worker b/hosting/build/worker deleted file mode 120000 index 8582fefbee..0000000000 --- a/hosting/build/worker +++ /dev/null @@ -1 +0,0 @@ -../../packages/worker/ \ No newline at end of file diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml new file mode 100644 index 0000000000..39fcb7ec83 --- /dev/null +++ b/hosting/docker-compose.dev.yaml @@ -0,0 +1,76 @@ +version: "3" + +# optional ports are specified throughout for more advanced use cases. + +services: + minio-service: + container_name: budi-minio-dev + restart: always + image: minio/minio + volumes: + - minio_data:/data + ports: + - "${MINIO_PORT}:9000" + environment: + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + MINIO_BROWSER: "off" + command: server /data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + + proxy-service: + container_name: budi-envoy-dev + restart: always + image: envoyproxy/envoy:v1.16-latest + volumes: + - ./envoy.dev.yaml:/etc/envoy/envoy.yaml + ports: + - "${MAIN_PORT}:10000" + #- "9901:9901" + depends_on: + - minio-service + - couchdb-service + + couchdb-service: + container_name: budi-couchdb-dev + restart: always + image: apache/couchdb:3.0 + environment: + - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} + - COUCHDB_USER=${COUCH_DB_USER} + ports: + - "${COUCH_DB_PORT}:5984" + #- "4369:4369" + #- "9100:9100" + volumes: + - couchdb_data:/opt/couchdb/data + + couch-init: + image: curlimages/curl + environment: + PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984" + depends_on: + - couchdb-service + command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] + + redis-service: + container_name: budi-redis-dev + restart: always + image: redis + ports: + - "${REDIS_PORT}:6379" + volumes: + - redis_data:/data + + +volumes: + couchdb_data: + driver: local + minio_data: + driver: local + redis_data: + driver: local diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index e8408d9a7d..86269837c2 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -11,13 +11,18 @@ services: - "${APP_PORT}:4002" environment: SELF_HOSTED: 1 - CLOUD: 1 COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 WORKER_URL: http://worker-service:4003 + MINIO_URL: http://minio-service:9000 + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} HOSTING_KEY: ${HOSTING_KEY} BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT} PORT: 4002 JWT_SECRET: ${JWT_SECRET} + LOG_LEVEL: info + SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 + ENABLE_ANALYTICS: true depends_on: - worker-service @@ -28,7 +33,7 @@ services: ports: - "${WORKER_PORT}:4003" environment: - SELF_HOSTED: 1, + SELF_HOSTED: 1 PORT: 4003 MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} @@ -93,9 +98,19 @@ services: depends_on: - couchdb-service command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] + + redis-service: + restart: always + image: redis + ports: + - "${REDIS_PORT}:6379" + volumes: + - redis_data:/data volumes: couchdb_data: driver: local minio_data: driver: local + redis_data: + driver: local diff --git a/hosting/envoy.dev.yaml b/hosting/envoy.dev.yaml new file mode 100644 index 0000000000..e12bc2c0e0 --- /dev/null +++ b/hosting/envoy.dev.yaml @@ -0,0 +1,79 @@ +static_resources: + listeners: + - name: main_listener + address: + socket_address: { address: 0.0.0.0, port_value: 10000 } + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress + codec_type: auto + route_config: + name: local_route + virtual_hosts: + - name: local_services + domains: ["*"] + routes: + - match: { prefix: "/db/" } + route: + cluster: couchdb-service + prefix_rewrite: "/" + + - match: { prefix: "/cache/" } + route: + cluster: redis-service + prefix_rewrite: "/" + + # minio is on the default route because this works + # best, minio + AWS SDK doesn't handle path proxy + - match: { prefix: "/" } + route: + cluster: minio-service + + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: minio-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: minio-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: minio-service + port_value: 9000 + + - name: couchdb-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: couchdb-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: couchdb-service + port_value: 5984 + + - name: redis-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: redis-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: redis-service + port_value: 6379 diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 11f5c81b99..8c6081d1a7 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -36,6 +36,11 @@ static_resources: cluster: worker-service prefix_rewrite: "/" + - match: { prefix: "/cache/" } + route: + cluster: redis-service + prefix_rewrite: "/" + - match: { prefix: "/db/" } route: cluster: couchdb-service @@ -107,3 +112,18 @@ static_resources: address: couchdb-service port_value: 5984 + - name: redis-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: redis-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: redis-service + port_value: 6379 + + diff --git a/hosting/hosting.properties b/hosting/hosting.properties index ad047a3826..138e66d629 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -18,4 +18,5 @@ APP_PORT=4002 WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 +REDIS_PORT=6379 BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index 09b38ae985..84c50ae863 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -3,6 +3,7 @@ "video": true, "projectId": "bmbemn", "env": { - "PORT": "4001" + "PORT": "4001", + "JWT_SECRET": "test" } } diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 6848d38cba..7b849eb887 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -1,25 +1,26 @@ -// What this script does: -// 1. Removes the old test folder if it exists (.budibase) -// 2. Initialises using `.budibase` -// 3. Runs the server using said folder - -const { join, resolve } = require("path") -const initialiseBudibase = require("../../server/src/utilities/initialiseBudibase") const cypressConfig = require("../cypress.json") +const path = require("path") -const homedir = join(require("os").homedir(), ".budibase") +const tmpdir = path.join(require("os").tmpdir(), ".budibase") process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" process.env.NODE_ENV = "cypress" process.env.ENABLE_ANALYTICS = "false" process.env.PORT = cypressConfig.env.PORT +process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET +process.env.COUCH_URL = `leveldb://${tmpdir}/.data/` +process.env.SELF_HOSTED = 1 +process.env.MINIO_URL = "http://localhost:10000/" +process.env.MINIO_ACCESS_KEY = "budibase" +process.env.MINIO_SECRET_KEY = "budibase" +process.env.COUCH_DB_USER = "budibase" +process.env.COUCH_DB_PASSWORD = "budibase" // Stop info logs polluting test outputs process.env.LOG_LEVEL = "error" -async function run(dir) { - process.env.BUDIBASE_DIR = resolve(dir) - require("dotenv").config({ path: resolve(dir, ".env") }) +async function run() { + // require("dotenv").config({ path: resolve(dir, ".env") }) // dont make this a variable or top level require // it will cause environment module to be loaded prematurely @@ -27,12 +28,15 @@ async function run(dir) { server.on("close", () => console.log("Server Closed")) } -initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) - .then(() => { - delete require.cache[require.resolve("../../server/src/environment")] - const xPlatHomeDir = homedir.startsWith("~") - ? join(homedir(), homedir.substring(1)) - : homedir - run(xPlatHomeDir) - }) - .catch(e => console.error(e)) +run() + +// TODO: ensure that this still works +// initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) +// .then(() => { +// delete require.cache[require.resolve("../../server/src/environment")] +// const xPlatHomeDir = homedir.startsWith("~") +// ? join(homedir(), homedir.substring(1)) +// : homedir +// run(xPlatHomeDir) +// }) +// .catch(e => console.error(e)) diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte index 7570cd6c5e..8c2ea880ae 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte @@ -27,7 +27,7 @@ notifier.success("Datasource deleted") // navigate to first index page if the source you are deleting is selected if (wasSelectedSource === datasource._id) { - $goto('./datasource') + $goto("./datasource") } hideEditor() } diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index 60886b5be1..0a186375d8 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -37,13 +37,13 @@ } async function deleteTable() { - const wasSelectedTable = $backendUiStore.selectedTable + const wasSelectedTable = $backendUiStore.selectedTable await backendUiStore.actions.tables.delete(table) store.actions.screens.delete(templateScreens) await backendUiStore.actions.tables.fetch() notifier.success("Table deleted") if (wasSelectedTable._id === table._id) { - $goto('./table') + $goto("./table") } hideEditor() } diff --git a/packages/cli/src/hosting/makeEnv.js b/packages/cli/src/hosting/makeEnv.js index c8359dd5e4..318a72def1 100644 --- a/packages/cli/src/hosting/makeEnv.js +++ b/packages/cli/src/hosting/makeEnv.js @@ -26,6 +26,7 @@ APP_PORT=4002 WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 +REDIS_PORT=6379 BUDIBASE_ENVIRONMENT=PRODUCTION` } diff --git a/packages/server/.env.template b/packages/server/.env.template deleted file mode 100644 index b2ff5be3f4..0000000000 --- a/packages/server/.env.template +++ /dev/null @@ -1,17 +0,0 @@ -# url of couch db, including username and password -# http://admin:password@localhost:5984 -COUCH_DB_URL={{couchDbUrl}} - -# identifies a client database - i.e. group of apps -CLIENT_ID={{clientId}} - -# used to create cookie hashes -JWT_SECRET={{cookieKey1}} - -# error level for koa-pino -LOG_LEVEL=info - -DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/" -DEPLOYMENT_DB_URL="https://couchdb.budi.live:5984" -SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 -ENABLE_ANALYTICS="true" diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index d75fe1f5d0..c13022c2d6 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -2,7 +2,6 @@ FROM node:12-alpine WORKDIR /app -ENV CLOUD=1 ENV PORT=4001 ENV COUCH_DB_URL=https://couchdb.budi.live:5984 ENV BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/packages/server/package.json b/packages/server/package.json index 6746b01c89..36cd215423 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -36,7 +36,10 @@ "test:integration": "jest --coverage --detectOpenHandles", "test:watch": "jest --watch", "run:docker": "node src/index", - "dev:builder": "cross-env PORT=4001 nodemon src/index.js", + "dev:stack:up": "node scripts/dev/manage.js up", + "dev:stack:down": "node scripts/dev/manage.js down", + "dev:stack:nuke": "node scripts/dev/manage.js nuke", + "dev:builder": "npm run dev:stack:up && nodemon src/index.js", "electron": "electron src/electron.js", "build:electron": "electron-builder --dir", "publish:electron": "electron-builder -mwl --publish always", @@ -63,7 +66,7 @@ "!src/tests/**/*", "!src/automations/tests/**/*", "!src/utilities/fileProcessor.js", - "!src/utilities/initialiseBudibase.js" + "!src/utilities/fileSystem/**/*" ], "coverageReporters": [ "lcov", @@ -134,7 +137,7 @@ "devDependencies": { "@budibase/standard-components": "^0.8.9", "@jest/test-sequencer": "^24.8.0", - "cross-env": "^7.0.3", + "docker-compose": "^0.23.6", "electron": "10.1.3", "electron-builder": "^22.9.1", "electron-builder-notarize": "^1.1.2", diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js new file mode 100644 index 0000000000..af4f67d23a --- /dev/null +++ b/packages/server/scripts/dev/manage.js @@ -0,0 +1,96 @@ +#!/usr/bin/env node +const compose = require("docker-compose") +const path = require("path") +const fs = require("fs") + +// This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands. +const CONFIG = { + cwd: path.resolve(process.cwd(), "../../hosting"), + config: "docker-compose.dev.yaml", + log: true, +} + +const Commands = { + Up: "up", + Down: "down", + Nuke: "nuke", +} + +async function init() { + const envFilePath = path.join(process.cwd(), ".env") + if (fs.existsSync(envFilePath)) { + return + } + const envFileJson = { + PORT: 4001, + MINIO_URL: "http://localhost:10000/", + COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", + WORKER_URL: "http://localhost:4002", + JWT_SECRET: "testsecret", + MINIO_ACCESS_KEY: "budibase", + MINIO_SECRET_KEY: "budibase", + COUCH_DB_PASSWORD: "budibase", + COUCH_DB_USER: "budibase", + SELF_HOSTED: 1, + } + let envFile = "" + Object.keys(envFileJson).forEach(key => { + envFile += `${key}=${envFileJson[key]}\n` + }) + fs.writeFileSync(envFilePath, envFile) +} + +async function up() { + console.log("Spinning up your budibase dev environment... πŸ”§βœ¨") + await init() + await compose.upAll(CONFIG) +} + +async function down() { + console.log("Spinning down your budibase dev environment... πŸŒ‡") + await compose.stop(CONFIG) +} + +async function nuke() { + console.log( + "Clearing down your budibase dev environment, including all containers and volumes... πŸ’₯" + ) + await compose.down(CONFIG) +} + +const managementCommand = process.argv.slice(2)[0] + +if ( + !managementCommand || + !Object.values(Commands).some(command => managementCommand === command) +) { + throw new Error( + "You must supply either an 'up', 'down' or 'nuke' commmand to manage the budibase development environment." + ) +} + +let command +switch (managementCommand) { + case Commands.Up: + command = up + break + case Commands.Down: + command = down + break + case Commands.Nuke: + command = nuke + break + default: + command = up +} + +command() + .then(() => { + console.log("Done! πŸŽ‰") + }) + .catch(err => { + console.error( + "Something went wrong while managing budibase dev environment:", + err.message + ) + }) diff --git a/packages/server/scripts/exportAppTemplate.js b/packages/server/scripts/exportAppTemplate.js index e896917d5b..34988cda37 100755 --- a/packages/server/scripts/exportAppTemplate.js +++ b/packages/server/scripts/exportAppTemplate.js @@ -1,6 +1,10 @@ #!/usr/bin/env node -const { exportTemplateFromApp } = require("../src/utilities/templates") const yargs = require("yargs") +const fs = require("fs") +const { join } = require("path") +const CouchDB = require("../src/db") +// load environment +const env = require("../src/environment") // Script to export a chosen budibase app into a package // Usage: ./scripts/exportAppTemplate.js export --name=Funky --appId=appId @@ -22,18 +26,26 @@ yargs }, }, async args => { + if (!env.isDev()) { + throw "Only works in dev" + } + const name = args.name, + appId = args.appId console.log("Exporting app..") - if (args.name == null || args.appId == null) { + if (name == null || appId == null) { console.error( "Unable to export without a name and app ID being specified, check help for more info." ) return } - const exportPath = await exportTemplateFromApp({ - templateName: args.name, - appId: args.appId, - }) - console.log(`Template ${args.name} exported to ${exportPath}`) + const exportPath = join(process.cwd(), name, "db") + fs.ensureDirSync(exportPath) + const writeStream = fs.createWriteStream(join(exportPath, "dump.text")) + // perform couch dump + + const instanceDb = new CouchDB(appId) + await instanceDb.dump(writeStream, {}) + console.log(`Template ${name} exported to ${exportPath}`) } ) .help() diff --git a/packages/server/scripts/initialise.js b/packages/server/scripts/initialise.js deleted file mode 100644 index 485ebd56a0..0000000000 --- a/packages/server/scripts/initialise.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path") -const { homedir } = require("os") - -const initialiseBudibase = require("../src/utilities/initialiseBudibase") -const DIRECTORY = "~/.budibase" - -function run() { - let opts = {} - let dir = DIRECTORY - opts.quiet = true - opts.dir = dir.startsWith("~") ? join(homedir(), dir.substring(1)) : dir - return initialiseBudibase(opts) -} - -run().then(() => { - console.log("Init complete.") -}) diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 96754f17cc..1c8caba1cb 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,56 +1,32 @@ -const fs = require("fs") -const { join } = require("../../utilities/centralPath") -const readline = require("readline") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") -const env = require("../../environment") -const ENV_FILE_PATH = "/.env" +const builderDB = require("../../db/builder") exports.fetch = async function(ctx) { - ctx.status = 200 - ctx.body = { - budibase: env.BUDIBASE_API_KEY, - userId: env.USERID_API_KEY, + try { + const mainDoc = await builderDB.getBuilderMainDoc() + ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} + } catch (err) { + /* istanbul ignore next */ + ctx.throw(400, err) } } exports.update = async function(ctx) { - const key = `${ctx.params.key.toUpperCase()}_API_KEY` + const key = ctx.params.key const value = ctx.request.body.value - // set environment variables - env._set(key, value) - - // Write to file - await updateValues([key, value]) - - ctx.status = 200 - ctx.message = `Updated ${ctx.params.key} API key succesfully.` - ctx.body = { [ctx.params.key]: ctx.request.body.value } -} - -async function updateValues([key, value]) { - let newContent = "" - let keyExists = false - let envPath = join(budibaseAppsDir(), ENV_FILE_PATH) - const readInterface = readline.createInterface({ - input: fs.createReadStream(envPath), - output: process.stdout, - console: false, - }) - readInterface.on("line", function(line) { - // Mutate lines and change API Key - if (line.startsWith(key)) { - line = `${key}=${value}` - keyExists = true + try { + const mainDoc = await builderDB.getBuilderMainDoc() + if (mainDoc.apiKeys == null) { + mainDoc.apiKeys = {} } - newContent = `${newContent}\n${line}` - }) - readInterface.on("close", function() { - // Write file here - if (!keyExists) { - // Add API Key if it doesn't exist in the file at all - newContent = `${newContent}\n${key}=${value}` + mainDoc.apiKeys[key] = value + const resp = await builderDB.setBuilderMainDoc(mainDoc) + ctx.body = { + _id: resp.id, + _rev: resp.rev, } - fs.writeFileSync(envPath, newContent) - }) + } catch (err) { + /* istanbul ignore next */ + ctx.throw(400, err) + } } diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 9e50319d5d..c5d48038ce 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -1,15 +1,14 @@ const CouchDB = require("../../db") -const compileStaticAssets = require("../../utilities/builder/compileStaticAssets") const env = require("../../environment") -const { existsSync } = require("fs-extra") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") const setBuilderToken = require("../../utilities/builder/setBuilderToken") -const fs = require("fs-extra") -const { join, resolve } = require("../../utilities/centralPath") const packageJson = require("../../../package.json") const { createLinkView } = require("../../db/linkedRows") const { createRoutingView } = require("../../utilities/routing") -const { downloadTemplate } = require("../../utilities/templates") +const { + getTemplateStream, + createApp, + deleteApp, +} = require("../../utilities/fileSystem") const { generateAppID, getLayoutParams, @@ -20,9 +19,6 @@ const { BUILTIN_ROLE_IDS, AccessController, } = require("../../utilities/security/roles") -const { - downloadExtractComponentLibraries, -} = require("../../utilities/createAppPackage") const { BASE_LAYOUTS } = require("../../constants/layouts") const { createHomeScreen, @@ -32,11 +28,7 @@ const { cloneDeep } = require("lodash/fp") const { processObject } = require("@budibase/string-templates") const { getAllApps } = require("../../utilities") const { USERS_TABLE_SCHEMA } = require("../../constants") -const { - getDeployedApps, - getHostingInfo, - HostingTypes, -} = require("../../utilities/builder/hosting") +const { getDeployedApps } = require("../../utilities/builder/hosting") const URL_REGEX_SLASH = /\/|\\/g @@ -75,8 +67,7 @@ async function getAppUrlIfNotInUse(ctx) { url = encodeURI(`${ctx.request.body.name}`) } url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase() - const hostingInfo = await getHostingInfo() - if (hostingInfo.type === HostingTypes.CLOUD) { + if (!env.SELF_HOSTED) { return url } const deployedApps = await getDeployedApps() @@ -106,16 +97,7 @@ async function createInstance(template) { // this is currently very hard to test, downloading and importing template files /* istanbul ignore next */ if (template && template.useTemplate === "true") { - let dbDumpReadStream - if (template.file) { - dbDumpReadStream = fs.createReadStream(template.file.path) - } else { - const templatePath = await downloadTemplate(...template.key.split("/")) - dbDumpReadStream = fs.createReadStream( - join(templatePath, "db", "dump.txt") - ) - } - const { ok } = await db.load(dbDumpReadStream) + const { ok } = await db.load(await getTemplateStream(template)) if (!ok) { throw "Error loading database dump from template." } @@ -190,10 +172,10 @@ exports.create = async function(ctx) { const instanceDb = new CouchDB(appId) await instanceDb.put(newApplication) - const newAppFolder = await createEmptyAppPackage(ctx, newApplication) + await createEmptyAppPackage(ctx, newApplication) /* istanbul ignore next */ - if (env.NODE_ENV !== "jest") { - await downloadExtractComponentLibraries(newAppFolder) + if (!env.isTest()) { + await createApp(appId) } await setBuilderToken(ctx, appId, version) @@ -222,11 +204,10 @@ exports.delete = async function(ctx) { const db = new CouchDB(ctx.params.appId) const app = await db.get(ctx.params.appId) const result = await db.destroy() - - // remove top level directory - await fs.rmdir(join(budibaseAppsDir(), ctx.params.appId), { - recursive: true, - }) + /* istanbul ignore next */ + if (!env.isTest()) { + await deleteApp(ctx.params.appId) + } ctx.status = 200 ctx.message = `Application ${app.name} deleted successfully.` @@ -234,17 +215,8 @@ exports.delete = async function(ctx) { } const createEmptyAppPackage = async (ctx, app) => { - const appsFolder = budibaseAppsDir() - const newAppFolder = resolve(appsFolder, app._id) - const db = new CouchDB(app._id) - if (existsSync(newAppFolder)) { - ctx.throw(400, "App folder already exists for this application") - } - - fs.mkdirpSync(newAppFolder) - let screensAndLayouts = [] for (let layout of BASE_LAYOUTS) { const cloned = cloneDeep(layout) @@ -260,6 +232,4 @@ const createEmptyAppPackage = async (ctx, app) => { screensAndLayouts.push(loginScreen) await db.bulkDocs(screensAndLayouts) - await compileStaticAssets(app._id) - return newAppFolder } diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index e5c0f9a029..fc486bcb50 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -45,9 +45,9 @@ exports.authenticate = async ctx => { roleId: dbUser.roleId, version: app.version, } - // if in cloud add the user api key, unless self hosted + // if in prod add the user api key, unless self hosted /* istanbul ignore next */ - if (env.CLOUD && !env.SELF_HOSTED) { + if (env.isProd() && !env.SELF_HOSTED) { const { apiKey } = await getAPIKey(ctx.user.appId) payload.apiKey = apiKey } diff --git a/packages/server/src/api/controllers/backup.js b/packages/server/src/api/controllers/backup.js index a83f96165b..02be10bbec 100644 --- a/packages/server/src/api/controllers/backup.js +++ b/packages/server/src/api/controllers/backup.js @@ -1,28 +1,10 @@ -const { performDump } = require("../../utilities/templates") -const path = require("path") -const os = require("os") -const fs = require("fs-extra") +const { performBackup } = require("../../utilities/fileSystem") exports.exportAppDump = async function(ctx) { const { appId } = ctx.query - const appname = decodeURI(ctx.query.appname) - - const backupsDir = path.join(os.homedir(), ".budibase", "backups") - fs.ensureDirSync(backupsDir) - const backupIdentifier = `${appname}Backup${new Date().getTime()}.txt` - await performDump({ - dir: backupsDir, - appId, - name: backupIdentifier, - }) - - ctx.status = 200 - - const backupFile = path.join(backupsDir, backupIdentifier) - ctx.attachment(backupIdentifier) - ctx.body = fs.createReadStream(backupFile) + ctx.body = await performBackup(appId, backupIdentifier) } diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index 092d154817..a9b88a5bff 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -1,44 +1,30 @@ const CouchDB = require("../../db") -const { resolve, join } = require("../../utilities/centralPath") -const { - budibaseTempDir, - budibaseAppsDir, -} = require("../../utilities/budibaseDir") +const { getComponentLibraryManifest } = require("../../utilities/fileSystem") exports.fetchAppComponentDefinitions = async function(ctx) { const appId = ctx.params.appId || ctx.appId const db = new CouchDB(appId) const app = await db.get(appId) - ctx.body = app.componentLibraries.reduce((acc, componentLibrary) => { - let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules") + let componentManifests = await Promise.all( + app.componentLibraries.map(async library => { + let manifest = await getComponentLibraryManifest(appId, library) - if (ctx.isDev) { - appDirectory = budibaseTempDir() - } - - const componentJson = require(join( - appDirectory, - componentLibrary, - ctx.isDev ? "" : "package", - "manifest.json" - )) - - const result = {} - - // map over the components.json and add the library identifier as a key - // button -> @budibase/standard-components/button - for (let key of Object.keys(componentJson)) { - const fullComponentName = `${componentLibrary}/${key}`.toLowerCase() - result[fullComponentName] = { + return { + manifest, + library, + } + }) + ) + const definitions = {} + for (let { manifest, library } of componentManifests) { + for (let key of Object.keys(manifest)) { + const fullComponentName = `${library}/${key}`.toLowerCase() + definitions[fullComponentName] = { component: fullComponentName, - ...componentJson[key], + ...manifest[key], } } - - return { - ...acc, - ...result, - } - }, {}) + } + ctx.body = definitions } diff --git a/packages/server/src/api/controllers/deploy/awsDeploy.js b/packages/server/src/api/controllers/deploy/awsDeploy.js index 18c9279515..2d34bc1b04 100644 --- a/packages/server/src/api/controllers/deploy/awsDeploy.js +++ b/packages/server/src/api/controllers/deploy/awsDeploy.js @@ -66,12 +66,7 @@ exports.deploy = async function(deployment) { const appId = deployment.getAppId() const { bucket, accountId } = deployment.getVerification() const metadata = { accountId } - const s3Client = new AWS.S3({ - params: { - Bucket: bucket, - }, - }) - await deployToObjectStore(appId, s3Client, metadata) + await deployToObjectStore(appId, bucket, metadata) } exports.replicateDb = async function(deployment) { diff --git a/packages/server/src/api/controllers/deploy/selfDeploy.js b/packages/server/src/api/controllers/deploy/selfDeploy.js index 81fa72cae5..444e7cd873 100644 --- a/packages/server/src/api/controllers/deploy/selfDeploy.js +++ b/packages/server/src/api/controllers/deploy/selfDeploy.js @@ -7,7 +7,6 @@ const { const { getWorkerUrl, getCouchUrl, - getMinioUrl, getSelfHostKey, } = require("../../../utilities/builder/hosting") @@ -45,17 +44,9 @@ exports.postDeployment = async function() { exports.deploy = async function(deployment) { const appId = deployment.getAppId() const verification = deployment.getVerification() - const objClient = new AWS.S3({ - endpoint: await getMinioUrl(), - s3ForcePathStyle: true, // needed with minio? - signatureVersion: "v4", - params: { - Bucket: verification.bucket, - }, - }) // no metadata, aws has account ID in metadata const metadata = {} - await deployToObjectStore(appId, objClient, metadata) + await deployToObjectStore(appId, verification.bucket, metadata) } exports.replicateDb = async function(deployment) { diff --git a/packages/server/src/api/controllers/deploy/utils.js b/packages/server/src/api/controllers/deploy/utils.js index 3536a6f630..de608acfb1 100644 --- a/packages/server/src/api/controllers/deploy/utils.js +++ b/packages/server/src/api/controllers/deploy/utils.js @@ -1,16 +1,24 @@ -const fs = require("fs") -const sanitize = require("sanitize-s3-objectkey") -const { walkDir } = require("../../../utilities") const { join } = require("../../../utilities/centralPath") +const fs = require("fs") const { budibaseAppsDir } = require("../../../utilities/budibaseDir") const fetch = require("node-fetch") const PouchDB = require("../../../db") const CouchDB = require("pouchdb") +const { upload } = require("../../../utilities/fileSystem") -const CONTENT_TYPE_MAP = { - html: "text/html", - css: "text/css", - js: "application/javascript", +// TODO: everything in this file is to be removed + +function walkDir(dirPath, callback) { + for (let filename of fs.readdirSync(dirPath)) { + const filePath = `${dirPath}/${filename}` + const stat = fs.lstatSync(filePath) + + if (stat.isFile()) { + callback(filePath) + } else { + walkDir(filePath, callback) + } + } } exports.fetchCredentials = async function(url, body) { @@ -34,30 +42,25 @@ exports.fetchCredentials = async function(url, body) { return json } -exports.prepareUpload = async function({ s3Key, metadata, client, file }) { - const extension = [...file.name.split(".")].pop() - const fileBytes = fs.readFileSync(file.path) - - const upload = await client - .upload({ - // windows file paths need to be converted to forward slashes for s3 - Key: sanitize(s3Key).replace(/\\/g, "/"), - Body: fileBytes, - ContentType: file.type || CONTENT_TYPE_MAP[extension.toLowerCase()], - Metadata: metadata, - }) - .promise() +exports.prepareUpload = async function({ s3Key, bucket, metadata, file }) { + const response = await upload({ + bucket, + metadata, + filename: s3Key, + path: file.path, + type: file.type, + }) return { size: file.size, name: file.name, - extension, - url: upload.Location, - key: upload.Key, + extension: [...file.name.split(".")].pop(), + url: response.Location, + key: response.Key, } } -exports.deployToObjectStore = async function(appId, objectClient, metadata) { +exports.deployToObjectStore = async function(appId, bucket, metadata) { const appAssetsPath = join(budibaseAppsDir(), appId, "public") let uploads = [] @@ -66,12 +69,12 @@ exports.deployToObjectStore = async function(appId, objectClient, metadata) { walkDir(appAssetsPath, function(filePath) { const filePathParts = filePath.split("/") const appAssetUpload = exports.prepareUpload({ + bucket, file: { path: filePath, name: filePathParts.pop(), }, s3Key: filePath.replace(appAssetsPath, `assets/${appId}`), - client: objectClient, metadata, }) uploads.push(appAssetUpload) @@ -92,7 +95,7 @@ exports.deployToObjectStore = async function(appId, objectClient, metadata) { const attachmentUpload = exports.prepareUpload({ file, s3Key: `assets/${appId}/attachments/${file.processedFileName}`, - client: objectClient, + bucket, metadata, }) diff --git a/packages/server/src/api/controllers/hosting.js b/packages/server/src/api/controllers/hosting.js index 1d1884eb52..4b070cf75b 100644 --- a/packages/server/src/api/controllers/hosting.js +++ b/packages/server/src/api/controllers/hosting.js @@ -1,11 +1,11 @@ const CouchDB = require("../../db") -const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") const { getHostingInfo, getDeployedApps, HostingTypes, getAppUrl, } = require("../../utilities/builder/hosting") +const { StaticDatabases } = require("../../db/utils") exports.fetchInfo = async ctx => { ctx.body = { @@ -14,17 +14,17 @@ exports.fetchInfo = async ctx => { } exports.save = async ctx => { - const db = new CouchDB(BUILDER_CONFIG_DB) + const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name) const { type } = ctx.request.body if (type === HostingTypes.CLOUD && ctx.request.body._rev) { ctx.body = await db.remove({ ...ctx.request.body, - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, }) } else { ctx.body = await db.put({ ...ctx.request.body, - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, }) } } diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index a2badb0d0d..b9b7c85427 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -93,7 +93,7 @@ exports.find = async function(ctx) { const db = new CouchDB(ctx.user.appId) const query = enrichQueries(await db.get(ctx.params.queryId)) // remove properties that could be dangerous in real app - if (env.CLOUD) { + if (env.isProd()) { delete query.fields delete query.parameters delete query.schema diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index ff1ecf5b2e..c866db3561 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -3,31 +3,48 @@ require("svelte/register") const send = require("koa-send") const { resolve, join } = require("../../../utilities/centralPath") const fetch = require("node-fetch") -const fs = require("fs-extra") const uuid = require("uuid") -const AWS = require("aws-sdk") const { prepareUpload } = require("../deploy/utils") const { processString } = require("@budibase/string-templates") -const { - budibaseAppsDir, - budibaseTempDir, -} = require("../../../utilities/budibaseDir") +const { budibaseTempDir } = require("../../../utilities/budibaseDir") const { getDeployedApps } = require("../../../utilities/builder/hosting") const CouchDB = require("../../../db") const setBuilderToken = require("../../../utilities/builder/setBuilderToken") -const fileProcessor = require("../../../utilities/fileProcessor") +const { loadHandlebarsFile } = require("../../../utilities/fileSystem") const env = require("../../../environment") const { OBJ_STORE_DIRECTORY } = require("../../../constants") +const fileProcessor = require("../../../utilities/fileSystem/processor") + +const BB_CDN = "https://cdn.app.budi.live/assets" function objectStoreUrl() { if (env.SELF_HOSTED) { // can use a relative url for this as all goes through the proxy (this is hosted in minio) return OBJ_STORE_DIRECTORY } else { - return "https://cdn.app.budi.live/assets" + return BB_CDN } } +function internalObjectStoreUrl() { + if (env.SELF_HOSTED) { + return (env.MINIO_URL + OBJ_STORE_DIRECTORY).replace( + /(https?:\/\/)|(\/)+/g, + "$1$2" + ) + } else { + return BB_CDN + } +} + +async function returnObjectStoreFile(ctx, path) { + const S3_URL = `${internalObjectStoreUrl()}/${path}` + const response = await fetch(S3_URL) + const body = await response.text() + ctx.set("Content-Type", response.headers.get("Content-Type")) + ctx.body = body +} + async function checkForSelfHostedURL(ctx) { // the "appId" component of the URL may actually be a specific self hosted URL let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}` @@ -50,106 +67,31 @@ exports.serveBuilder = async function(ctx) { await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } -exports.serveSelfHostPage = async function(ctx) { - const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8") - const hostingHbs = fs.readFileSync( - resolve(__dirname, "selfhost/index.hbs"), - "utf8" - ) - ctx.body = await processString(hostingHbs, { - logo, - }) -} - exports.uploadFile = async function(ctx) { - let files - files = + let files = ctx.request.files.file.length > 1 ? Array.from(ctx.request.files.file) : [ctx.request.files.file] - const attachmentsPath = resolve( - budibaseAppsDir(), - ctx.user.appId, - "attachments" - ) - - if (env.CLOUD) { - // remote upload - const s3 = new AWS.S3({ - params: { - Bucket: "prod-budi-app-assets", - }, - }) - - const uploads = files.map(file => { - const fileExtension = [...file.name.split(".")].pop() - const processedFileName = `${uuid.v4()}.${fileExtension}` - - return prepareUpload({ - file, - s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, - s3, - }) - }) - - ctx.body = await Promise.all(uploads) - return - } - - ctx.body = await processLocalFileUploads({ - files, - outputPath: attachmentsPath, - appId: ctx.user.appId, - }) -} - -async function processLocalFileUploads({ files, outputPath, appId }) { - // create attachments dir if it doesnt exist - !fs.existsSync(outputPath) && fs.mkdirSync(outputPath, { recursive: true }) - - const filesToProcess = files.map(file => { + const uploads = files.map(async file => { const fileExtension = [...file.name.split(".")].pop() // filenames converted to UUIDs so they are unique const processedFileName = `${uuid.v4()}.${fileExtension}` - return { - name: file.name, - path: file.path, - size: file.size, - type: file.type, - processedFileName, + // need to handle image processing + await fileProcessor.process({ + ...file, extension: fileExtension, - outputPath: join(outputPath, processedFileName), - url: join("/attachments", processedFileName), - } + }) + + return prepareUpload({ + file, + s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, + bucket: "prod-budi-app-assets", + }) }) - const fileProcessOperations = filesToProcess.map(fileProcessor.process) - - const processedFiles = await Promise.all(fileProcessOperations) - - let pendingFileUploads - // local document used to track which files need to be uploaded - // db.get throws an error if the document doesn't exist - // need to use a promise to default - const db = new CouchDB(appId) - await db - .get("_local/fileuploads") - .then(data => { - pendingFileUploads = data - }) - .catch(() => { - pendingFileUploads = { _id: "_local/fileuploads", uploads: [] } - }) - - pendingFileUploads.uploads = [ - ...processedFiles, - ...pendingFileUploads.uploads, - ] - await db.put(pendingFileUploads) - - return processedFiles + ctx.body = await Promise.all(uploads) } exports.serveApp = async function(ctx) { @@ -163,12 +105,12 @@ exports.serveApp = async function(ctx) { const { head, html, css } = App.render({ title: appInfo.name, - production: env.CLOUD, + production: env.isProd(), appId, objectStoreUrl: objectStoreUrl(), }) - const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8") + const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`) ctx.body = await processString(appHbs, { head, body: html, @@ -178,71 +120,47 @@ exports.serveApp = async function(ctx) { } exports.serveAttachment = async function(ctx) { - const appId = ctx.user.appId - const attachmentsPath = resolve(budibaseAppsDir(), appId, "attachments") - - // Serve from object store - if (env.CLOUD) { - const S3_URL = join(objectStoreUrl(), appId, "attachments", ctx.file) - const response = await fetch(S3_URL) - const body = await response.text() - ctx.set("Content-Type", response.headers.get("Content-Type")) - ctx.body = body - return - } - - await send(ctx, ctx.file, { root: attachmentsPath }) + await returnObjectStoreFile( + ctx, + join(ctx.user.appId, "attachments", ctx.file) + ) } exports.serveAppAsset = async function(ctx) { - // default to homedir - const appPath = resolve(budibaseAppsDir(), ctx.user.appId, "public") - - await send(ctx, ctx.file, { root: ctx.devPath || appPath }) + if (env.isDev() || env.isTest()) { + return send(ctx, ctx.file, { root: budibaseTempDir() }) + } + await returnObjectStoreFile(ctx, join(ctx.user.appId, "public", ctx.file)) } exports.serveComponentLibrary = async function(ctx) { const appId = ctx.query.appId || ctx.appId - // default to homedir - let componentLibraryPath = resolve( - budibaseAppsDir(), - appId, - "node_modules", - decodeURI(ctx.query.library), - "package", - "dist" - ) - if (ctx.isDev) { - componentLibraryPath = join( + if (env.isDev() || env.isTest()) { + const componentLibraryPath = join( budibaseTempDir(), decodeURI(ctx.query.library), "dist" ) + return send(ctx, "/awsDeploy.js", { root: componentLibraryPath }) } - - if (env.CLOUD) { - let componentLib = "componentlibrary" - if (ctx.user.version) { - componentLib += `-${ctx.user.version}` - } else { - componentLib += `-${COMP_LIB_BASE_APP_VERSION}` - } - const S3_URL = encodeURI( - join( - objectStoreUrl(appId), - componentLib, - ctx.query.library, - "dist", - "index.js" - ) + let componentLib = "componentlibrary" + if (ctx.user.version) { + componentLib += `-${ctx.user.version}` + } else { + componentLib += `-${COMP_LIB_BASE_APP_VERSION}` + } + const S3_URL = encodeURI( + join( + objectStoreUrl(appId), + componentLib, + ctx.query.library, + "dist", + "index.js" ) - const response = await fetch(S3_URL) - const body = await response.text() - ctx.type = "application/javascript" - ctx.body = body - return - } - - await send(ctx, "/awsDeploy.js", { root: componentLibraryPath }) + ) + const response = await fetch(S3_URL) + const body = await response.text() + ctx.type = "application/javascript" + ctx.body = body } diff --git a/packages/server/src/api/controllers/static/selfhost/index.hbs b/packages/server/src/api/controllers/static/selfhost/index.hbs deleted file mode 100644 index cbf1b8f3e5..0000000000 --- a/packages/server/src/api/controllers/static/selfhost/index.hbs +++ /dev/null @@ -1,173 +0,0 @@ - - - - - Budibase self hosting️ - - - -
- -

Get started with Budibase Self Hosting

-

Use the address in your Builder

-
-
-

πŸ“šDocumentation

-

- Find out more about your self hosted platform. -

- - Documentation - -
-
-

πŸ’»Next steps

-

- Find out how to make use of your self hosted Budibase platform. -

- - Next steps - -
-
-
- - - - - - -

A Hosting Key will also be required, this can be found in your hosting properties, info found here.

-
-
- - - \ No newline at end of file diff --git a/packages/server/src/api/controllers/static/selfhost/logo.svg b/packages/server/src/api/controllers/static/selfhost/logo.svg deleted file mode 100644 index 37bfb4ff83..0000000000 --- a/packages/server/src/api/controllers/static/selfhost/logo.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/server/src/api/controllers/templates.js b/packages/server/src/api/controllers/templates.js index c3cfa28706..4d55bc5957 100644 --- a/packages/server/src/api/controllers/templates.js +++ b/packages/server/src/api/controllers/templates.js @@ -1,10 +1,5 @@ const fetch = require("node-fetch") -const { - downloadTemplate, - exportTemplateFromApp, - getLocalTemplates, -} = require("../../utilities/templates") -const env = require("../../environment") +const { downloadTemplate } = require("../../utilities/fileSystem") // development flag, can be used to test against templates exported locally const DEFAULT_TEMPLATES_BUCKET = @@ -12,16 +7,11 @@ const DEFAULT_TEMPLATES_BUCKET = exports.fetch = async function(ctx) { const { type = "app" } = ctx.query - - if (env.LOCAL_TEMPLATES) { - ctx.body = Object.values(getLocalTemplates()[type]) - } else { - const response = await fetch( - `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json` - ) - const json = await response.json() - ctx.body = Object.values(json.templates[type]) - } + const response = await fetch( + `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json` + ) + const json = await response.json() + ctx.body = Object.values(json.templates[type]) } // can't currently test this, have to ignore from coverage @@ -29,26 +19,9 @@ exports.fetch = async function(ctx) { exports.downloadTemplate = async function(ctx) { const { type, name } = ctx.params - if (!env.LOCAL_TEMPLATES) { - await downloadTemplate(type, name) - } + await downloadTemplate(type, name) ctx.body = { message: `template ${type}:${name} downloaded successfully.`, } } - -exports.exportTemplateFromApp = async function(ctx) { - const { appId } = ctx.user - const { templateName } = ctx.request.body - - await exportTemplateFromApp({ - appId, - templateName, - }) - - ctx.status = 200 - ctx.body = { - message: `Created template: ${templateName}`, - } -} diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index f482f3f2a6..0f6f008a1b 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,8 +1,6 @@ const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") -const fs = require("fs") -const { join } = require("../../../utilities/centralPath") -const os = require("os") +const { apiFileReturn } = require("../../../utilities/fileSystem") const exporters = require("./exporters") const { fetchView } = require("../row") const { ViewNames } = require("../../../db/utils") @@ -120,12 +118,10 @@ const controller = { // Export part let headers = Object.keys(schema) const exporter = exporters[format] - const exportedFile = exporter(headers, ctx.body) const filename = `${viewName}.${format}` - fs.writeFileSync(join(os.tmpdir(), filename), exportedFile) - + // send down the file ctx.attachment(filename) - ctx.body = fs.createReadStream(join(os.tmpdir(), filename)) + ctx.body = apiFileReturn(exporter(headers, ctx.body)) }, } diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 7628fa2077..aeceb65039 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -2,8 +2,6 @@ const Router = require("@koa/router") const authenticated = require("../middleware/authenticated") const compress = require("koa-compress") const zlib = require("zlib") -const { budibaseAppsDir } = require("../utilities/budibaseDir") -const { isDev } = require("../utilities") const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const pkg = require("../../package.json") @@ -25,11 +23,9 @@ router ) .use(async (ctx, next) => { ctx.config = { - latestPackagesFolder: budibaseAppsDir(), jwtSecret: env.JWT_SECRET, useAppRootPath: true, } - ctx.isDev = isDev() await next() }) .use("/health", ctx => (ctx.status = 200)) @@ -68,8 +64,6 @@ for (let route of mainRoutes) { router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) -if (!env.SELF_HOSTED && !env.CLOUD) { - router.redirect("/", "/_builder") -} +router.redirect("/", "/_builder") module.exports = router diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 30701d578b..64500f078d 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -1,34 +1,32 @@ const Router = require("@koa/router") const controller = require("../controllers/static") const { budibaseTempDir } = require("../../utilities/budibaseDir") -const env = require("../../environment") const authorized = require("../../middleware/authorized") const { BUILDER } = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") +const env = require("../../environment") const router = Router() /* istanbul ignore next */ router.param("file", async (file, ctx, next) => { ctx.file = file && file.includes(".") ? file : "index.html" - - // Serving the client library from your local dir in dev - if (ctx.isDev && ctx.file.startsWith("budibase-client")) { + if (!ctx.file.startsWith("budibase-client")) { + return next() + } + // test serves from require + if (env.isTest()) { + ctx.devPath = require.resolve("@budibase/client").split(ctx.file)[0] + } else if (env.isDev()) { + // Serving the client library from your local dir in dev ctx.devPath = budibaseTempDir() } - - await next() + return next() }) -if (env.NODE_ENV !== "production") { - router.get("/_builder/:file*", controller.serveBuilder) -} - -if (env.SELF_HOSTED) { - router.get("/", controller.serveSelfHostPage) -} - router + // TODO: for now this _builder endpoint is not authorized/secured, will need to be + .get("/_builder/:file*", controller.serveBuilder) .post("/api/attachments/process", authorized(BUILDER), controller.uploadFile) .post("/api/attachments/upload", usage, controller.uploadFile) .get("/componentlibrary", controller.serveComponentLibrary) diff --git a/packages/server/src/api/routes/templates.js b/packages/server/src/api/routes/templates.js index 05882a22ea..6a427e8383 100644 --- a/packages/server/src/api/routes/templates.js +++ b/packages/server/src/api/routes/templates.js @@ -12,6 +12,5 @@ router authorized(BUILDER), controller.downloadTemplate ) - .post("/api/templates", authorized(BUILDER), controller.exportTemplateFromApp) module.exports = router diff --git a/packages/server/src/api/routes/tests/apikeys.spec.js b/packages/server/src/api/routes/tests/apikeys.spec.js index dbee57c8b0..24402a8794 100644 --- a/packages/server/src/api/routes/tests/apikeys.spec.js +++ b/packages/server/src/api/routes/tests/apikeys.spec.js @@ -1,8 +1,5 @@ const setup = require("./utilities") const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") -const fs = require("fs") -const path = require("path") describe("/api/keys", () => { let request = setup.getRequest() @@ -16,12 +13,14 @@ describe("/api/keys", () => { describe("fetch", () => { it("should allow fetching", async () => { - const res = await request - .get(`/api/keys`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body).toBeDefined() + await setup.switchToSelfHosted(async () => { + const res = await request + .get(`/api/keys`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + }) }) it("should check authorization for builder", async () => { @@ -35,17 +34,18 @@ describe("/api/keys", () => { describe("update", () => { it("should allow updating a value", async () => { - fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "TEST_API_KEY=thing") - const res = await request - .put(`/api/keys/TEST`) - .send({ - value: "test" - }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body["TEST"]).toEqual("test") - expect(process.env.TEST_API_KEY).toEqual("test") + await setup.switchToSelfHosted(async () => { + const res = await request + .put(`/api/keys/TEST`) + .send({ + value: "test" + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._id).toBeDefined() + expect(res.body._rev).toBeDefined() + }) }) it("should check authorization for builder", async () => { diff --git a/packages/server/src/api/routes/tests/auth.spec.js b/packages/server/src/api/routes/tests/auth.spec.js index 0eb0b6d851..13695d596d 100644 --- a/packages/server/src/api/routes/tests/auth.spec.js +++ b/packages/server/src/api/routes/tests/auth.spec.js @@ -1,4 +1,3 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") describe("/authenticate", () => { diff --git a/packages/server/src/api/routes/tests/backup.spec.js b/packages/server/src/api/routes/tests/backup.spec.js index d603990294..4e586bfd08 100644 --- a/packages/server/src/api/routes/tests/backup.spec.js +++ b/packages/server/src/api/routes/tests/backup.spec.js @@ -1,6 +1,8 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") +jest.mock("../../../utilities/fileSystem/utilities") + describe("/backups", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -14,7 +16,7 @@ describe("/backups", () => { describe("exportAppDump", () => { it("should be able to export app", async () => { const res = await request - .get(`/api/backups/export?appId=${config.getAppId()}`) + .get(`/api/backups/export?appId=${config.getAppId()}&appname=test`) .set(config.defaultHeaders()) .expect(200) expect(res.text).toBeDefined() diff --git a/packages/server/src/api/routes/tests/cloud.spec.js b/packages/server/src/api/routes/tests/cloud.spec.js deleted file mode 100644 index 3cb65ed819..0000000000 --- a/packages/server/src/api/routes/tests/cloud.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -const setup = require("./utilities") - -describe("test things in the Cloud/Self hosted", () => { - describe("test self hosted static page", () => { - it("should be able to load the static page", async () => { - await setup.switchToCloudForFunction(async () => { - let request = setup.getRequest() - let config = setup.getConfig() - await config.init() - const res = await request.get(`/`).expect(200) - expect(res.text.includes("Budibase self hosting️")).toEqual(true) - setup.afterAll() - }) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/component.spec.js b/packages/server/src/api/routes/tests/component.spec.js index cabf9f8223..35394a1537 100644 --- a/packages/server/src/api/routes/tests/component.spec.js +++ b/packages/server/src/api/routes/tests/component.spec.js @@ -1,8 +1,5 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -const fs = require("fs") -const { resolve, join } = require("path") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") describe("/component", () => { let request = setup.getRequest() @@ -14,23 +11,8 @@ describe("/component", () => { await config.init() }) - function mock() { - const manifestFile = "manifest.json" - const appId = config.getAppId() - const libraries = [join("@budibase", "standard-components")] - for (let library of libraries) { - let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package") - fs.mkdirSync(appDirectory, { recursive: true }) - - const file = require.resolve(library).split(join("dist", "index.js"))[0] + manifestFile - fs.copyFileSync(file, join(appDirectory, manifestFile)) - } - } - describe("fetch definitions", () => { it("should be able to fetch definitions", async () => { - // have to "mock" the files required - mock() const res = await request .get(`/${config.getAppId()}/components/definitions`) .set(config.defaultHeaders()) diff --git a/packages/server/src/api/routes/tests/hosting.spec.js b/packages/server/src/api/routes/tests/hosting.spec.js index 2da5b11778..99a44640bc 100644 --- a/packages/server/src/api/routes/tests/hosting.spec.js +++ b/packages/server/src/api/routes/tests/hosting.spec.js @@ -107,17 +107,16 @@ describe("/hosting", () => { }) describe("getDeployedApps", () => { - it("should get apps when in builder", async () => { - const res = await request + it("should fail when not self hosted", async () => { + await request .get(`/api/hosting/apps`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.body.app1).toEqual({url: "/app1"}) + .expect(400) }) it("should get apps when in cloud", async () => { - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const res = await request .get(`/api/hosting/apps`) .set(config.defaultHeaders()) diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 87938c6a37..2755c0230e 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -89,7 +89,7 @@ describe("/queries", () => { }) it("should find a query in cloud", async () => { - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const query = await config.createQuery() const res = await request .get(`/api/queries/${query._id}`) diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 652a17366d..c79f648c51 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -410,7 +410,7 @@ describe("/rows", () => { tableId: table._id, }) // the environment needs configured for this - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const enriched = await outputProcessing(config.getAppId(), table, [row]) expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`) }) diff --git a/packages/server/src/api/routes/tests/templates.spec.js b/packages/server/src/api/routes/tests/templates.spec.js index f0d26bc7db..30e337e855 100644 --- a/packages/server/src/api/routes/tests/templates.spec.js +++ b/packages/server/src/api/routes/tests/templates.spec.js @@ -1,7 +1,4 @@ const setup = require("./utilities") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") -const fs = require("fs") -const { join } = require("path") describe("/templates", () => { let request = setup.getRequest() @@ -24,26 +21,4 @@ describe("/templates", () => { expect(Array.isArray(res.body)).toEqual(true) }) }) - - describe("export", () => { - it("should be able to export the basic app", async () => { - const res = await request - .post(`/api/templates`) - .send({ - templateName: "test", - }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.message).toEqual("Created template: test") - const dir = join( - budibaseAppsDir(), - "templates", - "app", - "test", - "db" - ) - expect(fs.existsSync(dir)).toEqual(true) - }) - }) }) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js index ed5c98cc48..3bd3886a31 100644 --- a/packages/server/src/api/routes/tests/utilities/index.js +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -35,18 +35,18 @@ exports.getConfig = () => { return config } -exports.switchToCloudForFunction = async func => { +exports.switchToSelfHosted = async func => { // self hosted stops any attempts to Dynamo - env.CLOUD = true - env.SELF_HOSTED = true + env._set("NODE_ENV", "production") + env._set("SELF_HOSTED", true) let error try { await func() } catch (err) { error = err } - env.CLOUD = false - env.SELF_HOSTED = false + env._set("NODE_ENV", "jest") + env._set("SELF_HOSTED", false) // don't throw error until after reset if (error) { throw error diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 8bbea00474..e5e9b77084 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -9,6 +9,7 @@ const env = require("./environment") const eventEmitter = require("./events") const automations = require("./automations/index") const Sentry = require("@sentry/node") +const fileSystem = require("./utilities/fileSystem") const app = new Koa() @@ -65,6 +66,7 @@ module.exports = server.listen(env.PORT || 0, async () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) env._set("PORT", server.address().port) eventEmitter.emitPort(env.PORT) + fileSystem.init() await automations.init() }) diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index ee57f5a109..9675568808 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -5,17 +5,12 @@ const deleteRow = require("./steps/deleteRow") const createUser = require("./steps/createUser") const outgoingWebhook = require("./steps/outgoingWebhook") const env = require("../environment") -const download = require("download") -const fetch = require("node-fetch") -const { join } = require("../utilities/centralPath") -const os = require("os") -const fs = require("fs") const Sentry = require("@sentry/node") +const { + automationInit, + getExternalAutomationStep, +} = require("../utilities/fileSystem") -const DEFAULT_BUCKET = - "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" -const DEFAULT_DIRECTORY = ".budibase-automations" -const AUTOMATION_MANIFEST = "manifest.json" const BUILTIN_ACTIONS = { SEND_EMAIL: sendEmail.run, CREATE_ROW: createRow.run, @@ -33,8 +28,6 @@ const BUILTIN_DEFINITIONS = { OUTGOING_WEBHOOK: outgoingWebhook.definition, } -let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET -let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY let MANIFEST = null /* istanbul ignore next */ @@ -42,22 +35,13 @@ function buildBundleName(pkgName, version) { return `${pkgName}@${version}.min.js` } -/* istanbul ignore next */ -async function downloadPackage(name, version, bundleName) { - await download( - `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`, - AUTOMATION_DIRECTORY - ) - return require(join(AUTOMATION_DIRECTORY, bundleName)) -} - /* istanbul ignore next */ module.exports.getAction = async function(actionName) { if (BUILTIN_ACTIONS[actionName] != null) { return BUILTIN_ACTIONS[actionName] } // worker pools means that a worker may not have manifest - if (env.CLOUD && MANIFEST == null) { + if (env.isProd() && MANIFEST == null) { MANIFEST = await module.exports.init() } // env setup to get async packages @@ -66,28 +50,12 @@ module.exports.getAction = async function(actionName) { } const pkg = MANIFEST.packages[actionName] const bundleName = buildBundleName(pkg.stepId, pkg.version) - try { - return require(join(AUTOMATION_DIRECTORY, bundleName)) - } catch (err) { - return downloadPackage(pkg.stepId, pkg.version, bundleName) - } + return getExternalAutomationStep(pkg.stepId, pkg.version, bundleName) } module.exports.init = async function() { - // set defaults - if (!AUTOMATION_DIRECTORY) { - AUTOMATION_DIRECTORY = join(os.homedir(), DEFAULT_DIRECTORY) - } - if (!AUTOMATION_BUCKET) { - AUTOMATION_BUCKET = DEFAULT_BUCKET - } - if (!fs.existsSync(AUTOMATION_DIRECTORY)) { - fs.mkdirSync(AUTOMATION_DIRECTORY, { recursive: true }) - } - // env setup to get async packages try { - let response = await fetch(`${AUTOMATION_BUCKET}/${AUTOMATION_MANIFEST}`) - MANIFEST = await response.json() + MANIFEST = await automationInit() module.exports.DEFINITIONS = MANIFEST && MANIFEST.packages ? Object.assign(MANIFEST.packages, BUILTIN_DEFINITIONS) diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 9aba399133..d67227e6ac 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -34,10 +34,10 @@ module.exports.init = async function() { await actions.init() triggers.automationQueue.process(async job => { try { - if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) { + if (env.USE_QUOTAS) { job.data.automation.apiKey = await updateQuota(job.data.automation) } - if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") { + if (env.isProd()) { await runWorker(job) } else { await singleThread(job) diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index ef136e1131..aa910dbb42 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -85,7 +85,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { inputs.row.tableId, inputs.row ) - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.ROW, 1) } await rowController.save(ctx) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 8496967105..147a3f7868 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -72,7 +72,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { } try { - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.USER, 1) } await userController.create(ctx) diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index ea4d60a04e..57555ddaad 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -70,7 +70,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { } try { - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.ROW, -1) } await rowController.destroy(ctx) diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index f4d3b4c865..2e9bb16e55 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -47,27 +47,23 @@ describe("Run through some parts of the automations system", () => { expect(thread).toHaveBeenCalled() }) - it("should be able to init in cloud", async () => { - env.CLOUD = true - env.BUDIBASE_ENVIRONMENT = "PRODUCTION" - 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() - env.BUDIBASE_ENVIRONMENT = "JEST" - env.CLOUD = false + 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 () => { - env.CLOUD = true - env.BUDIBASE_ENVIRONMENT = "PRODUCTION" - // the second call will throw an error - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - expect(console.error).toHaveBeenCalled() - env.BUDIBASE_ENVIRONMENT = "JEST" - env.CLOUD = false + 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 () => { diff --git a/packages/server/src/automations/tests/createRow.spec.js b/packages/server/src/automations/tests/createRow.spec.js index 0be2803e47..c01d630bed 100644 --- a/packages/server/src/automations/tests/createRow.spec.js +++ b/packages/server/src/automations/tests/createRow.spec.js @@ -42,12 +42,12 @@ describe("test the create row action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.CREATE_ROW.stepId, { - row + await setup.runInProd(async () => { + await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row + }) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1) }) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1) - env.CLOUD = false }) it("should check invalid inputs return an error", async () => { diff --git a/packages/server/src/automations/tests/createUser.spec.js b/packages/server/src/automations/tests/createUser.spec.js index 5f65e260a9..f188c31aa4 100644 --- a/packages/server/src/automations/tests/createUser.spec.js +++ b/packages/server/src/automations/tests/createUser.spec.js @@ -35,9 +35,9 @@ describe("test the create user action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.CREATE_USER.stepId, user) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1) - env.CLOUD = false + await setup.runInProd(async () => { + await setup.runStep(setup.actions.CREATE_USER.stepId, user) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1) + }) }) }) diff --git a/packages/server/src/automations/tests/deleteRow.spec.js b/packages/server/src/automations/tests/deleteRow.spec.js index 0d5ff47ed8..2a300cbd8c 100644 --- a/packages/server/src/automations/tests/deleteRow.spec.js +++ b/packages/server/src/automations/tests/deleteRow.spec.js @@ -36,10 +36,10 @@ describe("test the delete row action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) - env.CLOUD = false + await setup.runInProd(async () => { + await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) + }) }) it("should check invalid inputs return an error", async () => { diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js index ad149d6bde..ab9de55430 100644 --- a/packages/server/src/automations/tests/utilities/index.js +++ b/packages/server/src/automations/tests/utilities/index.js @@ -2,6 +2,7 @@ const TestConfig = require("../../../tests/utilities/TestConfiguration") const actions = require("../../actions") const logic = require("../../logic") const emitter = require("../../../events/index") +const env = require("../../../environment") let config @@ -16,6 +17,22 @@ exports.afterAll = () => { config.end() } +exports.runInProd = async fn => { + env._set("NODE_ENV", "production") + env._set("USE_QUOTAS", 1) + let error + try { + await fn() + } catch (err) { + error = err + } + env._set("NODE_ENV", "jest") + env._set("USE_QUOTAS", null) + if (error) { + throw error + } +} + exports.runStep = async function runStep(stepId, inputs) { let step if ( diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 46fb5cb649..379cfb8aa5 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -80,8 +80,6 @@ exports.AutoFieldSubTypes = { AUTO_ID: "autoID", } -exports.BUILDER_CONFIG_DB = "builder-config-db" -exports.HOSTING_DOC = "hosting-doc" exports.OBJ_STORE_DIRECTORY = "/app-assets/assets" exports.BaseQueryVerbs = { CREATE: "create", @@ -89,3 +87,9 @@ exports.BaseQueryVerbs = { UPDATE: "update", DELETE: "delete", } + +exports.ObjectStoreBuckets = { + BACKUPS: "backups", + APPS: "prod-budi-app-assets", + TEMPLATES: "templates", +} diff --git a/packages/server/src/db/builder.js b/packages/server/src/db/builder.js new file mode 100644 index 0000000000..d2bbcd404b --- /dev/null +++ b/packages/server/src/db/builder.js @@ -0,0 +1,38 @@ +const CouchDB = require("./index") +const { StaticDatabases } = require("./utils") +const env = require("../environment") + +const SELF_HOST_ERR = "Unable to access builder DB/doc - not self hosted." +const BUILDER_DB = StaticDatabases.BUILDER + +/** + * This is the builder database, right now this is a single, static database + * that is present across the whole system and determines some core functionality + * for the builder (e.g. storage of API keys). This has been limited to self hosting + * as it doesn't make as much sense against the currently design Cloud system. + */ + +exports.getBuilderMainDoc = async () => { + if (!env.SELF_HOSTED) { + throw SELF_HOST_ERR + } + const db = new CouchDB(BUILDER_DB.name) + try { + return await db.get(BUILDER_DB.baseDoc) + } catch (err) { + // doesn't exist yet, nothing to get + return { + _id: BUILDER_DB.baseDoc, + } + } +} + +exports.setBuilderMainDoc = async doc => { + if (!env.SELF_HOSTED) { + throw SELF_HOST_ERR + } + // make sure to override the ID + doc._id = BUILDER_DB.baseDoc + const db = new CouchDB(BUILDER_DB.name) + return db.put(doc) +} diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index f6dea33a40..3e3a4f50fe 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -1,12 +1,10 @@ const PouchDB = require("pouchdb") const replicationStream = require("pouchdb-replication-stream") const allDbs = require("pouchdb-all-dbs") -const { budibaseAppsDir } = require("../utilities/budibaseDir") const find = require("pouchdb-find") const env = require("../environment") -const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/` -const isInMemory = env.NODE_ENV === "jest" +const COUCH_DB_URL = env.COUCH_DB_URL || "http://localhost:10000/db/" PouchDB.plugin(replicationStream.plugin) PouchDB.plugin(find) @@ -14,10 +12,10 @@ PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) let POUCH_DB_DEFAULTS = { prefix: COUCH_DB_URL, - skip_setup: !!env.CLOUD, + skip_setup: env.isProd(), } -if (isInMemory) { +if (env.isTest()) { PouchDB.plugin(require("pouchdb-adapter-memory")) POUCH_DB_DEFAULTS = { prefix: undefined, diff --git a/packages/server/src/db/dynamoClient.js b/packages/server/src/db/dynamoClient.js index fcba726f84..19924b1a7e 100644 --- a/packages/server/src/db/dynamoClient.js +++ b/packages/server/src/db/dynamoClient.js @@ -1,4 +1,4 @@ -let _ = require("lodash") +let { merge } = require("lodash") let env = require("../environment") const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1" @@ -38,7 +38,7 @@ class Table { params.Key[this._sort] = sort } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } let response = await docClient.get(params).promise() return response.Item @@ -77,7 +77,7 @@ class Table { params.ConditionExpression += "attribute_exists(#PRIMARY)" } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } return docClient.update(params).promise() } @@ -94,7 +94,7 @@ class Table { Item: item, } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } return docClient.put(params).promise() } @@ -119,7 +119,7 @@ exports.init = endpoint => { exports.apiKeyTable = new Table(TableInfo.API_KEYS) exports.userTable = new Table(TableInfo.USERS) -if (env.CLOUD) { +if (env.isProd()) { exports.init(`https://dynamodb.${AWS_REGION}.amazonaws.com`) } else { env._set("AWS_ACCESS_KEY_ID", "KEY_ID") diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 2d0722d83a..e480d4f554 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -3,6 +3,18 @@ const newid = require("./newid") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" +const StaticDatabases = { + BUILDER: { + name: "builder-db", + baseDoc: "builder-doc", + }, + // TODO: needs removed + BUILDER_HOSTING: { + name: "builder-config-db", + baseDoc: "hosting-doc", + }, +} + const DocumentTypes = { TABLE: "ta", ROW: "ro", @@ -25,6 +37,7 @@ const ViewNames = { USERS: "ta_users", } +exports.StaticDatabases = StaticDatabases exports.ViewNames = ViewNames exports.DocumentTypes = DocumentTypes exports.SEPARATOR = SEPARATOR diff --git a/packages/server/src/electron.js b/packages/server/src/electron.js index 290c13e6a0..49ef8758c3 100644 --- a/packages/server/src/electron.js +++ b/packages/server/src/electron.js @@ -1,112 +1,112 @@ -const { app, BrowserWindow, shell, dialog } = require("electron") -const { join } = require("./utilities/centralPath") -const isDev = require("electron-is-dev") -const { autoUpdater } = require("electron-updater") -const unhandled = require("electron-unhandled") -const { existsSync } = require("fs-extra") -const initialiseBudibase = require("./utilities/initialiseBudibase") -const { budibaseAppsDir } = require("./utilities/budibaseDir") -const { openNewGitHubIssue, debugInfo } = require("electron-util") -const eventEmitter = require("./events") +// const { app, BrowserWindow, shell, dialog } = require("electron") +// const { join } = require("./utilities/centralPath") +// const isDev = require("electron-is-dev") +// const { autoUpdater } = require("electron-updater") +// const unhandled = require("electron-unhandled") +// const { existsSync } = require("fs-extra") +// const initialiseBudibase = require("./utilities/initialiseBudibase") +// const { budibaseAppsDir } = require("./utilities/budibaseDir") +// const { openNewGitHubIssue, debugInfo } = require("electron-util") +// const eventEmitter = require("./events") -const budibaseDir = budibaseAppsDir() -const envFile = join(budibaseDir, ".env") +// const budibaseDir = budibaseAppsDir() +// const envFile = join(budibaseDir, ".env") -async function startApp() { - if (!existsSync(envFile)) { - await initialiseBudibase({ dir: budibaseDir }) - } - // evict environment from cache, so it reloads when next asked - delete require.cache[require.resolve("./environment")] - // store the port incase its going to get overridden - const port = process.env.PORT - require("dotenv").config({ path: envFile }) - // overwrite the port - don't want to use dotenv for the port - require("./environment")._set("PORT", port) +// async function startApp() { +// if (!existsSync(envFile)) { +// await initialiseBudibase({ dir: budibaseDir }) +// } +// // evict environment from cache, so it reloads when next asked +// delete require.cache[require.resolve("./environment")] +// // store the port incase its going to get overridden +// const port = process.env.PORT +// require("dotenv").config({ path: envFile }) +// // overwrite the port - don't want to use dotenv for the port +// require("./environment")._set("PORT", port) - unhandled({ - showDialog: true, - reportButton: error => { - openNewGitHubIssue({ - title: error.message, - user: "Budibase", - labels: ["error-report"], - repo: "budibase", - body: `### Error that occurred when using the budibase builder:\n\`\`\`\n${ - error.stack - }\n\`\`\`\n### Operating System Information:\n---\n\n${debugInfo()}`, - }) - }, - }) +// unhandled({ +// showDialog: true, +// reportButton: error => { +// openNewGitHubIssue({ +// title: error.message, +// user: "Budibase", +// labels: ["error-report"], +// repo: "budibase", +// body: `### Error that occurred when using the budibase builder:\n\`\`\`\n${ +// error.stack +// }\n\`\`\`\n### Operating System Information:\n---\n\n${debugInfo()}`, +// }) +// }, +// }) - let win +// let win - function handleRedirect(e, url) { - e.preventDefault() - shell.openExternal(url) - } +// function handleRedirect(e, url) { +// e.preventDefault() +// shell.openExternal(url) +// } - async function createWindow() { - app.server = require("./app") - eventEmitter.on("internal:port", port => { - const APP_URL = `http://localhost:${port}/_builder` - const APP_TITLE = "Budibase Builder" - win = new BrowserWindow({ - width: 1920, - height: 1080, - icon: join(__dirname, "..", "build", "icons", "512x512.png"), - }) - win.setTitle(APP_TITLE) - win.loadURL(APP_URL) - if (isDev) { - win.webContents.openDevTools() - } else { - autoUpdater.checkForUpdatesAndNotify() - } +// async function createWindow() { +// app.server = require("./app") +// eventEmitter.on("internal:port", port => { +// const APP_URL = `http://localhost:${port}/_builder` +// const APP_TITLE = "Budibase Builder" +// win = new BrowserWindow({ +// width: 1920, +// height: 1080, +// icon: join(__dirname, "..", "build", "icons", "512x512.png"), +// }) +// win.setTitle(APP_TITLE) +// win.loadURL(APP_URL) +// if (isDev) { +// win.webContents.openDevTools() +// } else { +// autoUpdater.checkForUpdatesAndNotify() +// } - // open _blank in default browser - win.webContents.on("new-window", handleRedirect) - win.webContents.on("will-navigate", handleRedirect) - }) - } +// // open _blank in default browser +// win.webContents.on("new-window", handleRedirect) +// win.webContents.on("will-navigate", handleRedirect) +// }) +// } - app.whenReady().then(createWindow) +// app.whenReady().then(createWindow) - // Quit when all windows are closed. - app.on("window-all-closed", () => { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== "darwin") { - app.server.close() - app.quit() - } - }) +// // Quit when all windows are closed. +// app.on("window-all-closed", () => { +// // On macOS it is common for applications and their menu bar +// // to stay active until the user quits explicitly with Cmd + Q +// if (process.platform !== "darwin") { +// app.server.close() +// app.quit() +// } +// }) - app.on("activate", () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (win === null) createWindow() - }) -} +// app.on("activate", () => { +// // On macOS it's common to re-create a window in the app when the +// // dock icon is clicked and there are no other windows open. +// if (win === null) createWindow() +// }) +// } -autoUpdater.on("update-downloaded", (event, releaseNotes, releaseName) => { - const dialogOpts = { - type: "info", - buttons: ["Restart", "Later"], - title: "Budibase Update Available", - message: process.platform === "win32" ? releaseNotes : releaseName, - detail: - "A new version of the budibase builder has been downloaded. Restart the application to apply the updates.", - } +// autoUpdater.on("update-downloaded", (event, releaseNotes, releaseName) => { +// const dialogOpts = { +// type: "info", +// buttons: ["Restart", "Later"], +// title: "Budibase Update Available", +// message: process.platform === "win32" ? releaseNotes : releaseName, +// detail: +// "A new version of the budibase builder has been downloaded. Restart the application to apply the updates.", +// } - dialog.showMessageBox(dialogOpts).then(returnValue => { - if (returnValue.response === 0) autoUpdater.quitAndInstall() - }) -}) +// dialog.showMessageBox(dialogOpts).then(returnValue => { +// if (returnValue.response === 0) autoUpdater.quitAndInstall() +// }) +// }) -autoUpdater.on("error", message => { - console.error("There was a problem updating the application") - console.error(message) -}) +// autoUpdater.on("error", message => { +// console.error("There was a problem updating the application") +// console.error(message) +// }) -startApp() +// startApp() diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 4faaabe6ab..dc15bc8a9a 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -1,45 +1,64 @@ -const { resolve, join } = require("./utilities/centralPath") -const { homedir } = require("os") -const { app } = require("electron") +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + +function isDev() { + return ( + process.env.NODE_ENV !== "production" && + process.env.BUDIBASE_ENVIRONMENT !== "production" + ) +} let LOADED = false - -if (!LOADED) { - const homeDir = app ? app.getPath("home") : homedir() - const budibaseDir = join(homeDir, ".budibase") - process.env.BUDIBASE_DIR = budibaseDir - require("dotenv").config({ path: resolve(budibaseDir, ".env") }) +if (!LOADED && isDev() && !isTest()) { + require("dotenv").config() LOADED = true } module.exports = { - CLIENT_ID: process.env.CLIENT_ID, - NODE_ENV: process.env.NODE_ENV, - JWT_SECRET: process.env.JWT_SECRET, - BUDIBASE_DIR: process.env.BUDIBASE_DIR, + // important PORT: process.env.PORT, + JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, + MINIO_URL: process.env.MINIO_URL, + WORKER_URL: process.env.WORKER_URL, + SELF_HOSTED: process.env.SELF_HOSTED, + AWS_REGION: process.env.AWS_REGION, + ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, + MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, + MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, + USE_QUOTAS: process.env.USE_QUOTAS, + // environment + NODE_ENV: process.env.NODE_ENV, + JEST_WORKER_ID: process.env.JEST_WORKER_ID, + BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT, + // minor SALT_ROUNDS: process.env.SALT_ROUNDS, LOGGER: process.env.LOGGER, LOG_LEVEL: process.env.LOG_LEVEL, AUTOMATION_DIRECTORY: process.env.AUTOMATION_DIRECTORY, AUTOMATION_BUCKET: process.env.AUTOMATION_BUCKET, - BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT, SENDGRID_API_KEY: process.env.SENDGRID_API_KEY, - CLOUD: process.env.CLOUD, - SELF_HOSTED: process.env.SELF_HOSTED, - WORKER_URL: process.env.WORKER_URL, - HOSTING_KEY: process.env.HOSTING_KEY, DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT, - AWS_REGION: process.env.AWS_REGION, - DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + // old - to remove + CLIENT_ID: process.env.CLIENT_ID, + BUDIBASE_DIR: process.env.BUDIBASE_DIR, + DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY, USERID_API_KEY: process.env.USERID_API_KEY, - ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, - DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, - LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES, + DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + HOSTING_KEY: process.env.HOSTING_KEY, _set(key, value) { process.env[key] = value module.exports[key] = value }, + isTest, + isDev, + isProd: () => { + return !isDev() + }, } diff --git a/packages/server/src/index.js b/packages/server/src/index.js index f5506aa2a3..3aa210f96c 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -1,17 +1,10 @@ -const { budibaseTempDir } = require("./utilities/budibaseDir") -const { isDev } = require("./utilities") - const fixPath = require("fix-path") -const fs = require("fs") - -async function runServer() { - if (isDev() && !fs.existsSync(budibaseTempDir())) { - console.error( - "Please run a build before attempting to run server independently to fill 'tmp' directory." - ) - process.exit(-1) - } +const { checkDevelopmentEnvironment } = require("./utilities/fileSystem") +function runServer() { + // this will shutdown the system if development environment not ready + // will print an error explaining what to do + checkDevelopmentEnvironment() fixPath() require("./app") } diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 2a1caef2a2..564896080e 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -13,19 +13,12 @@ const { AuthTypes } = require("../constants") const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER] -const LOCAL_PASS = new RegExp(["webhooks/trigger"].join("|")) - function hasResource(ctx) { return ctx.resourceId != null } module.exports = (permType, permLevel = null) => async (ctx, next) => { - // webhooks can pass locally - if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { - return next() - } - - if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { + if (env.isProd() && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { // api key header passed by external webhook if (await isAPIKeyValid(ctx.headers["x-api-key"])) { ctx.auth = { @@ -41,20 +34,23 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { return ctx.throw(403, "API key invalid") } - // don't expose builder endpoints in the cloud - if (env.CLOUD && permType === PermissionTypes.BUILDER) return - if (!ctx.user) { return ctx.throw(403, "No user info found") } const role = ctx.user.role + const isBuilder = role._id === BUILTIN_ROLE_IDS.BUILDER + const isAdmin = ADMIN_ROLES.includes(role._id) + const isAuthed = ctx.auth.authenticated + + if (permType === PermissionTypes.BUILDER && isBuilder) { + return next() + } + const { basePermissions, permissions } = await getUserPermissions( ctx.appId, role._id ) - const isAdmin = ADMIN_ROLES.includes(role._id) - const isAuthed = ctx.auth.authenticated // this may need to change in the future, right now only admins // can have access to builder features, this is hard coded into diff --git a/packages/server/src/middleware/selfhost.js b/packages/server/src/middleware/selfhost.js index 1c96cee33c..1e7117c83d 100644 --- a/packages/server/src/middleware/selfhost.js +++ b/packages/server/src/middleware/selfhost.js @@ -1,14 +1,8 @@ const env = require("../environment") -const hosting = require("../utilities/builder/hosting") // if added as a middleware will stop requests unless builder is in self host mode // or cloud is in self host module.exports = async (ctx, next) => { - if (env.CLOUD && env.SELF_HOSTED) { - await next() - return - } - const hostingInfo = await hosting.getHostingInfo() - if (hostingInfo.type === hosting.HostingTypes.SELF) { + if (env.SELF_HOSTED) { await next() return } diff --git a/packages/server/src/middleware/tests/authorized.spec.js b/packages/server/src/middleware/tests/authorized.spec.js index d3e5e52d2d..7968a8a939 100644 --- a/packages/server/src/middleware/tests/authorized.spec.js +++ b/packages/server/src/middleware/tests/authorized.spec.js @@ -3,8 +3,15 @@ const env = require("../../environment") const apiKey = require("../../utilities/security/apikey") const { AuthTypes } = require("../../constants") const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions") -const { Test } = require("supertest") -jest.mock("../../environment") +jest.mock("../../environment", () => ({ + prod: false, + isTest: () => true, + isProd: () => this.prod, + _set: (key, value) => { + this.prod = value === "production" + } + }) +) jest.mock("../../utilities/security/apikey") class TestConfiguration { @@ -47,8 +54,8 @@ class TestConfiguration { this.ctx.request.url = url } - setCloudEnv(isCloud) { - env.CLOUD = isCloud + setEnvironment(isProd) { + env._set("NODE_ENV", isProd ? "production" : "jest") } setRequestHeaders(headers) { @@ -71,12 +78,6 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - }) - - it("passes the middleware for local webhooks", async () => { - config.setRequestUrl("https://something/webhooks/trigger") - await config.executeMiddleware() - expect(config.next).toHaveBeenCalled() }) describe("external web hook call", () => { @@ -85,7 +86,7 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - config.setCloudEnv(true) + config.setEnvironment(true) config.setRequestHeaders({ "x-api-key": "abc123", "x-instanceid": "instance123", @@ -121,7 +122,7 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - config.setCloudEnv(true) + config.setEnvironment(true) config.setAuthenticated(true) }) @@ -144,7 +145,7 @@ describe("Authorization middleware", () => { }) it("throws if the user has only builder permissions", async () => { - config.setCloudEnv(false) + config.setEnvironment(false) config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER) config.setUser({ role: { diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 061da17f9c..6ce61c60ef 100644 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -1,6 +1,5 @@ -const selfHostMiddleware = require("../selfhost"); +const selfHostMiddleware = require("../selfhost") const env = require("../../environment") -const hosting = require("../../utilities/builder/hosting"); jest.mock("../../environment") jest.mock("../../utilities/builder/hosting") @@ -20,16 +19,6 @@ class TestConfiguration { return this.middleware(this.ctx, this.next) } - setCloudHosted() { - env.CLOUD = 1 - env.SELF_HOSTED = 0 - } - - setSelfHosted() { - env.CLOUD = 0 - env.SELF_HOSTED = 1 - } - afterEach() { jest.clearAllMocks() } @@ -46,30 +35,10 @@ describe("Self host middleware", () => { config.afterEach() }) - it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { - env.CLOUD = 1 + it("calls next() when SELF_HOSTED env var is set", async () => { env.SELF_HOSTED = 1 await config.executeMiddleware() expect(config.next).toHaveBeenCalled() }) - - it("throws when hostingInfo type is cloud", async () => { - config.setSelfHosted() - - hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD })) - - await config.executeMiddleware() - expect(config.throw).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") - expect(config.next).not.toHaveBeenCalled() - }) - - it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => { - config.setSelfHosted() - - hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF })) - - await config.executeMiddleware() - expect(config.next).toHaveBeenCalled() - }) }) diff --git a/packages/server/src/middleware/tests/usageQuota.spec.js b/packages/server/src/middleware/tests/usageQuota.spec.js index 395f14c1ed..9ab17ef992 100644 --- a/packages/server/src/middleware/tests/usageQuota.spec.js +++ b/packages/server/src/middleware/tests/usageQuota.spec.js @@ -5,7 +5,12 @@ const env = require("../../environment") jest.mock("../../db") jest.mock("../../utilities/usageQuota") -jest.mock("../../environment") +jest.mock("../../environment", () => ({ + isTest: () => true, + isProd: () => false, + isDev: () => true, + _set: () => {}, +})) class TestConfiguration { constructor() { @@ -32,12 +37,14 @@ class TestConfiguration { return this.middleware(this.ctx, this.next) } - cloudHosted(bool) { + setProd(bool) { if (bool) { - env.CLOUD = 1 + env.isDev = () => false + env.isProd = () => true this.ctx.auth = { apiKey: "test" } } else { - env.CLOUD = 0 + env.isDev = () => true + env.isProd = () => false } } @@ -102,7 +109,7 @@ describe("usageQuota middleware", () => { it("calculates and persists the correct usage quota for the relevant action", async () => { config.setUrl("/rows") - config.cloudHosted(true) + config.setProd(true) await config.executeMiddleware() @@ -112,7 +119,7 @@ describe("usageQuota middleware", () => { it("calculates the correct file size from a file upload call and adds it to quota", async () => { config.setUrl("/upload") - config.cloudHosted(true) + config.setProd(true) config.setFiles([ { size: 100 diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index 1b809868be..1bc829fbcf 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -44,8 +44,8 @@ module.exports = async (ctx, next) => { } } - // if running in builder or a self hosted cloud usage quotas should not be executed - if (!env.CLOUD || env.SELF_HOSTED) { + // if in development or a self hosted cloud usage quotas should not be executed + if (env.isDev() || env.SELF_HOSTED) { return next() } // update usage for uploads to be the total size diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index a12d596534..433cec4a0a 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -14,9 +14,7 @@ const { } = require("./structures") const controllers = require("./controllers") const supertest = require("supertest") -const fs = require("fs") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") -const { join } = require("path") +const { cleanup } = require("../../utilities/fileSystem") const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" @@ -66,13 +64,7 @@ class TestConfiguration { if (this.server) { this.server.close() } - const appDir = budibaseAppsDir() - const files = fs.readdirSync(appDir) - for (let file of files) { - if (this.allApps.some(app => file.includes(app._id))) { - fs.rmdirSync(join(appDir, file), { recursive: true }) - } - } + cleanup(this.allApps.map(app => app._id)) } defaultHeaders() { @@ -81,9 +73,11 @@ class TestConfiguration { roleId: BUILTIN_ROLE_IDS.BUILDER, } const builderToken = jwt.sign(builderUser, env.JWT_SECRET) + // can be "production" for test case + const type = env.isProd() ? "cloud" : "local" const headers = { Accept: "application/json", - Cookie: [`budibase:builder:local=${builderToken}`], + Cookie: [`budibase:builder:${type}=${builderToken}`], } if (this.appId) { headers["x-budibase-app-id"] = this.appId diff --git a/packages/server/src/utilities/builder/compileStaticAssets.js b/packages/server/src/utilities/builder/compileStaticAssets.js deleted file mode 100644 index 0389c920ee..0000000000 --- a/packages/server/src/utilities/builder/compileStaticAssets.js +++ /dev/null @@ -1,35 +0,0 @@ -const { ensureDir, constants, copyFile } = require("fs-extra") -const { join } = require("../centralPath") -const { budibaseAppsDir } = require("../budibaseDir") - -/** - * Compile all the non-db static web assets that are required for the running of - * a budibase application. This includes the JSON structure of the DOM and - * the client library, a script responsible for reading the JSON structure - * and rendering the application. - * @param {string} appId id of the application we want to compile static assets for - */ -module.exports = async appId => { - const publicPath = join(budibaseAppsDir(), appId, "public") - await ensureDir(publicPath) - await copyClientLib(publicPath) -} - -/** - * Copy the budibase client library and sourcemap from NPM to /public/. - * The client library is then served as a static asset when the budibase application - * is running in preview or prod - * @param {String} publicPath - path to write the client library to - */ -const copyClientLib = async publicPath => { - const sourcepath = require.resolve("@budibase/client") - const destPath = join(publicPath, "budibase-client.js") - - await copyFile(sourcepath, destPath, constants.COPYFILE_FICLONE) - - await copyFile( - sourcepath + ".map", - destPath + ".map", - constants.COPYFILE_FICLONE - ) -} diff --git a/packages/server/src/utilities/builder/hosting.js b/packages/server/src/utilities/builder/hosting.js index c265c26dd0..f852cefec1 100644 --- a/packages/server/src/utilities/builder/hosting.js +++ b/packages/server/src/utilities/builder/hosting.js @@ -1,5 +1,5 @@ const CouchDB = require("../../db") -const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") +const { StaticDatabases } = require("../../db/utils") const fetch = require("node-fetch") const env = require("../../environment") @@ -23,16 +23,16 @@ exports.HostingTypes = { } exports.getHostingInfo = async () => { - const db = new CouchDB(BUILDER_CONFIG_DB) + const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name) let doc try { - doc = await db.get(HOSTING_DOC) + doc = await db.get(StaticDatabases.BUILDER_HOSTING.baseDoc) } catch (err) { // don't write this doc, want to be able to update these default props // for our servers with a new release without needing to worry about state of // PouchDB in peoples installations doc = { - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, type: exports.HostingTypes.CLOUD, hostingUrl: PROD_HOSTING_URL, selfHostKey: "", @@ -85,15 +85,11 @@ exports.getTemplatesUrl = async (appId, type, name) => { } exports.getDeployedApps = async () => { - const hostingInfo = await exports.getHostingInfo() - if ( - (!env.CLOUD && hostingInfo.type === exports.HostingTypes.CLOUD) || - (env.CLOUD && !env.SELF_HOSTED) - ) { + if (!env.SELF_HOSTED) { throw "Can only check apps for self hosted environments" } - const workerUrl = !env.CLOUD ? await exports.getWorkerUrl() : env.WORKER_URL - const hostingKey = !env.CLOUD ? hostingInfo.selfHostKey : env.HOSTING_KEY + const workerUrl = env.WORKER_URL + const hostingKey = env.HOSTING_KEY try { const response = await fetch(`${workerUrl}/api/apps`, { method: "GET", @@ -102,12 +98,14 @@ exports.getDeployedApps = async () => { }, }) const json = await response.json() - for (let value of Object.values(json)) { + const apps = {} + for (let [key, value] of Object.entries(json)) { if (value.url) { value.url = value.url.toLowerCase() + apps[key] = value } } - return json + return apps } catch (err) { // error, cannot determine deployed apps, don't stop app creation - sort this later return {} diff --git a/packages/server/src/utilities/createAppPackage.js b/packages/server/src/utilities/createAppPackage.js deleted file mode 100644 index 9500554227..0000000000 --- a/packages/server/src/utilities/createAppPackage.js +++ /dev/null @@ -1,29 +0,0 @@ -const stream = require("stream") -const fetch = require("node-fetch") -const tar = require("tar-fs") -const zlib = require("zlib") -const { promisify } = require("util") -const packageJson = require("../../package.json") - -const streamPipeline = promisify(stream.pipeline) - -// can't really test this due to the downloading nature of it, wouldn't be a great test case -/* istanbul ignore next */ -exports.downloadExtractComponentLibraries = async appFolder => { - const LIBRARIES = ["standard-components"] - - // Need to download tarballs directly from NPM as our users may not have node on their machine - for (let lib of LIBRARIES) { - // download tarball - const registryUrl = `https://registry.npmjs.org/@budibase/${lib}/-/${lib}-${packageJson.version}.tgz` - const response = await fetch(registryUrl) - if (!response.ok) - throw new Error(`unexpected response ${response.statusText}`) - - await streamPipeline( - response.body, - zlib.Unzip(), - tar.extract(`${appFolder}/node_modules/@budibase/${lib}`) - ) - } -} diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js new file mode 100644 index 0000000000..7ef99eda41 --- /dev/null +++ b/packages/server/src/utilities/fileSystem/index.js @@ -0,0 +1,232 @@ +const { budibaseTempDir } = require("../budibaseDir") +const { isDev } = require("../index") +const fs = require("fs") +const { join } = require("path") +const uuid = require("uuid/v4") +const CouchDB = require("../../db") +const { ObjectStoreBuckets } = require("../../constants") +const { + upload, + retrieve, + retrieveToTmp, + streamUpload, + deleteFolder, + downloadTarball, +} = require("./utilities") +const { downloadLibraries, newAppPublicPath } = require("./newApp") +const download = require("download") +const env = require("../../environment") +const { homedir } = require("os") +const fetch = require("node-fetch") + +const DEFAULT_AUTOMATION_BUCKET = + "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" +const DEFAULT_AUTOMATION_DIRECTORY = ".budibase-automations" + +/** + * The single stack system (Cloud and Builder) should not make use of the file system where possible, + * this file handles all of the file access for the system with the intention of limiting it all to one + * place. Keeping all of this logic in one place means that when we need to do file system access (like + * downloading a package or opening a temporary file) in can be done in way that we can confirm it shouldn't + * be done through an object store instead. + */ + +/** + * Upon first startup of instance there may not be everything we need in tmp directory, set it up. + */ +exports.init = () => { + const tempDir = budibaseTempDir() + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir) + } + const clientLibPath = join(budibaseTempDir(), "budibase-client.js") + if (env.isTest() && !fs.existsSync(clientLibPath)) { + fs.copyFileSync(require.resolve("@budibase/client"), clientLibPath) + } +} + +/** + * Checks if the system is currently in development mode and if it is makes sure + * everything required to function is ready. + */ +exports.checkDevelopmentEnvironment = () => { + if (!isDev()) { + return + } + let error + if (!fs.existsSync(budibaseTempDir())) { + error = + "Please run a build before attempting to run server independently to fill 'tmp' directory." + } + if (!fs.existsSync(join(process.cwd(), ".env"))) { + error = "Must run via yarn once to generate environment." + } + if (error) { + console.error(error) + process.exit(-1) + } +} + +/** + * This function manages temporary template files which are stored by Koa. + * @param {Object} template The template object retrieved from the Koa context object. + * @returns {Object} Returns an fs read stream which can be loaded into the database. + */ +exports.getTemplateStream = async template => { + if (template.file) { + return fs.createReadStream(template.file.path) + } else { + const tmpPath = await exports.downloadTemplate(...template.key.split("/")) + return fs.createReadStream(join(tmpPath, "db", "dump.txt")) + } +} + +/** + * Used to retrieve a handlebars file from the system which will be used as a template. + * This is allowable as the template handlebars files should be static and identical across + * the cluster. + * @param {string} path The path to the handlebars file which is to be loaded. + * @returns {string} The loaded handlebars file as a string - loaded as utf8. + */ +exports.loadHandlebarsFile = path => { + return fs.readFileSync(path, "utf8") +} + +/** + * When return a file from the API need to write the file to the system temporarily so we + * can create a read stream to send. + * @param {string} contents the contents of the file which is to be returned from the API. + * @return {Object} the read stream which can be put into the koa context body. + */ +exports.apiFileReturn = contents => { + const path = join(budibaseTempDir(), uuid()) + fs.writeFileSync(path, contents) + return fs.createReadStream(path) +} + +/** + * Takes a copy of the database state for an app to the object store. + * @param {string} appId The ID of the app which is to be backed up. + * @param {string} backupName The name of the backup located in the object store. + * @return The backup has been completed when this promise completes and returns a file stream + * to the temporary backup file (to return via API if required). + */ +exports.performBackup = async (appId, backupName) => { + const path = join(budibaseTempDir(), backupName) + const writeStream = fs.createWriteStream(path) + // perform couch dump + const instanceDb = new CouchDB(appId) + await instanceDb.dump(writeStream, {}) + // write the file to the object store + await streamUpload( + ObjectStoreBuckets.BACKUPS, + join(appId, backupName), + fs.createReadStream(path) + ) + return fs.createReadStream(path) +} + +/** + * Downloads required libraries and creates a new path in the object store. + * @param {string} appId The ID of the app which is being created. + * @return {Promise} once promise completes app resources should be ready in object store. + */ +exports.createApp = async appId => { + await downloadLibraries(appId) + await newAppPublicPath(appId) +} + +/** + * Removes all of the assets created for an app in the object store. + * @param {string} appId The ID of the app which is being deleted. + * @return {Promise} once promise completes the app resources will be removed from object store. + */ +exports.deleteApp = async appId => { + await deleteFolder(ObjectStoreBuckets.APPS, `${appId}/`) +} + +/** + * Retrieves a template and pipes it to minio as well as making it available temporarily. + * @param {string} type The type of template which is to be retrieved. + * @param name + * @return {Promise<*>} + */ +exports.downloadTemplate = async (type, name) => { + const DEFAULT_TEMPLATES_BUCKET = + "prod-budi-templates.s3-eu-west-1.amazonaws.com" + const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` + return downloadTarball(templateUrl, ObjectStoreBuckets.TEMPLATES, type) +} + +/** + * Retrieves component libraries from object store (or tmp symlink if in local) + */ +exports.getComponentLibraryManifest = async (appId, library) => { + const filename = "manifest.json" + /* istanbul ignore next */ + // when testing in cypress and so on we need to get the package + // as the environment may not be fully fleshed out for dev or prod + if (env.isTest()) { + const lib = library.split("/")[1] + const path = require.resolve(library).split(lib)[0] + return require(join(path, lib, filename)) + } + const devPath = join(budibaseTempDir(), library, filename) + if (env.isDev() && fs.existsSync(devPath)) { + return require(devPath) + } + const path = join(appId, "node_modules", library, "package", filename) + let resp = await retrieve(ObjectStoreBuckets.APPS, path) + if (typeof resp !== "string") { + resp = resp.toString("utf8") + } + 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. + */ +exports.readFileSync = (filepath, options = "utf8") => { + return fs.readFileSync(filepath, options) +} + +/** + * Given a set of app IDs makes sure file system is cleared of any of their temp info. + */ +exports.cleanup = appIds => { + for (let appId of appIds) { + fs.rmdirSync(join(budibaseTempDir(), appId), { recursive: true }) + } +} + +/** + * Full function definition for below can be found in the utilities. + */ +exports.upload = upload +exports.retrieve = retrieve +exports.retrieveToTmp = retrieveToTmp diff --git a/packages/server/src/utilities/fileSystem/newApp.js b/packages/server/src/utilities/fileSystem/newApp.js new file mode 100644 index 0000000000..2f5c77912a --- /dev/null +++ b/packages/server/src/utilities/fileSystem/newApp.js @@ -0,0 +1,40 @@ +const packageJson = require("../../../package.json") +const { join } = require("path") +const { ObjectStoreBuckets } = require("../../constants") +const { streamUpload, downloadTarball } = require("./utilities") +const fs = require("fs") + +const BUCKET_NAME = ObjectStoreBuckets.APPS + +// can't really test this due to the downloading nature of it, wouldn't be a great test case +/* istanbul ignore next */ +exports.downloadLibraries = async appId => { + const LIBRARIES = ["standard-components"] + + const paths = {} + // Need to download tarballs directly from NPM as our users may not have node on their machine + for (let lib of LIBRARIES) { + // download tarball + const registryUrl = `https://registry.npmjs.org/@budibase/${lib}/-/${lib}-${packageJson.version}.tgz` + const path = join(appId, "node_modules", "@budibase", lib) + paths[`@budibase/${lib}`] = await downloadTarball( + registryUrl, + BUCKET_NAME, + path + ) + } + return paths +} + +exports.newAppPublicPath = async appId => { + const path = join(appId, "public") + const sourcepath = require.resolve("@budibase/client") + const destPath = join(path, "budibase-client.js") + + await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath)) + await streamUpload( + BUCKET_NAME, + destPath + ".map", + fs.createReadStream(sourcepath + ".map") + ) +} diff --git a/packages/server/src/utilities/fileProcessor.js b/packages/server/src/utilities/fileSystem/processor.js similarity index 62% rename from packages/server/src/utilities/fileProcessor.js rename to packages/server/src/utilities/fileSystem/processor.js index 15132b2d49..3778b50168 100644 --- a/packages/server/src/utilities/fileProcessor.js +++ b/packages/server/src/utilities/fileSystem/processor.js @@ -1,25 +1,20 @@ -const fs = require("fs") const jimp = require("jimp") -const fsPromises = fs.promises const FORMATS = { IMAGES: ["png", "jpg", "jpeg", "gif", "bmp", "tiff"], } function processImage(file) { + // this will overwrite the temp file return jimp.read(file.path).then(img => { - return img.resize(300, jimp.AUTO).write(file.outputPath) + return img.resize(300, jimp.AUTO).write(file.path) }) } async function process(file) { if (FORMATS.IMAGES.includes(file.extension.toLowerCase())) { await processImage(file) - return file } - - // No processing required - await fsPromises.copyFile(file.path, file.outputPath) return file } diff --git a/packages/server/src/utilities/fileSystem/utilities.js b/packages/server/src/utilities/fileSystem/utilities.js new file mode 100644 index 0000000000..b586cfcfe5 --- /dev/null +++ b/packages/server/src/utilities/fileSystem/utilities.js @@ -0,0 +1,243 @@ +const sanitize = require("sanitize-s3-objectkey") +const AWS = require("aws-sdk") +const stream = require("stream") +const fetch = require("node-fetch") +const tar = require("tar-fs") +const zlib = require("zlib") +const { promisify } = require("util") +const { join } = require("path") +const fs = require("fs") +const { budibaseTempDir } = require("../budibaseDir") +const env = require("../../environment") +const { ObjectStoreBuckets } = require("../../constants") +const uuid = require("uuid/v4") + +const streamPipeline = promisify(stream.pipeline) +// use this as a temporary store of buckets that are being created +const STATE = { + bucketCreationPromises: {}, +} + +const CONTENT_TYPE_MAP = { + html: "text/html", + css: "text/css", + js: "application/javascript", +} +const STRING_CONTENT_TYPES = [ + CONTENT_TYPE_MAP.html, + CONTENT_TYPE_MAP.css, + CONTENT_TYPE_MAP.js, +] + +function publicPolicy(bucketName) { + return { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { + AWS: ["*"], + }, + Action: "s3:GetObject", + Resource: [`arn:aws:s3:::${bucketName}/*`], + }, + ], + } +} + +const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS] + +/** + * Gets a connection to the object store using the S3 SDK. + * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. + * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. + * @constructor + */ +exports.ObjectStore = bucket => { + if (env.SELF_HOSTED) { + AWS.config.update({ + accessKeyId: env.MINIO_ACCESS_KEY, + secretAccessKey: env.MINIO_SECRET_KEY, + }) + } + const config = { + s3ForcePathStyle: true, + signatureVersion: "v4", + params: { + Bucket: bucket, + }, + } + if (env.MINIO_URL) { + config.endpoint = env.MINIO_URL + } + return new AWS.S3(config) +} + +/** + * Given an object store and a bucket name this will make sure the bucket exists, + * if it does not exist then it will create it. + */ +exports.makeSureBucketExists = async (client, bucketName) => { + try { + await client + .headBucket({ + Bucket: bucketName, + }) + .promise() + } catch (err) { + const promises = STATE.bucketCreationPromises + if (promises[bucketName]) { + await promises[bucketName] + } else if (err.statusCode === 404) { + // bucket doesn't exist create it + promises[bucketName] = client + .createBucket({ + Bucket: bucketName, + }) + .promise() + await promises[bucketName] + delete promises[bucketName] + // public buckets are quite hidden in the system, make sure + // no bucket is set accidentally + if (PUBLIC_BUCKETS.includes(bucketName)) { + await client + .putBucketPolicy({ + Bucket: bucketName, + Policy: JSON.stringify(publicPolicy(bucketName)), + }) + .promise() + } + } else { + throw err + } + } +} + +/** + * Uploads the contents of a file given the required parameters, useful when + * temp files in use (for example file uploaded as an attachment). + */ +exports.upload = async ({ bucket, filename, path, type, metadata }) => { + const extension = [...filename.split(".")].pop() + const fileBytes = fs.readFileSync(path) + + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const config = { + // windows file paths need to be converted to forward slashes for s3 + Key: sanitize(filename).replace(/\\/g, "/"), + Body: fileBytes, + ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()], + } + if (metadata) { + config.Metadata = metadata + } + return objectStore.upload(config).promise() +} + +/** + * Similar to the upload function but can be used to send a file stream + * through to the object store. + */ +exports.streamUpload = async (bucket, filename, stream) => { + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const params = { + Bucket: bucket, + Key: sanitize(filename).replace(/\\/g, "/"), + Body: stream, + } + return objectStore.upload(params).promise() +} + +/** + * retrieves the contents of a file from the object store, if it is a known content type it + * will be converted, otherwise it will be returned as a buffer stream. + */ +exports.retrieve = async (bucket, filepath) => { + const objectStore = exports.ObjectStore(bucket) + const params = { + Bucket: bucket, + Key: sanitize(filepath).replace(/\\/g, "/"), + } + const response = await objectStore.getObject(params).promise() + // currently these are all strings + if (STRING_CONTENT_TYPES.includes(response.ContentType)) { + return response.Body.toString("utf8") + } else { + return response.Body + } +} + +/** + * Same as retrieval function but puts to a temporary file. + */ +exports.retrieveToTmp = async (bucket, filepath) => { + const data = await exports.retrieve(bucket, filepath) + const outputPath = join(budibaseTempDir(), uuid()) + fs.writeFileSync(outputPath, data) + return outputPath +} + +exports.deleteFolder = async (bucket, folder) => { + const client = exports.ObjectStore(bucket) + const listParams = { + Bucket: bucket, + Prefix: folder, + } + + let response = await client.listObjects(listParams).promise() + if (response.Contents.length === 0) { + return + } + const deleteParams = { + Bucket: bucket, + Delete: { + Objects: [], + }, + } + + response.Contents.forEach(content => { + deleteParams.Delete.Objects.push({ Key: content.Key }) + }) + + response = await client.deleteObjects(deleteParams).promise() + // can only empty 1000 items at once + if (response.Deleted.length === 1000) { + return exports.deleteFolder(bucket, folder) + } +} + +exports.uploadDirectory = async (bucket, localPath, bucketPath) => { + let uploads = [] + const files = fs.readdirSync(localPath, { withFileTypes: true }) + for (let file of files) { + const path = join(bucketPath, file.name) + const local = join(localPath, file.name) + if (file.isDirectory()) { + uploads.push(exports.uploadDirectory(bucket, local, path)) + } else { + uploads.push( + exports.streamUpload(bucket, path, fs.createReadStream(local)) + ) + } + } + await Promise.all(uploads) +} + +exports.downloadTarball = async (url, bucket, path) => { + const response = await fetch(url) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + const tmpPath = join(budibaseTempDir(), path) + await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) + if (!env.isTest()) { + await exports.uploadDirectory(bucket, tmpPath, path) + } + // return the temporary path incase there is a use for it + return tmpPath +} diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 7420884d44..7d6794b1b3 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,5 @@ const env = require("../environment") const { DocumentTypes, SEPARATOR } = require("../db/utils") -const fs = require("fs") const CouchDB = require("../db") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -13,14 +12,7 @@ function confirmAppId(possibleAppId) { exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) -exports.isDev = () => { - return ( - !env.CLOUD && - env.NODE_ENV !== "production" && - env.NODE_ENV !== "jest" && - env.NODE_ENV !== "cypress" - ) -} +exports.isDev = env.isDev /** * Given a request tries to find the appId, which can be located in various places @@ -28,10 +20,18 @@ exports.isDev = () => { * @returns {string|undefined} If an appId was found it will be returned. */ exports.getAppId = ctx => { - let appId = confirmAppId(ctx.headers["x-budibase-app-id"]) - if (!appId) { - appId = confirmAppId(env.CLOUD ? ctx.subdomains[1] : ctx.params.appId) + const options = [ctx.headers["x-budibase-app-id"], ctx.params.appId] + if (ctx.subdomains) { + options.push(ctx.subdomains[1]) } + let appId + for (let option of options) { + appId = confirmAppId(option) + if (appId) { + break + } + } + // look in body if can't find it in subdomain if (!appId && ctx.request.body && ctx.request.body.appId) { appId = confirmAppId(ctx.request.body.appId) @@ -51,7 +51,7 @@ exports.getAppId = ctx => { * @returns {string} The name of the token trying to find */ exports.getCookieName = (name = "builder") => { - let environment = env.CLOUD ? "cloud" : "local" + let environment = env.isProd() ? "cloud" : "local" return `budibase:${name}:${environment}` } @@ -89,24 +89,6 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } -/** - * Recursively walk a directory tree and execute a callback on all files. - * @param {String} dirPath - Directory to traverse - * @param {Function} callback - callback to execute on files - */ -exports.walkDir = (dirPath, callback) => { - for (let filename of fs.readdirSync(dirPath)) { - const filePath = `${dirPath}/${filename}` - const stat = fs.lstatSync(filePath) - - if (stat.isFile()) { - callback(filePath) - } else { - exports.walkDir(filePath, callback) - } - } -} - exports.getLogoUrl = () => { return "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" } diff --git a/packages/server/src/utilities/initialiseBudibase.js b/packages/server/src/utilities/initialiseBudibase.js deleted file mode 100644 index bdbd194eaa..0000000000 --- a/packages/server/src/utilities/initialiseBudibase.js +++ /dev/null @@ -1,37 +0,0 @@ -const { existsSync, readFile, writeFile, ensureDir } = require("fs-extra") -const { join, resolve } = require("./centralPath") -const { processString } = require("@budibase/string-templates") -const uuid = require("uuid") - -module.exports = async opts => { - await ensureDir(opts.dir) - await setCouchDbUrl(opts) - - // need an env file - await createDevEnvFile(opts) -} - -const setCouchDbUrl = async opts => { - if (!opts.couchDbUrl) { - const dataDir = join(opts.dir, ".data") - await ensureDir(dataDir) - opts.couchDbUrl = - dataDir + (dataDir.endsWith("/") || dataDir.endsWith("\\") ? "" : "/") - } -} - -const createDevEnvFile = async opts => { - const destConfigFile = join(opts.dir, "./.env") - let createConfig = !existsSync(destConfigFile) || opts.quiet - if (createConfig) { - const template = await readFile( - resolve(__dirname, "..", "..", ".env.template"), - { - encoding: "utf8", - } - ) - opts.cookieKey1 = opts.cookieKey1 || uuid.v4() - const config = await processString(template, opts) - await writeFile(destConfigFile, config, { flag: "w+" }) - } -} diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index eddd597459..97e2a2880c 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -180,7 +180,7 @@ exports.outputProcessing = async (appId, table, rows) => { rows ) // update the attachments URL depending on hosting - if (env.CLOUD && env.SELF_HOSTED) { + if (env.isProd() && env.SELF_HOSTED) { for (let [property, column] of Object.entries(table.schema)) { if (column.type === FieldTypes.ATTACHMENT) { for (let row of outputRows) { diff --git a/packages/server/src/utilities/templates.js b/packages/server/src/utilities/templates.js deleted file mode 100644 index c3d89477df..0000000000 --- a/packages/server/src/utilities/templates.js +++ /dev/null @@ -1,81 +0,0 @@ -const fs = require("fs-extra") -const { join } = require("./centralPath") -const os = require("os") -const fetch = require("node-fetch") -const stream = require("stream") -const tar = require("tar-fs") -const zlib = require("zlib") -const { promisify } = require("util") -const streamPipeline = promisify(stream.pipeline) -const { budibaseAppsDir } = require("./budibaseDir") -const env = require("../environment") -const CouchDB = require("../db") - -const DEFAULT_TEMPLATES_BUCKET = - "prod-budi-templates.s3-eu-west-1.amazonaws.com" - -exports.getLocalTemplates = function() { - const templatesDir = join(os.homedir(), ".budibase", "templates", "app") - const templateObj = { app: {} } - fs.ensureDirSync(templatesDir) - const templateNames = fs.readdirSync(templatesDir) - for (let name of templateNames) { - templateObj.app[name] = { - name, - category: "local", - description: "local template", - type: "app", - key: `app/${name}`, - } - } - return templateObj -} - -// can't really test this, downloading is just not something we should do in a behavioural test -/* istanbul ignore next */ -exports.downloadTemplate = async function(type, name) { - const dirName = join(budibaseAppsDir(), "templates", type, name) - if (env.LOCAL_TEMPLATES) { - return dirName - } - const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` - const response = await fetch(templateUrl) - - if (!response.ok) { - throw new Error( - `Error downloading template ${type}:${name}: ${response.statusText}` - ) - } - - // stream the response, unzip and extract - await streamPipeline( - response.body, - zlib.Unzip(), - tar.extract(join(budibaseAppsDir(), "templates", type)) - ) - - return dirName -} - -async function performDump({ dir, appId, name = "dump.txt" }) { - const writeStream = fs.createWriteStream(join(dir, name)) - // perform couch dump - const instanceDb = new CouchDB(appId) - await instanceDb.dump(writeStream, {}) -} - -exports.performDump = performDump - -exports.exportTemplateFromApp = async function({ templateName, appId }) { - // Copy frontend files - const templatesDir = join( - budibaseAppsDir(), - "templates", - "app", - templateName, - "db" - ) - fs.ensureDirSync(templatesDir) - await performDump({ dir: templatesDir, appId }) - return templatesDir -} diff --git a/packages/server/src/utilities/tests/csvParser.spec.js b/packages/server/src/utilities/tests/csvParser.spec.js index 48e275fdd1..76ea9a7eb3 100644 --- a/packages/server/src/utilities/tests/csvParser.spec.js +++ b/packages/server/src/utilities/tests/csvParser.spec.js @@ -1,4 +1,4 @@ -const fs = require("fs") +const { readFileSync } = require("../fileSystem") const csvParser = require("../csvParser") const CSV_PATH = __dirname + "/test.csv" @@ -33,7 +33,7 @@ const SCHEMAS = { } describe("CSV Parser", () => { - const csvString = fs.readFileSync(CSV_PATH, "utf8") + const csvString = readFileSync(CSV_PATH, "utf8") describe("parsing", () => { it("returns status and types for a valid CSV transformation", async () => { diff --git a/packages/server/src/utilities/usageQuota.js b/packages/server/src/utilities/usageQuota.js index d809d9e673..d042d290d5 100644 --- a/packages/server/src/utilities/usageQuota.js +++ b/packages/server/src/utilities/usageQuota.js @@ -50,7 +50,7 @@ exports.Properties = { } exports.getAPIKey = async appId => { - if (env.SELF_HOSTED) { + if (!env.USE_QUOTAS) { return { apiKey: null } } return apiKeyTable.get({ primary: appId }) @@ -65,8 +65,7 @@ exports.getAPIKey = async appId => { * also been reset after this call. */ exports.update = async (apiKey, property, usage) => { - // don't try validate in builder - if (!env.CLOUD || env.SELF_HOSTED) { + if (!env.USE_QUOTAS) { return } try { diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 11aa95bf90..599b64ca60 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f" integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA== +"@adobe/spectrum-css-workflow-icons@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.0.tgz#cda8bbe873ba9317160458858ae979e5393e5550" + integrity sha512-STSQQHvoBM0kf1JrNL3KEt88RklIctaGyGOzwUTnhtTkT1jHLaF4FgxrPDCvr1AT8VOq1nGplKUCeyZ9vdUUmA== + "@azure/ms-rest-azure-env@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f" @@ -249,6 +254,110 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@budibase/bbui@^1.58.13": + version "1.58.13" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.13.tgz#59df9c73def2d81c75dcbd2266c52c19db88dbd7" + integrity sha512-Zk6CKXdBfKsTVzA1Xs5++shdSSZLfphVpZuKVbjfzkgtuhyH7ruucexuSHEpFsxjW5rEKgKIBoRFzCK5vPvN0w== + dependencies: + markdown-it "^12.0.2" + quill "^1.3.7" + sirv-cli "^0.4.6" + svelte-flatpickr "^2.4.0" + svelte-portal "^1.0.0" + turndown "^7.0.0" + +"@budibase/client@^0.8.9": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.10.tgz#18cd929dddfd2fa65c15e23871a26bccaa03049f" + integrity sha512-C/25rBAspQDk7Lp8w9icEcytgMAm8+7l+A4Lp7egpDJvrH+5jq2kVNuRRi3emAddCSTBmM0amIoS+PHUGvAa9Q== + dependencies: + "@budibase/string-templates" "^0.8.10" + regexparam "^1.3.0" + shortid "^2.2.15" + svelte-spa-router "^3.0.5" + +"@budibase/handlebars-helpers@^0.11.3": + version "0.11.3" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.3.tgz#b6e5c91b83e8906e7d7ff10ddde277a3d561016e" + integrity sha512-MS1ptZEYq8o9J3tNLM7cZ2RGSSJIer4GiMIUHtbBI3sC9UKqZebao1JYNfmZKpNjntuqhZKgjqc5GfnVIEjsYQ== + dependencies: + arr-flatten "^1.1.0" + array-sort "^0.1.4" + define-property "^1.0.0" + extend-shallow "^3.0.2" + "falsey" "^0.3.2" + for-in "^1.0.2" + for-own "^1.0.0" + get-object "^0.2.0" + get-value "^2.0.6" + handlebars "^4.0.11" + handlebars-utils "^1.0.6" + has-value "^1.0.0" + 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.0" + is-number "^4.0.0" + kind-of "^6.0.0" + logging-helpers "^1.0.0" + micromatch "^3.1.4" + relative "^3.0.2" + striptags "^3.1.0" + to-gfm-code-block "^0.1.1" + year "^0.2.1" + +"@budibase/standard-components@^0.8.9": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.8.10.tgz#ffde4e3bfe5afe14c242d7066328066500333b62" + integrity sha512-zXE9SbMFxrZ8tpYmTaBQEPwT+3xJ8p4QtDkSRfnEcxbWojNghh5ikn0897FpgnP7q9x+63tDQ0UCiUcNoiOk7w== + dependencies: + "@adobe/spectrum-css-workflow-icons" "^1.1.0" + "@budibase/bbui" "^1.58.13" + "@budibase/svelte-ag-grid" "^1.0.4" + "@spectrum-css/actionbutton" "^1.0.0-beta.1" + "@spectrum-css/button" "^3.0.0-beta.6" + "@spectrum-css/checkbox" "^3.0.0-beta.6" + "@spectrum-css/fieldlabel" "^3.0.0-beta.7" + "@spectrum-css/icon" "^3.0.0-beta.2" + "@spectrum-css/inputgroup" "^3.0.0-beta.7" + "@spectrum-css/menu" "^3.0.0-beta.5" + "@spectrum-css/page" "^3.0.0-beta.0" + "@spectrum-css/picker" "^1.0.0-beta.3" + "@spectrum-css/popover" "^3.0.0-beta.6" + "@spectrum-css/stepper" "^3.0.0-beta.7" + "@spectrum-css/textfield" "^3.0.0-beta.6" + "@spectrum-css/vars" "^3.0.0-beta.2" + apexcharts "^3.22.1" + flatpickr "^4.6.6" + loadicons "^1.0.0" + lodash.debounce "^4.0.8" + markdown-it "^12.0.2" + quill "^1.3.7" + remixicon "^2.5.0" + svelte-apexcharts "^1.0.2" + svelte-flatpickr "^3.1.0" + turndown "^7.0.0" + +"@budibase/string-templates@^0.8.10", "@budibase/string-templates@^0.8.9": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.10.tgz#57b1cfb44d4bdac0ab3ef9a4912a6185e972711b" + integrity sha512-YUtbbAvLcxpeEiCs36cF5bkEDgpLaklvpuG/pcUIOxAPr1feqcN1KyFbuoSPihZOWXjXLgsujQerQ5xn8MUrhg== + dependencies: + "@budibase/handlebars-helpers" "^0.11.3" + dayjs "^1.10.4" + handlebars "^4.7.6" + handlebars-utils "^1.0.6" + lodash "^4.17.20" + +"@budibase/svelte-ag-grid@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@budibase/svelte-ag-grid/-/svelte-ag-grid-1.0.4.tgz#41cceec4bde2c4aea8b9da8f610fe36055c7709f" + integrity sha512-JZm6qujxnZpqw7Twbegr6se4sHhyWzN0Cibrk5bVBH32hBgzD6dd33fxwrjHKkWFxjys9wRT+cqYgYVlSt9E3w== + dependencies: + ag-grid-community "^24.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -776,6 +885,11 @@ path-to-regexp "^1.1.1" urijs "^1.19.0" +"@polka/url@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31" + integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw== + "@sendgrid/client@^7.1.1": version "7.4.2" resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.4.2.tgz#204a9fbb5dc05a721a5d8cd8930f57f9f8e612b1" @@ -888,6 +1002,73 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@spectrum-css/actionbutton@^1.0.0-beta.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.1.tgz#9c75da37ea6915919fb574c74bd60dacc03b6577" + integrity sha512-AUqtyNabHF451Aj9i3xz82TxS5Z6k1dttA68/1hMeU9kbPCSS4P6Viw3vaRGs9CSspuR8xnnhDgrq+F+zMy2Hw== + +"@spectrum-css/button@^3.0.0-beta.6": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643" + integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA== + +"@spectrum-css/checkbox@^3.0.0-beta.6": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.1.tgz#6f36377d8bd556989ddd1dec2506dc295c5fcda8" + integrity sha512-fI0q2Cp6yU4ORyE6JWUSMYNgEtGf6AjYViZ2Weg3UPTYBQuWdQd8J0ZTcH38pDMyARFPRdiXgQ3KnyX5Hk5huw== + +"@spectrum-css/fieldlabel@^3.0.0-beta.7": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.1.tgz#39f7c0f25cc2ff402afeff005341b0832f7c588c" + integrity sha512-LMfwrwIq8wEEvxFLobdLvXRwKrp8o9Fty4iJ9aYl2Rj1uXkfRd8qLz9HGZjLEE1OuJgoTBgamYABl7EvoA5PLw== + +"@spectrum-css/icon@^3.0.0-beta.2": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.1.tgz#e300a6fc353c85c6b5d6e7a364408a940c31b177" + integrity sha512-cGFtIrcQ/7tthdkHK1npuEFiCdYVHLqwmLxghUYQw8Tb8KgJaw3OBO1tpjgsUizexNgu26BjVRIbGxNWuBXIHQ== + +"@spectrum-css/inputgroup@^3.0.0-beta.7": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.1.tgz#8c5b257b57b3b2cf04e99355709365fa0d6838cc" + integrity sha512-asBRa1jTlld6plkcq4ySO+xl+OJlCMSOLoAFdSSIJowcSlCV0yDy7oeOhf5YQv9mMHFWTKlWUSoAKDZTguIPxA== + +"@spectrum-css/menu@^3.0.0-beta.5": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.1.tgz#2a376f991acc24e12ec892bb6b9db2650fc41fbe" + integrity sha512-Qjg0+1O0eC89sb/bRFq2AGnQ8XqhVy23TUXHyffNM8qdcMssnlny3QmhzjURCZKvx/Y5UytCpzhedPQqSpQwZg== + +"@spectrum-css/page@^3.0.0-beta.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.1.tgz#5e1c3dd5b1a1ee591f9d636b75f03665f542d846" + integrity sha512-LAlKF8km5BlsGPpZ2SNtwKOQIHn1lz0X93aczGZVZceOg73O4gyeoT5cx4vi1z+KtBRY5VMDWx3XgGtUwwjqwA== + dependencies: + "@spectrum-css/vars" "^3.0.1" + +"@spectrum-css/picker@^1.0.0-beta.3": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.1.tgz#98991198576d26bd14160824e7b6f3c278ff930b" + integrity sha512-Rv4/UBOdNW1gs7WVBCJnPD5VFly8MqP++psDX6kcugUIcfJy0GC3acvElotmKRlCDk8Qxks2W2A0jKeSgphTmA== + +"@spectrum-css/popover@^3.0.0-beta.6": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.1.tgz#5863c1efc53f98f9aba2de9186666780041303fc" + integrity sha512-LmOSj/yCwQQ9iGmCYnHiJsJR/HfPiGqI1Jl7pkKxBOCxYBMS/5+ans9vfCN2Qnd0eK7WSbfPg72S6mjye7db2Q== + +"@spectrum-css/stepper@^3.0.0-beta.7": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.1.tgz#7f270f53505e7dbe082591e8ea1c4c8f397e045a" + integrity sha512-IvZlGFJ8QPr9tUz5xvVN4hASaTRDPdKu9IIp25q/x0ecgSrKAM55e3EBWEYWy1H1JI3h+zlPnNRuK0VLhDbCYA== + +"@spectrum-css/textfield@^3.0.0-beta.6": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.1.tgz#e875b8e37817378ad08fc4af7d53026df38911e5" + integrity sha512-MUV5q87CVxbkNdSNoxGrFbgyKc51ft/WWf3aVEoPdPw5yBnXqFe1w1YmAit5zYDOOhhs58sCLAlUcCMlOpkgrA== + +"@spectrum-css/vars@^3.0.0-beta.2", "@spectrum-css/vars@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.1.tgz#561fd69098f896a647242dd8d6108af603bfa31e" + integrity sha512-l4oRcCOqInChYXZN6OQhpe3isk6l4OE6Ys8cgdlsiKp53suNoQxyyd9p/eGRbCjZgH3xQ8nK0t4DHa7QYC0S6w== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -1172,6 +1353,11 @@ adal-node@^0.1.28: xmldom ">= 0.1.x" xpath.js "~1.1.0" +ag-grid-community@^24.0.0: + version "24.1.0" + resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-24.1.0.tgz#1e3cab51211822e08d56f03a491b7c0deaa398e6" + integrity sha512-pWnWphuDcejZ8ahf6C734EpCx3XQ6dHEZWMWTlCdHNT0mZBLJ4YKCGACX+ttAEtSX2MGM3G13JncvuratUlYag== + agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -1217,6 +1403,130 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" +ansi-bgblack@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz#a68ba5007887701b6aafbe3fa0dadfdfa8ee3ca2" + integrity sha1-poulAHiHcBtqr74/oNrf36juPKI= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgblue@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgblue/-/ansi-bgblue-0.1.1.tgz#67bdc04edc9b9b5278969da196dea3d75c8c3613" + integrity sha1-Z73ATtybm1J4lp2hlt6j11yMNhM= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgcyan@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgcyan/-/ansi-bgcyan-0.1.1.tgz#58489425600bde9f5507068dd969ebfdb50fe768" + integrity sha1-WEiUJWAL3p9VBwaN2Wnr/bUP52g= + dependencies: + ansi-wrap "0.1.0" + +ansi-bggreen@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bggreen/-/ansi-bggreen-0.1.1.tgz#4e3191248529943f4321e96bf131d1c13816af49" + integrity sha1-TjGRJIUplD9DIelr8THRwTgWr0k= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgmagenta@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgmagenta/-/ansi-bgmagenta-0.1.1.tgz#9b28432c076eaa999418672a3efbe19391c2c7a1" + integrity sha1-myhDLAduqpmUGGcqPvvhk5HCx6E= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgred@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgred/-/ansi-bgred-0.1.1.tgz#a76f92838382ba43290a6c1778424f984d6f1041" + integrity sha1-p2+Sg4OCukMpCmwXeEJPmE1vEEE= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgwhite@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgwhite/-/ansi-bgwhite-0.1.1.tgz#6504651377a58a6ececd0331994e480258e11ba8" + integrity sha1-ZQRlE3elim7OzQMxmU5IAljhG6g= + dependencies: + ansi-wrap "0.1.0" + +ansi-bgyellow@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bgyellow/-/ansi-bgyellow-0.1.1.tgz#c3fe2eb08cd476648029e6874d15a0b38f61d44f" + integrity sha1-w/4usIzUdmSAKeaHTRWgs49h1E8= + dependencies: + ansi-wrap "0.1.0" + +ansi-black@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-black/-/ansi-black-0.1.1.tgz#f6185e889360b2545a1ec50c0bf063fc43032453" + integrity sha1-9hheiJNgslRaHsUMC/Bj/EMDJFM= + dependencies: + ansi-wrap "0.1.0" + +ansi-blue@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-blue/-/ansi-blue-0.1.1.tgz#15b804990e92fc9ca8c5476ce8f699777c21edbf" + integrity sha1-FbgEmQ6S/JyoxUds6PaZd3wh7b8= + dependencies: + ansi-wrap "0.1.0" + +ansi-bold@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-bold/-/ansi-bold-0.1.1.tgz#3e63950af5acc2ae2e670e6f67deb115d1a5f505" + integrity sha1-PmOVCvWswq4uZw5vZ96xFdGl9QU= + dependencies: + ansi-wrap "0.1.0" + +ansi-colors@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-0.2.0.tgz#72c31de2a0d9a2ccd0cac30cc9823eeb2f6434b5" + integrity sha1-csMd4qDZoszQysMMyYI+6y9kNLU= + dependencies: + ansi-bgblack "^0.1.1" + ansi-bgblue "^0.1.1" + ansi-bgcyan "^0.1.1" + ansi-bggreen "^0.1.1" + ansi-bgmagenta "^0.1.1" + ansi-bgred "^0.1.1" + ansi-bgwhite "^0.1.1" + ansi-bgyellow "^0.1.1" + ansi-black "^0.1.1" + ansi-blue "^0.1.1" + ansi-bold "^0.1.1" + ansi-cyan "^0.1.1" + ansi-dim "^0.1.1" + ansi-gray "^0.1.1" + ansi-green "^0.1.1" + ansi-grey "^0.1.1" + ansi-hidden "^0.1.1" + ansi-inverse "^0.1.1" + ansi-italic "^0.1.1" + ansi-magenta "^0.1.1" + ansi-red "^0.1.1" + ansi-reset "^0.1.1" + ansi-strikethrough "^0.1.1" + ansi-underline "^0.1.1" + ansi-white "^0.1.1" + ansi-yellow "^0.1.1" + lazy-cache "^2.0.1" + +ansi-cyan@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" + integrity sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM= + dependencies: + ansi-wrap "0.1.0" + +ansi-dim@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-dim/-/ansi-dim-0.1.1.tgz#40de4c603aa8086d8e7a86b8ff998d5c36eefd6c" + integrity sha1-QN5MYDqoCG2Oeoa4/5mNXDbu/Ww= + dependencies: + ansi-wrap "0.1.0" + ansi-escapes@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -1229,6 +1539,62 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= + dependencies: + ansi-wrap "0.1.0" + +ansi-green@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-green/-/ansi-green-0.1.1.tgz#8a5d9a979e458d57c40e33580b37390b8e10d0f7" + integrity sha1-il2al55FjVfEDjNYCzc5C44Q0Pc= + dependencies: + ansi-wrap "0.1.0" + +ansi-grey@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-grey/-/ansi-grey-0.1.1.tgz#59d98b6ac2ba19f8a51798e9853fba78339a33c1" + integrity sha1-WdmLasK6GfilF5jphT+6eDOaM8E= + dependencies: + ansi-wrap "0.1.0" + +ansi-hidden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-hidden/-/ansi-hidden-0.1.1.tgz#ed6a4c498d2bb7cbb289dbf2a8d1dcc8567fae0f" + integrity sha1-7WpMSY0rt8uyidvyqNHcyFZ/rg8= + dependencies: + ansi-wrap "0.1.0" + +ansi-inverse@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-inverse/-/ansi-inverse-0.1.1.tgz#b6af45826fe826bfb528a6c79885794355ccd269" + integrity sha1-tq9Fgm/oJr+1KKbHmIV5Q1XM0mk= + dependencies: + ansi-wrap "0.1.0" + +ansi-italic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-italic/-/ansi-italic-0.1.1.tgz#104743463f625c142a036739cf85eda688986f23" + integrity sha1-EEdDRj9iXBQqA2c5z4XtpoiYbyM= + dependencies: + ansi-wrap "0.1.0" + +ansi-magenta@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-magenta/-/ansi-magenta-0.1.1.tgz#063b5ba16fb3f23e1cfda2b07c0a89de11e430ae" + integrity sha1-BjtboW+z8j4c/aKwfAqJ3hHkMK4= + dependencies: + ansi-wrap "0.1.0" + +ansi-red@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" + integrity sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw= + dependencies: + ansi-wrap "0.1.0" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -1249,6 +1615,20 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-reset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-reset/-/ansi-reset-0.1.1.tgz#e7e71292c3c7ddcd4d62ef4a6c7c05980911c3b7" + integrity sha1-5+cSksPH3c1NYu9KbHwFmAkRw7c= + dependencies: + ansi-wrap "0.1.0" + +ansi-strikethrough@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-strikethrough/-/ansi-strikethrough-0.1.1.tgz#d84877140b2cff07d1c93ebce69904f68885e568" + integrity sha1-2Eh3FAss/wfRyT685pkE9oiF5Wg= + dependencies: + ansi-wrap "0.1.0" + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1263,6 +1643,32 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-underline@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-underline/-/ansi-underline-0.1.1.tgz#dfc920f4c97b5977ea162df8ffb988308aaa71a4" + integrity sha1-38kg9Ml7WXfqFi34/7mIMIqqcaQ= + dependencies: + ansi-wrap "0.1.0" + +ansi-white@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-white/-/ansi-white-0.1.1.tgz#9c77b7c193c5ee992e6011d36ec4c921b4578944" + integrity sha1-nHe3wZPF7pkuYBHTbsTJIbRXiUQ= + dependencies: + ansi-wrap "0.1.0" + +ansi-wrap@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= + +ansi-yellow@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-yellow/-/ansi-yellow-0.1.1.tgz#cb9356f2f46c732f0e3199e6102955a77da83c1d" + integrity sha1-y5NW8vRscy8OMZnmEClVp32oPB0= + dependencies: + ansi-wrap "0.1.0" + any-base@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" @@ -1289,6 +1695,18 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apexcharts@^3.19.2, apexcharts@^3.22.1: + version "3.26.0" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.26.0.tgz#a78abc108b2e1b3086a738f0ec7c98e292f4a14b" + integrity sha512-zdYHs3k3tgmCn1BpYLj7rhGEndBYF33Pq1+g0ora37xAr+3act5CJrpdXM2jx2boVUyXgavoSp6sa8WpK7RkSA== + dependencies: + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + app-builder-bin@3.5.10: version "3.5.10" resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.10.tgz#4a7f9999fccc0c435b6284ae1366bc76a17c4a7d" @@ -1340,13 +1758,18 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -argparse@^1.0.7: +argparse@^1.0.10, 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" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + args@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761" @@ -1382,6 +1805,15 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= +array-sort@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-0.1.4.tgz#662855eaeb671b4188df4451b2f24a0753992b23" + integrity sha512-BNcM+RXxndPxiZ2rd76k6nyQLRZr2/B/sdi8pQ+Joafr5AH279L40dfokSUTp8O+AaqYjXWhblBWa2st2nc4fQ== + 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" @@ -1459,6 +1891,13 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +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-sdk@^2.767.0: version "2.771.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.771.0.tgz#ff4beb0a04d6ab1ae962c85dfb42e3e9bfe2b93b" @@ -2054,6 +2493,11 @@ clone-response@1.0.2, clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + co-body@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" @@ -2118,6 +2562,11 @@ commander@^2.5.0, commander@^2.8.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + commoner@^0.10.1: version "0.10.8" resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" @@ -2160,6 +2609,13 @@ concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-with-sourcemaps@*: + 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" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -2180,6 +2636,11 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" +console-clear@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7" + integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ== + content-disposition@^0.5.2, content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -2239,13 +2700,6 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - cross-spawn@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" @@ -2265,7 +2719,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.1: +cross-spawn@^7.0.0: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2321,11 +2775,23 @@ date-utils@*: resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= +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" + dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +dayjs@^1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" + integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== + debug@4, debug@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" @@ -2431,6 +2897,18 @@ decompress@^4.2.1: pify "^2.3.0" strip-dirs "^2.0.0" +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2451,6 +2929,13 @@ 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" + default-shell@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-1.0.1.tgz#752304bddc6174f49eb29cb988feea0b8813c8bc" @@ -2576,6 +3061,11 @@ dmg-builder@22.9.1: js-yaml "^3.14.0" sanitize-filename "^1.6.3" +docker-compose@^0.23.6: + version "0.23.6" + resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.6.tgz#bd21e17d599f17fcf7a4b5d607cff0358a9c378b" + integrity sha512-y3Q8MkwG862rNqkvEQG59/7Fi2/fzs3NYDCvqUAAD+z0WGs2qcJ9hRcn34hWgWv9ouPkFqe3Vwca0h+4bIIRWw== + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -2595,6 +3085,11 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" +domino@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe" + integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ== + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2811,6 +3306,24 @@ ensure-error@^2.0.0: resolved "https://registry.yarnpkg.com/ensure-error/-/ensure-error-2.1.0.tgz#f11fbe383c0cf4a54850ac77acceb7bc06e0f99d" integrity sha512-+BMSJHw9gxiJAAp2ZR1E0TNcL09dD3lOvkl7WVm4+Y6xnes/pMetP/TzCHiDduh8ihNDjbGfuYxl7l4PA1xZ8A== +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.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + +env-cmd@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-10.1.0.tgz#c7f5d3b550c9519f137fdac4dd8fb6866a8c8c4b" + integrity sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA== + dependencies: + commander "^4.0.0" + cross-spawn "^7.0.0" + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -2835,6 +3348,11 @@ error-inject@^1.0.0: resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37" integrity sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc= +error-symbol@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" + integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= + es-abstract@^1.18.0-next.2: version "1.18.0-next.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.3.tgz#56bc8b5cc36b2cca25a13be07f3c02c2343db6b7" @@ -3077,6 +3595,11 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3178,7 +3701,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, 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== @@ -3236,11 +3759,23 @@ falafel@^1.0.1: isarray "0.0.1" object-keys "^1.0.6" +"falsey@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/falsey/-/falsey-0.3.2.tgz#b21c90c5c34660fc192bf909575db95b6880d597" + integrity sha512-lxEuefF5MBIVDmE6XeqCdM4BWk1+vYmGZtkbKZ/VFcg6uBBw6fXNEbWmxCjDdQlFc9hy450nkiWwM3VAW6G1qg== + dependencies: + kind-of "^5.0.2" + 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" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -3420,6 +3955,11 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flatpickr@^4.5.2, flatpickr@^4.6.6: + version "4.6.9" + resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" + integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== + flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" @@ -3449,11 +3989,18 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.2: +for-in@^1.0.1, 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= +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= + dependencies: + for-in "^1.0.1" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -3512,6 +4059,11 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +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, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -3588,6 +4140,19 @@ 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-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -3785,6 +4350,35 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +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.0.11, handlebars@^4.7.6: + 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" @@ -3873,6 +4467,39 @@ has@^1.0.3: dependencies: function-bind "^1.1.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" + +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== + hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -3902,6 +4529,14 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +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-assert@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878" @@ -4059,6 +4694,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" +info-symbol@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/info-symbol/-/info-symbol-0.1.0.tgz#27841d72867ddb4242cd612d79c10633881c6a78" + integrity sha1-J4QdcoZ920JCzWEtecEGM4gcang= + inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -4135,6 +4775,13 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arguments@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.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" @@ -4230,6 +4877,13 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== +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" @@ -4307,6 +4961,13 @@ 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" @@ -4314,6 +4975,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -4329,6 +4995,13 @@ is-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= +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-path-inside@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" @@ -4346,7 +5019,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-regex@^1.1.2: +is-regex@^1.0.4, is-regex@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== @@ -4359,6 +5032,13 @@ is-retry-allowed@^1.1.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== +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-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -4437,6 +5117,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +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" @@ -5113,7 +5798,7 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.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= @@ -5127,7 +5812,7 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0: +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== @@ -5137,7 +5822,7 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@^3.0.3: +kleur@^3.0.0, kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== @@ -5268,6 +5953,13 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" +lazy-cache@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" + integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ= + dependencies: + set-getter "^0.1.0" + lazy-val@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.4.tgz#882636a7245c2cfe6e0a4e3ba6c5d68a137e5c65" @@ -5439,6 +6131,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +linkify-it@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" + integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== + dependencies: + uc.micro "^1.0.1" + load-bmfont@^1.3.1, load-bmfont@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" @@ -5463,6 +6162,16 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +loadicons@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loadicons/-/loadicons-1.0.0.tgz#79fd9b08ef2933988c94068cbd246ef3f21cbd04" + integrity sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw== + +local-access@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798" + integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5478,6 +6187,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +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.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -5548,6 +6262,21 @@ 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.without@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" @@ -5568,11 +6297,40 @@ lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.3: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -lodash@^4.17.19: +lodash@^4.17.19, lodash@^4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-ok@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/log-ok/-/log-ok-0.1.1.tgz#bea3dd36acd0b8a7240d78736b5b97c65444a334" + integrity sha1-vqPdNqzQuKckDXhza1uXxlREozQ= + dependencies: + ansi-green "^0.1.1" + success-symbol "^0.1.0" + +log-utils@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/log-utils/-/log-utils-0.2.1.tgz#a4c217a0dd9a50515d9b920206091ab3d4e031cf" + integrity sha1-pMIXoN2aUFFdm5ICBgkas9TgMc8= + dependencies: + ansi-colors "^0.2.0" + error-symbol "^0.1.0" + info-symbol "^0.1.0" + log-ok "^0.1.1" + success-symbol "^0.1.0" + time-stamp "^1.0.1" + warning-symbol "^0.1.0" + +logging-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/logging-helpers/-/logging-helpers-1.0.0.tgz#b5a37b32ad53eb0137c58c7898a47b175ddb7c36" + integrity sha512-qyIh2goLt1sOgQQrrIWuwkRjUx4NUcEqEGAcYqD8VOnOC6ItwkrVE8/tA4smGpjzyp4Svhc6RodDp9IO5ghpyA== + dependencies: + isobject "^3.0.0" + log-utils "^0.2.1" + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5668,6 +6426,17 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.4.tgz#eec8247d296327eac3ba9746bdeec9cfcc751e33" + integrity sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -5675,6 +6444,11 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5784,6 +6558,11 @@ mime@^1.3.4, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.3.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + mime@^2.4.6: version "2.4.6" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" @@ -5838,6 +6617,11 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4: 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== + mongodb@3.6.3: version "3.6.3" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.3.tgz#eddaed0cc3598474d7a15f0f2a5b04848489fd05" @@ -5856,6 +6640,11 @@ mri@1.1.4: resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== +mri@^1.1.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" + integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5917,6 +6706,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanoid@^2.1.0: + version "2.1.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" + integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -5964,6 +6758,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +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== + new-github-issue-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/new-github-issue-url/-/new-github-issue-url-0.2.1.tgz#e17be1f665a92de465926603e44b9f8685630c1d" @@ -6128,6 +6927,14 @@ object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -6338,6 +7145,11 @@ pako@^1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +parchment@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -7011,6 +7823,27 @@ quick-format-unescaped@^4.0.1: resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701" integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A== +quill-delta@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" + integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg== + dependencies: + deep-equal "^1.0.1" + extend "^3.0.2" + fast-diff "1.1.2" + +quill@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== + dependencies: + clone "^2.1.1" + deep-equal "^1.0.1" + eventemitter3 "^2.0.3" + extend "^3.0.2" + parchment "^1.1.4" + quill-delta "^3.6.2" + raw-body@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -7187,6 +8020,19 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +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== + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -7206,6 +8052,26 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +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" + +remixicon@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41" + integrity sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww== + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -7392,6 +8258,13 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" +sade@^1.4.0: + version "1.7.4" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" + integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== + dependencies: + mri "^1.1.0" + safe-buffer@*, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -7470,6 +8343,11 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" +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-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -7521,6 +8399,13 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-getter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" + integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y= + dependencies: + to-object-path "^0.3.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" @@ -7591,11 +8476,39 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +shortid@^2.2.15: + version "2.2.16" + resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608" + integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g== + dependencies: + nanoid "^2.1.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +sirv-cli@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04" + integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww== + dependencies: + console-clear "^1.1.0" + get-port "^3.2.0" + kleur "^3.0.0" + local-access "^1.0.1" + sade "^1.4.0" + sirv "^0.4.6" + tinydate "^1.0.0" + +sirv@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22" + integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ== + dependencies: + "@polka/url" "^0.5.0" + mime "^2.3.1" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -7986,6 +8899,11 @@ strip-outer@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +striptags@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.1.1.tgz#c8c3e7fdd6fb4bb3a32a3b752e5b5e3e38093ebd" + integrity sha1-yMPn/db7S7OjKjt1LltePjgJPr0= + sublevel-pouchdb@7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/sublevel-pouchdb/-/sublevel-pouchdb-7.2.2.tgz#49e46cd37883bf7ff5006d7c5b9bcc7bcc1f422f" @@ -7996,6 +8914,11 @@ sublevel-pouchdb@7.2.2: ltgt "2.2.1" readable-stream "1.1.14" +success-symbol@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" + integrity sha1-JAIuSG878c3KCUKDt2nEctO3KJc= + sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -8048,11 +8971,99 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +svelte-apexcharts@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1" + integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA== + dependencies: + apexcharts "^3.19.2" + +svelte-flatpickr@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-2.4.0.tgz#190871fc3305956c8c8fd3601cd036b8ac71ef49" + integrity sha512-UUC5Te+b0qi4POg7VDwfGh0m5W3Hf64OwkfOTj6FEe/dYZN4cBzpQ82EuuQl0CTbbBAsMkcjJcixV1d2V6EHCQ== + dependencies: + flatpickr "^4.5.2" + +svelte-flatpickr@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.1.0.tgz#ad83588430dbd55196a1a258b8ba27e7f9c1ee37" + integrity sha512-zKyV+ukeVuJ8CW0Ing3T19VSekc4bPkou/5Riutt1yATrLvSsanNqcgqi7Q5IePvIoOF9GJ5OtHvn1qK9Wx9BQ== + 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.1.0" + resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.1.0.tgz#a929f0def7e12c41f32bc356f91685aeadcd75bf" + integrity sha512-jlM/xwjn57mylr+pzHYCOOy+IPQauT46gOucNGTBu6jHcFXu3F+oaojN4PXC1LYizRGxFB6QA0qnYbZnRfX7Sg== + dependencies: + regexparam "1.3.0" + svelte@3.30.0: version "3.30.0" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.30.0.tgz#cbde341e96bf34f4ac73c8f14f8a014e03bfb7d6" integrity sha512-z+hdIACb9TROGvJBQWcItMtlr4s0DBUgJss6qWrtFkOoIInkG+iAMo/FJZQFyDBQZc+dul2+TzYSi/tpTT5/Ag== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12" + integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI= + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM= + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -8207,6 +9218,11 @@ through@^2.3.6, through@^2.3.8, through@~2.3.4: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +time-stamp@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= + timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -8227,6 +9243,11 @@ tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinydate@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb" + integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8249,6 +9270,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +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-json-schema@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/to-json-schema/-/to-json-schema-0.2.5.tgz#ef3c3f11ad64460dcfbdbafd0fd525d69d62a98f" @@ -8365,6 +9391,13 @@ tunnel@0.0.6, tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +turndown@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.0.0.tgz#19b2a6a2d1d700387a1e07665414e4af4fec5225" + integrity sha512-G1FfxfR0mUNMeGjszLYl3kxtopC4O9DRRiMlMDDVHvU1jaBkGFg4qxIyjIk2aiKLHyDyZvZyu4qBO2guuYBy3Q== + dependencies: + domino "^2.1.6" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -8417,6 +9450,23 @@ 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" + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +uglify-js@^3.1.4: + version "3.13.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.2.tgz#fe10319861bccc8682bfe2e8151fbdd8aa921c44" + integrity sha512-SbMu4D2Vo95LMC/MetNaso1194M1htEA+JrqE9Hk+G2DhI+itfS9TRu9ZKeCahLDNa/J3n4MqUJ/fOHMzQpRWw== + unbox-primitive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" @@ -8661,6 +9711,11 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +warning-symbol@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/warning-symbol/-/warning-symbol-0.1.0.tgz#bb31dd11b7a0f9d67ab2ed95f457b65825bbad21" + integrity sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -8738,6 +9793,11 @@ 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= + worker-farm@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -8976,6 +10036,11 @@ yauzl@^2.10.0, yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" +year@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" + integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= + ylru@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f" diff --git a/packages/worker/package.json b/packages/worker/package.json index 1670fd729e..7d88654a88 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -33,6 +33,5 @@ "pouchdb": "^7.2.2", "pouchdb-all-dbs": "^1.0.2", "server-destroy": "^1.0.1" - }, - "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" + } } diff --git a/packages/worker/src/db/index.js b/packages/worker/src/db/index.js index 24bbb55092..83f030c846 100644 --- a/packages/worker/src/db/index.js +++ b/packages/worker/src/db/index.js @@ -1,12 +1,9 @@ const PouchDB = require("pouchdb") const allDbs = require("pouchdb-all-dbs") const env = require("../environment") -const { join } = require("path") -const { homedir } = require("os") // level option is purely for testing (development) -const COUCH_DB_URL = - env.COUCH_DB_URL || `leveldb://${join(homedir(), ".budibase")}/.data/` +const COUCH_DB_URL = env.COUCH_DB_URL || "http://localhost:10000/db/" const Pouch = PouchDB.defaults({ prefix: COUCH_DB_URL,