Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: replace archiver with jszip #489

Merged
merged 3 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"@salesforce/kit": "^3.0.2",
"@salesforce/ts-types": "^2.0.2",
"@types/shelljs": "^0.8.12",
"archiver": "^5.2.0",
"debug": "^4.3.1",
"jszip": "^3.10.1",
"shelljs": "^0.8.4",
"strip-ansi": "6.0.1",
"ts-retry-promise": "^0.7.0"
Expand All @@ -59,7 +59,6 @@
"@salesforce/dev-scripts": "^5.4.2",
"@salesforce/prettier-config": "^0.0.3",
"@salesforce/ts-sinon": "^1.4.6",
"@types/archiver": "^5.3.2",
"@types/debug": "^4.1.8",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.6",
Expand Down
52 changes: 27 additions & 25 deletions src/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import * as fs from 'fs';
import * as path from 'path';
import { create as createArchive } from 'archiver';
import * as JSZip from 'jszip';
import Debug from 'debug';

export interface ZipDirConfig {
Expand Down Expand Up @@ -36,34 +36,36 @@ export interface ZipDirConfig {
export const zipDir = async (config: ZipDirConfig): Promise<string> => {
const debug = Debug('testkit:zipDir');
const { sourceDir, destDir, name } = config;
const zip = createArchive('zip', { zlib: { level: 3 } });
const zipFilePath = path.join(destDir, name);
const output = fs.createWriteStream(zipFilePath);
const zip = new JSZip();
debug(`Zipping contents of ${sourceDir} to ${zipFilePath}`);

return new Promise((resolve, reject) => {
output.on('close', () => {
debug(`Zip ${zipFilePath} is closed`);
resolve(zipFilePath);
});
output.on('end', () => {
debug(`Zip data has drained for ${zipFilePath}`);
resolve(zipFilePath);
});
zip.on('warning', (err) => {
if (err.code === 'ENOENT') {
debug(`Zip warning for ${zipFilePath}\n${err.message}`);
const zipDirRecursive = (dir: string): void => {
const dirents = fs.readdirSync(dir, { withFileTypes: true });
for (const dirent of dirents) {
const fullPath = path.resolve(dir, dirent.name);
if (dirent.isDirectory()) {
zipDirRecursive(fullPath);
} else {
reject(err);
const relPath = path.relative(sourceDir, fullPath);
// Ensure only posix paths are added to zip files
const relPosixPath = relPath.replace(/\\/g, '/');
zip.file(relPosixPath, fs.createReadStream(fullPath));
}
});
zip.on('error', (err) => {
reject(err);
});
zip.pipe(output);
zip.directory(sourceDir, false);
zip.finalize().catch((err: unknown) => {
debug(`Zip finalize error with: ${(err as Error).message}`);
});
}
};

zipDirRecursive(sourceDir);

const zipBuf = await zip.generateAsync({
type: 'nodebuffer',
compression: 'DEFLATE',
compressionOptions: { level: 3 },
});

fs.writeFileSync(zipFilePath, zipBuf);

debug('Zip file written');

return zipFilePath;
};
74 changes: 35 additions & 39 deletions test/unit/zip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,48 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import * as fs from 'fs';
import { join as pathJoin } from 'path';
import { EventEmitter } from 'events';
import { assert, expect } from 'chai';
import { stubMethod } from '@salesforce/ts-sinon';
import * as sinon from 'sinon';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { expect } from 'chai';
import * as JSZip from 'jszip';
import { zipDir } from '../../src/zip';

describe('zipDir', () => {
const sandbox = sinon.createSandbox();

class WriteStreamMock extends EventEmitter {
public write = sandbox.stub().returns(true);
public end = sandbox.stub().callsFake(() => {
this.emit('end');
});
}

afterEach(() => {
sandbox.restore();
});

it('should zip a directory', async () => {
stubMethod(sandbox, fs, 'createWriteStream').returns(new WriteStreamMock());
const zipName = 'zipTest1.zip';
const zipPath = await zipDir({
sourceDir: pathJoin(process.cwd(), 'test', 'unit'),
destDir: process.cwd(),
name: zipName,
});
expect(zipPath).to.equal(pathJoin(process.cwd(), zipName));
});

it('should fail on error', async () => {
stubMethod(sandbox, fs, 'createWriteStream').returns(new WriteStreamMock());
const zipName = 'zipTest2.zip';

const rootDir = join(tmpdir(), 'testkitZipTest');
if (fs.existsSync(rootDir)) {
fs.rmSync(rootDir, { recursive: true, force: true });
}
const sourceDir = join(rootDir, 'sourceDir');
const nestedDir = join(sourceDir, 'nestedDir');
const filePath1 = join(sourceDir, 'file1.txt');
const filePath2 = join(nestedDir, 'file2.txt');
let zipPath = '';
try {
await zipDir({
sourceDir: '',
destDir: process.cwd(),
fs.mkdirSync(nestedDir, { recursive: true });
fs.writeFileSync(filePath1, 'file 1 content');
fs.writeFileSync(filePath2, 'file 2 content');
const zipName = 'myZip.zip';
const expectedZipPath = join(rootDir, zipName);

zipPath = await zipDir({
sourceDir,
destDir: rootDir,
name: zipName,
});
assert(false, 'Expected zipDir() to throw an error');
} catch (e) {
const errMsg = 'diretory dirpath argument must be a non-empty string value';
expect(e).to.have.property('message', errMsg);

expect(fs.existsSync(expectedZipPath)).to.equal(true);
expect(fs.statSync(expectedZipPath).size).to.be.greaterThan(0);
expect(zipPath).to.equal(expectedZipPath);

// read the zip to ensure it has the expected files
const jsZip = new JSZip();
const zip = await jsZip.loadAsync(fs.readFileSync(zipPath));
expect(zip.files).to.haveOwnProperty('file1.txt');
expect(zip.files).to.haveOwnProperty('nestedDir/');
expect(zip.files).to.haveOwnProperty('nestedDir/file2.txt');
} finally {
fs.rmSync(rootDir, { recursive: true, force: true });
}
});
});
Loading