diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 2866f1f23..8dde3c934 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [macOS-latest] - node: [8.5.0, 13] + node: [8.3.0, 13] fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/index.js b/index.js deleted file mode 100644 index bead45a87..000000000 --- a/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const { zipFunction, zipFunctions } = require('./src/zip') - -module.exports = { - zipFunction, - zipFunctions -} diff --git a/package-lock.json b/package-lock.json index f6675a3ff..118bf8f5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, "requires": { "@babel/highlight": "^7.0.0" } @@ -414,6 +415,7 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -1165,11 +1167,6 @@ "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==", "dev": true }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" - }, "@typescript-eslint/typescript-estree": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", @@ -1201,7 +1198,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", - "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -2731,8 +2727,7 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "clean-yaml-object": { "version": "0.1.0", @@ -2894,8 +2889,7 @@ "common-path-prefix": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-2.0.0.tgz", - "integrity": "sha512-Lb9qbwwyQdRDmyib0qur7BC9/GHIbviTaQebayFsGC/n77AwFhZINCcJkQx2qVv9LJsA8F5ex65F2qrOfWGUyw==", - "dev": true + "integrity": "sha512-Lb9qbwwyQdRDmyib0qur7BC9/GHIbviTaQebayFsGC/n77AwFhZINCcJkQx2qVv9LJsA8F5ex65F2qrOfWGUyw==" }, "commondir": { "version": "1.0.1", @@ -3064,6 +3058,17 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cp-file": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", + "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + }, "cpy": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/cpy/-/cpy-7.3.0.tgz", @@ -3362,7 +3367,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -3688,6 +3692,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -3696,7 +3701,6 @@ "version": "1.14.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -3714,7 +3718,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -4841,8 +4844,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -5290,7 +5292,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -5320,8 +5321,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-unicode": { "version": "2.0.1", @@ -5379,7 +5379,8 @@ "hosted-git-info": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==" + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true }, "http-cache-semantics": { "version": "4.0.3", @@ -5476,14 +5477,6 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "requires": { - "minimatch": "^3.0.4" - } - }, "import-fresh": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", @@ -5527,8 +5520,7 @@ "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, "indexes-of": { "version": "1.0.1", @@ -5630,7 +5622,8 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -5650,8 +5643,7 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "2.0.0", @@ -5685,8 +5677,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -5853,7 +5844,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -5874,7 +5864,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -6074,7 +6063,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "3.13.1", @@ -6115,7 +6105,8 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true }, "json-schema": { "version": "0.2.3", @@ -6181,9 +6172,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "latest-version": { @@ -6234,11 +6225,6 @@ "integrity": "sha1-xDkrWH3qOFgMlnhXDm6OSfzlJiI=", "dev": true }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6402,7 +6388,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -6410,8 +6395,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -6788,8 +6772,7 @@ "nested-error-stacks": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" }, "nice-try": { "version": "1.0.5", @@ -6832,6 +6815,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -6850,20 +6834,6 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, - "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" - }, - "npm-packlist": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", - "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, "npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -7095,8 +7065,7 @@ "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" }, "object-is": { "version": "1.0.1", @@ -7107,8 +7076,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -7135,7 +7103,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -7252,27 +7219,20 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-all": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-all/-/p-all-2.1.0.tgz", - "integrity": "sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==", - "requires": { - "p-map": "^2.0.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" - } - } - }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", "dev": true }, + "p-event": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", + "requires": { + "p-timeout": "^2.0.1" + } + }, "p-finally": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", @@ -7301,6 +7261,29 @@ "p-limit": "^2.0.0" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "requires": { + "p-finally": "^1.0.0" + }, + "dependencies": { + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + } + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -7597,7 +7580,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, "requires": { "find-up": "^4.0.0" }, @@ -7606,7 +7588,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7616,7 +7597,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -7625,7 +7605,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -7857,77 +7836,6 @@ "path-type": "^3.0.0" } }, - "read-pkg-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.0.tgz", - "integrity": "sha512-t2ODkS/vTTcRlKwZiZsaLGb5iwfx9Urp924aGzVyboU6+7Z2i6eGr/G1Z4mjvwLLQV3uFOBKobNRGM3ux2PD/w==", - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - } - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - } - } - }, "readable-stream": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", @@ -8563,6 +8471,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -8571,12 +8480,14 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -8585,7 +8496,8 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true }, "speedometer": { "version": "1.0.0", @@ -8714,7 +8626,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -8724,7 +8635,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -9394,7 +9304,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, "requires": { "define-properties": "^1.1.2", "object.getownpropertydescriptors": "^2.0.3" @@ -9416,6 +9325,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" diff --git a/package.json b/package.json index a4206efc3..874593086 100644 --- a/package.json +++ b/package.json @@ -24,15 +24,19 @@ }, "dependencies": { "archiver": "^3.0.0", - "debug": "^4.1.1", + "common-path-prefix": "^2.0.0", + "cp-file": "^7.0.0", "elf-tools": "^1.1.1", + "end-of-stream": "^1.4.1", "glob": "^7.1.3", - "npm-packlist": "^1.1.12", - "p-all": "^2.0.0", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "path-exists": "^4.0.0", + "pkg-dir": "^4.2.0", "precinct": "^6.1.1", - "read-pkg-up": "^7.0.0", "require-package-name": "^2.0.1", "resolve": "^1.10.0", + "util.promisify": "^1.0.0", "yargs": "^14.2.0" }, "devDependencies": { @@ -62,11 +66,10 @@ "pkg": "^4.3.7", "prettier": "^1.16.4", "rimraf": "^3.0.0", - "tmp-promise": "^2.0.2", - "util.promisify": "^1.0.0" + "tmp-promise": "^2.0.2" }, "engines": { - "node": ">=8.5.0" + "node": ">=8.3.0" }, "homepage": "https://github.com/netlify/zip-it-and-ship-it#README", "keywords": [ @@ -79,7 +82,7 @@ "static" ], "license": "MIT", - "main": "index.js", + "main": "src/main.js", "pkg": { "targets": [ "node10" @@ -93,7 +96,7 @@ "prepublishOnly": "run-s pkg && git push && git push --tags && gh-release -a build/zip-it-and-ship-it_$(git describe --abbrev=0 --tags)_Linux-64bit.tar.gz,build/zip-it-and-ship-it_$(git describe --abbrev=0 --tags)_macOS-64bit.tar.gz", "test": "run-s test:* test:dev:*", "test-ci": "run-s test:* test:ci:*", - "test:lint": "eslint --ignore-path .gitignore --fix --cache --format=codeframe --max-warnings=0 \"src/**/*.js\" \"*.js\"", + "test:lint": "eslint --ignore-path .gitignore --fix --cache --format=codeframe --max-warnings=0 \"src/**/*.js\"", "test:prettier": "prettier --ignore-path .gitignore --write --loglevel warn \"src/**/*.js\" \"*.{js,md,yml,json}\"", "test:dev:ava": "ava", "test:ci:ava": "nyc -r lcovonly -r text -r json ava", diff --git a/src/archive.js b/src/archive.js new file mode 100644 index 000000000..4dd44d90e --- /dev/null +++ b/src/archive.js @@ -0,0 +1,34 @@ +const { createWriteStream } = require('fs') + +const archiver = require('archiver') +const endOfStream = require('end-of-stream') +const promisify = require('util.promisify') + +const pEndOfStream = promisify(endOfStream) + +// Start zipping files +const startZip = function(destPath) { + const output = createWriteStream(destPath) + const archive = archiver('zip', { level: 9 }) + archive.pipe(output) + return { archive, output } +} + +// Add new file to zip +const addZipFile = function(archive, file, name, stat) { + // Ensure sha256 stability regardless of mtime + archive.file(file, { name, mode: stat.mode, date: new Date(0), stats: stat }) +} + +// Add new file content to zip +const addZipContent = function(archive, content, name) { + archive.append(content, { name, date: new Date(0) }) +} + +// End zipping files +const endZip = async function(archive, output) { + archive.finalize() + await pEndOfStream(output) +} + +module.exports = { startZip, addZipFile, addZipContent, endZip } diff --git a/src/bin.test.js b/src/bin.test.js index 2f5f4c214..232d344e2 100644 --- a/src/bin.test.js +++ b/src/bin.test.js @@ -22,7 +22,7 @@ test('CLI | --help', async t => { t.true(stdout.includes('Options:')) }) -test.skip('CLI | Normal execution', async t => { +test('CLI | Normal execution', async t => { const tmpDir = await tmpName({ prefix: 'zip-it-test' }) const { stdout } = await execa.command(`${BINARY_PATH} ${join(FIXTURES_DIR, 'simple')} ${tmpDir}`) const zipped = JSON.parse(stdout) diff --git a/src/dependencies.js b/src/dependencies.js new file mode 100644 index 000000000..d980d65f6 --- /dev/null +++ b/src/dependencies.js @@ -0,0 +1,213 @@ +const { dirname } = require('path') + +const precinct = require('precinct') +const resolve = require('resolve') +const requirePackageName = require('require-package-name') +const promisify = require('util.promisify') +const glob = require('glob') + +const pResolve = promisify(resolve) +const pGlob = promisify(glob) + +// Retrieve all the files recursively required by a Node.js file +const getDependencies = async function(handler, packageRoot) { + const packageJson = getPackageJson(packageRoot) + + const state = { localFiles: [], modulePaths: [] } + + try { + return await getFileDependencies(handler, packageJson, state) + } catch (error) { + error.message = `In file "${handler}": ${error.message}` + throw error + } +} + +const getPackageJson = function(packageRoot) { + if (packageRoot === undefined) { + return {} + } + + return require(`${packageRoot}/package.json`) +} + +const getFileDependencies = async function(path, packageJson, state) { + if (state.localFiles.includes(path)) { + return [] + } + + state.localFiles.push(path) + + const basedir = dirname(path) + // This parses JavaScript in `path` to retrieve all the `require()` statements + // TODO: `precinct.paperwork()` uses `fs.readFileSync()` under the hood, + // but should use `fs.readFile()` instead + const dependencies = precinct.paperwork(path, { includeCore: false }) + + const depsPaths = await Promise.all( + dependencies.map(dependency => getImportDependencies(dependency, basedir, packageJson, state)) + ) + return [].concat(...depsPaths) +} + +// `require()` statements can be either `require('moduleName')` or +// `require(path)` +const getImportDependencies = function(dependency, basedir, packageJson, state) { + if (LOCAL_IMPORT_REGEXP.test(dependency)) { + return getLocalImportDependencies(dependency, basedir, packageJson, state) + } + + return getModuleDependencies(dependency, basedir, state, packageJson) +} + +const LOCAL_IMPORT_REGEXP = /^(\.|\/)/ + +// When a file requires another one, we apply the top-level logic recursively +const getLocalImportDependencies = async function(dependency, basedir, packageJson, state) { + const dependencyPath = await pResolve(dependency, { basedir }) + const depsPath = await getFileDependencies(dependencyPath, packageJson, state) + return [dependencyPath, ...depsPath] +} + +// When a file requires a module, we find its path inside `node_modules` and +// use all its published files. We also recurse on the module's dependencies. +const getModuleDependencies = async function(dependency, basedir, state, packageJson) { + const moduleName = requirePackageName(dependency.replace(BACKSLASH_REGEXP, '/')) + + try { + return await getModuleNameDependencies(moduleName, basedir, state) + } catch (error) { + return handleModuleNotFound({ error, moduleName, packageJson }) + } +} + +const BACKSLASH_REGEXP = /\\/g + +const getModuleNameDependencies = async function(moduleName, basedir, state) { + if (EXCLUDED_MODULES.includes(moduleName)) { + return [] + } + + // Find the Node.js module directory path + const packagePath = await pResolve(`${moduleName}/package.json`, { basedir }) + const modulePath = dirname(packagePath) + + if (state.modulePaths.includes(modulePath)) { + return [] + } + + state.modulePaths.push(modulePath) + + const pkg = require(packagePath) + + const [publishedFiles, depsPaths] = await Promise.all([ + getPublishedFiles(modulePath), + getNestedModules(modulePath, state, pkg) + ]) + return [...publishedFiles, ...depsPaths] +} + +const EXCLUDED_MODULES = ['aws-sdk'] + +// We use all the files published by the Node.js except some that are not needed +const getPublishedFiles = async function(modulePath) { + const ignore = getIgnoredFiles(modulePath) + const publishedFiles = await pGlob(`${modulePath}/**`, { + ignore, + nodir: true, + absolute: true, + dot: true + }) + return publishedFiles +} + +const getIgnoredFiles = function(modulePath) { + return IGNORED_FILES.map(ignoreFile => `${modulePath}/${ignoreFile}`) +} + +// To make the zip archive smaller, we remove those. +const IGNORED_FILES = [ + 'node_modules/**', + '.npmignore', + 'package-lock.json', + 'yarn.lock', + '*.log', + '*.lock', + '*~', + '*.map', + '*.ts', + '*.patch' +] + +// Apply the Node.js module logic recursively on its own dependencies, using +// the `package.json` `dependencies`, `peerDependencies` and +// `optionalDependencies` keys +const getNestedModules = async function(modulePath, state, pkg) { + const dependencies = getNestedDependencies(pkg) + + const depsPaths = await Promise.all( + dependencies.map(dependency => getModuleDependencies(dependency, modulePath, state, pkg)) + ) + return [].concat(...depsPaths) +} + +const getNestedDependencies = function({ dependencies = {}, peerDependencies = {}, optionalDependencies = {} }) { + return [ + ...Object.keys(dependencies), + ...Object.keys(peerDependencies).filter(shouldIncludePeerDependency), + ...Object.keys(optionalDependencies) + ] +} + +// Workaround for https://github.com/netlify/zip-it-and-ship-it/issues/73 +// TODO: remove this after adding proper modules exclusion as outlined in +// https://github.com/netlify/zip-it-and-ship-it/issues/68 +const shouldIncludePeerDependency = function(name) { + return !EXCLUDED_PEER_DEPENDENCIES.includes(name) +} + +const EXCLUDED_PEER_DEPENDENCIES = ['prisma2'] + +// Modules can be required conditionally (inside an `if` or `try`/`catch` block). +// When a `require()` statement is found but the module is not found, it is +// possible that that block either always evaluates to: +// - `false`: in which case, we should not bundle the dependency +// - `true`: in which case, we should report the dependency as missing +// Those conditional modules might be: +// - present in the `package.json` `dependencies` +// - present in the `package.json` `optionalDependencies` +// - present in the `package.json` `peerDependencies` +// - not present in the `package.json`, if the module author wants its users +// to explicitly install it as an optional dependency. +// The current implementation: +// - when parsing `require()` statements inside function files, always consider +// conditional modules to be included, i.e. report them if not found. +// This is because our current parsing logic does not know whether a +// `require()` is conditional or not. +// - when parsing module dependencies, ignore `require()` statements if not +// present in the `package.json` `*dependencies`. I.e. user must manually +// install them if the module is used. +// `optionalDependencies`: +// - are not reported when missing +// - are included in module dependencies +const handleModuleNotFound = function({ error, moduleName, packageJson }) { + if (error.code === 'MODULE_NOT_FOUND' && isOptionalModule(moduleName, packageJson)) { + return [] + } + + throw error +} + +const isOptionalModule = function( + moduleName, + { optionalDependencies = {}, peerDependenciesMeta = {}, peerDependencies = {} } +) { + return ( + optionalDependencies[moduleName] !== undefined || + (peerDependenciesMeta[moduleName] && + peerDependenciesMeta[moduleName].optional && + peerDependencies[moduleName] !== undefined) + ) +} + +module.exports = { getDependencies } diff --git a/src/finders.js b/src/finders.js deleted file mode 100644 index 619bc3c22..000000000 --- a/src/finders.js +++ /dev/null @@ -1,195 +0,0 @@ -const path = require('path') -const fs = require('fs') - -const packList = require('npm-packlist') -const precinct = require('precinct') -const resolve = require('resolve') -const readPkgUp = require('read-pkg-up') -const requirePackageName = require('require-package-name') -const alwaysIgnored = new Set(['aws-sdk']) -const debug = require('debug')('@netlify/zip-it-and-ship-it:finders') - -const ignoredExtensions = new Set([ - '.log', - '.lock', - '.html', - '.md', - '.map', - '.ts', - '.png', - '.jpeg', - '.jpg', - '.gif', - '.css', - '.patch' -]) - -function ignoreMissing(dependency, optional) { - return alwaysIgnored.has(dependency) || (optional && dependency in optional) -} - -function includeModuleFile(packageJson, moduleFilePath) { - if (packageJson.files) { - return true - } - - return !ignoredExtensions.has(path.extname(moduleFilePath)) -} - -function getDependencies(filename, basedir) { - const servicePath = basedir - - const filePaths = new Set() - const modulePaths = new Set() - const pkgs = {} - - const modulesToProcess = [] - const localFilesToProcess = [filename] - - function handle(name, basedir, optionalDependencies) { - const moduleName = requirePackageName(name.replace(/\\/, '/')) - - if (alwaysIgnored.has(moduleName)) { - return - } - - try { - const pathToModule = resolve.sync(path.join(moduleName, 'package.json'), { - basedir - }) - const pkg = readPkgUp.sync({ cwd: pathToModule }) - - if (pkg) { - modulesToProcess.push(pkg) - } - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - if (ignoreMissing(moduleName, optionalDependencies)) { - debug(`WARNING missing optional dependency: ${moduleName}`) - return null - } - try { - // this resolves the requested import also against any set up NODE_PATH extensions, etc. - const resolved = require.resolve(name) - localFilesToProcess.push(resolved) - return - } catch (e) { - if (moduleName === 'encoding') { - // node-fetch has an undeclared peer dependency on encoding - // We add an exception for this one module because it is very popular and people complain about it - debug(`WARNING missing optional dependency: ${moduleName}`) - } else { - throw new Error(`Could not find "${moduleName}" module in file: ${filename.replace( - path.dirname(basedir), - '' - )}. - - Please ensure "${moduleName}" is installed in the project.`) - } - } - } - throw e - } - } - - while (localFilesToProcess.length) { - const currentLocalFile = localFilesToProcess.pop() - - if (filePaths.has(currentLocalFile)) { - continue - } - - filePaths.add(currentLocalFile) - precinct.paperwork(currentLocalFile, { includeCore: false }).forEach(dependency => { - if (dependency.indexOf('.') === 0) { - const abs = resolve.sync(dependency, { - basedir: path.dirname(currentLocalFile) - }) - localFilesToProcess.push(abs) - } else { - handle(dependency, servicePath) - } - }) - } - - while (modulesToProcess.length) { - const currentModule = modulesToProcess.pop() - const currentModulePath = path.join(currentModule.path, '..') - const packageJson = currentModule.packageJson - - if (packageJson.peerDependenciesMeta) { - packageJson.optionalDependencies = packageJson.optionalDependencies || {} - - for (const depKey in packageJson.peerDependenciesMeta) { - if (packageJson.peerDependenciesMeta[depKey].optional) { - packageJson.optionalDependencies[depKey] = packageJson.peerDependencies[depKey] - } - } - } - - if (modulePaths.has(currentModulePath)) { - continue - } - modulePaths.add(currentModulePath) - pkgs[currentModulePath] = packageJson - ;['dependencies', 'peerDependencies', 'optionalDependencies'].forEach(key => { - const dependencies = packageJson[key] - - if (dependencies) { - Object.keys(dependencies).forEach(dependency => { - handle(dependency, currentModulePath, packageJson.optionalDependencies) - }) - } - }) - } - - modulePaths.forEach(modulePath => { - const packageJson = pkgs[modulePath] - let moduleFilePaths - - moduleFilePaths = packList.sync({ path: modulePath }) - - moduleFilePaths.forEach(moduleFilePath => { - if (includeModuleFile(packageJson, moduleFilePath)) { - filePaths.add(path.join(modulePath, moduleFilePath)) - } - }) - }) - - // TODO: get rid of this - const sizes = {} - filePaths.forEach(filepath => { - const stat = fs.lstatSync(filepath) - const ext = path.extname(filepath) - sizes[ext] = (sizes[ext] || 0) + stat.size - }) - debug('Sizes per extension: ', sizes) - - return Array.from(filePaths) -} - -function findModuleDir(dir) { - let basedir = dir - while (!fs.existsSync(path.join(basedir, 'package.json'))) { - const newBasedir = path.dirname(basedir) - if (newBasedir === basedir) { - return null - } - basedir = newBasedir - } - return basedir -} - -function findHandler(functionPath) { - if (fs.lstatSync(functionPath).isFile()) { - return functionPath - } - - const handlerPath = path.join(functionPath, `${path.basename(functionPath)}.js`) - if (!fs.existsSync(handlerPath)) { - return - } - return handlerPath -} - -module.exports = { getDependencies, findModuleDir, findHandler } diff --git a/src/fixtures/node-module-deep-conditional/function.js b/src/fixtures/node-module-deep-conditional/function.js new file mode 100644 index 000000000..b15ec8ed6 --- /dev/null +++ b/src/fixtures/node-module-deep-conditional/function.js @@ -0,0 +1,3 @@ +require('test') + +module.exports = true diff --git a/src/fixtures/node-module-deep-conditional/node_modules/test/index.js b/src/fixtures/node-module-deep-conditional/node_modules/test/index.js new file mode 100644 index 000000000..676ccac83 --- /dev/null +++ b/src/fixtures/node-module-deep-conditional/node_modules/test/index.js @@ -0,0 +1,3 @@ +try { + require('conditional-dep') +} catch (error) {} diff --git a/src/fixtures/node-module-deep-conditional/node_modules/test/package.json b/src/fixtures/node-module-deep-conditional/node_modules/test/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/fixtures/node-module-deep-conditional/node_modules/test/package.json @@ -0,0 +1 @@ +{} diff --git a/src/fixtures/node-module-deep-conditional/package.json b/src/fixtures/node-module-deep-conditional/package.json new file mode 100644 index 000000000..57c3fd90b --- /dev/null +++ b/src/fixtures/node-module-deep-conditional/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "test": "0.0.1" + } +} diff --git a/src/fixtures/node-module-included/function.js b/src/fixtures/node-module-included/function.js new file mode 100644 index 000000000..28b1bd444 --- /dev/null +++ b/src/fixtures/node-module-included/function.js @@ -0,0 +1 @@ +module.exports = require('test') diff --git a/src/fixtures/node-module-included/node_modules/test/index.js b/src/fixtures/node-module-included/node_modules/test/index.js new file mode 100644 index 000000000..33c1891f8 --- /dev/null +++ b/src/fixtures/node-module-included/node_modules/test/index.js @@ -0,0 +1 @@ +module.exports = true diff --git a/src/fixtures/node-module-included/node_modules/test/package.json b/src/fixtures/node-module-included/node_modules/test/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/fixtures/node-module-included/node_modules/test/package.json @@ -0,0 +1 @@ +{} diff --git a/src/fixtures/node-module-included/node_modules/test/test.html b/src/fixtures/node-module-included/node_modules/test/test.html new file mode 100644 index 000000000..e69de29bb diff --git a/src/fixtures/node-module-included/node_modules/test/test.map b/src/fixtures/node-module-included/node_modules/test/test.map new file mode 100644 index 000000000..e69de29bb diff --git a/src/fixtures/node-module-included/package.json b/src/fixtures/node-module-included/package.json new file mode 100644 index 000000000..5416bb918 --- /dev/null +++ b/src/fixtures/node-module-included/package.json @@ -0,0 +1,7 @@ +{ + "name": "local-node-module", + "version": "1.0.0", + "dependencies": { + "test": "1.0.2" + } +} diff --git a/src/fixtures/node-module-optional-whitelist/function.js b/src/fixtures/node-module-missing-conditional/function.js similarity index 100% rename from src/fixtures/node-module-optional-whitelist/function.js rename to src/fixtures/node-module-missing-conditional/function.js diff --git a/src/fixtures/node-module-missing-deep/function.js b/src/fixtures/node-module-missing-deep/function.js new file mode 100644 index 000000000..b15ec8ed6 --- /dev/null +++ b/src/fixtures/node-module-missing-deep/function.js @@ -0,0 +1,3 @@ +require('test') + +module.exports = true diff --git a/src/fixtures/node-module-missing-deep/node_modules/test/index.js b/src/fixtures/node-module-missing-deep/node_modules/test/index.js new file mode 100644 index 000000000..b803e7c91 --- /dev/null +++ b/src/fixtures/node-module-missing-deep/node_modules/test/index.js @@ -0,0 +1 @@ +require('missing-dep') diff --git a/src/fixtures/node-module-missing-deep/node_modules/test/package.json b/src/fixtures/node-module-missing-deep/node_modules/test/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/fixtures/node-module-missing-deep/node_modules/test/package.json @@ -0,0 +1 @@ +{} diff --git a/src/fixtures/node-module-missing-deep/package.json b/src/fixtures/node-module-missing-deep/package.json new file mode 100644 index 000000000..57c3fd90b --- /dev/null +++ b/src/fixtures/node-module-missing-deep/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "test": "0.0.1" + } +} diff --git a/src/fixtures/node-module-optional/function.js b/src/fixtures/node-module-missing-optional/function.js similarity index 100% rename from src/fixtures/node-module-optional/function.js rename to src/fixtures/node-module-missing-optional/function.js diff --git a/src/fixtures/node-module-optional/package.json b/src/fixtures/node-module-missing-optional/package.json similarity index 100% rename from src/fixtures/node-module-optional/package.json rename to src/fixtures/node-module-missing-optional/package.json diff --git a/src/fixtures/node-module-peer-not-optional/function.js b/src/fixtures/node-module-peer-not-optional/function.js new file mode 100644 index 000000000..4295c0e6e --- /dev/null +++ b/src/fixtures/node-module-peer-not-optional/function.js @@ -0,0 +1,7 @@ +try { + // eslint-disable-next-line node/no-missing-require + require('consistent-ids') + // eslint-disable-next-line no-empty +} catch (error) {} + +module.exports = true diff --git a/src/fixtures/node-module-peer-not-optional/package.json b/src/fixtures/node-module-peer-not-optional/package.json new file mode 100644 index 000000000..2de56ba39 --- /dev/null +++ b/src/fixtures/node-module-peer-not-optional/package.json @@ -0,0 +1,10 @@ +{ + "peerDependencies": { + "consistent-ids": "0.1.1" + }, + "peerDependenciesMeta": { + "consistent-ids": { + "optional": false + } + } +} diff --git a/src/fixtures/node-module-peer-optional-none/function.js b/src/fixtures/node-module-peer-optional-none/function.js new file mode 100644 index 000000000..4295c0e6e --- /dev/null +++ b/src/fixtures/node-module-peer-optional-none/function.js @@ -0,0 +1,7 @@ +try { + // eslint-disable-next-line node/no-missing-require + require('consistent-ids') + // eslint-disable-next-line no-empty +} catch (error) {} + +module.exports = true diff --git a/src/fixtures/node-module-peer-optional-none/package.json b/src/fixtures/node-module-peer-optional-none/package.json new file mode 100644 index 000000000..7eb4b0ec5 --- /dev/null +++ b/src/fixtures/node-module-peer-optional-none/package.json @@ -0,0 +1,7 @@ +{ + "peerDependenciesMeta": { + "consistent-ids": { + "optional": true + } + } +} diff --git a/src/fixtures/node-module-peer-optional/package.json b/src/fixtures/node-module-peer-optional/package.json index c3600c138..b2131634e 100644 --- a/src/fixtures/node-module-peer-optional/package.json +++ b/src/fixtures/node-module-peer-optional/package.json @@ -3,9 +3,8 @@ "consistent-ids": "0.1.1" }, "peerDependenciesMeta": { - "consistent-ids": { - "optional": true - } + "consistent-ids": { + "optional": true + } } } - diff --git a/src/go.js b/src/go.js new file mode 100644 index 000000000..2093e765c --- /dev/null +++ b/src/go.js @@ -0,0 +1,32 @@ +const { readFile } = require('fs') + +const { parse: parseElf } = require('elf-tools') +const promisify = require('util.promisify') + +const { startZip, addZipFile, endZip } = require('./archive') + +const pReadFile = promisify(readFile) + +// Check if a file is a Go executable +const isGoExe = async function(path) { + try { + const buffer = await pReadFile(path) + const { sections } = parseElf(buffer) + return sections.some(isGoHeader) + } catch (error) { + return false + } +} + +const isGoHeader = function({ header: { name } }) { + return name === '.note.go.buildid' +} + +// Zip a Go function file +const zipGoExe = async function(srcPath, destPath, filename, stat) { + const { archive, output } = startZip(destPath) + addZipFile(archive, srcPath, filename, stat) + await endZip(archive, output) +} + +module.exports = { isGoExe, zipGoExe } diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..45392a22d --- /dev/null +++ b/src/main.js @@ -0,0 +1,108 @@ +const { join, resolve, dirname, basename, extname } = require('path') +const { readdir, lstat } = require('fs') + +const pMap = require('p-map') +const promisify = require('util.promisify') +const pathExists = require('path-exists') +const makeDir = require('make-dir') +const cpFile = require('cp-file') + +const { zipNodeJs } = require('./node') +const { isGoExe, zipGoExe } = require('./go') + +const pReaddir = promisify(readdir) +const pLstat = promisify(lstat) + +// Zip `srcFolder/*` (Node.js or Go files) to `destFolder/*.zip` so it can be +// used by AWS Lambda +const zipFunctions = async function(srcFolder, destFolder, { parallelLimit = 5, skipGo } = {}) { + const filenames = await listFilenames(srcFolder) + const srcPaths = filenames.map(filename => resolve(srcFolder, filename)) + + const zipped = await pMap(srcPaths, srcPath => zipFunction(srcPath, destFolder, { skipGo }), { + concurrency: parallelLimit + }) + return zipped.filter(Boolean) +} + +const listFilenames = async function(srcFolder) { + try { + return await pReaddir(srcFolder) + } catch (error) { + throw new Error(`Functions folder does not exist: ${srcFolder}`) + } +} + +const zipFunction = async function(srcPath, destFolder, { skipGo } = {}) { + const { filename, extension, srcDir, stat, handler, destPath, destCopyPath } = await statFile(srcPath, destFolder) + + if (filename === 'node_modules' || (stat.isDirectory() && handler === undefined)) { + return + } + + if (extension === '.zip') { + await cpFile(srcPath, destCopyPath) + return { path: destCopyPath, runtime: 'js' } + } + + if (extension === '.js' || stat.isDirectory()) { + await zipNodeJs(srcPath, srcDir, destPath, filename, handler, stat) + return { path: destPath, runtime: 'js' } + } + + const isGoExecutable = await isGoExe(srcPath) + + if (isGoExecutable && skipGo) { + await cpFile(srcPath, destCopyPath) + return { path: destCopyPath, runtime: 'go' } + } + + if (isGoExecutable) { + await zipGoExe(srcPath, destPath, filename, stat) + return { path: destPath, runtime: 'go' } + } +} + +const statFile = async function(srcPath, destFolder) { + const filename = basename(srcPath) + const extension = extname(srcPath) + const stat = await pLstat(srcPath) + const handler = await getHandler(srcPath, filename, stat) + const srcDir = stat.isDirectory() ? srcPath : dirname(srcPath) + + await makeDir(destFolder) + const destCopyPath = join(destFolder, filename) + const destPath = join(destFolder, `${filename.replace(FUNCTION_EXTENSIONS, '')}.zip`) + + return { + filename, + extension, + srcDir, + stat, + handler, + destCopyPath, + destPath + } +} + +// Each `srcPath` can also be a directory with an `index.js` file or a file +// using the same filename as its directory +const getHandler = async function(srcPath, filename, stat) { + if (!stat.isDirectory()) { + return srcPath + } + + const namedHandler = join(srcPath, `${filename}.js`) + if (await pathExists(namedHandler)) { + return namedHandler + } + + const indexHandler = join(srcPath, 'index.js') + if (await pathExists(indexHandler)) { + return indexHandler + } +} + +const FUNCTION_EXTENSIONS = /\.js$/ + +module.exports = { zipFunctions, zipFunction } diff --git a/src/main.test.js b/src/main.test.js index 7e3f6dfab..8892c24ae 100644 --- a/src/main.test.js +++ b/src/main.test.js @@ -48,6 +48,16 @@ test('Ignore some excluded node modules', async t => { t.false(await pathExists(`${tmpDir}/node_modules/aws-sdk`)) }) +test('Include most files from node modules', async t => { + const { tmpDir } = await zipNode(t, 'node-module-included') + const [mapExists, htmlExists] = await Promise.all([ + pathExists(`${tmpDir}/src/node_modules/test/test.map`), + pathExists(`${tmpDir}/src/node_modules/test/test.html`) + ]) + t.false(mapExists) + t.true(htmlExists) +}) + test('Throws on runtime errors', async t => { await t.throwsAsync(zipNode(t, 'node-module-error')) }) @@ -60,18 +70,34 @@ test('Throws on missing dependencies with no optionalDependencies', async t => { await t.throwsAsync(zipNode(t, 'node-module-missing-package')) }) -test.skip('Ignore missing optional dependencies', async t => { - await zipNode(t, 'node-module-optional') +test('Throws on missing conditional dependencies', async t => { + await t.throwsAsync(zipNode(t, 'node-module-missing-conditional')) +}) + +test("Throws on missing dependencies' dependencies", async t => { + await t.throwsAsync(zipNode(t, 'node-module-missing-deep')) +}) + +test('Ignore missing optional dependencies', async t => { + await zipNode(t, 'node-module-missing-optional') }) -test.skip('Ignore missing whitelisted optional dependencies', async t => { - await zipNode(t, 'node-module-optional-whitelist') +test('Ignore modules conditional dependencies', async t => { + await zipNode(t, 'node-module-deep-conditional') }) -test.skip('Ignore missing optional peer dependencies', async t => { +test('Ignore missing optional peer dependencies', async t => { await zipNode(t, 'node-module-peer-optional') }) +test('Throws on missing optional peer dependencies with no peer dependencies', async t => { + await t.throwsAsync(zipNode(t, 'node-module-peer-optional-none')) +}) + +test('Throws on missing non-optional peer dependencies', async t => { + await t.throwsAsync(zipNode(t, 'node-module-peer-not-optional')) +}) + test('Can require local files', async t => { await zipNode(t, 'local-require') }) @@ -80,7 +106,7 @@ test('Can require local files deeply', async t => { await zipNode(t, 'local-deep-require') }) -test.skip('Can require local files in the parent directories', async t => { +test('Can require local files in the parent directories', async t => { await zipNode(t, 'local-parent-require') }) @@ -88,23 +114,23 @@ test('Can target a directory with a handler with the same name', async t => { await zipNode(t, 'directory-handler') }) -test.skip('Can target a directory with an index.js file', async t => { +test('Can target a directory with an index.js file', async t => { const { files, tmpDir } = await zipFixture(t, 'index-handler') await unzipFiles(files) - t.true(require(`${tmpDir}/index.js`)) + t.true(require(`${tmpDir}/function.js`)) }) -test.skip('Keeps non-required files inside the target directory', async t => { +test('Keeps non-required files inside the target directory', async t => { const { tmpDir } = await zipNode(t, 'keep-dir-files') - t.true(await pathExists(`${tmpDir}/file.js`)) + t.true(await pathExists(`${tmpDir}/function.js`)) }) -test.skip('Ignores non-required node_modules inside the target directory', async t => { +test('Ignores non-required node_modules inside the target directory', async t => { const { tmpDir } = await zipNode(t, 'ignore-dir-node-modules') t.false(await pathExists(`${tmpDir}/node_modules`)) }) -test.skip('Ignores deep non-required node_modules inside the target directory', async t => { +test('Ignores deep non-required node_modules inside the target directory', async t => { const { tmpDir } = await zipNode(t, 'ignore-deep-dir-node-modules') t.false(await pathExists(`${tmpDir}/deep/node_modules`)) }) @@ -117,7 +143,7 @@ test('Works with many function files', async t => { await zipNode(t, 'many-functions', 6) }) -test.skip('Throws when the source folder does not exist', async t => { +test('Throws when the source folder does not exist', async t => { await t.throwsAsync(zipNode(t, 'does-not-exist'), /Functions folder does not exist/) }) @@ -137,14 +163,14 @@ test('Works on empty directories', async t => { await zipNode(t, 'empty', 0) }) -test.skip('Works when no package.json is present', async t => { +test('Works when no package.json is present', async t => { const tmpDir = await tmpName({ prefix: 'zip-it-test' }) const files = await cpy(`${FIXTURES_DIR}/no-package-json`, `${tmpDir}/no-package-json`, { parents: true }) const commonDir = commonPathPrefix(files) await zipNode(t, 'no-package-json', 1, {}, `${commonDir}/..`) }) -test.skip('Copies already zipped files', async t => { +test('Copies already zipped files', async t => { const tmpDir = await tmpName({ prefix: 'zip-it-test' }) const { files } = await zipCheckFunctions(t, 'keep-zip', tmpDir) @@ -195,7 +221,7 @@ test('Can reduce parallelism', async t => { await zipNode(t, 'simple', 1, { parallelLimit: 1 }) }) -test.skip('Can use zipFunction()', async t => { +test('Can use zipFunction()', async t => { const { path: tmpDir } = await getTmpDir({ prefix: 'zip-it-test' }) const { runtime } = await zipFunction(`${FIXTURES_DIR}/simple/function.js`, tmpDir) t.is(runtime, 'js') diff --git a/src/node.js b/src/node.js new file mode 100644 index 000000000..e8589c6da --- /dev/null +++ b/src/node.js @@ -0,0 +1,69 @@ +const { lstat } = require('fs') +const { dirname } = require('path') + +const pkgDir = require('pkg-dir') +const glob = require('glob') +const promisify = require('util.promisify') +const commonPathPrefix = require('common-path-prefix') + +const { startZip, addZipFile, addZipContent, endZip } = require('./archive') +const { getDependencies } = require('./dependencies') + +const pGlob = promisify(glob) +const pLstat = promisify(lstat) + +// Zip a Node.js function file +const zipNodeJs = async function(srcPath, srcDir, destPath, filename, handler, stat) { + const { archive, output } = startZip(destPath) + + const packageRoot = await pkgDir(srcDir) + + const files = await filesForFunctionZip(srcPath, filename, handler, packageRoot, stat) + const dirnames = files.map(dirname) + const commonPrefix = commonPathPrefix(dirnames) + + addEntryFile(srcPath, commonPrefix, archive, filename, handler) + + await Promise.all(files.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, filename, handler, packageRoot, stat) { + const [treeFiles, depFiles] = await Promise.all([getTreeFiles(srcPath, stat), getDependencies(handler, packageRoot)]) + const files = [...treeFiles, ...depFiles] + const uniqueFiles = [...new Set(files)] + return uniqueFiles +} + +// 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 + }) +} + +const addEntryFile = function(srcPath, commonPrefix, archive, filename, handler) { + const mainPath = handler.replace(commonPrefix, 'src/') + const content = Buffer.from(`module.exports = require('./${mainPath}')`) + const entryFilename = filename.endsWith('.js') ? filename : `${filename}.js` + + addZipContent(archive, content, entryFilename) +} + +const zipJsFile = async function(file, commonPrefix, archive) { + const filename = file.replace(commonPrefix, 'src/') + const stat = await pLstat(file) + addZipFile(archive, file, filename, stat) +} + +module.exports = { zipNodeJs } diff --git a/src/zip.js b/src/zip.js deleted file mode 100644 index 09c1ccd5a..000000000 --- a/src/zip.js +++ /dev/null @@ -1,183 +0,0 @@ -const path = require('path') -const fs = require('fs') - -const glob = require('glob') -const archiver = require('archiver') -const elfTools = require('elf-tools') -const pAll = require('p-all') - -const { getDependencies, findModuleDir, findHandler } = require('./finders') - -class Zip { - constructor(path) { - this.output = fs.createWriteStream(path) - this.archive = archiver('zip', { level: 9 }) - this.archive.pipe(this.output) - } - - addLocalFile(path, data) { - this.archive.file(path, data) - } - - finalize() { - return new Promise((resolve, reject) => { - this.output.on('end', resolve) - this.output.on('close', resolve) - this.output.on('finish', resolve) - this.output.on('error', reject) - this.archive.finalize() - }) - } -} - -function filesForFunctionZip(functionPath) { - const filesToBundle = new Set() - if (fs.lstatSync(functionPath).isDirectory()) { - const moduledir = findModuleDir(functionPath) - const ignoreArgs = [moduledir, 'node_modules', '**'].filter(segment => segment != null) - glob - .sync(path.join(functionPath, '**'), { - nodir: true, - ignore: path.join(...ignoreArgs), - absolute: true - }) - .forEach(file => filesToBundle.add(file)) - if (moduledir) { - let handler = null - const namedHandler = path.join(functionPath, `${path.basename(functionPath)}.js`) - const indexHandler = path.join(functionPath, 'index.js') - if (fs.existsSync(namedHandler)) { - handler = namedHandler - } else if (fs.existsSync(indexHandler)) { - handler = indexHandler - } else { - throw ('Failed to find handler for ', functionPath) - } - - getDependencies(handler, moduledir).forEach(file => filesToBundle.add(file)) - } - } else { - filesToBundle.add(functionPath) - const moduledir = findModuleDir(path.dirname(functionPath)) - if (moduledir) { - getDependencies(functionPath, moduledir).forEach(file => filesToBundle.add(file)) - } - } - return filesToBundle -} - -function isGoExe(file) { - try { - const buf = fs.readFileSync(file) - const elf = elfTools.parse(buf) - return elf.sections.find(s => s.header.name === '.note.go.buildid') - } catch (e) { - return false - } -} - -function zipEntryPath(file, basedir, moduledir) { - return file - .replace(basedir, '') - .replace(moduledir, '') - .replace(/^\//, '') -} - -function zipGoExe(file, zipPath) { - const zip = new Zip(zipPath) - const stat = fs.lstatSync(file) - zip.addLocalFile(file, { - name: path.basename(file), - mode: stat.mode, - date: new Date(0), // Ensure sha256 stability regardless of mtime - stats: stat - }) - return zip.finalize() -} - -function zipJs(functionPath, zipPath) { - const zip = new Zip(zipPath) - let basedir = functionPath - if (fs.lstatSync(functionPath).isFile()) { - basedir = path.dirname(basedir) - } - const moduledir = findModuleDir(basedir) - - filesForFunctionZip(functionPath).forEach(file => { - const entryPath = zipEntryPath(file, basedir, moduledir) - const stat = fs.lstatSync(file) - zip.addLocalFile(file, { - name: entryPath, - mode: stat.mode, - date: new Date(0), // Ensure sha256 stability regardless of mtime - stats: stat - }) - }) - return zip.finalize() -} - -function zipFunction(functionPath, destFolder, options) { - if (path.basename(functionPath) === 'node_modules') { - return Promise.resolve(null) - } - const zipPath = path.join(destFolder, path.basename(functionPath).replace(/\.(js|zip)$/, '') + '.zip') - if (path.extname(functionPath) === '.zip') { - fs.copyFileSync(functionPath, zipPath) - return Promise.resolve({ - path: zipPath, - runtime: 'js' - }) - } - const ds = fs.lstatSync(functionPath) - if (ds.isDirectory() || path.extname(functionPath) === '.js') { - if (!findHandler(functionPath)) { - return Promise.resolve(null) - } - return zipJs(functionPath, zipPath).then(() => { - return { - path: zipPath, - runtime: 'js' - } - }) - } - if (isGoExe(functionPath)) { - if (options && options.skipGo) { - const goPath = path.join(destFolder, path.basename(functionPath)) - fs.copyFileSync(functionPath, goPath) - return Promise.resolve({ - path: goPath, - runtime: 'go' - }) - } - - return zipGoExe(functionPath, zipPath).then(() => { - return { - path: zipPath, - runtime: 'go' - } - }) - } - return Promise.resolve(null) -} - -function zipFunctions(srcFolder, destFolder, options) { - options = Object.assign( - { - parallelLimit: 5 - }, - options - ) - return pAll( - fs - .readdirSync(srcFolder) - .map(file => () => zipFunction(path.resolve(path.join(srcFolder, file)), destFolder, options)), - { concurrency: options.parallelLimit } - ).then(zipped => zipped.filter(e => e)) -} - -module.exports = { - getDependencies, - filesForFunctionZip, - zipFunction, - zipFunctions -}