mirror of https://github.com/coder/code-server.git
Make updating work for both binary and loose releases
This commit is contained in:
parent
815dc06118
commit
319cd3f7ab
|
@ -14,7 +14,7 @@ import * as util from "util"
|
||||||
import * as zlib from "zlib"
|
import * as zlib from "zlib"
|
||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
|
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
|
||||||
import { settings } from "../settings"
|
import { settings as globalSettings, SettingsProvider, UpdateSettings } from "../settings"
|
||||||
import { tmpdir } from "../util"
|
import { tmpdir } from "../util"
|
||||||
import { ipcMain } from "../wrapper"
|
import { ipcMain } from "../wrapper"
|
||||||
|
|
||||||
|
@ -23,6 +23,10 @@ export interface Update {
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LatestResponse {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update HTTP provider.
|
* Update HTTP provider.
|
||||||
*/
|
*/
|
||||||
|
@ -30,7 +34,26 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
private update?: Promise<Update>
|
private update?: Promise<Update>
|
||||||
private updateInterval = 1000 * 60 * 60 * 24 // Milliseconds between update checks.
|
private updateInterval = 1000 * 60 * 60 * 24 // Milliseconds between update checks.
|
||||||
|
|
||||||
public constructor(options: HttpProviderOptions, public readonly enabled: boolean) {
|
public constructor(
|
||||||
|
options: HttpProviderOptions,
|
||||||
|
public readonly enabled: boolean,
|
||||||
|
/**
|
||||||
|
* The URL for getting the latest version of code-server. Should return JSON
|
||||||
|
* that fulfills `LatestResponse`.
|
||||||
|
*/
|
||||||
|
private readonly latestUrl = "https://api.github.com/repos/cdr/code-server/releases/latest",
|
||||||
|
/**
|
||||||
|
* The URL for downloading a version of code-server. {{VERSION}} and
|
||||||
|
* {{RELEASE_NAME}} will be replaced (for example 2.1.0 and
|
||||||
|
* code-server-2.1.0-linux-x86_64.tar.gz).
|
||||||
|
*/
|
||||||
|
private readonly downloadUrl = "https://github.com/cdr/code-server/releases/download/{{VERSION}}/{{RELEASE_NAME}}",
|
||||||
|
/**
|
||||||
|
* Update information will be stored here. If not provided, the global
|
||||||
|
* settings will be used.
|
||||||
|
*/
|
||||||
|
private readonly settings: SettingsProvider<UpdateSettings> = globalSettings,
|
||||||
|
) {
|
||||||
super(options)
|
super(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +105,7 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
throw new Error("updates are not enabled")
|
throw new Error("updates are not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't run multiple requests at a time.
|
||||||
if (!this.update) {
|
if (!this.update) {
|
||||||
this.update = this._getUpdate(force)
|
this.update = this._getUpdate(force)
|
||||||
this.update.then(() => (this.update = undefined))
|
this.update.then(() => (this.update = undefined))
|
||||||
|
@ -91,15 +115,14 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getUpdate(force?: boolean): Promise<Update> {
|
private async _getUpdate(force?: boolean): Promise<Update> {
|
||||||
const url = "https://api.github.com/repos/cdr/code-server/releases/latest"
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
try {
|
try {
|
||||||
let { update } = !force ? await settings.read() : { update: undefined }
|
let { update } = !force ? await this.settings.read() : { update: undefined }
|
||||||
if (!update || update.checked + this.updateInterval < now) {
|
if (!update || update.checked + this.updateInterval < now) {
|
||||||
const buffer = await this.request(url)
|
const buffer = await this.request(this.latestUrl)
|
||||||
const data = JSON.parse(buffer.toString())
|
const data = JSON.parse(buffer.toString()) as LatestResponse
|
||||||
update = { checked: now, version: data.name as string }
|
update = { checked: now, version: data.name }
|
||||||
settings.write({ update })
|
await this.settings.write({ update })
|
||||||
}
|
}
|
||||||
logger.debug("Got latest version", field("latest", update.version))
|
logger.debug("Got latest version", field("latest", update.version))
|
||||||
return update
|
return update
|
||||||
|
@ -160,16 +183,16 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async downloadUpdate(update: Update): Promise<void> {
|
public async downloadUpdate(update: Update, targetPath?: string, target?: string): Promise<void> {
|
||||||
const releaseName = await this.getReleaseName(update)
|
const releaseName = await this.getReleaseName(update, target)
|
||||||
const url = `https://github.com/cdr/code-server/releases/download/${update.version.replace}/${releaseName}`
|
const url = this.downloadUrl.replace("{{VERSION}}", update.version).replace("{{RELEASE_NAME}}", releaseName)
|
||||||
|
|
||||||
await fs.mkdirp(tmpdir)
|
let downloadPath = path.join(tmpdir, "updates", releaseName)
|
||||||
|
fs.mkdirp(path.dirname(downloadPath))
|
||||||
|
|
||||||
const response = await this.requestResponse(url)
|
const response = await this.requestResponse(url)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let downloadPath = path.join(tmpdir, releaseName)
|
|
||||||
if (downloadPath.endsWith(".tar.gz")) {
|
if (downloadPath.endsWith(".tar.gz")) {
|
||||||
downloadPath = await this.extractTar(response, downloadPath)
|
downloadPath = await this.extractTar(response, downloadPath)
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,12 +200,53 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
}
|
}
|
||||||
logger.debug("Downloaded update", field("path", downloadPath))
|
logger.debug("Downloaded update", field("path", downloadPath))
|
||||||
|
|
||||||
const target = path.resolve(__dirname, "../")
|
// The archive should have a code-server directory at the top level.
|
||||||
logger.debug("Replacing files", field("target", target))
|
try {
|
||||||
await fs.unlink(target)
|
const stat = await fs.stat(path.join(downloadPath, "code-server"))
|
||||||
await fs.move(downloadPath, target)
|
if (!stat.isDirectory()) {
|
||||||
|
throw new Error("ENOENT")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("no code-server directory found in downloaded archive")
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain().relaunch(update.version)
|
// The archive might contain a binary or it might contain loose files.
|
||||||
|
// This is probably stupid but just check if `node` exists since we
|
||||||
|
// package it with the loose files.
|
||||||
|
const isBinary = !(await fs.pathExists(path.join(downloadPath, "code-server/node")))
|
||||||
|
|
||||||
|
// In the binary we need to replace the binary, otherwise we can replace
|
||||||
|
// the directory.
|
||||||
|
if (!targetPath) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
targetPath = (process.versions as any).nbin ? process.argv[0] : path.resolve(__dirname, "../../../")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're currently running a binary it must be unlinked to avoid
|
||||||
|
// ETXTBSY.
|
||||||
|
try {
|
||||||
|
const stat = await fs.stat(targetPath)
|
||||||
|
if (stat.isFile()) {
|
||||||
|
await fs.unlink(targetPath)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "ENOENT") {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Replacing files", field("target", targetPath), field("isBinary", isBinary))
|
||||||
|
if (isBinary) {
|
||||||
|
await fs.move(path.join(downloadPath, "code-server/code-server"), targetPath, { overwrite: true })
|
||||||
|
} else {
|
||||||
|
await fs.move(path.join(downloadPath, "code-server"), targetPath, { overwrite: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.remove(downloadPath)
|
||||||
|
|
||||||
|
if (process.send) {
|
||||||
|
ipcMain().relaunch(update.version)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
response.destroy(error)
|
response.destroy(error)
|
||||||
throw error
|
throw error
|
||||||
|
@ -252,8 +316,7 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
/**
|
/**
|
||||||
* Given an update return the name for the packaged archived.
|
* Given an update return the name for the packaged archived.
|
||||||
*/
|
*/
|
||||||
private async getReleaseName(update: Update): Promise<string> {
|
private async getReleaseName(update: Update, target: string = os.platform()): Promise<string> {
|
||||||
let target: string = os.platform()
|
|
||||||
if (target === "linux") {
|
if (target === "linux") {
|
||||||
const result = await util
|
const result = await util
|
||||||
.promisify(cp.exec)("ldd --version")
|
.promisify(cp.exec)("ldd --version")
|
||||||
|
@ -294,7 +357,8 @@ export class UpdateHttpProvider extends HttpProvider {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = (uri: string): void => {
|
const request = (uri: string): void => {
|
||||||
logger.debug("Making request", field("uri", uri))
|
logger.debug("Making request", field("uri", uri))
|
||||||
https.get(uri, { headers: { "User-Agent": "code-server" } }, (response) => {
|
const httpx = uri.startsWith("https") ? https : http
|
||||||
|
httpx.get(uri, { headers: { "User-Agent": "code-server" } }, (response) => {
|
||||||
if (
|
if (
|
||||||
response.statusCode &&
|
response.statusCode &&
|
||||||
response.statusCode >= 300 &&
|
response.statusCode >= 300 &&
|
||||||
|
|
|
@ -500,6 +500,7 @@ export class HttpServer {
|
||||||
response.writeHead(error.code === "ENOENT" ? HttpCode.NotFound : HttpCode.ServerError)
|
response.writeHead(error.code === "ENOENT" ? HttpCode.NotFound : HttpCode.ServerError)
|
||||||
response.end(error.message)
|
response.end(error.message)
|
||||||
})
|
})
|
||||||
|
payload.stream.on("close", () => response.end())
|
||||||
payload.stream.pipe(response)
|
payload.stream.pipe(response)
|
||||||
} else if (typeof payload.content === "string" || payload.content instanceof Buffer) {
|
} else if (typeof payload.content === "string" || payload.content instanceof Buffer) {
|
||||||
response.end(payload.content)
|
response.end(payload.content)
|
||||||
|
|
|
@ -40,20 +40,23 @@ export class SettingsProvider<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface UpdateSettings {
|
||||||
* Global code-server settings.
|
|
||||||
*/
|
|
||||||
export interface CoderSettings {
|
|
||||||
lastVisited: {
|
|
||||||
url: string
|
|
||||||
workspace: boolean
|
|
||||||
}
|
|
||||||
update: {
|
update: {
|
||||||
checked: number
|
checked: number
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global code-server settings.
|
||||||
|
*/
|
||||||
|
export interface CoderSettings extends UpdateSettings {
|
||||||
|
lastVisited: {
|
||||||
|
url: string
|
||||||
|
workspace: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global code-server settings file.
|
* Global code-server settings file.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["./**/*.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
import zip from "adm-zip"
|
||||||
|
import * as assert from "assert"
|
||||||
|
import * as fs from "fs-extra"
|
||||||
|
import * as http from "http"
|
||||||
|
import * as os from "os"
|
||||||
|
import * as path from "path"
|
||||||
|
import * as tar from "tar-fs"
|
||||||
|
import * as zlib from "zlib"
|
||||||
|
import { LatestResponse, UpdateHttpProvider } from "../src/node/app/update"
|
||||||
|
import { AuthType } from "../src/node/http"
|
||||||
|
import { SettingsProvider, UpdateSettings } from "../src/node/settings"
|
||||||
|
import { tmpdir } from "../src/node/util"
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
const archivePaths = {
|
||||||
|
loose: path.join(tmpdir, "tests/updates/code-server-loose-source"),
|
||||||
|
binary: path.join(tmpdir, "tests/updates/code-server-binary-source"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let useBinary = false
|
||||||
|
let version = "1.0.0"
|
||||||
|
let spy: string[] = []
|
||||||
|
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
|
||||||
|
if (!request.url) {
|
||||||
|
throw new Error("no url")
|
||||||
|
}
|
||||||
|
spy.push(request.url)
|
||||||
|
response.writeHead(200)
|
||||||
|
if (request.url === "/latest") {
|
||||||
|
const latest: LatestResponse = {
|
||||||
|
name: version,
|
||||||
|
}
|
||||||
|
return response.end(JSON.stringify(latest))
|
||||||
|
}
|
||||||
|
|
||||||
|
const path =
|
||||||
|
(useBinary ? archivePaths.binary : archivePaths.loose) + (request.url.endsWith(".tar.gz") ? ".tar.gz" : ".zip")
|
||||||
|
|
||||||
|
const stream = fs.createReadStream(path)
|
||||||
|
stream.on("error", (error: NodeJS.ErrnoException) => {
|
||||||
|
response.writeHead(500)
|
||||||
|
response.end(error.message)
|
||||||
|
})
|
||||||
|
response.writeHead(200)
|
||||||
|
stream.on("close", () => response.end())
|
||||||
|
stream.pipe(response)
|
||||||
|
})
|
||||||
|
|
||||||
|
const jsonPath = path.join(tmpdir, "tests/updates/update.json")
|
||||||
|
const settings = new SettingsProvider<UpdateSettings>(jsonPath)
|
||||||
|
|
||||||
|
let _provider: UpdateHttpProvider | undefined
|
||||||
|
const provider = (): UpdateHttpProvider => {
|
||||||
|
if (!_provider) {
|
||||||
|
const address = server.address()
|
||||||
|
if (!address || typeof address === "string" || !address.port) {
|
||||||
|
throw new Error("unexpected address")
|
||||||
|
}
|
||||||
|
_provider = new UpdateHttpProvider(
|
||||||
|
{
|
||||||
|
auth: AuthType.None,
|
||||||
|
base: "/update",
|
||||||
|
commit: "test",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
`http://${address.address}:${address.port}/latest`,
|
||||||
|
`http://${address.address}:${address.port}/download/{{VERSION}}/{{RELEASE_NAME}}`,
|
||||||
|
settings,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return _provider
|
||||||
|
}
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await fs.remove(path.join(tmpdir, "tests/updates"))
|
||||||
|
await Promise.all(Object.values(archivePaths).map((p) => fs.mkdirp(path.join(p, "code-server"))))
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(path.join(archivePaths.binary, "code-server", "code-server"), "BINARY"),
|
||||||
|
fs.writeFile(path.join(archivePaths.loose, "code-server", "code-server"), `console.log("UPDATED")`),
|
||||||
|
fs.writeFile(path.join(archivePaths.loose, "code-server", "node"), `NODE BINARY`),
|
||||||
|
])
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.values(archivePaths).map((p) => {
|
||||||
|
return Promise.all([
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const write = fs.createWriteStream(p + ".tar.gz")
|
||||||
|
const compress = zlib.createGzip()
|
||||||
|
compress.pipe(write)
|
||||||
|
compress.on("error", (error) => compress.destroy(error))
|
||||||
|
compress.on("close", () => write.end())
|
||||||
|
tar.pack(p).pipe(compress)
|
||||||
|
write.on("close", reject)
|
||||||
|
write.on("finish", () => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const zipFile = new zip()
|
||||||
|
zipFile.addLocalFolder(p)
|
||||||
|
zipFile.writeZip(p + ".zip", (error) => {
|
||||||
|
return error ? reject(error) : resolve(error)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
server.on("error", reject)
|
||||||
|
server.on("listening", resolve)
|
||||||
|
server.listen({
|
||||||
|
port: 0,
|
||||||
|
host: "localhost",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spy = []
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get the latest", async () => {
|
||||||
|
version = "2.1.0"
|
||||||
|
|
||||||
|
const p = provider()
|
||||||
|
const now = Date.now()
|
||||||
|
const update = await p.getUpdate()
|
||||||
|
|
||||||
|
assert.deepEqual({ update }, await settings.read())
|
||||||
|
assert.equal(isNaN(update.checked), false)
|
||||||
|
assert.equal(update.checked < Date.now() && update.checked >= now, true)
|
||||||
|
assert.equal(update.version, "2.1.0")
|
||||||
|
assert.deepEqual(spy, ["/latest"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should keep existing information", async () => {
|
||||||
|
version = "3.0.1"
|
||||||
|
|
||||||
|
const p = provider()
|
||||||
|
const now = Date.now()
|
||||||
|
const update = await p.getUpdate()
|
||||||
|
|
||||||
|
assert.deepEqual({ update }, await settings.read())
|
||||||
|
assert.equal(isNaN(update.checked), false)
|
||||||
|
assert.equal(update.checked < now, true)
|
||||||
|
assert.equal(update.version, "2.1.0")
|
||||||
|
assert.deepEqual(spy, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should force getting the latest", async () => {
|
||||||
|
version = "4.1.1"
|
||||||
|
|
||||||
|
const p = provider()
|
||||||
|
const now = Date.now()
|
||||||
|
const update = await p.getUpdate(true)
|
||||||
|
|
||||||
|
assert.deepEqual({ update }, await settings.read())
|
||||||
|
assert.equal(isNaN(update.checked), false)
|
||||||
|
assert.equal(update.checked < Date.now() && update.checked >= now, true)
|
||||||
|
assert.equal(update.version, "4.1.1")
|
||||||
|
assert.deepEqual(spy, ["/latest"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should get latest after interval passes", async () => {
|
||||||
|
const p = provider()
|
||||||
|
await p.getUpdate()
|
||||||
|
assert.deepEqual(spy, [])
|
||||||
|
|
||||||
|
let checked = Date.now() - 1000 * 60 * 60 * 23
|
||||||
|
await settings.write({ update: { checked, version } })
|
||||||
|
await p.getUpdate()
|
||||||
|
assert.deepEqual(spy, [])
|
||||||
|
|
||||||
|
checked = Date.now() - 1000 * 60 * 60 * 25
|
||||||
|
await settings.write({ update: { checked, version } })
|
||||||
|
|
||||||
|
const update = await p.getUpdate()
|
||||||
|
assert.notEqual(update.checked, checked)
|
||||||
|
assert.deepEqual(spy, ["/latest"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should check if it's the current version", async () => {
|
||||||
|
version = "9999999.99999.9999"
|
||||||
|
|
||||||
|
const p = provider()
|
||||||
|
let update = await p.getUpdate(true)
|
||||||
|
assert.equal(p.isLatestVersion(update), false)
|
||||||
|
|
||||||
|
version = "0.0.0"
|
||||||
|
update = await p.getUpdate(true)
|
||||||
|
assert.equal(p.isLatestVersion(update), true)
|
||||||
|
|
||||||
|
// Old version format; make sure it doesn't report as being later.
|
||||||
|
version = "999999.9999-invalid999.99.9"
|
||||||
|
update = await p.getUpdate(true)
|
||||||
|
assert.equal(p.isLatestVersion(update), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should download and apply an update", async () => {
|
||||||
|
version = "9999999.99999.9999"
|
||||||
|
|
||||||
|
const p = provider()
|
||||||
|
const update = await p.getUpdate(true)
|
||||||
|
|
||||||
|
// Create an existing version.
|
||||||
|
const destination = path.join(tmpdir, "tests/updates/code-server")
|
||||||
|
await fs.mkdirp(destination)
|
||||||
|
const entry = path.join(destination, "code-server")
|
||||||
|
await fs.writeFile(entry, `console.log("OLD")`)
|
||||||
|
assert.equal(`console.log("OLD")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
|
// Updating should replace the existing version.
|
||||||
|
await p.downloadUpdate(update, destination)
|
||||||
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
|
// Should still work if there is no existing version somehow.
|
||||||
|
await fs.remove(destination)
|
||||||
|
await p.downloadUpdate(update, destination)
|
||||||
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
|
// Try the other platform.
|
||||||
|
const altTarget = os.platform() === "darwin" ? "linux" : "darwin"
|
||||||
|
await fs.writeFile(entry, `console.log("OLD")`)
|
||||||
|
await p.downloadUpdate(update, destination, altTarget)
|
||||||
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
|
// Extracting a binary should also work.
|
||||||
|
useBinary = true
|
||||||
|
await p.downloadUpdate(update, destination)
|
||||||
|
assert.equal(`BINARY`, await fs.readFile(destination, "utf8"))
|
||||||
|
|
||||||
|
// Back to flat files.
|
||||||
|
useBinary = false
|
||||||
|
await fs.remove(destination)
|
||||||
|
await p.downloadUpdate(update, destination)
|
||||||
|
assert.equal(`console.log("UPDATED")`, await fs.readFile(entry, "utf8"))
|
||||||
|
|
||||||
|
const target = os.platform()
|
||||||
|
const targetExt = target === "darwin" ? "zip" : "tar.gz"
|
||||||
|
const altTargetExt = altTarget === "darwin" ? "zip" : "tar.gz"
|
||||||
|
assert.deepEqual(spy, [
|
||||||
|
"/latest",
|
||||||
|
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
|
||||||
|
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
|
||||||
|
`/download/${version}/code-server-${version}-${altTarget}-x86_64.${altTargetExt}`,
|
||||||
|
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
|
||||||
|
`/download/${version}/code-server-${version}-${target}-x86_64.${targetExt}`,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue