Repository: netlify/zip-it-and-ship-it
Pull request #572 (2021-07-05 15:06:41)

feat: add support for multiple src directories to list methods
**- Summary**

https://github.com/netlify/zip-it-and-ship-it/pull/537 added support for multiple source directories to the `zipFunction` and `zipFunctions` functions. This PR adds it to `listFunctions` and `listFunctionsFiles`.

**- Test plan**

Adds new tests.

**- A picture of a cute animal (not mandatory but encouraged)**

![maxresdefault](https://user-images.githubusercontent.com/4162329/124491255-f46e9a00-ddaa-11eb-9adb-31c8994aaf51.jpg)


github-actions[bot]: ## ⏱ Benchmark results

*Comparing with 2a11f56404dfdb6d92881751e9a21ed11f432ec8*

### largeDepsEsbuild: 12.7s
 ⬇️ **0.14% decrease** vs. 2a11f56404dfdb6d92881751e9a21ed11f432ec8
```
^          13.1s           12.9s                                                                          
│  12.2s    ┌──┐   12.6s    ┌──┐                   12.4s   12.4s                           12.5s   12.7s  
│   ┌──┐    |  |    ┌──┐    |  |                    ┌──┐    ┌──┐                            ┌──┐    ┌──┐  
│ ──┼──┼────┼──┼────┼──┼────┼──┼───10.9s────────────┼──┼────┼──┼───────────────────10.9s────┼──┼────|▒▒|──
│   |  |    |  |    |  |    |  |    ┌──┐   10.8s    |  |    |  |   10.5s   10.7s    ┌──┐    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    ┌──┐    |  |    |  |    ┌──┐    ┌──┐    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
└───┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴──>
    T-12    T-11    T-10    T-9     T-8     T-7     T-6     T-5     T-4     T-3     T-2     T-1      T    
```
<details>
<summary>Legend</summary>

- T-30 (2a11f56404dfdb6d92881751e9a21ed11f432ec8): 12.7s
- T-29 (30ec7f6a4e97007c2f5e0c40b85b196884acd10f): 12.2s
- T-28 (49c33d8209130930cdeed3b953ac874047cba15a): 10s
- T-27 (0bc0ff937f2bfc93d930c78ea4afb7ede53e30c1): 12.4s
- T-26 (69142468b5054449c51186dd64f79ec67ea7899b): 13.3s
- T-25 (d5392a0cb21fa9b712f5dd8c9d4703f6b080afbf): 12.6s
- T-24 (000ffff7ecbceeeb73e4b305859db4a1f8ff1e18): 12s
- T-23 (a36adf0bfcb253ec0271e974752f52a29796476e): 12.6s
- T-22 (01ae53482bc80186659d9a214394315f71135aa5): 13.3s
- T-21 (786deef392e4fa94987bd3667f5def20d1f34e5c): 12.4s
- T-20 (4bcecdda6622821f35844b7521b9f37fa1e69ff2): 12.5s
- T-19 (cfa7e98354411a4a0239e429a70bb620f7a8aa4d): 12.2s
- T-18 (1782d559b8179fd6e8785425f0d053470339df01): 9.9s
- T-17 (db05daa3366142f4dbb844397c25ace1af966123): 12.1s
- T-16 (f69dc7bb918004a5c4b06c826c2f7189be8e129e): 14.1s
- T-15 (11e3486bc4f2ecf0f7ea6dd72b40432cd5e48306): 12s
- T-14 (c7b62ca7080b6a1bbe77483fde32e9bcde326855): 10.1s
- T-13 (d7bbd41934ed22e31e1c56d64c1c98f8b1cac86f): 11.4s
- T-12 (c733b46dd3470999d2c2e51d5cfc1e38c087839a): 12.2s
- T-11 (f29156ef5395795ad40e9df9556908b58aec6ae9): 13.1s
- T-10 (459ae63da849a1e0d4a271276e0333936c058e4a): 12.6s
- T-9 (3b2bee45e41518ab4c5695de581c2e6202e533b6): 12.9s
- T-8 (3fb86ef73475a197318baf522e16966d35fc485b): 10.9s
- T-7 (f49afa12e3df4054cbc9191446b2d6f19c5deea4): 10.8s
- T-6 (baf9dfb9661d508bf052802101c5078a3ae7b6e5): 12.4s
- T-5 (4768f2fa3afe71f00b62e543291677ed130c92f8): 12.4s
- T-4 (6495b80b9e5886189c7f20a150d8c2ed6cf082b5): 10.5s
- T-3 (9e47927ca83777dd42b27f31d5feb3096d3e8ced): 10.7s
- T-2 (e2ae5ca6d1f640f13789b83e7211fc6a78116958): 10.9s
- T-1 (508721c3c2e3af2d672da86172686aa2e4e0776c): 12.5s
- **T (current commit): 12.7s**</details>
### largeDepsZisi: 1m 4.7s
 ⬇️ **6.26% decrease** vs. 2a11f56404dfdb6d92881751e9a21ed11f432ec8
```
^         1m 11.6s                                                                                        
│           ┌──┐          1m 6.6s                                                                         
│ 1m 3.5s   |  |  1m 4.5s   ┌──┐                  1m 4.8s 1m 4.7s                         1m 3.1s 1m 4.7s 
│   ┌──┐    |  |    ┌──┐    |  |                    ┌──┐    ┌──┐                            ┌──┐    ┌──┐  
│ ──┼──┼────┼──┼────┼──┼────┼──┼───55.7s────────────┼──┼────┼──┼────────────────────────────┼──┼────|▒▒|──
│   |  |    |  |    |  |    |  |    ┌──┐   54.8s    |  |    |  |   54.5s   54.6s   55.4s    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    ┌──┐    |  |    |  |    ┌──┐    ┌──┐    ┌──┐    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
│   |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |  |    |▒▒|  
└───┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴────┴──┴──>
    T-12    T-11    T-10    T-9     T-8     T-7     T-6     T-5     T-4     T-3     T-2     T-1      T    
```
<details>
<summary>Legend</summary>

- T-30 (2a11f56404dfdb6d92881751e9a21ed11f432ec8): 1m 8.7s
- T-29 (30ec7f6a4e97007c2f5e0c40b85b196884acd10f): 1m 1.5s
- T-28 (49c33d8209130930cdeed3b953ac874047cba15a): 52.8s
- T-27 (0bc0ff937f2bfc93d930c78ea4afb7ede53e30c1): 1m 4s
- T-26 (69142468b5054449c51186dd64f79ec67ea7899b): 1m 14.3s
- T-25 (d5392a0cb21fa9b712f5dd8c9d4703f6b080afbf): 1m 5.3s
- T-24 (000ffff7ecbceeeb73e4b305859db4a1f8ff1e18): 1m 3.6s
- T-23 (a36adf0bfcb253ec0271e974752f52a29796476e): 1m 7.1s
- T-22 (01ae53482bc80186659d9a214394315f71135aa5): 1m 8.3s
- T-21 (786deef392e4fa94987bd3667f5def20d1f34e5c): 1m 7.1s
- T-20 (4bcecdda6622821f35844b7521b9f37fa1e69ff2): 1m 3.2s
- T-19 (cfa7e98354411a4a0239e429a70bb620f7a8aa4d): 1m 3.1s
- T-18 (1782d559b8179fd6e8785425f0d053470339df01): 53.7s
- T-17 (db05daa3366142f4dbb844397c25ace1af966123): 1m 5.6s
- T-16 (f69dc7bb918004a5c4b06c826c2f7189be8e129e): 1m 16.4s
- T-15 (11e3486bc4f2ecf0f7ea6dd72b40432cd5e48306): 1m 2.9s
- T-14 (c7b62ca7080b6a1bbe77483fde32e9bcde326855): 53.4s
- T-13 (d7bbd41934ed22e31e1c56d64c1c98f8b1cac86f): 59.9s
- T-12 (c733b46dd3470999d2c2e51d5cfc1e38c087839a): 1m 3.5s
- T-11 (f29156ef5395795ad40e9df9556908b58aec6ae9): 1m 11.6s
- T-10 (459ae63da849a1e0d4a271276e0333936c058e4a): 1m 4.5s
- T-9 (3b2bee45e41518ab4c5695de581c2e6202e533b6): 1m 6.6s
- T-8 (3fb86ef73475a197318baf522e16966d35fc485b): 55.7s
- T-7 (f49afa12e3df4054cbc9191446b2d6f19c5deea4): 54.8s
- T-6 (baf9dfb9661d508bf052802101c5078a3ae7b6e5): 1m 4.8s
- T-5 (4768f2fa3afe71f00b62e543291677ed130c92f8): 1m 4.7s
- T-4 (6495b80b9e5886189c7f20a150d8c2ed6cf082b5): 54.5s
- T-3 (9e47927ca83777dd42b27f31d5feb3096d3e8ced): 54.6s
- T-2 (e2ae5ca6d1f640f13789b83e7211fc6a78116958): 55.4s
- T-1 (508721c3c2e3af2d672da86172686aa2e4e0776c): 1m 3.1s
- **T (current commit): 1m 4.7s**</details>
<!--delta:benchmark@{}-->

Patch:
diff --git a/src/main.js b/src/main.js
index 95e2ead..83bfd30 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,27 +1,27 @@
-const { extname, resolve } = require('path')
+const { extname } = require('path')
 
 require('./utils/polyfills')
 const { getPluginsModulesPath } = require('./node_dependencies')
 const { getFunctionsFromPaths } = require('./runtimes')
-const { listFunctionsDirectory } = require('./utils/fs')
+const { listFunctionsDirectories, resolveFunctionsDirectories } = require('./utils/fs')
 const { zipFunction, zipFunctions } = require('./zip')
 
 // List all Netlify Functions main entry files for a specific directory
-const listFunctions = async function (relativeSrcFolder) {
-  const srcFolder = resolve(relativeSrcFolder)
-  const paths = await listFunctionsDirectory(srcFolder)
+const listFunctions = async function (relativeSrcFolders) {
+  const srcFolders = resolveFunctionsDirectories(relativeSrcFolders)
+  const paths = await listFunctionsDirectories(srcFolders)
   const functions = await getFunctionsFromPaths(paths)
   const listedFunctions = [...functions.values()].map(getListedFunction)
   return listedFunctions
 }
 
 // List all Netlify Functions files for a specific directory
-const listFunctionsFiles = async function (relativeSrcFolder, { config } = {}) {
-  const srcFolder = resolve(relativeSrcFolder)
-  const paths = await listFunctionsDirectory(srcFolder)
+const listFunctionsFiles = async function (relativeSrcFolders, { config } = {}) {
+  const srcFolders = resolveFunctionsDirectories(relativeSrcFolders)
+  const paths = await listFunctionsDirectories(srcFolders)
   const [functions, pluginsModulesPath] = await Promise.all([
     getFunctionsFromPaths(paths, { config }),
-    getPluginsModulesPath(srcFolder),
+    getPluginsModulesPath(srcFolders[0]),
   ])
   const listedFunctionsFiles = await Promise.all(
     [...functions.values()].map((func) => getListedFunctionFiles(func, { pluginsModulesPath })),


Test patch:
diff --git a/tests/main.js b/tests/main.js
index cd6a5e1..92af237 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -537,6 +537,29 @@ test('Can list function main files with listFunctions()', async (t) => {
   )
 })
 
+test('Can list function main files from multiple source directories with listFunctions()', async (t) => {
+  const fixtureDir = `${FIXTURES_DIR}/multiple-src-directories`
+  const functions = await listFunctions([
+    join(fixtureDir, '.netlify', 'internal-functions'),
+    join(fixtureDir, 'netlify', 'functions'),
+  ])
+
+  t.deepEqual(
+    functions,
+    [
+      { name: 'function', mainFile: '.netlify/internal-functions/function.js', runtime: 'js', extension: '.js' },
+      {
+        name: 'function_internal',
+        mainFile: '.netlify/internal-functions/function_internal.js',
+        runtime: 'js',
+        extension: '.js',
+      },
+      { name: 'function', mainFile: 'netlify/functions/function.js', runtime: 'js', extension: '.js' },
+      { name: 'function_user', mainFile: 'netlify/functions/function_user.js', runtime: 'js', extension: '.js' },
+    ].map(normalizeFiles.bind(null, fixtureDir)),
+  )
+})
+
 testBundlers(
   'Can list all function files with listFunctionsFiles()',
   [ESBUILD, ESBUILD_ZISI, DEFAULT],
@@ -576,6 +599,122 @@ testBundlers(
   },
 )
 
+testBundlers(
+  'Can list all function files from multiple source directorires with listFunctionsFiles()',
+  [ESBUILD, ESBUILD_ZISI, DEFAULT],
+  // eslint-disable-next-line complexity
+  async (bundler, t) => {
+    const fixtureDir = `${FIXTURES_DIR}/multiple-src-directories`
+    const functions = await listFunctionsFiles(
+      [join(fixtureDir, '.netlify', 'internal-functions'), join(fixtureDir, 'netlify', 'functions')],
+      { config: { '*': { nodeBundler: bundler } } },
+    )
+
+    t.deepEqual(
+      functions,
+      [
+        {
+          name: 'function',
+          mainFile: '.netlify/internal-functions/function.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: '.netlify/internal-functions/function.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function',
+          mainFile: '.netlify/internal-functions/function.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'node_modules/test/index.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function',
+          mainFile: '.netlify/internal-functions/function.js',
+          runtime: 'js',
+          extension: '.json',
+          srcFile: 'node_modules/test/package.json',
+        },
+
+        {
+          name: 'function_internal',
+          mainFile: '.netlify/internal-functions/function_internal.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: '.netlify/internal-functions/function_internal.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function_internal',
+          mainFile: '.netlify/internal-functions/function_internal.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'node_modules/test/index.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function_internal',
+          mainFile: '.netlify/internal-functions/function_internal.js',
+          runtime: 'js',
+          extension: '.json',
+          srcFile: 'node_modules/test/package.json',
+        },
+
+        {
+          name: 'function',
+          mainFile: 'netlify/functions/function.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'netlify/functions/function.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function',
+          mainFile: 'netlify/functions/function.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'node_modules/test/index.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function',
+          mainFile: 'netlify/functions/function.js',
+          runtime: 'js',
+          extension: '.json',
+          srcFile: 'node_modules/test/package.json',
+        },
+
+        {
+          name: 'function_user',
+          mainFile: 'netlify/functions/function_user.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'netlify/functions/function_user.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function_user',
+          mainFile: 'netlify/functions/function_user.js',
+          runtime: 'js',
+          extension: '.js',
+          srcFile: 'node_modules/test/index.js',
+        },
+
+        bundler === DEFAULT && {
+          name: 'function_user',
+          mainFile: 'netlify/functions/function_user.js',
+          runtime: 'js',
+          extension: '.json',
+          srcFile: 'node_modules/test/package.json',
+        },
+      ]
+        .filter(Boolean)
+        .map(normalizeFiles.bind(null, fixtureDir)),
+    )
+  },
+)
+
 testBundlers('Zips node modules', [DEFAULT], async (bundler, t) => {
   await zipNode(t, 'node-module', { opts: { config: { '*': { nodeBundler: bundler } } } })
 })