vscode-offline 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vscode_offline/__init__.py +3 -0
- vscode_offline/app.py +104 -0
- vscode_offline/download.py +106 -0
- vscode_offline/install.py +74 -0
- vscode_offline/loggers.py +5 -0
- vscode_offline/paths.py +25 -0
- vscode_offline-0.1.0.dist-info/METADATA +50 -0
- vscode_offline-0.1.0.dist-info/RECORD +10 -0
- vscode_offline-0.1.0.dist-info/WHEEL +4 -0
- vscode_offline-0.1.0.dist-info/entry_points.txt +3 -0
vscode_offline/app.py
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from argparse import ArgumentParser, Namespace
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from vscode_offline.download import download_vscode_extensions, download_vscode_server
|
8
|
+
from vscode_offline.install import install_vscode_extensions, install_vscode_server
|
9
|
+
from vscode_offline.paths import (
|
10
|
+
get_vscode_cli_bin,
|
11
|
+
get_vscode_extensions_config,
|
12
|
+
get_vscode_server_home,
|
13
|
+
)
|
14
|
+
|
15
|
+
cli_os_mapping = {
|
16
|
+
"linux-x64": "cli-alpine-x64",
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
def download(args: Namespace) -> None:
|
21
|
+
download_vscode_server(
|
22
|
+
args.commit,
|
23
|
+
output=args.installer / f"cli-{args.commit}",
|
24
|
+
target_platform=args.target_platform,
|
25
|
+
cli_os=cli_os_mapping[args.target_platform],
|
26
|
+
)
|
27
|
+
extensions_config = Path(args.extensions_config).expanduser()
|
28
|
+
download_vscode_extensions(
|
29
|
+
extensions_config, args.target_platform, args.installer / "extensions"
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def install(args: Namespace) -> None:
|
34
|
+
install_vscode_server(
|
35
|
+
args.commit,
|
36
|
+
installer=args.installer / f"cli-{args.commit}",
|
37
|
+
vscode_cli_bin=get_vscode_cli_bin(args.commit),
|
38
|
+
target_platform=args.target_platform,
|
39
|
+
cli_os=cli_os_mapping[args.target_platform],
|
40
|
+
)
|
41
|
+
vscode_server_home = get_vscode_server_home(args.commit)
|
42
|
+
install_vscode_extensions(
|
43
|
+
Path(vscode_server_home) / "bin/code-server",
|
44
|
+
vsix_dir=args.installer / "extensions",
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
def make_argparser() -> ArgumentParser:
|
49
|
+
parent_parser = ArgumentParser(add_help=False)
|
50
|
+
|
51
|
+
parent_parser.add_argument(
|
52
|
+
"--commit",
|
53
|
+
type=str,
|
54
|
+
required=True,
|
55
|
+
help="The commit hash of the VSCode server to download, must match the version of the VSCode client.",
|
56
|
+
)
|
57
|
+
parent_parser.add_argument(
|
58
|
+
"--target-platform",
|
59
|
+
type=str,
|
60
|
+
default="linux-x64",
|
61
|
+
help="The target platform for the VSCode server.",
|
62
|
+
)
|
63
|
+
parent_parser.add_argument(
|
64
|
+
"--installer",
|
65
|
+
type=Path,
|
66
|
+
default="./vscode-offline-installer",
|
67
|
+
help="The output directory for downloaded files. Also used as the installer directory.",
|
68
|
+
)
|
69
|
+
|
70
|
+
parser = ArgumentParser()
|
71
|
+
subparsers = parser.add_subparsers(required=True)
|
72
|
+
|
73
|
+
download_parser = subparsers.add_parser(
|
74
|
+
"download",
|
75
|
+
help="Download VSCode server and extensions",
|
76
|
+
parents=[parent_parser],
|
77
|
+
)
|
78
|
+
download_parser.set_defaults(func=download)
|
79
|
+
download_parser.add_argument(
|
80
|
+
"--extensions-config",
|
81
|
+
type=Path,
|
82
|
+
default=get_vscode_extensions_config(),
|
83
|
+
help="Path to the extensions configuration file. Will search for extensions to download.",
|
84
|
+
)
|
85
|
+
|
86
|
+
install_parsers = subparsers.add_parser(
|
87
|
+
"install",
|
88
|
+
help="Install VSCode server and extensions",
|
89
|
+
parents=[parent_parser],
|
90
|
+
)
|
91
|
+
install_parsers.set_defaults(func=install)
|
92
|
+
|
93
|
+
return parser
|
94
|
+
|
95
|
+
|
96
|
+
def main() -> None:
|
97
|
+
logging.basicConfig(level=logging.INFO)
|
98
|
+
parser = make_argparser()
|
99
|
+
args = parser.parse_args()
|
100
|
+
args.func(args)
|
101
|
+
|
102
|
+
|
103
|
+
if __name__ == "__main__":
|
104
|
+
main()
|
@@ -0,0 +1,106 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
from gzip import GzipFile
|
6
|
+
from urllib.error import HTTPError
|
7
|
+
from urllib.request import urlopen
|
8
|
+
|
9
|
+
from vscode_offline.loggers import logger
|
10
|
+
|
11
|
+
|
12
|
+
def _download_file(url: str, filename: str) -> None:
|
13
|
+
with urlopen(url) as resp:
|
14
|
+
content_encoding = resp.headers.get("Content-Encoding")
|
15
|
+
if content_encoding in {"gzip", "deflate"}:
|
16
|
+
logger.info(f"Content encoding is {content_encoding}, using GzipFile")
|
17
|
+
reader = GzipFile(fileobj=resp)
|
18
|
+
elif not content_encoding:
|
19
|
+
reader = resp
|
20
|
+
else:
|
21
|
+
raise ValueError(f"Unsupported Content-Encoding: {content_encoding}")
|
22
|
+
|
23
|
+
with reader, open(filename, "wb") as fp:
|
24
|
+
while True:
|
25
|
+
chunk = reader.read(1024)
|
26
|
+
if not chunk:
|
27
|
+
break
|
28
|
+
fp.write(chunk)
|
29
|
+
|
30
|
+
|
31
|
+
def download_file(
|
32
|
+
url: str,
|
33
|
+
filename: str,
|
34
|
+
) -> None:
|
35
|
+
logger.info(f"Downloading {filename}")
|
36
|
+
tmp_filename = f"{filename}.tmp"
|
37
|
+
|
38
|
+
for i in range(3):
|
39
|
+
try:
|
40
|
+
_download_file(url, tmp_filename)
|
41
|
+
break
|
42
|
+
except Exception as e:
|
43
|
+
if isinstance(e, HTTPError) and e.code == 404:
|
44
|
+
raise
|
45
|
+
logger.info(f"Attempt {i + 1} failed: {e}")
|
46
|
+
|
47
|
+
if os.path.exists(filename):
|
48
|
+
os.remove(filename)
|
49
|
+
os.rename(tmp_filename, filename)
|
50
|
+
|
51
|
+
logger.info(f"Saved to {filename}")
|
52
|
+
|
53
|
+
|
54
|
+
def download_extension(
|
55
|
+
publisher: str,
|
56
|
+
name: str,
|
57
|
+
version: str,
|
58
|
+
platform: str | None = None,
|
59
|
+
output: str = ".",
|
60
|
+
) -> None:
|
61
|
+
url = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/{publisher}/vsextensions/{name}/{version}/vspackage"
|
62
|
+
if platform:
|
63
|
+
url = f"{url}?targetPlatform={platform}"
|
64
|
+
filename = f"{publisher}.{name}-{version}"
|
65
|
+
if platform:
|
66
|
+
filename = f"{filename}@{platform}"
|
67
|
+
filename = f"{filename}.vsix"
|
68
|
+
download_file(url, f"{output}/{filename}")
|
69
|
+
|
70
|
+
|
71
|
+
def download_vscode_extensions(
|
72
|
+
extensions_config: os.PathLike[str], target_platform: str, output: str = "."
|
73
|
+
) -> None:
|
74
|
+
logger.info(f"Reading extensions config from {extensions_config}")
|
75
|
+
with open(extensions_config) as fp:
|
76
|
+
data = json.loads(fp.read())
|
77
|
+
|
78
|
+
os.makedirs(output, exist_ok=True)
|
79
|
+
for extension in data:
|
80
|
+
identifier = extension["identifier"]
|
81
|
+
publisher, name = identifier["id"].split(".")
|
82
|
+
version = extension["version"]
|
83
|
+
try:
|
84
|
+
download_extension(publisher, name, version, target_platform, output=output)
|
85
|
+
except HTTPError as e:
|
86
|
+
if e.code != 404:
|
87
|
+
raise
|
88
|
+
download_extension(publisher, name, version, output=output)
|
89
|
+
logger.info("============================================")
|
90
|
+
|
91
|
+
|
92
|
+
def download_vscode_server(
|
93
|
+
commit: str, output: str, target_platform: str, cli_os: str
|
94
|
+
) -> None:
|
95
|
+
os.makedirs(output, exist_ok=True)
|
96
|
+
download_file(
|
97
|
+
f"https://vscode.download.prss.microsoft.com/dbazure/download/stable/{commit}/vscode-server-linux-x64.tar.gz",
|
98
|
+
f"{output}/vscode-server-linux-x64.tar.gz",
|
99
|
+
)
|
100
|
+
logger.info("============================================")
|
101
|
+
cli_os_ = cli_os.replace("-", "_")
|
102
|
+
download_file(
|
103
|
+
f"https://vscode.download.prss.microsoft.com/dbazure/download/stable/{commit}/vscode_{cli_os_}_cli.tar.gz",
|
104
|
+
f"{output}/vscode_{cli_os_}_cli.tar.gz",
|
105
|
+
)
|
106
|
+
logger.info("============================================")
|
@@ -0,0 +1,74 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
import subprocess
|
5
|
+
from pathlib import Path
|
6
|
+
from tempfile import TemporaryDirectory
|
7
|
+
|
8
|
+
from vscode_offline.loggers import logger
|
9
|
+
from vscode_offline.paths import get_vscode_server_home
|
10
|
+
|
11
|
+
EXCLUDE_EXTENSIONS = {
|
12
|
+
"ms-vscode-remote.remote-ssh",
|
13
|
+
"ms-vscode-remote.remote-ssh-edit",
|
14
|
+
"ms-vscode-remote.remote-wsl",
|
15
|
+
"ms-vscode-remote.remote-containers",
|
16
|
+
"ms-vscode.remote-explorer",
|
17
|
+
"ms-vscode-remote.vscode-remote-extensionpack",
|
18
|
+
"ms-vscode.remote-server",
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
def get_extension_identifier(filename: str) -> str:
|
23
|
+
filename = os.path.splitext(filename)[0]
|
24
|
+
identifier_version = filename.rsplit("@", maxsplit=1)[0]
|
25
|
+
extension_identifier = identifier_version.rsplit("-", maxsplit=1)[0]
|
26
|
+
return extension_identifier
|
27
|
+
|
28
|
+
|
29
|
+
def install_vscode_extensions(vscode_bin: os.PathLike[str], vsix_dir: str) -> None:
|
30
|
+
for vsix_file in Path(vsix_dir).glob("*.vsix"):
|
31
|
+
extension_identifier = get_extension_identifier(vsix_file.name)
|
32
|
+
if extension_identifier in EXCLUDE_EXTENSIONS:
|
33
|
+
logger.info(f"Skipping excluded extension {extension_identifier}")
|
34
|
+
continue
|
35
|
+
logger.info(f"Installing {vsix_file}")
|
36
|
+
subprocess.check_call([vscode_bin, "--install-extension", vsix_file, "--force"])
|
37
|
+
logger.info(f"Installed {vsix_file}")
|
38
|
+
|
39
|
+
|
40
|
+
def install_vscode_server(
|
41
|
+
commit: str,
|
42
|
+
installer: str,
|
43
|
+
vscode_cli_bin: os.PathLike[str],
|
44
|
+
target_platform: str,
|
45
|
+
cli_os: str,
|
46
|
+
) -> None:
|
47
|
+
cli_os_ = cli_os.replace("-", "_")
|
48
|
+
|
49
|
+
vscode_cli_tarball = Path(installer) / f"vscode_{cli_os_}_cli.tar.gz"
|
50
|
+
with TemporaryDirectory() as tmpdir:
|
51
|
+
subprocess.check_call(["tar", "-xzf", vscode_cli_tarball, "-C", tmpdir])
|
52
|
+
tmpfile = Path(tmpdir) / "code"
|
53
|
+
if os.path.exists(vscode_cli_bin):
|
54
|
+
os.remove(vscode_cli_bin)
|
55
|
+
os.makedirs(os.path.dirname(vscode_cli_bin), exist_ok=True)
|
56
|
+
os.rename(tmpfile, vscode_cli_bin)
|
57
|
+
logger.info(f"Extracted vscode_{cli_os_}_cli.tar.gz to {vscode_cli_bin}")
|
58
|
+
|
59
|
+
vscode_server_tarball = Path(installer) / f"vscode-server-{target_platform}.tar.gz"
|
60
|
+
vscode_server_home = get_vscode_server_home(commit)
|
61
|
+
os.makedirs(vscode_server_home, exist_ok=True)
|
62
|
+
subprocess.check_call(
|
63
|
+
[
|
64
|
+
"tar",
|
65
|
+
"-xzf",
|
66
|
+
vscode_server_tarball,
|
67
|
+
"-C",
|
68
|
+
vscode_server_home,
|
69
|
+
"--strip-components=1",
|
70
|
+
]
|
71
|
+
)
|
72
|
+
logger.info(
|
73
|
+
f"Extracted vscode-server-{target_platform}.tar.gz to {vscode_server_home}"
|
74
|
+
)
|
vscode_offline/paths.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
VSCODE_DATA = Path("~/.vscode").expanduser()
|
7
|
+
VSCODE_SERVER_DATA = Path("~/.vscode-server").expanduser()
|
8
|
+
|
9
|
+
|
10
|
+
def get_vscode_cli_bin(commit: str) -> os.PathLike[str]:
|
11
|
+
return VSCODE_SERVER_DATA / f"code-{commit}"
|
12
|
+
|
13
|
+
|
14
|
+
def get_vscode_server_home(commit: str) -> os.PathLike[str]:
|
15
|
+
return VSCODE_SERVER_DATA / f"cli/servers/Stable-{commit}/server"
|
16
|
+
|
17
|
+
|
18
|
+
def get_vscode_extensions_config() -> os.PathLike[str]:
|
19
|
+
p = VSCODE_DATA / "extensions/extensions.json"
|
20
|
+
if p.exists():
|
21
|
+
return p
|
22
|
+
s = VSCODE_SERVER_DATA / "extensions/extensions.json"
|
23
|
+
if s.exists():
|
24
|
+
return s
|
25
|
+
return p # default to this path
|
@@ -0,0 +1,50 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: vscode-offline
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Download and install vscode server for offline environments
|
5
|
+
Author: Chuck Fan
|
6
|
+
Author-email: Chuck Fan <fanck0605@qq.com>
|
7
|
+
Requires-Python: >=3.9
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
|
10
|
+
# vscode-offline
|
11
|
+
|
12
|
+
vscode-offline 主要用于在无网环境下安装 VSCode Server,方便使用 *Remote - SSH* 插件进行远程开发。
|
13
|
+
|
14
|
+
## 安装
|
15
|
+
|
16
|
+
```shell
|
17
|
+
pip install -U vscode-offline
|
18
|
+
```
|
19
|
+
|
20
|
+
## 用法
|
21
|
+
|
22
|
+
(1)在联网环境安装好 VSCode 和你需要的插件。
|
23
|
+
|
24
|
+
(2)联网环境执行如下命令,将会自动下载当前 VSCode 所有的插件
|
25
|
+
|
26
|
+
> `--commit` 为对应 VSCode 的 Commit 号,*帮助* -> *关于* -> *Commit*
|
27
|
+
|
28
|
+
```shell
|
29
|
+
vscode-offline download \
|
30
|
+
--commit 385651c938df8a906869babee516bffd0ddb9829 \
|
31
|
+
--target-platform linux-x64 \
|
32
|
+
--installer ./vscode-offline-installer
|
33
|
+
```
|
34
|
+
|
35
|
+
(3)复制 `vscode-offline-installer` 到内网服务器
|
36
|
+
|
37
|
+
```shell
|
38
|
+
vscode-offline install \
|
39
|
+
--commit 385651c938df8a906869babee516bffd0ddb9829 \
|
40
|
+
--target-platform linux-x64 \
|
41
|
+
--installer ./vscode-offline-installer
|
42
|
+
```
|
43
|
+
|
44
|
+
## 贡献
|
45
|
+
|
46
|
+
欢迎提交 Issue 和 PR 改进本项目。
|
47
|
+
|
48
|
+
## 许可证
|
49
|
+
|
50
|
+
[MIT License](./LICENSE)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
vscode_offline/__init__.py,sha256=dr6Jtj0XT9eQEC4fzNigEYsAIEfCsaom3HDbUsS-2O4,57
|
2
|
+
vscode_offline/app.py,sha256=pdr7F7vjDVIupkaZSoqL1ZAWNZPAGyPx52lLtcTUYpo,3052
|
3
|
+
vscode_offline/download.py,sha256=thEFFn4XuoHqH0f6cuUcOWWeUQwNhPjKO5d681xIyL4,3478
|
4
|
+
vscode_offline/install.py,sha256=D4klu6aKeqp6bSQkWLzFmaTNalvekw5L93Y4FQs-jFA,2554
|
5
|
+
vscode_offline/loggers.py,sha256=vX91NMtNo1xfxq5y4BCtm_uhCTKtCODqBJHNvcT7JdQ,104
|
6
|
+
vscode_offline/paths.py,sha256=WnOzG6a7PXFurgkVUirHtIyRDnF80PzjKHtdMXSlcbU,693
|
7
|
+
vscode_offline-0.1.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
|
8
|
+
vscode_offline-0.1.0.dist-info/entry_points.txt,sha256=zIMeh_ENKKzlt9lDao8icofSI0TeCQxH8eCwxIRI2G8,56
|
9
|
+
vscode_offline-0.1.0.dist-info/METADATA,sha256=Zi7Fel9ALNPmqHosPrBhRa0X8y8BAaS-9YkVbhONa08,1198
|
10
|
+
vscode_offline-0.1.0.dist-info/RECORD,,
|