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..e326a42 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,20 +315,72 @@ 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}`); - } + + // Can't use promisify(yauzl.open) because yauzl.open is overloaded and we + // want the second overload. + const open = promisify( + ( + file: string, + callback: (err: Error | null, zipfile: yauzl.ZipFile) => void, + ) => yauzl.open(file, {lazyEntries: true}, callback), + ); + + const clangdPath = await open(zipFile).then((zip) => + new Promise((resolve, reject) => { + zip.on('entry', (entry: yauzl.Entry) => { + if (path.basename(entry.fileName) === clangdFilename) { + return resolve(path.join(extractRoot, entry.fileName)); + } + zip.readEntry(); + }); + + zip.on('error', reject); + + zip.on('end', () => { + reject(new Error(`Didn't find ${clangdFilename} in ${zipFile}`)); + }); + + // Start reading. + zip.readEntry(); + }).finally(() => zip.close()), + ); + + await fs.promises.mkdir(extractRoot); + await ui.slow( ui.localize('Extracting {0}', asset.name), - new Promise((resolve) => { - zip.extractAllToAsync(extractRoot, true, false, resolve); - }), + open(zipFile).then((zip) => + new Promise((resolve, reject) => { + zip.on('entry', (entry: yauzl.Entry) => { + const entryPath = path.join(extractRoot, entry.fileName); + + (entry.fileName.endsWith('/') + ? fs.promises.mkdir(entryPath) + : promisify(zip.openReadStream.bind(zip))(entry).then( + (readStream) => + stream.promises.pipeline( + readStream, + fs.createWriteStream(entryPath), + ), + ) + ) + .then(() => zip.readEntry()) + .catch(reject); + }); + + zip.on('error', reject); + + zip.on('end', resolve); + + // Start reading. + zip.readEntry(); + }).finally(() => zip.close()), + ), ); - const clangdPath = path.join(extractRoot, executable.entryName); + await fs.promises.chmod(clangdPath, 0o755); await fs.promises.unlink(zipFile); return clangdPath;