Skip to content

Commit 67845f3

Browse files
authored
Merge pull request #272 from microsoft/connor4312/update-and-spinners
chore: update dependencies, improve progress reporting appearence
2 parents 2d17e1e + 813ab3a commit 67845f3

13 files changed

+1265
-930
lines changed

.eslintrc.js

-26
This file was deleted.

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
### 2.4.0 | 2024-05-24
44

55
- Allow installing unreleased builds using an `-unreleased` suffix, such as `insiders-unreleased`.
6+
- Allow passing different data directories in `runVSCodeCommand`, using it for extension development.
7+
- Improve the appearance progress reporting.
68

79
### 2.3.10 | 2024-05-13
810

eslint.config.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import eslint from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended);

lib/download.test.ts renamed to lib/download.test.mts

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
16
import { spawnSync } from 'child_process';
27
import { existsSync, promises as fs } from 'fs';
38
import { tmpdir } from 'os';
@@ -8,9 +13,9 @@ import {
813
fetchInsiderVersions,
914
fetchStableVersions,
1015
fetchTargetInferredVersion,
11-
} from './download';
12-
import { SilentReporter } from './progress';
13-
import { resolveCliPathFromVSCodeExecutablePath, systemDefaultPlatform } from './util';
16+
} from './download.js';
17+
import { SilentReporter } from './progress.js';
18+
import { resolveCliPathFromVSCodeExecutablePath, systemDefaultPlatform } from './util.js';
1419

1520
const platforms = [
1621
'darwin',
@@ -70,7 +75,7 @@ describe('sane downloads', () => {
7075
describe('fetchTargetInferredVersion', () => {
7176
let stable: string[];
7277
let insiders: string[];
73-
let extensionsDevelopmentPath = join(tmpdir(), 'vscode-test-tmp-workspace');
78+
const extensionsDevelopmentPath = join(tmpdir(), 'vscode-test-tmp-workspace');
7479

7580
beforeAll(async () => {
7681
[stable, insiders] = await Promise.all([fetchStableVersions(true, 5000), fetchInsiderVersions(true, 5000)]);
@@ -80,7 +85,7 @@ describe('fetchTargetInferredVersion', () => {
8085
await fs.rm(extensionsDevelopmentPath, { recursive: true, force: true });
8186
});
8287

83-
const writeJSON = async (path: string, contents: object) => {
88+
const writeJSON = async (path: string, contents: unknown) => {
8489
const target = join(extensionsDevelopmentPath, path);
8590
await fs.mkdir(dirname(target), { recursive: true });
8691
await fs.writeFile(target, JSON.stringify(contents));
@@ -96,49 +101,49 @@ describe('fetchTargetInferredVersion', () => {
96101

97102
test('matches stable if no workspace', async () => {
98103
const version = await doFetch();
99-
expect(version).to.equal(stable[0]);
104+
expect(version.id).to.equal(stable[0]);
100105
});
101106

102107
test('matches stable by default', async () => {
103108
await writeJSON('package.json', {});
104109
const version = await doFetch();
105-
expect(version).to.equal(stable[0]);
110+
expect(version.id).to.equal(stable[0]);
106111
});
107112

108113
test('matches if stable is defined', async () => {
109114
await writeJSON('package.json', { engines: { vscode: '^1.50.0' } });
110115
const version = await doFetch();
111-
expect(version).to.equal(stable[0]);
116+
expect(version.id).to.equal(stable[0]);
112117
});
113118

114119
test('matches best', async () => {
115120
await writeJSON('package.json', { engines: { vscode: '<=1.60.5' } });
116121
const version = await doFetch();
117-
expect(version).to.equal('1.60.2');
122+
expect(version.id).to.equal('1.60.2');
118123
});
119124

120125
test('matches multiple workspaces', async () => {
121126
await writeJSON('a/package.json', { engines: { vscode: '<=1.60.5' } });
122127
await writeJSON('b/package.json', { engines: { vscode: '<=1.55.5' } });
123128
const version = await doFetch(['a', 'b']);
124-
expect(version).to.equal('1.55.2');
129+
expect(version.id).to.equal('1.55.2');
125130
});
126131

127132
test('matches insiders to better stable if there is one', async () => {
128133
await writeJSON('package.json', { engines: { vscode: '^1.60.0-insider' } });
129134
const version = await doFetch();
130-
expect(version).to.equal(stable[0]);
135+
expect(version.id).to.equal(stable[0]);
131136
});
132137

133138
test('matches current insiders', async () => {
134139
await writeJSON('package.json', { engines: { vscode: `^${insiders[0]}` } });
135140
const version = await doFetch();
136-
expect(version).to.equal(insiders[0]);
141+
expect(version.id).to.equal(insiders[0]);
137142
});
138143

139144
test('matches insiders to exact', async () => {
140145
await writeJSON('package.json', { engines: { vscode: '1.60.0-insider' } });
141146
const version = await doFetch();
142-
expect(version).to.equal('1.60.0-insider');
147+
expect(version.id).to.equal('1.60.0-insider');
143148
});
144149
});

lib/download.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as path from 'path';
1010
import * as semver from 'semver';
1111
import { pipeline } from 'stream';
1212
import { promisify } from 'util';
13-
import { ConsoleReporter, ProgressReporter, ProgressReportStage } from './progress';
13+
import { makeConsoleReporter, ProgressReporter, ProgressReportStage } from './progress.js';
1414
import * as request from './request';
1515
import {
1616
downloadDirToExecutablePath,
@@ -359,7 +359,7 @@ async function unzipVSCode(
359359
// eslint-disable-next-line @typescript-eslint/no-explicit-any
360360
(process as any).noAsar = true;
361361

362-
const content = await JSZip.loadAsync(buffer);
362+
const content = await JSZip.default.loadAsync(buffer);
363363
// extract file with jszip
364364
for (const filename of Object.keys(content.files)) {
365365
const file = content.files[filename];
@@ -429,7 +429,7 @@ export async function download(options: Partial<DownloadOptions> = {}): Promise<
429429
const {
430430
platform = systemDefaultPlatform,
431431
cachePath = defaultCachePath,
432-
reporter = new ConsoleReporter(process.stdout.isTTY),
432+
reporter = await makeConsoleReporter(),
433433
timeout = 15_000,
434434
} = options;
435435

@@ -459,7 +459,7 @@ export async function download(options: Partial<DownloadOptions> = {}): Promise<
459459
throw new Error('Windows 32-bit is no longer supported from v1.85 onwards');
460460
}
461461

462-
reporter.report({ stage: ProgressReportStage.ResolvedVersion, version: version.id });
462+
reporter.report({ stage: ProgressReportStage.ResolvedVersion, version: version.toString() });
463463

464464
const downloadedPath = path.resolve(cachePath, makeDownloadDirName(platform, version));
465465
if (fs.existsSync(path.join(downloadedPath, COMPLETE_FILE_NAME))) {

lib/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ export {
1212
VSCodeCommandError,
1313
RunVSCodeCommandOptions,
1414
} from './util';
15-
export * from './progress';
15+
export * from './progress.js';

lib/progress.ts

+66-70
Original file line numberDiff line numberDiff line change
@@ -63,77 +63,73 @@ export class SilentReporter implements ProgressReporter {
6363
}
6464

6565
/** Default progress reporter that logs VS Code download progress to console */
66-
export class ConsoleReporter implements ProgressReporter {
67-
private version?: string;
66+
export const makeConsoleReporter = async (): Promise<ProgressReporter> => {
67+
// needs to be async targeting Node 16 because ora is an es module that cannot be required
68+
const { default: ora } = await import('ora');
69+
let version: undefined | string;
6870

69-
private downloadReport?: {
70-
timeout: NodeJS.Timeout;
71-
report: { stage: ProgressReportStage.Downloading; totalBytes: number; bytesSoFar: number };
72-
};
73-
74-
constructor(private readonly showDownloadProgress: boolean) {}
75-
76-
public report(report: ProgressReport): void {
77-
switch (report.stage) {
78-
case ProgressReportStage.ResolvedVersion:
79-
this.version = report.version;
80-
break;
81-
case ProgressReportStage.ReplacingOldInsiders:
82-
console.log(`Removing outdated Insiders at ${report.downloadedPath} and re-downloading.`);
83-
console.log(`Old: ${report.oldHash} | ${report.oldDate.toISOString()}`);
84-
console.log(`New: ${report.newHash} | ${report.newDate.toISOString()}`);
85-
break;
86-
case ProgressReportStage.FoundMatchingInstall:
87-
console.log(`Found existing install in ${report.downloadedPath}. Skipping download`);
88-
break;
89-
case ProgressReportStage.ResolvingCDNLocation:
90-
console.log(`Downloading VS Code ${this.version} from ${report.url}`);
91-
break;
92-
case ProgressReportStage.Downloading:
93-
if (!this.showDownloadProgress && report.bytesSoFar === 0) {
94-
console.log(`Downloading VS Code (${report.totalBytes}B)`);
95-
} else if (!this.downloadReport) {
96-
this.downloadReport = { timeout: setTimeout(() => this.reportDownload(), 100), report };
97-
} else {
98-
this.downloadReport.report = report;
99-
}
100-
break;
101-
case ProgressReportStage.Retrying:
102-
this.flushDownloadReport();
103-
console.log(
104-
`Error downloading, retrying (attempt ${report.attempt} of ${report.totalAttempts}): ${report.error.message}`
105-
);
106-
break;
107-
case ProgressReportStage.NewInstallComplete:
108-
this.flushDownloadReport();
109-
console.log(`Downloaded VS Code into ${report.downloadedPath}`);
110-
break;
111-
}
71+
let spinner: undefined | ReturnType<typeof ora> = ora('Resolving version...').start();
72+
function toMB(bytes: number) {
73+
return (bytes / 1024 / 1024).toFixed(2);
11274
}
11375

114-
public error(err: unknown) {
115-
console.error(err);
116-
}
117-
118-
private flushDownloadReport() {
119-
if (this.showDownloadProgress) {
120-
this.reportDownload();
121-
console.log('');
122-
}
123-
}
76+
return {
77+
error(err: unknown): void {
78+
if (spinner) {
79+
spinner?.fail(`Error: ${err}`);
80+
spinner = undefined;
81+
} else {
82+
console.error(err);
83+
}
84+
},
12485

125-
private reportDownload() {
126-
if (!this.downloadReport) {
127-
return;
128-
}
129-
130-
const { totalBytes, bytesSoFar } = this.downloadReport.report;
131-
this.downloadReport = undefined;
132-
133-
const percent = Math.max(0, Math.min(1, bytesSoFar / totalBytes));
134-
const progressBarSize = 30;
135-
const barTicks = Math.floor(percent * progressBarSize);
136-
const progressBar = '='.repeat(barTicks) + '-'.repeat(progressBarSize - barTicks);
137-
process.stdout.write(`\x1b[G\x1b[0KDownloading VS Code [${progressBar}] ${(percent * 100).toFixed()}%`);
138-
}
139-
}
86+
report(report: ProgressReport): void {
87+
switch (report.stage) {
88+
case ProgressReportStage.ResolvedVersion:
89+
version = report.version;
90+
spinner?.succeed(`Validated version: ${version}`);
91+
spinner = undefined;
92+
break;
93+
case ProgressReportStage.ReplacingOldInsiders:
94+
spinner?.succeed();
95+
spinner = ora(
96+
`Updating Insiders ${report.oldHash} (${report.oldDate.toISOString()}) -> ${report.newHash}`
97+
).start();
98+
break;
99+
case ProgressReportStage.FoundMatchingInstall:
100+
spinner?.succeed();
101+
spinner = undefined;
102+
ora(`Found existing install in ${report.downloadedPath}`).succeed();
103+
break;
104+
case ProgressReportStage.ResolvingCDNLocation:
105+
spinner?.succeed();
106+
spinner = ora(`Found at ${report.url}`).start();
107+
break;
108+
case ProgressReportStage.Downloading:
109+
if (report.bytesSoFar === 0) {
110+
spinner?.succeed();
111+
spinner = ora(`Downloading (${toMB(report.totalBytes)} MB)`).start();
112+
} else if (spinner) {
113+
if (report.bytesSoFar === report.totalBytes) {
114+
spinner.text = 'Extracting...';
115+
} else {
116+
const percent = Math.max(0, Math.min(1, report.bytesSoFar / report.totalBytes));
117+
const size = `${toMB(report.bytesSoFar)}/${toMB(report.totalBytes)}MB`;
118+
spinner.text = `Downloading VS Code: ${size} (${(percent * 100).toFixed()}%)`;
119+
}
120+
}
121+
break;
122+
case ProgressReportStage.Retrying:
123+
spinner?.fail(
124+
`Error downloading, retrying (attempt ${report.attempt} of ${report.totalAttempts}): ${report.error.message}`
125+
);
126+
spinner = undefined;
127+
break;
128+
case ProgressReportStage.NewInstallComplete:
129+
spinner?.succeed(`Downloaded VS Code into ${report.downloadedPath}`);
130+
spinner = undefined;
131+
break;
132+
}
133+
},
134+
};
135+
};

lib/runTest.ts

+2-22
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as cp from 'child_process';
7-
import * as path from 'path';
8-
import { DownloadOptions, defaultCachePath, downloadAndUnzipVSCode } from './download';
9-
import { killTree } from './util';
7+
import { DownloadOptions, downloadAndUnzipVSCode } from './download';
8+
import { getProfileArguments, killTree } from './util';
109

1110
export interface TestOptions extends Partial<DownloadOptions> {
1211
/**
@@ -108,25 +107,6 @@ export async function runTests(options: TestOptions): Promise<number> {
108107

109108
return innerRunTests(options.vscodeExecutablePath, args, options.extensionTestsEnv);
110109
}
111-
112-
/** Adds the extensions and user data dir to the arguments for the VS Code CLI */
113-
export function getProfileArguments(args: readonly string[]) {
114-
const out: string[] = [];
115-
if (!hasArg('extensions-dir', args)) {
116-
out.push(`--extensions-dir=${path.join(defaultCachePath, 'extensions')}`);
117-
}
118-
119-
if (!hasArg('user-data-dir', args)) {
120-
out.push(`--user-data-dir=${path.join(defaultCachePath, 'user-data')}`);
121-
}
122-
123-
return out;
124-
}
125-
126-
function hasArg(argName: string, argList: readonly string[]) {
127-
return argList.some((a) => a === `--${argName}` || a.startsWith(`--${argName}=`));
128-
}
129-
130110
const SIGINT = 'SIGINT';
131111

132112
async function innerRunTests(

0 commit comments

Comments
 (0)