Skip to content

Commit 2f99366

Browse files
authored
feat: allow downloading server versions (#304)
1 parent 70040b7 commit 2f99366

File tree

3 files changed

+78
-28
lines changed

3 files changed

+78
-28
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22

3-
### Unreleased
3+
### 2.5.1 | 2024-04-07
4+
5+
- Allow downloading server versions
6+
7+
### 2.5.0 | 2024-02-04
48

59
- Fix deleting old Insiders in electron not working
610

lib/download.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
insidersDownloadDirMetadata,
2121
insidersDownloadDirToExecutablePath,
2222
isDefined,
23+
isPlatformCLI,
2324
isSubdirectory,
2425
onceWithoutRejections,
2526
streamToBuffer,
@@ -348,6 +349,7 @@ async function unzipVSCode(
348349
) {
349350
const stagingFile = path.join(tmpdir(), `vscode-test-${Date.now()}.zip`);
350351
const checksum = validateStream(stream, length, sha256);
352+
const stripComponents = isPlatformCLI(platform) ? 0 : 1;
351353

352354
if (format === 'zip') {
353355
try {
@@ -366,11 +368,14 @@ async function unzipVSCode(
366368
// extract file with jszip
367369
for (const filename of Object.keys(content.files)) {
368370
const file = content.files[filename];
369-
const filepath = path.join(extractDir, filename);
370371
if (file.dir) {
371372
continue;
372373
}
373374

375+
const filepath = stripComponents
376+
? path.join(extractDir, filename.split(/[/\\]/g).slice(stripComponents).join(path.sep))
377+
: path.join(extractDir, filename);
378+
374379
// vscode update zips are trusted, but check for zip slip anyway.
375380
if (!isSubdirectory(extractDir, filepath)) {
376381
throw new Error(`Invalid zip file: ${filename}`);
@@ -388,6 +393,17 @@ async function unzipVSCode(
388393
await fs.promises.mkdir(extractDir, { recursive: true });
389394

390395
await spawnDecompressorChild('unzip', ['-q', stagingFile, '-d', extractDir]);
396+
397+
// unzip has no --strip-components equivalent
398+
if (stripComponents) {
399+
const files = await fs.promises.readdir(extractDir);
400+
for (const file of files) {
401+
const dirPath = path.join(extractDir, file);
402+
const children = await fs.promises.readdir(dirPath);
403+
await Promise.all(children.map((c) => fs.promises.rename(path.join(dirPath, c), path.join(extractDir, c))));
404+
await fs.promises.rmdir(dirPath);
405+
}
406+
}
391407
}
392408
} finally {
393409
fs.unlink(stagingFile, () => undefined);
@@ -399,8 +415,11 @@ async function unzipVSCode(
399415
}
400416

401417
// The CLI is a singular binary that doesn't have a wrapper component to remove
402-
const s = platform.includes('cli-') ? 0 : 1;
403-
await spawnDecompressorChild('tar', ['-xzf', '-', `--strip-components=${s}`, '-C', extractDir], stream);
418+
await spawnDecompressorChild(
419+
'tar',
420+
['-xzf', '-', `--strip-components=${stripComponents}`, '-C', extractDir],
421+
stream,
422+
);
404423
await checksum;
405424
}
406425
}
@@ -472,7 +491,11 @@ export async function download(options: Partial<DownloadOptions> = {}): Promise<
472491
if (fs.existsSync(path.join(downloadedPath, COMPLETE_FILE_NAME))) {
473492
if (version.isInsiders) {
474493
reporter.report({ stage: ProgressReportStage.FetchingInsidersMetadata });
475-
const { version: currentHash, date: currentDate } = insidersDownloadDirMetadata(downloadedPath, platform);
494+
const { version: currentHash, date: currentDate } = insidersDownloadDirMetadata(
495+
downloadedPath,
496+
platform,
497+
reporter,
498+
);
476499

477500
const { version: latestHash, timestamp: latestTimestamp } =
478501
version.id === 'insiders' // not qualified with a date

lib/util.ts

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import { URL } from 'url';
1414
import { DownloadOptions, DownloadPlatform, defaultCachePath, downloadAndUnzipVSCode } from './download';
1515
import * as request from './request';
1616
import { TestOptions } from './runTest';
17+
import { ProgressReporter } from './progress';
1718

1819
export let systemDefaultPlatform: DownloadPlatform;
1920

20-
const windowsPlatforms = new Set<DownloadPlatform>(['win32-x64-archive', 'win32-arm64-archive']);
21-
const darwinPlatforms = new Set<DownloadPlatform>(['darwin-arm64', 'darwin']);
21+
export const isPlatformWindows = (platform: string) => platform.includes('win32');
22+
export const isPlatformDarwin = (platform: string) => platform.includes('darwin');
23+
export const isPlatformServer = (platform: string) => platform.includes('server');
24+
export const isPlatformCLI = (platform: string) => platform.includes('cli-');
2225

2326
switch (process.platform) {
2427
case 'darwin':
@@ -44,7 +47,10 @@ export class Version {
4447
return new Version(version, !unreleased);
4548
}
4649

47-
constructor(public readonly id: string, public readonly isReleased = true) {}
50+
constructor(
51+
public readonly id: string,
52+
public readonly isReleased = true,
53+
) {}
4854

4955
public get isCommit() {
5056
return /^[0-9a-f]{40}$/.test(this.id);
@@ -102,40 +108,57 @@ export function urlToOptions(url: string): https.RequestOptions {
102108
}
103109

104110
export function downloadDirToExecutablePath(dir: string, platform: DownloadPlatform) {
105-
if (windowsPlatforms.has(platform)) {
106-
return path.resolve(dir, 'Code.exe');
107-
} else if (darwinPlatforms.has(platform)) {
111+
if (isPlatformWindows(platform)) {
112+
return isPlatformServer(platform) ? path.resolve(dir, 'bin', 'code-server.cmd') : path.resolve(dir, 'Code.exe');
113+
} else if (isPlatformServer(platform)) {
114+
return path.resolve(dir, 'bin', 'code-server');
115+
} else if (isPlatformDarwin(platform)) {
108116
return path.resolve(dir, 'Visual Studio Code.app/Contents/MacOS/Electron');
109117
} else {
110118
return path.resolve(dir, 'code');
111119
}
112120
}
113121

114122
export function insidersDownloadDirToExecutablePath(dir: string, platform: DownloadPlatform) {
115-
if (windowsPlatforms.has(platform)) {
116-
return path.resolve(dir, 'Code - Insiders.exe');
117-
} else if (darwinPlatforms.has(platform)) {
123+
if (isPlatformWindows(platform)) {
124+
return isPlatformServer(platform)
125+
? path.resolve(dir, 'bin', 'code-server-insiders.cmd')
126+
: path.resolve(dir, 'Code - Insiders.exe');
127+
} else if (isPlatformServer(platform)) {
128+
return path.resolve(dir, 'bin', 'code-server-insiders');
129+
} else if (isPlatformDarwin(platform)) {
118130
return path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron');
119131
} else {
120132
return path.resolve(dir, 'code-insiders');
121133
}
122134
}
123135

124-
export function insidersDownloadDirMetadata(dir: string, platform: DownloadPlatform) {
136+
export function insidersDownloadDirMetadata(dir: string, platform: DownloadPlatform, reporter: ProgressReporter) {
125137
let productJsonPath;
126-
if (windowsPlatforms.has(platform)) {
138+
if (isPlatformServer(platform)) {
139+
productJsonPath = path.resolve(dir, 'product.json');
140+
} else if (isPlatformWindows(platform)) {
127141
productJsonPath = path.resolve(dir, 'resources/app/product.json');
128-
} else if (darwinPlatforms.has(platform)) {
142+
} else if (isPlatformDarwin(platform)) {
129143
productJsonPath = path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/Resources/app/product.json');
130144
} else {
131145
productJsonPath = path.resolve(dir, 'resources/app/product.json');
132146
}
133-
const productJson = JSON.parse(readFileSync(productJsonPath, 'utf-8'));
134147

135-
return {
136-
version: productJson.commit,
137-
date: new Date(productJson.date),
138-
};
148+
try {
149+
const productJson = JSON.parse(readFileSync(productJsonPath, 'utf-8'));
150+
151+
return {
152+
version: productJson.commit,
153+
date: new Date(productJson.date),
154+
};
155+
} catch (e) {
156+
reporter.error(`Error reading product.json (${e}) will download again`);
157+
return {
158+
version: 'unknown',
159+
date: new Date(0),
160+
};
161+
}
139162
}
140163

141164
export interface IUpdateMetadata {
@@ -165,18 +188,18 @@ export async function getLatestInsidersMetadata(platform: string, released: bool
165188
*/
166189
export function resolveCliPathFromVSCodeExecutablePath(
167190
vscodeExecutablePath: string,
168-
platform: DownloadPlatform = systemDefaultPlatform
191+
platform: DownloadPlatform = systemDefaultPlatform,
169192
) {
170193
if (platform === 'win32-archive') {
171194
throw new Error('Windows 32-bit is no longer supported');
172195
}
173-
if (windowsPlatforms.has(platform)) {
196+
if (isPlatformWindows(platform)) {
174197
if (vscodeExecutablePath.endsWith('Code - Insiders.exe')) {
175198
return path.resolve(vscodeExecutablePath, '../bin/code-insiders.cmd');
176199
} else {
177200
return path.resolve(vscodeExecutablePath, '../bin/code.cmd');
178201
}
179-
} else if (darwinPlatforms.has(platform)) {
202+
} else if (isPlatformDarwin(platform)) {
180203
return path.resolve(vscodeExecutablePath, '../../../Contents/Resources/app/bin/code');
181204
} else {
182205
if (vscodeExecutablePath.endsWith('code-insiders')) {
@@ -207,7 +230,7 @@ export function resolveCliPathFromVSCodeExecutablePath(
207230
*/
208231
export function resolveCliArgsFromVSCodeExecutablePath(
209232
vscodeExecutablePath: string,
210-
options?: Pick<TestOptions, 'reuseMachineInstall' | 'platform'>
233+
options?: Pick<TestOptions, 'reuseMachineInstall' | 'platform'>,
211234
) {
212235
const args = [
213236
resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, options?.platform ?? systemDefaultPlatform),
@@ -258,7 +281,7 @@ export class VSCodeCommandError extends Error {
258281
args: string[],
259282
public readonly exitCode: number | null,
260283
public readonly stderr: string,
261-
public stdout: string
284+
public stdout: string,
262285
) {
263286
super(`'code ${args.join(' ')}' failed with exit code ${exitCode}:\n\n${stderr}\n\n${stdout}`);
264287
}
@@ -388,7 +411,7 @@ export function killTree(processId: number, force: boolean) {
388411
cp = spawn(
389412
path.join(windir, 'System32', 'taskkill.exe'),
390413
[...(force ? ['/F'] : []), '/T', '/PID', processId.toString()],
391-
{ stdio: 'inherit' }
414+
{ stdio: 'inherit' },
392415
);
393416
} else {
394417
// on linux and OS X we kill all direct and indirect child processes as well

0 commit comments

Comments
 (0)