From 21cb9b1a47173af8cfec0c1bd76afad98cc1ef62 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Mon, 11 Nov 2024 02:28:10 +0100 Subject: [PATCH 1/9] add test for macos and windows --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3948f0c..8a91236 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,10 +11,12 @@ on: jobs: test: name: lint & test - runs-on: ubuntu-latest + strategy: matrix: node-version: [18, 20, 22, lts/*] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: pnpm-setup From 9b9bf88ec4b7a02dddee4c877d89501d3a621746 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Mon, 11 Nov 2024 17:36:06 +0100 Subject: [PATCH 2/9] fix in memory and common --- adapters/in-memory/src/core/adapter.spec.ts | 17 ++- adapters/in-memory/src/core/adapter.ts | 13 +- packages/common/src/path.spec.ts | 156 ++++++++++++-------- packages/common/src/path.ts | 40 ++--- tests/interface-tests/src/adapter.ts | 48 +++--- tests/interface-tests/src/source.ts | 9 +- 6 files changed, 159 insertions(+), 124 deletions(-) diff --git a/adapters/in-memory/src/core/adapter.spec.ts b/adapters/in-memory/src/core/adapter.spec.ts index 8b457f8..90f0f98 100644 --- a/adapters/in-memory/src/core/adapter.spec.ts +++ b/adapters/in-memory/src/core/adapter.spec.ts @@ -1,3 +1,4 @@ +import { normalize, sep } from 'node:path'; import { MemoryDirectory, MEMORY_TYPE, MemoryRoot, MemoryObject, MemoryFile } from '../definitions.js'; import { AlreadyExistsException, NotFoundException } from '../exceptions.js'; import { Adapter } from './adapter.js'; @@ -157,7 +158,7 @@ describe('Memory Adapter internal functions', async () => { const path = 'test/some/cotton/coding/file.txt'; const file = adapter.createObject(path, MEMORY_TYPE.FILE); expect(adapter._storage.content.length).toBe(1); - const parts = path.split('/'); + const parts = path.split(sep); const fileName = parts.pop(); let ref: MemoryDirectory | MemoryRoot = adapter._storage; for(const part of parts) { @@ -208,7 +209,7 @@ describe('Memory Adapter internal functions', async () => { adapter.setTestStorage(); const dir = adapter.getLastPartOfPath('test', MEMORY_TYPE.DIRECTORY); expect(dir).toBe(adapter._storage.content[0]); - const file = adapter.getLastPartOfPath('test/file.txt', MEMORY_TYPE.FILE); + const file = adapter.getLastPartOfPath(normalize('test/file.txt'), MEMORY_TYPE.FILE); expect(file).toBe(adapter._storage.content[0].content[0]); }); @@ -216,29 +217,29 @@ describe('Memory Adapter internal functions', async () => { adapter.setTestStorage(); const dir = adapter.getLastPartOfPath('cotton-coding', MEMORY_TYPE.DIRECTORY); expect(dir).toBe(adapter._storage.content[1]); - const file = adapter.getLastPartOfPath('cotton-coding/sub-dir', MEMORY_TYPE.DIRECTORY); + const file = adapter.getLastPartOfPath(normalize('cotton-coding/sub-dir'), MEMORY_TYPE.DIRECTORY); expect(file).toBe(adapter._storage.content[1].content[0]); }); test('get last part of path (find empty dir)', () => { adapter.setTestStorage(); - const dir = adapter.getLastPartOfPath('cotton-coding/empty-dir/', MEMORY_TYPE.DIRECTORY); + const dir = adapter.getLastPartOfPath(normalize('cotton-coding/empty-dir/'), MEMORY_TYPE.DIRECTORY); expect(dir).toBe(adapter._storage.content[1].content[1]); }); test('get last part of path (not found with type)', () => { adapter.setTestStorage(); - expect(() => adapter.getLastPartOfPath('cotton-coding/sub-dir', MEMORY_TYPE.FILE)).toThrow(NotFoundException); + expect(() => adapter.getLastPartOfPath(normalize('cotton-coding/sub-dir'), MEMORY_TYPE.FILE)).toThrow(NotFoundException); }); test('get last part of path (not found)', () => { adapter.setTestStorage(); - expect(() => adapter.getLastPartOfPath('/cotton-coding/sub-dir/file.txt')).toThrow(NotFoundException); + expect(() => adapter.getLastPartOfPath(normalize('/cotton-coding/sub-dir/file.txt'))).toThrow(NotFoundException); }); - test('Crate object by filename with deep path and existing directories', () => { - const path = 'test/some/cotton/coding/file.txt'; + test('Create object by filename with deep path and existing directories', () => { + const path = normalize('test/some/cotton/coding/file.txt'); adapter.createObject(path, MEMORY_TYPE.FILE); expect(() => adapter.createObject(path, MEMORY_TYPE.FILE)).toThrow(AlreadyExistsException); expect(adapter._storage.content.length).toBe(1); diff --git a/adapters/in-memory/src/core/adapter.ts b/adapters/in-memory/src/core/adapter.ts index 798f018..e1dc92a 100644 --- a/adapters/in-memory/src/core/adapter.ts +++ b/adapters/in-memory/src/core/adapter.ts @@ -5,7 +5,7 @@ import { ObjectDirent } from './object-dirent.js'; import { MEMORY_TYPE, MemoryDirectory, MemoryFile, MemoryObject, MemoryRoot } from '../definitions.js'; import { isMemoryDirectoryAndMatchNamePrepared } from '../utils/validations.js'; import { removePrecedingAndTrailingSlash, splitTailingPath } from '@loom-io/common'; -import { basename, dirname } from 'path'; +import { basename, dirname, sep } from 'node:path'; export class Adapter implements SourceAdapter { protected storage: MemoryRoot; @@ -33,10 +33,10 @@ export class Adapter implements SourceAdapter { protected getLastPartOfPath(path: string | undefined, ref?: MEMORY_TYPE): MemoryObject | MemoryRoot; protected getLastPartOfPath(path: string | undefined, ref?: MEMORY_TYPE): MemoryObject | MemoryRoot { path = path?.trim(); - if(path === undefined || path === '' || path === '/' || path === '.') { + if(path === undefined || path === '' || path === sep || path === '.') { return this.storage; } - const parts = removePrecedingAndTrailingSlash(path).split('/'); + const parts = removePrecedingAndTrailingSlash(path).split(sep); const lastPart = parts.pop(); if(lastPart === undefined) { return this.storage; @@ -96,8 +96,7 @@ export class Adapter implements SourceAdapter { protected createObject(path: string, ref: MEMORY_TYPE.FILE): MemoryFile; protected createObject(path: string, ref: MEMORY_TYPE.DIRECTORY): MemoryDirectory; protected createObject(path: string, ref: MEMORY_TYPE): MemoryObject { - - const parts = removePrecedingAndTrailingSlash(path).split('/'); + const parts = removePrecedingAndTrailingSlash(path).split(sep); try { const lastPart = this.getLastPartOfPath(path); throw new AlreadyExistsException(path, lastPart); @@ -138,7 +137,7 @@ export class Adapter implements SourceAdapter { mkdir(path: string): void { path = path.trim(); - if(path === '/' || path === '') { + if(path === sep || path === '') { return; } try { @@ -214,7 +213,7 @@ export class Adapter implements SourceAdapter { file.mtime = new Date(); } catch (err) { if(err instanceof NotFoundException) { - if(removePrecedingAndTrailingSlash(path).split('/').length === err.depth + 1) { + if(removePrecedingAndTrailingSlash(path).split(sep).length === err.depth + 1) { const [,tail] = splitTailingPath(path); if(tail === undefined) { throw new Error('Invalid path'); // TODO: Create a custom exception diff --git a/packages/common/src/path.spec.ts b/packages/common/src/path.spec.ts index 22ca721..8b65499 100644 --- a/packages/common/src/path.spec.ts +++ b/packages/common/src/path.spec.ts @@ -1,87 +1,119 @@ import { describe, test, expect } from 'vitest'; import { addPrecedingAndTailingSlash, getUniqSegmentsOfPath, getSegmentsOfPath, getPathDepth, removePrecedingAndTrailingSlash, splitTailingPath, removePrecedingSlash, removeTailingSlash } from './path.js'; +import { normalize } from 'node:path'; + describe('path utils', async () => { test('removePrecedingSlash', async () => { - expect(removePrecedingSlash('/test')).toBe('test'); - expect(removePrecedingSlash('test')).toBe('test'); - expect(removePrecedingSlash('/')).toBe(''); - expect(removePrecedingSlash('')).toBe(''); - expect(removePrecedingSlash('/test/some/slashing')).toBe('test/some/slashing'); + + expect(removePrecedingSlash(normalize('/test'))).toBe(normalize('test')) + expect(removePrecedingSlash(normalize('test'))).toBe(normalize('test')) + expect(removePrecedingSlash(normalize('/test/'))).toBe(normalize('test/')) + expect(removePrecedingSlash(normalize('test/'))).toBe(normalize('test/')) + expect(removePrecedingSlash(normalize('/'))).toBe('') + expect(removePrecedingSlash('')).toBe('') + expect(removePrecedingSlash(normalize('/test/some/slashing/'))).toBe(normalize('test/some/slashing/')) }); test('removeTailingSlash', async () => { - expect(removeTailingSlash('/test/')).toBe('/test'); - expect(removeTailingSlash('/test')).toBe('/test'); - expect(removeTailingSlash('test/')).toBe('test'); - expect(removeTailingSlash('test')).toBe('test'); - expect(removeTailingSlash('/')).toBe(''); - expect(removeTailingSlash('')).toBe(''); - expect(removeTailingSlash('/test/tailing/sla_sigs-d/')).toBe('/test/tailing/sla_sigs-d'); + + expect(removeTailingSlash(normalize('/test'))).toBe(normalize('/test')) + expect(removeTailingSlash(normalize('test'))).toBe(normalize('test')) + expect(removeTailingSlash(normalize('/test/'))).toBe(normalize('/test')) + expect(removeTailingSlash(normalize('test/'))).toBe(normalize('test')) + expect(removeTailingSlash(normalize('/'))).toBe('') + expect(removeTailingSlash('')).toBe('') + expect(removeTailingSlash(normalize('/test/some/slashing/'))).toBe(normalize('/test/some/slashing')) + + + }); test('removePresentedAndTrailingSlash', async () => { - expect(removePrecedingAndTrailingSlash('/test/')).toBe('test'); - expect(removePrecedingAndTrailingSlash('/test')).toBe('test'); - expect(removePrecedingAndTrailingSlash('test/')).toBe('test'); - expect(removePrecedingAndTrailingSlash('test')).toBe('test'); - expect(removePrecedingAndTrailingSlash('/')).toBe(''); + + + expect(removePrecedingAndTrailingSlash(normalize('/test/'))).toBe(normalize('test')); + expect(removePrecedingAndTrailingSlash(normalize('/test'))).toBe(normalize('test')); + expect(removePrecedingAndTrailingSlash(normalize('test/'))).toBe(normalize('test')); + expect(removePrecedingAndTrailingSlash(normalize('test'))).toBe(normalize('test')); + expect(removePrecedingAndTrailingSlash(normalize('/'))).toBe(''); expect(removePrecedingAndTrailingSlash('')).toBe(''); - expect(removePrecedingAndTrailingSlash('/test/some/slashing/')).toBe('test/some/slashing'); + expect(removePrecedingAndTrailingSlash(normalize('/test/some/slashing/'))).toBe(normalize('test/some/slashing')); + }); test('addPrecedingAndTailingSlash', async () => { - expect(addPrecedingAndTailingSlash('test')).toBe('/test/'); - expect(addPrecedingAndTailingSlash('/test')).toBe('/test/'); - expect(addPrecedingAndTailingSlash('test/')).toBe('/test/'); - expect(addPrecedingAndTailingSlash('/test/')).toBe('/test/'); - expect(addPrecedingAndTailingSlash('/')).toBe('/'); - expect(addPrecedingAndTailingSlash('')).toBe('/'); + expect(addPrecedingAndTailingSlash(normalize('test'))).toBe(normalize('/test/')); + expect(addPrecedingAndTailingSlash(normalize('/test'))).toBe(normalize('/test/')); + expect(addPrecedingAndTailingSlash(normalize('test/'))).toBe(normalize('/test/')); + expect(addPrecedingAndTailingSlash(normalize('/test/'))).toBe(normalize('/test/')); + expect(addPrecedingAndTailingSlash(normalize('/'))).toBe(normalize('/')); + expect(addPrecedingAndTailingSlash('')).toBe(normalize('/')); + expect(addPrecedingAndTailingSlash(normalize('/test/some/slashing'))).toBe(normalize('/test/some/slashing/')); + }); test('splitTailingPath', async () => { - expect(splitTailingPath('/test/some/slashing/')).toEqual(['/test/some/', 'slashing/']); - expect(splitTailingPath('/test/some/slashing')).toEqual(['/test/some/', 'slashing']); - expect(splitTailingPath('test/some/slashing/')).toEqual(['test/some/', 'slashing/']); - expect(splitTailingPath('test/some/slashing')).toEqual(['test/some/', 'slashing']); - expect(splitTailingPath('/someFile.txt')).toEqual(['/', 'someFile.txt']); - expect(splitTailingPath('someFile.txt')).toEqual(['/', 'someFile.txt']); - expect(splitTailingPath('/')).toEqual(['/', undefined]); - expect(splitTailingPath('')).toEqual(['/', undefined]); + + + + expect(splitTailingPath(normalize('/test/some/slashing/'))).toEqual([normalize('/test/some/'), normalize('slashing/')]); + expect(splitTailingPath(normalize('/test/some/slashing'))).toEqual([normalize('/test/some/'), normalize('slashing')]); + expect(splitTailingPath(normalize('test/some/slashing/'))).toEqual([normalize('test/some/'), normalize('slashing/')]); + expect(splitTailingPath(normalize('test/some/slashing'))).toEqual([normalize('test/some/'), normalize('slashing')]); + expect(splitTailingPath(normalize('/'))).toEqual([normalize('/'), undefined]); + expect(splitTailingPath('')).toEqual([normalize('/'), undefined]); + expect(splitTailingPath(normalize('/someFile.txt'))).toEqual([normalize('/'), normalize('someFile.txt')]); + expect(splitTailingPath(normalize('someFile.txt'))).toEqual([normalize('/'), normalize('someFile.txt')]); + expect(splitTailingPath(normalize('/test/some/slashing/long/with/file.txt'))).toEqual([normalize('/test/some/slashing/long/with/'), normalize('file.txt')]); + + + }); test('getPathDepth', async () => { - expect(getPathDepth('/test/some/slashing/')).toBe(3); - expect(getPathDepth('/test/some/slashing')).toBe(3); - expect(getPathDepth('test/some/slashing/')).toBe(3); - expect(getPathDepth('test/some/slashing')).toBe(3); - expect(getPathDepth('/')).toBe(0); + expect(getPathDepth(normalize('/test/some/slashing/'))).toBe(3); + expect(getPathDepth(normalize('/test/some/slashing'))).toBe(3); + expect(getPathDepth(normalize('test/some/slashing/'))).toBe(3); + expect(getPathDepth(normalize('test/some/slashing'))).toBe(3); + expect(getPathDepth(normalize('/'))).toBe(0); expect(getPathDepth('')).toBe(0); - expect(getPathDepth('/someFile.txt')).toBe(1); - expect(getPathDepth('someFile.txt')).toBe(1); - expect(getPathDepth('/test/some/slashing/long/with/file.txt')).toBe(6); + expect(getPathDepth(normalize('/someFile.txt'))).toBe(1); + expect(getPathDepth(normalize('someFile.txt'))).toBe(1); + expect(getPathDepth(normalize('/test/some/slashing/long/with/file.txt'))).toBe(6); + expect(getPathDepth(normalize('./relative'))).toBe(1); + expect(getPathDepth(normalize('./relative/'))).toBe(1); + expect(getPathDepth(normalize('./relative/cotton'))).toBe(2); + expect(getPathDepth(normalize('./relative/cotton/'))).toBe(2); + expect(getPathDepth(normalize('./relative/cotton/programming'))).toBe(3); + expect(getPathDepth(normalize('./relative/cotton/programming/'))).toBe(3); + expect(getPathDepth(normalize('.hidden'))).toBe(1); + expect(getPathDepth(normalize('.hidden/'))).toBe(1); + expect(getPathDepth(normalize('.hidden/file'))).toBe(2); + expect(getPathDepth(normalize('.hidden/file/'))).toBe(2); + }); test('getSegmentsOfPath', async () => { - expect(getSegmentsOfPath('/test/some/slashing/', 3)).toBe('/test/some/slashing/'); - expect(getSegmentsOfPath('/test/some/slashing', 2)).toBe('/test/some/'); - expect(getSegmentsOfPath('test/some/slashing/', 2)).toBe('test/some/'); - expect(getSegmentsOfPath('test/some/slashing', 3)).toBe('test/some/slashing'); - expect(getSegmentsOfPath('/test/some/slashing/', 1)).toBe('/test/'); - expect(getSegmentsOfPath('/test/some/slashing', 1)).toBe('/test/'); - expect(getSegmentsOfPath('test/some/slashing/', 1)).toBe('test/'); - expect(getSegmentsOfPath('test/some/slashing', 1)).toBe('test/'); - expect(getSegmentsOfPath('/cotton/slashing/', 0)).toBe('/'); - expect(getSegmentsOfPath('/test/some/slashing', 0)).toBe('/'); - expect(getSegmentsOfPath('test/some/slashing/', 0)).toBe('/'); - expect(getSegmentsOfPath('test/some/slashing', 0)).toBe('/'); - expect(getSegmentsOfPath('../../cotton-going-up/loop', 1)).toBe('../'); - expect(getSegmentsOfPath('./relative/', 1)).toBe('./relative/'); - expect(getSegmentsOfPath('./relative/cotton', 1)).toBe('./relative/'); - expect(getSegmentsOfPath('./relative/cotton', 2)).toBe('./relative/cotton'); - expect(getSegmentsOfPath('.hidden', 1)).toBe('.hidden'); + expect(getSegmentsOfPath(normalize('/test/some/slashing/'), 3)).toBe(normalize('/test/some/slashing/')); + expect(getSegmentsOfPath(normalize('/test/some/slashing'), 2)).toBe(normalize('/test/some/')); + expect(getSegmentsOfPath(normalize('test/some/slashing/'), 2)).toBe(normalize('test/some/')); + expect(getSegmentsOfPath(normalize('test/some/slashing'), 3)).toBe(normalize('test/some/slashing')); + expect(getSegmentsOfPath(normalize('/test/some/slashing/'), 1)).toBe(normalize('/test/')); + expect(getSegmentsOfPath(normalize('/test/some/slashing'), 1)).toBe(normalize('/test/')); + expect(getSegmentsOfPath(normalize('test/some/slashing/'), 1)).toBe(normalize('test/')); + expect(getSegmentsOfPath(normalize('test/some/slashing'), 1)).toBe(normalize('test/')); + expect(getSegmentsOfPath(normalize('/cotton/slashing/'), 0)).toBe(normalize('/')); + expect(getSegmentsOfPath(normalize('/test/some/slashing'), 0)).toBe(normalize('/')); + expect(getSegmentsOfPath(normalize('test/some/slashing/'), 0)).toBe(normalize('/')); + expect(getSegmentsOfPath(normalize('test/some/slashing'), 0)).toBe(normalize('/')); + expect(getSegmentsOfPath(normalize('../../cotton-going-up/loop'), 1)).toBe(normalize('../')); + expect(getSegmentsOfPath(normalize('./relative/'), 1)).toBe(normalize('./relative/')); + expect(getSegmentsOfPath(normalize('./relative/cotton'), 1)).toBe(normalize('./relative/')); + expect(getSegmentsOfPath(normalize('./relative/cotton'), 2)).toBe(normalize('./relative/cotton')); + expect(getSegmentsOfPath(normalize('.hidden'), 1)).toBe(normalize('.hidden')); }); @@ -100,7 +132,7 @@ describe('path utils', async () => { '.hidden/file', '/.hidden/at/root', 'a/long/.with/all/_po-si-ble/&symbols/and/numbers/1234567890', - ]; + ].map(normalize); expect(getUniqSegmentsOfPath(paths, 1)).toEqual([ '/cotton', @@ -112,7 +144,7 @@ describe('path utils', async () => { '.hidden', '/.hidden', 'a' - ]); + ].map(normalize)); expect(getUniqSegmentsOfPath(paths, 2)).toEqual([ '/cotton/wool', @@ -127,7 +159,7 @@ describe('path utils', async () => { '.hidden/file', '/.hidden/at', 'a/long' - ]); + ].map(normalize)); expect(getUniqSegmentsOfPath(paths, 3)).toEqual([ '/cotton/wool/silk', @@ -142,7 +174,7 @@ describe('path utils', async () => { '.hidden/file', '/.hidden/at/root', 'a/long/.with' - ]); + ].map(normalize)); expect(getUniqSegmentsOfPath(paths, 7)).toEqual([ '/cotton/wool/silk', @@ -157,7 +189,7 @@ describe('path utils', async () => { '.hidden/file', '/.hidden/at/root', 'a/long/.with/all/_po-si-ble/&symbols/and' - ]); + ].map(normalize)); }); }); \ No newline at end of file diff --git a/packages/common/src/path.ts b/packages/common/src/path.ts index 694d79e..5d47296 100644 --- a/packages/common/src/path.ts +++ b/packages/common/src/path.ts @@ -1,12 +1,14 @@ +import {sep} from 'node:path' + export function removePrecedingSlash(path: string): string { - if (path.startsWith('/')) { + if (path.startsWith(sep)) { return path.slice(1); } return path; } export function removeTailingSlash(path: string): string { - if (path.endsWith('/')) { + if (path.endsWith(sep)) { return path.slice(0, -1); } return path; @@ -16,15 +18,15 @@ export function removePrecedingAndTrailingSlash(path: string): string { } export function addPrecedingSlash(path: string): string { - if (!path.startsWith('/')) { - return `/${path}`; + if (!path.startsWith(sep)) { + return `${sep}${path}`; } return path; } export function addTailingSlash(path: string): string { - if (!path.endsWith('/')) { - return `${path}/`; + if (!path.endsWith(sep)) { + return `${path}${sep}`; } return path; } @@ -34,18 +36,18 @@ export function addPrecedingAndTailingSlash(path: string): string { } export function splitTailingPath(path: string): [string, string] | [string, undefined] | [string, string]{ - if(path === '' || path === '/') { - return ['/', undefined]; + if(path === '' || path === sep) { + return [sep, undefined]; } - const index = removeTailingSlash(path).lastIndexOf('/'); + const index = removeTailingSlash(path).lastIndexOf(sep); if (index === -1) { - return ['/', path]; + return [sep, path]; } return [path.slice(0, index + 1), path.slice(index + 1)]; } export function getPathDepth(path: string): number { - const elements = removePrecedingAndTrailingSlash(path).split('/'); + const elements = removePrecedingAndTrailingSlash(path).split(sep); if (elements.length === 1 && elements[0] === '') { return 0; } @@ -62,19 +64,19 @@ export function getPathDepth(path: string): number { * @returns */ export function getSegmentsOfPath(path: string, depth: number): string { - const isRelative = path.startsWith('./'); - const firstIsSlash = path.startsWith('/'); - const parts = removePrecedingAndTrailingSlash(path).split('/'); + const isRelative = path.startsWith(`.${sep}`); + const firstIsSlash = path.startsWith(sep); + const parts = removePrecedingAndTrailingSlash(path).split(sep); if (depth === 0) { - return '/'; + return sep; } else if (depth >= parts.length || (isRelative && depth === parts.length - 1)) { return path; } else { if(isRelative) { - return `${parts.slice(0, depth+1).join('/')}/`; + return `${parts.slice(0, depth+1).join(sep)}/`; } - const subPath = parts.slice(0, depth).join('/'); - return `${firstIsSlash ? '/' : ''}${subPath}/`; + const subPath = parts.slice(0, depth).join(sep); + return `${firstIsSlash ? sep : ''}${subPath}${sep}`; } } @@ -89,7 +91,7 @@ export function getSegmentsOfPath(path: string, depth: number): string { */ export function getUniqSegmentsOfPath(paths: string[], depth: number): string[] { return Array.from(paths.reduce((acc, path) => { - path = path.startsWith('./') ? path.slice(2) : path; + path = path.startsWith(`.${sep}`) ? path.slice(2) : path; const first = getSegmentsOfPath(path, depth); acc.add(removeTailingSlash(first)); return acc; diff --git a/tests/interface-tests/src/adapter.ts b/tests/interface-tests/src/adapter.ts index 952f098..3d938a9 100644 --- a/tests/interface-tests/src/adapter.ts +++ b/tests/interface-tests/src/adapter.ts @@ -13,7 +13,7 @@ import { type SourceAdapter, } from "@loom-io/core"; import { faker } from "@faker-js/faker"; -import { basename, dirname, join } from "node:path"; +import { basename, dirname, join, normalize, sep } from "node:path"; import { getUniqSegmentsOfPath, splitTailingPath } from "@loom-io/common"; import { nanoid } from "nanoid"; @@ -108,7 +108,7 @@ export const TestAdapter = ( }; } - describe.concurrent("Adapter", async () => { + describe("Adapter", async () => { if (config?.beforeAll) { beforeAll(config.beforeAll); } @@ -144,10 +144,10 @@ export const TestAdapter = ( }); test("rmdir with file should fail", async () => { - const path = getRandomPath("test/other/mkdir"); + const path = getRandomPath(normalize("test/other/mkdir")); const fileName = faker.system.commonFileName("txt"); await adapter.mkdir(path); - await adapter.writeFile(`${path}/${fileName}`, "test"); + await adapter.writeFile(normalize(`${path}/${fileName}`), "test"); expect(async () => await adapter.rmdir(path)).rejects.toThrow( DirectoryNotEmptyException, ); @@ -163,7 +163,7 @@ export const TestAdapter = ( test("exists with path", async () => { const path = getRandomPath(); await adapter.mkdir(path); - const subPath = path.split("/").slice(0, -1).join("/"); + const subPath = path.split(sep).slice(0, -1).join(sep); expect(await adapter.dirExists(path)).toBe(true); expect(await adapter.dirExists(subPath)).toBe(true); }); @@ -177,20 +177,20 @@ export const TestAdapter = ( }); test("list dir content", async () => { - await adapter.mkdir("list-dir-content/list"); - await adapter.writeFile("list-dir-content/list/file.txt", "test"); - const list = await adapter.readdir("list-dir-content/list/"); + await adapter.mkdir(normalize("list-dir-content/list")); + await adapter.writeFile(normalize("list-dir-content/list/file.txt"), "test"); + const list = await adapter.readdir(normalize("list-dir-content/list/")); expect(list).toHaveLength(1); expect(list[0].name).toEqual("file.txt"); expect(list[0].isFile()).toBe(true); expect(list[0].isDirectory()).toBe(false); - expect(list[0].path).toEqual("/list-dir-content/list/"); + expect(list[0].path).toEqual(normalize("/list-dir-content/list/")); }); test("list dir content with multiple sub directories", async () => { const basePath = getRandomPath("list-dir-content"); await adapter.mkdir(basePath); - const dirs = ["a/cow", "b/ape", "c/human"]; + const dirs = ["a/cow", "b/ape", "c/human"].map(normalize); const dirPromises = dirs.map(async (dir) => { await adapter.mkdir(join(basePath, dir)); }); @@ -210,22 +210,22 @@ export const TestAdapter = ( "cotton-coding", "loom-io", "some", - ]; + ].map(normalize); const files = [ "some/file.txt", "cotton-file.md", "not-ignore-this.yml", "there-is-more.txt", - ]; + ].map(normalize); const firstLevelDirsAndFiles = new Set(); const dirPromises = dirs.map(async (dir) => { - const first = dir.split("/")[0]; + const first = dir.split(sep)[0]; firstLevelDirsAndFiles.add(first); await adapter.mkdir(join(basePath, dir)); }); const filePromises = files.map(async (file) => { - const first = file.split("/")[0]; + const first = file.split(sep)[0]; firstLevelDirsAndFiles.add(first); await adapter.mkdir(dirname(join(basePath, file))); await adapter.writeFile(join(basePath, file), Math.random().toString()); @@ -256,7 +256,7 @@ export const TestAdapter = ( const path = getRandomPath("test/other/mkdir"); const fileName = faker.system.commonFileName("txt"); try { - await adapter.writeFile(`${path}/${fileName}`, "test"); + await adapter.writeFile(normalize(`${path}/${fileName}`), "test"); } catch (error) { expect(error).toBeInstanceOf(PathNotFoundException); } @@ -430,7 +430,7 @@ export const TestAdapter = ( test("Validate DirentObject of file", async () => { const file = await getRandomPath(faker.system.commonFileName("md")); - const path = `${dirname(file)}/`; + const path = normalize(`${dirname(file)}/`); const fileName = basename(file); const content = faker.lorem.words(100); await adapter.mkdir(path); @@ -441,16 +441,16 @@ export const TestAdapter = ( expect(dirent.isFile()).toBe(true); expect(dirent.isDirectory()).toBe(false); expect(dirent.name).toBe(fileName); - expect(dirent.path).toBe(path.startsWith("/") ? path : `/${path}`); + expect(dirent.path).toBe(path.startsWith(sep) ? path : normalize(`/${path}`)); }); describe.sequential("root directory tests", () => { beforeEach(async () => { - await adapter.rmdir("/", { recursive: true }); + await adapter.rmdir(sep, { recursive: true }); }); afterAll(async () => { - await adapter.rmdir("/", { recursive: true }); + await adapter.rmdir(sep, { recursive: true }); }); test.sequential( @@ -461,7 +461,7 @@ export const TestAdapter = ( ); const paths = await createMultipleDirectories(adapter, 50); const amount = getUniqSegmentsOfPath(paths, 1).length; - const read = await adapter.readdir("/"); + const read = await adapter.readdir(sep); expect(read.length).toBe(amount); finish(); }, @@ -481,15 +481,15 @@ export const TestAdapter = ( const finish = beginTest( "list dir content with multiple objects in sub directory", ); - await adapter.mkdir("list-dir-content/list"); - await adapter.writeFile("list-dir-content/list/file.txt", "test"); + await adapter.mkdir(normalize("list-dir-content/list")); + await adapter.writeFile(normalize("list-dir-content/list/file.txt"), "test"); - const list = await adapter.readdir("/"); + const list = await adapter.readdir(sep); expect(list).toHaveLength(1); expect(list[0].name).toEqual("list-dir-content"); expect(list[0].isFile()).toBe(false); expect(list[0].isDirectory()).toBe(true); - expect(list[0].path).toEqual("/"); + expect(list[0].path).toEqual(sep); finish(); }, ); diff --git a/tests/interface-tests/src/source.ts b/tests/interface-tests/src/source.ts index 6da2598..605f34a 100644 --- a/tests/interface-tests/src/source.ts +++ b/tests/interface-tests/src/source.ts @@ -1,6 +1,7 @@ import { expect, describe, test } from 'vitest'; import { LoomSourceAdapter } from '@loom-io/core'; import { Directory, LoomFile } from '@loom-io/core/internal'; +import { normalize } from 'node:path'; @@ -14,15 +15,15 @@ export function testSource(LoomSourceAdapter: LoomSourceAdapter) { }); test('Should return a Directory', async () => { - const result = await LoomSourceAdapter.dir('/some/data'); + const result = await LoomSourceAdapter.dir(normalize('/some/data')); expect(result).toBeInstanceOf(Directory); - expect(result.path).toBe('/some/data'); + expect(result.path).toBe(normalize('/some/data')); }); test('Should return a LoomFile', async () => { - const result = await LoomSourceAdapter.file('/some/data.pdf'); + const result = await LoomSourceAdapter.file(normalize('/some/data.pdf')); expect(result).toBeInstanceOf(LoomFile); - expect(result.path).toBe('/some/data.pdf'); + expect(result.path).toBe(normalize('/some/data.pdf')); }); }); From b1f0d418310395b1a9b0c3a90eb9adce7feaf5fb Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Mon, 11 Nov 2024 20:35:25 +0100 Subject: [PATCH 3/9] solve node-fs tests --- adapters/node-fs/src/core/adapter.ts | 15 ++++++++---- adapters/node-fs/src/core/object-dirent.ts | 3 ++- adapters/node-fs/src/exports/lib.spec.ts | 27 +++++++++++----------- tests/interface-tests/src/adapter.ts | 3 ++- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/adapters/node-fs/src/core/adapter.ts b/adapters/node-fs/src/core/adapter.ts index 44e640d..bb4ecfe 100644 --- a/adapters/node-fs/src/core/adapter.ts +++ b/adapters/node-fs/src/core/adapter.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises'; import type { SourceAdapter, rmdirOptions, ObjectDirentInterface } from '@loom-io/core'; import { DirectoryNotEmptyException, PathNotFoundException } from '@loom-io/core'; import { PathLike } from 'node:fs'; -import { dirname, join, resolve } from 'node:path'; +import { dirname, join, normalize, resolve, sep } from 'node:path'; import { isNodeErrnoExpression } from '../utils/error-handling.js'; import { ObjectDirent } from './object-dirent.js'; export class Adapter implements SourceAdapter { @@ -14,7 +14,7 @@ export class Adapter implements SourceAdapter { rootdir: PathLike = process.cwd(), ) { const fullPath = resolve(rootdir.toString()); - this.rootdir = fullPath.endsWith('/') ? fullPath : `${fullPath}/`; + this.rootdir = fullPath.endsWith(sep) ? fullPath : normalize(`${fullPath}/`); } get raw() { @@ -22,11 +22,15 @@ export class Adapter implements SourceAdapter { } protected getFullPath(path: string): string { + console.log({path, rootdir: this.rootdir}); + if(path.match(/^[A-Za-z]{1}:/) && !['', '\\'].includes(this.rootdir)) { + return join(this.rootdir, path.slice(2)); + } return join(this.rootdir || '', path); } protected getRelativePath(path: string): string { - return path.replace(this.rootdir, '/'); + return path.replace(this.rootdir, sep); } protected async exists(path: string, ref: number): Promise { @@ -50,6 +54,7 @@ export class Adapter implements SourceAdapter { async mkdir(path: string): Promise { const fullPath = this.getFullPath(path); + console.log({fullPath}); await fs.mkdir(fullPath, { recursive: true }); } @@ -65,11 +70,11 @@ export class Adapter implements SourceAdapter { const fullPath = this.getFullPath(path); if(options.recursive || options.force) { await fs.rm(fullPath, options); - if(path === '/' || path === '') { + if(path === sep || path === '') { await fs.mkdir(this.rootdir); } } else { - if(path !== '/' && path !== '') { + if(path !== sep && path !== '') { await fs.rmdir(fullPath); } } diff --git a/adapters/node-fs/src/core/object-dirent.ts b/adapters/node-fs/src/core/object-dirent.ts index cfc3877..e3eddd8 100644 --- a/adapters/node-fs/src/core/object-dirent.ts +++ b/adapters/node-fs/src/core/object-dirent.ts @@ -22,7 +22,8 @@ export class ObjectDirent implements ObjectDirentInterface{ } get path() { - const pathFromRelativeRoot = this._dirent.path.slice(this._rootPath.length); + console.log({rootPath: this._rootPath, dirent: this._dirent}); + const pathFromRelativeRoot = this._dirent.parentPath.slice(this._rootPath.length); if(process.version.startsWith('v18') && pathFromRelativeRoot.endsWith(this.name)) { return addPrecedingAndTailingSlash(pathFromRelativeRoot.slice(0, -this.name.length)); } diff --git a/adapters/node-fs/src/exports/lib.spec.ts b/adapters/node-fs/src/exports/lib.spec.ts index 9a2d61f..bb02ff4 100644 --- a/adapters/node-fs/src/exports/lib.spec.ts +++ b/adapters/node-fs/src/exports/lib.spec.ts @@ -1,5 +1,6 @@ import { describe, test, expect } from "vitest"; import { FilesystemAdapter } from "./lib"; +import { normalize } from "node:path"; describe("FilesystemAdapter", () => { test("should be able to create a new instance", () => { @@ -8,47 +9,47 @@ describe("FilesystemAdapter", () => { }); test("fullPath should return the full path of a file", () => { - const adapter = new FilesystemAdapter("/root/dir"); + const adapter = new FilesystemAdapter(normalize("/root/dir")); const file = adapter.file("file.txt"); - expect(adapter.getFullPath(file)).toBe("/root/dir/file.txt"); + expect(adapter.getFullPath(file)).toBe(normalize("/root/dir/file.txt")); }); test("fullPath should return the full path of a directory", () => { - const adapter = new FilesystemAdapter("/etc/loom"); + const adapter = new FilesystemAdapter(normalize("/etc/loom")); const dir = adapter.dir("dir"); - expect(adapter.getFullPath(dir)).toBe("/etc/loom/dir"); + expect(adapter.getFullPath(dir)).toBe(normalize("/etc/loom/dir")); }); test("fullPath should fail to other adapters", () => { - const adapter = new FilesystemAdapter("/etc/loom"); - const adapter2 = new FilesystemAdapter("/etc/loom"); + const adapter = new FilesystemAdapter(normalize("/etc/loom")); + const adapter2 = new FilesystemAdapter(normalize("/etc/loom")); const dir2 = adapter2.dir("dir"); expect(() => adapter.getFullPath(dir2)).toThrowError(); }); test("fullPath should return the full path also from other adapter if flag is set", () => { - const adapter = new FilesystemAdapter("/etc/loom"); - const adapter2 = new FilesystemAdapter("/etc/loom"); + const adapter = new FilesystemAdapter(normalize("/etc/loom")); + const adapter2 = new FilesystemAdapter(normalize("/etc/loom")); const dir2 = adapter2.dir("dir"); - expect(adapter.getFullPath(dir2, true)).toBe("/etc/loom/dir"); + expect(adapter.getFullPath(dir2, true)).toBe(normalize("/etc/loom/dir")); }); test("fullPath should return the full path of a file (cwd)", () => { const adapter = new FilesystemAdapter(); const file = adapter.file("file.txt"); - expect(adapter.getFullPath(file)).toBe(process.cwd() + "/file.txt"); + expect(adapter.getFullPath(file)).toBe(normalize(process.cwd() + "/file.txt")); }); test("fullPath should return the full path of a file (cwd)", () => { const adapter = new FilesystemAdapter(); - const file = adapter.file("/deep/file.txt"); - expect(adapter.getFullPath(file)).toBe(process.cwd() + "/deep/file.txt"); + const file = adapter.file(normalize("/deep/file.txt")); + expect(adapter.getFullPath(file)).toBe(normalize(process.cwd() + "/deep/file.txt")); }); test("fullPath should return the full path of a directory (cwd)", () => { const adapter = new FilesystemAdapter(); const dir = adapter.dir("dir"); - expect(adapter.getFullPath(dir)).toBe(process.cwd() + "/dir"); + expect(adapter.getFullPath(dir)).toBe(normalize(process.cwd() + "/dir")); }); test("file should return a new LoomFile instance", () => { diff --git a/tests/interface-tests/src/adapter.ts b/tests/interface-tests/src/adapter.ts index 3d938a9..9db1e60 100644 --- a/tests/interface-tests/src/adapter.ts +++ b/tests/interface-tests/src/adapter.ts @@ -441,7 +441,8 @@ export const TestAdapter = ( expect(dirent.isFile()).toBe(true); expect(dirent.isDirectory()).toBe(false); expect(dirent.name).toBe(fileName); - expect(dirent.path).toBe(path.startsWith(sep) ? path : normalize(`/${path}`)); + const pathRef = path.match(/^([A-Za-z]:)?[\\/]/) ? path.slice(2) : path; + expect(dirent.path).toBe(pathRef.startsWith(sep) ? pathRef : normalize(`/${pathRef}`)); }); describe.sequential("root directory tests", () => { From 7194d3539c0e9dc9bec2bebd2283c81af9d1fbc8 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 00:11:52 +0100 Subject: [PATCH 4/9] fix minio --- adapters/minio/src/core/adapter.ts | 83 +++++++++++++----------- adapters/minio/src/core/object-dirent.ts | 3 +- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/adapters/minio/src/core/adapter.ts b/adapters/minio/src/core/adapter.ts index a00a2da..eff5904 100644 --- a/adapters/minio/src/core/adapter.ts +++ b/adapters/minio/src/core/adapter.ts @@ -9,13 +9,10 @@ import { DirectoryNotEmptyException, PathNotFoundException, } from "@loom-io/core"; -import { removePrecedingSlash } from "@loom-io/common"; -import { join } from "path"; +import { removePrecedingSlash, addTailingSlash } from "@loom-io/common"; +import { join, normalize } from "node:path"; import { S3Error } from "minio/dist/esm/errors.mjs"; -function addTailSlash(path: string): string { - return path.endsWith("/") ? path : `${path}/`; -} type AdapterStat = { size: number; @@ -28,7 +25,7 @@ export class Adapter implements SourceAdapter { protected bucket: string, ) {} async deleteFile(path: string): Promise { - await this.s3.removeObject(this.bucket, path); + await this.s3.removeObject(this.bucket, this.translatePath(path)); } get raw() { @@ -39,7 +36,17 @@ export class Adapter implements SourceAdapter { return this.bucket; } + protected translatePath(path: string) { + if(path.match(/[A-Za-z]{1}:/)) { + return path.slice(2).replaceAll('\\', '/'); + } else if (path.includes('\\')) { + return path.replaceAll('\\', '/'); + } + return path; + } + protected async exists(path: string): Promise { + const bucketStream = this.s3.listObjectsV2(this.bucket, path); return new Promise((resolve, reject) => { bucketStream.on("data", () => { @@ -56,11 +63,11 @@ export class Adapter implements SourceAdapter { } async fileExists(path: string): Promise { - return this.exists(path); + return this.exists(this.translatePath(path)); } async dirExists(path: string): Promise { - const pathWithTailSlash = addTailSlash(path); + const pathWithTailSlash = this.translatePath(addTailingSlash(path)); if (path === "/") { return true; } @@ -68,7 +75,7 @@ export class Adapter implements SourceAdapter { } async mkdir(path: string): Promise { - const pathWithTailSlash = removePrecedingSlash(addTailSlash(path)); + const pathWithTailSlash = this.translatePath(removePrecedingSlash(addTailingSlash(path))); if (pathWithTailSlash === "") { return; } @@ -76,15 +83,15 @@ export class Adapter implements SourceAdapter { } protected async rmdirRecursive(bucket: string, path: string): Promise { - path = removePrecedingSlash(addTailSlash(path)); - const objects = await this.s3.listObjectsV2(bucket, path, true); + const pathWithTailSlash = this.translatePath(removePrecedingSlash(addTailingSlash(path))); + const objects = await this.s3.listObjectsV2(bucket, pathWithTailSlash, true); for await (const obj of objects) { await this.s3.removeObject(bucket, obj.name); } } protected async rmdirForce(path: string): Promise { - const pathWithTailSlash = removePrecedingSlash(addTailSlash(path)); + const pathWithTailSlash = this.translatePath(removePrecedingSlash(addTailingSlash(path))); if (pathWithTailSlash === "") { await this.rmdirRecursive(this.bucket, pathWithTailSlash); return; @@ -95,6 +102,7 @@ export class Adapter implements SourceAdapter { } protected async dirHasFiles(path: string): Promise { + path = this.translatePath(path); const pathWithTailSlash = path.endsWith("/") ? path : `${path}/`; const bucketStream = await this.s3.listObjectsV2( this.bucket, @@ -117,8 +125,25 @@ export class Adapter implements SourceAdapter { }); } + async rmdir(path: string, options: rmdirOptions = {}): Promise { + if (options.force) { + await this.rmdirForce(path); + return; + } else if (options.recursive) { + await this.rmdirRecursive(this.bucket, path); + return; + } else { + path = this.translatePath(path); + if (await this.dirHasFiles(path)) { + throw new DirectoryNotEmptyException(path); + } + const pathWithTailSlash = path.endsWith("/") ? path : `${path}/`; + await this.s3.removeObject(this.bucket, pathWithTailSlash); + } + } + async readdir(path: string): Promise { - const pathWithTailSlash = removePrecedingSlash(addTailSlash(path)); + const pathWithTailSlash = this.translatePath(removePrecedingSlash(addTailingSlash(path))); const bucketStream = await this.s3.listObjectsV2( this.bucket, pathWithTailSlash, @@ -144,25 +169,9 @@ export class Adapter implements SourceAdapter { }); }); } - - async rmdir(path: string, options: rmdirOptions = {}): Promise { - if (options.force) { - await this.rmdirForce(path); - return; - } else if (options.recursive) { - await this.rmdirRecursive(this.bucket, path); - return; - } else { - if (await this.dirHasFiles(path)) { - throw new DirectoryNotEmptyException(path); - } - const pathWithTailSlash = path.endsWith("/") ? path : `${path}/`; - await this.s3.removeObject(this.bucket, pathWithTailSlash); - } - } - + async stat(path: string) { - const stat = await this.s3.statObject(this.bucket, path); + const stat = await this.s3.statObject(this.bucket, this.translatePath(path)); return { size: stat.size, mtime: stat.lastModified, @@ -175,7 +184,7 @@ export class Adapter implements SourceAdapter { path: string, encoding?: BufferEncoding, ): Promise { - const stream = await this.s3.getObject(this.bucket, path); + const stream = await this.s3.getObject(this.bucket, this.translatePath(path)); return new Promise((resolve, reject) => { const buffers: Buffer[] = []; stream.on("data", (data) => { @@ -195,14 +204,14 @@ export class Adapter implements SourceAdapter { async writeFile(path: string, data: Buffer | string): Promise { const buffer = Buffer.from(data); - await this.s3.putObject(this.bucket, path, buffer, buffer.length, { + await this.s3.putObject(this.bucket, this.translatePath(path), buffer, buffer.length, { "Content-Type": "text/plain", }); return; } async openFile(path: string): Promise { - return new FileHandler(this, this.bucket, path); + return new FileHandler(this, this.bucket, this.translatePath(path)); } async isCopyable(adapter: SourceAdapter): Promise { @@ -217,15 +226,15 @@ export class Adapter implements SourceAdapter { const conds = new CopyConditions(); await this.s3.copyObject( this.bucket, - dest, - join(this.bucket, src), + this.translatePath(dest), + this.translatePath(join(this.bucket, src)), conds, ); } catch (err) { if (err instanceof S3Error) { if (err.code === "NoSuchKey") { // @ts-expect-error property code does not exist on S3Error - throw new PathNotFoundException(err.key); + throw new PathNotFoundException(normalize(err.key)); } throw err; diff --git a/adapters/minio/src/core/object-dirent.ts b/adapters/minio/src/core/object-dirent.ts index 2855cda..7dcc9ed 100644 --- a/adapters/minio/src/core/object-dirent.ts +++ b/adapters/minio/src/core/object-dirent.ts @@ -1,6 +1,7 @@ import { ObjectDirentInterface } from '@loom-io/core'; import { BucketItem } from 'minio'; import { addPrecedingAndTailingSlash } from '@loom-io/common'; +import { normalize } from 'node:path'; export class ObjectDirent implements ObjectDirentInterface{ @@ -44,7 +45,7 @@ export class ObjectDirent implements ObjectDirentInterface{ get path() { const [path] = this.getPathAndName(); - return addPrecedingAndTailingSlash(path); + return normalize(addPrecedingAndTailingSlash(path)); } } \ No newline at end of file From 501854f77223b1a019557c1a1ad0bb40a83d2922 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 00:15:15 +0100 Subject: [PATCH 5/9] remove logs --- adapters/node-fs/src/core/adapter.ts | 2 -- adapters/node-fs/src/core/object-dirent.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/adapters/node-fs/src/core/adapter.ts b/adapters/node-fs/src/core/adapter.ts index bb4ecfe..9bf74b6 100644 --- a/adapters/node-fs/src/core/adapter.ts +++ b/adapters/node-fs/src/core/adapter.ts @@ -22,7 +22,6 @@ export class Adapter implements SourceAdapter { } protected getFullPath(path: string): string { - console.log({path, rootdir: this.rootdir}); if(path.match(/^[A-Za-z]{1}:/) && !['', '\\'].includes(this.rootdir)) { return join(this.rootdir, path.slice(2)); } @@ -54,7 +53,6 @@ export class Adapter implements SourceAdapter { async mkdir(path: string): Promise { const fullPath = this.getFullPath(path); - console.log({fullPath}); await fs.mkdir(fullPath, { recursive: true }); } diff --git a/adapters/node-fs/src/core/object-dirent.ts b/adapters/node-fs/src/core/object-dirent.ts index e3eddd8..623edfd 100644 --- a/adapters/node-fs/src/core/object-dirent.ts +++ b/adapters/node-fs/src/core/object-dirent.ts @@ -22,7 +22,6 @@ export class ObjectDirent implements ObjectDirentInterface{ } get path() { - console.log({rootPath: this._rootPath, dirent: this._dirent}); const pathFromRelativeRoot = this._dirent.parentPath.slice(this._rootPath.length); if(process.version.startsWith('v18') && pathFromRelativeRoot.endsWith(this.name)) { return addPrecedingAndTailingSlash(pathFromRelativeRoot.slice(0, -this.name.length)); From 7aa7f7cb75e339f288fcf1c05ccfb389d5654371 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 00:58:43 +0100 Subject: [PATCH 6/9] fix core test --- packages/core/src/core/dir.spec.ts | 36 +++++++++++++-------------- packages/core/src/core/dir.ts | 14 +++++------ packages/core/src/core/editor.spec.ts | 12 ++++----- packages/core/src/core/file.spec.ts | 21 ++++++++-------- packages/core/src/core/list.spec.ts | 29 ++++++++++----------- 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/packages/core/src/core/dir.spec.ts b/packages/core/src/core/dir.spec.ts index 6996d97..d9dc570 100644 --- a/packages/core/src/core/dir.spec.ts +++ b/packages/core/src/core/dir.spec.ts @@ -1,6 +1,6 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest"; import { Directory } from "./dir.js"; -import { join as joinPath } from "node:path"; +import { join as joinPath, normalize, sep } from "node:path"; import { InMemoryAdapterHelper } from "@loom-io/test-utils"; import { LoomFile } from "./file.js"; import { DirectoryNotEmptyException } from "../exceptions.js"; @@ -15,18 +15,18 @@ describe("Test Directory Service", () => { }); test("path", () => { - const dir = new Directory(adapter, "./test/data"); - expect(dir.path).toBe("test/data"); + const dir = new Directory(adapter, normalize("./test/data")); + expect(dir.path).toBe(normalize("test/data")); }); test("relativePath", () => { - const dir = new Directory(adapter, "./test/"); - const dir2 = new Directory(adapter, "./test/data"); + const dir = new Directory(adapter, normalize("./test/")); + const dir2 = new Directory(adapter, normalize("./test/data")); expect(dir.relativePath(dir2)).toBe("data"); }); test("exists", async () => { - adapter.mkdir("test/data"); + adapter.mkdir(normalize("test/data")); const dir = new Directory(adapter, "./test/data/"); const exists = await dir.exists(); expect(exists).toBeTruthy(); @@ -41,18 +41,18 @@ describe("Test Directory Service", () => { test("parent", () => { const dir = new Directory(adapter, "./test/data"); expect(dir.parent).toBeInstanceOf(Directory); - expect(dir.parent!.path).toBe("/test"); + expect(dir.parent!.path).toBe(normalize("/test")); }); test("parent of root", () => { - const root = new Directory(adapter, "/"); + const root = new Directory(adapter, sep); expect(root.parent).toBe(undefined); }); test("parent of first level after root", () => { const dir = new Directory(adapter, "/etc"); expect(dir.parent).toBeDefined(); - expect(dir.parent!.path).toBe("/"); + expect(dir.parent!.path).toBe(sep); }); test.each([ @@ -67,7 +67,7 @@ describe("Test Directory Service", () => { }); test("get name of root", () => { - const root = new Directory(adapter, "/"); + const root = new Directory(adapter, sep); expect(root.name).toBe(""); }); @@ -92,7 +92,7 @@ describe("Test Directory Service", () => { }); test("copyTo", async () => { - const basePath = adapterHelper.last!; + const basePath = normalize(adapterHelper.last!); const dir = new Directory(adapter, basePath, "loom"); await dir.create(); await adapterHelper.createDirectory(joinPath(basePath, "loom/cotton")); @@ -194,7 +194,7 @@ describe("Test Directory Service", () => { describe("files method", () => { test("files test returned amount", async () => { await adapterHelper.createMultipleDirectories(); - await adapterHelper.createFile("dorem/tt.txt", "lorem-dorem"); + await adapterHelper.createFile(normalize("dorem/tt.txt"), "lorem-dorem"); await adapterHelper.createFile("tt2.txt", "lorem"); const dir = new Directory(adapter, "/"); @@ -210,12 +210,12 @@ describe("Test Directory Service", () => { test("get files recursive", async () => { await adapterHelper.createMultipleDirectories(); - await adapterHelper.createFile("dorem/tt.txt", "lorem"); + await adapterHelper.createFile(normalize("dorem/tt.txt"), "lorem"); await adapterHelper.createFile( - "random/path/to/cotton/coding/lorem.txt", + normalize("random/path/to/cotton/coding/lorem.txt"), "lorem", ); - await adapterHelper.createFile("more/lorem.txt", "lorem"); + await adapterHelper.createFile(normalize("more/lorem.txt"), "lorem"); const dir = new Directory(adapter, "/"); @@ -233,9 +233,9 @@ describe("Test Directory Service", () => { describe("test symbols", () => { test("toPrimitive", () => { const dir = new Directory(adapter, "/test/data"); - expect(`${dir}`).toBe("/test/data"); - expect(dir + "").toBe("/test/data"); - expect(String(dir)).toBe("/test/data"); + expect(`${dir}`).toBe(normalize("/test/data")); + expect(dir + "").toBe(normalize("/test/data")); + expect(String(dir)).toBe(normalize("/test/data")); expect(+dir).toBeNaN(); }); diff --git a/packages/core/src/core/dir.ts b/packages/core/src/core/dir.ts index d631b9c..825347c 100644 --- a/packages/core/src/core/dir.ts +++ b/packages/core/src/core/dir.ts @@ -1,4 +1,4 @@ -import { join as joinPath, relative as relativePath} from 'node:path'; +import { join as joinPath, normalize, relative as relativePath, sep} from 'node:path'; import { LoomFile } from './file.js'; import { List } from './list.js'; import { SourceAdapter } from '../definitions.js'; @@ -19,7 +19,7 @@ export class Directory { ) { this._path = joinPath(path, ...(paths || [])); this._adapter = adapter; - this.isRoot = this._path === '' || this._path === '/'; + this.isRoot = this._path === '' || this._path === sep; } strict(strictMode: boolean = true) { @@ -36,15 +36,15 @@ export class Directory { return ''; } - const split = removeTailingSlash(this.path).split('/'); + const split = removeTailingSlash(this.path).split(sep); return split.pop()!; } get parent(): Directory | undefined { if(this.isRoot) return undefined; - const split = this.path.split('/'); + const split = this.path.split(sep); split.pop(); - return new Directory(this._adapter, `/${split.join('/')}`); + return new Directory(this._adapter, normalize(`/${split.join('/')}`)); } get adapter() { @@ -89,7 +89,7 @@ export class Directory { } subDir(name: string) { - return new Directory(this._adapter, this.path, name); + return new Directory(this._adapter, this.path, normalize(name)); } async list(): Promise { @@ -109,7 +109,7 @@ export class Directory { file(name: string): LoomFile { - return new LoomFile(this._adapter, this, name); + return new LoomFile(this._adapter, this, normalize(name)); } protected async filesRecursion(list: List): Promise>{ diff --git a/packages/core/src/core/editor.spec.ts b/packages/core/src/core/editor.spec.ts index 0e088e9..4199efd 100644 --- a/packages/core/src/core/editor.spec.ts +++ b/packages/core/src/core/editor.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; import { Editor, Reader } from './editor.js'; -import { dirname, basename } from 'node:path'; +import { dirname, basename, normalize } from 'node:path'; import { Directory } from './dir.js'; import { LoomFile } from './file.js'; @@ -16,9 +16,9 @@ function createEditor(adapter, testFile: string): Promise { return Editor.from(adapter, file); } -const TEST_FILE_PATH = '/test/data/editor.md'; -const TEST_TXT_FILE_PATH = '/test/data/test.txt'; -const TEST_EMPTY_FILE_PATH = '/test/data/empty.txt'; +const TEST_FILE_PATH = normalize('/test/data/editor.md'); +const TEST_TXT_FILE_PATH = normalize('/test/data/test.txt'); +const TEST_EMPTY_FILE_PATH = normalize('/test/data/empty.txt'); class TestEditor extends Editor { @@ -81,7 +81,7 @@ I already started to implement some ideas, but only two had the ability to get s I also have some ideas in my mind, but not sure if they are worth to invest time. I am open to good ideas, co-founders, or only a good Open-Source project. ### EOF`; await testHelper.createFile(TEST_FILE_PATH, testFileContent); - await testHelper.createFile('/test/data/test.txt', 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'); + await testHelper.createFile(normalize('/test/data/test.txt'), 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'); await testHelper.createFile(TEST_EMPTY_FILE_PATH, ''); }); @@ -465,7 +465,7 @@ I also have some ideas in my mind, but not sure if they are worth to invest time test('read file with only one line', async () => { const longText = faker.lorem.words(10000); - testHelper.createFile('/test/data/test_large.txt', longText); + testHelper.createFile(normalize('/test/data/test_large.txt'), longText); const fileContentLength = longText.length; const reader = await createEditor(adapter, '/test/data/test_large.txt'); const resultForward = await reader.firstLine(); diff --git a/packages/core/src/core/file.spec.ts b/packages/core/src/core/file.spec.ts index 677d79c..2053188 100644 --- a/packages/core/src/core/file.spec.ts +++ b/packages/core/src/core/file.spec.ts @@ -6,6 +6,7 @@ import { Directory } from "./dir.js"; import { FILE_SIZE_UNIT } from "../definitions.js"; import { faker } from "@faker-js/faker"; import { Editor } from "./editor.js"; +import { normalize } from "path"; describe("Test File Service", () => { let testHelper: InMemoryAdapterHelper; @@ -13,23 +14,23 @@ describe("Test File Service", () => { beforeEach(async () => { testHelper = await InMemoryAdapterHelper.init(); - await testHelper.createDirectory("test/data"); + await testHelper.createDirectory(normalize("test/data")); await testHelper.createFile( - "test/data/test.json", + normalize("test/data/test.json"), JSON.stringify({ test: true }) ); adapter = testHelper.adapter; }); test("Create Instance and set path", () => { - const path = "test/data/test.txt"; + const path = normalize("test/data/test.txt"); const file = LoomFile.from(adapter, path); expect(file).toBeInstanceOf(LoomFile); expect(file.path).toBe(`${path}`); }); test("Get file size", async () => { - const path = "test/data/test.txt"; + const path = normalize("test/data/test.txt"); const content = faker.lorem.words(10000); const testFilePath = testHelper.createFile(path, content); const file = LoomFile.from(adapter, testFilePath); @@ -47,7 +48,7 @@ describe("Test File Service", () => { }); test("Get file meta data", async () => { - const path = "test/data/test.txt"; + const path = normalize("test/data/test.txt"); const content = faker.lorem.words(10000); const testFilePath = testHelper.createFile(path, content); const file = LoomFile.from(adapter, testFilePath); @@ -73,7 +74,7 @@ describe("Test File Service", () => { test("get parent or dir", () => { const file = LoomFile.from(adapter, "./test/data/test.json"); expect(file.dir).instanceOf(Directory); - expect(file.dir.path).toBe("test/data"); + expect(file.dir.path).toBe(normalize("test/data")); expect(file.dir).toBe(file.parent); }); @@ -122,7 +123,7 @@ describe("Test File Service", () => { test("Read text file", async () => { const contentToWrite = faker.lorem.words(1000); const testFile = testHelper.createFile( - "someTestFile/file.txt", + normalize("someTestFile/file.txt"), contentToWrite ); const file = LoomFile.from(adapter, testFile); @@ -204,9 +205,9 @@ describe("Test File Service", () => { describe("Test Symbol", () => { test("toPrimitive", () => { const file = LoomFile.from(adapter, "./test/data/test.json"); - expect(`${file}`).toBe("test/data/test.json"); - expect(file + "").toBe("test/data/test.json"); - expect(String(file)).toBe("test/data/test.json"); + expect(`${file}`).toBe(normalize("test/data/test.json")); + expect(file + "").toBe(normalize("test/data/test.json")); + expect(String(file)).toBe(normalize("test/data/test.json")); expect(+file).toBeNaN(); }); diff --git a/packages/core/src/core/list.spec.ts b/packages/core/src/core/list.spec.ts index 26ed27e..c48dfc6 100644 --- a/packages/core/src/core/list.spec.ts +++ b/packages/core/src/core/list.spec.ts @@ -4,6 +4,7 @@ import { Directory } from './dir.js'; import { LoomFile } from './file.js'; import { InMemoryAdapterHelper } from '@loom-io/test-utils'; import { getUniqSegmentsOfPath, removePrecedingSlash } from '@loom-io/common'; +import { normalize, sep } from 'node:path'; class RevealedList extends List { @@ -73,15 +74,15 @@ describe('Test List', () => { test('Concat Lists', async () => { - await adapterHelper.createFile('testDir/testFile.txt'); + await adapterHelper.createFile(normalize('testDir/testFile.txt')); const base1 = 'cotton'; const base2 = 'wool'; const testPaths1 = adapterHelper.createMultipleDirectories(7, base1); const testPaths2 = adapterHelper.createMultipleDirectories(13, base2); - const basePaths1 = Array.from(new Set(testPaths1.map((path) => removePrecedingSlash(path).split('/').splice(1, 1)).flat())); - const basePaths2 = Array.from(new Set(testPaths2.map((path) => removePrecedingSlash(path).split('/').splice(1, 1)).flat())); + const basePaths1 = Array.from(new Set(testPaths1.map((path) => removePrecedingSlash(path).split(sep).splice(1, 1)).flat())); + const basePaths2 = Array.from(new Set(testPaths2.map((path) => removePrecedingSlash(path).split(sep).splice(1, 1)).flat())); const dir1 = new Directory(adapter, base1); const dir2 = new Directory(adapter, base2); @@ -120,7 +121,7 @@ describe('Test List', () => { await adapterHelper.createFile('testFile.txt'); // TODO: Handle '/' and './' and '.' - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); const filtered = list.filter((wrap) => wrap.name === 'testDir'); expect(filtered).toBeInstanceOf(List); @@ -131,11 +132,11 @@ describe('Test List', () => { test('filter function with subDir', async () => { const subDirName = 'testDir'; adapterHelper.createDirectory(subDirName); - adapterHelper.createFile(`${subDirName}/some.log`, 'lorem'); + adapterHelper.createFile(normalize(`${subDirName}/some.log`), 'lorem'); adapterHelper.createMultipleDirectories(); adapterHelper.createFile('testFile.txt'); - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); const filtered = list.filter((wrap) => wrap.name === subDirName); expect(filtered).toBeInstanceOf(List); @@ -153,7 +154,7 @@ describe('Test List', () => { await adapterHelper.createMultipleDirectories(); await adapterHelper.createFile('testFile.txt'); - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); const filtered = list.filterByType('isDirectory'); expect(filtered).toBeInstanceOf(List); @@ -173,7 +174,7 @@ describe('Test List', () => { await adapterHelper.createFile(); - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); const filtered = list.filterByType('isFile'); expect(filtered).toBeInstanceOf(List); @@ -206,7 +207,7 @@ describe('Test List', () => { const total = amount + 2; - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); expect(list).toHaveLength(total); @@ -242,7 +243,7 @@ describe('Test List', () => { const total = amount + 2; - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); expect(list).toHaveLength(total); }); @@ -255,7 +256,7 @@ describe('Test List', () => { const total = amount + 2; - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); expect(list).toHaveLength(total); @@ -274,7 +275,7 @@ describe('Test List', () => { const total = amount + 2; - const dir = new Directory(adapter, '/'); + const dir = new Directory(adapter, sep); const list = await dir.list(); expect(list).toHaveLength(total); @@ -289,7 +290,7 @@ describe('Test List', () => { describe('protected functions', () => { test('Add DirentWrapper', async () => { - const dir = new Directory(adapter, './test/data'); + const dir = new Directory(adapter, normalize('./test/data')); const list = await dir.list(); const revealedList = new RevealedList(list); const paths = revealedList.getWraps(); @@ -299,7 +300,7 @@ describe('Test List', () => { }); test('Add List', async () => { - const dir = new Directory(adapter, './test/data'); + const dir = new Directory(adapter, normalize('./test/data')); const list = await dir.list(); const revealedList = new RevealedList(list); const newList = new RevealedList(); From 4604573b51dc14ef65aea045a78fb67fff1f2fb3 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 00:59:06 +0100 Subject: [PATCH 7/9] fix minio adapter with root dir --- adapters/minio/src/core/adapter.ts | 2 +- tests/interface-tests/src/adapter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/minio/src/core/adapter.ts b/adapters/minio/src/core/adapter.ts index eff5904..66f95cc 100644 --- a/adapters/minio/src/core/adapter.ts +++ b/adapters/minio/src/core/adapter.ts @@ -68,7 +68,7 @@ export class Adapter implements SourceAdapter { async dirExists(path: string): Promise { const pathWithTailSlash = this.translatePath(addTailingSlash(path)); - if (path === "/") { + if (pathWithTailSlash === "/") { return true; } return this.exists(pathWithTailSlash); diff --git a/tests/interface-tests/src/adapter.ts b/tests/interface-tests/src/adapter.ts index 9db1e60..a91a621 100644 --- a/tests/interface-tests/src/adapter.ts +++ b/tests/interface-tests/src/adapter.ts @@ -470,7 +470,7 @@ export const TestAdapter = ( ); test.sequential("should handle root slash", async () => { - const path = "/"; + const path = sep; await adapter.mkdir(path); expect(await adapter.dirExists(path)).toBe(true); expect(await adapter.readdir(path)).toHaveLength(0); From 83cdfa2a1c68efc678b722e53188cdb5d9248348 Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 01:02:26 +0100 Subject: [PATCH 8/9] correct regex to match only window path --- tests/interface-tests/src/adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/interface-tests/src/adapter.ts b/tests/interface-tests/src/adapter.ts index a91a621..0dc0b16 100644 --- a/tests/interface-tests/src/adapter.ts +++ b/tests/interface-tests/src/adapter.ts @@ -441,7 +441,7 @@ export const TestAdapter = ( expect(dirent.isFile()).toBe(true); expect(dirent.isDirectory()).toBe(false); expect(dirent.name).toBe(fileName); - const pathRef = path.match(/^([A-Za-z]:)?[\\/]/) ? path.slice(2) : path; + const pathRef = path.match(/^([A-Za-z]{1}:)[\\/]/) ? path.slice(2) : path; expect(dirent.path).toBe(pathRef.startsWith(sep) ? pathRef : normalize(`/${pathRef}`)); }); From 8c57f0a8138fed0a80c6038299723029cb468ece Mon Sep 17 00:00:00 2001 From: Wolfgang Rathgeb Date: Tue, 12 Nov 2024 01:28:28 +0100 Subject: [PATCH 9/9] do not upload code coverage on windows --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8a91236..7122b06 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,7 @@ jobs: - name: Test run: pnpm test:ci - name: Upload coverage reports to Codecov + if: runner.os != 'windows' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }}