Repository: netlify/zip-it-and-ship-it
Pull request #132 (2020-06-22 13:25:34)

Return list of files about to be bundled in `listFunctions()`
`listFunctions()` returns the list of Netlify Functions main files.

However, in some cases, it is also useful to return the list of files required by those main files. For example, `netlify-plugin-inline-functions-env` [inlines those](https://github.com/bencao/netlify-plugin-inline-functions-env/pull/6) using environment variables.

This PR implements this, making `listFunctions()` return those as `srcFiles`.


Patch:
diff --git a/src/dependencies.js b/src/dependencies.js
index a688cf3..fb566ea 100644
--- a/src/dependencies.js
+++ b/src/dependencies.js
@@ -1,6 +1,7 @@
-const { dirname } = require('path')
+const { dirname, basename, normalize } = require('path')
 
 const glob = require('glob')
+const { not: notJunk } = require('junk')
 const pkgDir = require('pkg-dir')
 const precinct = require('precinct')
 const requirePackageName = require('require-package-name')
@@ -10,6 +11,35 @@ const { resolvePathPreserveSymlinks, resolvePackage } = require('./resolve')
 
 const pGlob = promisify(glob)
 
+// Retrieve the paths to the Node.js files to zip.
+// We only include the files actually needed by the function because AWS Lambda
+// has a size limit for the zipped file. It also makes cold starts faster.
+const listNodeFiles = async function(srcPath, filename, mainFile, srcDir, stat) {
+  const [treeFiles, depFiles] = await Promise.all([getTreeFiles(srcPath, stat), getDependencies(mainFile, srcDir)])
+  const files = [...treeFiles, ...depFiles].map(normalize)
+  const uniqueFiles = [...new Set(files)]
+  const filteredFiles = uniqueFiles.filter(isNotJunk)
+  return filteredFiles
+}
+
+// When using a directory, we include all its descendants except `node_modules`
+const getTreeFiles = function(srcPath, stat) {
+  if (!stat.isDirectory()) {
+    return [srcPath]
+  }
+
+  return pGlob(`${srcPath}/**`, {
+    ignore: `${srcPath}/**/node_modules/**`,
+    nodir: true,
+    absolute: true
+  })
+}
+
+// Remove temporary files like *~, *.swp, etc.
+const isNotJunk = function(file) {
+  return notJunk(basename(file))
+}
+
 // Retrieve all the files recursively required by a Node.js file
 const getDependencies = async function(mainFile, srcDir) {
   const packageRoot = await pkgDir(srcDir)
@@ -245,4 +275,4 @@ const isOptionalModule = function(
   )
 }
 
-module.exports = { getDependencies }
+module.exports = { listNodeFiles }
diff --git a/src/fixtures/list/two/three.js b/src/fixtures/list/two/three.js
new file mode 100644
index 0000000..33c1891
--- /dev/null
+++ b/src/fixtures/list/two/three.js
@@ -0,0 +1,1 @@
+module.exports = true
diff --git a/src/fixtures/list/two/two.js b/src/fixtures/list/two/two.js
index 33c1891..e973b7f 100644
--- a/src/fixtures/list/two/two.js
+++ b/src/fixtures/list/two/two.js
@@ -1,1 +1,1 @@
-module.exports = true
+module.exports = require('./three')
diff --git a/src/main.js b/src/main.js
index ea33027..0f15f59 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,6 +7,7 @@ const makeDir = require('make-dir')
 const pMap = require('p-map')
 const promisify = require('util.promisify')
 
+const { listNodeFiles } = require('./dependencies')
 const { isGoExe, zipGoExe } = require('./go')
 const { zipNodeJs } = require('./node')
 
@@ -26,7 +27,7 @@ const zipFunctions = async function(srcFolder, destFolder, { parallelLimit = 5,
 }
 
 const zipFunction = async function(srcPath, destFolder, { skipGo = true, zipGo = !skipGo } = {}) {
-  const { runtime, filename, extension, srcDir, stat, mainFile } = await getFunctionInfo(srcPath)
+  const { runtime, filename, extension, stat, mainFile, srcFiles } = await getFunctionInfo(srcPath)
 
   if (runtime === undefined) {
     return
@@ -41,7 +42,7 @@ const zipFunction = async function(srcPath, destFolder, { skipGo = true, zipGo =
       return { path: destPath, runtime }
     } else {
       const destPath = join(destFolder, `${basename(filename, '.js')}.zip`)
-      await zipNodeJs(srcPath, srcDir, destPath, filename, mainFile, stat)
+      await zipNodeJs(srcFiles, destPath, filename, mainFile)
       return { path: destPath, runtime }
     }
   }
@@ -87,12 +88,17 @@ const getFunctionInfo = async function(srcPath) {
     return {}
   }
 
-  if (extension === '.zip' || extension === '.js') {
-    return { runtime: 'js', filename, stat, mainFile, extension, srcDir }
+  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 (await isGoExe(srcPath)) {
-    return { runtime: 'go', filename, stat, mainFile, extension, srcDir }
+    return { runtime: 'go', filename, stat, mainFile, extension, srcFiles: [srcPath] }
   }
 
   return {}
@@ -129,8 +135,8 @@ const hasMainFile = function({ mainFile }) {
   return mainFile !== undefined
 }
 
-const getListedFunction = function({ mainFile, runtime, extension }) {
-  return { mainFile, runtime, extension }
+const getListedFunction = function({ mainFile, srcFiles, runtime, extension }) {
+  return { mainFile, srcFiles, runtime, extension }
 }
 
 module.exports = { zipFunctions, zipFunction, listFunctions }
diff --git a/src/node.js b/src/node.js
index 7f34a28..7322721 100644
--- a/src/node.js
+++ b/src/node.js
@@ -1,63 +1,28 @@
 const { stat } = require('fs')
-const { dirname, basename, normalize, sep } = require('path')
+const { dirname, normalize, sep } = require('path')
 
 const commonPathPrefix = require('common-path-prefix')
-const glob = require('glob')
-const { not: notJunk } = require('junk')
 const unixify = require('unixify')
 const promisify = require('util.promisify')
 
 const { startZip, addZipFile, addZipContent, endZip } = require('./archive')
-const { getDependencies } = require('./dependencies')
 
-const pGlob = promisify(glob)
 const pStat = promisify(stat)
 
 // Zip a Node.js function file
-const zipNodeJs = async function(srcPath, srcDir, destPath, filename, mainFile, stat) {
-  const files = await filesForFunctionZip(srcPath, srcDir, filename, mainFile, stat)
-
+const zipNodeJs = async function(srcFiles, destPath, filename, mainFile) {
   const { archive, output } = startZip(destPath)
 
-  const dirnames = files.map(dirname)
+  const dirnames = srcFiles.map(dirname)
   const commonPrefix = commonPathPrefix(dirnames)
 
   addEntryFile(commonPrefix, archive, filename, mainFile)
 
-  await Promise.all(files.map(file => zipJsFile(file, commonPrefix, archive)))
+  await Promise.all(srcFiles.map(file => zipJsFile(file, commonPrefix, archive)))
 
   await endZip(archive, output)
 }
 
-// Retrieve the paths to the files to zip.
-// We only include the files actually needed by the function because AWS Lambda
-// has a size limit for the zipped file. It also makes cold starts faster.
-const filesForFunctionZip = async function(srcPath, srcDir, filename, mainFile, stat) {
-  const [treeFiles, depFiles] = await Promise.all([getTreeFiles(srcPath, stat), getDependencies(mainFile, srcDir)])
-  const files = [...treeFiles, ...depFiles].map(normalize)
-  const uniqueFiles = [...new Set(files)]
-  const filteredFiles = uniqueFiles.filter(isNotJunk)
-  return filteredFiles
-}
-
-// When using a directory, we include all its descendants except `node_modules`
-const getTreeFiles = function(srcPath, stat) {
-  if (!stat.isDirectory()) {
-    return [srcPath]
-  }
-
-  return pGlob(`${srcPath}/**`, {
-    ignore: `${srcPath}/**/node_modules/**`,
-    nodir: true,
-    absolute: true
-  })
-}
-
-// Remove temporary files like *~, *.swp, etc.
-const isNotJunk = function(file) {
-  return notJunk(basename(file))
-}
-
 const addEntryFile = function(commonPrefix, archive, filename, mainFile) {
   const mainPath = normalizeFilePath(mainFile, commonPrefix)
   const content = Buffer.from(`module.exports = require('./${mainPath}')`)


Test patch:
diff --git a/src/main.test.js b/src/main.test.js
index a15bdc3..15c06a0 100644
--- a/src/main.test.js
+++ b/src/main.test.js
@@ -266,9 +266,10 @@ test('Can use zipFunction()', async t => {
   t.is(runtime, 'js')
 })
 
-const normalizeMainFile = function(fixtureDir, { mainFile, runtime, extension }) {
+const normalizeFiles = function(fixtureDir, { mainFile, runtime, extension, srcFiles }) {
   const mainFileA = normalize(`${fixtureDir}/${mainFile}`)
-  return { mainFile: mainFileA, runtime, extension }
+  const srcFilesA = srcFiles.map(file => normalize(`${fixtureDir}/${file}`))
+  return { mainFile: mainFileA, runtime, extension, srcFiles: srcFilesA }
 }
 
 test('Can list function file with listFunctions()', async t => {
@@ -277,11 +278,11 @@ test('Can list function file with listFunctions()', async t => {
   t.deepEqual(
     functions,
     [
-      { 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(normalizeMainFile.bind(null, fixtureDir))
+      { 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'] }
+    ].map(normalizeFiles.bind(null, fixtureDir))
   )
 })