util.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. "use strict";
  2. /*---------------------------------------------------------------------------------------------
  3. * Copyright (c) Microsoft Corporation. All rights reserved.
  4. * Licensed under the MIT License. See License.txt in the project root for license information.
  5. *--------------------------------------------------------------------------------------------*/
  6. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  7. if (k2 === undefined) k2 = k;
  8. var desc = Object.getOwnPropertyDescriptor(m, k);
  9. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  10. desc = { enumerable: true, get: function() { return m[k]; } };
  11. }
  12. Object.defineProperty(o, k2, desc);
  13. }) : (function(o, m, k, k2) {
  14. if (k2 === undefined) k2 = k;
  15. o[k2] = m[k];
  16. }));
  17. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  18. Object.defineProperty(o, "default", { enumerable: true, value: v });
  19. }) : function(o, v) {
  20. o["default"] = v;
  21. });
  22. var __importStar = (this && this.__importStar) || function (mod) {
  23. if (mod && mod.__esModule) return mod;
  24. var result = {};
  25. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  26. __setModuleDefault(result, mod);
  27. return result;
  28. };
  29. Object.defineProperty(exports, "__esModule", { value: true });
  30. exports.VSCodeCommandError = exports.Version = exports.isPlatformCLI = exports.isPlatformServer = exports.isPlatformLinux = exports.isPlatformDarwin = exports.isPlatformWindows = exports.systemDefaultPlatform = void 0;
  31. exports.getVSCodeDownloadUrl = getVSCodeDownloadUrl;
  32. exports.urlToOptions = urlToOptions;
  33. exports.downloadDirToExecutablePath = downloadDirToExecutablePath;
  34. exports.insidersDownloadDirToExecutablePath = insidersDownloadDirToExecutablePath;
  35. exports.insidersDownloadDirMetadata = insidersDownloadDirMetadata;
  36. exports.getInsidersVersionMetadata = getInsidersVersionMetadata;
  37. exports.getLatestInsidersMetadata = getLatestInsidersMetadata;
  38. exports.resolveCliPathFromVSCodeExecutablePath = resolveCliPathFromVSCodeExecutablePath;
  39. exports.resolveCliArgsFromVSCodeExecutablePath = resolveCliArgsFromVSCodeExecutablePath;
  40. exports.getProfileArguments = getProfileArguments;
  41. exports.hasArg = hasArg;
  42. exports.runVSCodeCommand = runVSCodeCommand;
  43. exports.isDefined = isDefined;
  44. exports.validateStream = validateStream;
  45. exports.streamToBuffer = streamToBuffer;
  46. exports.isSubdirectory = isSubdirectory;
  47. exports.onceWithoutRejections = onceWithoutRejections;
  48. exports.killTree = killTree;
  49. const child_process_1 = require("child_process");
  50. const crypto_1 = require("crypto");
  51. const fs_1 = require("fs");
  52. const http_proxy_agent_1 = require("http-proxy-agent");
  53. const https_proxy_agent_1 = require("https-proxy-agent");
  54. const path = __importStar(require("path"));
  55. const url_1 = require("url");
  56. const download_1 = require("./download");
  57. const request = __importStar(require("./request"));
  58. const isPlatformWindows = (platform) => platform.includes('win32');
  59. exports.isPlatformWindows = isPlatformWindows;
  60. const isPlatformDarwin = (platform) => platform.includes('darwin');
  61. exports.isPlatformDarwin = isPlatformDarwin;
  62. const isPlatformLinux = (platform) => platform.includes('linux');
  63. exports.isPlatformLinux = isPlatformLinux;
  64. const isPlatformServer = (platform) => platform.includes('server');
  65. exports.isPlatformServer = isPlatformServer;
  66. const isPlatformCLI = (platform) => platform.includes('cli-');
  67. exports.isPlatformCLI = isPlatformCLI;
  68. switch (process.platform) {
  69. case 'darwin':
  70. exports.systemDefaultPlatform = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin';
  71. break;
  72. case 'win32':
  73. exports.systemDefaultPlatform = process.arch === 'arm64' ? 'win32-arm64-archive' : 'win32-x64-archive';
  74. break;
  75. default:
  76. exports.systemDefaultPlatform =
  77. process.arch === 'arm64' ? 'linux-arm64' : process.arch === 'arm' ? 'linux-armhf' : 'linux-x64';
  78. }
  79. const UNRELEASED_SUFFIX = '-unreleased';
  80. class Version {
  81. static parse(version) {
  82. const unreleased = version.endsWith(UNRELEASED_SUFFIX);
  83. if (unreleased) {
  84. version = version.slice(0, -UNRELEASED_SUFFIX.length);
  85. }
  86. return new Version(version, !unreleased);
  87. }
  88. constructor(id, isReleased = true) {
  89. this.id = id;
  90. this.isReleased = isReleased;
  91. }
  92. get isCommit() {
  93. return /^[0-9a-f]{40}$/.test(this.id);
  94. }
  95. get isInsiders() {
  96. return this.id === 'insiders' || this.id.endsWith('-insider');
  97. }
  98. get isStable() {
  99. return this.id === 'stable' || /^[0-9]+\.[0-9]+\.[0-9]$/.test(this.id);
  100. }
  101. toString() {
  102. return this.id + (this.isReleased ? '' : UNRELEASED_SUFFIX);
  103. }
  104. }
  105. exports.Version = Version;
  106. function getVSCodeDownloadUrl(version, platform) {
  107. if (version.id === 'insiders') {
  108. return `https://update.code.visualstudio.com/latest/${platform}/insider?released=${version.isReleased}`;
  109. }
  110. else if (version.isInsiders) {
  111. return `https://update.code.visualstudio.com/${version.id}/${platform}/insider?released=${version.isReleased}`;
  112. }
  113. else if (version.isStable) {
  114. return `https://update.code.visualstudio.com/${version.id}/${platform}/stable?released=${version.isReleased}`;
  115. }
  116. else {
  117. // insiders commit hash
  118. return `https://update.code.visualstudio.com/commit:${version.id}/${platform}/insider`;
  119. }
  120. }
  121. let PROXY_AGENT = undefined;
  122. let HTTPS_PROXY_AGENT = undefined;
  123. if (process.env.npm_config_proxy) {
  124. PROXY_AGENT = new http_proxy_agent_1.HttpProxyAgent(process.env.npm_config_proxy);
  125. HTTPS_PROXY_AGENT = new https_proxy_agent_1.HttpsProxyAgent(process.env.npm_config_proxy);
  126. }
  127. if (process.env.npm_config_https_proxy) {
  128. HTTPS_PROXY_AGENT = new https_proxy_agent_1.HttpsProxyAgent(process.env.npm_config_https_proxy);
  129. }
  130. function urlToOptions(url) {
  131. const parsed = new url_1.URL(url);
  132. const options = {};
  133. if (PROXY_AGENT && parsed.protocol.startsWith('http:')) {
  134. options.agent = PROXY_AGENT;
  135. }
  136. if (HTTPS_PROXY_AGENT && parsed.protocol.startsWith('https:')) {
  137. options.agent = HTTPS_PROXY_AGENT;
  138. }
  139. return options;
  140. }
  141. function downloadDirToExecutablePath(dir, platform) {
  142. if ((0, exports.isPlatformServer)(platform)) {
  143. return (0, exports.isPlatformWindows)(platform)
  144. ? path.resolve(dir, 'bin', 'code-server.cmd')
  145. : path.resolve(dir, 'bin', 'code-server');
  146. }
  147. else if ((0, exports.isPlatformCLI)(platform)) {
  148. return (0, exports.isPlatformWindows)(platform) ? path.resolve(dir, 'code.exe') : path.resolve(dir, 'code');
  149. }
  150. else {
  151. if ((0, exports.isPlatformWindows)(platform)) {
  152. return path.resolve(dir, 'Code.exe');
  153. }
  154. else if ((0, exports.isPlatformDarwin)(platform)) {
  155. return path.resolve(dir, 'Visual Studio Code.app/Contents/MacOS/Electron');
  156. }
  157. else {
  158. return path.resolve(dir, 'code');
  159. }
  160. }
  161. }
  162. function insidersDownloadDirToExecutablePath(dir, platform) {
  163. if ((0, exports.isPlatformServer)(platform)) {
  164. return (0, exports.isPlatformWindows)(platform)
  165. ? path.resolve(dir, 'bin', 'code-server-insiders.cmd')
  166. : path.resolve(dir, 'bin', 'code-server-insiders');
  167. }
  168. else if ((0, exports.isPlatformCLI)(platform)) {
  169. return (0, exports.isPlatformWindows)(platform) ? path.resolve(dir, 'code-insiders.exe') : path.resolve(dir, 'code-insiders');
  170. }
  171. else {
  172. if ((0, exports.isPlatformWindows)(platform)) {
  173. return path.resolve(dir, 'Code - Insiders.exe');
  174. }
  175. else if ((0, exports.isPlatformDarwin)(platform)) {
  176. return path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron');
  177. }
  178. else {
  179. return path.resolve(dir, 'code-insiders');
  180. }
  181. }
  182. }
  183. function insidersDownloadDirMetadata(dir, platform, reporter) {
  184. let productJsonPath;
  185. if ((0, exports.isPlatformServer)(platform)) {
  186. productJsonPath = path.resolve(dir, 'product.json');
  187. }
  188. else if ((0, exports.isPlatformWindows)(platform)) {
  189. productJsonPath = path.resolve(dir, 'resources/app/product.json');
  190. }
  191. else if ((0, exports.isPlatformDarwin)(platform)) {
  192. productJsonPath = path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/Resources/app/product.json');
  193. }
  194. else {
  195. productJsonPath = path.resolve(dir, 'resources/app/product.json');
  196. }
  197. try {
  198. const productJson = JSON.parse((0, fs_1.readFileSync)(productJsonPath, 'utf-8'));
  199. return {
  200. version: productJson.commit,
  201. date: new Date(productJson.date),
  202. };
  203. }
  204. catch (e) {
  205. reporter.error(`Error reading product.json (${e}) will download again`);
  206. return {
  207. version: 'unknown',
  208. date: new Date(0),
  209. };
  210. }
  211. }
  212. async function getInsidersVersionMetadata(platform, version, released) {
  213. const remoteUrl = `https://update.code.visualstudio.com/api/versions/${version}/${platform}/insider?released=${released}`;
  214. return await request.getJSON(remoteUrl, 30_000);
  215. }
  216. async function getLatestInsidersMetadata(platform, released) {
  217. const remoteUrl = `https://update.code.visualstudio.com/api/update/${platform}/insider/latest?released=${released}`;
  218. return await request.getJSON(remoteUrl, 30_000);
  219. }
  220. /**
  221. * Resolve the VS Code cli path from executable path returned from `downloadAndUnzipVSCode`.
  222. * Usually you will want {@link resolveCliArgsFromVSCodeExecutablePath} instead.
  223. */
  224. function resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, platform = exports.systemDefaultPlatform) {
  225. if (platform === 'win32-archive') {
  226. throw new Error('Windows 32-bit is no longer supported');
  227. }
  228. if ((0, exports.isPlatformServer)(platform) || (0, exports.isPlatformCLI)(platform)) {
  229. // no separate CLI
  230. return vscodeExecutablePath;
  231. }
  232. if ((0, exports.isPlatformWindows)(platform)) {
  233. if (vscodeExecutablePath.endsWith('Code - Insiders.exe')) {
  234. return path.resolve(vscodeExecutablePath, '../bin/code-insiders.cmd');
  235. }
  236. else {
  237. return path.resolve(vscodeExecutablePath, '../bin/code.cmd');
  238. }
  239. }
  240. else if ((0, exports.isPlatformDarwin)(platform)) {
  241. return path.resolve(vscodeExecutablePath, '../../../Contents/Resources/app/bin/code');
  242. }
  243. else {
  244. if (vscodeExecutablePath.endsWith('code-insiders')) {
  245. return path.resolve(vscodeExecutablePath, '../bin/code-insiders');
  246. }
  247. else {
  248. return path.resolve(vscodeExecutablePath, '../bin/code');
  249. }
  250. }
  251. }
  252. /**
  253. * Resolve the VS Code cli arguments from executable path returned from `downloadAndUnzipVSCode`.
  254. * You can use this path to spawn processes for extension management. For example:
  255. *
  256. * ```ts
  257. * const cp = require('child_process');
  258. * const { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } = require('@vscode/test-electron')
  259. * const vscodeExecutablePath = await downloadAndUnzipVSCode('1.36.0');
  260. * const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
  261. *
  262. * cp.spawnSync(cli, [...args, '--install-extension', '<EXTENSION-ID-OR-PATH-TO-VSIX>'], {
  263. * encoding: 'utf-8',
  264. * stdio: 'inherit'
  265. * shell: process.platform === 'win32',
  266. * });
  267. * ```
  268. *
  269. * @param vscodeExecutablePath The `vscodeExecutablePath` from `downloadAndUnzipVSCode`.
  270. */
  271. function resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath, options) {
  272. const args = [
  273. resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, options?.platform ?? exports.systemDefaultPlatform),
  274. ];
  275. if (!options?.reuseMachineInstall) {
  276. args.push(...getProfileArguments(args));
  277. }
  278. return args;
  279. }
  280. /** Adds the extensions and user data dir to the arguments for the VS Code CLI */
  281. function getProfileArguments(args) {
  282. const out = [];
  283. if (!hasArg('extensions-dir', args)) {
  284. out.push(`--extensions-dir=${path.join(download_1.defaultCachePath, 'extensions')}`);
  285. }
  286. if (!hasArg('user-data-dir', args)) {
  287. out.push(`--user-data-dir=${path.join(download_1.defaultCachePath, 'user-data')}`);
  288. }
  289. return out;
  290. }
  291. function hasArg(argName, argList) {
  292. return argList.some((a) => a === `--${argName}` || a.startsWith(`--${argName}=`));
  293. }
  294. class VSCodeCommandError extends Error {
  295. constructor(args, exitCode, stderr, stdout) {
  296. super(`'code ${args.join(' ')}' failed with exit code ${exitCode}:\n\n${stderr}\n\n${stdout}`);
  297. this.exitCode = exitCode;
  298. this.stderr = stderr;
  299. this.stdout = stdout;
  300. }
  301. }
  302. exports.VSCodeCommandError = VSCodeCommandError;
  303. /**
  304. * Runs a VS Code command, and returns its output.
  305. *
  306. * @throws a {@link VSCodeCommandError} if the command fails
  307. */
  308. async function runVSCodeCommand(_args, options = {}) {
  309. const args = _args.slice();
  310. let executable = await (0, download_1.downloadAndUnzipVSCode)(options);
  311. let shell = false;
  312. if (!options.reuseMachineInstall) {
  313. args.push(...getProfileArguments(args));
  314. }
  315. // Unless the user is manually running tests or extension development, then resolve to the CLI script
  316. if (!hasArg('extensionTestsPath', args) && !hasArg('extensionDevelopmentPath', args)) {
  317. executable = resolveCliPathFromVSCodeExecutablePath(executable, options?.platform ?? exports.systemDefaultPlatform);
  318. shell = process.platform === 'win32'; // CVE-2024-27980
  319. }
  320. return new Promise((resolve, reject) => {
  321. let stdout = '';
  322. let stderr = '';
  323. const child = (0, child_process_1.spawn)(shell ? `"${executable}"` : executable, args, {
  324. stdio: 'pipe',
  325. shell,
  326. windowsHide: true,
  327. ...options.spawn,
  328. });
  329. child.stdout?.setEncoding('utf-8').on('data', (data) => (stdout += data));
  330. child.stderr?.setEncoding('utf-8').on('data', (data) => (stderr += data));
  331. child.on('error', reject);
  332. child.on('exit', (code) => {
  333. if (code !== 0) {
  334. reject(new VSCodeCommandError(args, code, stderr, stdout));
  335. }
  336. else {
  337. resolve({ stdout, stderr });
  338. }
  339. });
  340. });
  341. }
  342. /** Predicates whether arg is undefined or null */
  343. function isDefined(arg) {
  344. return arg != null;
  345. }
  346. /**
  347. * Validates the stream data matches the given length and checksum, if any.
  348. *
  349. * Note: md5 is not ideal, but it's what we get from the CDN, and for the
  350. * purposes of self-reported content verification is sufficient.
  351. */
  352. function validateStream(readable, length, sha256) {
  353. let actualLen = 0;
  354. const checksum = sha256 ? (0, crypto_1.createHash)('sha256') : undefined;
  355. return new Promise((resolve, reject) => {
  356. readable.on('data', (chunk) => {
  357. checksum?.update(chunk);
  358. actualLen += chunk.length;
  359. });
  360. readable.on('error', reject);
  361. readable.on('end', () => {
  362. if (actualLen !== length) {
  363. return reject(new Error(`Downloaded stream length ${actualLen} does not match expected length ${length}`));
  364. }
  365. const digest = checksum?.digest('hex');
  366. if (digest && digest !== sha256) {
  367. return reject(new Error(`Downloaded file checksum ${digest} does not match expected checksum ${sha256}`));
  368. }
  369. resolve();
  370. });
  371. });
  372. }
  373. /** Gets a Buffer from a Node.js stream */
  374. function streamToBuffer(readable) {
  375. return new Promise((resolve, reject) => {
  376. const chunks = [];
  377. readable.on('data', (chunk) => chunks.push(chunk));
  378. readable.on('error', reject);
  379. readable.on('end', () => resolve(Buffer.concat(chunks)));
  380. });
  381. }
  382. /** Gets whether child is a subdirectory of the parent */
  383. function isSubdirectory(parent, child) {
  384. const relative = path.relative(parent, child);
  385. return !relative.startsWith('..') && !path.isAbsolute(relative);
  386. }
  387. /**
  388. * Wraps a function so that it's called once, and never again, memoizing
  389. * the result unless it rejects.
  390. */
  391. function onceWithoutRejections(fn) {
  392. let value;
  393. return (...args) => {
  394. if (!value) {
  395. value = fn(...args).catch((err) => {
  396. value = undefined;
  397. throw err;
  398. });
  399. }
  400. return value;
  401. };
  402. }
  403. function killTree(processId, force) {
  404. let cp;
  405. if (process.platform === 'win32') {
  406. const windir = process.env['WINDIR'] || 'C:\\Windows';
  407. // when killing a process in Windows its child processes are *not* killed but become root processes.
  408. // Therefore we use TASKKILL.EXE
  409. cp = (0, child_process_1.spawn)(path.join(windir, 'System32', 'taskkill.exe'), [...(force ? ['/F'] : []), '/T', '/PID', processId.toString()], { stdio: 'inherit' });
  410. }
  411. else {
  412. // on linux and OS X we kill all direct and indirect child processes as well
  413. cp = (0, child_process_1.spawn)('sh', [path.resolve(__dirname, '../killTree.sh'), processId.toString(), force ? '9' : '15'], {
  414. stdio: 'inherit',
  415. });
  416. }
  417. return new Promise((resolve, reject) => {
  418. cp.on('error', reject).on('exit', resolve);
  419. });
  420. }