"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.killTree = exports.onceWithoutRejections = exports.isSubdirectory = exports.streamToBuffer = exports.validateStream = exports.isDefined = exports.runVSCodeCommand = exports.VSCodeCommandError = exports.hasArg = exports.getProfileArguments = exports.resolveCliArgsFromVSCodeExecutablePath = exports.resolveCliPathFromVSCodeExecutablePath = exports.getLatestInsidersMetadata = exports.getInsidersVersionMetadata = exports.insidersDownloadDirMetadata = exports.insidersDownloadDirToExecutablePath = exports.downloadDirToExecutablePath = exports.urlToOptions = exports.getVSCodeDownloadUrl = exports.Version = exports.systemDefaultPlatform = void 0; const child_process_1 = require("child_process"); const crypto_1 = require("crypto"); const fs_1 = require("fs"); const http_proxy_agent_1 = require("http-proxy-agent"); const https_proxy_agent_1 = require("https-proxy-agent"); const path = __importStar(require("path")); const url_1 = require("url"); const download_1 = require("./download"); const request = __importStar(require("./request")); const windowsPlatforms = new Set(['win32-x64-archive', 'win32-arm64-archive']); const darwinPlatforms = new Set(['darwin-arm64', 'darwin']); switch (process.platform) { case 'darwin': exports.systemDefaultPlatform = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin'; break; case 'win32': exports.systemDefaultPlatform = process.arch === 'arm64' ? 'win32-arm64-archive' : 'win32-x64-archive'; break; default: exports.systemDefaultPlatform = process.arch === 'arm64' ? 'linux-arm64' : process.arch === 'arm' ? 'linux-armhf' : 'linux-x64'; } const UNRELEASED_SUFFIX = '-unreleased'; class Version { static parse(version) { const unreleased = version.endsWith(UNRELEASED_SUFFIX); if (unreleased) { version = version.slice(0, -UNRELEASED_SUFFIX.length); } return new Version(version, !unreleased); } constructor(id, isReleased = true) { this.id = id; this.isReleased = isReleased; } get isCommit() { return /^[0-9a-f]{40}$/.test(this.id); } get isInsiders() { return this.id === 'insiders' || this.id.endsWith('-insider'); } get isStable() { return this.id === 'stable' || /^[0-9]+\.[0-9]+\.[0-9]$/.test(this.id); } toString() { return this.id + (this.isReleased ? '' : UNRELEASED_SUFFIX); } } exports.Version = Version; function getVSCodeDownloadUrl(version, platform) { if (version.id === 'insiders') { return `https://update.code.visualstudio.com/latest/${platform}/insider?released=${version.isReleased}`; } else if (version.isInsiders) { return `https://update.code.visualstudio.com/${version.id}/${platform}/insider?released=${version.isReleased}`; } else if (version.isStable) { return `https://update.code.visualstudio.com/${version.id}/${platform}/stable?released=${version.isReleased}`; } else { // insiders commit hash return `https://update.code.visualstudio.com/commit:${version.id}/${platform}/insider`; } } exports.getVSCodeDownloadUrl = getVSCodeDownloadUrl; let PROXY_AGENT = undefined; let HTTPS_PROXY_AGENT = undefined; if (process.env.npm_config_proxy) { PROXY_AGENT = new http_proxy_agent_1.HttpProxyAgent(process.env.npm_config_proxy); HTTPS_PROXY_AGENT = new https_proxy_agent_1.HttpsProxyAgent(process.env.npm_config_proxy); } if (process.env.npm_config_https_proxy) { HTTPS_PROXY_AGENT = new https_proxy_agent_1.HttpsProxyAgent(process.env.npm_config_https_proxy); } function urlToOptions(url) { const parsed = new url_1.URL(url); const options = {}; if (PROXY_AGENT && parsed.protocol.startsWith('http:')) { options.agent = PROXY_AGENT; } if (HTTPS_PROXY_AGENT && parsed.protocol.startsWith('https:')) { options.agent = HTTPS_PROXY_AGENT; } return options; } exports.urlToOptions = urlToOptions; function downloadDirToExecutablePath(dir, platform) { if (windowsPlatforms.has(platform)) { return path.resolve(dir, 'Code.exe'); } else if (darwinPlatforms.has(platform)) { return path.resolve(dir, 'Visual Studio Code.app/Contents/MacOS/Electron'); } else { return path.resolve(dir, 'code'); } } exports.downloadDirToExecutablePath = downloadDirToExecutablePath; function insidersDownloadDirToExecutablePath(dir, platform) { if (windowsPlatforms.has(platform)) { return path.resolve(dir, 'Code - Insiders.exe'); } else if (darwinPlatforms.has(platform)) { return path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron'); } else { return path.resolve(dir, 'code-insiders'); } } exports.insidersDownloadDirToExecutablePath = insidersDownloadDirToExecutablePath; function insidersDownloadDirMetadata(dir, platform) { let productJsonPath; if (windowsPlatforms.has(platform)) { productJsonPath = path.resolve(dir, 'resources/app/product.json'); } else if (darwinPlatforms.has(platform)) { productJsonPath = path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/Resources/app/product.json'); } else { productJsonPath = path.resolve(dir, 'resources/app/product.json'); } const productJson = JSON.parse((0, fs_1.readFileSync)(productJsonPath, 'utf-8')); return { version: productJson.commit, date: new Date(productJson.date), }; } exports.insidersDownloadDirMetadata = insidersDownloadDirMetadata; async function getInsidersVersionMetadata(platform, version, released) { const remoteUrl = `https://update.code.visualstudio.com/api/versions/${version}/${platform}/insider?released=${released}`; return await request.getJSON(remoteUrl, 30_000); } exports.getInsidersVersionMetadata = getInsidersVersionMetadata; async function getLatestInsidersMetadata(platform, released) { const remoteUrl = `https://update.code.visualstudio.com/api/update/${platform}/insider/latest?released=${released}`; return await request.getJSON(remoteUrl, 30_000); } exports.getLatestInsidersMetadata = getLatestInsidersMetadata; /** * Resolve the VS Code cli path from executable path returned from `downloadAndUnzipVSCode`. * Usually you will want {@link resolveCliArgsFromVSCodeExecutablePath} instead. */ function resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, platform = exports.systemDefaultPlatform) { if (platform === 'win32-archive') { throw new Error('Windows 32-bit is no longer supported'); } if (windowsPlatforms.has(platform)) { if (vscodeExecutablePath.endsWith('Code - Insiders.exe')) { return path.resolve(vscodeExecutablePath, '../bin/code-insiders.cmd'); } else { return path.resolve(vscodeExecutablePath, '../bin/code.cmd'); } } else if (darwinPlatforms.has(platform)) { return path.resolve(vscodeExecutablePath, '../../../Contents/Resources/app/bin/code'); } else { if (vscodeExecutablePath.endsWith('code-insiders')) { return path.resolve(vscodeExecutablePath, '../bin/code-insiders'); } else { return path.resolve(vscodeExecutablePath, '../bin/code'); } } } exports.resolveCliPathFromVSCodeExecutablePath = resolveCliPathFromVSCodeExecutablePath; /** * Resolve the VS Code cli arguments from executable path returned from `downloadAndUnzipVSCode`. * You can use this path to spawn processes for extension management. For example: * * ```ts * const cp = require('child_process'); * const { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } = require('@vscode/test-electron') * const vscodeExecutablePath = await downloadAndUnzipVSCode('1.36.0'); * const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); * * cp.spawnSync(cli, [...args, '--install-extension', ''], { * encoding: 'utf-8', * stdio: 'inherit' * shell: process.platform === 'win32', * }); * ``` * * @param vscodeExecutablePath The `vscodeExecutablePath` from `downloadAndUnzipVSCode`. */ function resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath, options) { const args = [ resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, options?.platform ?? exports.systemDefaultPlatform), ]; if (!options?.reuseMachineInstall) { args.push(...getProfileArguments(args)); } return args; } exports.resolveCliArgsFromVSCodeExecutablePath = resolveCliArgsFromVSCodeExecutablePath; /** Adds the extensions and user data dir to the arguments for the VS Code CLI */ function getProfileArguments(args) { const out = []; if (!hasArg('extensions-dir', args)) { out.push(`--extensions-dir=${path.join(download_1.defaultCachePath, 'extensions')}`); } if (!hasArg('user-data-dir', args)) { out.push(`--user-data-dir=${path.join(download_1.defaultCachePath, 'user-data')}`); } return out; } exports.getProfileArguments = getProfileArguments; function hasArg(argName, argList) { return argList.some((a) => a === `--${argName}` || a.startsWith(`--${argName}=`)); } exports.hasArg = hasArg; class VSCodeCommandError extends Error { constructor(args, exitCode, stderr, stdout) { super(`'code ${args.join(' ')}' failed with exit code ${exitCode}:\n\n${stderr}\n\n${stdout}`); this.exitCode = exitCode; this.stderr = stderr; this.stdout = stdout; } } exports.VSCodeCommandError = VSCodeCommandError; /** * Runs a VS Code command, and returns its output. * * @throws a {@link VSCodeCommandError} if the command fails */ async function runVSCodeCommand(_args, options = {}) { const args = _args.slice(); let executable = await (0, download_1.downloadAndUnzipVSCode)(options); let shell = false; if (!options.reuseMachineInstall) { args.push(...getProfileArguments(args)); } // Unless the user is manually running tests or extension development, then resolve to the CLI script if (!hasArg('extensionTestsPath', args) && !hasArg('extensionDevelopmentPath', args)) { executable = resolveCliPathFromVSCodeExecutablePath(executable, options?.platform ?? exports.systemDefaultPlatform); shell = process.platform === 'win32'; // CVE-2024-27980 } return new Promise((resolve, reject) => { let stdout = ''; let stderr = ''; const child = (0, child_process_1.spawn)(shell ? `"${executable}"` : executable, 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 }); } }); }); } exports.runVSCodeCommand = runVSCodeCommand; /** Predicates whether arg is undefined or null */ function isDefined(arg) { return arg != null; } exports.isDefined = isDefined; /** * Validates the stream data matches the given length and checksum, if any. * * Note: md5 is not ideal, but it's what we get from the CDN, and for the * purposes of self-reported content verification is sufficient. */ function validateStream(readable, length, sha256) { let actualLen = 0; const checksum = sha256 ? (0, crypto_1.createHash)('sha256') : undefined; return new Promise((resolve, reject) => { readable.on('data', (chunk) => { checksum?.update(chunk); actualLen += chunk.length; }); readable.on('error', reject); readable.on('end', () => { if (actualLen !== length) { return reject(new Error(`Downloaded stream length ${actualLen} does not match expected length ${length}`)); } const digest = checksum?.digest('hex'); if (digest && digest !== sha256) { return reject(new Error(`Downloaded file checksum ${digest} does not match expected checksum ${sha256}`)); } resolve(); }); }); } exports.validateStream = validateStream; /** Gets a Buffer from a Node.js stream */ function streamToBuffer(readable) { return new Promise((resolve, reject) => { const chunks = []; readable.on('data', (chunk) => chunks.push(chunk)); readable.on('error', reject); readable.on('end', () => resolve(Buffer.concat(chunks))); }); } exports.streamToBuffer = streamToBuffer; /** Gets whether child is a subdirectory of the parent */ function isSubdirectory(parent, child) { const relative = path.relative(parent, child); return !relative.startsWith('..') && !path.isAbsolute(relative); } exports.isSubdirectory = isSubdirectory; /** * Wraps a function so that it's called once, and never again, memoizing * the result unless it rejects. */ function onceWithoutRejections(fn) { let value; return (...args) => { if (!value) { value = fn(...args).catch((err) => { value = undefined; throw err; }); } return value; }; } exports.onceWithoutRejections = onceWithoutRejections; function killTree(processId, force) { let cp; if (process.platform === 'win32') { const windir = process.env['WINDIR'] || 'C:\\Windows'; // when killing a process in Windows its child processes are *not* killed but become root processes. // Therefore we use TASKKILL.EXE cp = (0, child_process_1.spawn)(path.join(windir, 'System32', 'taskkill.exe'), [...(force ? ['/F'] : []), '/T', '/PID', processId.toString()], { stdio: 'inherit' }); } else { // on linux and OS X we kill all direct and indirect child processes as well cp = (0, child_process_1.spawn)('sh', [path.resolve(__dirname, '../killTree.sh'), processId.toString(), force ? '9' : '15'], { stdio: 'inherit', }); } return new Promise((resolve, reject) => { cp.on('error', reject).on('exit', resolve); }); } exports.killTree = killTree;