From b9ced16083ab7f7511339063101f1c9af67b622d Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 21 Feb 2025 10:46:06 -0500 Subject: [PATCH] Replace adm-zip with yauzl All adm-zip versions after 0.5.10 seem to have bugs: - 0.5.11 causes tests to fail. - 0.5.12 causes tests to hang. - 0.5.13 causes extracted files to be empty. This also avoids extracting anything other than the clangd binary. --- package-lock.json | 57 ++++++++++++++++++++++----------- package.json | 6 ++-- src/index.ts | 80 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index eaebff4..c0cc812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,14 @@ "version": "0.1.19", "license": "Apache-2.0 WITH LLVM-exception", "dependencies": { - "adm-zip": "^0.5.14", "node-fetch": "^2.7.0", "readdirp": "^4.1.1", "rimraf": "^6.0.1", "semver": "^7.6.3", - "which": "^5.0.0" + "which": "^5.0.0", + "yauzl": "^3.2.0" }, "devDependencies": { - "@types/adm-zip": "^0.5.5", "@types/node": "^22.0.2", "@types/node-fetch": "^2.6.11", "@types/node-static": "^0.7.11", @@ -25,6 +24,7 @@ "@types/tape": "^5.6.4", "@types/tmp": "^0.2.6", "@types/which": "^3.0.4", + "@types/yauzl": "^2.10.3", "node-static": "^0.7.11", "prettier": "^3.3.3", "tap-spec": "^5.0.0", @@ -77,16 +77,6 @@ "node": ">= 0.4" } }, - "node_modules/@types/adm-zip": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", - "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -159,13 +149,14 @@ "dev": true, "license": "MIT" }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12.0" + "dependencies": { + "@types/node": "*" } }, "node_modules/ansi-regex": { @@ -297,6 +288,15 @@ "balanced-match": "^1.0.0" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-shims": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", @@ -1986,6 +1986,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/plur": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", @@ -3219,6 +3225,19 @@ "engines": { "node": ">=0.4" } + }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index aecc5e7..4157c30 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,14 @@ "url": "https://github.com/clangd/node-clangd/issues" }, "dependencies": { - "adm-zip": "^0.5.14", "node-fetch": "^2.7.0", "readdirp": "^4.1.1", "rimraf": "^6.0.1", "semver": "^7.6.3", - "which": "^5.0.0" + "which": "^5.0.0", + "yauzl": "^3.2.0" }, "devDependencies": { - "@types/adm-zip": "^0.5.5", "@types/node": "^22.0.2", "@types/node-fetch": "^2.6.11", "@types/node-static": "^0.7.11", @@ -42,6 +41,7 @@ "@types/tape": "^5.6.4", "@types/tmp": "^0.2.6", "@types/which": "^3.0.4", + "@types/yauzl": "^2.10.3", "node-static": "^0.7.11", "prettier": "^3.3.3", "tap-spec": "^5.0.0", diff --git a/src/index.ts b/src/index.ts index 4bf1aee..977a36e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ // - checking for updates (manual or automatic) // - no usable clangd found, try to recover // These have different flows, but the same underlying mechanisms. -import AdmZip from 'adm-zip'; +import yauzl from 'yauzl'; import * as child_process from 'child_process'; import * as fs from 'fs'; import fetch from 'node-fetch'; @@ -18,6 +18,7 @@ import {readdirpPromise} from 'readdirp'; import {rimraf} from 'rimraf'; import * as semver from 'semver'; import * as stream from 'stream'; +import {promisify} from 'util'; import which from 'which'; // Abstracts the editor UI and configuration. @@ -314,23 +315,70 @@ namespace Install { // continue with installation. } } + const zipFile = path.join(dirs.download, asset.name); await download(asset.browser_download_url, zipFile, abort, ui); - const zip = new AdmZip(zipFile); - const executable = zip.getEntries().find((e) => e.name == clangdFilename); - if (executable === undefined) { - throw new Error(`Didn't find ${clangdFilename} in ${zipFile}`); - } - await ui.slow( - ui.localize('Extracting {0}', asset.name), - new Promise((resolve) => { - zip.extractAllToAsync(extractRoot, true, false, resolve); - }), - ); - const clangdPath = path.join(extractRoot, executable.entryName); - await fs.promises.chmod(clangdPath, 0o755); - await fs.promises.unlink(zipFile); - return clangdPath; + + // Can't use promisify because yauzl.open is overloaded. + const zip = await new Promise((resolve, reject) => { + yauzl.open(zipFile, {lazyEntries: true}, (err, zip) => { + if (err) return reject(err); + + resolve(zip); + }); + }); + + return await new Promise((resolve, reject) => { + zip.on('entry', (entry) => { + if (path.basename(entry.fileName) !== clangdFilename) { + // Keep reading. + zip.readEntry(); + return; + } + + // We found the clangd binary, extract it. + (async () => { + try { + await ui.slow( + ui.localize('Extracting {0}', asset.name), + (async () => { + const readStream = await promisify( + zip.openReadStream.bind(zip), + )(entry); + + const clangdPath = path.join(extractRoot, entry.fileName); + + await fs.promises.mkdir(path.dirname(clangdPath), { + recursive: true, + }); + + await stream.promises.pipeline( + readStream, + fs.createWriteStream(clangdPath), + ); + await fs.promises.chmod(clangdPath, 0o755); + await fs.promises.unlink(zipFile); + + resolve(clangdPath); + })(), + ); + } catch (e) { + reject(e); + } finally { + zip.close(); + } + })(); + }); + + zip.on('error', reject); + + zip.on('end', () => { + reject(new Error(`Didn't find ${clangdFilename} in ${zipFile}`)); + }); + + // Start reading. + zip.readEntry(); + }); } // Create the 'install' and 'download' directories, and return absolute paths.