diff --git a/package-lock.json b/package-lock.json index df4548f..f82e418 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,9 @@ "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", + "fast-glob": "^3.2.11", "fs-extra": "^7.0.1", - "glob": "^7.1.6", + "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", "jszip": "^3.6.0", "minimatch": "^3.0.4", @@ -27,6 +28,7 @@ "devDependencies": { "@types/chai": "^4.2.22", "@types/fs-extra": "^5.0.1", + "@types/is-glob": "^4.0.2", "@types/mocha": "^9.0.0", "@types/node": "^16.11.3", "@types/request": "^2.47.0", @@ -554,7 +556,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -567,7 +568,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -576,7 +576,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -665,6 +664,12 @@ "@types/node": "*" } }, + "node_modules/@types/is-glob": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/is-glob/-/is-glob-4.0.2.tgz", + "integrity": "sha512-4j5G9Y5jljDSICQ1R2f/Rcyoj6DZmYGneny+p/cDkjep0rkqNg0W73Ty0bVjMUTZgLXHf8oiMjg1XC3CDwCz+g==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -1102,7 +1107,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1898,10 +1902,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1910,14 +1913,13 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1940,7 +1942,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -1961,7 +1962,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2116,7 +2116,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -2186,6 +2187,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2397,6 +2399,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2423,7 +2426,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2441,7 +2443,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2453,7 +2454,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -2981,7 +2981,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -2990,7 +2989,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -3450,6 +3448,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "dependencies": { "wrappy": "1" } @@ -3575,6 +3574,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3636,7 +3636,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -3768,7 +3767,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3915,7 +3913,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3937,7 +3934,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -4273,7 +4269,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -4578,7 +4573,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -5115,7 +5111,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -5124,14 +5119,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -5217,6 +5210,12 @@ "@types/node": "*" } }, + "@types/is-glob": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/is-glob/-/is-glob-4.0.2.tgz", + "integrity": "sha512-4j5G9Y5jljDSICQ1R2f/Rcyoj6DZmYGneny+p/cDkjep0rkqNg0W73Ty0bVjMUTZgLXHf8oiMjg1XC3CDwCz+g==", + "dev": true + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -5532,7 +5531,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -6133,10 +6131,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", - "dev": true, + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -6149,7 +6146,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -6171,7 +6167,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -6189,7 +6184,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -6293,7 +6287,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -6344,6 +6339,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6492,6 +6488,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -6514,8 +6511,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -6527,7 +6523,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -6535,8 +6530,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-stream": { "version": "2.0.1", @@ -6946,14 +6940,12 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -7307,6 +7299,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -7398,7 +7391,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -7449,8 +7443,7 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pkg-dir": { "version": "4.2.0", @@ -7544,8 +7537,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "randombytes": { "version": "2.1.0", @@ -7654,8 +7646,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "2.7.1", @@ -7670,7 +7661,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -7923,7 +7913,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -8142,7 +8131,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index 6a73a32..be218f7 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,9 @@ "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", + "fast-glob": "^3.2.11", "fs-extra": "^7.0.1", - "glob": "^7.1.6", + "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", "jszip": "^3.6.0", "minimatch": "^3.0.4", @@ -31,6 +32,7 @@ "devDependencies": { "@types/chai": "^4.2.22", "@types/fs-extra": "^5.0.1", + "@types/is-glob": "^4.0.2", "@types/mocha": "^9.0.0", "@types/node": "^16.11.3", "@types/request": "^2.47.0", diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index e7826f9..3d70fad 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -1686,6 +1686,44 @@ describe('index', () => { } describe('top-level-patterns', () => { + it('excludes a file that is negated', async () => { + expect(await getFilePaths([ + 'source/**/*', + '!source/main.brs' + ])).to.eql([{ + src: s`${rootDir}/source/lib.brs`, + dest: s`source/lib.brs` + }]); + }); + + it('excludes file from non-rootdir top-level pattern', async () => { + writeFiles(rootDir, ['../externalDir/source/main.brs']); + expect(await getFilePaths([ + '../externalDir/**/*', + '!../externalDir/**/*' + ])).to.eql([]); + }); + + it('throws when using top-level string referencing file outside the root dir', async () => { + writeFiles(rootDir, [`../source/main.brs`]); + await expectThrowsAsync(async () => { + await getFilePaths([ + '../source/**/*' + ]); + }, 'Cannot reference a file outside of rootDir when using a top-level string. Please use a src;des; object instead'); + }); + + it('works for brighterscript files', async () => { + writeFiles(rootDir, ['src/source/main.bs']); + expect(await getFilePaths([ + 'manifest', + 'source/**/*.bs' + ], s`${rootDir}/src`)).to.eql([{ + src: s`${rootDir}/src/source/main.bs`, + dest: s`source/main.bs` + }]); + }); + it('works for root-level double star in top-level pattern', async () => { expect(await getFilePaths([ '**/*' @@ -1889,6 +1927,18 @@ describe('index', () => { }); describe('{src;dest} objects', () => { + it('excludes a file that is negated in src;dest;', async () => { + expect(await getFilePaths([ + 'source/**/*', + { + src: '!source/main.brs' + } + ])).to.eql([{ + src: s`${rootDir}/source/lib.brs`, + dest: s`source/lib.brs` + }]); + }); + it('works for root-level double star in {src;dest} object', async () => { expect(await getFilePaths([{ src: '**/*', @@ -2160,8 +2210,12 @@ describe('index', () => { src: s`${rootDir}/../README.md`, dest: s`docs/README.md` }]); + }); - //should throw exception because we can't have top-level string paths pointed to files outside the root + it('should throw exception because we cannot have top-level string paths pointed to files outside the root', async () => { + writeFiles(rootDir, [ + '../README.md' + ]); await expectThrowsAsync( rokuDeploy.getFilePaths([ path.join('..', 'README.md') @@ -2218,7 +2272,28 @@ describe('index', () => { }); }); + describe('computeFileDestPath', () => { + it('treats {src;dest} without dest as a top-level string', () => { + expect( + rokuDeploy['computeFileDestPath'](s`${rootDir}/source/main.brs`, { src: s`source/main.brs` } as any, rootDir) + ).to.eql(s`source/main.brs`); + }); + }); + describe('getDestPath', () => { + it('handles unrelated exclusions properly', () => { + expect( + rokuDeploy.getDestPath( + s`${rootDir}/components/comp1/comp1.brs`, + [ + '**/*', + '!exclude.me' + ], + rootDir + ) + ).to.equal(s`components/comp1/comp1.brs`); + }); + it('finds dest path for top-level path', () => { expect( rokuDeploy.getDestPath( @@ -2291,26 +2366,6 @@ describe('index', () => { expect(s`${destPath}`).to.equal(s`source/main.bs`); }); - it('throws exception when rootDir is not absolute', () => { - writeFiles(rootDir, [ - 'source/main.bs' - ]); - - let stub = sinon.stub(rokuDeploy, 'getOptions').callThrough(); - let destPath = rokuDeploy.getDestPath( - util.standardizePath(`${cwd}/src/source/main.bs`), - [ - 'manifest', - 'source/**/*.bs' - ], - `./src` - ); - expect(stub.callCount).to.be.greaterThan(0); - expect(stub.getCall(0).args[0].rootDir).to.eql('./src'); - expect(stub.getCall(0).returnValue.rootDir).to.eql(s`${cwd}/src`); - expect(s`${destPath}`).to.equal(s`source/main.bs`); - }); - it('excludes a file found outside the root dir', () => { expect( rokuDeploy.getDestPath( diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index d76c2bc..117c062 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -4,14 +4,11 @@ import * as request from 'request'; import * as JSZip from 'jszip'; import * as dateformat from 'dateformat'; import * as errors from './Errors'; -import * as minimatch from 'minimatch'; -import * as glob from 'glob'; +import * as isGlob from 'is-glob'; +import * as picomatch from 'picomatch'; import * as xml2js from 'xml2js'; -import { promisify } from 'util'; import type { ParseError } from 'jsonc-parser'; import { parse as parseJsonc, printParseErrorCode } from 'jsonc-parser'; -const globAsync = promisify(glob); - import { util } from './util'; import type { RokuDeployOptions, FileEntry } from './RokuDeployOptions'; import { Logger, LogLevel } from './Logger'; @@ -34,19 +31,17 @@ export class RokuDeploy { public async prepublishToStaging(options: RokuDeployOptions) { options = this.getOptions(options); - const files = this.normalizeFilesArray(options.files); - //clean the staging directory await this.fsExtra.remove(options.stagingFolderPath); //make sure the staging folder exists await this.fsExtra.ensureDir(options.stagingFolderPath); - await this.copyToStaging(files, options.stagingFolderPath, options.rootDir); + await this.copyToStaging(options.files, options.stagingFolderPath, options.rootDir); return options.stagingFolderPath; } /** - * Given an array of `FilesType`, normalize each of them into a standard {src;dest} object. + * Given an array of `FilesType`, normalize them each into a `StandardizedFileEntry`. * Each entry in the array or inner `src` array will be extracted out into its own object. * This makes it easier to reason about later on in the process. * @param files @@ -167,212 +162,173 @@ export class RokuDeploy { } /** - * Get all file paths for the specified options - * @param files - * @param stagingFolderPath - the absolute path to the staging folder - * @param rootFolderPath - the absolute path to the root dir where relative files entries are relative to - */ - public async getFilePaths(files: FileEntry[], rootDir: string) { + * Get all file paths for the specified options + * @param files + * @param stagingFolderPath - the absolute path to the staging folder + * @param rootFolderPath - the absolute path to the root dir where relative files entries are relative to + */ + public async getFilePaths(files: FileEntry[], rootDir: string): Promise { //if the rootDir isn't absolute, convert it to absolute using the standard options flow if (path.isAbsolute(rootDir) === false) { rootDir = this.getOptions({ rootDir: rootDir }).rootDir; } - const normalizedFiles = this.normalizeFilesArray(files); - - let result = [] as StandardizedFileEntry[]; - - for (let entry of normalizedFiles) { - let src = typeof entry === 'string' ? entry : entry.src; - - //if starts with !, this is a negated glob. - let isNegated = src.startsWith('!'); - - //remove the ! so the glob will match properly - if (isNegated) { - src = src.substring(1); - //create the entry as a src;dest; so it doesn't throw that "Cannot reference a file outside of rootDir..." error in `getFilePathsForEntry`. - entry = { - src: src, - // Negated globs don't need a `dest` path but add the key to satisfy the typescript check - dest: undefined - }; - } - - let entryResults = await this.getFilePathsForEntry( - typeof entry === 'string' ? src : { ...entry, src: src }, - rootDir - ); - - //if negated, remove all of the negated matches from the results - if (isNegated) { - let paths = entryResults.map(x => x.src); - result = result.filter(x => !paths.includes(x.src)); - - //add all of the entries to the results - } else { - result.push(...entryResults); - } - } - - //only keep the last entry of each `dest` path - let destPaths = {} as Record; - for (let i = result.length - 1; i >= 0; i--) { - let entry = result[i]; - - //if we have already seen this dest path, this is a duplicate...throw it out - if (destPaths[entry.dest]) { - result.splice(i, 1); - } else { - //this is the first time we've seen this entry, keep it and move on - destPaths[entry.dest] = true; - } - } - - return result; - } - - private async getFilePathsForEntry(entry: StandardizedFileEntry | string, rootDir: string) { - //container for the files for this entry - let result = [] as StandardizedFileEntry[]; - - let pattern = typeof entry === 'string' ? entry : entry.src; - let files = await globAsync(pattern, { - cwd: rootDir, - absolute: true, - follow: true - }); - //reduce garbage collection churn by using the same filesEntry array for each file below - let fileEntries = [entry]; - for (let filePathAbsolute of files) { - //only include files (i.e. skip directories) - if (await util.isFile(filePathAbsolute)) { - //throw an exception when a top-level string references a file outside of the rootDir - if (typeof entry === 'string' && util.isParentOfPath(rootDir, filePathAbsolute) === false) { - throw new Error('Cannot reference a file outside of rootDir when using a top-level string. Please use a src;des; object instead'); + const entries = this.normalizeFilesArray(files); + const srcPathsByIndex = await util.globAllByIndex( + entries.map(x => { + return typeof x === 'string' ? x : x.src; + }), + rootDir + ); + + /** + * Result indexed by the dest path + */ + let result = new Map(); + + //compute `dest` path for every file + for (let i = 0; i < srcPathsByIndex.length; i++) { + const srcPaths = srcPathsByIndex[i]; + const entry = entries[i]; + if (srcPaths) { + for (let srcPath of srcPaths) { + srcPath = util.standardizePath(srcPath); + + const dest = this.computeFileDestPath(srcPath, entry, rootDir); + //the last file with this `dest` will win, so just replace any existing entry with this one. + result.set(dest, { + src: srcPath, + dest: dest + }); } - result.push({ - src: util.standardizePath(filePathAbsolute), - dest: this.getDestPath(filePathAbsolute, fileEntries, rootDir, true) - }); } } - return result; + return [...result.values()]; } /** * Given a full path to a file, determine its dest path - * @param srcPathAbsolute the path to the file. This MUST be a file path, and it is not verified to exist on the filesystem + * @param srcPath the absolute path to the file. This MUST be a file path, and it is not verified to exist on the filesystem * @param files the files array * @param rootDir the absolute path to the root dir * @param skipMatch - skip running the minimatch process (i.e. assume the file is a match * @returns the RELATIVE path to the dest location for the file. */ - public getDestPath(srcPathAbsolute: string, files: FileEntry[], rootDir: string, skipMatch = false): string | undefined { - //if the rootDir isn't absolute, convert it to absolute using the standard options flow - if (path.isAbsolute(rootDir) === false) { - rootDir = this.getOptions({ rootDir: rootDir }).rootDir; - } - //container for the files for this entry - const standardizedFiles = this.normalizeFilesArray(files); - let dest: string; - - //walk through the entire files array and find the last dest entry that matches - for (let entry of standardizedFiles) { - let srcGlobPattern = typeof entry === 'string' ? entry : entry.src; - const isNegated = srcGlobPattern.startsWith('!'); - if (isNegated) { - srcGlobPattern = srcGlobPattern.substring(1); - } - let isMatch: boolean; - - //if skipMatch is true, assume the file is a match and don't run the match function - if (skipMatch === true) { - isMatch = true; + public getDestPath(srcPathAbsolute: string, files: FileEntry[], rootDir: string, skipMatch = false) { + srcPathAbsolute = util.standardizePath(srcPathAbsolute); + rootDir = rootDir.replace(/\\+/g, '/'); + const entries = this.normalizeFilesArray(files); + + function makeGlobAbsolute(pattern: string) { + return path.resolve( + path.posix.join( + rootDir, + //remove leading exclamation point if pattern is negated + pattern + //coerce all slashes to forward + ) + ).replace(/\\/g, '/'); + } + + let result: string; + + //add the file into every matching cache bucket + for (let entry of entries) { + const pattern = (typeof entry === 'string' ? entry : entry.src); + //filter previous paths + if (pattern.startsWith('!')) { + const keepFile = picomatch('!' + makeGlobAbsolute(pattern.replace(/^!/, ''))); + if (!keepFile(srcPathAbsolute)) { + result = undefined; + } } else { - //make the glob path absolute - srcGlobPattern = path.resolve(util.toForwardSlashes(rootDir), srcGlobPattern); - - isMatch = minimatch(util.toForwardSlashes(srcPathAbsolute), srcGlobPattern); - } - - //if not a match, move to the next pattern - if (!isMatch) { - continue; - } - //if this was a negated pattern, discard dest (i.e. exclude the file) and move to next pattern - if (isNegated) { - dest = undefined; - continue; - } - - //root-level files array strings are treated like file filters. These must be globs/paths relative to `rootDir` - if (typeof entry === 'string') { - //if the path is not found within the rootDir, this is not a match - if (util.isParentOfPath(rootDir, srcPathAbsolute) === false) { - continue; + const keepFile = picomatch(makeGlobAbsolute(pattern)); + if (keepFile(srcPathAbsolute)) { + try { + result = this.computeFileDestPath( + srcPathAbsolute, + entry, + util.standardizePath(rootDir) + ); + } catch { + //ignore errors...the file just has no dest path + } } - //normalize the path - srcPathAbsolute = util.standardizePath(srcPathAbsolute); - let srcPathRelative = util.stringReplaceInsensitive(srcPathAbsolute, rootDir, ''); - dest = util.standardizePath(`${srcPathRelative}`); - continue; } + } + return result; + } - //if this is an explicit file reference - if (glob.hasMagic(entry.src) === false) { - let isSrcPathAbsolute = path.isAbsolute(entry.src); - let entrySrcPathAbsolute = isSrcPathAbsolute ? entry.src : util.standardizePath(`${rootDir}/${entry.src}`); + /** + * Compute the `dest` path. This accounts for magic globstars in the pattern, + * as well as relative paths based on the dest. This is only used internally. + * @param src an absolute, normalized path for a file + * @param dest the `dest` entry for this file. If omitted, files will derive their paths relative to rootDir. + * @param pattern the glob pattern originally used to find this file + * @param rootDir absolute normalized path to the rootDir + */ + private computeFileDestPath(srcPath: string, entry: string | StandardizedFileEntry, rootDir: string) { + let result: string; + let globstarIdx: number; + //files under rootDir with no specified dest + if (typeof entry === 'string') { + if (util.isParentOfPath(rootDir, srcPath, false)) { + //files that are actually relative to rootDir + result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + } else { + // result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + throw new Error('Cannot reference a file outside of rootDir when using a top-level string. Please use a src;des; object instead'); + } - let isSrcChildOfRootDir = util.isParentOfPath(rootDir, entrySrcPathAbsolute); + //non-glob-pattern explicit file reference + } else if (!isGlob(entry.src.replace(/\\/g, '/'), { strict: false })) { + let isEntrySrcAbsolute = path.isAbsolute(entry.src); + let entrySrcPathAbsolute = isEntrySrcAbsolute ? entry.src : util.standardizePath(`${rootDir}/${entry.src}`); - let fileNameAndExtension = path.basename(entrySrcPathAbsolute); + let isSrcChildOfRootDir = util.isParentOfPath(rootDir, entrySrcPathAbsolute, false); - //no dest - if (!entry.dest) { - //no dest, absolute path or file outside of rootDir - if (isSrcPathAbsolute || isSrcChildOfRootDir === false) { - //copy file to root of staging folder - dest = fileNameAndExtension; + let fileNameAndExtension = path.basename(entrySrcPathAbsolute); - //no dest, relative path, lives INSIDE rootDir - } else { - //copy relative file structure to root of staging folder - let srcPathRelative = util.stringReplaceInsensitive(entrySrcPathAbsolute, rootDir, ''); - dest = srcPathRelative; - } + //no dest + if (entry.dest === null || entry.dest === undefined) { + //no dest, absolute path or file outside of rootDir + if (isEntrySrcAbsolute || isSrcChildOfRootDir === false) { + //copy file to root of staging folder + result = fileNameAndExtension; - //assume entry.dest is the relative path to the folder AND file if applicable + //no dest, relative path, lives INSIDE rootDir } else { - dest = entry.dest; + //copy relative file structure to root of staging folder + let srcPathRelative = util.stringReplaceInsensitive(entrySrcPathAbsolute, rootDir, ''); + result = srcPathRelative; } - continue; - } - - //if src contains double wildcard - if (entry.src.includes('**')) { - //run the glob lookup - srcPathAbsolute = util.standardizePath(srcPathAbsolute); - //matches should retain structure relative to star star - let absolutePathToStarStar = path.resolve(rootDir, entry.src.split('**')[0]); - let srcPathRelative = util.stringReplaceInsensitive(srcPathAbsolute, absolutePathToStarStar, ''); - - dest = entry.dest ? entry.dest : ''; - dest = util.standardizePath(`${dest}/${srcPathRelative}`); - continue; + //assume entry.dest is the relative path to the folder AND file if applicable + } else if (entry.dest === '') { + result = fileNameAndExtension; + } else { + result = entry.dest; } - - //src is some other type of glob - { - let fileNameAndExtension = path.basename(srcPathAbsolute); - dest = entry.dest ? entry.dest : ''; - dest = util.standardizePath(`${dest}/${fileNameAndExtension}`); - continue; + //has a globstar + } else if ((globstarIdx = entry.src.indexOf('**')) > -1) { + const rootGlobstarPath = path.resolve(rootDir, entry.src.substring(0, globstarIdx)) + path.sep; + const srcPathRelative = util.stringReplaceInsensitive(srcPath, rootGlobstarPath, ''); + if (entry.dest) { + result = `${entry.dest}/${srcPathRelative}`; + } else { + result = srcPathRelative; } + + //`pattern` is some other glob magic + } else { + const fileNameAndExtension = path.basename(srcPath); + result = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); } - //remove any leading slash - dest = typeof dest === 'string' ? dest.replace(/^[\/\\]*/, '') : undefined; - return dest; + + result = util.standardizePath( + //remove leading slashes + result.replace(/^[\/\\]+/, '') + ); + return result; } /** diff --git a/src/util.spec.ts b/src/util.spec.ts index f0e81a4..b9c8626 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -1,7 +1,14 @@ -import { util } from './util'; +import { util, standardizePath as s } from './util'; import { expect } from 'chai'; +import * as fsExtra from 'fs-extra'; +import { tempDir } from './testUtils.spec'; +import * as path from 'path'; describe('util', () => { + beforeEach(() => { + fsExtra.emptyDirSync(tempDir); + }); + describe('isFile', () => { it('recognizes valid files', async () => { expect(await util.isFile(util.standardizePath(`${process.cwd()}/README.md`))).to.be.true; @@ -16,7 +23,12 @@ describe('util', () => { expect(util.toForwardSlashes(undefined)).to.be.undefined; expect(util.toForwardSlashes(false)).to.be.false; }); + + it('converts mixed slashes to forward', () => { + expect(util.toForwardSlashes('a\\b/c\\d/e')).to.eql('a/b/c/d/e'); + }); }); + describe('isChildOfPath', () => { it('works for child path', () => { let parentPath = `${process.cwd()}\\testProject`; @@ -85,4 +97,138 @@ describe('util', () => { expect(error).to.exist; }); }); + + describe('globAllByIndex', () => { + function writeFiles(filePaths: string[], cwd = tempDir) { + for (const filePath of filePaths) { + fsExtra.outputFileSync( + path.resolve(cwd, filePath), + '' + ); + } + } + + async function doTest(patterns: string[], expectedPaths: string[][]) { + const results = await util.globAllByIndex(patterns, tempDir); + for (let i = 0; i < results.length; i++) { + results[i] = results[i]?.map(x => s(x))?.sort(); + } + for (let i = 0; i < expectedPaths.length; i++) { + expectedPaths[i] = expectedPaths[i]?.map(x => { + return s`${path.resolve(tempDir, x)}`; + })?.sort(); + } + expect(results).to.eql(expectedPaths); + } + + it('finds direct file paths', async () => { + writeFiles([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ]); + await doTest([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ], [ + [ + 'manifest' + ], [ + 'source/main.brs' + ], [ + 'components/Component1/lib.brs' + ] + ]); + }); + + it('matches the wildcard glob', async () => { + writeFiles([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ]); + await doTest([ + '**/*' + ], [ + [ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ] + ]); + }); + + it('returns the same file path in multiple matches', async () => { + writeFiles([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ]); + await doTest([ + 'manifest', + 'source/main.brs', + 'manifest', + 'source/main.brs' + ], [ + [ + 'manifest' + ], [ + 'source/main.brs' + ], [ + 'manifest' + ], [ + 'source/main.brs' + ] + ]); + }); + + it('filters files', async () => { + writeFiles([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ]); + await doTest([ + '**/*', + //filter out brs files + '!**/*.brs' + ], [ + [ + 'manifest' + ], + null + ]); + }); + + it('filters files and adds them back in later', async () => { + writeFiles([ + 'manifest', + 'source/main.brs', + 'components/Component1/lib.brs' + ]); + await doTest([ + '**/*', + //filter out brs files + '!**/*.brs', + //re-add the main file + '**/main.brs' + ], [ + [ + 'manifest' + ], + undefined, + [ + 'source/main.brs' + ] + ]); + }); + }); + + describe('filterPaths', () => { + it('does not crash with bad params', () => { + //shouldn't crash + util['filterPaths']('*', [], '', 2); + }); + }); }); diff --git a/src/util.ts b/src/util.ts index 1f00b39..6ac1e5e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,16 +1,22 @@ +/* eslint-disable */ import * as fsExtra from 'fs-extra'; import * as path from 'path'; import * as fs from 'fs'; +import * as picomatch from 'picomatch'; +import fastGlob = require("fast-glob") export class Util { /** * Determine if `childPath` is contained within the `parentPath` * @param parentPath * @param childPath + * @param standardizePaths if false, the paths are assumed to already be in the same format and are not re-standardized */ - public isParentOfPath(parentPath: string, childPath: string) { - parentPath = util.standardizePath(parentPath); - childPath = util.standardizePath(childPath); + public isParentOfPath(parentPath: string, childPath: string, standardizePaths = true) { + if (standardizePaths) { + parentPath = util.standardizePath(parentPath); + childPath = util.standardizePath(childPath); + } const relative = path.relative(parentPath, childPath); return relative && !relative.startsWith('..') && !path.isAbsolute(relative); } @@ -124,6 +130,65 @@ export class Util { } return false; } + + /** + * Run a series of glob patterns, returning the matches in buckets corresponding to their pattern index. + */ + public async globAllByIndex(patterns: string[], cwd: string) { + //force all path separators to unit style + cwd = cwd.replace(/\\/g, '/'); + + const globResults = patterns.map(async (pattern) => { + //force all windows-style slashes to unix style + pattern = pattern.replace(/\\/g, '/'); + //skip negated patterns (we will use them to filter later on) + if (pattern.startsWith('!')) { + return pattern; + } else { + //run glob matcher + + return fastGlob([pattern], { + cwd: cwd, + absolute: true, + followSymbolicLinks: true, + onlyFiles: true, + }); + } + }); + + const matchesByIndex: Array> = []; + + for (let i = 0; i < globResults.length; i++) { + const globResult = await globResults[i]; + //if the matches collection is missing, this is a filter + if (typeof globResult === 'string') { + this.filterPaths(globResult, matchesByIndex, cwd, i - 1); + matchesByIndex.push(undefined); + } else { + matchesByIndex.push(globResult); + } + } + return matchesByIndex; + } + + /** + * Filter all of the matches based on a minimatch pattern + * @param stopIndex the max index of `matchesByIndex` to filter until + * @param pattern - the pattern used to filter out entries from `matchesByIndex`. Usually preceeded by a `!` + */ + private filterPaths(pattern: string, filesByIndex: string[][], cwd: string, stopIndex: number) { + //move the ! to the start of the string to negate the absolute path, replace windows slashes with unix ones + let negatedPatternAbsolute = '!' + path.posix.join(cwd, pattern.replace(/^!/, '')); + let filter = picomatch(negatedPatternAbsolute); + for (let i = 0; i <= stopIndex; i++) { + if (filesByIndex[i]) { + //filter all matches by the specified pattern + filesByIndex[i] = filesByIndex[i].filter(x => { + return filter(x); + }); + } + } + } } export let util = new Util();