diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 852f195a2..c06111810 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -41,34 +41,39 @@ export const safeUnlink = async (path: string) => { // filenames within those directories, if at least one of the directories // exists. If not, an error is thrown. export const listFunctionsDirectories = async function (srcFolders: string[]) { - const filenamesByDirectory = await Promise.all( - srcFolders.map(async (srcFolder) => { - try { - const filenames = await listFunctionsDirectory(srcFolder) - - return filenames - } catch { - return null - } - }), + const filenamesByDirectory = await Promise.allSettled( + srcFolders.map((srcFolder) => listFunctionsDirectory(srcFolder)), ) - const validDirectories = filenamesByDirectory.filter(nonNullable) + const errorMessages: string[] = [] + const validDirectories = filenamesByDirectory + .map((result) => { + if (result.status === 'rejected') { + // If the error is about `ENOENT` (FileNotFound) then we only throw later if this happens + // for all directories. + if (result.reason instanceof Error && (result.reason as NodeJS.ErrnoException).code === 'ENOENT') { + return null + } + + // In any other error case besides `ENOENT` throw immediately + throw result.reason + } + + return result.value + }) + .filter(nonNullable) if (validDirectories.length === 0) { - throw new Error(`Functions folder does not exist: ${srcFolders.join(', ')}`) + throw new Error(`Functions folders do not exist: ${srcFolders.join(', ')} +${errorMessages.join('\n')}`) } return validDirectories.flat() } -export const listFunctionsDirectory = async function (srcFolder: string) { - try { - const filenames = await fs.readdir(srcFolder) +const listFunctionsDirectory = async function (srcFolder: string) { + const filenames = await fs.readdir(srcFolder) - return filenames.map((name) => join(srcFolder, name)) - } catch { - throw new Error(`Functions folder does not exist: ${srcFolder}`) - } + return filenames.map((name) => join(srcFolder, name)) } export const resolveFunctionsDirectories = (input: string | string[]) => { diff --git a/tests/fixtures/missing-functions-folder/.keep b/tests/fixtures/missing-functions-folder/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/tests/main.js b/tests/main.js index 58a1b5503..ff7dab0f8 100644 --- a/tests/main.js +++ b/tests/main.js @@ -823,7 +823,7 @@ testMany( ['bundler_default', 'bundler_esbuild', 'bundler_esbuild_zisi', 'bundler_default_nft', 'bundler_nft'], async (options, t) => { await t.throwsAsync(zipNode(t, 'does-not-exist', { opts: options }), { - message: /Functions folder does not exist/, + message: /Functions folders do not exist/, }) }, ) @@ -2700,6 +2700,22 @@ test('listFunction includes in-source config declarations', async (t) => { }) }) +test('listFunctionsFiles throws if all function directories do not exist', async (t) => { + const error = await t.throwsAsync( + async () => + await listFunctionsFiles([ + join(FIXTURES_DIR, 'missing-functions-folder', 'functions'), + join(FIXTURES_DIR, 'missing-functions-folder', 'functions2'), + ]), + ) + t.regex(error.message, /Functions folders do not exist: /) +}) + +test('listFunctionsFiles does not hide errors that have nothing todo with folder existents', async (t) => { + const error = await t.throwsAsync(async () => await listFunctionsFiles([true])) + t.notRegex(error.message, /Functions folders do not exist: /) +}) + test('listFunctionsFiles includes in-source config declarations', async (t) => { const functions = await listFunctionsFiles(join(FIXTURES_DIR, 'in-source-config', 'functions'), { parseISC: true,