Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
彭彦祖
code-server
提交
319cd3f7
C
code-server
项目概览
彭彦祖
/
code-server
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
C
code-server
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
319cd3f7
编写于
2月 20, 2020
作者:
A
Asher
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Make updating work for both binary and loose releases
上级
815dc061
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
354 addition
and
26 deletion
+354
-26
src/node/app/update.ts
src/node/app/update.ts
+85
-21
src/node/http.ts
src/node/http.ts
+1
-0
src/node/settings.ts
src/node/settings.ts
+8
-5
test/tsconfig.json
test/tsconfig.json
+4
-0
test/update.test.ts
test/update.test.ts
+256
-0
未找到文件。
src/node/app/update.ts
浏览文件 @
319cd3f7
...
...
@@ -14,7 +14,7 @@ import * as util from "util"
import
*
as
zlib
from
"
zlib
"
import
{
HttpCode
,
HttpError
}
from
"
../../common/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
{
ipcMain
}
from
"
../wrapper
"
...
...
@@ -23,6 +23,10 @@ export interface Update {
version
:
string
}
export
interface
LatestResponse
{
name
:
string
}
/**
* Update HTTP provider.
*/
...
...
@@ -30,7 +34,26 @@ export class UpdateHttpProvider extends HttpProvider {
private
update
?:
Promise
<
Update
>
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
)
}
...
...
@@ -82,6 +105,7 @@ export class UpdateHttpProvider extends HttpProvider {
throw
new
Error
(
"
updates are not enabled
"
)
}
// Don't run multiple requests at a time.
if
(
!
this
.
update
)
{
this
.
update
=
this
.
_getUpdate
(
force
)
this
.
update
.
then
(()
=>
(
this
.
update
=
undefined
))
...
...
@@ -91,15 +115,14 @@ export class UpdateHttpProvider extends HttpProvider {
}
private
async
_getUpdate
(
force
?:
boolean
):
Promise
<
Update
>
{
const
url
=
"
https://api.github.com/repos/cdr/code-server/releases/latest
"
const
now
=
Date
.
now
()
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
)
{
const
buffer
=
await
this
.
request
(
u
rl
)
const
data
=
JSON
.
parse
(
buffer
.
toString
())
update
=
{
checked
:
now
,
version
:
data
.
name
as
string
}
settings
.
write
({
update
})
const
buffer
=
await
this
.
request
(
this
.
latestU
rl
)
const
data
=
JSON
.
parse
(
buffer
.
toString
())
as
LatestResponse
update
=
{
checked
:
now
,
version
:
data
.
name
}
await
this
.
settings
.
write
({
update
})
}
logger
.
debug
(
"
Got latest version
"
,
field
(
"
latest
"
,
update
.
version
))
return
update
...
...
@@ -160,16 +183,16 @@ export class UpdateHttpProvider extends HttpProvider {
}
}
p
rivate
async
downloadUpdate
(
update
:
Update
):
Promise
<
void
>
{
const
releaseName
=
await
this
.
getReleaseName
(
update
)
const
url
=
`https://github.com/cdr/code-server/releases/download/
${
update
.
version
.
replace
}
/
${
releaseName
}
`
p
ublic
async
downloadUpdate
(
update
:
Update
,
targetPath
?:
string
,
target
?:
string
):
Promise
<
void
>
{
const
releaseName
=
await
this
.
getReleaseName
(
update
,
target
)
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
)
try
{
let
downloadPath
=
path
.
join
(
tmpdir
,
releaseName
)
if
(
downloadPath
.
endsWith
(
"
.tar.gz
"
))
{
downloadPath
=
await
this
.
extractTar
(
response
,
downloadPath
)
}
else
{
...
...
@@ -177,12 +200,53 @@ export class UpdateHttpProvider extends HttpProvider {
}
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
)
// The archive should have a code-server directory at the top level.
try
{
const
stat
=
await
fs
.
stat
(
path
.
join
(
downloadPath
,
"
code-server
"
))
if
(
!
stat
.
isDirectory
())
{
throw
new
Error
(
"
ENOENT
"
)
}
}
catch
(
error
)
{
throw
new
Error
(
"
no code-server directory found in downloaded archive
"
)
}
// 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
})
}
ipcMain
().
relaunch
(
update
.
version
)
await
fs
.
remove
(
downloadPath
)
if
(
process
.
send
)
{
ipcMain
().
relaunch
(
update
.
version
)
}
}
catch
(
error
)
{
response
.
destroy
(
error
)
throw
error
...
...
@@ -252,8 +316,7 @@ export class UpdateHttpProvider extends HttpProvider {
/**
* Given an update return the name for the packaged archived.
*/
private
async
getReleaseName
(
update
:
Update
):
Promise
<
string
>
{
let
target
:
string
=
os
.
platform
()
private
async
getReleaseName
(
update
:
Update
,
target
:
string
=
os
.
platform
()):
Promise
<
string
>
{
if
(
target
===
"
linux
"
)
{
const
result
=
await
util
.
promisify
(
cp
.
exec
)(
"
ldd --version
"
)
...
...
@@ -294,7 +357,8 @@ export class UpdateHttpProvider extends HttpProvider {
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
)
=>
{
const
httpx
=
uri
.
startsWith
(
"
https
"
)
?
https
:
http
httpx
.
get
(
uri
,
{
headers
:
{
"
User-Agent
"
:
"
code-server
"
}
},
(
response
)
=>
{
if
(
response
.
statusCode
&&
response
.
statusCode
>=
300
&&
...
...
src/node/http.ts
浏览文件 @
319cd3f7
...
...
@@ -500,6 +500,7 @@ export class HttpServer {
response
.
writeHead
(
error
.
code
===
"
ENOENT
"
?
HttpCode
.
NotFound
:
HttpCode
.
ServerError
)
response
.
end
(
error
.
message
)
})
payload
.
stream
.
on
(
"
close
"
,
()
=>
response
.
end
())
payload
.
stream
.
pipe
(
response
)
}
else
if
(
typeof
payload
.
content
===
"
string
"
||
payload
.
content
instanceof
Buffer
)
{
response
.
end
(
payload
.
content
)
...
...
src/node/settings.ts
浏览文件 @
319cd3f7
...
...
@@ -40,18 +40,21 @@ export class SettingsProvider<T> {
}
}
export
interface
UpdateSettings
{
update
:
{
checked
:
number
version
:
string
}
}
/**
* Global code-server settings.
*/
export
interface
CoderSettings
{
export
interface
CoderSettings
extends
UpdateSettings
{
lastVisited
:
{
url
:
string
workspace
:
boolean
}
update
:
{
checked
:
number
version
:
string
}
}
/**
...
...
test/tsconfig.json
0 → 100644
浏览文件 @
319cd3f7
{
"extends"
:
"../tsconfig.json"
,
"include"
:
[
"./**/*.ts"
]
}
test/update.test.ts
0 → 100644
浏览文件 @
319cd3f7
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
}
`
,
])
})
})
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录