diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 0cd7bc92bf..c672f911a2 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -24,6 +24,8 @@ services: ENABLE_ANALYTICS: "true" REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} + volumes: + - ./logs:/logs depends_on: - worker-service @@ -46,6 +48,8 @@ services: INTERNAL_API_KEY: ${INTERNAL_API_KEY} REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} + volumes: + - ./logs:/logs depends_on: - redis-service - minio-service @@ -109,6 +113,21 @@ services: - "${REDIS_PORT}:6379" volumes: - redis_data:/data + + watchtower-service: + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --debug --http-api-update budibase/apps budibase/worker + environment: + - WATCHTOWER_HTTP_API=true + - WATCHTOWER_HTTP_API_TOKEN=budibase + - WATCHTOWER_CLEANUP=true + labels: + - "com.centurylinklabs.watchtower.enable=false" + ports: + - 6666:8080 + volumes: couchdb3_data: diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 463b32ab60..6e143ac843 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -38,6 +38,11 @@ static_resources: route: cluster: worker-service + - match: { prefix: "/v1/update" } + route: + cluster: watchtower-service + + - match: { path: "/" } route: cluster: app-service @@ -123,3 +128,17 @@ static_resources: address: couchdb-service port_value: 5984 + - name: watchtower-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: watchtower-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: watchtower-service + port_value: 6666 + diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index d2ba86a524..9582d6ffd6 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -2,7 +2,7 @@ const passport = require("koa-passport") const LocalStrategy = require("passport-local").Strategy const JwtStrategy = require("passport-jwt").Strategy const { StaticDatabases } = require("./db/utils") -const { jwt, local, authenticated, google } = require("./middleware") +const { jwt, local, authenticated, google, auditLog } = require("./middleware") const { setDB, getDB } = require("./db") // Strategies @@ -45,6 +45,7 @@ module.exports = { passport, google, jwt: require("jsonwebtoken"), + auditLog, }, StaticDatabases, constants: require("./constants"), diff --git a/packages/auth/src/middleware/auditLog.js b/packages/auth/src/middleware/auditLog.js new file mode 100644 index 0000000000..919d7257ba --- /dev/null +++ b/packages/auth/src/middleware/auditLog.js @@ -0,0 +1,10 @@ +module.exports = async (ctx, next) => { + ctx.log.info({ + userId: ctx.user && ctx.user._id, + ip: ctx.ip, + url: ctx.originalUrl, + origin: ctx.origin, + method: ctx.method, + }) + return next() +} diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js index 519233eda4..2a249ce0f9 100644 --- a/packages/auth/src/middleware/index.js +++ b/packages/auth/src/middleware/index.js @@ -2,10 +2,12 @@ const jwt = require("./passport/jwt") const local = require("./passport/local") const google = require("./passport/google") const authenticated = require("./authenticated") +const auditLog = require("./auditLog") module.exports = { google, jwt, local, authenticated, + auditLog, } diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index ba5da18d98..3d9ae71fff 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -43,6 +43,10 @@ title: "Theming", href: "/builder/portal/settings/theming", }, + { + title: "Update", + href: "/builder/portal/settings/update", + }, ]) } else { menu = menu.concat([ diff --git a/packages/builder/src/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte index 046f55615b..9cb448fb17 100644 --- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte +++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte @@ -12,7 +12,7 @@ notifications, } from "@budibase/bbui" import { auth, organisation } from "stores/portal" - import { post } from "builderStore/api" + import { post, get } from "builderStore/api" import analytics from "analytics" import { writable } from "svelte/store" import { redirect } from "@roxi/routify" @@ -70,6 +70,7 @@ loading = false } + {#if $auth.isAdmin} @@ -130,10 +131,10 @@ +
+ +
-
- -
{/if} diff --git a/packages/builder/src/pages/builder/portal/settings/update.svelte b/packages/builder/src/pages/builder/portal/settings/update.svelte new file mode 100644 index 0000000000..a0eaa987cd --- /dev/null +++ b/packages/builder/src/pages/builder/portal/settings/update.svelte @@ -0,0 +1,70 @@ + + +{#if $auth.isAdmin} + + + Update + + Keep your budibase installation up to date to take advantage of the latest features, security updates and much more. + + + +
+
+ +
+
+
+{/if} + + diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 332b917a76..6c4188a5dc 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -1,5 +1,5 @@ const Router = require("@koa/router") -const { buildAuthMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth const currentApp = require("../middleware/currentapp") const compress = require("koa-compress") const zlib = require("zlib") @@ -37,6 +37,7 @@ router }) ) .use(currentApp) + .use(auditLog) // error handling middleware router.use(async (ctx, next) => { diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 50df056b2a..5772eefad3 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -5,7 +5,7 @@ require("@budibase/auth").init(CouchDB) const Koa = require("koa") const destroyable = require("server-destroy") const koaBody = require("koa-body") -const logger = require("koa-pino-logger") +const pino = require("koa-pino-logger") const http = require("http") const api = require("./api") const eventEmitter = require("./events") @@ -28,14 +28,14 @@ app.use( }) ) -app.use( - logger({ - prettyPrint: { - levelFirst: true, - }, - level: env.LOG_LEVEL || "error", - }) -) +let logger = pino({ + prettyPrint: { + levelFirst: true, + }, + level: env.LOG_LEVEL || "error", +}) + +app.use(logger) if (!env.isTest()) { const bullApp = bullboard.init() diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index eb7ce605d2..76d6713c5b 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -1,4 +1,3 @@ -const scriptController = require("../../api/controllers/script") const { execSync } = require("child_process") const { processStringSync } = require("@budibase/string-templates") diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index ae18b82e02..e1fa632003 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -12,8 +12,8 @@ exports.init = async () => { } exports.shutdown = async () => { - await devAppClient.finish() - await debounceClient.finish() + if (devAppClient) await devAppClient.finish() + if (debounceClient) await debounceClient.finish() } exports.doesUserHaveLock = async (devAppId, user) => { diff --git a/packages/worker/package.json b/packages/worker/package.json index 7a51c8ac49..b234990966 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -26,6 +26,7 @@ "@koa/router": "^8.0.0", "aws-sdk": "^2.811.0", "bcryptjs": "^2.4.3", + "dockerode": "^3.3.0", "dotenv": "^8.2.0", "got": "^11.8.1", "joi": "^17.4.0", diff --git a/packages/worker/src/api/controllers/admin/debug.js b/packages/worker/src/api/controllers/admin/debug.js new file mode 100644 index 0000000000..a96bf00c83 --- /dev/null +++ b/packages/worker/src/api/controllers/admin/debug.js @@ -0,0 +1,4 @@ +exports.fetchDebugLogs = async ctx => { + // read them from file + // serve them +} diff --git a/packages/worker/src/api/controllers/admin/updates.js b/packages/worker/src/api/controllers/admin/updates.js new file mode 100644 index 0000000000..a41beaf50c --- /dev/null +++ b/packages/worker/src/api/controllers/admin/updates.js @@ -0,0 +1 @@ +exports.updateSystem = async ctx => {} diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 1b2586a6b6..d456778d54 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -2,7 +2,7 @@ const Router = require("@koa/router") const compress = require("koa-compress") const zlib = require("zlib") const { routes } = require("./routes") -const { buildAuthMiddleware } = require("@budibase/auth").auth +const { buildAuthMiddleware, auditLog } = require("@budibase/auth").auth const PUBLIC_ENDPOINTS = [ { @@ -62,6 +62,7 @@ router } return next() }) + .use(auditLog) // error handling middleware router.use(async (ctx, next) => { diff --git a/packages/worker/src/api/routes/admin/debug.js b/packages/worker/src/api/routes/admin/debug.js new file mode 100644 index 0000000000..ee8e490d0e --- /dev/null +++ b/packages/worker/src/api/routes/admin/debug.js @@ -0,0 +1,9 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/debug") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router.get("/api/admin/debug/logs", adminOnly, controller.fetchDebugLogs) + +module.exports = router diff --git a/packages/worker/src/api/routes/admin/updates.js b/packages/worker/src/api/routes/admin/updates.js new file mode 100644 index 0000000000..a89ce8b68a --- /dev/null +++ b/packages/worker/src/api/routes/admin/updates.js @@ -0,0 +1,9 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/updates") +const adminOnly = require("../../../middleware/adminOnly") + +const router = Router() + +router.get("/api/admin/update", adminOnly, controller.updateSystem) + +module.exports = router diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index 8b232f7b7c..ae83a714db 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -5,6 +5,7 @@ const templateRoutes = require("./admin/templates") const emailRoutes = require("./admin/email") const authRoutes = require("./admin/auth") const roleRoutes = require("./admin/roles") +const updatesRoutes = require("./admin/updates") const appRoutes = require("./app") exports.routes = [ @@ -16,4 +17,5 @@ exports.routes = [ templateRoutes, emailRoutes, roleRoutes, + updatesRoutes, ] diff --git a/packages/worker/src/utilities/redis.js b/packages/worker/src/utilities/redis.js index 28162a0c14..0701e500dd 100644 --- a/packages/worker/src/utilities/redis.js +++ b/packages/worker/src/utilities/redis.js @@ -51,8 +51,8 @@ exports.init = async () => { * make sure redis connection is closed. */ exports.shutdown = async () => { - await pwResetClient.finish() - await invitationClient.finish() + if (pwResetClient) await pwResetClient.finish() + if (invitationClient) await invitationClient.finish() } /** diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index ce1185f832..9f17e18bc9 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -910,7 +910,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -asn1@~0.2.3: +asn1@~0.2.0, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -1076,7 +1076,7 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bcrypt-pbkdf@^1.0.0: +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= @@ -1093,6 +1093,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -1337,6 +1346,11 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.3.1" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -1773,6 +1787,24 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +docker-modem@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.0.tgz#cb912ad8daed42f858269fb3be6944df281ec12d" + integrity sha512-WwFajJ8I5geZ/dDZ5FDMDA6TBkWa76xWwGIGw8uzUjNUGCN0to83wJ8Oi1AxrJTC0JBn+7fvIxUctnawtlwXeg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^0.8.7" + +dockerode@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.0.tgz#bedaf48ef9fa9124275a54a9881a92374c51008e" + integrity sha512-St08lfOjpYCOXEM8XA0VLu3B3hRjtddODphNW5GFoA0AS3JHgoPQKOz0Qmdzg3P+hUPxhb02g1o1Cu1G+U3lRg== + dependencies: + docker-modem "^3.0.0" + tar-fs "~2.0.1" + domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -1857,7 +1889,7 @@ encoding-down@^6.3.0: level-codec "^9.0.0" level-errors "^2.0.0" -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2238,6 +2270,11 @@ fresh@~0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "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.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3998,6 +4035,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4829,7 +4871,7 @@ readable-stream@1.1.14: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -5326,6 +5368,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -5345,6 +5392,22 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssh2-streams@~0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" + integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== + dependencies: + asn1 "~0.2.0" + bcrypt-pbkdf "^1.0.2" + streamsearch "~0.1.2" + +ssh2@^0.8.7: + version "0.8.9" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" + integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== + dependencies: + ssh2-streams "~0.4.10" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -5390,6 +5453,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -5529,6 +5597,27 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"