diff --git a/package.json b/package.json
index 73f29e70..3ae61242 100644
--- a/package.json
+++ b/package.json
@@ -21,14 +21,15 @@
},
"devDependencies": {
"@coder/nbin": "^1.2.7",
+ "@types/adm-zip": "^0.4.32",
"@types/fs-extra": "^8.0.1",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.7",
"@types/parcel-bundler": "^1.12.1",
"@types/pem": "^1.9.5",
"@types/safe-compare": "^1.1.0",
- "@types/tar-fs": "^1.16.1",
- "@types/tar-stream": "^1.6.1",
+ "@types/semver": "^7.1.0",
+ "@types/tar-fs": "^1.16.2",
"@types/ws": "^6.0.4",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
@@ -52,12 +53,14 @@
},
"dependencies": {
"@coder/logger": "1.1.11",
+ "adm-zip": "^0.4.14",
"fs-extra": "^8.1.0",
"httpolyglot": "^0.1.2",
"pem": "^1.14.2",
"safe-compare": "^1.1.4",
+ "semver": "^7.1.3",
+ "tar": "^6.0.1",
"tar-fs": "^2.0.0",
- "tar-stream": "^2.1.0",
"ws": "^7.2.0"
}
}
diff --git a/src/browser/pages/app.ts b/src/browser/pages/app.ts
index ea453b0e..f4990195 100644
--- a/src/browser/pages/app.ts
+++ b/src/browser/pages/app.ts
@@ -5,6 +5,7 @@ import "./error.css"
import "./global.css"
import "./home.css"
import "./login.css"
+import "./update.css"
const options = getOptions()
const parts = window.location.pathname.replace(/^\//g, "").split("/")
diff --git a/src/browser/pages/global.css b/src/browser/pages/global.css
index e4c03134..5aa52865 100644
--- a/src/browser/pages/global.css
+++ b/src/browser/pages/global.css
@@ -16,6 +16,7 @@ body {
button {
font-family: inherit;
+ font-size: inherit;
}
.center-container {
diff --git a/src/browser/pages/home.css b/src/browser/pages/home.css
index 4ead54af..c14a539a 100644
--- a/src/browser/pages/home.css
+++ b/src/browser/pages/home.css
@@ -1,50 +1,54 @@
-.app-lists {
- max-width: 400px;
+.info-blocks {
+ max-width: 500px;
width: 100%;
}
-.app-list > .header {
- margin: 1rem 0;
+.info-block > .header {
+ font-size: 1.3rem;
+ margin: 1.5rem 0;
}
-.app-list > .none {
+.info-block > .none {
color: #b6b6b6;
}
-.app-list + .app-list {
+.info-block + .info-block {
border-top: 1px solid #666;
margin-top: 1rem;
}
-.app-row {
+.block-row {
display: flex;
}
-.app-row > .open {
+.block-row > .item {
color: #b6b6b6;
- cursor: pointer;
display: flex;
flex: 1;
text-decoration: none;
}
-.app-row > .open:hover {
+.block-row > .item.-link {
+ cursor: pointer;
+}
+
+.block-row > .item.-link:hover {
color: #fafafa;
}
-.app-row > .open > .icon {
+.block-row > .item > .icon {
height: 1rem;
margin-right: 5px;
width: 1rem;
}
-.app-row > .open > .icon.-missing {
+.block-row > .item > .icon.-missing {
background-color: #eee;
color: #b6b6b6;
text-align: center;
}
-.app-row > .open > .icon.-missing::after {
+.block-row > .item > .icon.-missing::after {
content: "?";
font-size: 0.7rem;
vertical-align: middle;
diff --git a/src/browser/pages/home.html b/src/browser/pages/home.html
index abe33a1a..d67bb5f5 100644
--- a/src/browser/pages/home.html
+++ b/src/browser/pages/home.html
@@ -13,18 +13,23 @@
-
-
+
+
{{APP_LIST:RUNNING}}
-
+
+
+ {{UPDATE:NAME}}
+
+
+
{{APP_LIST:EDITORS}}
-
+
{{APP_LIST:OTHER}}
diff --git a/src/browser/pages/update.css b/src/browser/pages/update.css
new file mode 100644
index 00000000..21b5e610
--- /dev/null
+++ b/src/browser/pages/update.css
@@ -0,0 +1,40 @@
+.update-form {
+ text-align: center;
+}
+
+.update-form > .apply {
+ background-color: transparent;
+ color: #b6b6b6;
+ cursor: pointer;
+ border: 1px solid #b6b6b6;
+ box-sizing: border-box;
+ padding: 1rem 2rem;
+}
+
+.update-form > .apply:hover {
+ color: #fafafa;
+ border-color: #fafafa;
+}
+
+.update-form > .current {
+ margin-top: 1rem;
+}
+
+.update-form > .links {
+ margin-top: 1rem;
+}
+
+.update-form > .links > .link {
+ color: #b6b6b6;
+ text-decoration: none;
+}
+
+.update-form > .links > .link:hover {
+ color: #fcfcfc;
+ text-decoration: underline;
+}
+
+.update-form > .error {
+ color: red;
+ margin-top: 1rem;
+}
diff --git a/src/browser/pages/update.html b/src/browser/pages/update.html
new file mode 100644
index 00000000..7ddefa33
--- /dev/null
+++ b/src/browser/pages/update.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
code-server
+
+
+
+
+
+
+
+
+
diff --git a/src/node/app/app.ts b/src/node/app/app.ts
index ff17b252..62672e45 100644
--- a/src/node/app/app.ts
+++ b/src/node/app/app.ts
@@ -6,12 +6,17 @@ import { HttpCode, HttpError } from "../../common/http"
import { Options } from "../../common/util"
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
import { ApiHttpProvider } from "./api"
+import { UpdateHttpProvider } from "./update"
/**
* Top-level and fallback HTTP provider.
*/
export class MainHttpProvider extends HttpProvider {
- public constructor(options: HttpProviderOptions, private readonly api: ApiHttpProvider) {
+ public constructor(
+ options: HttpProviderOptions,
+ private readonly api: ApiHttpProvider,
+ private readonly update: UpdateHttpProvider
+ ) {
super(options)
}
@@ -77,13 +82,14 @@ export class MainHttpProvider extends HttpProvider {
response.content = response.content
.replace(/{{COMMIT}}/g, this.options.commit)
.replace(/{{BASE}}/g, this.base(route))
- .replace(/{{APP_LIST:RUNNING}}/g, this.getAppRows(recent.running))
+ .replace(/{{UPDATE:NAME}}/, await this.getUpdate())
+ .replace(/{{APP_LIST:RUNNING}}/, this.getAppRows(recent.running))
.replace(
- /{{APP_LIST:EDITORS}}/g,
+ /{{APP_LIST:EDITORS}}/,
this.getAppRows(apps.filter((app) => app.categories && app.categories.includes("Editor")))
)
.replace(
- /{{APP_LIST:OTHER}}/g,
+ /{{APP_LIST:OTHER}}/,
this.getAppRows(apps.filter((app) => !app.categories || !app.categories.includes("Editor")))
)
return response
@@ -94,8 +100,8 @@ export class MainHttpProvider extends HttpProvider {
response.content = response.content
.replace(/{{COMMIT}}/g, this.options.commit)
.replace(/{{BASE}}/g, this.base(route))
- .replace(/{{APP_NAME}}/g, name)
- .replace(/"{{OPTIONS}}"/g, `'${JSON.stringify(options)}'`)
+ .replace(/{{APP_NAME}}/, name)
+ .replace(/"{{OPTIONS}}"/, `'${JSON.stringify(options)}'`)
return response
}
@@ -108,8 +114,8 @@ export class MainHttpProvider extends HttpProvider {
}
private getAppRow(app: Application): string {
- return `
-
+ return ``
}
+
+ private async getUpdate(): Promise {
+ if (!this.update.enabled) {
+ return "Updates are disabled"
+ }
+
+ const update = await this.update.getUpdate()
+ if (!update) {
+ return `
+ No updates available
+ Current: ${this.update.currentVersion}
+
`
+ }
+
+ return ``
+ }
}
diff --git a/src/node/app/login.ts b/src/node/app/login.ts
index 207d7084..1b79b4b7 100644
--- a/src/node/app/login.ts
+++ b/src/node/app/login.ts
@@ -50,8 +50,8 @@ export class LoginHttpProvider extends HttpProvider {
response.content = response.content
.replace(/{{COMMIT}}/g, this.options.commit)
.replace(/{{BASE}}/g, this.base(route))
- .replace(/{{VALUE}}/g, value || "")
- .replace(/{{ERROR}}/g, error ? `${error.message}
` : "")
+ .replace(/{{VALUE}}/, value || "")
+ .replace(/{{ERROR}}/, error ? `${error.message}
` : "")
return response
}
diff --git a/src/node/app/update.ts b/src/node/app/update.ts
new file mode 100644
index 00000000..34df2cfc
--- /dev/null
+++ b/src/node/app/update.ts
@@ -0,0 +1,302 @@
+import { field, logger } from "@coder/logger"
+import * as cp from "child_process"
+import * as fs from "fs-extra"
+import * as http from "http"
+import * as https from "https"
+import * as os from "os"
+import * as path from "path"
+import * as semver from "semver"
+import { Readable, Writable } from "stream"
+import * as tar from "tar-fs"
+import * as url from "url"
+import * as util from "util"
+import zip from "adm-zip"
+import * as zlib from "zlib"
+import { HttpCode, HttpError } from "../../common/http"
+import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
+import { tmpdir } from "../util"
+import { ipcMain } from "../wrapper"
+
+export interface Update {
+ version: string
+}
+
+/**
+ * Update HTTP provider.
+ */
+export class UpdateHttpProvider extends HttpProvider {
+ private update?: Promise
+
+ public constructor(options: HttpProviderOptions, public readonly enabled: boolean) {
+ super(options)
+ }
+
+ public async handleRequest(route: Route, request: http.IncomingMessage): Promise {
+ switch (route.base) {
+ case "/": {
+ this.ensureMethod(request, ["GET", "POST"])
+ if (route.requestPath !== "/index.html") {
+ throw new HttpError("Not found", HttpCode.NotFound)
+ } else if (!this.authenticated(request)) {
+ return { redirect: "/login" }
+ }
+
+ switch (request.method) {
+ case "GET":
+ return this.getRoot(route)
+ case "POST":
+ return this.tryUpdate(route)
+ }
+ }
+ }
+
+ return undefined
+ }
+
+ public async getRoot(route: Route, error?: Error): Promise {
+ const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/update.html")
+ response.content = response.content
+ .replace(/{{COMMIT}}/g, this.options.commit)
+ .replace(/{{BASE}}/g, this.base(route))
+ .replace(/{{UPDATE_STATUS}}/, await this.getUpdateHtml())
+ .replace(/{{ERROR}}/, error ? `${error.message}
` : "")
+ return response
+ }
+
+ public async handleWebSocket(): Promise {
+ return undefined
+ }
+
+ /**
+ * Query for and return the latest update.
+ */
+ public async getUpdate(): Promise {
+ if (!this.enabled) {
+ throw new Error("updates are not enabled")
+ }
+
+ if (!this.update) {
+ this.update = this._getUpdate()
+ }
+
+ return this.update
+ }
+
+ private async _getUpdate(): Promise {
+ const url = "https://api.github.com/repos/cdr/code-server/releases/latest"
+ try {
+ const buffer = await this.request(url)
+ const data = JSON.parse(buffer.toString())
+ const latest = { version: data.name }
+ logger.debug("Got latest version", field("latest", latest.version))
+ return this.isLatestVersion(latest) ? undefined : latest
+ } catch (error) {
+ logger.error("Failed to get latest version", field("error", error.message))
+ return undefined
+ }
+ }
+
+ public get currentVersion(): string {
+ return require(path.resolve(__dirname, "../../../package.json")).version
+ }
+
+ /**
+ * Return true if the currently installed version is the latest.
+ */
+ private isLatestVersion(latest: Update): boolean {
+ const version = this.currentVersion
+ logger.debug("Comparing versions", field("current", version), field("latest", latest.version))
+ return latest.version === version || semver.lt(latest.version, version)
+ }
+
+ private async getUpdateHtml(): Promise {
+ if (!this.enabled) {
+ return "Updates are disabled"
+ }
+
+ const update = await this.getUpdate()
+ if (!update) {
+ return "No updates available"
+ }
+
+ return `
+ Current: ${this.currentVersion}
`
+ }
+
+ public async tryUpdate(route: Route): Promise {
+ try {
+ const update = await this.getUpdate()
+ if (!update) {
+ throw new Error("no update available")
+ }
+ await this.downloadUpdate(update)
+ return {
+ redirect: (Array.isArray(route.query.to) ? route.query.to[0] : route.query.to) || "/",
+ }
+ } catch (error) {
+ return this.getRoot(route, error)
+ }
+ }
+
+ private async downloadUpdate(update: Update): Promise {
+ const releaseName = await this.getReleaseName(update)
+ const url = `https://github.com/cdr/code-server/releases/download/${update.version.replace}/${releaseName}`
+
+ await fs.mkdirp(tmpdir)
+
+ const response = await this.requestResponse(url)
+
+ try {
+ let downloadPath = path.join(tmpdir, releaseName)
+ if (downloadPath.endsWith(".tar.gz")) {
+ downloadPath = await this.extractTar(response, downloadPath)
+ } else {
+ downloadPath = await this.extractZip(response, downloadPath)
+ }
+ logger.debug("Downloaded update", field("path", downloadPath))
+
+ const target = path.resolve(__dirname, "../")
+ logger.debug("Replacing files", field("target", target))
+ await fs.unlink(target)
+ await fs.move(downloadPath, target)
+
+ ipcMain().relaunch(update.version)
+ } catch (error) {
+ response.destroy(error)
+ throw error
+ }
+ }
+
+ private async extractTar(response: Readable, downloadPath: string): Promise {
+ downloadPath = downloadPath.replace(/\.tar\.gz$/, "")
+ logger.debug("Extracting tar", field("path", downloadPath))
+
+ response.pause()
+ await fs.remove(downloadPath)
+
+ const decompress = zlib.createGunzip()
+ response.pipe(decompress as Writable)
+ response.on("error", (error) => decompress.destroy(error))
+ response.on("close", () => decompress.end())
+
+ const destination = tar.extract(downloadPath)
+ decompress.pipe(destination)
+ decompress.on("error", (error) => destination.destroy(error))
+ decompress.on("close", () => destination.end())
+
+ await new Promise((resolve, reject) => {
+ destination.on("finish", resolve)
+ destination.on("error", reject)
+ response.resume()
+ })
+
+ return downloadPath
+ }
+
+ private async extractZip(response: Readable, downloadPath: string): Promise {
+ logger.debug("Downloading zip", field("path", downloadPath))
+
+ response.pause()
+ await fs.remove(downloadPath)
+
+ const write = fs.createWriteStream(downloadPath)
+ response.pipe(write)
+ response.on("error", (error) => write.destroy(error))
+ response.on("close", () => write.end())
+
+ await new Promise((resolve, reject) => {
+ write.on("error", reject)
+ write.on("close", resolve)
+ response.resume
+ })
+
+ const zipPath = downloadPath
+ downloadPath = downloadPath.replace(/\.zip$/, "")
+ await fs.remove(downloadPath)
+
+ logger.debug("Extracting zip", field("path", zipPath))
+
+ await new Promise((resolve, reject) => {
+ new zip(zipPath).extractAllToAsync(downloadPath, true, (error) => {
+ return error ? reject(error) : resolve()
+ })
+ })
+
+ await fs.remove(zipPath)
+
+ return downloadPath
+ }
+
+ /**
+ * Given an update return the name for the packaged archived.
+ */
+ private async getReleaseName(update: Update): Promise {
+ let target: string = os.platform()
+ if (target === "linux") {
+ const result = await util
+ .promisify(cp.exec)("ldd --version")
+ .catch((error) => ({
+ stderr: error.message,
+ stdout: "",
+ }))
+ if (/musl/.test(result.stderr) || /musl/.test(result.stdout)) {
+ target = "alpine"
+ }
+ }
+ let arch = os.arch()
+ if (arch === "x64") {
+ arch = "x86_64"
+ }
+ return `code-server-${update.version}-${target}-${arch}.${target === "darwin" ? "zip" : "tar.gz"}`
+ }
+
+ private async request(uri: string): Promise {
+ const response = await this.requestResponse(uri)
+ return new Promise((resolve, reject) => {
+ const chunks: Buffer[] = []
+ let bufferLength = 0
+ response.on("data", (chunk) => {
+ bufferLength += chunk.length
+ chunks.push(chunk)
+ })
+ response.on("error", reject)
+ response.on("end", () => {
+ resolve(Buffer.concat(chunks, bufferLength))
+ })
+ })
+ }
+
+ private async requestResponse(uri: string): Promise {
+ let redirects = 0
+ const maxRedirects = 10
+ return new Promise((resolve, reject) => {
+ const request = (uri: string): void => {
+ logger.debug("Making request", field("uri", uri))
+ https.get(uri, { headers: { "User-Agent": "code-server" } }, (response) => {
+ if (
+ response.statusCode &&
+ response.statusCode >= 300 &&
+ response.statusCode < 400 &&
+ response.headers.location
+ ) {
+ ++redirects
+ if (redirects > maxRedirects) {
+ return reject(new Error("reached max redirects"))
+ }
+ response.destroy()
+ return request(url.resolve(uri, response.headers.location))
+ }
+
+ if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 400) {
+ return reject(new Error(`${response.statusCode || "500"}`))
+ }
+
+ resolve(response)
+ })
+ }
+ request(uri)
+ })
+ }
+}
diff --git a/src/node/cli.ts b/src/node/cli.ts
index 8026209f..34409ac4 100644
--- a/src/node/cli.ts
+++ b/src/node/cli.ts
@@ -14,6 +14,7 @@ export interface Args extends VsArgs {
readonly auth?: AuthType
readonly cert?: OptionalString
readonly "cert-key"?: string
+ readonly "disable-updates"?: boolean
readonly help?: boolean
readonly host?: string
readonly json?: boolean
@@ -66,6 +67,7 @@ const options: Options> = {
description: "Path to certificate. Generated if no path is provided.",
},
"cert-key": { type: "string", path: true, description: "Path to certificate key when using non-generated cert." },
+ "disable-updates": { type: "boolean", description: "Disable automatic updates." },
host: { type: "string", description: "Host for the HTTP server." },
help: { type: "boolean", short: "h", description: "Show this output." },
json: { type: "boolean" },
diff --git a/src/node/entry.ts b/src/node/entry.ts
index d0b7c87d..b9a03a93 100644
--- a/src/node/entry.ts
+++ b/src/node/entry.ts
@@ -3,6 +3,7 @@ import { Args, optionDescriptions, parse } from "./cli"
import { ApiHttpProvider } from "./app/api"
import { MainHttpProvider } from "./app/app"
import { LoginHttpProvider } from "./app/login"
+import { UpdateHttpProvider } from "./app/update"
import { VscodeHttpProvider } from "./app/vscode"
import { AuthType, HttpServer } from "./http"
import { generateCertificate, generatePassword, hash, open } from "./util"
@@ -41,9 +42,10 @@ const main = async (args: Args): Promise => {
const httpServer = new HttpServer(options)
const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer)
+ const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, !args["disable-updates"])
httpServer.registerHttpProvider("/vscode", VscodeHttpProvider, args)
httpServer.registerHttpProvider("/login", LoginHttpProvider)
- httpServer.registerHttpProvider("/", MainHttpProvider, api)
+ httpServer.registerHttpProvider("/", MainHttpProvider, api, update)
ipcMain().onDispose(() => httpServer.dispose())
@@ -72,6 +74,8 @@ const main = async (args: Args): Promise => {
logger.info(" - Not serving HTTPS")
}
+ logger.info(` - Automatic updates are ${update.enabled ? "enabled" : "disabled"}`)
+
if (serverAddress && !options.socket && args.open) {
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
diff --git a/src/node/http.ts b/src/node/http.ts
index 9d2783b6..2c51567c 100644
--- a/src/node/http.ts
+++ b/src/node/http.ts
@@ -355,6 +355,10 @@ export interface HttpProvider1 {
new (options: HttpProviderOptions, a1: A1): T
}
+export interface HttpProvider2 {
+ new (options: HttpProviderOptions, a1: A1, a2: A2): T
+}
+
/**
* An HTTP server. Its main role is to route incoming HTTP requests to the
* appropriate provider for that endpoint then write out the response. It also
@@ -404,8 +408,14 @@ export class HttpServer {
*/
public registerHttpProvider(endpoint: string, provider: HttpProvider0): T
public registerHttpProvider(endpoint: string, provider: HttpProvider1, a1: A1): T
+ public registerHttpProvider(
+ endpoint: string,
+ provider: HttpProvider2,
+ a1: A1,
+ a2: A2
+ ): T
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- public registerHttpProvider(endpoint: string, provider: any, a1?: any): any {
+ public registerHttpProvider(endpoint: string, provider: any, ...args: any[]): any {
endpoint = endpoint.replace(/^\/+|\/+$/g, "")
if (this.providers.has(`/${endpoint}`)) {
throw new Error(`${endpoint} is already registered`)
@@ -420,7 +430,7 @@ export class HttpServer {
commit: this.options.commit,
password: this.options.password,
},
- a1
+ ...args
)
this.providers.set(`/${endpoint}`, p)
return p
diff --git a/yarn.lock b/yarn.lock
index 802a4f1d..e426b347 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -856,6 +856,13 @@
"@parcel/utils" "^1.11.0"
physical-cpu-count "^2.0.0"
+"@types/adm-zip@^0.4.32":
+ version "0.4.32"
+ resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.4.32.tgz#6de01309af60677065d2e52b417a023303220931"
+ integrity sha512-hv1O7ySn+XvP5OeDQcJFWwVb2v+GFGO1A9aMTQ5B/bzxb7WW21O8iRhVdsKKr8QwuiagzGmPP+gsUAYZ6bRddQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -940,7 +947,14 @@
resolved "https://registry.yarnpkg.com/@types/safe-compare/-/safe-compare-1.1.0.tgz#47ed9b9ca51a3a791b431cd59b28f47fa9bf1224"
integrity sha512-1ri+LJhh0gRxIa37IpGytdaW7yDEHeJniBSMD1BmitS07R1j63brcYCzry+l0WJvGdEKQNQ7DYXO2epgborWPw==
-"@types/tar-fs@^1.16.1":
+"@types/semver@^7.1.0":
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.1.0.tgz#c8c630d4c18cd326beff77404887596f96408408"
+ integrity sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA==
+ dependencies:
+ "@types/node" "*"
+
+"@types/tar-fs@^1.16.2":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-1.16.2.tgz#6f5acea15d3b7777b8bf3f1c6d4e80ce71288f34"
integrity sha512-eds/pbRf0Fe0EKmrHDbs8mRkfbjz2upAdoUfREw14dPboZaHqqZ1Y+uVeoakoPavpZMpj22nhUTAYkX5bz3DXA==
@@ -948,7 +962,7 @@
"@types/node" "*"
"@types/tar-stream" "*"
-"@types/tar-stream@*", "@types/tar-stream@^1.6.1":
+"@types/tar-stream@*":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-1.6.1.tgz#67d759068ff781d976cad978893bb7a334ec8809"
integrity sha512-pYCDOPuRE+4tXFk1rSMYiuI+kSrXiJ4av1bboQbkcEBA2rqwEWfIn9kdMSH+5nYu58WksHuxwx+7kVbtg0Le7w==
@@ -1086,6 +1100,11 @@ acorn@^7.0.0, acorn@^7.1.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
+adm-zip@^0.4.14:
+ version "0.4.14"
+ resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.14.tgz#2cf312bcc9f8875df835b0f6040bd89be0a727a9"
+ integrity sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==
+
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
version "6.11.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
@@ -1834,6 +1853,11 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
+chownr@^1.1.3:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+ integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -3206,6 +3230,13 @@ fs-extra@^8.1.0:
jsonfile "^4.0.0"
universalify "^0.1.0"
+fs-minipass@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+ integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+ dependencies:
+ minipass "^3.0.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -4567,6 +4598,21 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+minipass@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
+ integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==
+ dependencies:
+ yallist "^4.0.0"
+
+minizlib@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
+ integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
+ dependencies:
+ minipass "^3.0.0"
+ yallist "^4.0.0"
+
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -4582,6 +4628,11 @@ mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies:
minimist "0.0.8"
+mkdirp@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea"
+ integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==
+
mocha@^6.2.0:
version "6.2.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.2.tgz#5d8987e28940caf8957a7d7664b910dc5b2fea20"
@@ -6332,6 +6383,11 @@ semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6"
+ integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==
+
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -6995,7 +7051,7 @@ tar-fs@^2.0.0:
pump "^3.0.0"
tar-stream "^2.0.0"
-tar-stream@^2.0.0, tar-stream@^2.1.0:
+tar-stream@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
@@ -7006,6 +7062,18 @@ tar-stream@^2.0.0, tar-stream@^2.1.0:
inherits "^2.0.3"
readable-stream "^3.1.1"
+tar@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa"
+ integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==
+ dependencies:
+ chownr "^1.1.3"
+ fs-minipass "^2.0.0"
+ minipass "^3.0.0"
+ minizlib "^2.1.0"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
terser@^3.7.3:
version "3.17.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
@@ -7698,6 +7766,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
yaml@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"