From 3daca56bf18311e9abe0e854728f730265ed34de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andra=CC=81s=20Pozsgai?= Date: Tue, 19 May 2026 16:29:18 +0200 Subject: [PATCH 1/3] fix: correctly match files with extensions or without any --- shared/packages/api/src/filePath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/packages/api/src/filePath.ts b/shared/packages/api/src/filePath.ts index 9d5d6aa2..3ae5cbcb 100644 --- a/shared/packages/api/src/filePath.ts +++ b/shared/packages/api/src/filePath.ts @@ -64,7 +64,7 @@ export async function resolveFileWithoutExtension(fullPath: string): Promise f.startsWith(base + '.')) + const matches = files.filter((f) => f.startsWith(base + '.') || f === base) if (matches.length === 0) { return { result: 'notFound' } From 4bc20bfc055edca4b2843dfbdf72ed94d53b3c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andra=CC=81s=20Pozsgai?= Date: Tue, 19 May 2026 16:42:33 +0200 Subject: [PATCH 2/3] chore: tests --- .../api/src/__tests__/filePath.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shared/packages/api/src/__tests__/filePath.spec.ts b/shared/packages/api/src/__tests__/filePath.spec.ts index 9d7c707b..9bd84692 100644 --- a/shared/packages/api/src/__tests__/filePath.spec.ts +++ b/shared/packages/api/src/__tests__/filePath.spec.ts @@ -47,6 +47,26 @@ describe('resolveFileWithoutExtension', () => { }) }) + test('returns found when filename has no extension', async () => { + touch('myclip') + const result = await resolveFileWithoutExtension(path.join(tmpDir, 'myclip')) + expect(result).toEqual({ + result: 'found', + fullPath: path.join(tmpDir, 'myclip'), + extension: '', + }) + }) + + test('returns found when path already includes the extension', async () => { + touch('myclip.mp4') + const result = await resolveFileWithoutExtension(path.join(tmpDir, 'myclip.mp4')) + expect(result).toEqual({ + result: 'found', + fullPath: path.join(tmpDir, 'myclip.mp4'), + extension: '', + }) + }) + test('returns multiple when more than one file matches', async () => { touch('myclip.mp4') touch('myclip.mov') @@ -59,6 +79,18 @@ describe('resolveFileWithoutExtension', () => { ) }) + test('returns multiple when both extensionless and extension files exist', async () => { + touch('myclip') + touch('myclip.mp4') + const result = await resolveFileWithoutExtension(path.join(tmpDir, 'myclip')) + expect(result.result).toBe('multiple') + if (result.result !== 'multiple') return + expect(result.matches).toHaveLength(2) + expect(result.matches).toEqual( + expect.arrayContaining([path.join(tmpDir, 'myclip'), path.join(tmpDir, 'myclip.mp4')]) + ) + }) + test('returns notFound when no files match', async () => { touch('other.mp4') const result = await resolveFileWithoutExtension(path.join(tmpDir, 'myclip')) From 0322ec198185232b688cadf420e5489f0055fb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andra=CC=81s=20Pozsgai?= Date: Tue, 19 May 2026 17:26:41 +0200 Subject: [PATCH 3/3] chore: update JSDoc for resolveFileWithoutExtension --- shared/packages/api/src/filePath.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/shared/packages/api/src/filePath.ts b/shared/packages/api/src/filePath.ts index 3ae5cbcb..6307853a 100644 --- a/shared/packages/api/src/filePath.ts +++ b/shared/packages/api/src/filePath.ts @@ -38,7 +38,8 @@ export type FileResolutionResult = /** * Attempts to resolve a file path by matching filenames without extensions. * If the exact path doesn't exist, searches for files that start with the base name - * followed by a dot and any extension. + * followed by a dot and any extension. Exact basename matches (extensionless file, + * or input path that already includes the extension) return `extension: ''`. * * @param fullPath - The full path to the file to resolve * @returns A FileResolutionResult indicating whether the file was found, not found, had multiple matches, or encountered an error @@ -57,6 +58,18 @@ export type FileResolutionResult = * // If both file.mp4 and file.mov exist: * const result = await resolveFileWithoutExtension('/path/to/file') * // Returns: { result: 'multiple', matches: ['/path/to/file.mp4', '/path/to/file.mov'] } + * + * @example + * // If looking for /path/to/file and file (no extension) exists: + * // Returns: { result: 'found', fullPath: '/path/to/file', extension: '' } + * + * @example + * // If looking for /path/to/file.mp4 and file.mp4 exists: + * // Returns: { result: 'found', fullPath: '/path/to/file.mp4', extension: '' } + * + * @example + * // If both file and file.mp4 exist: + * // Returns: { result: 'multiple', matches: ['/path/to/file', '/path/to/file.mp4'] } */ export async function resolveFileWithoutExtension(fullPath: string): Promise { const dir = path.dirname(fullPath)