feat: runVSCodeCommand as workaround for CVE-2024-27980
This commit is contained in:
Родитель
40ecedf255
Коммит
3f7a3cc5c5
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
### 2.3.10 | 2024-01-19
|
||||
|
||||
- Add `runVSCodeCommand` method and workaround for Node CVE-2024-27980
|
||||
|
||||
### 2.3.9 | 2024-01-19
|
||||
|
||||
- Fix archive extraction on Windows failing when run under Electron
|
||||
|
|
12
README.md
12
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
![Test Status Badge](https://github.com/microsoft/vscode-test/workflows/Tests/badge.svg)
|
||||
|
||||
This module helps you test VS Code extensions.
|
||||
This module helps you test VS Code extensions. Note that new extensions may want to use the [VS Code Test CLI](https://github.com/microsoft/vscode-test-cli/blob/main/README.md), which leverages this module, for a richer editing and execution experience.
|
||||
|
||||
Supported:
|
||||
|
||||
|
@ -13,10 +13,10 @@ Supported:
|
|||
|
||||
## Usage
|
||||
|
||||
See [./sample](./sample) for a runnable sample, with [Azure DevOps Pipelines](https://github.com/microsoft/vscode-test/blob/master/sample/azure-pipelines.yml) and [Travis CI](https://github.com/microsoft/vscode-test/blob/master/.travis.yml) configuration.
|
||||
See [./sample](./sample) for a runnable sample, with [Azure DevOps Pipelines](https://github.com/microsoft/vscode-test/blob/main/sample/azure-pipelines.yml) and [Github ACtions](https://github.com/microsoft/vscode-test/blob/main/sample/.travis.yml) configuration.
|
||||
|
||||
```ts
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
import { runTests, runVSCodeCommand, downloadAndUnzipVSCode } from '@vscode/test-electron';
|
||||
|
||||
async function go() {
|
||||
try {
|
||||
|
@ -82,11 +82,7 @@ async function go() {
|
|||
/**
|
||||
* Install Python extension
|
||||
*/
|
||||
const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
|
||||
cp.spawnSync(cli, [...args, '--install-extension', 'ms-python.python'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await runVSCodeCommand(['--install-extension', 'ms-python.python'], { version: '1.35.0' });
|
||||
|
||||
/**
|
||||
* - Add additional launch flags for VS Code
|
||||
|
|
|
@ -49,7 +49,8 @@ describe('sane downloads', () => {
|
|||
}
|
||||
|
||||
if (platform === systemDefaultPlatform) {
|
||||
const version = spawnSync(exePath, ['--version']);
|
||||
const shell = process.platform === 'win32';
|
||||
const version = spawnSync(shell ? `"${exePath}"` : exePath, ['--version'], { shell });
|
||||
expect(version.status).to.equal(0);
|
||||
expect(version.stdout.toString().trim()).to.not.be.empty;
|
||||
}
|
||||
|
|
|
@ -173,13 +173,64 @@ export type DownloadPlatform = StringLiteralUnion<
|
|||
>;
|
||||
|
||||
export interface DownloadOptions {
|
||||
readonly cachePath: string;
|
||||
readonly version: DownloadVersion;
|
||||
readonly platform: DownloadPlatform;
|
||||
readonly extensionDevelopmentPath?: string | string[];
|
||||
readonly reporter?: ProgressReporter;
|
||||
readonly extractSync?: boolean;
|
||||
readonly timeout?: number;
|
||||
/**
|
||||
* The VS Code version to download. Valid versions are:
|
||||
* - `'stable'`
|
||||
* - `'insiders'`
|
||||
* - `'1.32.0'`, `'1.31.1'`, etc
|
||||
*
|
||||
* Defaults to `stable`, which is latest stable version.
|
||||
*
|
||||
* *If a local copy exists at `.vscode-test/vscode-<VERSION>`, skip download.*
|
||||
*/
|
||||
version: DownloadVersion;
|
||||
|
||||
/**
|
||||
* The VS Code platform to download. If not specified, it defaults to the
|
||||
* current platform.
|
||||
*
|
||||
* Possible values are:
|
||||
* - `win32-x64-archive`
|
||||
* - `win32-arm64-archive `
|
||||
* - `darwin`
|
||||
* - `darwin-arm64`
|
||||
* - `linux-x64`
|
||||
* - `linux-arm64`
|
||||
* - `linux-armhf`
|
||||
*/
|
||||
platform: DownloadPlatform;
|
||||
|
||||
/**
|
||||
* Path where the downloaded VS Code instance is stored.
|
||||
* Defaults to `.vscode-test` within your working directory folder.
|
||||
*/
|
||||
cachePath: string;
|
||||
|
||||
/**
|
||||
* Absolute path to the extension root. Passed to `--extensionDevelopmentPath`.
|
||||
* Must include a `package.json` Extension Manifest.
|
||||
*/
|
||||
extensionDevelopmentPath?: string | string[];
|
||||
|
||||
/**
|
||||
* Progress reporter to use while VS Code is downloaded. Defaults to a
|
||||
* console reporter. A {@link SilentReporter} is also available, and you
|
||||
* may implement your own.
|
||||
*/
|
||||
reporter?: ProgressReporter;
|
||||
|
||||
/**
|
||||
* Whether the downloaded zip should be synchronously extracted. Should be
|
||||
* omitted unless you're experiencing issues installing VS Code versions.
|
||||
*/
|
||||
extractSync?: boolean;
|
||||
|
||||
/**
|
||||
* Number of milliseconds after which to time out if no data is received from
|
||||
* the remote when downloading VS Code. Note that this is an 'idle' timeout
|
||||
* and does not enforce the total time VS Code may take to download.
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
interface IDownload {
|
||||
|
|
12
lib/index.ts
12
lib/index.ts
|
@ -3,7 +3,13 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export { download, downloadAndUnzipVSCode } from './download';
|
||||
export { runTests } from './runTest';
|
||||
export { resolveCliPathFromVSCodeExecutablePath, resolveCliArgsFromVSCodeExecutablePath } from './util';
|
||||
export { download, downloadAndUnzipVSCode, DownloadOptions } from './download';
|
||||
export { runTests, TestOptions } from './runTest';
|
||||
export {
|
||||
resolveCliPathFromVSCodeExecutablePath,
|
||||
resolveCliArgsFromVSCodeExecutablePath,
|
||||
runVSCodeCommand,
|
||||
VSCodeCommandError,
|
||||
RunVSCodeCommandOptions,
|
||||
} from './util';
|
||||
export * from './progress';
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { downloadAndUnzipVSCode, DownloadVersion, DownloadPlatform, defaultCachePath } from './download';
|
||||
import { ProgressReporter } from './progress';
|
||||
import { DownloadOptions, defaultCachePath, downloadAndUnzipVSCode } from './download';
|
||||
import { killTree } from './util';
|
||||
|
||||
export interface TestOptions {
|
||||
export interface TestOptions extends Partial<DownloadOptions> {
|
||||
/**
|
||||
* The VS Code executable path used for testing.
|
||||
*
|
||||
|
@ -18,33 +17,6 @@ export interface TestOptions {
|
|||
*/
|
||||
vscodeExecutablePath?: string;
|
||||
|
||||
/**
|
||||
* The VS Code version to download. Valid versions are:
|
||||
* - `'stable'`
|
||||
* - `'insiders'`
|
||||
* - `'1.32.0'`, `'1.31.1'`, etc
|
||||
*
|
||||
* Defaults to `stable`, which is latest stable version.
|
||||
*
|
||||
* *If a local copy exists at `.vscode-test/vscode-<VERSION>`, skip download.*
|
||||
*/
|
||||
version?: DownloadVersion;
|
||||
|
||||
/**
|
||||
* The VS Code platform to download. If not specified, it defaults to the
|
||||
* current platform.
|
||||
*
|
||||
* Possible values are:
|
||||
* - `win32-x64-archive`
|
||||
* - `win32-arm64-archive `
|
||||
* - `darwin`
|
||||
* - `darwin-arm64`
|
||||
* - `linux-x64`
|
||||
* - `linux-arm64`
|
||||
* - `linux-armhf`
|
||||
*/
|
||||
platform?: DownloadPlatform;
|
||||
|
||||
/**
|
||||
* Whether VS Code should be launched using default settings and extensions
|
||||
* installed on this machine. If `false`, then separate directories will be
|
||||
|
@ -95,26 +67,6 @@ export interface TestOptions {
|
|||
* See `code --help` for possible arguments.
|
||||
*/
|
||||
launchArgs?: string[];
|
||||
|
||||
/**
|
||||
* Progress reporter to use while VS Code is downloaded. Defaults to a
|
||||
* console reporter. A {@link SilentReporter} is also available, and you
|
||||
* may implement your own.
|
||||
*/
|
||||
reporter?: ProgressReporter;
|
||||
|
||||
/**
|
||||
* Whether the downloaded zip should be synchronously extracted. Should be
|
||||
* omitted unless you're experiencing issues installing VS Code versions.
|
||||
*/
|
||||
extractSync?: boolean;
|
||||
|
||||
/**
|
||||
* Number of milliseconds after which to time out if no data is received from
|
||||
* the remote when downloading VS Code. Note that this is an 'idle' timeout
|
||||
* and does not enforce the total time VS Code may take to download.
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,7 +137,9 @@ async function innerRunTests(
|
|||
}
|
||||
): Promise<number> {
|
||||
const fullEnv = Object.assign({}, process.env, testRunnerEnv);
|
||||
const cmd = cp.spawn(executable, args, { env: fullEnv });
|
||||
const shell = process.platform === 'win32';
|
||||
const cmd = cp.spawn(shell ? `"${executable}"` : executable, args, { env: fullEnv, shell });
|
||||
|
||||
let exitRequested = false;
|
||||
const ctrlc1 = () => {
|
||||
process.removeListener(SIGINT, ctrlc1);
|
||||
|
|
55
lib/util.ts
55
lib/util.ts
|
@ -3,17 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
import { ChildProcess, SpawnOptions, spawn } from 'child_process';
|
||||
import { createHash } from 'crypto';
|
||||
import { readFileSync } from 'fs';
|
||||
import * as createHttpProxyAgent from 'http-proxy-agent';
|
||||
import * as https from 'https';
|
||||
import * as createHttpsProxyAgent from 'https-proxy-agent';
|
||||
import * as path from 'path';
|
||||
import { URL } from 'url';
|
||||
import { DownloadPlatform } from './download';
|
||||
import { DownloadOptions, DownloadPlatform, downloadAndUnzipVSCode } from './download';
|
||||
import * as request from './request';
|
||||
import { TestOptions, getProfileArguments } from './runTest';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
export let systemDefaultPlatform: DownloadPlatform;
|
||||
|
||||
|
@ -176,6 +176,7 @@ export function resolveCliPathFromVSCodeExecutablePath(
|
|||
* cp.spawnSync(cli, [...args, '--install-extension', '<EXTENSION-ID-OR-PATH-TO-VSIX>'], {
|
||||
* encoding: 'utf-8',
|
||||
* stdio: 'inherit'
|
||||
* shell: process.platform === 'win32',
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
|
@ -195,6 +196,54 @@ export function resolveCliArgsFromVSCodeExecutablePath(
|
|||
return args;
|
||||
}
|
||||
|
||||
export type RunVSCodeCommandOptions = Partial<DownloadOptions> & { spawn?: SpawnOptions };
|
||||
|
||||
export class VSCodeCommandError extends Error {
|
||||
constructor(
|
||||
args: string[],
|
||||
public readonly exitCode: number | null,
|
||||
public readonly stderr: string,
|
||||
public stdout: string
|
||||
) {
|
||||
super(`'code ${args.join(' ')}' failed with exit code ${exitCode}:\n\n${stderr}\n\n${stdout}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a VS Code command, and returns its output
|
||||
* @throws a {@link VSCodeCommandError} if the command fails
|
||||
*/
|
||||
export async function runVSCodeCommand(args: string[], options: RunVSCodeCommandOptions = {}) {
|
||||
const vscodeExecutablePath = await downloadAndUnzipVSCode(options);
|
||||
const [cli, ...baseArgs] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
|
||||
|
||||
const shell = process.platform === 'win32';
|
||||
|
||||
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
const child = spawn(shell ? `"${cli}"` : cli, [...baseArgs, ...args], {
|
||||
stdio: 'pipe',
|
||||
shell,
|
||||
windowsHide: true,
|
||||
...options.spawn,
|
||||
});
|
||||
|
||||
child.stdout?.setEncoding('utf-8').on('data', (data) => (stdout += data));
|
||||
child.stderr?.setEncoding('utf-8').on('data', (data) => (stderr += data));
|
||||
|
||||
child.on('error', reject);
|
||||
child.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new VSCodeCommandError(args, code, stderr, stdout));
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Predicates whether arg is undefined or null */
|
||||
export function isDefined<T>(arg: T | undefined | null): arg is T {
|
||||
return arg != null;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
name: Run VSCode Extension Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Compile
|
||||
run: yarn compile
|
||||
|
||||
- name: Run tests
|
||||
run: xvfb-run -a yarn test
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
if: runner.os != 'Linux'
|
|
@ -1,7 +1,7 @@
|
|||
import * as path from 'path';
|
||||
import * as cp from 'child_process';
|
||||
|
||||
import { runTests, downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '../../..';
|
||||
import { runTests, downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath, runVSCodeCommand } from '../../..';
|
||||
|
||||
async function go() {
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
|
||||
|
@ -28,10 +28,10 @@ async function go() {
|
|||
});
|
||||
|
||||
/**
|
||||
* Use 1.36.1 release for testing
|
||||
* Use 1.61.0 release for testing
|
||||
*/
|
||||
await runTests({
|
||||
version: '1.36.1',
|
||||
version: '1.61.0',
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
launchArgs: [testWorkspace],
|
||||
|
@ -58,14 +58,14 @@ async function go() {
|
|||
});
|
||||
|
||||
/**
|
||||
* Noop, since 1.36.1 already downloaded to .vscode-test/vscode-1.36.1
|
||||
* Noop, since 1.61.0 already downloaded to .vscode-test/vscode-1.61.0
|
||||
*/
|
||||
await downloadAndUnzipVSCode('1.36.1');
|
||||
await downloadAndUnzipVSCode('1.61.0');
|
||||
|
||||
/**
|
||||
* Manually download VS Code 1.35.0 release for testing.
|
||||
*/
|
||||
const vscodeExecutablePath = await downloadAndUnzipVSCode('1.35.0');
|
||||
const vscodeExecutablePath = await downloadAndUnzipVSCode('1.60.0');
|
||||
await runTests({
|
||||
vscodeExecutablePath,
|
||||
extensionDevelopmentPath,
|
||||
|
@ -76,11 +76,7 @@ async function go() {
|
|||
/**
|
||||
* Install Python extension
|
||||
*/
|
||||
const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
|
||||
cp.spawnSync(cli, [...args, '--install-extension', 'ms-python.python'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await runVSCodeCommand(['--install-extension', 'ms-python.python'], { version: '1.60.0' });
|
||||
|
||||
/**
|
||||
* - Add additional launch flags for VS Code
|
||||
|
|
Загрузка…
Ссылка в новой задаче