Repository: netlify/zip-it-and-ship-it
Pull request #133 (2020-06-22 19:30:28)

Add `listFunctionsFiles()`
This PR splits `listFunctions()` into two methods:
  - `listFunctions()`, which finds Functions main files
  - `listFunctionsFiles()`, which also finds Functions required files, but is much slower

This allows `listFunctions()` to be much faster when required files are not needed.


Patch:
diff --git a/src/main.js b/src/main.js
index 0f15f59..21e2215 100644
--- a/src/main.js
+++ b/src/main.js
@@ -27,12 +27,14 @@ const zipFunctions = async function(srcFolder, destFolder, { parallelLimit = 5,
 }
 
 const zipFunction = async function(srcPath, destFolder, { skipGo = true, zipGo = !skipGo } = {}) {
-  const { runtime, filename, extension, stat, mainFile, srcFiles } = await getFunctionInfo(srcPath)
+  const { runtime, filename, extension, srcDir, stat, mainFile } = await getFunctionInfo(srcPath)
 
   if (runtime === undefined) {
     return
   }
 
+  const srcFiles = await getSrcFiles({ runtime, filename, stat, mainFile, extension, srcPath, srcDir })
+
   await makeDir(destFolder)
 
   if (runtime === 'js') {
@@ -60,11 +62,25 @@ const zipFunction = async function(srcPath, destFolder, { skipGo = true, zipGo =
   }
 }
 
-// List all Netlify Functions for a specific directory
+// List all Netlify Functions main entry files for a specific directory
 const listFunctions = async function(srcFolder) {
+  const functionInfos = await getFunctionInfos(srcFolder)
+  const listedFunctions = functionInfos.map(getListedFunction)
+  return listedFunctions
+}
+
+// List all Netlify Functions files for a specific directory
+const listFunctionsFiles = async function(srcFolder) {
+  const functionInfos = await getFunctionInfos(srcFolder)
+  const listedFunctionsFiles = await Promise.all(functionInfos.map(getListedFunctionFiles))
+  return [].concat(...listedFunctionsFiles)
+}
+
+const getFunctionInfos = async function(srcFolder) {
   const srcPaths = await getSrcPaths(srcFolder)
   const functionInfos = await Promise.all(srcPaths.map(getFunctionInfo))
-  return functionInfos.filter(hasMainFile).map(getListedFunction)
+  const functionInfosA = functionInfos.filter(hasMainFile)
+  return functionInfosA
 }
 
 const getSrcPaths = async function(srcFolder) {
@@ -88,17 +104,12 @@ const getFunctionInfo = async function(srcPath) {
     return {}
   }
 
-  if (extension === '.zip') {
-    return { runtime: 'js', filename, stat, mainFile, extension, srcFiles: [srcPath] }
-  }
-
-  if (extension === '.js') {
-    const srcFiles = await listNodeFiles(srcPath, filename, mainFile, srcDir, stat)
-    return { runtime: 'js', filename, stat, mainFile, extension, srcFiles }
+  if (extension === '.zip' || extension === '.js') {
+    return { runtime: 'js', filename, stat, mainFile, extension, srcPath, srcDir }
   }
 
   if (await isGoExe(srcPath)) {
-    return { runtime: 'go', filename, stat, mainFile, extension, srcFiles: [srcPath] }
+    return { runtime: 'go', filename, stat, mainFile, extension, srcPath, srcDir }
   }
 
   return {}
@@ -135,8 +146,21 @@ const hasMainFile = function({ mainFile }) {
   return mainFile !== undefined
 }
 
-const getListedFunction = function({ mainFile, srcFiles, runtime, extension }) {
-  return { mainFile, srcFiles, runtime, extension }
+const getListedFunction = function({ runtime, mainFile, extension }) {
+  return { mainFile, runtime, extension }
+}
+
+const getListedFunctionFiles = async function({ runtime, filename, stat, mainFile, extension, srcPath, srcDir }) {
+  const srcFiles = await getSrcFiles({ runtime, filename, stat, mainFile, extension, srcPath, srcDir })
+  return srcFiles.map(srcFile => ({ srcFile, mainFile, runtime, extension }))
+}
+
+const getSrcFiles = function({ runtime, filename, stat, mainFile, extension, srcPath, srcDir }) {
+  if (runtime === 'js' && extension === '.js') {
+    return listNodeFiles(srcPath, filename, mainFile, srcDir, stat)
+  }
+
+  return [srcPath]
 }
 
-module.exports = { zipFunctions, zipFunction, listFunctions }
+module.exports = { zipFunctions, zipFunction, listFunctions, listFunctionsFiles }


Test patch:
diff --git a/src/main.test.js b/src/main.test.js
index 15c06a0..54b1066 100644
--- a/src/main.test.js
+++ b/src/main.test.js
@@ -11,7 +11,7 @@ const pathExists = require('path-exists')
 const { dir: getTmpDir, tmpName } = require('tmp-promise')
 const promisify = require('util.promisify')
 
-const { zipFunction, listFunctions } = require('..')
+const { zipFunction, listFunctions, listFunctionsFiles } = require('..')
 
 const { zipNode, zipFixture, unzipFiles, zipCheckFunctions, FIXTURES_DIR } = require('./helpers/main.js')
 
@@ -266,23 +266,39 @@ test('Can use zipFunction()', async t => {
   t.is(runtime, 'js')
 })
 
-const normalizeFiles = function(fixtureDir, { mainFile, runtime, extension, srcFiles }) {
+const normalizeFiles = function(fixtureDir, { mainFile, runtime, extension, srcFile }) {
   const mainFileA = normalize(`${fixtureDir}/${mainFile}`)
-  const srcFilesA = srcFiles.map(file => normalize(`${fixtureDir}/${file}`))
-  return { mainFile: mainFileA, runtime, extension, srcFiles: srcFilesA }
+  const srcFileA = srcFile === undefined ? {} : { srcFile: normalize(`${fixtureDir}/${srcFile}`) }
+  return { mainFile: mainFileA, runtime, extension, ...srcFileA }
 }
 
-test('Can list function file with listFunctions()', async t => {
+test('Can list function main files with listFunctions()', async t => {
   const fixtureDir = `${FIXTURES_DIR}/list`
   const functions = await listFunctions(fixtureDir)
   t.deepEqual(
     functions,
     [
-      { mainFile: 'one/index.js', runtime: 'js', extension: '.js', srcFiles: ['one/index.js'] },
-      { mainFile: 'test', runtime: 'go', extension: '', srcFiles: ['test'] },
-      { mainFile: 'test.js', runtime: 'js', extension: '.js', srcFiles: ['test.js'] },
-      { mainFile: 'test.zip', runtime: 'js', extension: '.zip', srcFiles: ['test.zip'] },
-      { mainFile: 'two/two.js', runtime: 'js', extension: '.js', srcFiles: ['two/three.js', 'two/two.js'] }
+      { mainFile: 'one/index.js', runtime: 'js', extension: '.js' },
+      { mainFile: 'test', runtime: 'go', extension: '' },
+      { mainFile: 'test.js', runtime: 'js', extension: '.js' },
+      { mainFile: 'test.zip', runtime: 'js', extension: '.zip' },
+      { mainFile: 'two/two.js', runtime: 'js', extension: '.js' }
+    ].map(normalizeFiles.bind(null, fixtureDir))
+  )
+})
+
+test('Can list all function files with listFunctionsFiles()', async t => {
+  const fixtureDir = `${FIXTURES_DIR}/list`
+  const functions = await listFunctionsFiles(fixtureDir)
+  t.deepEqual(
+    functions,
+    [
+      { mainFile: 'one/index.js', runtime: 'js', extension: '.js', srcFile: 'one/index.js' },
+      { mainFile: 'test', runtime: 'go', extension: '', srcFile: 'test' },
+      { mainFile: 'test.js', runtime: 'js', extension: '.js', srcFile: 'test.js' },
+      { mainFile: 'test.zip', runtime: 'js', extension: '.zip', srcFile: 'test.zip' },
+      { mainFile: 'two/two.js', runtime: 'js', extension: '.js', srcFile: 'two/three.js' },
+      { mainFile: 'two/two.js', runtime: 'js', extension: '.js', srcFile: 'two/two.js' }
     ].map(normalizeFiles.bind(null, fixtureDir))
   )
 })