vscode-offline 0.1.3__py3-none-any.whl → 0.1.4__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/app.py CHANGED
@@ -6,6 +6,7 @@ from argparse import ArgumentParser, Namespace
6
6
  from pathlib import Path
7
7
 
8
8
  from vscode_offline.download import (
9
+ download_vscode_client,
9
10
  download_vscode_extensions,
10
11
  download_vscode_server,
11
12
  )
@@ -59,7 +60,7 @@ def cmd_install_server(args: Namespace) -> None:
59
60
 
60
61
  install_vscode_server(
61
62
  args.commit,
62
- cli_installer=args.installer / f"server-{args.commit}",
63
+ server_installer=args.installer / f"server-{args.commit}",
63
64
  vscode_cli_bin=get_vscode_cli_bin(args.commit),
64
65
  platform=host_platform,
65
66
  )
@@ -88,6 +89,26 @@ def cmd_install_extensions(args: Namespace) -> None:
88
89
  )
89
90
 
90
91
 
92
+ def cmd_download_client(args: Namespace) -> None:
93
+ if args.commit is None:
94
+ args.commit = get_vscode_commit_from_code_version()
95
+ if args.commit is None:
96
+ logger.info(
97
+ "Cannot determine commit from `code --version`, please specify --commit manually."
98
+ )
99
+ raise ValueError("Please specify --commit when installing.")
100
+
101
+ download_vscode_client(
102
+ args.commit,
103
+ output=args.installer / f"client-{args.commit}",
104
+ target_platform=args.target_platform,
105
+ )
106
+ extensions_config = Path(args.extensions_config).expanduser()
107
+ download_vscode_extensions(
108
+ extensions_config, args.target_platform, args.installer / "extensions"
109
+ )
110
+
111
+
91
112
  def make_argparser() -> ArgumentParser:
92
113
  parent_parser = ArgumentParser(add_help=False)
93
114
 
@@ -169,6 +190,30 @@ def make_argparser() -> ArgumentParser:
169
190
  help="Path to the `code` binary.",
170
191
  )
171
192
 
193
+ download_client_parser = subparsers.add_parser(
194
+ "download-client",
195
+ help="Download VS Code and extensions",
196
+ parents=[parent_parser],
197
+ )
198
+ download_client_parser.set_defaults(func=cmd_download_client)
199
+ download_client_parser.add_argument(
200
+ "--commit",
201
+ type=str,
202
+ help="The commit hash of the VS Code to download, must match the version of the VSCode client.",
203
+ )
204
+ download_client_parser.add_argument(
205
+ "--target-platform",
206
+ type=str,
207
+ required=True,
208
+ help="The target platform of the VS Code to download.",
209
+ )
210
+ download_client_parser.add_argument(
211
+ "--extensions-config",
212
+ type=Path,
213
+ default=get_vscode_extensions_config(),
214
+ help="Path to the extensions configuration file. Will search for extensions to download.",
215
+ )
216
+
172
217
  return parser
173
218
 
174
219
 
@@ -3,16 +3,20 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  from gzip import GzipFile
6
+ from io import DEFAULT_BUFFER_SIZE
7
+ from pathlib import Path
6
8
  from urllib.error import HTTPError
7
9
  from urllib.request import urlopen
8
10
 
9
11
  from vscode_offline.loggers import logger
10
- from vscode_offline.utils import get_cli_os_arch
12
+ from vscode_offline.utils import get_cli_platform, get_filename_from_headers
11
13
 
12
- _CHUCK_SIZE = 4 * 1024 * 1024 # 4MiB
13
14
 
14
-
15
- def _download_file(url: str, filename: str) -> None:
15
+ def _download_file(
16
+ url: str,
17
+ directory: str | os.PathLike[str],
18
+ filename: str | None = None,
19
+ ) -> os.PathLike[str]:
16
20
  with urlopen(url) as resp:
17
21
  content_encoding = resp.headers.get("Content-Encoding")
18
22
  if content_encoding in {"gzip", "deflate"}:
@@ -23,39 +27,56 @@ def _download_file(url: str, filename: str) -> None:
23
27
  else:
24
28
  raise ValueError(f"Unsupported Content-Encoding: {content_encoding}")
25
29
 
26
- with reader, open(filename, "wb") as fp:
30
+ if filename:
31
+ file_path = Path(directory).joinpath(filename)
32
+ else:
33
+ filename = get_filename_from_headers(resp.headers)
34
+ if not filename:
35
+ raise ValueError(
36
+ "Cannot get filename from HTTP headers, please specify argument `filename`."
37
+ )
38
+ logger.info(f"Get filename `{filename}` from HTTP headers.")
39
+ file_path = Path(directory).joinpath(filename)
40
+ if file_path.exists():
41
+ logger.info(f"File {file_path} already exists, skipping download.")
42
+ return file_path
43
+
44
+ tmp_file_path = Path(directory).joinpath(f"{filename}.tmp")
45
+ with reader, tmp_file_path.open("wb") as fp:
27
46
  while True:
28
- chunk = reader.read(_CHUCK_SIZE)
47
+ chunk = reader.read(DEFAULT_BUFFER_SIZE)
29
48
  if not chunk:
30
49
  break
31
50
  fp.write(chunk)
32
51
 
52
+ if os.path.exists(file_path):
53
+ os.remove(file_path)
54
+ os.rename(tmp_file_path, file_path)
55
+
56
+ logger.info(f"Saved to {file_path} .")
57
+ return file_path
58
+
33
59
 
34
60
  def download_file(
35
61
  url: str,
36
- filename: str,
62
+ directory: str | os.PathLike[str],
63
+ filename: str | None = None,
37
64
  ) -> None:
38
- if os.path.exists(filename):
39
- logger.info(f"File {filename} already exists, skipping download.")
40
- return
41
-
42
- logger.info(f"Downloading {url}")
43
- tmp_filename = f"{filename}.tmp"
65
+ if filename:
66
+ file_path = Path(directory).joinpath(filename)
67
+ if file_path.exists():
68
+ logger.info(f"File {file_path} already exists, skipping download.")
69
+ return
44
70
 
71
+ logger.info(f"Downloading {url} ...")
45
72
  for i in range(3):
46
73
  try:
47
- _download_file(url, tmp_filename)
74
+ _download_file(url, directory, filename)
48
75
  break
49
76
  except Exception as e:
50
77
  if isinstance(e, HTTPError) and e.code == 404:
51
78
  raise
52
- logger.info(f"Attempt {i + 1} failed: {e}")
53
-
54
- if os.path.exists(filename):
55
- os.remove(filename)
56
- os.rename(tmp_filename, filename)
57
-
58
- logger.info(f"Saved to {filename}")
79
+ logger.info(f"Attempt {i + 1} times failed: {e}")
59
80
 
60
81
 
61
82
  def download_extension(
@@ -68,11 +89,8 @@ def download_extension(
68
89
  url = f"https://marketplace.visualstudio.com/_apis/public/gallery/publishers/{publisher}/vsextensions/{name}/{version}/vspackage"
69
90
  if platform:
70
91
  url = f"{url}?targetPlatform={platform}"
71
- filename = f"{publisher}.{name}-{version}"
72
- if platform:
73
- filename = f"{filename}@{platform}"
74
- filename = f"{filename}.vsix"
75
- download_file(url, f"{output}/{filename}")
92
+ filename = f"{publisher}.{name}-{version}{f'@{platform}' if platform else ''}.vsix"
93
+ download_file(url, output, filename)
76
94
 
77
95
 
78
96
  def download_vscode_extensions(
@@ -109,11 +127,27 @@ def download_vscode_server(
109
127
  os.makedirs(output, exist_ok=True)
110
128
  download_file(
111
129
  f"https://update.code.visualstudio.com/commit:{commit}/server-{target_platform}/stable",
112
- f"{output}/vscode-server-{target_platform}.tar.gz",
130
+ output,
131
+ f"vscode-server-{target_platform}.tar.gz",
113
132
  )
114
- target_os_arch = get_cli_os_arch(target_platform)
115
- target_os_arch_ = target_os_arch.replace("-", "_")
133
+ cli_target_platform = get_cli_platform(target_platform)
134
+ cli_target_platform_ = cli_target_platform.replace("-", "_")
135
+ download_file(
136
+ f"https://update.code.visualstudio.com/commit:{commit}/cli-{cli_target_platform}/stable",
137
+ output,
138
+ f"vscode_cli_{cli_target_platform_}_cli.tar.gz",
139
+ )
140
+
141
+
142
+ def download_vscode_client(
143
+ commit: str,
144
+ output: str,
145
+ target_platform: str,
146
+ ) -> None:
147
+ """Download VS Code for the given commit and target platform."""
148
+ os.makedirs(output, exist_ok=True)
116
149
  download_file(
117
- f"https://update.code.visualstudio.com/commit:{commit}/cli-{target_os_arch}/stable",
118
- f"{output}/vscode_cli_{target_os_arch_}_cli.tar.gz",
150
+ f"https://update.code.visualstudio.com/commit:{commit}/{target_platform}/stable",
151
+ output,
152
+ # filename is like "VSCodeSetup-x64-1.104.3.exe" for windows
119
153
  )
vscode_offline/install.py CHANGED
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from tempfile import TemporaryDirectory
8
8
 
9
9
  from vscode_offline.loggers import logger
10
- from vscode_offline.utils import get_cli_os_arch, get_vscode_server_home
10
+ from vscode_offline.utils import get_cli_platform, get_vscode_server_home
11
11
 
12
12
  # These extensions are excluded because they are not needed in a VS Code Server.
13
13
  SERVER_EXCLUDE_EXTENSIONS = frozenset(
@@ -66,14 +66,16 @@ def install_vscode_extensions(
66
66
 
67
67
  def install_vscode_server(
68
68
  commit: str,
69
- cli_installer: str,
69
+ server_installer: str,
70
70
  vscode_cli_bin: os.PathLike[str],
71
71
  platform: str,
72
72
  ) -> None:
73
- cli_os = get_cli_os_arch(platform)
74
- cli_os_ = cli_os.replace("-", "_")
73
+ cli_platform = get_cli_platform(platform)
74
+ cli_platform_ = cli_platform.replace("-", "_")
75
75
 
76
- vscode_cli_tarball = Path(cli_installer) / f"vscode_cli_{cli_os_}_cli.tar.gz"
76
+ vscode_cli_tarball = (
77
+ Path(server_installer) / f"vscode_cli_{cli_platform_}_cli.tar.gz"
78
+ )
77
79
  with TemporaryDirectory() as tmpdir:
78
80
  subprocess.check_call(["tar", "-xzf", vscode_cli_tarball, "-C", tmpdir])
79
81
  tmpfile = Path(tmpdir) / "code"
@@ -81,9 +83,9 @@ def install_vscode_server(
81
83
  os.remove(vscode_cli_bin)
82
84
  os.makedirs(os.path.dirname(vscode_cli_bin), exist_ok=True)
83
85
  os.rename(tmpfile, vscode_cli_bin)
84
- logger.info(f"Extracted vscode_cli_{cli_os_}_cli.tar.gz to {vscode_cli_bin}")
86
+ logger.info(f"Extracted vscode_cli_{cli_platform_}_cli.tar.gz to {vscode_cli_bin}")
85
87
 
86
- vscode_server_tarball = Path(cli_installer) / f"vscode-server-{platform}.tar.gz"
88
+ vscode_server_tarball = Path(server_installer) / f"vscode-server-{platform}.tar.gz"
87
89
  vscode_server_home = get_vscode_server_home(commit)
88
90
  os.makedirs(vscode_server_home, exist_ok=True)
89
91
  subprocess.check_call(
vscode_offline/utils.py CHANGED
@@ -4,6 +4,8 @@ import os
4
4
  import shutil
5
5
  import subprocess
6
6
  import sys
7
+ from collections.abc import Mapping
8
+ from email.parser import HeaderParser
7
9
  from pathlib import Path
8
10
 
9
11
  from vscode_offline.loggers import logger
@@ -72,32 +74,27 @@ def get_vscode_commit_from_code_version() -> str | None:
72
74
  commit = lines[1].strip().decode("utf-8")
73
75
  logger.info(f"Getting commit from `code --version`: {commit}")
74
76
 
75
- return lines[1].strip().decode("utf-8")
76
-
77
-
78
- def get_target_platform_from_installer(cli_installer: str) -> str | None:
79
- directories = list(Path(cli_installer).glob("vscode-server-*.tar.gz"))
80
- if len(directories) == 1:
81
- return directories[0].name[len("vscode-server-") : -len(".tar.gz")]
82
- return None
77
+ return commit
83
78
 
84
79
 
85
80
  # Mapping from target platform to CLI OS and architecture used in download URLs
86
- _cli_os_arch_mapping = {
81
+ _cli_platform_mapping = {
87
82
  "linux-x64": "alpine-x64",
88
83
  "linux-arm64": "alpine-arm64",
84
+ "linux-armhf": "linux-arm64",
85
+ "win32-x64": "win32-x64",
89
86
  }
90
87
 
91
88
 
92
- def get_cli_os_arch(platform: str) -> str:
89
+ def get_cli_platform(platform: str) -> str:
93
90
  """Get the CLI OS and architecture for the given target platform."""
94
- if platform not in _cli_os_arch_mapping:
91
+ if platform not in _cli_platform_mapping:
95
92
  raise ValueError(f"Unsupported target platform: {platform}")
96
- return _cli_os_arch_mapping[platform]
93
+ return _cli_platform_mapping[platform]
97
94
 
98
95
 
99
96
  def get_host_platform() -> str:
100
- """Get the host platform in the format used by VS Code Server install."""
97
+ """Get the host platform in the format used by VS Code install."""
101
98
  if os.name == "nt":
102
99
  if "amd64" in sys.version.lower():
103
100
  return "win32-x64"
@@ -111,3 +108,21 @@ def get_host_platform() -> str:
111
108
  elif machine in ("aarch64", "arm64"):
112
109
  return "linux-arm64"
113
110
  raise ValueError(f"Unsupported host platform: {osname}-{machine}")
111
+
112
+
113
+ def get_filename_from_headers(headers: Mapping[str, str]) -> str | None:
114
+ """Get the filename from HTTP headers.
115
+
116
+ Args:
117
+ headers: The HTTP headers.
118
+ """
119
+ content_disposition = headers.get("Content-Disposition")
120
+ header_str = ""
121
+ if content_type := headers.get("Content-Type"):
122
+ header_str += f"Content-Type: {content_type}\n"
123
+ if content_disposition := headers.get("Content-Disposition"):
124
+ header_str += f"Content-Disposition: {content_disposition}\n"
125
+ if not header_str:
126
+ return None
127
+ header = HeaderParser().parsestr(header_str)
128
+ return header.get_filename()
@@ -1,25 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vscode-offline
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Download and install VS Code Server for offline environments
5
- Author: Chuck Fan
5
+ Project-URL: Homepage, https://github.com/fanck0605/vscode-offline
6
6
  Author-email: Chuck Fan <fanck0605@qq.com>
7
7
  License-Expression: MIT
8
8
  License-File: LICENSE
9
9
  Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: POSIX :: Linux
10
14
  Classifier: Programming Language :: Python :: 3 :: Only
11
15
  Classifier: Programming Language :: Python :: 3.9
12
16
  Classifier: Programming Language :: Python :: 3.10
13
17
  Classifier: Programming Language :: Python :: 3.11
14
18
  Classifier: Programming Language :: Python :: 3.12
15
19
  Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Intended Audience :: Developers
17
- Classifier: License :: OSI Approved :: MIT License
18
- Classifier: Operating System :: POSIX :: Linux
19
- Classifier: Operating System :: Microsoft :: Windows
20
20
  Classifier: Topic :: Utilities
21
21
  Requires-Python: >=3.9
22
- Project-URL: Homepage, https://github.com/fanck0605/vscode-offline
23
22
  Description-Content-Type: text/markdown
24
23
 
25
24
  # vscode-offline
@@ -0,0 +1,11 @@
1
+ vscode_offline/__init__.py,sha256=dr6Jtj0XT9eQEC4fzNigEYsAIEfCsaom3HDbUsS-2O4,57
2
+ vscode_offline/app.py,sha256=uO7netdS6U-CxN77zcXwzH301dBDlwvds1GUVVMYGok,7290
3
+ vscode_offline/download.py,sha256=-6vFRoY5QKEQgIqSbx3XgzoZJz6TzLpSS8mCUygiogE,5136
4
+ vscode_offline/install.py,sha256=pIibDCbCmbidXeGUYIS6H1aAE6d1bWwZyIFJecmAASw,3545
5
+ vscode_offline/loggers.py,sha256=vX91NMtNo1xfxq5y4BCtm_uhCTKtCODqBJHNvcT7JdQ,104
6
+ vscode_offline/utils.py,sha256=Pp5W7yeT883sVkoAn5snZ2Obdqdu7wqa0vGGhLYGYmc,4000
7
+ vscode_offline-0.1.4.dist-info/METADATA,sha256=oD4uE9hkprb961ZjY6WZj0s-QMfVaOUyvbIzRA0tvp4,2474
8
+ vscode_offline-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ vscode_offline-0.1.4.dist-info/entry_points.txt,sha256=XyuZLe7bgm2RmZp9oh9qCxcrAwHypD8XrTnm4G0_CzM,55
10
+ vscode_offline-0.1.4.dist-info/licenses/LICENSE,sha256=pUIXFkLeTS986b7dopOVLyuw72fJsUxhl8H3rEMIycA,1053
11
+ vscode_offline-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  vscode-offline = vscode_offline:main
3
-
@@ -1,11 +0,0 @@
1
- vscode_offline/__init__.py,sha256=dr6Jtj0XT9eQEC4fzNigEYsAIEfCsaom3HDbUsS-2O4,57
2
- vscode_offline/app.py,sha256=MLYg_2q3pudTL0zBAWTRSjVM_8zDccLIZt0s00xvT7Q,5686
3
- vscode_offline/download.py,sha256=hImUW23npX0hgNMcJ2E3nRLbW8fvzeIFEovg0mWzm9U,3763
4
- vscode_offline/install.py,sha256=UNgLnMAAZEOtX4s8v6QlmuIHPf3aeuXrCWfC6_TEM-0,3488
5
- vscode_offline/loggers.py,sha256=vX91NMtNo1xfxq5y4BCtm_uhCTKtCODqBJHNvcT7JdQ,104
6
- vscode_offline/utils.py,sha256=XknMz1aBMyyTvWG-szE_0eMG3KOdpl9aAeX3E5BrDAI,3542
7
- vscode_offline-0.1.3.dist-info/licenses/LICENSE,sha256=pUIXFkLeTS986b7dopOVLyuw72fJsUxhl8H3rEMIycA,1053
8
- vscode_offline-0.1.3.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
9
- vscode_offline-0.1.3.dist-info/entry_points.txt,sha256=zIMeh_ENKKzlt9lDao8icofSI0TeCQxH8eCwxIRI2G8,56
10
- vscode_offline-0.1.3.dist-info/METADATA,sha256=hjeP62mRkggLHpIypE8RgSNXUceC9PYGYBWCrhvabak,2492
11
- vscode_offline-0.1.3.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: uv 0.8.22
3
- Root-Is-Purelib: true
4
- Tag: py3-none-any