vsix-utils
Version:
utilities for working with VSIX packages
128 lines (125 loc) • 3.83 kB
JavaScript
import { isInMemoryFile } from './chunk-M7VDGRNO.mjs';
import { Buffer } from 'node:buffer';
import { existsSync, createWriteStream } from 'node:fs';
import { unlink, constants } from 'node:fs/promises';
import { XMLParser } from 'fast-xml-parser';
import yauzl from 'yauzl';
import yazl from 'yazl';
async function writeVsix(options) {
const { files, packagePath, force, epoch } = options;
if (!files || files.length === 0) {
throw new Error("no files specified to package");
}
if (!packagePath) {
throw new Error("no package path specified");
}
if (existsSync(packagePath)) {
if (!force) {
throw new Error(`package already exists at ${packagePath}`);
}
await unlink(packagePath);
}
const zip = new yazl.ZipFile();
const zipOptions = {};
if (epoch != null) {
zipOptions.mtime = new Date(epoch * 1e3);
files.sort((a, b) => a.path.localeCompare(b.path));
}
try {
for (const f of files) {
if (isInMemoryFile(f)) {
zip.addBuffer(
typeof f.contents === "string" ? Buffer.from(f.contents, "utf8") : f.contents,
f.path,
{ ...zipOptions }
);
continue;
}
zip.addFile(f.localPath, f.path, { ...zipOptions });
}
zip.end();
const zipStream = createWriteStream(packagePath, {
mode: constants.S_IRWXU | constants.S_IRGRP | constants.S_IXGRP | constants.S_IROTH | constants.S_IXOTH
});
zip.outputStream.pipe(zipStream);
await new Promise((resolve, reject) => {
zipStream.once("finish", resolve);
zipStream.once("error", reject);
zip.once("error", (err) => {
zipStream.destroy();
reject(err);
});
});
return true;
} catch (err) {
if (existsSync(packagePath)) {
await unlink(packagePath);
}
if (err instanceof Error) {
throw new TypeError(`failed to create package: ${err.message}`);
}
throw new Error(`failed to create package: ${err}`);
}
}
async function readVsix(options) {
const { packagePath } = options;
const zip = await new Promise((resolve, reject) => {
yauzl.open(packagePath, { lazyEntries: true }, (err, zip2) => {
if (err) {
reject(err);
} else {
resolve(zip2);
}
});
});
const files = await new Promise((resolve, reject) => {
const fileMap = /* @__PURE__ */ new Map();
zip.once("close", () => resolve(fileMap));
zip.on("entry", async (entry) => {
try {
const buffer = await new Promise((resolve2, reject2) => {
zip.openReadStream(entry, (err, stream) => {
if (err) reject2(err);
stream.once("error", reject2);
stream.once("end", () => {
if (stream.readable) {
reject2(new Error("Stream ended before all data was read"));
}
});
return resolve2(bufferStream(stream));
});
});
fileMap.set(entry.fileName, buffer);
zip.readEntry();
} catch (err) {
zip.close();
reject(err);
}
});
zip.readEntry();
});
if (!files.has("extension.vsixmanifest")) {
throw new Error("extension.vsixmanifest file is missing");
}
const rawManifest = files.get("extension.vsixmanifest").toString("utf8");
const parser = new XMLParser({
preserveOrder: false,
transformTagName(tagName) {
return tagName.toLowerCase();
}
});
const manifest = parser.parse(rawManifest);
return {
files,
manifest
};
}
async function bufferStream(stream) {
return await new Promise((resolve, reject) => {
const buffers = [];
stream.on("data", (buffer) => buffers.push(buffer));
stream.once("error", reject);
stream.once("end", () => resolve(Buffer.concat(buffers)));
});
}
export { readVsix, writeVsix };