From 9b41af215c34a1dbd2dc012411773596b50997a5 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 24 Oct 2023 09:58:24 -0600 Subject: [PATCH] fix: read directory with resolved path --- src/resolve/treeContainers.ts | 33 +++++++++++++++++++++-------- test/resolve/treeContainers.test.ts | 15 ++++++++----- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/resolve/treeContainers.ts b/src/resolve/treeContainers.ts index 54f5bb0a90..18d400e568 100644 --- a/src/resolve/treeContainers.ts +++ b/src/resolve/treeContainers.ts @@ -143,17 +143,17 @@ export class ZipTreeContainer extends TreeContainer { public isDirectory(fsPath: string): boolean { const resolvedPath = this.match(fsPath); if (resolvedPath) { - // JSZip can have directory entries or only file entries (with virtual directory entries) - const zipObj = this.zip.file(resolvedPath); - return zipObj?.dir === true || !zipObj; + return this.ensureDirectory(resolvedPath); } throw new SfError(messages.getMessage('error_path_not_found', [fsPath]), 'LibraryError'); } public readDirectory(fsPath: string): string[] { - if (this.isDirectory(fsPath)) { - // remove trailing path sep if it exists - const dirPath = fsPath.endsWith(sep) ? fsPath.slice(0, -1) : fsPath; + const resolvedPath = this.match(fsPath); + if (resolvedPath && this.ensureDirectory(resolvedPath)) { + // Remove trailing path sep if it exists. JSZip always adds them for directories but + // when comparing we call `dirname()` which does not include them. + const dirPath = resolvedPath.endsWith('/') ? resolvedPath.slice(0, -1) : resolvedPath; return Object.keys(this.zip.files) .filter((filePath) => dirname(filePath) === dirPath) .map((filePath) => basename(filePath)); @@ -194,14 +194,29 @@ export class ZipTreeContainer extends TreeContainer { // Note that zip files always use forward slash separators, so paths within the // zip files are normalized for the OS file system before comparing. private match(fsPath: string): string | undefined { + // "dot" has a special meaning as a directory name and always matches. Just return it. + if (fsPath === '.') { + return fsPath; + } + const fsPathBasename = basename(fsPath); const fsPathDirname = dirname(fsPath); - return Object.keys(this.zip.files).find((f) => { - if (normalize(basename(f)) === fsPathBasename || fsPath === '.') { - return normalize(dirname(f)) === fsPathDirname; + return Object.keys(this.zip.files).find((filePath) => { + const normFilePath = normalize(filePath); + if (basename(normFilePath) === fsPathBasename) { + return dirname(normFilePath) === fsPathDirname; } }); } + + private ensureDirectory(dirPath: string): boolean { + if (dirPath) { + // JSZip can have directory entries or only file entries (with virtual directory entries) + const zipObj = this.zip.file(dirPath); + return zipObj?.dir === true || !zipObj; + } + throw new SfError(messages.getMessage('error_path_not_found', [dirPath]), 'LibraryError'); + } } /** diff --git a/test/resolve/treeContainers.test.ts b/test/resolve/treeContainers.test.ts index 0647cc1301..76a93d897c 100644 --- a/test/resolve/treeContainers.test.ts +++ b/test/resolve/treeContainers.test.ts @@ -138,15 +138,20 @@ describe('Tree Containers', () => { let tree: ZipTreeContainer; let zipBuffer: Buffer; - const filesRoot = join('.', 'main', 'default'); - const moreFiles = join(filesRoot, 'morefiles'); + // + // NOTE: All files in zips use a forward slash as a file separator, so we build + // the zip using paths with hard-coded forward slashes, not OS specific seps. + // + + const filesRoot = 'main/default'; + const moreFiles = `${filesRoot}/morefiles`; before(async () => { const zip = new JSZip(); zip - ?.file(join(filesRoot, 'test.txt'), 'test text') - ?.file(join(filesRoot, 'test2.txt'), 'test text 2') - ?.file(join(moreFiles, 'test3.txt'), 'test text 3'); + ?.file(`${filesRoot}/test.txt`, 'test text') + ?.file(`${filesRoot}/test2.txt`, 'test text 2') + ?.file(`${moreFiles}/test3.txt`, 'test text 3'); zipBuffer = await zip.generateAsync({ type: 'nodebuffer',